参数优化优化向前优化

This commit is contained in:
2026-07-06 04:46:30 +08:00
parent 15716894bc
commit 04be8aee9a
4 changed files with 128 additions and 13 deletions
+118 -9
View File
@@ -1507,20 +1507,28 @@ class MT5BacktestGUI:
self.opt_leverage.set("1:100")
self.opt_leverage.pack(side='left', padx=5)
wf_frame = ttk.LabelFrame(test_frame, text="Walk-Forward 防过拟合")
wf_frame = ttk.LabelFrame(test_frame, text="向前优化 (Forward)")
wf_frame.pack(fill='x', padx=5, pady=5)
wf_row1 = ttk.Frame(wf_frame)
wf_row1.pack(fill='x', padx=5, pady=3)
self.wf_enabled = tk.BooleanVar(value=False)
ttk.Checkbutton(wf_row1, text="启用Walk-Forward", variable=self.wf_enabled).pack(side='left', padx=5)
ttk.Checkbutton(wf_row1, text="启用向前优化", variable=self.wf_enabled, command=self._update_wf_ui_state).pack(side='left', padx=5)
ttk.Label(wf_row1, text="OOS比例:").pack(side='left', padx=5)
self.wf_mode = ttk.Combobox(wf_row1, width=12, values=['1/2 (50%)', '1/3 (33%)', '1/4 (25%)'])
ttk.Label(wf_row1, text="模式:").pack(side='left', padx=5)
self.wf_mode = ttk.Combobox(wf_row1, width=18, values=['1/2 (50%)', '1/3 (33%)', '1/4 (25%)', '自定义日期 (4)'])
self.wf_mode.set('1/3 (33%)')
self.wf_mode.pack(side='left', padx=5)
self.wf_mode.bind('<<ComboboxSelected>>', lambda e: self._update_wf_ui_state())
self.wf_mode_map = {'1/2 (50%)': 1, '1/3 (33%)': 2, '1/4 (25%)': 3}
ttk.Label(wf_row1, text="ForwardDate:").pack(side='left', padx=(10, 5))
self.wf_custom_date = ttk.Entry(wf_row1, width=12)
self.wf_custom_date.insert(0, "")
self.wf_custom_date.pack(side='left', padx=5)
self.wf_mode_map = {'1/2 (50%)': 1, '1/3 (33%)': 2, '1/4 (25%)': 3, '自定义日期 (4)': 4}
self.wf_mode_reverse_map = {v: k for k, v in self.wf_mode_map.items()}
self._update_wf_ui_state()
action_frame = ttk.Frame(opt_frame)
action_frame.pack(fill='x', padx=5, pady=5)
@@ -1693,6 +1701,75 @@ class MT5BacktestGUI:
config_path = os.path.join(configs_dir, ea_name + '.json')
if os.path.exists(config_path):
config = load_ea_config(config_path)
test_cfg = config.test_config or {}
wf_cfg = config.walk_forward or {}
if test_cfg.get('symbol'):
self.opt_symbol.set(str(test_cfg.get('symbol')))
if test_cfg.get('period'):
self.opt_period.set(str(test_cfg.get('period')))
if 'model' in test_cfg:
model_val = int(test_cfg.get('model', 1))
model_map = {
0: 'Every Tick (0)',
1: '1分钟OHLC (1)',
2: '仅开盘价 (2)',
3: '数学计算 (3)',
4: '真实Tick (4)',
}
self.opt_model.set(model_map.get(model_val, '1分钟OHLC (1)'))
if test_cfg.get('from_date'):
self.opt_from_date.delete(0, tk.END)
self.opt_from_date.insert(0, str(test_cfg.get('from_date')))
if test_cfg.get('to_date'):
self.opt_to_date.delete(0, tk.END)
self.opt_to_date.insert(0, str(test_cfg.get('to_date')))
if 'deposit' in test_cfg:
self.opt_deposit.delete(0, tk.END)
self.opt_deposit.insert(0, str(test_cfg.get('deposit')))
if test_cfg.get('leverage'):
self.opt_leverage.set(str(test_cfg.get('leverage')))
exec_delay = int(test_cfg.get('execution_delay', 0) or 0)
if exec_delay == 0:
self.opt_delay_type.set('无延迟 (0)')
elif exec_delay == -1:
self.opt_delay_type.set('随机延迟 (-1)')
else:
self.opt_delay_type.set('固定延迟')
self.opt_delay_value.delete(0, tk.END)
self.opt_delay_value.insert(0, str(exec_delay))
opt_mode = int(test_cfg.get('optimization_mode', 2) or 2)
opt_mode_map = {
0: '禁用 (0)',
1: '慢速完整算法 (1)',
2: '快速遗传算法 (2)',
3: 'MarketWatch所有符号 (3)',
}
self.opt_optimization_mode.set(opt_mode_map.get(opt_mode, '快速遗传算法 (2)'))
opt_criterion = int(test_cfg.get('optimization_criterion', 1) or 1)
criterion_map = {
0: '余额最大值 (0)',
1: '盈利因子最大值 (1)',
2: '期望收益 (2)',
3: '回撤最小值 (3)',
4: '恢复因子 (4)',
5: '夏普比率 (5)',
6: '自定义 (6)',
7: '复合指标 (7)',
}
self.opt_criterion.set(criterion_map.get(opt_criterion, '盈利因子最大值 (1)'))
wf_enabled = bool(wf_cfg.get('enabled', False))
self.wf_enabled.set(wf_enabled)
wf_mode = int(wf_cfg.get('forward_mode', 2) or 2)
self.wf_mode.set(self.wf_mode_reverse_map.get(wf_mode, '1/3 (33%)'))
self.wf_custom_date.delete(0, tk.END)
self.wf_custom_date.insert(0, str(wf_cfg.get('forward_date', '') or ''))
self._update_wf_ui_state()
if config.parameters:
for pname, param in config.parameters.items():
optimize = 'Y' if param.optimize else 'N'
@@ -1744,11 +1821,25 @@ class MT5BacktestGUI:
entry.bind('<Escape>', lambda e: entry.destroy())
entry.place(x=event.x, y=event.y, width=100)
def _save_ea_config(self):
def _update_wf_ui_state(self):
"""ForwardDate 仅在启用向前优化且选择自定义日期(4)时可编辑。"""
enabled = self.wf_enabled.get()
self.wf_mode.configure(state='readonly' if enabled else 'disabled')
mode_val = self.wf_mode_map.get(self.wf_mode.get(), 2) if enabled else 0
self.wf_custom_date.configure(state='normal' if (enabled and mode_val == 4) else 'disabled')
def _save_ea_config(self, quiet=False):
ea_name = self.opt_ea_var.get()
if not ea_name:
messagebox.showwarning('警告', '请先选择一个EA')
return
if not quiet:
messagebox.showwarning('警告', '请先选择一个EA')
return False
wf_mode_val = self.wf_mode_map.get(self.wf_mode.get(), 2) if self.wf_enabled.get() else 0
wf_forward_date = self.wf_custom_date.get().strip()
if self.wf_enabled.get() and wf_mode_val == 4 and not wf_forward_date:
if not quiet:
messagebox.showwarning('警告', 'ForwardMode=自定义日期 (4) 时,必须填写 ForwardDate (YYYY.MM.DD)')
return False
configs_dir = os.path.join(self.opt_dir.get(), 'configs')
os.makedirs(configs_dir, exist_ok=True)
params = {}
@@ -1793,12 +1884,16 @@ class MT5BacktestGUI:
},
'walk_forward': {
'enabled': self.wf_enabled.get(),
'forward_mode': self.wf_mode_map.get(self.wf_mode.get(), 2)
'forward_mode': wf_mode_val,
'forward_date': wf_forward_date if (self.wf_enabled.get() and wf_mode_val == 4) else ''
}
}
with open(config_path, 'w', encoding='utf-8') as f:
json.dump(config_data, f, indent=4, ensure_ascii=False)
self.opt_log.insert('end', f"配置已保存: {config_path}\n")
if not quiet:
self.opt_log.update()
return True
def _get_opt_execution_delay(self):
delay_type = self.opt_delay_type.get()
@@ -1977,6 +2072,13 @@ class MT5BacktestGUI:
if not ea_name:
messagebox.showwarning('警告', '请先选择一个EA')
return
if not self._save_ea_config(quiet=True):
messagebox.showwarning('警告', '当前优化配置无效,请检查 ForwardDate 或先选择 EA')
return
wf_mode_val = self.wf_mode_map.get(self.wf_mode.get(), 2) if self.wf_enabled.get() else 0
if self.wf_enabled.get() and wf_mode_val == 4 and not self.wf_custom_date.get().strip():
messagebox.showwarning('警告', '当前选择了自定义向前优化日期,请先填写 ForwardDate 并保存配置')
return
mt5_path = self.mt5_path.get()
if not mt5_path or not os.path.isfile(mt5_path):
messagebox.showerror('错误', f'MT5终端路径无效: {mt5_path!r}\n请在"回测配置"标签页用"浏览..."按钮设置 terminal64.exe')
@@ -2014,6 +2116,13 @@ class MT5BacktestGUI:
def _run_batch_optimize(self):
_gui_log("点击: 批量执行优化")
if not self._save_ea_config(quiet=True):
messagebox.showwarning('警告', '当前优化配置无效,请检查 ForwardDate 或先选择 EA')
return
wf_mode_val = self.wf_mode_map.get(self.wf_mode.get(), 2) if self.wf_enabled.get() else 0
if self.wf_enabled.get() and wf_mode_val == 4 and not self.wf_custom_date.get().strip():
messagebox.showwarning('警告', '当前选择了自定义向前优化日期,请先填写 ForwardDate 并保存配置')
return
mt5_path = self.mt5_path.get()
if not mt5_path or not os.path.isfile(mt5_path):
messagebox.showerror('错误', f'MT5终端路径无效: {mt5_path!r}\n请在"回测配置"标签页用"浏览..."按钮设置 terminal64.exe')
+2 -2
View File
@@ -35,7 +35,7 @@ class EAAutoScanner:
"from_date": "2025.01.01", "to_date": "2025.12.31",
"model": 1, "deposit": 10000, "leverage": "1:100"
},
"walk_forward": {"enabled": False}
"walk_forward": {"enabled": False, "forward_mode": 0, "forward_date": ""}
}
if params:
for pname, pinfo in params.items():
@@ -84,4 +84,4 @@ if __name__ == "__main__":
fname = cfg["ea_name"].replace(" ", "_") + ".json"
with open(os.path.join(args.output, fname), "w", encoding="utf-8") as f:
json.dump(cfg, f, indent=4, ensure_ascii=False)
print(f"Saved: {fname}")
print(f"Saved: {fname}")
+6 -1
View File
@@ -168,6 +168,11 @@ class EABatchOptimizer:
if wf.get('enabled', False):
fm = wf.get('forward_mode', 2)
ini_lines.append('ForwardMode=' + str(fm))
if fm == 4:
fwd = str(wf.get('forward_date', '') or '').strip()
if not fwd:
raise ValueError('walk_forward.forward_mode=4 时必须提供 walk_forward.forward_date (YYYY.MM.DD)')
ini_lines.append('ForwardDate=' + fwd)
else:
ini_lines.append('ForwardMode=0')
@@ -328,4 +333,4 @@ def main():
if __name__ == '__main__':
main()
main()
+2 -1
View File
@@ -283,7 +283,8 @@ def generate_prompt(results: List[SetFileResult], output_dir: str = '.') -> List
lines.append(' },')
lines.append(' "walk_forward": {')
lines.append(' "enabled": true, // 是否启用前向测试')
lines.append(' "forward_mode": 2 // 前向模式: 0=禁用, 1=OOS 50%, 2=OOS 33%, 3=OOS 25%')
lines.append(' "forward_mode": 2, // 前向模式: 0=禁用, 1=OOS 50%, 2=OOS 33%, 3=OOS 25%, 4=自定义日期')
lines.append(' "forward_date": "" // 仅当 forward_mode=4 时必填, 格式 YYYY.MM.DD')
lines.append(' }')
lines.append('}')
lines.append('```')