diff --git a/gui.py b/gui.py index d216f58..b9f3c0c 100644 --- a/gui.py +++ b/gui.py @@ -168,6 +168,31 @@ def _launch_mt5(mt5_path, ini_path, data_dir, log_lines: list = None): return process +def _find_latest_optimizer_xml(search_dirs, ea_name): + """在多个候选目录里找最新的优化 XML 结果。""" + patterns = [ + f'{ea_name}_optimization.xml', + f'{ea_name}_optimization[1].xml', + f'{ea_name}_optimization[2].xml', + ] + latest = None + latest_mtime = 0 + for base in search_dirs: + if not base or not os.path.isdir(base): + continue + for pattern in patterns: + p = os.path.join(base, pattern) + if os.path.exists(p): + try: + mt = os.path.getmtime(p) + except OSError: + continue + if mt > latest_mtime: + latest_mtime = mt + latest = p + return latest + + class MT5BacktestGUI: _CONFIG_FILE = "config/ea_configs.yaml" # 仅给"回测配置"标签页用 _MT5_STATE_FILE = "optimizer/mt5_state.json" # 优化器用的 MT5 路径,独立小文件 @@ -2175,16 +2200,17 @@ class MT5BacktestGUI: self.opt_log.insert('end', "启动失败,跳过该EA\n") continue - xml_name = ea_name + '_optimization.xml' - xml_path = os.path.join(mt5_data_dir, xml_name) + mt5_install_dir = os.path.dirname(mt5_path) + xml_path = _find_latest_optimizer_xml([mt5_data_dir, mt5_install_dir], ea_name) - self.opt_log.insert('end', f"等待XML结果: {xml_path}\n") + self.opt_log.insert('end', f"等待XML结果,候选目录: {mt5_data_dir} | {mt5_install_dir}\n") self.opt_log.update() wait_timeout = 600 start_time = time.time() while time.time() - start_time < wait_timeout: - if os.path.exists(xml_path): + xml_path = _find_latest_optimizer_xml([mt5_data_dir, mt5_install_dir], ea_name) + if xml_path and os.path.exists(xml_path): time.sleep(2) try: with open(xml_path, 'r', encoding='utf-8') as f: @@ -2197,14 +2223,16 @@ class MT5BacktestGUI: time.sleep(5) if process.poll() is not None: - if os.path.exists(xml_path): + xml_path = _find_latest_optimizer_xml([mt5_data_dir, mt5_install_dir], ea_name) + if xml_path and os.path.exists(xml_path): self.opt_log.insert('end', "MT5已退出,结果文件存在\n") self.opt_log.update() break self.opt_log.insert('end', "MT5已退出,等待结果文件...\n") self.opt_log.update() time.sleep(3) - if os.path.exists(xml_path): + xml_path = _find_latest_optimizer_xml([mt5_data_dir, mt5_install_dir], ea_name) + if xml_path and os.path.exists(xml_path): break if process.poll() is None: @@ -2216,11 +2244,13 @@ class MT5BacktestGUI: except: pass - dst_xml = os.path.join(reports_dir, ea_name, xml_name) - if os.path.exists(xml_path): + dst_xml = os.path.join(reports_dir, ea_name, ea_name + '_optimization.xml') + if xml_path and os.path.exists(xml_path): os.makedirs(os.path.dirname(dst_xml), exist_ok=True) shutil.copy2(xml_path, dst_xml) self.opt_log.insert('end', f"已保存结果: {dst_xml}\n") + else: + self.opt_log.insert('end', f"未找到优化 XML,已检查: {mt5_data_dir} 与 {mt5_install_dir}\n") self.opt_log.insert('end', f"{ea_name} 优化完成\n") self.opt_log.update() @@ -2237,17 +2267,17 @@ class MT5BacktestGUI: from ea_batch_optimizer import EABatchOptimizer from analyze_results import ResultAnalyzer optimizer = EABatchOptimizer(self.opt_dir.get(), mt5_data_dir=self.data_dir.get()) - mt5_terminal_dir = self.data_dir.get() + mt5_search_dirs = [self.data_dir.get(), os.path.dirname(self.mt5_path.get())] self.opt_log.insert('end', "正在分析结果...\n") self.opt_log.update() results = [] - if mt5_terminal_dir: + if any(mt5_search_dirs): reports_dir = os.path.join(self.opt_dir.get(), 'reports', ea_name) analyzer = ResultAnalyzer(reports_dir) - xml_path = analyzer.copy_xml_results_to_reports(mt5_terminal_dir, ea_name, reports_dir) + xml_path = analyzer.copy_xml_results_to_reports(mt5_search_dirs, ea_name, reports_dir) if xml_path: self.opt_log.insert('end', f"从MT5复制结果: {xml_path}\n") xml_results = analyzer.parse_mt5_xml_optimization(xml_path) @@ -2311,7 +2341,7 @@ class MT5BacktestGUI: ea_cfg = json.load(f) wf = ea_cfg.get('walk_forward', {}) if wf.get('enabled'): - wf_result = analyzer.analyze_walk_forward(mt5_terminal_dir, ea_name, reports_dir) + wf_result = analyzer.analyze_walk_forward(mt5_search_dirs, ea_name, reports_dir) is_pf = wf_result.get('in_sample', {}).get('profit_factor', 0) oos_pf = wf_result.get('out_of_sample', {}).get('profit_factor', 0) self.opt_log.insert('end', f"In-Sample PF: {is_pf}\n") diff --git a/scripts/optimizer/analyze_results.py b/scripts/optimizer/analyze_results.py index 8e4298c..a288e45 100644 --- a/scripts/optimizer/analyze_results.py +++ b/scripts/optimizer/analyze_results.py @@ -161,7 +161,7 @@ class ResultAnalyzer: return results - def copy_xml_results_to_reports(self, mt5_terminal_dir: str, ea_name: str, reports_dir: str) -> str: + def copy_xml_results_to_reports(self, mt5_terminal_dir, ea_name: str, reports_dir: str) -> str: xml_patterns = [ f'{ea_name}_optimization.xml', f'{ea_name}_optimization[1].xml', @@ -172,13 +172,18 @@ class ResultAnalyzer: latest_xml = None latest_time = 0 - for pattern in xml_patterns: - xml_path = os.path.join(mt5_terminal_dir, pattern) - if os.path.exists(xml_path): - mtime = os.path.getmtime(xml_path) - if mtime > latest_time: - latest_time = mtime - latest_xml = xml_path + search_dirs = mt5_terminal_dir if isinstance(mt5_terminal_dir, (list, tuple)) else [mt5_terminal_dir] + + for base_dir in search_dirs: + if not base_dir or not os.path.isdir(base_dir): + continue + for pattern in xml_patterns: + xml_path = os.path.join(base_dir, pattern) + if os.path.exists(xml_path): + mtime = os.path.getmtime(xml_path) + if mtime > latest_time: + latest_time = mtime + latest_xml = xml_path if latest_xml: dest_path = os.path.join(reports_dir, os.path.basename(latest_xml)) @@ -269,7 +274,7 @@ class ResultAnalyzer: return output_path - def analyze_walk_forward(self, mt5_terminal_dir: str, ea_name: str, reports_dir: str) -> Dict: + def analyze_walk_forward(self, mt5_terminal_dir, ea_name: str, reports_dir: str) -> Dict: result = { 'ea_name': ea_name, 'in_sample': {}, @@ -286,11 +291,20 @@ class ResultAnalyzer: best_is = sorted(is_results, key=lambda x: x.get('profit_factor', 0), reverse=True)[0] result['in_sample'] = best_is - forward_xml = os.path.join(mt5_terminal_dir, f'{ea_name}_optimization.forward.xml') - if not os.path.exists(forward_xml): - forward_xml = os.path.join(mt5_terminal_dir, f'{ea_name}_optimization[1].forward.xml') + search_dirs = mt5_terminal_dir if isinstance(mt5_terminal_dir, (list, tuple)) else [mt5_terminal_dir] + forward_xml = None + for base_dir in search_dirs: + if not base_dir or not os.path.isdir(base_dir): + continue + for candidate in (f'{ea_name}_optimization.forward.xml', f'{ea_name}_optimization[1].forward.xml'): + p = os.path.join(base_dir, candidate) + if os.path.exists(p): + forward_xml = p + break + if forward_xml: + break - if os.path.exists(forward_xml): + if forward_xml and os.path.exists(forward_xml): oos_results = self.parse_mt5_xml_optimization(forward_xml) if oos_results: best_oos = sorted(oos_results, key=lambda x: x.get('profit_factor', 0), reverse=True)[0] @@ -317,4 +331,4 @@ class ResultAnalyzer: if is_dd > 0 and oos_dd > 0: result['dd_increase'] = round((oos_dd / is_dd - 1) * 100, 1) if is_dd > 0 else 0 - return result \ No newline at end of file + return result