Improve jq microcap execution semantics

This commit is contained in:
boris
2026-04-18 18:02:50 +08:00
parent 9f4165e689
commit 0e2c25e4c4
26 changed files with 5058 additions and 362 deletions

View File

@@ -4,15 +4,18 @@
## 当前能力
- Phase 2:增加 snapshot bundle 视图与更贴近 jqdata 策略语义的动态市值带策略
- Phase 3:增加预索引数据层、可配置决策/执行语义,以及更贴近聚宽微盘股脚本的 native 策略
- 日频交易日历与确定性逐日回放
- A 股日频市场快照、估值/因子快照、基准快照、候选资格标记
- 策略接口与引擎驱动,不直接模拟 `jqdata` API
- `BacktestConfig` 支持 `decision_lag_trading_days``execution_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 布局
@@ -43,12 +46,12 @@
- `calendar`: 交易日历和滚动窗口工具,负责日频迭代和均线 lookback。
- `instrument`: 证券静态定义。
- `data`: 日频市场、因子、基准、候选资格数据模型与 CSV loader。
- `data`: 日频市场、因子、基准、候选资格数据模型与 CSV loader;内部预建 symbol 级价格前缀和、按日预排序 eligible universe
- `universe`: 动态市值带 Universe Selector。
- `portfolio`: 现金、持仓、FIFO lots、T+1 可卖数量、盈亏汇总。
- `rules`: 中国股票规则钩子隔离停牌、涨跌停、T+1 检查。
- `cost`: 佣金、印花税、最低佣金模型。
- `broker`: 目标权重到订单执行的模拟器,先卖后买,买单按 100 股向下取整
- `broker`: 同时支持“目标权重再平衡”和显式 `order_target_value / order_value` 订单意图,买单按 100 股向下取整;执行价可选 `open / close / last`
- `strategy`: 引擎驱动的策略 trait 与具体策略实现。
- `engine`: 确定性的逐日回测循环和结果收集。
@@ -81,6 +84,25 @@
这更接近平台化引擎需要的“策略意图”和“执行语义”分离。
新增的 `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 策略族的映射
如果原始逻辑大致是:
@@ -97,7 +119,7 @@
- `get_price` / `history` -> `DailyMarketSnapshot` + `BenchmarkSnapshot`
- `set_benchmark` -> `BacktestConfig.benchmark_code`
- `filter_paused` / `filter_st` / 新股过滤 -> `CandidateEligibility`
- `order_target_value` -> `StrategyDecision.target_weights``BrokerSimulator` 解释执行
- `order_target_value` / `order_value` -> `StrategyDecision.order_intents``BrokerSimulator` 顺序解释执行
- 风险控制逻辑 -> `CnSmallCapRotationStrategy::gross_exposure`
## Phase 2 新增内容
@@ -108,16 +130,25 @@
- 候选资格快照扩展:补入 `is_kcb``is_one_yuan`
- 增加策略选择行为测试
## V1 / V2 当前仍保留的简化点
## 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 简化,而不是遗漏:
- 只支持日频,不做分钟级、集合竞价、盘中撮合
- 决策基于 `T-1` 收盘后可见数据,在 `T` 开盘价执行
- 只支持日频 snapshot不直接做逐笔 tick 回放
- `JqMicroCapStrategy` 已支持同日 `last_price` 决策/执行,但这仍然是 snapshot 近似,不是盘口逐笔成交
- 不模拟盘口排队、成交量约束和滑点模型,成交默认按开盘价完成。
- 买单按 100 股整手向下取整,卖单允许按实际持仓数量退出。
- 未处理复权、分红送转、融资融券、可转债、科创板/北交所差异规则。
- 止损止盈基于上一交易日收盘价相对持仓成本触发,下一交易日开盘执行
- 止损止盈仍然是 snapshot 驱动,不是逐笔止损链
这些简化都在代码结构上留了扩展位,不会阻断后续升级到更完整的执行层。
@@ -129,6 +160,14 @@
cargo run --bin bt-demo
```
运行更贴近聚宽微盘股脚本的策略:
```bash
FIDC_BT_STRATEGY=jq-microcap \
FIDC_BT_SIGNAL_SYMBOL=000001.SH \
cargo run --release --bin bt-demo
```
如果要接更接近真实数据面的按日分区 snapshot 目录:
```bash
@@ -154,7 +193,7 @@ snapshots/
```
其中:
- `market/`:日级行情快照,可显式携带 `upper_limit/lower_limit`
- `market/`:日级行情快照,可显式携带 `upper_limit/lower_limit/day_open/last_price`
- `factors/`:估值/因子快照,可扩展 `turnover_ratio/effective_turnover_ratio`
- `candidates/`:候选资格/过滤标记快照
- `benchmark/`:业绩基准指数快照
@@ -192,12 +231,14 @@ cargo build
- broker 做持仓差量执行
- 不把查询逻辑塞进策略内部,避免回测时频繁回源数据层。
如果未来把日频因子、资格标记、可交易标记和开/收盘价全部预计算到列式存储再按日期分块读入内存6 年全市场回测在 5 分钟是合理目标,原因是:
如果未来把日频因子、资格标记、可交易标记和 `day_open / last_price / high_limit / low_limit` 全部预计算到列式存储再按日期分块读入内存6 年全市场回测在分钟是合理目标,原因是:
- 回测时不再做昂贵的 SQL join
- 因子筛选可直接消费预先物化的 snapshot
- 因子筛选可直接消费预先物化并排序的 snapshot
- 组合调仓只关心“目标持仓”和“当前持仓”的差量
- 事件流是 append-only适合批量写出和后处理分析
- 均线查询通过 prefix sums 变成 O(1)
- 市值带选股通过预排序 universe + 二分定位变成 O(log N + K)
## 距离真实 6 年 / 5 分钟平台还差什么