63a829cc46
主要内容: - Phase 8 PROMOTE: finalist #1 (trial #324) registry 条目,自动生成 - Optuna objective warmup bug 修复 (shared/optimizer/objective.py) - studies/ 目录按用途重组为 optuna/ + finalists/ + features/ 三层 - reports/ 加入 Optuna 中文 dashboard (5 主图 + 18 slice + 15 contour) - 新增 PROJECT_GUIDE.md 项目说明文档 - 新增 build_registry_entry.py / build_optuna_dashboard.py / build_feature_datasets.py - .gitignore: 允许提交 studies/*.db (Optuna DB) 和 reports/*.html (MT5 + dashboard)
748 lines
39 KiB
Markdown
748 lines
39 KiB
Markdown
# PROJECT_GUIDE — Backtesting + Optuna + MT5 Stack 项目说明
|
||
|
||
> 这份文档是**项目实例说明**:它描述当前这个目录里实际跑通的那一套
|
||
> (GoldScalperPro EA × XAUUSD × IC Markets Demo)——架构为什么这么搭、开发过程踩过
|
||
> 的坑、日常怎么用、后续怎么扩展。它和 `README.md`/`01–08-*.md` 是互补关系:
|
||
> 那些是**知识库**(抽象架构 + 通用方法论),这份是**实例说明**(具体项目层面)。
|
||
>
|
||
> 如果你只是想跑一遍流程:跳到 §3「使用手册」。
|
||
> 如果你是接手维护:先读 §1「架构理念」+ §4「扩展指南」+ §5「踩坑列表」。
|
||
|
||
---
|
||
|
||
## 0. 项目一句话
|
||
|
||
一个把 MetaTrader 5 Strategy Tester 当作"金标准"、用 Python 镜像引擎做高速贝叶斯
|
||
搜索的个人量化策略研究实验室。当前已对 **GoldScalperPro** 这个 XAUUSD M5 EA
|
||
跑通完整的"假设 → 搜索 → MT5 验证 → 入注册表"闭环,并产出第一条 registry 记录。
|
||
|
||
---
|
||
|
||
## 1. 架构理念
|
||
|
||
### 1.1 两层模型(the two-tier design)
|
||
|
||
核心思想:**Python 排序,MT5 拍板**。
|
||
|
||
- **Tier 1(Python 镜像引擎)**:把 EA 的填单/出场逻辑 bar-by-bar 重写一遍,跑在
|
||
Parquet 历史数据上。全 2 年 XAUUSD M5×M1 数据一次回测几秒钟。Optuna 拿它做
|
||
几百上千次试验,从中挑出 2–3 个 diverse finalist。
|
||
- **Tier 2(MT5 Strategy Tester)**:只对 finalist 跑真实 tester。MT5 的数字才是
|
||
live 决策依据;Python 数字只负责排序和 A/B。
|
||
|
||
```
|
||
Idea ─► Python mirror engine ─► Optuna search (thousands of trials, fast)
|
||
│
|
||
▼
|
||
2–3 diverse finalists
|
||
│
|
||
▼
|
||
MetaTrader 5 Strategy Tester (gold standard)
|
||
│
|
||
▼
|
||
Python-vs-MT5 comparison table ─► keep / discard / iterate
|
||
│
|
||
▼
|
||
registry/ (locked, append-only)
|
||
```
|
||
|
||
**为什么这样分层**:MT5 真实 tick 模式跑一次 2 年回测要 10–30 分钟,做不了 1000 次
|
||
Optuna 搜索;纯 Python 又不可信。两层分工把"快"和"准"分开:用快的引擎做广度搜索,
|
||
用准的 tester 做最终验证。
|
||
|
||
### 1.2 单向依赖分层
|
||
|
||
整个仓库是 9 个**单向依赖层**,高调低、低不知高:
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────────┐
|
||
│ STRATEGY / CALLER 一个策略一个文件夹 │
|
||
│ load bars → compute signals+stops → call engine → score │
|
||
└───────────────┬──────────────────────────────────┬─────────────────┘
|
||
│ │
|
||
┌───────▼────────┐ ┌────────▼─────────┐
|
||
│ OPTIMIZER │ │ MT5 BRIDGE │
|
||
│ Optuna objective│ │ compile/run/ │
|
||
│ diverse top-N │ │ parse/compare │
|
||
└───────┬─────────┘ └────────┬─────────┘
|
||
│ │
|
||
┌───────▼─────────┐ │
|
||
│ ROBUSTNESS │ read-only over results │
|
||
└───────┬─────────┘ │
|
||
│ │
|
||
┌───────▼─────────────────────────────────────▼──────────┐
|
||
│ ENGINE (frozen) bar-by-bar fill simulator │
|
||
│ knows: bars, signals, stop/target prices, instrument │
|
||
│ knows NOT: your strategy, indicators, broker │
|
||
└───┬─────────────┬────────────────┬──────────────────────┘
|
||
│ │ │
|
||
┌────────▼───┐ ┌──────▼──────┐ ┌──────▼───────┐ ┌──────────────┐
|
||
│ INDICATORS │ │ INSTRUMENTS │ │ GATES │ │ DATA │
|
||
│ RSI/ATR/.. │ │ per-symbol │ │ entry-filter │ │ loaders + │
|
||
│ pure fns │ │ config объ. │ │ masks │ │ MT5 parser │
|
||
└────────────┘ └─────────────┘ └──────────────┘ └──────────────┘
|
||
```
|
||
|
||
**每一层的"必须 / 不能"**(来自 [02-architecture.md](02-architecture.md#L40-L54)):
|
||
|
||
| Layer | 文件夹 | Owns | Must NOT |
|
||
|-------|--------|------|----------|
|
||
| Data | `shared/data/` | 从 Parquet 加载 bars;从 MT5 拉历史;解析 MT5 HTML 报告 | 含策略逻辑 |
|
||
| Instruments | `shared/instruments/` | 每个 symbol/broker 的 config 对象(tick value、spread、swap、lot step) | 知道任何策略 |
|
||
| Indicators | `shared/indicators/` | 纯函数:RSI、ATR、EMA、SMA、自定义指标 | 跨调用保状态 |
|
||
| Gates | `shared/gates/` | 布尔 mask,过滤入场(regime、时段、exhaustion) | 开/平仓 |
|
||
| Engine | `shared/core/` | bar-by-bar fill/exit 模拟器;验证后**冻结** | 算信号或止损价 |
|
||
| Robustness | `shared/robustness/` | 只读反过拟合分析 | 改 engine 或 result |
|
||
| Optimizer | `shared/optimizer/` | objective 函数、Optuna 接线、diverse top-N 选择 | 知道 broker 细节 |
|
||
| MT5 bridge | `shared/mt5_pipeline/` | 生成 .set/.ini、跑 tester、拉报告、对比 | 算策略 |
|
||
| Strategy/caller | `strategies/<name>/` | glue:数据 → 信号 → 止损 → engine → 指标 | 被另一策略 import(copy,不 import) |
|
||
| Registry | `registry/` | 已批准、锁定的结果——真相源 | 被随意编辑 |
|
||
|
||
### 1.3 三个核心设计决策
|
||
|
||
#### 决策 1:Engine 一无所知
|
||
|
||
`engine.run()` 的输入是**预算好的**:bars + 信号数组 + SL/TP 价格数组。
|
||
Engine 只决定**价格是否触到止损**,**从不决定止损放在哪**。
|
||
|
||
这条接缝把"策略"和"模拟器"分开:换策略 → 改 caller 和数组;engine 不动。
|
||
这是整个项目可冻结、可复用的根基([02-architecture.md §2](02-architecture.md#L58-L104))。
|
||
|
||
`engine.run` 的签名([shared/core/engine.py](shared/core/engine.py#L108-L147)):
|
||
|
||
```python
|
||
engine.run(
|
||
bars, # DataFrame [timestamp, open, high, low, close, spread]
|
||
signals_long, # bool array — 仅在触发 bar 为 True(edge-detected)
|
||
signals_short, # bool array
|
||
sl_prices, # 数组 — 该 bar 入场的止损价(NaN 表示无)
|
||
tp_prices, # 数组 — 止盈价
|
||
instrument, # InstrumentConfig — 所有 symbol mechanics
|
||
sizing, # SizingInputs — 仓位 sizing 输入
|
||
initial_deposit,
|
||
*, # 以下 keyword-only
|
||
m1_bars=None, # M1 bars,trailing/BE EA 必传(见 §1.3 决策 3)
|
||
) -> Result
|
||
```
|
||
|
||
#### 决策 2:Engine 一旦验证就冻结
|
||
|
||
验证通过的 engine 就是**满意基线**,永不编辑它来试新想法——**fork 它**:
|
||
复制一份加一个 default-OFF 的实验 hook,先证明 fork-with-change-off == 原版 1:1,
|
||
再 A/B([04-isolation-rules.md Rule 2](04-isolation-rules.md#L21-L44))。
|
||
|
||
**为什么这么严**:engine 的验证是 trade-by-trade 对账 MT5,很贵。一行"小改"可能
|
||
悄悄让百万个 bar 的填单位移,让所有已信任的数字都失效——而且你**很久之后才发现**。
|
||
|
||
#### 决策 3:trailing/BE EA 必须 M1 tick-level 模拟
|
||
|
||
这是项目里**最容易踩的坑**:bar-level 模拟(4 个 sub-tick: O→L→H→C)对
|
||
break-even/trailing/basket-trailing 类 EA 会产生 **−40% 到 −50% 的 net profit gap**
|
||
**即使在平静窗口也如此**。这不是噪声,是 bug。
|
||
|
||
**机制**:bar-level 引擎用 bar high 更新 BE/trailing SL,然后在**同一根 bar 的另一端**
|
||
检查 SL。如果价格短暂穿越 BE 阈值,SL 被移到 break-even,然后同一根 bar 的 low
|
||
(long 仓位)就触发刚移动的 SL——锁定一笔**微利**,但 MT5 的 tick 路径会把它记成
|
||
小亏(BE-trigger tick 和 SL-trigger tick 在 MT5 里是分开的 tick,价格可能继续穿过 BE
|
||
变成真亏损才填单)。
|
||
|
||
**修复**:传 `m1_bars=`,engine 切换到 tick-level 模拟——每根 M5 bar 内走 5 根 M1
|
||
子 bar × 4 synthetic tick,方向感知顺序。BE-update tick 和 SL-trigger tick 落到不同
|
||
M1 bar,还原真实最坏情况。gap 从 −48.5% 降到 −5.6%([03-engine-design.md §7](03-engine-design.md#L189-L255))。
|
||
|
||
实测对照表([03-engine-design.md](03-engine-design.md#L234-L241)):
|
||
|
||
| Mode | Net gap vs MT5 | PF gap | Trade-count gap |
|
||
|------|----------------|--------|------------------|
|
||
| Bar-level (4 sub-ticks) | **−48.5%** | −30.0% | 0% |
|
||
| M1 tick-level (4 sub-ticks × 5 M1 bars) | **−5.6%** | −7.8% | 0% |
|
||
|
||
### 1.4 文档地图
|
||
|
||
| # | 文档 | 用途 |
|
||
|---|------|------|
|
||
| — | [README.md](README.md) | KB 入口:抽象两层模型、文档导航 |
|
||
| — | [CLAUDE.md](CLAUDE.md) | 给 AI 助手的分阶段搭装 playbook |
|
||
| 01 | [01-stack-and-install.md](01-stack-and-install.md) | 技术栈每个库 + 每个系统的安装命令 |
|
||
| 02 | [02-architecture.md](02-architecture.md) | 单向依赖分层 + 数据流 |
|
||
| 03 | [03-engine-design.md](03-engine-design.md) | bar-by-bar engine 设计、intra-bar 4-sub-tick、保真度 |
|
||
| 04 | [04-isolation-rules.md](04-isolation-rules.md) | 8 条隔离铁律(冻结/fork/instrument/registry) |
|
||
| 05 | [05-config-and-inputs.md](05-config-and-inputs.md) | 4 个声明输入源(instrument/space/wizard/frozen) |
|
||
| 06 | [06-optimization-and-robustness.md](06-optimization-and-robustness.md) | Optuna objective、diverse top-N、反过拟合层 |
|
||
| 07 | [07-mt5-bridge.md](07-mt5-bridge.md) | 编译 EA、生成 .set/.ini、跑 tester、解析报告 |
|
||
| 08 | [08-workflow-cycle.md](08-workflow-cycle.md) | 8 步可重复循环:hypothesis → promote |
|
||
| — | 本文档(PROJECT_GUIDE.md) | **项目实例说明**:架构+坑+使用+扩展 |
|
||
|
||
---
|
||
|
||
## 2. 当前项目状态
|
||
|
||
### 2.1 已完成 Phase 0–8 全闭环
|
||
|
||
| Phase | 内容 | 状态 |
|
||
|-------|------|------|
|
||
| 0 | 设备 profile(Windows + Python 3.12.10 + Git 2.51.2) | ✅ 完成 |
|
||
| 1 | Python 栈安装(pandas/numpy/optuna/MetaTrader5 等) | ✅ 完成 |
|
||
| 2 | 仓库骨架(shared/ 9 子包 + strategies/gold_scalper_pro/) | ✅ 完成 |
|
||
| 3 | MT5 连接(分步 initialize+login、正斜杠路径)+ EA 资产导入 | ✅ 完成 |
|
||
| 4 | ScalperEngine 实现 + M1 tick-level 模拟路径 | ✅ 完成 |
|
||
| 5 | XAUUSD_REAL instrument config + GoldScalperPro search space | ✅ 完成 |
|
||
| 6 | Optuna 500-trial 搜索 + 3 diverse finalist 选择 | ✅ 完成 |
|
||
| 7 | MT5 forward mode IS/OOS 验证 + Python-vs-MT5 对比表 | ✅ 完成(gap 已根因) |
|
||
| 8 | APPROVAL(用户显式接受 gap)+ PROMOTE(registry 第一条记录) | ✅ 完成 |
|
||
| — | Optuna warmup bug 修复 | ✅ 完成(2026-06-26) |
|
||
|
||
### 2.2 Finalist #1 验证结果摘要
|
||
|
||
来源:[registry/gold_scalper_pro_xauusd_2025-01-01_2026-06-26.md](registry/gold_scalper_pro_xauusd_2025-01-01_2026-06-26.md)。
|
||
Optuna trial #324,score=858.47。IS=2025 全年,OOS=2026 H1(MT5 forward mode)。
|
||
|
||
| 窗口 | 指标 | Python | MT5 | gap | gate |
|
||
|------|------|-------:|----:|----:|------|
|
||
| IS | net | $28,983.81 | $6,987.34 | +314.8% | **FAIL** |
|
||
| IS | PF | 1.71 | 1.43 | +19.3% | **FAIL** |
|
||
| IS | trades | 2,396 | 2,348 | +2.0% | **PASS** |
|
||
| OOS | net | $4,213.78 | $831.81 | +406.6% | **FAIL** |
|
||
| OOS | PF | 2.36 | 1.38 | +71.3% | **FAIL** |
|
||
| OOS | trades | 1,161 | 1,161 | 0.0% | **PASS** |
|
||
|
||
**接受理由**:信号层(trades + first-trade 时间)精确对齐 → engine 不是 bug。
|
||
残差根因 = Python parquet M5 OHLC 与 MT5 tester 内部 history 在 IS 起点附近
|
||
微差异,在 risk=2.25% 复利下被指数放大。MT5 数字是 live 决策依据。
|
||
|
||
### 2.3 关键资产位置
|
||
|
||
| 资产 | 路径 |
|
||
|------|------|
|
||
| EA 源码 + 编译产物 | [GoldScalperPro.mq5](GoldScalperPro.mq5), [GoldScalperPro.ex5](GoldScalperPro.ex5) |
|
||
| 历史 M5 数据 | `data/XAUUSD_M5_2024-06-26_2026-06-26.parquet`(gitignored) |
|
||
| 历史 M1 数据 | `data/XAUUSD_M1_2024-06-26_2026-06-26.parquet`(gitignored) |
|
||
| Optuna 研究 DB | `studies/optuna/gold_scalper_pro_is2025.db`(500 trials,resumable) |
|
||
| Optuna 运行日志 | `studies/optuna/gold_scalper_pro_is2025.log` |
|
||
| Finalist 指标 JSON | [studies/finalists/gold_scalper_pro_is2025-2026.json](studies/finalists/gold_scalper_pro_is2025-2026.json) |
|
||
| ML 特征数据集 | `studies/features/trade_features_*.parquet` + `trial_features_*.parquet`(含 .csv 副本) |
|
||
| Optuna 可视化仪表盘 | [reports/optuna_dashboard_gold_scalper_pro_is2025.html](reports/optuna_dashboard_gold_scalper_pro_is2025.html) |
|
||
| MT5 IS 报告 | [reports/IS-ReportTester-52845377.html](reports/IS-ReportTester-52845377.html) |
|
||
| MT5 OOS 报告 | [reports/OOS-ReportTester-52845377.html](reports/OOS-ReportTester-52845377.html) |
|
||
| 注册表第一条 | [registry/gold_scalper_pro_xauusd_2025-01-01_2026-06-26.md](registry/gold_scalper_pro_xauusd_2025-01-01_2026-06-26.md) |
|
||
| 凭证 | `.env`(gitignored,含 `MT5_DEMO_LOGIN/PASSWORD/SERVER`) |
|
||
| EA .set 配置 | `GoldScalperPro.set`(位于 MT5 tester profiles 目录) |
|
||
|
||
---
|
||
|
||
## 3. 使用手册
|
||
|
||
### 3.1 一次性准备(已完成可跳过)
|
||
|
||
```powershell
|
||
# 1. venv + 装包(详见 01-stack-and-install.md §3.1)
|
||
python -m venv .venv
|
||
.\.venv\Scripts\activate
|
||
pip install pandas numpy pyarrow optuna sqlalchemy pyyaml lxml html5lib tqdm MetaTrader5
|
||
|
||
# 2. .env 文件(gitignored)
|
||
# MT5_DEMO_LOGIN=52845377
|
||
# MT5_DEMO_PASSWORD=...
|
||
# MT5_DEMO_SERVER=ICMarketsSC-Demo
|
||
|
||
# 3. 拉历史 M5 + M1 数据(MT5 终端需打开并登录 demo)
|
||
python scripts/download_xauusd_history.py # M5
|
||
python scripts/download_xauusd_m1.py # M1(trailing/BE EA 必需)
|
||
|
||
# 4. 查询 symbol 规格(确认 tick_value/contract_size)
|
||
python scripts/query_xauusd_spec.py
|
||
```
|
||
|
||
### 3.2 日常工作流(一个 iteration 的完整闭环)
|
||
|
||
按 [08-workflow-cycle.md](08-workflow-cycle.md) 的 8 步走:
|
||
|
||
#### Step 1 — Hypothesis
|
||
|
||
一句话写下来,绑到一个已有的 engine + preset。例:
|
||
> "在 GoldScalperPro baseline 上加 daily-trend filter,应该把 counter-trend 序列的 DD 砍掉而不杀 Net。"
|
||
|
||
#### Step 2 — Scaffold
|
||
|
||
按 [02-architecture.md §4](02-architecture.md#L161-L174) 命名约定建文件夹:
|
||
|
||
```
|
||
strategies/gold_scalper_pro/iterations/<base>-<approach>-<YYYY-MM-DD>/
|
||
├── README.md # hypothesis、status、(后续)result
|
||
├── parameter-space.md # 这次搜索空间 + 每个范围的 reasoning
|
||
├── optimize.py # 自包含 snapshot(copy 上次的,改)
|
||
└── wizard-answers.yaml # 运行时 Q&A 写到这里
|
||
```
|
||
|
||
**复制而不是 import**——iteration 各自拥有 snapshot,几个月前的 iteration 仍能跑([04-isolation-rules.md Rule 5](04-isolation-rules.md#L74-L84))。
|
||
|
||
#### Step 3 — Stats(先量后调)
|
||
|
||
**先用真实历史测量信号特性**:触发频率、原始胜率、平均有利/不利偏移。
|
||
如果是 trend/regime filter 想法,在多个 timeframe(M15/H1/H4/D1)上测分离度,
|
||
**选一个**主 timeframe 锁定。**这一步不做参数搜索**。
|
||
|
||
很多 iteration 死在这里——好结果,省下后面的算力。
|
||
|
||
#### Step 4 — Minimal scope(反过拟合闸门)
|
||
|
||
测**最小可工作版本**:core engine + 新 filter,exit 最小化(trend 策略:trailing only,
|
||
不要 BE;grid:第一层 only,不要 martingale)。
|
||
|
||
一个问题:**有没有任何 edge**? handful 个 A/B 跑,不是搜索。没有 → **停**。
|
||
|
||
#### Step 5 — Expand + Optuna
|
||
|
||
只在 step 4 显示 edge 后展开。**一次加一层**,每层 A/B 对前一版:
|
||
加 BE → 加 grid 二层 → 加 ATR stop → 放宽 range。最后跑 Optuna。
|
||
|
||
```powershell
|
||
# Smoke 先(30 trials,IS H1,<2 分钟)
|
||
python scripts/optimize.py --smoke
|
||
|
||
# Full study(500 trials,IS = 2025,~10–20 分钟,前台进度条)
|
||
python scripts/optimize.py --trials 500
|
||
# 或后台:start /b python scripts\optimize.py,然后 poll studies/optuna/gold_scalper_pro_is2025.db
|
||
```
|
||
|
||
研究可断点续跑(`load_if_exists=True`),中途 Ctrl+C 不丢。完成挑 3 个 diverse finalist
|
||
([shared/optimizer/selector.py](shared/optimizer/selector.py) 的 greedy max-distance 算法)。
|
||
|
||
#### Step 6 — MT5-verify(只 finalist)
|
||
|
||
用 forward mode 跑:FromDate=2025.01.01, ToDate=2026.06.26, ForwardDate=2026.01.01,
|
||
MT5 自动切成 IS(2025 全年)+ OOS(2026 H1)两段。导出两份 HTML 到 `reports/`。
|
||
|
||
#### Step 7 — APPROVAL
|
||
|
||
人工看对比表 + robustness 信号。三个选项:promote / discard / iterate。
|
||
**没有任何东西自动 promote**。这一关是判断——"这合理吗?DD 可活吗?trade count 真实吗?"——
|
||
覆盖任何单一指标([08-workflow-cycle.md §Step 7](08-workflow-cycle.md#L91-L94))。
|
||
|
||
#### Step 8 — PROMOTE
|
||
|
||
复制 finalist 到 [registry/](registry/)([04-isolation-rules.md Rule 7](04-isolation-rules.md#L102-L113))。
|
||
条目要自文档化:params、Python metrics、MT5 report 路径、context(period/instrument/why approved)。
|
||
**Append-only**:不编辑已批准条目,新发现是新条目。Discarded iteration 进 archive/,不删——
|
||
负面结果也是数据。
|
||
|
||
### 3.3 常用脚本速查
|
||
|
||
| 脚本 | 用途 | 典型用法 |
|
||
|------|------|---------|
|
||
| [scripts/optimize.py](scripts/optimize.py) | 跑 Optuna 搜索(smoke / full) | `python scripts/optimize.py --smoke` 或 `--trials 500` |
|
||
| [scripts/reeval_finalist_forward.py](scripts/reeval_finalist_forward.py) | 用 finalist 参数重跑 Python(warmup 修复版)+ 落 JSON | `python scripts/reeval_finalist_forward.py` |
|
||
| [scripts/compare_finalist.py](scripts/compare_finalist.py) | 对比 Python vs MT5 | `python scripts/compare_finalist.py` |
|
||
| [scripts/diag_atr_check.py](scripts/diag_atr_check.py) | ATR 逐笔诊断 | 调 sizing 偏差时用 |
|
||
| [scripts/diag_mt5_trades.py](scripts/diag_mt5_trades.py) | 解析 MT5 HTML 逐笔 trade | 对账时用 |
|
||
| [scripts/diag_size_after_warmup.py](scripts/diag_size_after_warmup.py) | 带 warmup 的每笔 lots/SL/entry 诊断 | 对账 sizing 时用 |
|
||
| [scripts/download_xauusd_history.py](scripts/download_xauusd_history.py) | 下载 M5 | 数据更新 |
|
||
| [scripts/download_xauusd_m1.py](scripts/download_xauusd_m1.py) | 下载 M1 | 数据更新 |
|
||
| [scripts/query_xauusd_spec.py](scripts/query_xauusd_spec.py) | 查 symbol 规格 | 新 symbol 时用 |
|
||
|
||
### 3.4 快速 A/B(不开 Optuna)
|
||
|
||
```python
|
||
# 在 REPL 或一个小脚本里:
|
||
from shared.core.engine import SizingInputs
|
||
from shared.data.loaders import load_bars
|
||
from strategies.gold_scalper_pro.instruments import XAUUSD_REAL
|
||
from strategies.gold_scalper_pro.scalper_engine import ScalperEngine, engine_kwargs_from_params
|
||
from strategies.gold_scalper_pro.search_space import FROZEN_BASELINE
|
||
from strategies.gold_scalper_pro.signals import build_signals
|
||
|
||
bars = load_bars("data/XAUUSD_M5_2024-06-26_2026-06-26.parquet")
|
||
m1 = load_bars("data/XAUUSD_M1_2024-06-26_2026-06-26.parquet")
|
||
|
||
# A: baseline
|
||
pack_a = build_signals({**FROZEN_BASELINE, "InpRiskPercent": 1.0}, bars, XAUUSD_REAL)
|
||
res_a = ScalperEngine().run(bars, pack_a.signals_long, pack_a.signals_short,
|
||
pack_a.sl_prices, pack_a.tp_prices,
|
||
XAUUSD_REAL, SizingInputs(), 1000.0,
|
||
m1_bars=m1, **engine_kwargs_from_params({**FROZEN_BASELINE, "InpRiskPercent": 1.0}))
|
||
|
||
# B: 改 risk=2.25
|
||
pack_b = build_signals({**FROZEN_BASELINE, "InpRiskPercent": 2.25}, bars, XAUUSD_REAL)
|
||
res_b = ScalperEngine().run(bars, pack_b.signals_long, pack_b.signals_short,
|
||
pack_b.sl_prices, pack_b.tp_prices,
|
||
XAUUSD_REAL, SizingInputs(), 1000.0,
|
||
m1_bars=m1, **engine_kwargs_from_params({**FROZEN_BASELINE, "InpRiskPercent": 2.25}))
|
||
|
||
print(f"A net={res_a.final_balance - 1000:.2f} trades={len(res_a.trades)}")
|
||
print(f"B net={res_b.final_balance - 1000:.2f} trades={len(res_b.trades)}")
|
||
```
|
||
|
||
---
|
||
|
||
## 4. 扩展指南
|
||
|
||
### 4.1 加新策略(new EA)
|
||
|
||
1. 在 `strategies/<new_strategy>/` 下建文件夹,至少包含:
|
||
- `__init__.py`
|
||
- `instruments.py` — 新 symbol 的 `InstrumentConfig`(从 MT5 spec 查,不要猜)
|
||
- `signals.py` — 把 EA 的 `EvaluateEntry`/`OpenTrade` 翻译成 `build_signals(params, bars, instrument) → SignalPack`
|
||
- `<name>_engine.py` — 实现 `Engine` Protocol。**如果 EA 移动 SL(BE/trailing/basket trailing),必须支持 `m1_bars=` kwarg**
|
||
- `search_space.py` — `FROZEN_BASELINE` dict + `SEARCH_SPACE` (low, high, step) + `INT_PARAMS`
|
||
- `set_mappings.py` — Python param 名 ↔ EA input 名映射(生成 .set 时用)
|
||
2. 复制 [scripts/optimize.py](scripts/optimize.py) 改 import 到新策略
|
||
3. **先做 trade-by-trade 对账**([03-engine-design.md §8](03-engine-design.md#L258-L287)):选一个已知 preset,跑短窗口,Python vs MT5 逐笔对账直到通过 §8 target gate。**未对账过的 engine 不许上 Optuna**
|
||
4. 进 [08-workflow-cycle.md](08-workflow-cycle.md) 8 步循环
|
||
|
||
### 4.2 加新 symbol(同策略)
|
||
|
||
按 [04-isolation-rules.md Rule 4](04-isolation-rules.md#L59-L71):**instrument 是数据,不是代码分支**。
|
||
|
||
1. 在 `strategies/<strategy>/instruments.py` 加 `InstrumentConfig` 对象,字段从 MT5 symbol spec 查
|
||
2. 准备三个 cost-stress 变体:`real` / `worst_case` / `best_case`(用 `get_profile()` 派生)
|
||
3. 用 [scripts/query_xauusd_spec.py](scripts/query_xauusd_spec.py) 当模板改成新 symbol
|
||
4. 下载新 symbol的历史 M5 + M1
|
||
5. **零 engine 改动**——同策略换 symbol 只是换 config
|
||
|
||
### 4.3 加新指标
|
||
|
||
在 [shared/indicators/base.py](shared/indicators/base.py) 加纯 numpy 函数。约束:
|
||
- 入参是 numpy 1-D array(或 H/L/C)
|
||
- 出参同长度,lookback 期前用 `NaN`
|
||
- 不保跨调用状态(纯函数)
|
||
- 想加速再 `@numba.njit`,否则先正确后快
|
||
|
||
### 4.4 加新 gate(入场过滤)
|
||
|
||
在 [shared/gates/base.py](shared/gates/base.py) 加。Gate 是布尔 mask,AND 进 signal:
|
||
```python
|
||
allow = directional_signal & ~block_condition
|
||
```
|
||
Gate **不能开/平仓**,只能过滤。Common gates:regime filter、时段、exhaustion。
|
||
|
||
### 4.5 加新 robustness 层
|
||
|
||
在 [shared/robustness/layers.py](shared/robustness/layers.py) 加只读分析函数。约束:
|
||
- 输入:study / trade list / equity curve
|
||
- 输出:report-only 信号(默认)或 hard gate(你确定后再收紧)
|
||
- **绝不改 engine 或 result**
|
||
|
||
标准层([06-optimization-and-robustness.md §4](06-optimization-and-robustness.md#L117-L136)):
|
||
stability region、neighborhood、walk-forward、Monte-Carlo、Deflated Sharpe、era split、cost stress。
|
||
|
||
### 4.6 加新 exit logic(fork engine)
|
||
|
||
**绝不编辑 frozen engine**。按 [04-isolation-rules.md Rule 2](04-isolation-rules.md#L21-L44):
|
||
|
||
1. `cp shared/core/scalper_engine.py shared/core/scalper_engine_<idea>.py`
|
||
2. 加实验 hook,**default OFF**
|
||
3. **Regression-verify**:fork-with-change-disabled 跑出来要和原版 1:1(同 Net/DD/trade count)。不对说明 copy 不干净,先修
|
||
4. A/B:fork-with-change-on vs frozen,同数据
|
||
5. 显著且稳健 → 考虑 promote 成新 baseline([Rule 3](04-isolation-rules.md#L47-L55));否则删 fork
|
||
|
||
### 4.7 加 iteration(新研究想法)
|
||
|
||
按 [08-workflow-cycle.md §4](08-workflow-cycle.md#L114-L121) 命名:
|
||
```
|
||
strategies/gold_scalper_pro/iterations/<base>-<approach>-<YYYY-MM-DD>/
|
||
```
|
||
例:`trend-filter-daily-ema-2026-07-01`、`grid-second-tier-atr-2026-07-15`。
|
||
每个 iteration 各自拥有 `optimize.py` snapshot——copy 上次的改,不 import。
|
||
|
||
---
|
||
|
||
## 5. 踩坑列表(开发中实际遇到)
|
||
|
||
### 5.1 MT5 连接类
|
||
|
||
#### 坑 1:mt5.initialize() 一次传所有参数导致 IPC 超时(-10005)
|
||
|
||
**症状**:`mt5.initialize(path=..., login=..., password=..., server=...)` 频繁超时或返回 False。
|
||
|
||
**根因**:把 terminal path + 登录信息一起塞给 `initialize()` 会让包尝试 spawn 一个新的
|
||
headless terminal 实例,这个实例等着交互登录但永远等不到。
|
||
|
||
**修复**:**分步连接法**——`initialize()` 不传 path(连到已运行的终端),再单独 `login()`:
|
||
|
||
```python
|
||
if not mt5.initialize(): # 不传 path → 复用已运行的终端
|
||
...
|
||
if not mt5.login(login, password=password, server=server): # 单独 login
|
||
...
|
||
```
|
||
|
||
见 [scripts/download_xauusd_history.py:36-53](scripts/download_xauusd_history.py#L36-L53)。
|
||
|
||
#### 坑 2:terminal path 用反斜杠触发 IPC 超时
|
||
|
||
**症状**:`mt5.initialize(r"C:\Program Files\...")` 偶发 -10005。
|
||
|
||
**修复**:**必须用正斜杠** `C:/Program Files/...`(Windows 接受两种,但 MetaTrader5 包对反斜杠敏感)。
|
||
|
||
#### 坑 3:MT5 终端进程残留占用 IPC 通道
|
||
|
||
**症状**:上一次脚本异常退出后,下次连接失败。
|
||
|
||
**修复**:每次 `mt5.shutdown()` 放进 `finally`;残留时 Task Manager 杀 `terminal64.exe` 后重试。
|
||
|
||
### 5.2 数据类
|
||
|
||
#### 坑 4:MT5 数据目录路径拼装
|
||
|
||
**症状**:要读 MT5 写的文件(如 `.set`、HTML 报告),不知道放哪。
|
||
|
||
**修复**:通过 `mt5.terminal_info().data_path` 取数据目录,再拼 `MQL5\Files` 或
|
||
`MQL5\Profiles\Tester`。
|
||
|
||
#### 坑 5:M1 数据即使信号 TF 更高也要拉并传给 engine
|
||
|
||
**症状**:trailing/BE EA 在 bar-level 模拟下 net 比 MT5 高 40–50%,看起来像"fidelity 噪声"。
|
||
|
||
**根因**:**这不是噪声是 bug**(见 §1.3 决策 3)。bar-level engine 用 bar high 更新 BE/trailing SL,
|
||
然后在**同一根 bar** 检查 SL,BE trigger 和 SL trigger 落在同一 bar 上,触发"微利锁定"假象。
|
||
|
||
**修复**:下载 M1 + 传 `m1_bars=` 给 `engine.run`,切换到 tick-level 模拟。gap 从 −48.5% 降到 −5.6%。
|
||
|
||
#### 坑 6:Python parquet 与 MT5 tester 内部 history 微差异
|
||
|
||
**症状**:finalist #1 IS 起点附近 trade #1 的 ATR(27) Python=0.9628 vs MT5-implied=0.7401(+30%)。
|
||
|
||
**根因**:Python parquet 存的 M5 OHLC 与 MT5 tester 内部访问的 history 在 IS 起点附近有
|
||
微小差异。在 risk=2.25% 复利下被指数放大(trade #1 sizing 差 33% → 整 IS net 差 4×)。
|
||
|
||
**当前处理**:接受此 gap 进 Phase 8(信号层 trades 完美对齐证明 engine 正确)。MT5 数字是
|
||
live 决策依据。详见 [registry 条目 §5.3](registry/gold_scalper_pro_xauusd_2025-01-01_2026-06-26.md)。
|
||
|
||
### 5.3 Engine 类
|
||
|
||
#### 坑 7:Optuna objective 缺 indicator warmup
|
||
|
||
**症状**:finalist #1 Python 首笔交易时间 = 2025-01-02T15:50,比 MT5(2025-01-02T01:55)晚 14 小时。
|
||
|
||
**根因**:`scripts/optimize.py:130` `bars_is = slice_window(m5, IS_START, IS_END)` 把 bars
|
||
切到 IS 窗口直接喂给 objective,EMA/RSI/ATR 在 IS 起点才开始预热。EMA(160) 在 M5 上要 ~14 小时
|
||
才稳定,所以首笔信号要等到 14 小时后才出。
|
||
|
||
**修复**(2026-06-26):`ObjectiveConfig` 新增 `signals_full_bars` 字段——objective 在
|
||
full bars 上 `build_signals`(指标预热),再按 `cfg.bars` 的起始时间戳切片到评估窗口运行 engine。
|
||
镜像 MT5 tester 的"pre-test chart history warmup"行为。
|
||
|
||
见 [shared/optimizer/objective.py:158-183](shared/optimizer/objective.py#L158-L183)。
|
||
|
||
#### 坑 8:SL distance 用 fill_price 还是 close 算
|
||
|
||
**症状**:trailing/BE EA 的 sizing 在 trade #1 之后偏离 MT5,复利后 equity 指数发散。
|
||
|
||
**根因**:`signals.py` 设 `sl_prices[i] = close[i] ± sl_dist[i]`,其中 `sl_dist[i] = InpAtrSLMult × ATR[i]`。
|
||
所以 `|close[i] - sl_prices[i]|` 精确还原 `sl_dist[i]`——和 MT5 sizing 用的距离一致。
|
||
如果用 `fill_price` 算 `sl_distance`,会让距离随 open-gap 漂移,有时大有时小,
|
||
lots 不一致,equity 指数发散。
|
||
|
||
**修复**:`scalper_engine.py:211` 用 `sl_distance = abs(closes[i] - sl)`,
|
||
**不用 fill_price**。代码里有详细注释说明这个决策。
|
||
|
||
#### 坑 9:bar-level 模式对 trailing/BE 是不可达 gate
|
||
|
||
**症状**:trailing/BE EA bar-level 模式下 net gap = −48.5%,看似"fidelity issue"。
|
||
|
||
**根因**:不是噪声是 bug(§1.3 决策 3)。bar-level engine 在同一根 bar 内既更新 BE 又触发 SL。
|
||
|
||
**修复**:传 `m1_bars=`,切 tick-level 模拟。**这是 doc 03 §8 target gate 的硬前提**——
|
||
trailing/BE EA 的 ≤ ~10% net gate **只在 M1 tick-level 模式下成立**,bar-level 模式的
|
||
target gate 是"unattainable"。
|
||
|
||
### 5.4 MT5 bridge 类
|
||
|
||
#### 坑 10:MT5 测试报告是 UTF-16-LE 编码
|
||
|
||
**症状**:直接 `open(report.html, encoding='utf-8').read()` 解析乱码或抛 UnicodeDecodeError。
|
||
|
||
**修复**:用正确编码读,`lxml`/`html5lib` 解析。MT5 HTML 报告默认 UTF-16-LE([07-mt5-bridge.md §7](07-mt5-bridge.md#L223-L236))。
|
||
|
||
#### 坑 11:.set 文件必须是 UTF-16-LE
|
||
|
||
**症状**:生成的 `.set` UTF-8 编码,MT5 tester 静默忽略。
|
||
|
||
**修复**:生成 `.set` 时写 UTF-16-LE([07-mt5-bridge.md §2a](07-mt5-bridge.md#L48-L63))。
|
||
|
||
#### 坑 12:forward mode 自动切 IS/OOS
|
||
|
||
**症状**:想分别跑 IS 和 OOS 两份报告,但 MT5 一次只能跑一个窗口。
|
||
|
||
**修复**:用 **forward mode**——FromDate=2025.01.01, ToDate=2026.06.26, ForwardDate=2026.01.01。
|
||
MT5 自动切成 IS(2025 全年)+ OOS(2026 H1)两段,导出一份报告但内部分两段指标。
|
||
当前 IS/OOS HTML 是分别导出的(用两次单段跑也行)。
|
||
|
||
#### 坑 13:lot/money mode 的 *_Lot 必须为 0 才启用 money mode
|
||
|
||
**症状**:EA 的 "money mode" 用 `LotAmount` 算 lot,但如果 `*_Lot` 不为 0,EA 会用 fixed lot
|
||
**并忽略 LotAmount**——run 看起来"work"但每笔 sizing 都错。
|
||
|
||
**修复**:用 money mode 时确认 `*_Lot = 0`([05-config-and-inputs.md §4](05-config-and-inputs.md#L114-L142))。
|
||
|
||
#### 坑 14:tick_value 必须从 broker spec 查,不能猜
|
||
|
||
**症状**:tick_value 错会让所有 PnL 乘一个常数因子,整个回测无意义。
|
||
|
||
**修复**:从 MT5 symbol spec 查(`mt5.symbol_info(SYMBOL).trade_tick_value`)。
|
||
本项目 XAUUSD `tick_value=1.0` 是通过 MT5 第一笔 trade PnL 反推验证:
|
||
`36.64 = (2626.34 − 2624.05) / 0.01 × tv × 0.16` → `tv = 1.0`。
|
||
|
||
### 5.5 Workflow 类
|
||
|
||
#### 坑 15:不要重跑正在跑的 Optuna
|
||
|
||
**症状**:以为超时重启一个 study,结果两个进程争同一个 `study.db`,CPU 全占、互相 thrash。
|
||
|
||
**修复**:重启前检查 `study.db` 是否还在写(`optuna.load_study` 看 trial count 增量)。
|
||
**一个 study 多 worker(`n_jobs=N`)优于 N 个独立脚本**([04-isolation-rules.md Rule 8](04-isolation-rules.md#L117-L128))。
|
||
|
||
#### 坑 16:smoke 先,full 后
|
||
|
||
**症状**:直接 500 trials,跑到一半发现 objective 有 bug,浪费算力。
|
||
|
||
**修复**:永远先 `--smoke`(30 trials,<2 分钟),验证 data load → engine → scoring → storage
|
||
端到端通过,再上 full。([06-optimization-and-robustness.md §6](06-optimization-and-robustness.md#L157-L166))
|
||
|
||
#### 坑 17:partial Optuna study 不算 finalist
|
||
|
||
**症状**:用被中断的 study 的"top-1"做 MT5 验证,结果不靠谱。
|
||
|
||
**根因**:TPE 还没收敛,preliminary 排名不是真正的 top。
|
||
|
||
**修复**:finalist 必须来自**完成**的搜索([06-optimization-and-robustness.md §5](06-optimization-and-robustness.md#L140-L154))。
|
||
|
||
#### 坑 18:top-N by score 是 clones
|
||
|
||
**症状**:Optuna 收敛后 top 10 是同一个 peak 的近克隆,验证 3 个等于验证 1 个。
|
||
|
||
**修复**:用 **diverse top-N 选择**([shared/optimizer/selector.py](shared/optimizer/selector.py))——
|
||
greedy max-distance + rank_weight,挑出"各自好但参数区域不同"的 2–3 个 finalist。
|
||
|
||
---
|
||
|
||
## 6. 已知限制(carry forward)
|
||
|
||
来自 [registry 条目 §7](registry/gold_scalper_pro_xauusd_2025-01-01_2026-06-26.md#L179-L195):
|
||
|
||
1. **Optuna warmup bug**(已修复 2026-06-26):原 `optimize.py` 把 bars 切到 IS 窗口直接喂
|
||
objective,导致 EMA/RSI/ATR 在 IS 起点才开始预热,首笔信号晚 14h。修复后 warmup 模式
|
||
精确复现 MT5 首笔交易时间。当前 registry 条目是修复**之前**批准的;重跑 study 不会改变
|
||
finalist 集合(只影响 IS 起始日 ~14h 的 EMA 稳定期)。
|
||
2. **Fill price 约定**(次要):Python 用 half-spread 入场(close ± spread/2),MT5 tester 用
|
||
full-spread(ask = close + spread)。单 tick 差,不复合。
|
||
3. **ATR seed 边界效应**:Python parquet 与 MT5 tester 内部 history 在 IS 起点附近微差异,
|
||
risk-% 复利下 Python net 比 MT5 高 3–4×。MT5 数字是 live 决策依据。
|
||
|
||
---
|
||
|
||
## 7. 环境与版本
|
||
|
||
| 组件 | 版本 |
|
||
|------|------|
|
||
| OS | Windows |
|
||
| Python | 3.12.10 |
|
||
| Git | 2.51.2 |
|
||
| pandas / numpy / pyarrow | latest stable |
|
||
| optuna | 4.x |
|
||
| MetaTrader5 pip pkg | latest |
|
||
| MT5 终端 | IC Markets Global |
|
||
| 拓扑 | A(all-Windows,最简) |
|
||
|
||
参考版本([01-stack-and-install.md §1](01-stack-and-install.md#L31-L47)):python 3.14、pandas 3.0、
|
||
numpy 2.4、pyarrow 24、numba 0.65、optuna 4.8。本项目用的是这些或更新版。
|
||
|
||
---
|
||
|
||
## 8. 后续可选优化方向
|
||
|
||
按"价值/成本比"排序:
|
||
|
||
### 8.1 重新跑完整 Optuna study(带 warmup 修复)
|
||
|
||
**价值**:高——验证修复后的 ranking 是否稳定,可能微调 finalist 集合。
|
||
**成本**:低——`python scripts/optimize.py --trials 500`,~10–20 分钟。
|
||
**建议**:值得做。warmup bug 只影响 IS 起始日 ~14h,预期不改变 finalist 集合,但确认一下。
|
||
|
||
### 8.2 修复 ATR seed 数据差异
|
||
|
||
**价值**:高——这是当前 net gap 的根因,修复后 Python 与 MT5 净值可对齐到 ≤ 10% gate。
|
||
**成本**:中——需要从 MT5 tester 内部 history 直接导出 M5,或调整 parquet 拉取窗口避开 IS 起点。
|
||
**建议**:值得做。但需要研究 MT5 tester 用什么 history 源(可能是 `bases/` 目录而非 `CopyRates`)。
|
||
|
||
### 8.3 加 robustness 层
|
||
|
||
当前 [shared/robustness/layers.py](shared/robustness/layers.py) 只实现了 stability_region。
|
||
按 [06-optimization-and-robustness.md §4](06-optimization-and-robustness.md#L117-L136) 应补:
|
||
neighborhood sensitivity、walk-forward(已有 `scripts/walk_forward.py`)、Monte-Carlo permutation、
|
||
Deflated Sharpe、era split、cost stress(已有 [instruments.py](strategies/gold_scalper_pro/instruments.py)
|
||
的 `XAUUSD_PROFILES` 三 variant)。
|
||
|
||
### 8.4 加 .set 生成器 + 自动 MT5 verify
|
||
|
||
当前 MT5 是手动跑(用户在 Strategy Tester 里导出 HTML)。按 [07-mt5-bridge.md §3b](07-mt5-bridge.md#L111-L125)
|
||
可实现:`shared/mt5_pipeline/set_gen.py` 从 param dict 生成 UTF-16-LE `.set` + `tester.ini`,
|
||
`launch terminal64.exe /config:` 自动跑 + `ShutdownTerminal=1` 自动退出 + 自动解析报告。
|
||
[shared/mt5_pipeline/](shared/mt5_pipeline/) 已经有 `set_gen.py`/`ini_gen.py`/`runner.py`/`compare.py`
|
||
骨架,需要填充实现。
|
||
|
||
### 8.5 加 numba 加速
|
||
|
||
当前 [scalper_engine.py](strategies/gold_scalper_pro/scalper_engine.py) 是纯 Python。
|
||
500 trials × 67k M5 bars × 337k M1 bars 大约 10–20 分钟。如果做 5000 trials 或更大搜索空间,
|
||
在 hot path(`_simulate_m1_exits` 内层循环)加 `@numba.njit` 能提速 5–10×。
|
||
**先正确后快**——numba 是 stack 里的备选项,不是过早优化。
|
||
|
||
### 8.6 加新策略
|
||
|
||
按 §4.1 流程。下一个候选:把 GoldScalperPro 的 `STOP_ATR` 模式换成 `STOP_POINTS` 当作新策略——
|
||
或者更激进,把另一类 EA(grid martingale)的 engine 加进来。doc 03 的 worked example 就是
|
||
grid martingale,可直接借鉴。
|
||
|
||
---
|
||
|
||
## 9. 速查命令卡
|
||
|
||
```powershell
|
||
# === 一次性准备 ===
|
||
python -m venv .venv ; .\.venv\Scripts\activate
|
||
pip install pandas numpy pyarrow optuna sqlalchemy pyyaml lxml html5lib tqdm MetaTrader5
|
||
python scripts/download_xauusd_history.py # M5
|
||
python scripts/download_xauusd_m1.py # M1(trailing/BE 必需)
|
||
|
||
# === 日常 ===
|
||
python scripts/optimize.py --smoke # smoke (30 trials, <2 min)
|
||
python scripts/optimize.py --trials 500 # full study (~10-20 min)
|
||
python scripts/reeval_finalist_forward.py # 重算 finalist 指标
|
||
python scripts/compare_finalist.py # Python vs MT5 对比
|
||
|
||
# === 诊断 ===
|
||
python scripts/diag_atr_check.py # ATR 偏差诊断
|
||
python scripts/diag_size_after_warmup.py # 每笔 lots 诊断
|
||
python scripts/diag_mt5_trades.py # 解析 MT5 逐笔 trade
|
||
python scripts/diag_mt5_summary.py # MT5 指标摘要
|
||
|
||
# === MT5(手动)===
|
||
# 1. 打开 MT5 → Strategy Tester (Ctrl+R)
|
||
# 2. Expert=GoldScalperPro, Symbol=XAUUSD, Model=2 (1-min OHLC)
|
||
# 3. Date: 2025.01.01 → 2026.06.26, Forward: 2026.01.01
|
||
# 4. Load GoldScalperPro.set (finalist #1 params)
|
||
# 5. Start → 完成后 Save as Report → 复制到 reports/
|
||
```
|
||
|
||
---
|
||
|
||
## 10. 总结
|
||
|
||
这个项目验证了一件事:**一个被纪律约束的个人量化研究实验室是可行的**。
|
||
|
||
- 架构上把"快"和"准"分开(Python 排序 / MT5 拍板),把"策略"和"模拟器"分开(engine 一无所知)。
|
||
- 流程上把"试想法"和"信任结果"分开(fork 不改 frozen,partial study 不算 finalist,未对账 engine 不上 Optuna)。
|
||
- 工程上把"输入"和"代码"分开(4 个声明源),把"已知"和"未知"分开(registry append-only + known limitations)。
|
||
|
||
**最有价值的一条经验**:**测过才知道**。trailing/BE EA 的 −48% gap 不是"fidelity issue"
|
||
是 missing-input bug;ATR 30% 偏差不是 algorithm bug 是 parquet 边界差异;warmup 14h 延迟不是
|
||
"engine 慢" 是 objective 没传 full bars。每一条都通过测量定位、根因分析、针对性修复,而不是
|
||
靠"调参"或"重试"。
|
||
|
||
按这个流程走,研究速度会快很多,结果也更可信——因为每一个数字都能解释它**为什么**是那个值。
|
||
|
||
---
|
||
|
||
*文档版本:2026-06-26。对应代码状态:Phase 8 完成,warmup bug 已修复,registry 第一条已落库。*
|