资讯动态

基于指标策略与QP优化的量化投资方法:从信号生成到仓位配置

发布时间:2026/10/19 11:08:19 来源:尧图企业网站定制
1. 从一次策略回撤说起为什么要做这套量化投资方法去年下半年我管理的一个小资金实验账户在两个月里回撤了将近18%。策略本身不复杂就是最经典的双均线交叉外加一个简单的动量过滤。问题出在哪复盘之后发现两个致命伤一是单指标策略在震荡市里反复被打脸交易成本吃掉了大部分利润二是仓位分配完全靠拍脑袋每只标的等权买入结果波动率高的标的把整个组合拖下了水。这次教训让我下定决心把指标策略和QP优化这两件事捏到一起做。指标策略负责回答买什么、什么时候买QP优化负责回答买多少。前者是信号层后者是配置层两层分开设计、联合回测这套思路就是我今天要拆解的核心内容。所谓基于指标策略和QP优化的量化投资方法说白了就是用技术指标生成交易信号再用二次规划Quadratic ProgrammingQP来求解最优仓位权重。它能解决的核心问题是在给定风险约束下如何把资金分配到多个标的上去让组合的收益风险比尽可能高。这套方法适合有一定编程基础、想从单标的择时进阶到多标的组合管理的量化爱好者也适合做资产配置的朋友参考。哪怕你只会一点Python跟着我的思路走也能把这套框架搭起来。我下面会从整体设计思路、核心细节、实操过程、常见问题四个维度展开中间会穿插我自己踩过的坑和实测数据。所有代码和参数都是我实际跑过的你可以直接抄作业但参数一定要根据自己的标的池重新调。2. 整体设计与思路拆解为什么是指标策略加QP优化2.1 为什么不用机器学习端到端解决很多人第一反应是都什么年代了直接上机器学习模型端到端预测收益率不香吗我试过而且踩过坑。用LSTM或者XGBoost直接预测未来N日收益率在样本内表现漂亮得吓人一到样本外就原形毕露。原因有三个第一金融数据信噪比极低模型很容易学到噪声第二端到端模型是黑箱回撤的时候你根本不知道它为什么亏没法干预第三过拟合风险极高调参空间太大随便一调样本内夏普就能到3以上实盘直接打回原形。指标策略虽然土但它有一个巨大优势可解释、可干预、可归因。均线、MACD、RSI这些指标背后的逻辑是公开的你知道它在什么市场环境下会失效也知道什么时候该关掉它。这种可控性在实盘里比样本内的高收益重要得多。2.2 QP优化到底在优化什么QP优化的目标函数通常长这样minimize (1/2) * w^T * Σ * w - λ * μ^T * w subject to sum(w) 1 w 0 w w_max这里面w是各标的的权重向量Σ是协方差矩阵μ是预期收益向量λ是风险厌恶系数。翻译成人话就是我希望组合的方差尽量小同时预期收益尽量大两者的权衡由λ决定。约束条件里sum(w)1表示满仓w0表示不做空ww_max表示单标的不超过某个上限防止过度集中。为什么用QP而不是简单的均值方差优化因为均值方差优化在实操中经常给出极端权重比如把90%仓位压在一个标的上。QP可以通过加约束、加正则项来平滑权重让结果更稳健。而且QP有成熟求解器求解速度快几十个标的的规模毫秒级就能出结果。2.3 两层架构的衔接逻辑整个框架分三层信号层用指标策略对每个标的生成信号输出的是看多/看空/中性以及一个信号强度分数。预期收益估计层把信号强度映射成预期收益μ。这一步很关键不能直接用历史收益率当μ否则QP会追涨杀跌。配置层用QP求解最优权重输出目标持仓。信号层和配置层之间用μ衔接这样设计的好处是信号层可以随便换策略均线、突破、动量都行配置层不用动配置层也可以随便换优化目标最小方差、最大夏普都行信号层不用动。模块解耦方便迭代。提示很多人把信号强度和预期收益直接划等号这是大忌。信号强度是排序用的预期收益是带量纲的两者之间需要一个校准环节后面我会讲怎么校准。3. 核心细节解析与实操要点3.1 指标策略的选择与组合单一指标容易被市场风格切换打脸我的做法是选三个相关性低的指标做组合指标类型具体指标捕捉的市场特征参数建议趋势类双均线中长期趋势快线10日慢线30日动量类20日动量短期强势20日收益率排名反转类RSI超买超卖14日阈值30/70三个指标各自输出一个分数最后加权合成。权重我一般设趋势0.5、动量0.3、反转0.2。为什么趋势类权重最高因为趋势策略在A股这种趋势性较强的市场里长期有效而反转策略只在震荡市里管用属于辅助。每个指标的分数计算方式要统一到同一个量纲我习惯归一化到[-1, 1]区间。比如双均线快线在慢线之上给正分差距越大分数越高用tanh函数压缩到[-1,1]。这样三个指标合成后的总分也在[-1,1]之间方便后续映射。3.2 从信号到预期收益的校准这是整套框架里最容易被忽视、但最重要的一步。信号分数是[-1,1]预期收益是百分比怎么映射我的做法是用历史数据做回归。取过去N个交易日我一般用252天每个交易日算一次信号分数同时算未来20日的实际收益率然后做线性回归future_return a * signal_score b回归出来的斜率a就是信号到收益的转换系数。如果a显著为正说明信号有效如果a接近0甚至为负说明这个信号在当前市场环境下失效了应该降低权重甚至关掉。实测下来这个校准环节能让组合的夏普比率提升0.3到0.5。为什么因为不同标的的波动率不一样同样一个信号分数高波动标的的预期收益应该更高。回归系数自动捕捉了这个差异。注意校准用的历史窗口不能太长也不能太短。太长会包含过时的市场特征太短则统计不显著。我试过60天、120天、252天、504天252天约一年是比较平衡的选择。3.3 协方差矩阵的估计与收缩QP优化里另一个关键输入是协方差矩阵Σ。直接用样本协方差矩阵有个问题当标的数量接近或超过样本天数时矩阵会变得不可逆或者极不稳定。比如你有30个标的但只用了60天数据样本协方差矩阵的估计误差会非常大。解决办法是收缩估计Shrinkage。简单说就是把样本协方差矩阵向一个结构化矩阵比如对角矩阵收缩Σ_shrunk δ * F (1 - δ) * S其中S是样本协方差矩阵F是目标矩阵我一般用对角矩阵即只保留方差协方差置零δ是收缩强度取值在0到1之间。δ越大越偏向结构化矩阵估计越稳定但越偏离真实δ越小越接近样本矩阵但噪声越大。我一般用Ledoit-Wolf方法自动估计δPython的sklearn.covariance里有现成实现。实测下来收缩估计能让组合的波动率降低10%到15%代价是预期收益略微下降但收益风险比是提升的。3.4 约束条件的设置技巧QP的约束条件直接决定了权重的形态几个关键约束单标的上限w_max一般设0.1到0.2。设太高起不到分散作用设太低会导致小标的被迫超配。我一般设0.15。行业/板块上限如果有行业分类可以加行业约束防止某个行业过度集中。这个用矩阵形式表达A * w b。换手率约束sum(|w - w_prev|) turnover_max防止调仓过于频繁。这个约束会让QP从二次规划变成带绝对值的形式需要引入辅助变量线性化。换手率约束特别重要。我早期没加这个约束结果策略每天调仓交易成本吃掉了30%的利润。加上换手率约束后年化换手率从15倍降到4倍净收益反而提升了。4. 实操过程与核心环节实现4.1 数据准备与清洗数据是量化的地基地基不稳后面全白搭。我用的数据包括日频OHLCV、复权因子、停牌标记、涨跌停标记。清洗环节有几个必须做的复权处理用后复权价格避免除权除息造成的假信号。停牌处理停牌期间信号置零权重强制为0。涨跌停处理涨停当天不能买入跌停当天不能卖出回测时要模拟这个限制。异常值处理单日涨跌幅超过20%的非涨跌停标记为异常信号置零。import pandas as pd import numpy as np def clean_data(df): # 后复权 df[adj_close] df[close] * df[adj_factor] # 停牌标记 df[is_suspended] df[volume] 0 # 涨跌停标记以10%为例 df[pct_chg] df[adj_close].pct_change() df[is_limit_up] df[pct_chg] 0.095 df[is_limit_down] df[pct_chg] -0.095 # 异常值 df.loc[df[pct_chg].abs() 0.21, adj_close] np.nan df[adj_close] df[adj_close].ffill() return df4.2 信号生成与合成以双均线为例信号生成逻辑def ma_signal(close, fast10, slow30): ma_fast close.rolling(fast).mean() ma_slow close.rolling(slow).mean() diff (ma_fast - ma_slow) / ma_slow # 用tanh压缩到[-1,1] signal np.tanh(diff * 10) return signal动量信号def momentum_signal(close, window20): ret close.pct_change(window) # 横截面排名后归一化 rank ret.rank(pctTrue) signal (rank - 0.5) * 2 return signalRSI信号def rsi_signal(close, window14): delta close.diff() gain delta.clip(lower0).rolling(window).mean() loss -delta.clip(upper0).rolling(window).mean() rs gain / loss rsi 100 - 100 / (1 rs) # RSI低于30看多高于70看空 signal (50 - rsi) / 50 return signal.clip(-1, 1)三个信号合成def composite_signal(df): s1 ma_signal(df[adj_close]) s2 momentum_signal(df[adj_close]) s3 rsi_signal(df[adj_close]) composite 0.5 * s1 0.3 * s2 0.2 * s3 return composite4.3 预期收益校准的完整实现校准环节我用滚动窗口回归from scipy.stats import linregress def calibrate_return(signal_series, return_series, window252): # signal_series: 信号分数序列 # return_series: 未来20日收益率序列 df pd.DataFrame({signal: signal_series, ret: return_series}).dropna() if len(df) window: return 0.0, 0.0 recent df.iloc[-window:] slope, intercept, r_value, p_value, std_err linregress(recent[signal], recent[ret]) # 如果回归不显著斜率置零 if p_value 0.1: slope 0.0 return slope, intercept得到斜率和截距后预期收益就是mu slope * signal intercept这里有个细节mu要截断避免极端值。我一般把mu限制在[-0.05, 0.05]之间即预期收益不超过正负5%。4.4 QP优化的求解用cvxpy求解QPimport cvxpy as cp def optimize_weights(mu, Sigma, w_prev, w_max0.15, turnover_max0.3, lambda_risk2.0): n len(mu) w cp.Variable(n) # 目标函数 objective cp.Minimize(0.5 * cp.quad_form(w, Sigma) - lambda_risk * mu w) # 约束 constraints [ cp.sum(w) 1, w 0, w w_max, cp.norm(w - w_prev, 1) turnover_max ] prob cp.Problem(objective, constraints) prob.solve(solvercp.OSQP) if w.value is None: return w_prev # 求解失败则保持原仓位 return np.clip(w.value, 0, w_max)lambda_risk是风险厌恶系数越大越保守。我一般设2.0实测下来这个值在收益和波动之间比较平衡。如果你更保守可以设到5.0更激进可以设到0.5。4.5 回测框架与绩效评估回测要模拟真实交易约束def backtest(prices, signals, rebalance_freq5): n_days, n_assets prices.shape weights np.zeros((n_days, n_assets)) w_prev np.ones(n_assets) / n_assets nav [1.0] for t in range(252, n_days): if t % rebalance_freq 0: # 计算信号和预期收益 sig signals[t] mu calibrate_and_map(sig) Sigma estimate_covariance(prices[:t]) w_new optimize_weights(mu, Sigma, w_prev) # 考虑交易成本 turnover np.abs(w_new - w_prev).sum() cost turnover * 0.001 # 单边千分之一 w_prev w_new else: cost 0 # 计算当日收益 ret (prices[t] / prices[t-1] - 1) w_prev nav.append(nav[-1] * (1 ret - cost)) return np.array(nav)绩效指标我重点看四个年化收益、年化波动、夏普比率、最大回撤。另外还会看换手率和胜率。指标我的实测值基准等权买入持有年化收益18.3%9.7%年化波动14.2%22.5%夏普比率1.290.43最大回撤-12.6%-28.3%年化换手4.2倍0.1倍这组数据是我在30个标的、5年回测期上跑出来的。夏普从0.43提升到1.29最大回撤从-28%降到-12.6%提升主要来自QP优化的分散效应和换手率约束。5. 常见问题与排查技巧实录5.1 QP求解失败怎么办最常见的问题是求解器报infeasible或者unbounded。原因通常是约束条件互相矛盾。比如w_max0.05但你有15个标的15 * 0.05 0.75 1满仓约束无法满足。排查步骤检查n * w_max 1是否成立不成立就调大w_max或减少标的数。检查换手率约束是否太紧turnover_max太小会导致从当前仓位到任何可行仓位都不可达。检查协方差矩阵是否正定不是正定的话加一个小的对角项Sigma 1e-6 * np.eye(n)。实操心得我习惯在求解前先做一次可行性检查如果约束不可行自动放宽w_max到1/n * 1.5保证求解能进行下去。5.2 信号失效怎么识别信号失效的典型表现是校准回归的斜率a持续为负或接近零且p_value很大。这时候策略实际上在随机交易。我的处理方式是设一个信号有效性阈值如果连续20个交易日的回归斜率都低于0.01就把该信号的权重降到0只用剩下的信号。等斜率恢复后再加回来。这个机制在2022年那种风格剧烈切换的市场里救了我好几次。当时动量信号连续一个月失效系统自动把它关掉避免了持续亏损。5.3 回测收益高但实盘差很多这是量化最经典的坑原因通常有四个未来函数用了当天收盘价算信号又用当天收盘价成交。解决办法是信号用T日数据成交用T1日开盘价。幸存者偏差标的池只选了现在还在的标的退市的没算进去。解决办法是用历史全样本包括已退市标的。交易成本低估只算了佣金没算冲击成本和滑点。解决办法是加至少千分之一的单边成本。过拟合参数调得太细。解决办法是参数取整比如均线用10/30而不是9/31。我早期回测年化30%实盘只有8%排查下来主要是未来函数和交易成本低估。修正后回测年化降到18%实盘能跑到15%左右差距就合理了。5.4 常见问题速查表问题现象可能原因排查方法解决方案QP求解失败约束不可行检查n*w_max1放宽w_max或减少标的权重极端集中协方差矩阵病态检查条件数加收缩估计或正则项换手率过高无换手约束统计日均换手加turnover_max约束信号失效市场风格切换看回归斜率动态降权或关闭信号回测实盘差距大未来函数/成本逐笔核对用T1成交加成本组合波动大标的相关性高看相关矩阵加行业约束或选低相关标的5.5 几个容易被忽视的细节第一个细节是调仓频率。我试过日频、周频、双周频、月频。日频换手太高月频反应太慢周频5个交易日是比较好的平衡点。这个频率下信号有足够时间发酵交易成本也可控。第二个细节是现金处理。QP求解出来的是满仓权重但实际中可能有停牌标的导致无法满仓。我的做法是预留5%现金QP约束改成sum(w) 0.95剩下的5%吃货币基金收益。这样既保证了灵活性又不浪费太多收益。第三个细节是参数敏感性。lambda_risk从1.0变到3.0夏普比率变化不超过0.1说明这套框架对参数不敏感这是好事。但如果w_max从0.15变到0.05夏普会掉0.3以上说明分散度约束很关键不能设太紧。第四个细节是极端行情处理。2020年3月和2022年4月那种极端行情下所有信号都会失效QP也会给出奇怪的权重。我的做法是加一个市场状态过滤器当市场整体波动率超过历史90分位时强制降低总仓位到50%剩下的现金。这个过滤器在极端行情里能显著降低回撤。6. 我在这套方法上踩过的坑和后续扩展方向这套框架我从最初的想法到稳定运行前后迭代了大概七八个版本。最大的坑是早期太迷信QP的最优解觉得数学上最优的就是最好的。后来发现QP给出的权重在实盘里经常因为流动性问题无法执行比如它让你买5%的一个日成交额只有几百万的小标的你根本买不进去。所以后来我加了流动性约束单标的权重不超过其20日平均成交额的1%。另一个坑是过度追求信号的数量。我一度堆了七八个指标进去结果信号之间高度相关合成后反而噪声更大。后来砍到三个低相关指标效果反而更好。这印证了一个道理在量化里少即是多简单即是稳健。后续我打算往两个方向扩展。一是把风险模型做得更细现在只用了收缩协方差后面想引入因子风险模型把风险拆解成市场因子、行业因子、风格因子这样约束可以下得更精准。二是把交易成本模型做得更真实现在只用了固定千分之一后面想用成交量和波动率动态估计冲击成本让回测更贴近实盘。最后分享一个我个人的小习惯每次策略上线前我都会用最近半年的数据做一次模拟盘不接实盘资金但每天记录信号和权重跟实盘对比。这个习惯帮我发现了好几次数据对齐的问题比如复权因子更新延迟、停牌标记错误之类的。量化这行细节决定成败多一道检查就少一次亏损。

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

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

免费获取报价 →
↑