| use eframe::egui; |
| use rfd::FileDialog; |
| use std::path::PathBuf; |
| use crate::engine::backtester::BacktestConfig; |
|
|
| pub struct BacktestPanel { |
| pub initial_deposit_str: String, |
| pub leverage_str: String, |
| pub spread_modifier_str: String, |
| pub selected_symbols: Vec<String>, |
| pub new_symbol_input: String, |
| pub loaded_mql5_path: Option<PathBuf>, |
| pub compiler_log: String, |
| pub is_running: bool, |
| } |
|
|
| impl BacktestPanel { |
| pub fn new() -> Self { |
| Self { |
| initial_deposit_str: "10000.0".to_string(), |
| leverage_str: "100.0".to_string(), |
| spread_modifier_str: "0".to_string(), |
| selected_symbols: vec!["EURUSD".to_string()], |
| new_symbol_input: "".to_string(), |
| loaded_mql5_path: None, |
| compiler_log: "Ready.".to_string(), |
| is_running: false, |
| } |
| } |
|
|
| pub fn draw(&mut self, ui: &mut egui::Ui) -> Option<BacktestConfig> { |
| ui.heading("⚙️ Backtest Configuration"); |
| ui.separator(); |
|
|
| |
| ui.group(|ui| { |
| ui.label("Account Settings"); |
| egui::Grid::new("config_grid").num_columns(2).spacing([10.0, 5.0]).show(ui, |ui| { |
| ui.label("Initial Deposit ($):"); |
| ui.text_edit_singleline(&mut self.initial_deposit_str); |
| ui.end_row(); |
|
|
| ui.label("Leverage (1:X):"); |
| ui.text_edit_singleline(&mut self.leverage_str); |
| ui.end_row(); |
|
|
| ui.label("Spread Modifier (points):"); |
| ui.text_edit_singleline(&mut self.spread_modifier_str); |
| ui.end_row(); |
| }); |
| }); |
|
|
| ui.add_space(10.0); |
|
|
| |
| ui.group(|ui| { |
| ui.label("Symbols for Backtest"); |
| ui.horizontal(|ui| { |
| ui.text_edit_singleline(&mut self.new_symbol_input); |
| if ui.button("Add").clicked() { |
| if !self.new_symbol_input.is_empty() && !self.selected_symbols.contains(&self.new_symbol_input) { |
| self.selected_symbols.push(self.new_symbol_input.clone()); |
| self.new_symbol_input.clear(); |
| } |
| } |
| }); |
| |
| ui.horizontal_wrapped(|ui| { |
| let mut to_remove = None; |
| for (i, sym) in self.selected_symbols.iter().enumerate() { |
| ui.group(|ui| { |
| ui.horizontal(|ui| { |
| ui.label(sym); |
| if ui.small_button("x").clicked() { |
| to_remove = Some(i); |
| } |
| }); |
| }); |
| } |
| if let Some(i) = to_remove { |
| self.selected_symbols.remove(i); |
| } |
| }); |
| }); |
|
|
| ui.add_space(10.0); |
|
|
| |
| ui.group(|ui| { |
| ui.label("MQL5 Strategy Code"); |
| ui.horizontal(|ui| { |
| if ui.button("📂 Load .mq5 File").clicked() { |
| if let Some(path) = FileDialog::new() |
| .add_filter("MQL5 Source", &["mq5"]) |
| .pick_file() |
| { |
| self.loaded_mql5_path = Some(path); |
| self.compiler_log = "File loaded. Ready to compile.".to_string(); |
| } |
| } |
| if let Some(p) = &self.loaded_mql5_path { |
| ui.label(p.display().to_string()); |
| } else { |
| ui.label("No file selected."); |
| } |
| }); |
| }); |
|
|
| ui.add_space(10.0); |
|
|
| |
| ui.horizontal(|ui| { |
| if self.is_running { |
| ui.add_enabled(false, egui::Button::new("Running Backtest...")); |
| } else { |
| if ui.button("🚀 Compile & Run Backtest").clicked() { |
| if self.loaded_mql5_path.is_none() { |
| self.compiler_log = "Error: Please load an .mq5 file first.".to_string(); |
| return None; |
| } |
| if self.selected_symbols.is_empty() { |
| self.compiler_log = "Error: Please add at least one symbol.".to_string(); |
| return None; |
| } |
|
|
| if let (Ok(dep), Ok(lev), Ok(spr)) = ( |
| self.initial_deposit_str.parse::<f64>(), |
| self.leverage_str.parse::<f64>(), |
| self.spread_modifier_str.parse::<u32>(), |
| ) { |
| self.compiler_log = "Starting compilation...".to_string(); |
| self.is_running = true; |
| |
| return Some(BacktestConfig { |
| initial_deposit: dep, |
| leverage: lev, |
| spread_modifier: spr, |
| }); |
| } else { |
| self.compiler_log = "Error: Invalid numeric configuration fields.".to_string(); |
| } |
| } |
| } |
| }); |
|
|
| ui.add_space(5.0); |
| |
| |
| ui.group(|ui| { |
| ui.set_min_height(60.0); |
| ui.label(&self.compiler_log); |
| }); |
|
|
| None |
| } |
| } |
|
|