# 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 框架