Files
fidc-backtest-engine/README.md
2026-04-06 23:56:37 -07:00

157 lines
6.1 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` 的中国股票规则约束。
## 当前能力
- 日频交易日历与确定性逐日回放
- 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 框架