157 lines
6.1 KiB
Markdown
157 lines
6.1 KiB
Markdown
# fidc-backtest-engine
|
||
|
||
一个面向中国 A 股长周期选股策略的 Rust 回测核心骨架。这个仓库的第一版目标不是“玩具回测器”,而是提供一个可以继续演化为平台化引擎的最小可用核心,方向参考 `nautilus_trader` 的分层架构和 `rqalpha` 的中国股票规则约束。
|
||
|
||
## 当前能力
|
||
|
||
- 日频交易日历与确定性逐日回放
|
||
- 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 股小市值轮动逻辑:
|
||
|
||
1. 用指数点位相对基准水平切换市值带:
|
||
- 强势区间:更偏小市值
|
||
- 中性区间:中小市值
|
||
- 弱势区间:偏大一些的防御市值带
|
||
2. 在当前市值带内,按总市值升序取 Top-N。
|
||
3. 用指数短均线/长均线关系控制总仓位:
|
||
- `1.0`: 风险偏好正常
|
||
- `0.5`: 降半仓
|
||
- `0.0`: 全部转现金
|
||
4. 固定交易日频率再平衡。
|
||
5. 非再平衡日也会检查止损/止盈钩子并触发退出。
|
||
|
||
这个接口不是 `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`
|
||
|
||
## V1 明确简化点
|
||
|
||
下面这些是刻意保留为 v1 简化,而不是遗漏:
|
||
|
||
- 只支持日频,不做分钟级、集合竞价、盘中撮合。
|
||
- 决策基于 `T-1` 收盘后可见数据,在 `T` 开盘价执行。
|
||
- 不模拟盘口排队、成交量约束和滑点模型,成交默认按开盘价完成。
|
||
- 买单按 100 股整手向下取整,卖单允许按实际持仓数量退出。
|
||
- 未处理复权、分红送转、融资融券、可转债、科创板/北交所差异规则。
|
||
- 止损止盈基于上一交易日收盘价相对持仓成本触发,下一交易日开盘执行。
|
||
|
||
这些简化都在代码结构上留了扩展位,不会阻断后续升级到更完整的执行层。
|
||
|
||
## 运行方式
|
||
|
||
```bash
|
||
cargo run --bin bt-demo
|
||
```
|
||
|
||
运行后会生成:
|
||
|
||
- `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,适合批量写出和后处理分析
|
||
|
||
## Roadmap
|
||
|
||
- 引入更明确的事件总线和 portfolio/account ledger 分层
|
||
- 增加多 benchmark、多 universe、多个 broker model
|
||
- 支持企业行为、前后复权与现金分红
|
||
- 增加滑点、量比约束、成交量参与率
|
||
- 增加 parquet / ClickHouse 数据源与预计算管线
|
||
- 增加指标分析、分组收益、归因和 walk-forward 框架
|