十个经典量化策略源码详解:从回测到避坑指南
简介一份面向量化交易入门者与金融科技开发者的经典策略源码包内容以趋势跟踪、均值回归、统计套利、高频交易、机器学习、事件驱动、波动率、多因子选股、套利和另类数据10类策略为主线逐一说明核心逻辑、常用指标、适用市场与潜在风险帮助读者快速建立量化策略选型框架。包内文件共5个大小约146KB涵盖Python策略脚本、依赖清单、HTML说明文档、回测结果图以及在线运行配置等文件类型精简且分工明确既能查看代码逻辑也可对照图表检验回测效果。其中演示代码以移动平均线交叉趋势跟踪策略为案例基于backtrader框架完成策略定义、数据加载与回测并输出可视化结果展示了从指标计算到资金曲线生成的关键步骤。读者可从源码中直接学到如何组织策略类、设置交易信号、控制仓位与评估绩效结合依赖清单可快速复现实验环境。目前已有348人学习下载对于希望实际动手验证量化思路的初学者而言是一份轻量且完整的参考资源。1. 十个经典量化策略源码值不值得花两周去跑通拿到这类源码包我的建议是先别急着跑回测因为十个经典量化策略赚的是不同市场状态下的钱双均线和海龟吃单边趋势布林带和 RSI 吃震荡修复网格吃每一格的差价配对交易吃价差回归轮动吃标的之间的强弱差异。把它们放在一起比总收益没有意义先分对类再看曲线才有参考价值。这套代码适合两类人。一类是想把「金叉死叉」从看盘直觉升级成可回测信号的新手另一类是已经有实盘经验、想拿经典策略做基准对照的熟手。前者跟着代码改参数后者能直接看出每个策略在什么行情下会失效。我给自己定的验收标准是十个策略全部跑通能解释每一根信号的来源能说出每个参数改大改小的后果。如果只是跑出一条净值图那只是安装了源码还没开始用它。先用一句话立个标准策略源码的难点从来不在信号那几行而在成本模型、仓位管理和行情边界。下文从策略分类讲起逐个拆实现最后给你几组我自己踩过的坑。2. 先把策略分好类四类经典策略各自赚什么钱这一步不做后面调参就是玄学。十个策略看着多按盈利来源划分其实只有四类分类决定了你该用什么参数心态去面对回测曲线。2.1 趋势、回归、套利、横截面四类盈利逻辑与适用边界我平时给新同学讲源码先发一张分类表让每个人把一个策略先对号入座再动手。策略类别代表策略盈利来源最怕的市场状态趋势跟踪双均线、MACD、唐奇安通道海龟单边行情中的持续涨幅长期窄幅震荡均值回归布林带、RSI、网格价格偏离中枢后的修复单边趋势网格最危险统计套利配对交易价差偏离后的回归价差中枢永久漂移横截面比较动量、轮动、多因子不同标的之间的强弱差风格快速切换注意看「最怕的市场状态」这一列。趋势和回归在盈利逻辑上是互斥的趋势策略喜欢大波段震荡行情里频繁止损回归策略喜欢震荡单边行情里不断逆势接飞刀。一个很常见的错误是拿布林带的回测曲线去要求它在单边行情里也赚钱这是用错了工具。横截面类策略和前三类不一样它不预测涨跌只比较强弱。动量策略买过去 N 日涨得最多的那个资产轮动策略在每个调仓周期把持仓换成排名最前的 K 个。这类策略在标的池足够多、分化足够大时表现稳但风格一旦切换排名靠前的标的可能被快速打回去。统计套利最接近「市场中性」同时持有多头和空头赚价差收敛的钱。源码难度不高但隐含前提是价差会回归。如果两个标的的基本面关系变了价差不回归就是这类策略的翻车现场。2.2 信号、仓位、成本、风控源码的四层结构看源码不要从第一行顺序看到最后一行先找四样东西信号生成、仓位计算、交易成本、风控条件。大多数经典策略代码里信号生成只占三分之一剩下的工作是把信号换算成每一期的持仓再扣掉手续费和滑点最后统计净值和最大回撤。下面这段是我习惯的最小回测骨架四个函数各管一层。后文所有策略都复用这套骨架信号函数只负责生成 signal 列回测逻辑完全隔离。import pandas as pd import numpy as np def make_signal(df, **params): 策略接口在 df 里生成 signal 列。 signal1 表示持有0 表示空仓-1 表示做空。 raise NotImplementedError def run_backtest(df, fee_rate0.0003, slippage0.0005, init_cash100000): df df.copy() df[ret] df[close].pct_change() df[turnover] df[signal].diff().abs().fillna(0) df[cost] df[turnover] * (fee_rate slippage) df[strategy_ret] df[signal].shift(1) * df[ret] - df[cost] df[equity] (1 df[strategy_ret].fillna(0)).cumprod() * init_cash dd (df[equity] / df[equity].cummax() - 1).min() print(f总收益 {(df[equity].iloc[-1] / init_cash - 1) * 100:.2f}%) print(f最大回撤 {dd * 100:.2f}%) return df def add_atr_at(df, n14): 预计算 ATR真实波幅海龟这类需要波动率定仓位的策略要用。 prev_close df[close].shift(1) tr pd.concat([ df[high] - df[low], (df[high] - prev_close).abs(), (df[low] - prev_close).abs() ], axis1).max(axis1) df[atr] tr.rolling(n).mean() return df每个函数的职责说明make_signal是留给具体策略的接口所有策略都生成一列signal取值限定 1 / 0 / -1后端回测逻辑完全不用改十个策略共用一套骨架。run_backtest里的shift(1)是防未来函数的命门第 t 天产生的信号只能作用于第 t1 天的收益。这一行被很多人忽略后面避坑章节会重点展开。turnover用signal.diff().abs()估算换手率信号从 0 变 1 视为买入 100% 仓位成本按换手率线性扣除。add_atr_at里的 TR 取三个值的最大值对应跳空、涨跌停等极端情形的真实波幅是海龟类仓位管理的基础数据。这里立一个原则任何策略源码只要收益计算里没有shift(1)或等价的滞后处理基本可以判定作者要么是新手要么在测试未来函数能跑出多好看的假曲线。3. 十个经典策略逐个拆解核心逻辑与最小 Python 实现十个策略按顺序是双均线、MACD、唐奇安通道海龟、布林带、RSI、动量、轮动、网格、配对交易、多因子选股。这一章每个策略给一个最小可运行的信号函数加逻辑说明参数给经典默认值并解释为什么是这个值。以下函数默认已经导入 pandas 和 numpy数据列名统一用 open / high / low / close / volume。3.1 双均线和 MACD两个最容易被新手低估的趋势指标双均线策略的逻辑一句话讲完快线上穿慢线做多下穿做空。它是理解「趋势策略为什么会被反复打脸」的最佳教材代码也最短。def dual_ma_signal(df, fast5, slow20): df df.copy() df[ma_fast] df[close].rolling(fast).mean() df[ma_slow] df[close].rolling(slow).mean() df[signal] (df[ma_fast] df[ma_slow]).astype(int) return df参数说明fast5、slow20 是日线级别的经典组合对应一周和一个月的交易周期。fast 越短信号越密手续费消耗越大fast 和 slow 越接近策略越容易被一根大阴线打出假交叉。双均线的核心缺点是震荡市里均线反复缠绕每次交叉都是一次止损所以它必须配合仓位管理而不是单靠信号。MACD 可以看成双均线的高级版用 EMA 差值和差值均线的交叉生成信号对滞后性做了一点补偿。金叉死叉的源码实现同样短。def macd_signal(df, fast12, slow26, signal9): df df.copy() df[ema_fast] df[close].ewm(spanfast, adjustFalse).mean() df[ema_slow] df[close].ewm(spanslow, adjustFalse).mean() df[dif] df[ema_fast] - df[ema_slow] df[dea] df[dif].ewm(spansignal, adjustFalse).mean() df[signal] (df[dif] df[dea]).astype(int) return df12 / 26 / 9 是 MACD 的教科书参数最初来自证券市场的老经验。注意ewm(adjustFalse)这是国内行情软件里 MACD 的标准算法如果用了默认的adjustTrue算出来的 DIF 和行情软件对不上第一次对图就会被怀疑代码有误。两个策略可以互相印证双均线给大方向MACD 柱状图缩减给出离场预警。但入门阶段我建议先别做这种组合把单一策略的成本、回撤吃透再谈叠加。3.2 唐奇安通道与海龟通道突破与波动率仓位管理海龟交易法则是最广为人知的趋势跟踪系统核心是唐奇安通道突破过去 N 日最高价开多跌破过去 N 日最低价开空。海龟的价值不在信号而在仓位管理后面会单独讲。def donchian_signal(df, entry_n20, exit_n10): df df.copy() df[upper_entry] df[high].rolling(entry_n).max().shift(1) df[lower_entry] df[low].rolling(entry_n).min().shift(1) df[upper_exit] df[high].rolling(exit_n).max().shift(1) df[lower_exit] df[low].rolling(exit_n).min().shift(1) df[signal] 0 df.loc[df[close] df[upper_entry], signal] 1 df.loc[df[close] df[lower_entry], signal] -1 df[signal] df[signal].where(df[signal] ! 0).ffill().fillna(0) # 离场规则独立维护持多单时跌破 exit 低点平仓持空单时突破 exit 高点平仓 df.loc[(df[signal] 1) (df[close] df[lower_exit]), signal] 0 df.loc[(df[signal] -1) (df[close] df[upper_exit]), signal] 0 return df这里有两个非常容易踩的细节。第一rolling(n).max()必须加shift(1)否则当天的最高价被用来判断当天突破而现实中你不可能在最高价成交这就是标准的未来函数。第二入场用 entry 通道20 日、出场用 exit 通道10 日两条通道得分开放很多网上源码把退出逻辑和入场逻辑混在一起导致持仓状态错乱。where(...).ffill().fillna(0)的作用是把 0 信号延续成上一个非零状态同时避免开头一段数据被错误填充成持仓。海龟真正值钱的部分是 ATR 仓位管理每笔交易承担的风险固定仓位随波动率调整。def turtle_position(df, equity100000, risk_pct0.01, atr_n14, point_value1.0): df add_atr_at(df, natr_n) df[position_units] (equity * risk_pct) / (df[atr] * point_value) return dfrisk_pct0.01表示每笔交易最多亏本金的 1%point_value是每波动一个最小价位对应的盈亏金额期货和股票取值不同。波动率越大仓位自动越小。信号决定方向ATR 决定下多重的手两者解耦——这就是海龟哲学的核心信号错了靠仓位保护信号对了靠趋势放大。3.3 布林带与 RSI均值回归的两个标准件均值回归策略的假设是价格会回到均值附近。布林带盯着收盘价和 20 日均线的距离RSI 盯着超买超卖区间两个策略在震荡市里是双胞胎在趋势市里会一起翻车。def bollinger_signal(df, n20, k2.0): df df.copy() df[mid] df[close].rolling(n).mean() df[std] df[close].rolling(n).std(ddof0) df[upper] df[mid] k * df[std] df[lower] df[mid] - k * df[std] df[signal] 0 df.loc[df[close] df[lower], signal] 1 df.loc[df[close] df[upper], signal] -1 df[signal] df[signal].where(df[signal] ! 0).ffill().fillna(0) return df参数说明n20 是标准周期k2.0 是两倍标准差对应约 95% 的正态分布区间。std(ddof0)特意用了总体标准差因为行情软件里的布林带就是这么算的如果用ddof1算出来的带状更宽和你的行情软件对不上很多人回测和看盘观感不一致根源就在这一个参数。实战版本通常会在价格回到中轨时平仓代码里加一行df.loc[df[close] df[mid], signal] 0就行代价是交易次数变多。RSI 的源码核心是计算涨跌幅的强弱比值低于 30 买、高于 70 卖。def rsi_signal(df, n14, buy_th30, sell_th70): df df.copy() delta df[close].diff() gain delta.clip(lower0).rolling(n).mean() loss (-delta.clip(upper0)).rolling(n).mean() rs gain / loss.replace(0, np.nan) df[rsi] 100 - 100 / (1 rs) df[signal] 0 df.loc[df[rsi] buy_th, signal] 1 df.loc[df[rsi] sell_th, signal] -1 df[signal] df[signal].where(df[signal] ! 0).ffill().fillna(0) return df参数说明n14 是威尔德在原始著作里给出的周期buy_th30 和 sell_th70 是经典超买超卖阈值。loss.replace(0, np.nan)是为了防止连续上涨时 loss0 导致除零。这里用的是简单滚动均值而不是威尔德平滑数值上略有差异但逻辑完全一致入门阶段更容易调试。均值回归类策略有一个所有源码都不会替你处理的问题跌破下轨买入后行情可能继续单边下跌。这类策略的实盘必须有硬止损止损位一般放在开仓价减去 1.5 到 2 倍 ATR 的位置。源码里没写止损不代表不需要。3.4 动量与轮动把收益率排名直接当信号动量策略是最简单也最反直觉的策略之一买入过去 N 日涨得最好的资产逻辑是「强者恒强」。轮动是动量的横截面版本在多个资产里每期买入排名前 K 的。绝对动量策略的最小代码def momentum_signal(df, lookback20, hold_days5): df df.copy() df[momentum] df[close] / df[close].shift(lookback) - 1 df[signal] (df[momentum] 0).astype(int) idx np.arange(len(df)) df[signal] df[signal].where(idx % hold_days 0).ffill().fillna(0) return df这个实现有个值得说的细节where(idx % hold_days 0)表示只在固定周期调仓其余时间保持上一期信号不动。好处有两个——减少手续费损耗避免被动量数据的小波动反复触发。动量策略的高频版本坏就坏在每天调仓手续费会吃掉大部分超额收益。轮动策略的视角从单资产变成一张收益率矩阵。假设prices是多个资产的收盘价 DataFrame列名是各资产的代码。def rotation_signal(prices, lookback20, top_k2, rebalance_days5): prices prices.copy() ret prices.pct_change(lookback) positions pd.DataFrame(0.0, indexprices.index, columnsprices.columns) rebalance_dates prices.index[::rebalance_days] for date in rebalance_dates: row ret.loc[date].dropna() if row.empty: continue picks row.nlargest(top_k).index positions.loc[date, :] 0.0 positions.loc[date, picks] 1.0 / top_k positions positions.ffill() return positions代码逻辑每个调仓日先清空全部仓位再把排名前 K 的资产等权分配两次调仓之间用ffill()保持仓位不变。nlargest(top_k)选出排名最前的 K 个1.0 / top_k是等权权重。轮动的关键参数是标的池大小和 top_k池子越分散策略越稳但如果资产同质化高排名靠前的资产反而容易快速反转rebalance_days 和手续费直接相关第 4 章会给出敏感性测试方法。多因子选股是横截面策略的进阶版把市盈率、动量、波动率等多个维度标准化后加权打分每期选总分最高的前 N 个。这本质上是把「强弱」的定义从单一因子扩展成多维。def multi_factor_score(factor_df, weightsNone): # factor_df 是各股票的因子值行是股票列是因子 weights weights or {momentum: 0.4, value: 0.3, low_vol: 0.3} ranks factor_df.rank(pctTrue) scores sum(ranks[col] * w for col, w in weights.items()) return scores.sort_values(ascendingFalse)rank(pctTrue)把所有因子归一化到 0 到 1 的百分位消除市盈率和波动率的量纲差异这是多因子最基础的预处理。要注意因子方向市盈率越低越好排名时要反转实战里会把 value 因子取倒数或做 1 - rank 处理。权重怎么定人工经验给权重是初版做法进阶做法是对因子做 IC 分析后动态调整那超出十个经典策略的范畴这里不乱展开。3.5 网格交易与配对交易两套特殊行情工具网格交易在所有策略里最特殊它没有「信号」概念而是预先在价格上下方挂一张买卖网价格每跌一格买一份每涨一格卖一份赚震荡市里每一格的差价。它的核心是生成网格价位。def build_grid(anchor_price, grid_step0.03, grid_num10): levels [anchor_price * (1 grid_step * i) for i in range(-grid_num, grid_num 1)] return levels def grid_trade_orders(df, anchor_price, grid_step0.03, grid_num10): levels np.array(build_grid(anchor_price, grid_step, grid_num)) orders [] prev_price anchor_price for i, price in enumerate(df[close]): # 向下破一格买入向上破一格卖出真实实现必须叠加仓位上限 if price prev_price - grid_step * anchor_price: orders.append((buy, df.index[i], price)) elif price prev_price grid_step * anchor_price: orders.append((sell, df.index[i], price)) prev_price price return orders网格最容易被忽略的风险是单边行情价格一路下跌网格不断触发买入资金链断裂就是网格策略的爆仓方式。实盘至少要做三件事每格买入的仓位上限防止资金耗尽、anchor_price 的动态调整行情中枢漂移后网格要跟着移动、以及网格失效的退出条件。上面的代码只是示意真正的网格实现还必须处理同一根 K 线内多次触格的情况。配对交易是统计套利最经典的入门策略。它找两个长期走势相关的标的当价差偏离历史均值超过两个标准差时做空偏高的、做多偏低的等价差回归后平仓。def pairs_signal(a, b, window60, entry_z2.0): spread a - b mean spread.rolling(window).mean() std spread.rolling(window).std() zscore (spread - mean) / std df pd.DataFrame({zscore: zscore}) df[signal] 0 df.loc[df[zscore] -entry_z, signal] 1 df.loc[df[zscore] entry_z, signal] -1 df[signal] df[signal].where(df[signal] ! 0).ffill().fillna(0) return df配对交易和前面所有策略的最大区别是同时持有多空净值曲线理论上对大盘涨跌不敏感。但价差不回归时它就是最深的坑两个基本面已经脱钩的标的价差可能永远回不到均值。所以配对第一步要做协整检验而不是只看相关系数——相关系数高不代表价差会回归。源码里spread a - b是等权价差实战中更常见的是用 OLS 求对冲比率 beta再构造spread a - beta * b。我给这个策略加一条铁律实盘设硬止损价差超过 3 倍历史标准差就认错离场绝不等它回归。3.6 十个策略一起跑该怎么组织代码最后补充一种组织方式让你不用为每个策略各写一遍回测。把所有信号函数放进字典统一调度输出净值曲线直接对比。strategies { dual_ma: lambda df: dual_ma_signal(df, 5, 20), macd: lambda df: macd_signal(df, 12, 26, 9), donchian: lambda df: donchian_signal(df, 20, 10), bollinger: lambda df: bollinger_signal(df, 20, 2.0), rsi: lambda df: rsi_signal(df, 14, 30, 70), momentum: lambda df: momentum_signal(df, 20, 5), } # rotation / grid / pairs / multi_factor 入参不同 # 需要包一层 adapter 把各自输出转成 signal 或 positions 再进回测 results {} for name, func in strategies.items(): data func(df.copy()) results[name] run_backtest(data, fee_rate0.0003)这段代码的价值在于参数全部集中在构造处十个策略的回测口径完全一致手续费和滑点统一设置结果直接可比。有了这层封装第 4 章的参数敏感性测试只需要写成两层循环就能跑完不用复制粘贴十个回测脚本。4. 回测参数怎么设成本模型、样本外验证与过拟合识别跑通信号只是第一步回测的可信度取决于成本模型和验证方式。这一章把参数和验证讲透。4.1 手续费、滑点、停牌回测的可信度从成本模型开始经典策略源码里的默认成本参数往往是写给人看的不是写给实盘用的。手续费、滑点、印花税、冲击成本每一项在高频调仓的策略里都会被放大。网格和轮动这种高换手策略成本可以直接决定策略从赚钱变亏钱。国内市场的经验值我一般建议用下面这组参数基准成本项保守值激进值说明佣金费率0.00030.0001双边收取按成交额算滑点0.00050.0002流动性好的品种更接近下限印花税卖出0.00050.0005卖出单边部分品种免收冲击成本0.00050小资金可以忽略把保守值代回run_backtest的fee_rate slippage单边成本大约 0.0008 到 0.001。记住一个经验值如果某个策略改参数后收益提升的幅度小于成本提升的幅度这个优化就没有意义。滑点之外还有一个容易被忽视的问题涨跌停。信号触发时如果当天涨停或跌停实际是无法成交的。完善的做法是在回测里加成交过滤把停牌日和涨跌停日的 signal 置 0或者把成交价从收盘价改为次日开盘价并按涨跌停过滤。def apply_limit_filter(df, limit0.09): df df.copy() df[limit_up] df[close] df[close].shift(1) * (1 limit) df[limit_down] df[close] df[close].shift(1) * (1 - limit) df.loc[df[limit_up] | df[limit_down], signal] 0 return dflimit0.09是接近真实涨跌幅上限的保守值留一点余量是因为不同品种的涨跌停是 10% 或 20%取 9% 覆盖四舍五入和异常情形。把过滤加在信号生成之后回测曲线的可信度会明显上一个台阶。4.2 参数敏感性不要被一组最优参数带偏十个经典策略每个都有三四个参数网格搜索找最优参数是所有人拿到源码后最想做的事但这也是过拟合的开始。我跑网格搜索时会重点看一件事最优参数是孤峰还是高原。孤峰意味着参数一点小变动收益就大幅下滑这种参数不可靠高原意味着参数在很大范围内都能赚钱这才是值得实盘的区间。下面是网格搜索的骨架。import itertools def grid_search(signal_func, df, param_grid, fee_rate0.001): results [] for combo in itertools.product(*param_grid.values()): params dict(zip(param_grid.keys(), combo)) data signal_func(df.copy(), **params) data apply_limit_filter(data) out run_backtest(data, fee_ratefee_rate) max_dd (out[equity] / out[equity].cummax() - 1).min() results.append((out[equity].iloc[-1], max_dd, params)) return sorted(results, keylambda x: x[0], reverseTrue)输出结果要看两个维度排名前几的组合之间收益率差距是否很大以及各自最大回撤是否相近。如果第一名和第二名收益率差 30 个百分点但回撤类似第一名大概率是碰巧踩中了一段特殊行情。样本外验证是防止过拟合的底线操作。常见做法是把数据按时间切成三段训练段、验证段、测试段。在训练段上做网格搜索在验证段上确认参数有效最后在从未参与选择的测试段上跑一次最终确认。train df.iloc[:int(len(df) * 0.6)] valid df.iloc[int(len(df) * 0.6): int(len(df) * 0.8)] test df.iloc[int(len(df) * 0.8):]顺序很关键训练段选参数验证段微调测试段只能跑一次。测试段跑出来不好看也不要回头改参数否则测试段就失去了意义。我见过太多人在测试段之后继续回去调参这套流程一旦被污染整个回测的可信度就归零了——这属于典型的「后悔药」越吃越后悔。5. 源码落地避坑五个真实翻车现场与排查步骤这一章写的是我自己和身边人在这类源码上踩过的坑都是能直接复现的现象按「现象 → 原因 → 解决」写。这些教训用钱买来的居多能省你一次就省一次。5.1 回测收益漂亮到像作弊现象某个策略回测三年跑出年化 50% 以上最大回撤不到 5%净值曲线平滑向上让人热血沸腾。原因十有八九是未来函数。最常见的是rolling(n).max()没有加shift(1)当天的最高价被用来判断当天突破而现实中你不可能在最高价成交。解决把信号函数里所有rolling统计量都加shift(1)再在信号与收益之间加一层signal.shift(1)对齐验证。我有个习惯回测收益超过同策略行情软件显示结果的好几倍第一反应就是查未来函数而不是庆祝。5.2 回测表现和实盘差了十万八千里现象回测里每次信号都完美成交实盘里要么价格跳空买不到要么涨停排队排不上。原因回测用收盘价成交且没有涨跌停过滤实盘的成交价格和市场状态跟理想模型完全不一致。解决把成交价改成次日开盘价加上第 4.1 节的apply_limit_filter再把滑点从 0 提高到保守值。改完这三点之后回测收益通常会下降 20% 到 40%这个下降是正常的不回落的反而要警惕。5.3 换了数据时间段收益曲线直接变脸现象同一策略、同一参数在 2019 到 2021 年表现优异换到 2022 到 2024 年就变成稳定亏损。原因策略参数过拟合了前一段行情的特征。震荡市里选中的参数在趋势市里必然失效。解决用第 4.2 节的训练、验证、测试三段式流程重新评估并且别只盯收益率要看参数敏感性。如果最优参数附近一片深谷那这个策略在当前行情里就不该用这一段参数。5.4 网格交易在单边行情里把资金打光现象网格策略回测时年化收益很稳定实盘遇到一波连续下跌网格不断触发买入资金在底部区域耗尽后面反弹也没有仓位可卖了。原因网格的盈利模型建立在价格来回震荡的假设上单边行情下网格只会单向加仓没有任何止刹机制。解决给网格加两个硬约束最大持仓限额比如总资金的 60%和网格失效退出条件价格跌破最低网格且超过 N 日不回归就全部清仓。网格不是不能赚钱但它必须带刹车。5.5 配对交易价差死活不回归现象按历史数据选出的配对标的价差在开仓后不仅没收敛反而越拉越大多头空头两头亏。原因两个标的的相关性在某个时间点发生了结构性变化价差中枢已经漂移历史均值不再有效。解决配对之前先做协整检验而不是只看相关系数实盘设硬性止损阈值比如价差超过 3 倍历史标准差就离场。配对交易最忌讳的是「等它回归」四个字价差可能永远不回来。6. 让十个策略源码真正变成生产力三条进阶用法最后给三条我自己验证过的进阶用法滚动验证、行情状态过滤、策略组合。滚动验证比一次性切分数据集更接近实战。做法是固定训练窗口长度比如 250 个交易日每次向前滚动 50 天用训练窗口选参数只在接下来 50 天里执行执行完再滚动。这模拟了「每隔一段真实时间就要重新决策」的实盘节奏参数永远只在已发生的数据上确定过不会偷看未来。行情状态过滤是我认为性价比最高的一条。某个趋势策略在震荡市里必然亏损那就加一层「只在大趋势里开仓」的开关。最简单实现是加一条 200 日均线判断牛熊价格在长均线上方允许做多下方禁止开仓。给双均线加状态过滤信号函数只需要三行改动def dual_ma_with_filter(df, fast5, slow20, regime_n200): df dual_ma_signal(df, fast, slow) df[regime] df[close] df[close].rolling(regime_n).mean() df[signal] df[signal] * df[regime].astype(int) return df这一行相乘把策略和状态过滤接成了串联电路状态不对信号直接归零。加了这层过滤后双均线在震荡市里的频繁交叉被长周期状态压住回撤通常能显著下降。策略组合是更进阶的一步。把十个策略分成趋势组和回归组让它们在同一段行情里互相制衡趋势组贡献大行情的盈利回归组贡献震荡市的平滑收益。组合时不要按收益率加权要按最大回撤加权目标是最小化组合回撤而不是最大化收益。这一步做完十个策略源码就不再是十段孤立代码而是一套能应对不同行情状态的策略库。最后说一个我自己的习惯每跑完一个策略我会在代码文件头部写下三个问题的答案——这个策略赚哪种行情的钱最怕什么行情参数在哪几个值附近失效。时间长了你会发现真正有用的不是那几行信号代码而是你给每个策略建立的上下文。经典策略的源码不难找难的是想清楚它在你的资金、你的标的上还能不能成立。希望这几组代码和踩坑记录能帮到你哪怕只帮你少交一次学费也值了。本文还有配套的精品资源点击获取