资讯动态

A股多维度量化择时系统构建:从因子设计到回测实盘避坑

发布时间:2026/10/18 22:38:11 来源:尧图企业网站定制
简介华宝证券发布的金融工程专题报告《A股多维度量化择时系统构建与回测》面向量化投研、资产配置及指数择时实践者。报告沿用前期债券与黄金择时框架以经济运行、流动性、动量趋势、事件驱动四个维度构建多因子打分体系每个维度先对单指标进行择时测试按收益和胜率加权合成维度打分并配有样本内外净值走势图表。前三维度构成主交易系统事件驱动因高胜率、非连续性作为额外加分项经等权合成后采用分仓方式配置权益资产兼顾收益与回撤控制。回测区间2012年1月至2019年6月年化收益12.8%优于同期沪深300指数的6.8%滚动一年平均最大回撤由-22.2%收窄至-9.6%收益回撤比达1.33同时提示模型设定偏差风险为股市择时提供了可复现的量化框架。资源为PDF文档共1份约15页压缩包大小约1.06MB已有102人学习下载适合金融工程研究者及量化策略开发人员研读参考。1. 从“看对方向”到“做对仓位”A股多维度量化择时到底在解决什么问题做 A 股的人大多有过这种经历宏观判断对了指数也确实涨了但自己拿的票没动或者技术指标金叉了追进去就套在短期高点。说白了单一维度的择时信号在大类资产联动、市场情绪反复切换的 A 股里稳定性很差。所谓“A股多维度量化择时系统”核心思路是把价格、资金、情绪、日历效应等不同来源的信号拆开计算再用一套固定的合成规则变成最终的仓位建议最后用历史数据回测验证这套规则是否真的能跑赢简单持有。这套方案适合两类人一是自己做量化策略、想补择时模块的开发者二是做资产配置、需要用仓位信号控制回撤的投研人员。需要明确的是择时系统不追求预测每一天的涨跌它追求的是在关键拐点上给出可执行的仓位变化把“看对”转化成“做对”。一个容易被忽视的事实是纯多头的 A 股择时信号的价值不在于收益率有多惊艳而在于它能不能把最大回撤压到可承受的范围。回测里常见的“年化 30%”如果没有回撤约束实盘很难拿得住。所以这篇笔记会从因子选择、合成方法、回测框架到实盘前的避坑完整拆一遍这套系统的构建路径。2. 多维度信号从哪来价格、资金、情绪与日历效应的因子设计2.1 因子维度的选型逻辑为什么是这四个方向A 股择时系统里因子维度不是越多越好而是要覆盖不同的信息源。常见做法是围绕四个维度展开价格趋势、资金流向、市场情绪、日历效应。价格趋势反映的是市场自身的中短期动量与反转特征资金流向反映的是增量资金的态度尤其是北向资金和两融余额这类可观测的高频数据市场情绪反映的是参与者行为比如换手率、涨停家数、成交额占比日历效应则捕捉 A 股特有的季节性规律比如春节前后、季末考核期、重要会议窗口。这四个维度跟基本面数据相比有一个共同优势都是周频或日频可得的不需要等待财报披露天然适合择时系统的高频调仓需求。选维度的标准有三条第一逻辑上能解释不能是纯粹的统计相关性第二数据可得且序列足够长至少覆盖一轮完整的牛熊第三维度之间相关性要低否则合成之后等于重复加权了同一个信息源。这四个维度里价格趋势和情绪面相关性稍高但前者偏慢、后者偏快放在一起反而能构成快慢搭配。资金面与宏观流动性相关日历效应则是完全独立的时间维度这四者组合起来基本覆盖了 A 股的主要波动来源。2.2 每类因子的计算口径与参数设定因子计算是整个系统的地基口径错了后面所有合成和回测都会失真。先说价格趋势因子。常见做法是取沪深300或中证500指数收盘价计算三条均线20日、60日、120日再定义一个趋势得分。这个得分不是简单的金叉死叉而是看短期均线在中期均线上方的持续时间和偏离幅度。一个容易踩的细节是均线用收盘价还是用均价VWAP我建议用收盘价因为在做日频择时时收盘价是最终可执行的价格基准和回测撮合逻辑一致。资金面因子里北向资金净流入和两融余额变化是用的最多的两个代理变量。北向资金数据可以从交易所公开数据抓取但要注意口径变更——2024年后北向资金实时数据调整为按季度披露日频数据就得改用其他代理比如成交额占比或行业龙头的资金流向。两融余额的缺点是更新滞后一天且受监管政策影响大但它的趋势性比北向资金更强适合作为慢变量。这两个因子在代码里都要做标准化不能直接用绝对值。情绪面因子建议用三个全市场换手率成交额/流通市值、涨停家数剔除ST和上市未满60日的新股、以及上涨家数占比。换手率是很好的情绪温度计A股见顶时往往伴随换手率飙升涨停家数反映的是短线赚钱效应这个数据在情绪极端日会快速归零或爆量需要做平滑处理。日历效应因子则用一个二维结构月份效应和节假日前后效应。月份效应按过去十年每月收益率的正负比例编码节假日效应取长假前5个交易日和后3个交易日的统计特征。2.3 因子的平滑与标准化让信号可比较因子裸数据不能直接用因为不同因子的量纲和波动区间差异太大。比如换手率可能在0.5%到3%之间波动而均线偏离度可能在-5%到8%之间。不做标准化的话合成权重会被高波动的因子主导。我习惯的分两步第一步是去极值用滚动窗口的MAD中位数绝对偏差方法把极端值压缩到3倍中位数偏差以内避免单日极端行情对信号造成跳跃第二步是Z-Score标准化窗口长度选120个交易日和均线周期保持一致这样各个因子的输出都变成均值为0、标准差为1的序列可以直接加权合成。标准化之后还有一个容易被忽略的步骤因子降噪。日频数据的噪声很大直接用原始Z-Score会让信号频繁翻转。常见做法是对标准化后的因子再做一次5日指数移动平均平滑掉短周期的毛刺。这里要注意平滑窗口不能太长否则因子的及时性会变差择时系统最怕的就是信号出来后已经错过一段行情。3. 信号合成与仓位映射把多因子变成一个可执行的仓位值3.1 加权合成方法等权、IC加权与滚动回归的比较因子合成是多维择时的核心环节。最保守的做法是等权合成把四个维度的Z-Score直接平均。这种做法的好处是稳健不会因为某一段行情里某个因子失效而大幅拖累整体信号缺点是放弃了因子在不同市场状态下的强弱差异。稍微进阶一点的做法是IC加权即用每个因子过去一段时间比如60个交易日与未来5日收益的秩相关系数Spearman IC作为权重IC越高权重越大。IC加权在趋势行情里表现更好但在市场风格剧烈切换时容易追高因子、踩踏切换。我实际工程里更倾向于做滚动回归加权但加了两个约束条件第一权重必须非负负权重意味着某个因子反向预测这在样本外通常不可靠不如直接归零第二单个因子权重上限设为0.4防止某一个维度在局部行情里过度主导。滚动回归的窗口选120个交易日预测目标是未来5个交易日的收益。这个设定需要做滚动不能使用全样本回归否则就是未来函数。等权、IC加权、滚动回归加权这三者之间的差异在单边上涨行情里可能不大但在震荡市里滚动回归加权对突变信号的响应明显更平滑这是选它的主要原因。3.2 得分到仓位的分段映射避免满仓空仓的频繁切换合成得分本身是一个连续值但实盘交易不能跟着连续值频繁换仓。需要用分段映射把得分变成离散仓位。这里有一个关键参数调仓阈值。阈值太密比如每隔0.1个标准差就调一次仓交易成本会吃掉收益阈值太疏比如只有三档又容易在临界点出现大幅仓位跳变。我一般设五档仓位0%、25%、50%、75%、100%并设置滞回区间Hysteresis。滞回的意思是得分从低位上穿到0.5倍标准差时仓位从25%加到50%但要从50%降回25%需要得分跌破0倍标准差而不是跌回0.5倍以下。这样做能显著减少频繁调仓回测里年化换手率能降三分之一左右。滞回参数需要结合交易成本一起调。A股单边交易成本佣金印花税滑点按0.15%到0.2%估算比较现实。如果调仓阈值设得太小信号每天翻转一年下来交易成本可能吃掉5个点以上的收益。回测时要跑一组参数敏感性看换手率和净值曲线的关系确认分段映射没有让系统变成一个高频换手策略。3.3 最小实现一个可直接跑通的信号合成脚本下面给出一段精简的因子合成代码输入是四个因子的日频时间序列DataFrame输出是每日的目标仓位序列。这段代码可以直接跑通但参数需要根据自己的数据周期微调。import pandas as pd import numpy as np def mad_clip(series, window120, n3): MAD去极值滚动窗口中位数绝对偏差截断 median series.rolling(window, min_periods1).median() mad (series - median).abs().rolling(window, min_periods1).median() upper median n * 1.4826 * mad lower median - n * 1.4826 * mad return series.clip(lower, upper) def zscore(series, window120): 滚动Z-Score标准化 mean series.rolling(window, min_periods1).mean() std series.rolling(window, min_periods1).std() return (series - mean) / std def composite_signal(df, weights): df: DataFrame列分别为 [trend, capital, sentiment, calendar] weights: dict各因子权重例如 {trend: 0.3, capital: 0.3, sentiment: 0.2, calendar: 0.2} df df.copy() # 去极值 标准化 for col in df.columns: df[col] mad_clip(df[col]) df[col] zscore(df[col]).ewm(span5, adjustFalse).mean() # 加权合成 total sum(weights.values()) score sum(df[col] * weights[col] for col in df.columns) / total return score # 仓位映射带滞回区间 def map_position(score, up_thresh0.5, down_thresh0.0): position np.zeros(len(score)) current 0.0 for i in range(len(score)): if current 0.0 and score.iloc[i] up_thresh: current 0.25 elif current 0.25 and score.iloc[i] up_thresh 0.3: current 0.5 elif current 0.5 and score.iloc[i] up_thresh 0.6: current 0.75 elif current 0.75 and score.iloc[i] up_thresh 0.9: current 1.0 elif current 1.0 and score.iloc[i] down_thresh - 0.1: current 0.75 elif current 0.75 and score.iloc[i] down_thresh 0.2: current 0.5 elif current 0.5 and score.iloc[i] down_thresh 0.1: current 0.25 elif current 0.25 and score.iloc[i] down_thresh: current 0.0 position[i] current return position这段代码里mad_clip负责压缩极端值zscore配合ewm在标准化的同时做了5日平滑map_position里上穿阈值和下穿阈值不一致这就是滞回逻辑。权重参数在图上文提到的约束范围内微调每个因子的权重不能为负单因子不超过0.4。跑完合成后检查score序列的直方图确认分布接近正态、没有长尾再做下一步回测。4. 回测框架搭建从净值计算到绩效指标还原真实交易约束4.1 回测引擎选型与撮合细节择时系统的回测有两种路线向量化回测和事件驱动回测。向量化回测适合策略逻辑简单、调仓频率低的场景代码量小算得快事件驱动回测适合需要精细控制下单逻辑、冲击成本和极端行情处理的场景但代码复杂度高一个量级。对于日频调仓的多因子择时向量化回测足够。关键不是引擎本身而是撮合逻辑里的几个约束条件必须写对涨跌停不能成交、停牌不能成交、成交量限制、手续费和印花税。涨跌停约束是最容易被忽略的。A股有10%创业板和科创板20%的涨跌停限制回测里如果信号在涨停日发出买入指令实际是买不进的因为卖单极少。处理方式是在回测数据里加上涨跌停标记遇到涨停且买入信号时跳过本次买入遇到跌停且卖出信号时同样跳过。停牌也是类似逻辑直接用当日成交量或成交额为0来判断即可。这些约束如果省略回测收益会虚高尤其在捕捉反弹行情的择时策略里虚高可能达到一倍以上。4.2 交易成本模型佣金、印花税与滑点怎么一起算交易成本的设定直接决定回测结果是否可信。A股现在的费率结构是佣金双边万1到万3卖出收印花税千分之0.52023年8月调整后过户费双边万0.1。实盘还要加上滑点尤其在做指数择时时买卖一篮子成分股的冲击成本不容小觑。我常用的成本模型是买入成本 佣金万2 滑点万5约万分之7卖出成本 佣金万2 印花税万5 滑点万5约万分之12。注意印花税只在卖出收取回测里如果买卖用同一个费率会低估卖出成本、让换手率高的优化结果显得可行这是回测里最常见的翻车点之一。滑点模型可以简单设成固定比例也可以按历史波动率建模当日开盘价相对前收的跳空幅度反映隔夜滑点日内成交量分布反映冲击成本。更精细的做法是用VWAP偏移量来模拟但对日频择时系统固定比例的滑点加上灵敏度测试已经足够。关键是要在回测报告里同时给出“净成本”和“毛收益”两者差距太大时策略就没有实盘价值。4.3 最小实现一次完整回测需要哪些模块与结果指标一次完整的择时回测至少要输出下面这些模块策略净值曲线、基准净值一般是沪深300、仓位变化序列、逐年收益表、最大回撤区间、以及换手率统计。净值计算的基础是当日收益率乘以昨日仓位。举个简化流程假设信号在T日收盘后生成T1日按开盘价调仓当日的收益贡献是T日收盘到T1日收盘的指数涨幅乘以T1日的仓位。这里的调仓时点必须遵守“信号生成次日执行”原则不能当日信号当日成交这是前视偏差最常见的来源。绩效指标方面我建议记录五组年化收益率、年化波动率、夏普比率、最大回撤、卡玛比率年化收益/最大回撤。其中卡玛比率对择时系统尤其重要因为它直接刻画了收益与回撤的性价比。另外要单独统计空仓期的占比——一个择时系统如果空仓时间超过60%说明信号阈值太保守虽然回撤小但资金利用率太低实盘很难拿得住。import pandas as pd import numpy as np def backtest(price, position, cost_buy0.0007, cost_sell0.0012): price: 指数日收盘价序列 position: 目标仓位序列0到1之间 ret price.pct_change().fillna(0) # T日生成的仓位T1日生效 pos_eff position.shift(1).fillna(0) # 调仓日检测 turnover pos_eff.diff().abs().fillna(pos_eff) # 交易成本买入按调仓增量计算卖出按调仓减量计算 cost np.where(turnover 0, turnover * np.where(pos_eff.diff().fillna(0) 0, cost_buy, cost_sell), 0) cost pd.Series(cost, indexret.index) strategy_ret pos_eff * ret - cost nav (1 strategy_ret).cumprod() bench_nav (1 ret).cumprod() return nav, bench_nav, turnover def max_drawdown(nav): 计算最大回撤及区间 peak nav.cummax() drawdown nav / peak - 1 end_idx drawdown.idxmin() start_idx nav.loc[:end_idx].idxmax() return drawdown.min(), start_idx, end_idx这段回测代码里pos_eff position.shift(1)是防止前视偏差的关键当天生成的仓位必须等到下一交易日才生效。np.where区分买卖成本避免统一费率带来的低估。跑完回测后逐年收益比总收益更重要——很多策略总收益漂亮但拆开看某一年特别赚钱、其他年份平庸这种时间集中度高的收益实盘意义要打折扣。5. 回测与实盘之间的坑多维择时最容易翻车的五个细节5.1 前视偏差信号当天生效导致回测收益虚高现象回测曲线平滑向上最大回撤极小但同一套参数在模拟盘里连续两周跑输回测结果差异达到20%以上。原因最典型的是因子计算里用了当日收盘后才知道的数据但回测撮合却假设当日能以收盘价成交。比如用“当日北向资金净流入”作为当日买入信号实际上北向资金全天净流入数据盘后才出盘中根本不知道收盘值这就是必然的未来函数。解决把因子计算、信号生成、仓位生效拆成三个独立时点严格执行信号T日生成、T1日开盘执行。更稳妥的做法是连开盘价都不用用T1日的收盘价或VWAP虽然收益会打折但更接近可实现的业绩。5.2 涨跌停和停牌股未处理回测净值被高估现象回测里某个月净值大涨翻交易记录发现当天买入的标的正处于涨停状态模拟时根本买不进去。原因策略的回测数据只用了指数收盘价没有叠加成分股的涨跌停限制和停牌信息。指数本身不会涨停但按指数点位下单时实际买入的一篮子股票可能因为部分成分股涨停而无法按预设权重建仓。解决回测数据里除指数收盘价外必须至少带上指数的成交额和当日涨跌幅是否触发涨跌停的标记。更精细的做法是按指数成分股权重用个股的成交情况做一层过滤当天涨停的个股权重在调仓时强行置为0剩余权重归一化后再计算指数收益。5.3 参数过拟合滚动回归权重在样本外失效现象回测期内的收益曲线漂亮但把回测窗口挪到另一段年份策略很快失效甚至出现连续亏损。原因滚动回归加权阶段的窗口参数和约束条件是在全样本上调出来的本质上已经用到了样本外信息。选120日窗口、0.4权重上限这些参数如果是在看完整个回测表现后倒推出来的那它们同样属于未来函数。解决把数据切成三段训练段用于确定权重和阈值验证段用于测试滞回参数测试段完全不参与调参、只跑一次作为最终报告。分段比例建议6:2:2时间上按先后顺序切不能随机抽样因为择时序列有强自相关性。5.4 调仓成本被低估只算佣金不算滑点和冲击现象回测里年化收益20%换手率约400%看着不错。但实盘跑下来收益只剩8%主要亏在买卖价差上。原因回测成本只按费率算没有考虑调仓当日的开盘跳空和盘口深度问题。当信号集中发出时比如连续多日加仓实际买入会推高成交价格这个损耗在回测里是零。解决成本模型的滑点不能只设一个固定常数要按滚动波动率折算。波动率高的时段滑点自动放大。同时可以加入调仓限制单日仓位变化不超过20个百分点用减少交易频率换取更低的冲击成本。5.5 指数复权方式错误导致信号失真现象换手率和均线因子回测结果异常信号频繁翻转且与人工看图判断完全相反。原因有些数据源的指数行情只做了前复权除权除息日会出现价格跳空均线因子被这些跳空干扰产生虚假趋势信号。解决指数择时一律用全收益指数包含分红再投资或直接用真实点位数据并检查因子计算所用的数据源里指数在分红日的涨跌幅是否为0。如果发现某日指数振幅极小但均线偏离度剧变基本就是复权处理出了问题。这个坑在创业板指数和红利类指数上特别常见。6. 落地前的最后一步样本外验证与参数灵敏度检查系统回测结束不代表工作完成真正决定这个择时系统能不能投入模拟盘甚至实盘的是样本外验证和参数灵敏度分析。我一般会做一个“滚动再拟合”测试每3个月重新跑一次回测用最新数据更新因子权重然后观察两个指标——样本外超额收益是否稳定、权重在滚动中的漂移幅度是否过大。如果每次滚动后权重变化超过50%说明因子本身不稳定系统对参数太敏感实盘里会不断出现信号置信度下降的问题。一个具体习惯把回测报告按年份拆分重点看每一年的最大回撤发生时间和原因。如果某年的回撤恰好集中在权重最高的因子失效期说明系统对单一信息源依赖过大。通过日志查看每次调仓时各因子的贡献值可以定位是哪一类因子主导了这轮仓位下调这比看净值曲线直观得多。把这些记录放在监控面板里每天盘后自动更新一次信号状态和因子拆解连续跟踪两个月后你对这套系统的脾气就摸清楚了。这套系统的价值不在于预测准确率有多高而在于它的决策过程透明、可复盘、可干预——这也是量化择时相比主观判断最大的优势。关于择时系统我的实际教训是回测里越精确的东西实盘里越可疑。留好安全边际用0.5个标准差的变化作为调仓触发条件比用一个精确到小数点的信号值可靠得多。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑