$npx -y skills add dfkai/xtquantai --skill qmt-inner-backtest根据策略描述、研报 PDF 或截图,解读因子/选股逻辑,基于 scripts/daily-factors-backtest.py 框架生成 QMT 内置日频因子回测脚本。用户提到 QMT 内置回测、因子选股回测、截面因子、 研报复现、handlebar 回测、after_init 预计算信号时使用。
| 1 | # QMT 内置因子回测 |
| 2 | |
| 3 | ## 概述 |
| 4 | |
| 5 | 基于 `scripts/daily-factors-backtest.py` 生成 **QMT 策略编辑器内置回测** 脚本。 |
| 6 | |
| 7 | 核心模式:**`after_init` 预计算全区间因子与买卖信号 → `handlebar` 按调仓日执行交易**。 |
| 8 | |
| 9 | 母版路径:本 skill 目录下的 `scripts/daily-factors-backtest.py`(相对 SKILL.md 所在目录) |
| 10 | |
| 11 | ## 适用场景 |
| 12 | |
| 13 | | 适合 | 不适合 | |
| 14 | |------|--------| |
| 15 | | 日频截面因子选股(Barra 风格处理) | Tick/分钟高频 | |
| 16 | | 固定持仓数 Top-N 等权调仓 | 期货开平仓(`qmt-future-trade`,规划中) | |
| 17 | | 研报因子复现、上下影线/价值/动量等 | 目标持仓型期货实盘(`qmt-live-strategy-template`,规划中) | |
| 18 | | 申万行业 + 市值中性化 | 仅要信号推送(`qmt-live-signal-feishu`,规划中) | |
| 19 | |
| 20 | ## 母版架构(必须理解再改) |
| 21 | |
| 22 | ``` |
| 23 | daily-factors-backtest.py |
| 24 | ├── 文件头 # coding:gbk + 策略说明 docstring |
| 25 | ├── 因子函数库 ← 【主要替换区】factor_xxx + 中性化/去极值 |
| 26 | ├── init(C) ← 【配置区】回测区间、股票池、资金、因子参数 |
| 27 | ├── after_init(C) ← 【信号区】拉数据 → 算因子 → 过滤 → 生成 g.buy/sell_signals |
| 28 | ├── handlebar(C) ← 【执行区】调仓日卖出/买入(通常保留) |
| 29 | ├── 交易执行函数 ← 通常原样保留 |
| 30 | └── 辅助工具函数 ← 通常原样保留(IPO/ST/涨跌停/财务宽表) |
| 31 | ``` |
| 32 | |
| 33 | ### 各段职责 |
| 34 | |
| 35 | | 段 | 函数/变量 | 做什么 | |
| 36 | |----|-----------|--------| |
| 37 | | 全局状态 | `g = G()` | 跨函数共享参数、信号矩阵、持仓 | |
| 38 | | 因子库 | `factor_ubl(...)` | 输入 OHLCV/市值等宽表,输出因子 DataFrame(index=日期, columns=股票) | |
| 39 | | 因子后处理 | `filter_extreme_mad_df` / `neutralize_by_market_cap` / `neutralize_by_industry_zscore` / `cross_section_zscore` | Barra 风格流水线,按研报需求保留或删减 | |
| 40 | | 初始化 | `init(C)` | 设 `g.start_date`/`g.end_date`、`g.stock_pool`、`g.max_positions`、`g.rebalance_days` 等;**预定义** `g.buy_signals`/`g.sell_signals` 防空矩阵 | |
| 41 | | 预计算 | `after_init(C)` | 一次性拉全区间行情+财务 → 算因子 → IPO/ST/停牌过滤 → 截面排名 → `shift(1)` 生成 T+1 信号 | |
| 42 | | 执行 | `handlebar(C)` | 每 `g.rebalance_days` 个交易日调仓:先卖后买,开盘价成交 | |
| 43 | | 交易 | `execute_sell/buy_signals` | 涨停不买、跌停不卖;科创板 200 股、其余 100 股整数倍 | |
| 44 | | 辅助 | `get_ipo_mask` / `get_st_mask` / `get_financial_wide_table` | 上市满 120 天、ST 区间、财务字段宽表 | |
| 45 | |
| 46 | ### 信号时序(防未来函数) |
| 47 | |
| 48 | ```python |
| 49 | df_rank = df_factor_filtered.rank(axis=1, ascending=g.rank_ascending) |
| 50 | df_is_top_n = df_rank <= g.max_positions |
| 51 | g.buy_signals = df_is_top_n.shift(1).fillna(False) # T 日因子 → T+1 日买入 |
| 52 | g.sell_signals = ~g.buy_signals |
| 53 | ``` |
| 54 | |
| 55 | **禁止**去掉 `.shift(1)`,除非用户明确要求当日收盘调仓且接受前视偏差。 |
| 56 | |
| 57 | ## Agent 工作流 |
| 58 | |
| 59 | ### 1. 解读策略输入 |
| 60 | |
| 61 | 用户可能提供:文字描述、研报 PDF、截图、已有因子公式。提取并输出 **策略规格表**(生成前给用户确认): |
| 62 | |
| 63 | ```markdown |
| 64 | ## 策略规格(待确认) |
| 65 | |
| 66 | | 项 | 内容 | |
| 67 | |----|------| |
| 68 | | 策略名称 | | |
| 69 | | 因子公式 | 逐行写明计算步骤 | |
| 70 | | 所需行情字段 | open/high/low/close/volume/... | |
| 71 | | 所需财务字段 | 如 CAPITALSTRUCTURE.free_float_capital | |
| 72 | | 因子窗口 | std_period / factor_period 等 | |
| 73 | | 排序方向 | ascending=True(值越小越好)或 False | |
| 74 | | 中性化 | 市值 OLS / 申万行业 Z-score / 无 | |
| 75 | | 股票池 | 如 中证1000、沪深300、全 A | |
| 76 | | 持仓数 | max_positions | |
| 77 | | 调仓频率 | rebalance_days(交易日) | |
| 78 | | 回测区间 | start_date ~ end_date | |
| 79 | | 初始资金 | initial_capital | |
| 80 | ``` |
| 81 | |
| 82 | **研报/PDF 解读要点:** |
| 83 | - 区分「因子定义」与「组合构建」(Top 10、5 日调仓等) |
| 84 | - 注意「蜡烛上影线」「威廉下影线」等术语对应的 OHLC 公式 |
| 85 | - 记录去极值方法(MAD 几倍)、中性化顺序 |
| 86 | - 参数缺省时标注假设,不要静默编造 |
| 87 | |
| 88 | **截图解读要点:** |
| 89 | - 对照图中公式、参数表、回测设置截图 |
| 90 | - 股票池名称必须与 QMT 板块名一致(见下方板块表) |
| 91 | |
| 92 | ### 2. 复制母版并替换 |
| 93 | |
| 94 | 1. 读取 `scripts/daily-factors-backtest.py` 全文作模板 |
| 95 | 2. 输出到用户指定路径,默认 `strategies/<策略名>-backtest/backtest.py` |
| 96 | 3. **只改必要部分**,交易执行与辅助函数原样保留 |
| 97 | |
| 98 | **必改清单:** |
| 99 | |
| 100 | | 位置 | 改什么 | |
| 101 | |------|--------| |
| 102 | | 文件头 docstring | 策略名、研报来源、因子逻辑、参数说明 | |
| 103 | | `logger` 名称 | 与策略一致,便于日志过滤 | |
| 104 | | `factor_xxx()` | 新因子计算;函数名与 `g.factor_name` 对应 | |
| 105 | | `init()` | 回测区间、股票池、资金、因子参数、`rank_ascending` | |
| 106 | | `after_init()` | 数据字段获取、调用新因子函数;中性化步骤按研报增删 | |
| 107 | | `init` 日志文案 | 策略名称与参数摘要 | |
| 108 | |
| 109 | **通常不改:** `handlebar`、`execute_*`、`get_ipo_mask`、`get_st_mask`、涨跌停判断、`get_df_ex`。 |
| 110 | |
| 111 | ### 3. 因子替换模式 |
| 112 | |
| 113 | **模式 A — 单因子 Top-N(母版默认)** |
| 114 | |
| 115 | ```python |
| 116 | def factor_xxx(daily_open, daily_high, daily_low, daily_close, daily_market_cap, |
| 117 | stock_industry_map=None, **kwargs): |
| 118 | # 1. 原始特征 |
| 119 | # 2. 滚动统计 |
| 120 | # 3. 去极值 → 市值中性 → 行业中性(可选) |
| 121 | # 4. 截面 Z-score(单因子可跳过第 4 步) |
| 122 | return factor_df |
| 123 | ``` |
| 124 | |
| 125 | **模式 B — 多子因子合成** |
| 126 | |
| 127 | 每个子因子独立走 MAD + 中性化,最后 `zscore(A) + zscore(B)` 或加权求和。 |
| 128 | |
| 129 | **模式 C — 无需中性化** |
| 130 | |
| 131 | 跳过 `neutralize_by_*`,仅 `filter_extreme_mad_df` + `cross_section_zscore`。 |
| 132 | |
| 133 | **模式 D — 需额外财务因子** |
| 134 | |
| 135 | 在 `after_init` 用 `get_financial_wide_table(C, g.stock_pool, 'TABLE.field', ...)` 拉宽表,传入 `factor_xxx`。 |
| 136 | |
| 137 | 常用财务字段示例: |
| 138 | - `CAPITALSTRUCTURE.free_float_capital` — 自由流通股本(母版用于市值) |
| 139 | - `PERSHAREINDEX.eps` — 每股收益 |
| 140 | - `ASHAREINCOME.net_profit_incl_min_int_inc` — 净利润 |
| 141 | |
| 142 | ### 4. 生成后自检 |
| 143 | |
| 144 | - [ ] 首行 `# coding:gbk` |
| 145 | - [ ] `init` 中预定义 `g.buy_signals` / `g.sell_signals` 空 DataFrame |
| 146 | - [ ] `after_init` 行情为空时 `return`,不抛未捕获异常 |
| 147 | - [ ] 信号含 `.shift(1)` |
| 148 | - [ ] `g.start_date` 与 `g.backtest_start_time` 区间一致 |
| 149 | - [ ] 股票池 `C.get_stock_list_in_sector(...)` 名称在 QMT 中存在 |
| 150 | - [ ] 因子函数返回值 shape 与 `daily_close` 对齐(index=日期, columns=股票代码) |
| 151 | - [ ] `rank_ascending` 与研报「因子越大越好/越小越好」一致 |
| 152 | |
| 153 | ## 用户必须配置的回测项 |
| 154 | |
| 155 | 生成脚本后,**必须提醒用户**在 QMT 中核对以下配置(Agent 不代替用户在 QMT GUI 操作): |
| 156 | |
| 157 | ### A. 脚本内 `init()` 参数 |
| 158 | |
| 159 | | 参数 | 格式 | 说明 | |
| 160 | |------|------|------| |
| 161 | | `g.start_date` / `g.end_date` | `'YYYYMMDD'` | `after_init` 拉行情/财务的起止 | |
| 162 | | `g.backtest_start_time` / `g.backtest_end_time` | `'YYYY-MM-DD HH:MM:SS'` | 与上面区间一致 | |
| 163 | | `g.stock_pool` | 板块名或代码列表 | `C.get_stock_list_in_sector("中证1000")` | |
| 164 | | `g.initial_capital` | 整数 | 初始资金 | |
| 165 | | `g.max_positions` | 整数 | 持仓只数 = Top N | |
| 166 | | `g.cash_usage_ratio` | 0~1 | 调仓日可用资金比例,默认 0 |