# research/ —— 研究管线骨架(你的代码) > ⚠️ 这是**你的研究管线骨架**,只用于离线研究。它包装 `mtdata-cli` 作为子 > 进程拉数据/跑回测,**不 import 任何 mtdata 内部模块**,因此不会被上游同步覆盖。 > 研究结果(模型文件 + 报告)最终通过 `trading/` 的 `signal(ctx)` 接口进入执行壳。 ## 管线一览 ``` ┌──────────┬──────────┬──────────────┬──────────────┬─────────────┬───────────┐ │ 取数据 │ 存储 │ 特征/标签 │ 前向验证 │ 训练 │ 评估 │ │ fetch │ store │ features │ walk-forward │ train │ evaluate │ ├──────────┼──────────┼──────────────┼──────────────┼─────────────┼───────────┤ │ mtdata-cli│ parquet │ pandas_ta / │ mtdata-cli │ sklearn / │ sklearn │ │ data_ │ (CSV 兜底)│ 手写指标兜底 │ strategy_ │ lightgbm │ 指标 + │ │ fetch_ │ manifest │ 三触线标签 │ validate │ (可选) │ markdown │ │ candles │ │ │ 或本地 stub │ │ 报告 │ └──────────┴──────────┴──────────────┴──────────────┴─────────────┴───────────┘ ``` ## 快速上手(离线、无需下单) ```bash # 一键端到端:取数 → 数据集 → 前向验证 → 训练 → OOS 评估 → 模型落盘 py -3.14 -m research --symbol EURUSD --timeframe H1 --limit 3000 # 可选:最后再对同一份数据回测一个 trading/ 注册表策略(与实盘同一套代码) py -3.14 -m research --symbol EURUSD --timeframe H1 --limit 3000 \ --backtest --strategy ma_cross # 也可只手动调用某个阶段(见下方"手动"示例) py -3.14 -c " from research.config import ResearchConfig from research import data_pipeline as dp cfg = ResearchConfig() ds = dp.fetch_and_store(cfg, 'EURUSD', limit=3000) print('stored:', ds.to_dict()) " ``` `py -3.14 -m research` 的六个阶段: 1. **fetch**:`fetch_and_store` 拉 K 线并落盘(parquet 优先,无 pyarrow 时 CSV)。 2. **dataset**:`make_dataset` 产出特征 + 二分类标签(`1`=止盈先到)。 3. **walk-forward**:`local_walk_forward` 按时序分 5 折,报告每折标签命中率。 4. **split**:按 `RESEARCH_TRAIN_RATIO`(默认 `0.8`)做时序切分,留出 OOS 尾部。 5. **train + evaluate**:训练指定模型,输出 OOS 指标 + 与"永远做多"基线的对比报告。 6. **save**:写到 `research/models/latest.joblib` —— 即 `trading/strategies/model_threshold.py` 的默认 `model_path`。 7. **backtest(可选,`--backtest`)**:从 `trading/` 策略注册表加载一个策略 (`--strategy`,默认 `ma_cross`),对同一份历史数据跑离线回测 —— 与实盘 同一个 `signal(ctx)` + 可选的 `manage_position`,输出统计并落 markdown 报告。 入口参数:`--symbol` `--timeframe` `--limit` `--kind`(model 类型)`--model-name` (默认 `latest`)`--seed`,以及回测组的 `--backtest` `--strategy`(策略名或 模块路径)`--strategy-param key=value`(可重复)。训练跳出或样本不足时返回码 `2`,成功为 `0`。 研究管线不接触订单:任何会触达 `trade_*` 的调用都留在 `trading/`。 ## 文件地图 | 文件 | 职责 | |------|------| | `main.py` / `__main__.py` | 端到端编排 CLI(`py -3.14 -m research`) | | `config.py` | 环境变量 → `ResearchConfig`(frozen dataclass) | | `data_pipeline.py` | 取 K 线 → 存储(parquet/CSV)→ manifest 记录 | | `feature_engineering.py` | 指标特征 + 前向三触线标签(`make_dataset`) | | `backtest.py` | `strategy_validate` 包装 + 本地前向验证 + 离线回测(含持仓管理模拟) | | `model_trainer.py` | sklearn / lightgbm 训练、`ModelWrapper` 存取(含 baseline 命中率) | | `evaluate.py` | 分类指标 + OOS 对比 markdown 报告 | ## 环境变量(默认值) | 变量 | 默认 | 说明 | |------|------|------| | `RESEARCH_DATA_DIR` | `research/data` | 数据与 manifest 目录 | | `RESEARCH_STORAGE_FORMAT` | `parquet` | `parquet` / `csv`(parquet 需 pyarrow) | | `RESEARCH_SYMBOLS` | `EURUSD,GBPUSD` | 默认研究品种 | | `RESEARCH_TIMEFRAME` | `H1` | K 线周期 | | `RESEARCH_LOOKBACK_BARS` | `3000` | 默认取数根数 | | `RESEARCH_INDICATORS` | `rsi,atr,ema,bbands` | 取数时附带指标 | | `RESEARCH_LABEL_TP_PCT` | `0.4` | 标签止盈距离(%) | | `RESEARCH_LABEL_SL_PCT` | `0.6` | 标签止损距离(%) | | `RESEARCH_LABEL_HORIZON` | `12` | 标签前视 K 线数 | | `RESEARCH_WF_N_SPLITS` | `5` | `strategy_validate` 折数 | | `RESEARCH_WF_MIN_TRAIN_BARS` | `1000` | 前向验证最小训练根数 | | `RESEARCH_TRAIN_RATIO` | `0.8` | 时序切分:前 80% 训练,尾部留 OOS | | `RESEARCH_SPREAD_BPS` / `RESEARCH_COMMISSION_BPS` / `RESEARCH_SLIPPAGE_BPS` | `1.0` / `0.0` / `1.0` | 回测成本(bps) | | `RESEARCH_MODEL_DIR` | `research/models` | 模型输出目录 | | `RESEARCH_MODEL` | `logistic` | `logistic` / `rf` / `gboost` / `lightgbm` | | `RESEARCH_REPORT_DIR` | `research/reports` | 报告输出目录 | | `MTDATA_CLI` | `mtdata-cli` | CLI 路径 | ## 模型如何接入 trading/ 执行壳 研究侧只产出两类东西,供 `trading/` 使用: 1. **特征/标签约定**:`feature_engineering.make_dataset(frame, cfg)` 的输出 直接作为训练输入;在线推理时对最新一根 K 线同样调用 `build_features`。 目标已折叠为二分类:`1` = 止盈先到(+1),`0` = 止损先到或超时; 模型输出的单一概率即 `P(+1)`,可直接喂给 `signal()` 的阈值。 2. **模型文件**:`model_trainer.ModelWrapper.save(path)` 落盘,`trading/` 侧 的策略模块用 `ModelWrapper.load` 读取,然后在 `signal(ctx)` 里: ```python def signal(ctx): X = research.feature_engineering.build_features(ctx.candles).tail(1) p = wrapper.predict_proba_positive(X)[0] if p >= BUY_THRESHOLD: return TradeSignal(side="BUY", stop_loss=..., take_profit=..., reason=f"p={p:.3f}") return None ``` 训练模型**永远不直接下单**——它只是 `signal()` 的一个输入;是否真的下单仍由 `trading/` 的 dry-run 分层和护栏把关。任何模型上线实盘前,请先完成 [docs/LIVE_TRADING_CHECKLIST.md](../docs/LIVE_TRADING_CHECKLIST.md)。 ## 前向验证(walk-forward) - 优先:`research.backtest.run_strategy_validate(cfg, symbol, candidates)` 直接 调用 `mtdata-cli strategy_validate`(含 purge/embargo 防泄漏、固定候选、 horizon-safe 结果)。 - 兜底:`local_walk_forward(frame, cfg)` 在本地做时序切分的标签分布摘要, 用于无 broker 环境下的骨架验证。 ## 离线回测(可选,任意 `signal(ctx)` 策略) `research.backtest.backtest_strategy(strategy, frame, cfg)` 用与实盘**同一个** 策略对象逐 bar 回测:`signal(ctx)` 开仓后,每根 K 线先结算服务器端 SL/TP (价格先行动),未命中再调用策略的 `manage_position(ctx, position)` 做移损 / 保本 / 分批平仓 / 提前离场 —— 与实盘循环的持仓管理 pass 调用的是同一个方法, 动作语义也完全一致(`apply_position_action` 模拟 `Executor.modify/close`)。 成本按 `RESEARCH_SPREAD_BPS` / `RESEARCH_COMMISSION_BPS` / `RESEARCH_SLIPPAGE_BPS` 结算。`backtest_registered(spec, frame, cfg)` 接受 `trading/` 注册表名字(`ma_cross`、`model_threshold` 或任意模块路径), `py -3.14 -m research --backtest` 即其一键入口。