参数优化报告自动保存

This commit is contained in:
2026-07-06 04:55:21 +08:00
parent 04be8aee9a
commit e49bdb1116
2 changed files with 70 additions and 26 deletions
+42 -12
View File
@@ -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")
+28 -14
View File
@@ -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
return result