参数优化优化向前优化

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.set("1:100")
self.opt_leverage.pack(side='left', padx=5) 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_frame.pack(fill='x', padx=5, pady=5)
wf_row1 = ttk.Frame(wf_frame) wf_row1 = ttk.Frame(wf_frame)
wf_row1.pack(fill='x', padx=5, pady=3) wf_row1.pack(fill='x', padx=5, pady=3)
self.wf_enabled = tk.BooleanVar(value=False) 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) ttk.Label(wf_row1, text="模式:").pack(side='left', padx=5)
self.wf_mode = ttk.Combobox(wf_row1, width=12, values=['1/2 (50%)', '1/3 (33%)', '1/4 (25%)']) 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.set('1/3 (33%)')
self.wf_mode.pack(side='left', padx=5) 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 = ttk.Frame(opt_frame)
action_frame.pack(fill='x', padx=5, pady=5) 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') config_path = os.path.join(configs_dir, ea_name + '.json')
if os.path.exists(config_path): if os.path.exists(config_path):
config = load_ea_config(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: if config.parameters:
for pname, param in config.parameters.items(): for pname, param in config.parameters.items():
optimize = 'Y' if param.optimize else 'N' optimize = 'Y' if param.optimize else 'N'
@@ -1744,11 +1821,25 @@ class MT5BacktestGUI:
entry.bind('<Escape>', lambda e: entry.destroy()) entry.bind('<Escape>', lambda e: entry.destroy())
entry.place(x=event.x, y=event.y, width=100) 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() ea_name = self.opt_ea_var.get()
if not ea_name: if not ea_name:
messagebox.showwarning('警告', '请先选择一个EA') if not quiet:
return 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') configs_dir = os.path.join(self.opt_dir.get(), 'configs')
os.makedirs(configs_dir, exist_ok=True) os.makedirs(configs_dir, exist_ok=True)
params = {} params = {}
@@ -1793,12 +1884,16 @@ class MT5BacktestGUI:
}, },
'walk_forward': { 'walk_forward': {
'enabled': self.wf_enabled.get(), '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: with open(config_path, 'w', encoding='utf-8') as f:
json.dump(config_data, f, indent=4, ensure_ascii=False) json.dump(config_data, f, indent=4, ensure_ascii=False)
self.opt_log.insert('end', f"配置已保存: {config_path}\n") self.opt_log.insert('end', f"配置已保存: {config_path}\n")
if not quiet:
self.opt_log.update()
return True
def _get_opt_execution_delay(self): def _get_opt_execution_delay(self):
delay_type = self.opt_delay_type.get() delay_type = self.opt_delay_type.get()
@@ -1977,6 +2072,13 @@ class MT5BacktestGUI:
if not ea_name: if not ea_name:
messagebox.showwarning('警告', '请先选择一个EA') messagebox.showwarning('警告', '请先选择一个EA')
return 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() mt5_path = self.mt5_path.get()
if not mt5_path or not os.path.isfile(mt5_path): if not mt5_path or not os.path.isfile(mt5_path):
messagebox.showerror('错误', f'MT5终端路径无效: {mt5_path!r}\n请在"回测配置"标签页用"浏览..."按钮设置 terminal64.exe') messagebox.showerror('错误', f'MT5终端路径无效: {mt5_path!r}\n请在"回测配置"标签页用"浏览..."按钮设置 terminal64.exe')
@@ -2014,6 +2116,13 @@ class MT5BacktestGUI:
def _run_batch_optimize(self): def _run_batch_optimize(self):
_gui_log("点击: 批量执行优化") _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() mt5_path = self.mt5_path.get()
if not mt5_path or not os.path.isfile(mt5_path): if not mt5_path or not os.path.isfile(mt5_path):
messagebox.showerror('错误', f'MT5终端路径无效: {mt5_path!r}\n请在"回测配置"标签页用"浏览..."按钮设置 terminal64.exe') messagebox.showerror('错误', f'MT5终端路径无效: {mt5_path!r}\n请在"回测配置"标签页用"浏览..."按钮设置 terminal64.exe')
+1 -1
View File
@@ -35,7 +35,7 @@ class EAAutoScanner:
"from_date": "2025.01.01", "to_date": "2025.12.31", "from_date": "2025.01.01", "to_date": "2025.12.31",
"model": 1, "deposit": 10000, "leverage": "1:100" "model": 1, "deposit": 10000, "leverage": "1:100"
}, },
"walk_forward": {"enabled": False} "walk_forward": {"enabled": False, "forward_mode": 0, "forward_date": ""}
} }
if params: if params:
for pname, pinfo in params.items(): for pname, pinfo in params.items():
+5
View File
@@ -168,6 +168,11 @@ class EABatchOptimizer:
if wf.get('enabled', False): if wf.get('enabled', False):
fm = wf.get('forward_mode', 2) fm = wf.get('forward_mode', 2)
ini_lines.append('ForwardMode=' + str(fm)) 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: else:
ini_lines.append('ForwardMode=0') ini_lines.append('ForwardMode=0')
+2 -1
View File
@@ -283,7 +283,8 @@ def generate_prompt(results: List[SetFileResult], output_dir: str = '.') -> List
lines.append(' },') lines.append(' },')
lines.append(' "walk_forward": {') lines.append(' "walk_forward": {')
lines.append(' "enabled": true, // 是否启用前向测试') 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('}') lines.append('}')
lines.append('```') lines.append('```')