| | use eframe::egui;
|
| | use egui_plot::{Line, Plot, PlotPoints};
|
| | use serde::Deserialize;
|
| | use std::sync::Arc;
|
| | use tokio::sync::mpsc;
|
| | use zeromq::{Socket, SocketRecv};
|
| |
|
| | #[derive(Clone, Debug, Deserialize)]
|
| | struct TickData {
|
| | symbol: String,
|
| | bid: f64,
|
| | ask: f64,
|
| | time: i64,
|
| | }
|
| |
|
| | struct Mt5ChartApp {
|
| | receiver: mpsc::Receiver<TickData>,
|
| | data: Vec<TickData>,
|
| | symbol: String,
|
| | }
|
| |
|
| | impl Mt5ChartApp {
|
| | fn new(receiver: mpsc::Receiver<TickData>) -> Self {
|
| | Self {
|
| | receiver,
|
| | data: Vec::new(),
|
| | symbol: "Waiting for data...".to_string(),
|
| | }
|
| | }
|
| | }
|
| |
|
| | impl eframe::App for Mt5ChartApp {
|
| | fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
| |
|
| | while let Ok(tick) = self.receiver.try_recv() {
|
| | self.symbol = tick.symbol.clone();
|
| | self.data.push(tick);
|
| |
|
| | if self.data.len() > 1000 {
|
| | self.data.remove(0);
|
| | }
|
| | }
|
| |
|
| | egui::CentralPanel::default().show(ctx, |ui| {
|
| | ui.heading(format!("MT5 Live Chart: {}", self.symbol));
|
| | if let Some(last_tick) = self.data.last() {
|
| | ui.label(format!("Bid: {:.5} | Ask: {:.5}", last_tick.bid, last_tick.ask));
|
| | }
|
| |
|
| | let plot = Plot::new("mt5_plot")
|
| | .view_aspect(2.0)
|
| | .legend(egui_plot::Legend::default());
|
| |
|
| | plot.show(ui, |plot_ui| {
|
| | let bid_points: PlotPoints = self.data
|
| | .iter()
|
| | .enumerate()
|
| | .map(|(i, t)| [i as f64, t.bid])
|
| | .collect();
|
| |
|
| | let ask_points: PlotPoints = self.data
|
| | .iter()
|
| | .enumerate()
|
| | .map(|(i, t)| [i as f64, t.ask])
|
| | .collect();
|
| |
|
| | plot_ui.line(Line::new(bid_points).name("Bid").color(egui::Color32::from_rgb(100, 200, 100)));
|
| | plot_ui.line(Line::new(ask_points).name("Ask").color(egui::Color32::from_rgb(200, 100, 100)));
|
| | });
|
| | });
|
| |
|
| |
|
| | ctx.request_repaint();
|
| | }
|
| | }
|
| |
|
| | #[tokio::main]
|
| | async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
| | let (tx, rx) = mpsc::channel(100);
|
| |
|
| |
|
| | tokio::spawn(async move {
|
| |
|
| | let mut socket = zeromq::SubSocket::new();
|
| | match socket.connect("tcp://127.0.0.1:5555").await {
|
| | Ok(_) => println!("Connected to ZMQ Publisher"),
|
| | Err(e) => eprintln!("Failed to connect to ZMQ: {}", e),
|
| | }
|
| |
|
| | let _ = socket.subscribe("").await;
|
| |
|
| | loop {
|
| | match socket.recv().await {
|
| | Ok(msg) => {
|
| |
|
| |
|
| | if let Some(payload_bytes) = msg.get(0) {
|
| | if let Ok(json_str) = std::str::from_utf8(payload_bytes) {
|
| |
|
| |
|
| | match serde_json::from_str::<TickData>(json_str) {
|
| | Ok(tick) => {
|
| | if let Err(e) = tx.send(tick).await {
|
| | eprintln!("Channel error: {}", e);
|
| | break;
|
| | }
|
| | }
|
| | Err(e) => eprintln!("JSON Parse Error: {}. Msg: {}", e, json_str),
|
| | }
|
| | }
|
| | }
|
| | }
|
| | Err(e) => {
|
| | eprintln!("ZMQ Recv Error: {}", e);
|
| | tokio::time::sleep(tokio::time::Duration::from_millis(1000)).await;
|
| | }
|
| | }
|
| | }
|
| | });
|
| |
|
| | let options = eframe::NativeOptions::default();
|
| | eframe::run_native(
|
| | "Rust + ZMQ + MT5's MQL5 Chart",
|
| | options,
|
| | Box::new(|_cc| Box::new(Mt5ChartApp::new(rx))),
|
| | ).map_err(|e| e.into())
|
| | }
|
| |
|