【QMT 量化实战】五因子大盘风险预警系统 完整 Python 实现(下篇)
本文承接《QMT 之如何预警大盘走向(上)》,在上篇我们详解了大盘预警的核心逻辑与五大因子设计思路,本篇将基于迅投 QMT 的
xtquant接口,给出完整可运行的 Python 代码实现,并对回测结果进行实战解读。
一、环境准备与基础配置
1.1 运行环境说明
本代码基于迅投 QMT 官方 Python 接口xtquant开发,可直接在 QMT 客户端内置 Python 环境、或配置了 xtquant 的本地 Python 环境中运行。核心依赖库如下:
numpy:数值计算与矩阵运算pandas:结构化行情数据处理xtquant:QMT 官方行情与交易接口
1.2 全局参数配置
所有回测参数、因子阈值统一配置,严格对应上篇的因子定义,支持按需调优。
import numpy as np
import pandas as pd
from xtquant import xtdata
import datetime
from typing import Tuple, Dict
# ====================== 回测基础参数 ======================
START_DATE = "20260301" # 回测起始日期
END_DATE = "20260630" # 回测结束日期
SH_INDEX = "000001.SH" # 上证指数
HS300_INDEX = "000300.SH" # 沪深300指数
ZZ1000_INDEX = "000852.SH"# 中证1000指数
# ====================== 因子阈值参数 ======================
# 趋势因子参数
TREND_MA20 = 20
TREND_MA60 = 60
TREND_RETURN_DAYS = 20
# 广度因子参数
BREADTH_MA20 = 20
BREADTH_MA60 = 60
BREADTH_MA20_THRESHOLD = 0.35 # MA20站上比例风险阈值
BREADTH_MA60_THRESHOLD = 0.50 # MA60站上比例风险阈值
# 风格因子参数
STYLE_RATIO_MA = 20
STYLE_COMPARE_DAYS = 5
# 波动率因子参数
VOL_SHORT = 10
VOL_LONG = 60
VOL_MULTIPLE = 1.2
# 成交额因子参数
AMOUNT_MA = 20
AMOUNT_MULTIPLE = 1.1
二、通用工具函数封装
先封装底层数据获取和通用指标计算函数,提升代码复用性与可维护性。
2.1 日线行情数据获取
通过 xtquant 下载并格式化指数 / 个股日线数据,统一处理时间索引与成交额单位。
def get_index_daily_data(code: str, start: str, end: str) -> pd.DataFrame:
"""
获取指数/个股日线数据(前复权)
:param code: 股票/指数代码
:param start: 起始日期
:param end: 结束日期
:return: 格式化后的日线DataFrame
"""
# 下载历史行情数据到本地
xtdata.download_history_data(code, "1d", start, end)
# 读取本地行情数据
df = xtdata.get_local_data(
field_list=['open', 'high', 'low', 'close', 'volume', 'amount'],
stock_code=code,
period="1d",
start_time=start,
end_time=end
)
# 数据格式化:重置索引、转换时间格式
df = df.reset_index()
df["time"] = pd.to_datetime(df["time"], unit='ms')
df = df.set_index("time").sort_index()
# 成交额单位转换为万元
df["amount_wan"] = df["amount"] / 10000
return df
2.2 沪深 300 成分股获取
市场广度因子需要遍历沪深 300 全成分股,通过板块接口获取最新成分股列表。
def get_hs300_constituents() -> list:
"""获取沪深300最新成分股列表"""
xtdata.download_sector_data()
constituents = xtdata.get_sector_component(HS300_INDEX)
return [code for code in constituents[0]]
2.3 通用技术指标计算
封装移动平均线、年化波动率两个通用计算函数,供所有因子调用。
def calculate_ma(series: pd.Series, window: int) -> pd.Series:
"""计算移动平均线MA"""
return series.rolling(window=window).mean()
def calculate_annual_vol(series: pd.Series, window: int) -> pd.Series:
"""计算年化波动率 = 日收益率滚动标准差 * sqrt(252)"""
daily_return = series.pct_change()
vol = daily_return.rolling(window=window).std() * np.sqrt(252)
return vol
三、五大预警因子核心实现
每个因子独立封装为函数,输入行情数据,输出每日 0/1 信号(1 表示触发风险预警)。最终 5 个因子得分相加,得到 0-5 分的大盘风险预警总分。
3.1 趋势信号(Trend Signal)
核心逻辑:3 个条件同时满足时,判定为明确下跌趋势,触发预警得 1 分。
- 收盘价低于 20 日均线
- 20 日均线低于 60 日均线(均线空头排列)
- 近 20 日累计收益率为负
def calculate_trend_signal(sh_df: pd.DataFrame) -> pd.Series:
"""
趋势风险信号:3条件全满足得1分
"""
df = sh_df.copy()
# 计算长短周期均线
df["ma20"] = calculate_ma(df["close"], TREND_MA20)
df["ma60"] = calculate_ma(df["close"], TREND_MA60)
# 计算20日区间收益率
df["return_20d"] = df["close"].pct_change(TREND_RETURN_DAYS)
# 三个风险触发条件
cond1 = df["close"] < df["ma20"] # 收盘价跌破MA20
cond2 = df["ma20"] < df["ma60"] # 均线空头排列
cond3 = df["return_20d"] < 0 # 中期收益转负
return (cond1 & cond2 & cond3).astype(int)
3.2 市场广度信号(Breadth Signal)
核心逻辑:基于沪深 300 全成分股统计市场赚钱效应,3 个条件同时满足时,判定为市场普跌、赚钱效应极差,触发预警得 1 分。
- 成分股中站上 MA20 的比例低于 35%
- 成分股中站上 MA60 的比例低于 50%
- 当日创新低个股数量多于创新高个股数量
def calculate_breadth_signal(start: str, end: str) -> pd.Series:
"""
广度风险信号:3条件全满足得1分(基于沪深300成分股)
"""
stock_list = get_hs300_constituents()
signal_dict = {}
# 批量计算每只成分股的均线、新高新低标记
for stock in stock_list:
try:
df = get_index_daily_data(stock, start, end)
if len(df) < 60:
continue
# 计算均线
df["ma20"] = calculate_ma(df["close"], BREADTH_MA20)
df["ma60"] = calculate_ma(df["close"], BREADTH_MA60)
# 标记是否站上均线
df["above_ma20"] = (df["close"] > df["ma20"]).astype(int)
df["above_ma60"] = (df["close"] > df["ma60"]).astype(int)
# 标记60日新高/新低
df["high_60d"] = df["high"].rolling(BREADTH_MA60).max()
df["low_60d"] = df["low"].rolling(BREADTH_MA60).min()
df["is_new_high"] = (df["high"] >= df["high_60d"]).astype(int)
df["is_new_low"] = (df["low"] <= df["low_60d"]).astype(int)
signal_dict[stock] = df
except:
continue
# 按交易日统计全市场指标占比
dates = pd.date_range(start=start, end=end, freq="B")
breadth_df = pd.DataFrame(index=dates)
above_ma20_ratio = []
above_ma60_ratio = []
new_high_cnt = []
new_low_cnt = []
for date in dates:
valid_stocks = [s for s in signal_dict if date in signal_dict[s].index]
if not valid_stocks:
above_ma20_ratio.append(np.nan)
above_ma60_ratio.append(np.nan)
new_high_cnt.append(np.nan)
new_low_cnt.append(np.nan)
continue
ma20_up = 0
ma60_up = 0
high_cnt = 0
low_cnt = 0
for stock in valid_stocks:
row = signal_dict[stock].loc[date]
ma20_up += row["above_ma20"]
ma60_up += row["above_ma60"]
high_cnt += row["is_new_high"]
low_cnt += row["is_new_low"]
total = len(valid_stocks)
above_ma20_ratio.append(ma20_up / total)
above_ma60_ratio.append(ma60_up / total)
new_high_cnt.append(high_cnt)
new_low_cnt.append(low_cnt)
breadth_df["above_ma20_ratio"] = above_ma20_ratio
breadth_df["above_ma60_ratio"] = above_ma60_ratio
breadth_df["new_high"] = new_high_cnt
breadth_df["new_low"] = new_low_cnt
# 三个风险触发条件
cond1 = breadth_df["above_ma20_ratio"] < BREADTH_MA20_THRESHOLD
cond2 = breadth_df["above_ma60_ratio"] < BREADTH_MA60_THRESHOLD
cond3 = breadth_df["new_low"] > breadth_df["new_high"]
return (cond1 & cond2 & cond3).astype(int)
3.3 风格轮动信号(Style Signal)
核心逻辑:通过中证 1000 与沪深 300 的比值判断大小盘风格,2 个条件同时满足时,判定为小盘走弱、市场风险偏好下降,触发预警得 1 分。
- 大小盘比值低于 20 日均线
- 大小盘比值低于 5 日前的水平
def calculate_style_signal(zz1000_df: pd.DataFrame, hs300_df: pd.DataFrame) -> pd.Series:
"""
风格风险信号:2条件全满足得1分
"""
# 合并两个指数收盘价数据
df = pd.DataFrame()
df["zz1000_close"] = zz1000_df["close"]
df["hs300_close"] = hs300_df["close"]
df = df.dropna()
# 计算大小盘相对强弱比值
df["ratio"] = df["zz1000_close"] / df["hs300_close"]
df["ratio_ma20"] = calculate_ma(df["ratio"], STYLE_RATIO_MA)
df["ratio_5d_ago"] = df["ratio"].shift(STYLE_COMPARE_DAYS)
# 两个风险触发条件
cond1 = df["ratio"] < df["ratio_ma20"] # 比值跌破均线
cond2 = df["ratio"] < df["ratio_5d_ago"] # 比值持续走弱
return (cond1 & cond2).astype(int)
3.4 波动率信号(Volatility Signal)
核心逻辑:短期波动率大幅放大往往伴随风险集中释放,满足 1 个条件即触发预警得 1 分。
- 10 日年化波动率 > 1.2 倍的 60 日年化波动率
def calculate_vol_signal(sh_df: pd.DataFrame) -> pd.Series:
"""
波动率风险信号:1条件满足得1分
"""
df = sh_df.copy()
# 计算短长周期年化波动率
df["vol_10d"] = calculate_annual_vol(df["close"], VOL_SHORT)
df["vol_60d"] = calculate_annual_vol(df["close"], VOL_LONG)
# 触发条件:短期波动率显著放大
cond = df["vol_10d"] > (df["vol_60d"] * VOL_MULTIPLE)
return cond.astype(int)
3.5 成交额信号(Amount Signal)
核心逻辑:下跌趋势中放量代表抛压加大,2 个条件同时满足时触发预警得 1 分。
- 当日成交额 > 1.1 倍的 20 日均成交额
- 当日收盘价低于 20 日均线
def calculate_amount_signal(sh_df: pd.DataFrame) -> pd.Series:
"""
成交额风险信号:2条件全满足得1分
"""
df = sh_df.copy()
# 计算20日均成交额与价格均线
df["amount_ma20"] = calculate_ma(df["amount_wan"], AMOUNT_MA)
df["ma20"] = calculate_ma(df["close"], TREND_MA20)
# 两个风险触发条件:放量 + 下跌趋势
cond1 = df["amount_wan"] > (df["amount_ma20"] * AMOUNT_MULTIPLE)
cond2 = df["close"] < df["ma20"]
return (cond1 & cond2).astype(int)
四、主函数:信号合并与预警计算
主函数负责串联全流程:获取基础数据、计算各因子信号、合并得到每日预警总分、输出最终结果。
def main():
print("=" * 50)
print(f"开始计算五因子风险预警 | 时间区间: {START_DATE} - {END_DATE}")
print("=" * 50)
# 1. 获取三大指数基础行情数据
print("\n>>> 获取指数行情数据...")
sh_df = get_index_daily_data(SH_INDEX, START_DATE, END_DATE)
hs300_df = get_index_daily_data(HS300_INDEX, START_DATE, END_DATE)
zz1000_df = get_index_daily_data(ZZ1000_INDEX, START_DATE, END_DATE)
# 2. 逐个计算五大因子风险信号
print("\n1. 计算趋势信号...")
trend_sig = calculate_trend_signal(sh_df)
print("2. 计算广度信号(遍历成分股,耗时较长,请耐心等待)...")
breadth_sig = calculate_breadth_signal(START_DATE, END_DATE)
print("3. 计算风格信号...")
style_sig = calculate_style_signal(zz1000_df, hs300_df)
print("4. 计算波动率信号...")
vol_sig = calculate_vol_signal(sh_df)
print("5. 计算成交额信号...")
amount_sig = calculate_amount_signal(sh_df)
# 3. 合并所有信号,计算风险总分
result = pd.DataFrame()
result["trend_signal"] = trend_sig
result["breadth_signal"] = breadth_sig
result["style_signal"] = style_sig
result["vol_signal"] = vol_sig
result["amount_signal"] = amount_sig
# 风险总分:0-5分,分数越高市场风险越大
result["warning_score"] = result.sum(axis=1)
# 附加上证指数收盘价便于对照
result["close"] = sh_df["close"]
# 4. 数据清洗:去除无效空值
result = result.dropna()
# 5. 控制台输出结果
print("\n" + "=" * 50)
print("五因子大盘风险预警结果(每日)")
print("=" * 50)
print(result.round(4))
return result
if __name__ == "__main__":
warning_result = main()
五、回测结果与实战解读
我们以上证指数为标的,对历史行情进行回测,得到预警时序结果如下:

回测图分为上下两部分:
- 上半部分:上证指数日线收盘价走势
- 下半部分:每日预警总分(0-5 分),以及五个因子各自的触发状态
核心解读
- 信号累积比单日分数更重要:量化预警的核心价值不是单日拿到 5 分才警惕,而是观察信号如何逐个触发、分数逐步抬升的过程。当分数从 0 分逐步上涨到 3 分以上时,往往是风险持续累积的阶段,是提前降仓的关键窗口。
- 多维度交叉验证降噪:单一因子触发可能是市场噪音,但当趋势、广度、波动率多个维度同时发出信号,市场进入下跌行情的概率会大幅提升。
- 提前预警特性:这套体系的设计目标是在下跌初期捕捉信号,而非下跌已经发生后才确认,给仓位调整留出充足的决策时间。
六、总结与实盘使用建议
- 开箱即用:将上述代码完整复制到 QMT 的 Python 编辑器中,配置好日期参数即可直接运行;广度因子需要遍历 300 只个股,计算耗时稍长属于正常现象。
- 阈值自定义优化:所有因子的周期、阈值都可以根据自身风险偏好调整,保守型投资者可降低阈值,更早触发风险预警。
- 实盘运行方案:可将脚本设置为每日收盘后自动运行,输出当日预警分数,作为次日仓位调整的核心参考依据。
- 扩展方向:可在此基础上加入北向资金、股指期货升贴水、行业轮动等更多因子,也可以根据预警分数设计对应的动态仓位管理策略。
量化预警的意义,从来不是事后证明 “我预判对了”,而是把模糊的 “市场不对劲” 拆解成有数据、有时间点、可验证的决策依据。
风险提示:本文仅为量化策略技术分享,不构成任何投资建议。市场有风险,投资需谨慎。
更多推荐

所有评论(0)