SuperAPI AI Directory

qmt-inner-backtest

Testing & quality

View repository

Updated 10-15 17:32

About this skill

QMT Built-in Factor Backtest

Overview

Based on scripts/daily-factors-backtest.py generate QMT Strategy Editor built-in backtest script.

Core pattern:after_init Precompute full-period factors and buy/sell signals → handlebar Execute trades on rebalance days。

Template path: in this skill directory, scripts/daily-factors-backtest.py(relative to the directory containing SKILL.md)

Use Cases

Suitable Not suitable
Daily-frequency cross-sectional factor stock selection (Barra-style processing) Tick/minute high frequency
Fixed holding count Top-N equal-weight rebalancing Futures opening and closing positions (qmt-future-trade, planned)
Research report factor replication, upper/lower shadow, value/momentum, etc. Target position-type futures live trading (qmt-live-strategy-template, planned)
SW industry + market cap neutralization Signal push only (qmt-live-signal-feishu, planned)

Template Architecture (must understand before modifying)

daily-factors-backtest.py
├── 文件头 # coding:gbk + 策略说明 docstring
├── 因子函数库          ← 【主要替换区】factor_xxx + 中性化/去极值
├── init(C)             ← 【配置区】回测区间、股票池、资金、因子参数
├── after_init(C)       ← 【信号区】拉数据 → 算因子 → 过滤 → 生成 g.buy/sell_signals
├── handlebar(C)        ← 【执行区】调仓日卖出/买入(通常保留)
├── 交易执行函数         ← 通常原样保留
└── 辅助工具函数         ← 通常原样保留(IPO/ST/涨跌停/财务宽表)

Responsibilities of Each Section

Section Function/Variable What it does
Global state g = G() Shared parameters, signal matrix, positions across functions
Factor library factor_ubl(...) Input wide tables such as OHLCV/market cap, output factor DataFrame (index=date, columns=stocks)
Factor post-processing filter_extreme_mad_df / neutralize_by_market_cap / neutralize_by_industry_zscore / cross_section_zscore Barra-style pipeline, keep or remove according to research report requirements
Initialization init(C) Set g.start_date/g.end_date、g.stock_pool、g.max_positions、g.rebalance_days etc.;predefine g.buy_signals/g.sell_signals anti-empty matrix
Precomputation after_init(C) One-time pull of full-period market data + financials → compute factors → IPO/ST/suspension filtering → cross-sectional ranking → shift(1) generate T+1 signals
Execution handlebar(C) Rebalance every g.rebalance_days trading days: sell first then buy, execute at open price
Trading execute_sell/buy_signals Do not buy at limit-up, do not sell at limit-down; STAR Market 200 shares, others integer multiples of 100 shares
Auxiliary get_ipo_mask / get_st_mask / get_financial_wide_table Listed for at least 120 days, ST periods, financial field wide tables

Signal Timing (prevent look-ahead bias)

df_rank = df_factor_filtered.rank(axis=1, ascending=g.rank_ascending)
df_is_top_n = df_rank <= g.max_positions
g.buy_signals = df_is_top_n.shift(1).fillna(False)   # T 日因子 → T+1 日买入
g.sell_signals = ~g.buy_signals

ProhibitedRemove .shift(1), unless the user explicitly requires same-day close rebalancing and accepts look-ahead bias.

Agent Workflow

1. Interpret strategy input

The user may provide: text description, research report PDF, screenshot, existing factor formula. Extract and output strategy specification table(give to user for confirmation before generation):

## 策略规格(待确认)

| 项 | 内容 |
|----|------|
| 策略名称 | |
| 因子公式 | 逐行写明计算步骤 |
| 所需行情字段 | open/high/low/close/volume/... |
| 所需财务字段 | 如 CAPITALSTRUCTURE.free_float_capital |
| 因子窗口 | std_period / factor_period 等 |
| 排序方向 | ascending=True(值越小越好)或 False |
| 中性化 | 市值 OLS / 申万行业 Z-score / 无 |
| 股票池 | 如 中证1000、沪深300、全 A |
| 持仓数 | max_positions |
| 调仓频率 | rebalance_days(交易日) |
| 回测区间 | start_date ~ end_date |
| 初始资金 | initial_capital |

Key points for interpreting research reports/PDFs:

  • Distinguish between 'factor definition' and 'portfolio construction' (Top 10, 5-day rebalancing, etc.)
  • Note the OHLC formulas corresponding to terms such as 'candle upper shadow' and 'Williams lower shadow'
  • Record the outlier removal method (how many multiples of MAD), neutralization order
  • Mark assumptions when parameters are missing; do not silently fabricate

Screenshot interpretation key points:

  • Cross-check the formulas, parameter tables, and backtest settings in the screenshot
  • The stock pool name must match the QMT sector name (see the sector table below)

2. Copy the template and replace

  1. Read scripts/daily-factors-backtest.py in full as the template
  2. Output to the user-specified path, default strategies/<策略名>-backtest/backtest.py
  3. Only change the necessary parts, keep the trade execution and helper functions as-is

Required changes list:

Location What to change
File header docstring Strategy name, research report source, factor logic, parameter description
logger name Consistent with the strategy, for easier log filtering
factor_xxx() New factor calculation; function name corresponds to g.factor_name corresponding
init() Backtest period, stock pool, capital, factor parameters,rank_ascending
after_init() Data field retrieval, call the new factor function; add or remove neutralization steps according to the research report
init log text Strategy name and parameter summary

Usually not changed: handlebar、execute_*、get_ipo_mask、get_st_mask, limit-up/limit-down determination,get_df_ex。

3. Factor replacement modes

Mode A — Single-factor Top-N (template default)

def factor_xxx(daily_open, daily_high, daily_low, daily_close, daily_market_cap,
               stock_industry_map=None, **kwargs):
    # 1. 原始特征
    # 2. 滚动统计
    # 3. 去极值 → 市值中性 → 行业中性(可选)
    # 4. 截面 Z-score(单因子可跳过第 4 步)
    return factor_df

Mode B — Multi-sub-factor synthesis

Each sub-factor independently goes through MAD + neutralization, and finally zscore(A) + zscore(B) or weighted sum.

Mode C — No neutralization needed

Skip neutralize_by_*, only filter_extreme_mad_df + cross_section_zscore。

Mode D — Extra financial factors needed

In after_init use get_financial_wide_table(C, g.stock_pool, 'TABLE.field', ...) to pull the wide table and pass it to factor_xxx。

Common financial field examples:

  • CAPITALSTRUCTURE.free_float_capital — free-float share capital (used by the template for market cap)
  • PERSHAREINDEX.eps — earnings per share
  • ASHAREINCOME.net_profit_incl_min_int_inc — net profit

4. Self-check after generation

  • First line # coding:gbk
  • init predefined in g.buy_signals / g.sell_signals empty DataFrame
  • after_init When market data is empty, return, do not throw uncaught exceptions
  • Signals include .shift(1)
  • g.start_date and g.backtest_start_time period consistent
  • Stock pool C.get_stock_list_in_sector(...) name exists in QMT
  • The shape of the factor function return value aligns with daily_close (index=date, columns=stock code)
  • rank_ascending Consistent with the research report's "larger factor is better/smaller factor is better"

Backtest items the user must configure

After generating the script,must remind the userto verify the following configuration in QMT (the Agent does not operate the QMT GUI on behalf of the user):

A. In-script init() parameters

Parameter Format Description
g.start_date / g.end_date 'YYYYMMDD' after_init Start/end for pulling market/financial data
g.backtest_start_time / g.backtest_end_time 'YYYY-MM-DD HH:MM:SS' Consistent with the period above
g.stock_pool Sector name or code list C.get_stock_list_in_sector("中证1000")
g.initial_capital Integer Initial capital
g.max_positions Integer Number of holdings = Top N
g.cash_usage_ratio 0~1 Proportion of available capital on rebalance day, default 0.95
g.rebalance_days Integer Rebalance every N trading days
g.accid 'test' Backtest account, keep as test

B. QMT strategy editor backtest panel

The user needs to, in QMT Model Trading / Strategy Research manually set:

  1. Backtest start and end dates — consistent with the script g.backtest_* consistent
  2. Initial capital — consistent with g.initial_capital consistent
  3. Benchmark — e.g., CSI 300, CSI 1000 (for comparison)
  4. Commission / stamp duty / slippage — if specified in the research report, tell the user to set it per the report
  5. Price adjustment method — in the script dividend_type='front_ratio'(forward-adjusted), the panel must be consistent
  6. Instrument type — Stocks

C. Common QMT sector names

User wording QMT sector name
CSI 1000 "中证1000"
CSI 300 "沪深300"
CSI 500 "中证500"
All A-shares "沪深A股"
ChiNext "创业板"
STAR Market "科创板"
Shenwan Level-1 industries get_sector_list('申万一级行业板块') each industry under

Sector names may differ slightly depending on the QMT version; if get_stock_list_in_sector fails, prompt the user to confirm the exact name in the local QMT sector list.

D. Data prerequisites

  1. QMT client is logged in
  2. In "Data Management", download the backtest period daily bar data and the required financial data
  3. The more constituent stocks in the universe,after_init the slower (CSI 1000 has about 1000 stocks, which is normal)

How to run

QMT built-in backtest Do not run in the conda command line, the process is as follows:

  1. Copy the generated .py to the QMT strategy directory, or create a new strategy in the strategy editor and paste the code
  2. After saving, click Compile, and confirm there are no syntax errors
  3. Open the Backtest panel, and set the date/capital/fees
  4. Run the backtest and view the return curve, positions, and log output
  5. In the logs, pay attention to:[数据检查]、[因子] step statistics,【最新调仓建议】

If the user needs to keep it in the project:

strategies/<name>-backtest/
└── backtest.py    # 生成的策略文件

Template for confirming with the user

Before strategy generation:

Please confirm in the strategy specification table: stock universe, backtest period, number of holdings, rebalancing frequency, and factor direction.
If there are parameters not specified in the research report (such as MAD multiplier, neutralization order), I will handle them according to the master template defaults and annotate them.

After delivering the script:

The script has been generated. In QMT, please:

  1. Check that the backtest start and end dates are consistent with the script init() consistent
  2. Confirm that the sector names of the stock universe are available in the local QMT
  3. Download the daily bar and financial data for the corresponding period
  4. Set commission/slippage (if the research report has requirements, follow the report)
  5. Compile and run the backtest

By default, factors are calculated at the close of day T, and positions are rebalanced at the open of day T+1. If you need to change the rebalancing logic, please specify.

Correspondence with the master template example

Master template factor_ubl implements the Soochow Securities upper and lower shadow factor:

蜡烛上影线 = High - max(Open, Close)
威廉下影线 = Close - Low
→ 标准化 → 20日 std/mean → MAD去极值 → 市值OLS中性 → 申万行业Z-score → 截面Z-score → 相加
→ 值越小越好 → Top 10 → 每5日调仓

When replacing with other factors, keep the same "wide table in, wide table out" interface, and tailor the rest of the pipeline according to the research report.

Prohibitions

  • Do not remove # coding:gbk
  • Do not remove init the predefined signal variables in
  • Do not click Run in QMT on the user's behalf by default; only generate the script and provide a configuration checklist
  • Do not mix futures order placement logic into this framework
  • Do not introduce future data into the factor matrix (use shift(1) or an equivalent lag)
  • Do not submit changes containing a fund account number without user confirmation

Quick example

User requirement: Reproduce the 20-day momentum factor, CSI 300 constituents, Top 20, monthly rebalancing.

Agent action:

  1. Output the strategy specification table for confirmation
  2. Create factor_momentum(daily_close, lookback=20):daily_close / daily_close.shift(20) - 1
  3. MAD winsorization + market-cap neutrality (if required by the research report)
  4. g.rank_ascending = False(the larger the momentum, the better)
  5. g.stock_pool = C.get_stock_list_in_sector("沪深300")
  6. g.max_positions = 20,g.rebalance_days = 20(approximately monthly)
  7. Remind the user to download CSI 300 constituent daily data and set the backtest fee rate