fidc-backtest-engine

一个面向中国 A 股长周期选股策略的 Rust 回测核心骨架。这个仓库的第一版目标不是“玩具回测器”,而是提供一个可以继续演化为平台化引擎的最小可用核心,方向参考 nautilus_trader 的分层架构和 rqalpha 的中国股票规则约束。

当前能力

  • Phase 3增加预索引数据层、可配置决策/执行语义,以及更贴近聚宽微盘股脚本的 native 策略
  • 日频交易日历与确定性逐日回放
  • A 股日频市场快照、估值/因子快照、基准快照、候选资格标记
  • 策略接口与引擎驱动,不直接模拟 jqdata API
  • BacktestConfig 支持 decision_lag_trading_daysexecution_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 布局

.
├── 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_intentsBrokerSimulator 顺序解释执行
  • 风险控制逻辑 -> CnSmallCapRotationStrategy::gross_exposure

Phase 2 新增内容

  • DataSet::bundle_on(date):引入按日 snapshot bundle 视图,方便未来直接对接 FiDataCenter / FiDataScraper 预计算快照
  • DataSet::from_partitioned_dir(path):新增按日分区 snapshot 目录读取入口,为真实回测数据源接入打基础
  • 策略诊断输出equity curve 里新增 diagnostics 字段,记录市值带、候选样本、退出原因等信息
  • 候选资格快照扩展:补入 is_kcbis_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

cargo run --bin bt-demo

运行更贴近聚宽微盘股脚本的策略:

FIDC_BT_STRATEGY=jq-microcap \
FIDC_BT_SIGNAL_SYMBOL=000001.SH \
cargo run --release --bin bt-demo

如果要接更接近真实数据面的按日分区 snapshot 目录:

FIDC_BT_DATA_LAYOUT=partitioned \
FIDC_BT_DATA_DIR=/path/to/snapshots \
FIDC_BT_SIGNAL_SYMBOL=000001.SH \
cargo run --bin bt-demo

约定目录结构:

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

测试与构建

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 框架
Description
China A-share long-term backtest engine core, referencing nautilus_trader architecture and rqalpha market rules.
Readme 9.3 MiB
Languages
Rust 100%