Files
fidc-backtest-engine/README.md
2026-04-18 18:02:50 +08:00

261 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# fidc-backtest-engine
一个面向中国 A 股长周期选股策略的 Rust 回测核心骨架。这个仓库的第一版目标不是“玩具回测器”,而是提供一个可以继续演化为平台化引擎的最小可用核心,方向参考 `nautilus_trader` 的分层架构和 `rqalpha` 的中国股票规则约束。
## 当前能力
- Phase 3增加预索引数据层、可配置决策/执行语义,以及更贴近聚宽微盘股脚本的 native 策略
- 日频交易日历与确定性逐日回放
- A 股日频市场快照、估值/因子快照、基准快照、候选资格标记
- 策略接口与引擎驱动,不直接模拟 `jqdata` API
- `BacktestConfig` 支持 `decision_lag_trading_days``execution_price_field(open/close/last)`
- `DailyMarketSnapshot` 支持 `day_open` / `last_price`
- Universe 选择器:按指数位置动态切换市值带,再取最小市值 Top-N
- 风险节流:基于指数均线状态切换 100% / 50% / 0% 仓位
- Broker Simulator按次日开盘价撮合支持手续费、印花税、最小佣金
- 中国 A 股规则钩子T+1、停牌、涨停不可买、跌停不可卖
- 回测输出:权益曲线、成交记录、期末持仓摘要
- 新增 `JqMicroCapStrategy`覆盖动态市值带、停运窗口、1 元股 / ST / 科创板过滤、均线过滤、止损止盈、固定频率再平衡
- `cargo run --bin bt-demo` 可直接运行仓库内置 demo 数据
## Workspace 布局
```text
.
├── Cargo.toml
├── crates
│ ├── bt-demo
│ │ └── src/main.rs
│ └── fidc-core
│ └── src
│ ├── broker.rs
│ ├── calendar.rs
│ ├── cost.rs
│ ├── data.rs
│ ├── engine.rs
│ ├── events.rs
│ ├── instrument.rs
│ ├── portfolio.rs
│ ├── rules.rs
│ ├── strategy.rs
│ └── universe.rs
└── data/demo
```
## 核心模块概览
- `calendar`: 交易日历和滚动窗口工具,负责日频迭代和均线 lookback。
- `instrument`: 证券静态定义。
- `data`: 日频市场、因子、基准、候选资格数据模型与 CSV loader内部预建 symbol 级价格前缀和、按日预排序 eligible universe。
- `universe`: 动态市值带 Universe Selector。
- `portfolio`: 现金、持仓、FIFO lots、T+1 可卖数量、盈亏汇总。
- `rules`: 中国股票规则钩子隔离停牌、涨跌停、T+1 检查。
- `cost`: 佣金、印花税、最低佣金模型。
- `broker`: 同时支持“目标权重再平衡”和显式 `order_target_value / order_value` 订单意图,买单按 100 股向下取整;执行价可选 `open / close / last`
- `strategy`: 引擎驱动的策略 trait 与具体策略实现。
- `engine`: 确定性的逐日回测循环和结果收集。
## 策略实现
示例策略 `CnSmallCapRotationStrategy` 对应一类典型的 A 股小市值轮动逻辑,并在 phase 2 里更贴近原始 jqdata 语义:
1. 用指数点位动态计算市值带:
- `mystart = round((index_close - base_index_level) * xs + base_cap_floor)`
- `myend = mystart + cap_span`
2. 在当前市值带内,按总市值升序取 Top-N。
3. 用指数短均线/长均线关系控制总仓位:
-`MA(short) < MA(long) * rsi_rate` 时降到 `trade_rate`
- 否则恢复到 `1.0`
4.`refresh_rate` 固定频率再平衡。
5. 非再平衡日也会检查止损/止盈钩子并触发退出。
6. 候选过滤纳入资格快照:
- 停牌
- ST
- 新股
- 科创板
- 1 元股
- allow_buy / allow_sell
这个接口不是 `jqdata` 风格的 `before_trading_start` / `handle_data` 直接脚本 API而是
- 策略收到 `StrategyContext`
- 返回 `StrategyDecision`
- 引擎和 broker 负责把目标权重和退出指令变成实际成交
这更接近平台化引擎需要的“策略意图”和“执行语义”分离。
新增的 `JqMicroCapStrategy` 更直接对齐 `/聚宽微盘股策略.py`
1. 指数信号使用 `benchmark_signal_symbol` 的同日 `last_price`
2. 市值带按 `round((index_level - base_index_level) * xs + base_cap_floor)` 动态计算。
3. 在预排序后的 eligible universe 上做带内截取,避免每个交易日全表扫描。
4. 叠加脚本中的盘中规则:
- 涨停开盘 / 跌停开盘
- 当前涨停 / 当前跌停
- 停牌 / ST / 名称含退 / 科创板
- 1 元股
- 个股 5/10/20 日均线过滤
5. 止损/止盈与固定 15 日再平衡同时工作。
6. 当前实现将 `run_daily(10:17/10:18)` 近似为“同日快照决策 + `last_price` 执行”,比传统 `T-1 -> T open` 更接近原脚本。
7. 执行层不再只做目标权重映射,而是支持更接近原脚本的显式订单链路:
- `order_target_value(symbol, 0)` 风格清仓
- `order_value(symbol, cash)` 风格补仓
- 止损/止盈后按剩余现金和剩余槽位补首个可买标的
- 定期调仓时先卖出池外持仓,再按固定现金分配逐笔买入
## 与原始 jqdata 策略族的映射
如果原始逻辑大致是:
- 依据指数点位动态切换可接受市值带
- 从候选股票里选最小市值若干只
- 按均线决定是否降仓
- 周期性调仓
- 带止损/止盈
那么本仓库中的映射关系是:
- `get_fundamentals` / `valuation.market_cap` -> `DailyFactorSnapshot.market_cap_bn`
- `get_price` / `history` -> `DailyMarketSnapshot` + `BenchmarkSnapshot`
- `set_benchmark` -> `BacktestConfig.benchmark_code`
- `filter_paused` / `filter_st` / 新股过滤 -> `CandidateEligibility`
- `order_target_value` / `order_value` -> `StrategyDecision.order_intents``BrokerSimulator` 顺序解释执行
- 风险控制逻辑 -> `CnSmallCapRotationStrategy::gross_exposure`
## Phase 2 新增内容
- `DataSet::bundle_on(date)`:引入按日 snapshot bundle 视图,方便未来直接对接 FiDataCenter / FiDataScraper 预计算快照
- `DataSet::from_partitioned_dir(path)`:新增按日分区 snapshot 目录读取入口,为真实回测数据源接入打基础
- 策略诊断输出equity curve 里新增 `diagnostics` 字段,记录市值带、候选样本、退出原因等信息
- 候选资格快照扩展:补入 `is_kcb``is_one_yuan`
- 增加策略选择行为测试
## Phase 3 新增内容
- `DataSet` 新增 symbol 级价格前缀和,均线查询变为 O(1)
- `DynamicMarketCapBandSelector` 新增预排序 eligible universe + 二分带内截取
- `BrokerSimulator` 新增 `execution_price_field`
- `BacktestEngine` 新增 `decision_lag_trading_days`
- 新增 `JqMicroCapStrategy` 和对应测试
- `StrategyDecision` / `BrokerSimulator` 新增显式订单意图,开始覆盖 `order_target_value / order_value` 语义
## 当前仍保留的简化点
下面这些是刻意保留为 v1 简化,而不是遗漏:
- 只支持日频 snapshot不直接做逐笔 tick 回放。
- `JqMicroCapStrategy` 已支持同日 `last_price` 决策/执行,但这仍然是 snapshot 近似,不是盘口逐笔成交。
- 不模拟盘口排队、成交量约束和滑点模型,成交默认按开盘价完成。
- 买单按 100 股整手向下取整,卖单允许按实际持仓数量退出。
- 未处理复权、分红送转、融资融券、可转债、科创板/北交所差异规则。
- 止损止盈仍然是 snapshot 驱动,不是逐笔止损链。
这些简化都在代码结构上留了扩展位,不会阻断后续升级到更完整的执行层。
## 运行方式
默认跑仓库内置 flat demo CSV
```bash
cargo run --bin bt-demo
```
运行更贴近聚宽微盘股脚本的策略:
```bash
FIDC_BT_STRATEGY=jq-microcap \
FIDC_BT_SIGNAL_SYMBOL=000001.SH \
cargo run --release --bin bt-demo
```
如果要接更接近真实数据面的按日分区 snapshot 目录:
```bash
FIDC_BT_DATA_LAYOUT=partitioned \
FIDC_BT_DATA_DIR=/path/to/snapshots \
FIDC_BT_SIGNAL_SYMBOL=000001.SH \
cargo run --bin bt-demo
```
约定目录结构:
```text
snapshots/
├── instruments.csv
├── benchmark/
│ └── YYYY/MM/*.csv
├── market/
│ └── YYYY/MM/*.csv
├── factors/
│ └── YYYY/MM/*.csv
└── candidates/
└── YYYY/MM/*.csv
```
其中:
- `market/`:日级行情快照,可显式携带 `upper_limit/lower_limit/day_open/last_price`
- `factors/`:估值/因子快照,可扩展 `turnover_ratio/effective_turnover_ratio`
- `candidates/`:候选资格/过滤标记快照
- `benchmark/`:业绩基准指数快照
补充说明:
- 策略的“调仓信号指数”可以通过 `FIDC_BT_SIGNAL_SYMBOL` 单独指定,例如 `000001.SH`
- `benchmark/` 仍用于业绩基准和默认风险参考,两者现在不必强制相同
- 分区目录支持递归读取,因此可直接消费 `YYYY/MM/*.csv` 这类真实导出布局
这层接口是为后续对接 `FiDataCenter / FiDataScraper` 的预计算 snapshot 数据准备的。
运行后会生成:
- `output/demo/equity_curve.csv`
- `output/demo/trades.csv`
- `output/demo/holdings_summary.csv`
## 测试与构建
```bash
cargo fmt
cargo test
cargo build
```
## 为什么这个设计适合后续做快
这个版本已经按“预计算后高速回放”的思路组织:
- 因子与资格数据和市场行情解耦,适合把 `T x N` 的选股输入预先展开。
- 快照结构是列式数据库友好的固定字段模型,后续可以自然对接 ClickHouse/Parquet。
- Engine 逐日回放时只做:
- 取当天切片
- 策略计算 target weights
- broker 做持仓差量执行
- 不把查询逻辑塞进策略内部,避免回测时频繁回源数据层。
如果未来把日频因子、资格标记、可交易标记和 `day_open / last_price / high_limit / low_limit` 全部预计算到列式存储再按日期分块读入内存6 年全市场回测在分钟级是合理目标,原因是:
- 回测时不再做昂贵的 SQL join
- 因子筛选可直接消费预先物化并排序的 snapshot
- 组合调仓只关心“目标持仓”和“当前持仓”的差量
- 事件流是 append-only适合批量写出和后处理分析
- 均线查询通过 prefix sums 变成 O(1)
- 市值带选股通过预排序 universe + 二分定位变成 O(log N + K)
## 距离真实 6 年 / 5 分钟平台还差什么
当前仓库已经有“核心引擎 + 规则钩子 + 策略接口 + demo 回放”,但距离生产级目标还差:
- 真实 snapshot loader接入 FiDataCenter / FiDataScraper 的 ClickHouse / Parquet / PostgreSQL 预计算快照,而不是 demo CSV
- 分钟级执行层:把当前 `T-1 决策 / T 开盘执行` 扩展到更接近 `10:17 / 10:18` 的分钟级执行语义
- 更完整的 A 股规则:复权、分红、涨跌停细分、创业板/北交所规则、成交量约束、滑点模型
- 更高效的数据访问:按日期块和列式布局一次性加载 6 年快照,避免回测时回源拼表
- 批量参数回测:多个参数集共享预计算快照与候选池缓存
## Roadmap
- 引入更明确的事件总线和 portfolio/account ledger 分层
- 增加多 benchmark、多 universe、多个 broker model
- 支持企业行为、前后复权与现金分红
- 增加滑点、量比约束、成交量参与率
- 增加 parquet / ClickHouse 数据源与预计算管线
- 增加指标分析、分组收益、归因和 walk-forward 框架