Files
fidc-backtest-engine/README.md
2026-04-07 00:40:53 -07:00

212 lines
8.3 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 2增加 snapshot bundle 视图与更贴近 jqdata 策略语义的动态市值带策略
- 日频交易日历与确定性逐日回放
- A 股日频市场快照、估值/因子快照、基准快照、候选资格标记
- 策略接口与引擎驱动,不直接模拟 `jqdata` API
- Universe 选择器:按指数位置动态切换市值带,再取最小市值 Top-N
- 风险节流:基于指数均线状态切换 100% / 50% / 0% 仓位
- Broker Simulator按次日开盘价撮合支持手续费、印花税、最小佣金
- 中国 A 股规则钩子T+1、停牌、涨停不可买、跌停不可卖
- 回测输出:权益曲线、成交记录、期末持仓摘要
- `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。
- `universe`: 动态市值带 Universe Selector。
- `portfolio`: 现金、持仓、FIFO lots、T+1 可卖数量、盈亏汇总。
- `rules`: 中国股票规则钩子隔离停牌、涨跌停、T+1 检查。
- `cost`: 佣金、印花税、最低佣金模型。
- `broker`: 目标权重到订单执行的模拟器,先卖后买,买单按 100 股向下取整。
- `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 负责把目标权重和退出指令变成实际成交
这更接近平台化引擎需要的“策略意图”和“执行语义”分离。
## 与原始 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` -> `StrategyDecision.target_weights``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`
- 增加策略选择行为测试
## V1 / V2 当前仍保留的简化点
下面这些是刻意保留为 v1 简化,而不是遗漏:
- 只支持日频,不做分钟级、集合竞价、盘中撮合。
- 决策基于 `T-1` 收盘后可见数据,在 `T` 开盘价执行。
- 不模拟盘口排队、成交量约束和滑点模型,成交默认按开盘价完成。
- 买单按 100 股整手向下取整,卖单允许按实际持仓数量退出。
- 未处理复权、分红送转、融资融券、可转债、科创板/北交所差异规则。
- 止损止盈基于上一交易日收盘价相对持仓成本触发,下一交易日开盘执行。
这些简化都在代码结构上留了扩展位,不会阻断后续升级到更完整的执行层。
## 运行方式
默认跑仓库内置 flat demo CSV
```bash
cargo run --bin bt-demo
```
如果要接更接近真实数据面的按日分区 snapshot 目录:
```bash
FIDC_BT_DATA_LAYOUT=partitioned \
FIDC_BT_DATA_DIR=/path/to/snapshots \
cargo run --bin bt-demo
```
约定目录结构:
```text
snapshots/
├── instruments.csv
├── benchmark/
│ ├── 2024-01-02.csv
│ └── ...
├── market/
├── factors/
└── candidates/
```
其中:
- `market/`:日级行情快照
- `factors/`:估值/因子快照
- `candidates/`:候选资格/过滤标记快照
- `benchmark/`:指数快照
这层接口是为后续对接 `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 做持仓差量执行
- 不把查询逻辑塞进策略内部,避免回测时频繁回源数据层。
如果未来把日频因子、资格标记、可交易标记和开/收盘价全部预计算到列式存储再按日期分块读入内存6 年全市场回测在 5 分钟内是合理目标,原因是:
- 回测时不再做昂贵的 SQL join
- 因子筛选可直接消费预先物化的 snapshot
- 组合调仓只关心“目标持仓”和“当前持仓”的差量
- 事件流是 append-only适合批量写出和后处理分析
## 距离真实 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 框架