ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

10种经典量化策略源码解析:Python实现与回测避坑指南

10种经典量化策略源码解析:Python实现与回测避坑指南 简介量化交易策略的经典集合包专门面向金融科技与算法交易方向的学习者。系统梳理趋势跟踪、均值回归、统计套利、高频交易、机器学习、事件驱动、波动率、多因子选股、套利及另类数据等十种策略每种策略涵盖核心逻辑、常用指标、适用市场与潜在风险便于横向对比不同思路的适用边界。实践部分基于backtrader框架给出移动平均线交叉策略的代码示例配合说明文档与依赖配置可快速完成从策略设计到结果输出的回测流程。包内共五个文件包含源码、回测结果图、依赖清单、说明文档与辅助配置压缩包仅一百四十六KB内容紧凑易读。已有三百四十二人学习下载适合量化入门者系统搭建策略知识库也为有经验者提供策略盘点与回测参考。1. 这 10 种经典量化策略到底值不值得你下载源码做量化这行大家最常问的一句话不是“你收益多少”而是“你那个策略源码能不能发我一份”。我电脑里存了上百份别人发的策略源码其中真正跑起来不亏钱的不到两成能跑满一年不出逻辑 bug 的不到三成。这 10 种经典量化策略源码也是这么来的不是某个大神的神秘黑匣子而是把趋势、均值回归、套利、网格这几类最常见的交易逻辑用最朴素的 Python 代码实现出来。它们解决的核心问题是让你在写自己的策略之前先有一条不用从零开始的起跑线知道一个完整的量化策略代码长什么样、参数怎么传、回测怎么跑。如果你正在找“python量化交易策略代码”甚至想直接拿去跑实盘这篇文章会告诉你每一行代码背后的选择理由以及哪些参数是拍脑袋定下来的、哪些是真有统计依据的。新手照着这个框架走能把回测跑通熟手能在这里找到改进的边界和可替换的模块。源码的意义不在代码本身而在于你拿到它之后能改出什么。2. 先建地基量化策略依赖的数据管道与回测底座2.1 数据从哪里来免费数据源的选型与边界写策略前先要解决数据问题这是所有策略能跑起来的前提。常见做法是用akshare或tushare这类免费接口拉行情数据我不建议一上来就买昂贵的数据终端因为你还在验证策略逻辑的阶段免费数据的精度完全够用。但要注意免费数据有几个边界前复权价格在除权除息日附近可能跳变分钟级数据的历史深度通常只有两年以内财务数据的更新存在时滞。这些边界直接影响回测真实性后面会重点讲。我的数据管道一般长这样每日收盘后定时拉取日线数据落本地存成 parquet 文件回测时直接用本地文件。这样做的好处是策略回测不依赖网络结果可复现不会因为数据源更新导致回测结果第二天就变了。以下是我常用的数据拉取代码import akshare as ak import pandas as pd from pathlib import Path def fetch_daily_data(symbol: str, start_date: str, end_date: str) - pd.DataFrame: 从 akshare 拉取 A 股日线数据落本地 parquet df ak.stock_zh_a_hist( symbolsymbol, perioddaily, start_datestart_date, end_dateend_date, adjustqfq # 前复权 ) df.rename(columns{ 日期: date, 开盘: open, 收盘: close, 最高: high, 最低: low, 成交量: volume }, inplaceTrue) df df[[date, open, close, high, low, volume]] df[date] pd.to_datetime(df[date]) return df # 使用示例 if __name__ __main__: Path(./data).mkdir(exist_okTrue) df fetch_daily_data(600519, 20190101, 20241231) df.to_parquet(./data/600519.parquet) print(df.tail())这里最关键的是adjustqfq这个参数。前复权能保证你计算收益率时不受分红送股影响但如果你要做的是多股票横截面比较比如第 3 章要讲的单因子选股策略就需要用后复权数据否则每只股票的复权基准不同对比会有偏差。另一个注意点是stock_zh_a_hist的返回字段是中文列名不 rename 的话后面每个策略代码里都要处理一遍提前改好省心很多。2.2 回测引擎选型轻量自研还是用现成框架数据就绪后需要一个回测引擎把策略跑起来。市面上有backtrader、vectorbt、zipline等现成框架但我个人倾向于写一个 200 行的轻量向量化回测逻辑原因有三。第一这 10 种策略大多是信号驱动的同向操作模式全仓或空仓不需要处理复杂的仓位管理第二自研回测的每行代码你都看得懂出了问题好排查第三现成框架的学习曲线本身就是一道坎很多初学者会在配置backtrader的 cerebro 实例时卡住反而忽略了策略本身的逻辑。我用的回测核心是一个run_backtest函数从策略信号列生成仓位列、资金曲线和交易记录import numpy as np def run_backtest(df: pd.DataFrame, signal_col: str, initial_cash: float 1_000_000): 向量化回测信号为 1买入/持有或 0空仓 返回资金曲线和每笔交易记录 df df.copy() df[position] df[signal_col].shift(1).fillna(0) # 次日开盘执行信号 df[returns] df[close].pct_change().fillna(0) df[strategy_returns] df[position] * df[returns] # 计算资金曲线考虑手续费和滑点 fee_rate 0.0003 # 单边万三 slippage 0.0002 # 滑点万二 df[cost] np.abs(df[position].diff()) * (fee_rate slippage) df[net_returns] df[strategy_returns] - df[cost] df[equity] initial_cash * (1 df[net_returns]).cumprod() # 提取交易点 trades [] position_changed df[position].diff().fillna(0) ! 0 for idx in df.index[position_changed]: trades.append({ date: df.loc[idx, date], action: BUY if df.loc[idx, position] 1 else SELL, price: df.loc[idx, open], signal_prev: df.loc[idx, signal_col] }) return df, trades一个极容易被忽略的细节是shift(1)。盘中不能看到收盘价后立刻按收盘价成交所以信号要滞后一天用次日开盘价执行。如果你忽略这行回测收益会被信号穿越虚增不少——这个是新手最容易犯错的地方也是回测看起来很美好实盘却翻车的第一大原因。position.diff()算出的交易次数用于提取交易明细同时也用于计算单边成本。默认万三手续费加万二滑点是对 A 股比较保守的估计如果你的策略交易频率很高这部分成本会吞掉大部分利润后面网格策略部分会具体演示。3. 10 种经典策略的信号逻辑与可运行实现3.1 策略全貌收益来源与止损依赖一览在贴代码之前先用一张总表看清这 10 种策略分别赚什么钱。这个视角很重要因为同一类型的策略源码拿再多也不会变出新的 alpha你必须知道每个策略在赚谁的钱才能正确设置参数和判断失效边界。序号策略名称分类核心逻辑适合品种持仓周期止损依赖1双均线交叉趋势快线上穿慢线做多股指期货/ETF中长线低2MACD 金叉死叉趋势金叉做多死叉做空宽基指数中长线低3RSI 超买超卖反转RSI 低于阈值买入震荡品种短线高4布林带回归反转触下轨买入破中轨平仓商品短线高5动量截面排序趋势过去 N 日涨幅前 K 名多品种组合月度调仓中6均值回归 Z-score反转价格偏离均值 2 倍标准差买入外汇/商品短线高7网格交易震荡价格每跌一格买入每涨一格卖出区间震荡品种短线极高8海龟唐奇安通道趋势突破 20 日高点入场趋势品种中长线低9配对交易统计套利价差 Z-score 回归同赛道两只股票中短线中10单因子截面选股多因子按估值/动量因子排序买入全市场股票月度调仓中这张表只解决一个问题你在什么场景下用什么策略。A 股这种牛短熊长的市场里趋势策略的胜率虽然低但盈亏比高行情来了能吃到整段震荡市里网格和均值回归更容易出利润。下面逐个给出可运行的核心信号代码统一用前面建好的本地 parquet 数据文件作为输入。3.2 趋势类策略双均线、MACD、唐奇安通道趋势类策略本质上都是“追涨杀跌”。它们的共同特点是胜率低、盈亏比高靠吃大波段覆盖多次小额亏损。双均线交叉是其中最基础的逻辑是当短期均线从下方穿越长期均线时说明趋势可能反转向上产生买入信号。直接给核心代码def signal_ma_cross(df: pd.DataFrame, fast: int 5, slow: int 20) - pd.Series: 双均线交叉信号快线上穿慢线时返回 1下穿返回 0 ma_fast df[close].rolling(fast).mean() ma_slow df[close].rolling(slow).mean() # s1 为 1 表示快线在慢线上方 s1 (ma_fast ma_slow).astype(int) # 用 diff 捕捉上穿动作状态从 0 变 1 的时刻 signal s1.diff() # cross_up 是上穿点把信号延续成持仓状态 signal signal.fillna(0) signal (signal 1).astype(int) # 用 forward fill 让持仓状态持续到出现下穿信号 # 这里用一个简单做法直接返回 1 表示持有0 表示空仓 # 完整持仓状态由信号拼接而成出现下穿时产生 0 cross_down (s1.diff() -1).astype(int) # 通过两列信号构造持仓状态 pos pd.Series(np.nan, indexdf.index) pos[signal 1] 1 pos[cross_down 1] 0 pos pos.ffill().fillna(0) return pos参数fast和slow的选择是整段逻辑的灵魂。经典的 5/20 组合在日线上换手适中5 日线对价格波动敏感20 日线过滤了大部分噪声两者在大多数宽基指数上能跑出正收益。但如果你把它用到外汇黄金这种波动率高的品种上5 日线会被来回打脸建议改成 10/30 甚至 20/60。注意我这套实现里把上穿点之后的持仓状态直接 forward fill这比很多网上源码里“信号为 1 就买入为 0 就卖出”的写法更准确——后者会在信号消失的当天马上卖出交易成本高得多。MACD 金叉本质是双均线的变体它先把快慢线做差得到 DIF再把 DIF 做指数平均得到 DEA金叉就是 DIF 上穿 DEA。唐奇安通道则是海龟交易法的核心当收盘价突破过去 20 日的最高价时全仓买入跌破过去 10 日最低价时离场。唐奇安通道的优势是入场逻辑直接不绕弯参数 20/10 沿用海龟法则的原版参数在绝大多数品种上表现稳定不太需要调优。3.3 反转类策略RSI 与布林带回归反转类策略赚的是“跌过头”的钱。RSI 超买超卖的逻辑是当相对强弱指标低于 30 时说明市场恐慌过度是一个潜在的买点。这类策略的问题在于它是抄底摸顶在单边下跌趋势中会一路接飞刀所以必须搭配止损。以下是一个带止损的 RSI 策略实现def signal_rsi_reversal(df: pd.DataFrame, period: int 14, buy_thresh: float 30, stop_loss: float 0.08) - pd.Series: RSI 超卖买入 固定止损 # 计算 RSI delta df[close].diff() gain delta.clip(lower0).rolling(period).mean() loss (-delta.clip(upper0)).rolling(period).mean() rs gain / (loss 1e-10) # 加极小值防止除零 rsi 100 - (100 / (1 rs)) pos pd.Series(0, indexdf.index) entry_price None for i in range(1, len(df)): if pos.iloc[i-1] 0: # 空仓时检测买入条件 if rsi.iloc[i] buy_thresh: pos.iloc[i] 1 entry_price df[close].iloc[i] else: # 持仓时检测止损条件 pnl_pct (df[close].iloc[i] - entry_price) / entry_price if pnl_pct -stop_loss: pos.iloc[i] 0 entry_price None else: pos.iloc[i] 1 # 继续持有 return pos这代码用了循环而不是向量化因为止损条件依赖持仓路径遇到这种情况循环是直白可靠的写法性能损耗在单标的日线回测中无所谓。stop_loss这个参数我见过很多人设成 5%但结合 A 股日线波动率看8% 到 10% 更合理5% 太容易被单日大阴线洗出去。RSI 的周期参数也要结合交易周期调1 小时图上用 9 更敏感日线上 14 是默认值用 7 会更频繁地触发买卖、交易成本随之翻倍。布林带回归的逻辑差别不大核心区别在于它的买卖界限是动态的布林带带宽跟随波动率自动调整所以它在波动率突变的环境里比固定阈值的 RSI 更稳健。布林带参数是 NB 日移动平均加减 K 倍标准差我常用 20/2.0 组合入场条件是触及下轨离场条件是回归到中轨绝不等到上轨因为从下轨到上轨那一段利润空间并不总是存在。3.4 截面与统计套利策略动量轮动、配对交易、单因子选股动量轮动策略的逻辑是每月月初把所有可交易标的按过去 20 天涨幅排序买入前 N 名持有到月底换仓。它属于高换手策略所以我前面的成本模型必须保留。以下代码演示如何在一个股票池上执行截面排序def signal_momentum_universe(stock_pool: dict, lookback: int 20, top_n: int 3): stock_pool: {600519: df, 000001: df, ...} 每只股票都有 close 列 返回每只股票每个调仓日的目标仓位 # 对齐所有股票的最新的 close 到统一日期索引 all_close pd.DataFrame({ code: df[close] for code, df in stock_pool.items() }) # 计算每只股票过去 lookback 天的涨跌幅 momentum all_close.pct_change(lookback) # 取每月第一个交易日作为调仓日 rebalance_days momentum.resample(ME).first().index position_df pd.DataFrame(0.0, indexmomentum.index, columnsmomentum.columns) for day in rebalance_days: day_momentum momentum.loc[day] top_codes day_momentum.nlargest(top_n).index for code in top_codes: position_df.loc[day:, code] 1.0 / top_n # 等权买入 top_n 只 return position_dfresample(ME)是 Pandas 2.x 的写法老代码里写的M现在已经废弃照抄老网的源码会直接报错这是我在迁移过程中最容易踩的坑。动量策略有个不显著的细节它天然自带行业过滤效应涨得最好的往往是当时最热赛道等于让模型替你做了行业轮动。但这也意味着一旦赛道切换策略会在最大的那根阴线上完成换仓所以必须容忍回撤没有绝对的止损线反而是它的优势。配对交易的逻辑更偏统计套利找到两只基本面相近或同属一板块的股票当它们的比价关系偏离历史均值时做多被低估的那只、同时做空被高估的那只。A 股做空受限常见的替代方案是做多价差方向本身。这里给一个完整的价差 Z-score 计算和交易信号生成def signal_pairs_trading(df_a: pd.DataFrame, df_b: pd.DataFrame, window: int 60, z_entry: float 2.0): 配对交易两只股票价格序列的价差 Z-score 突破阈值时交易 返回信号序列1 表示做多 A 做空 B-1 表示反向 # 合并两只股票收盘价按日期对齐 merged pd.DataFrame({ a: df_a.set_index(date)[close], b: df_b.set_index(date)[close] }).dropna() # 用对数价格差保证量纲稳定 spread np.log(merged[a] / merged[b]) zscore (spread - spread.rolling(window).mean()) / spread.rolling(window).std() signal pd.Series(0, indexmerged.index) signal[zscore z_entry] -1 # 价差过大做空价差买 B 卖 A signal[zscore -z_entry] 1 signal[zscore.abs() 0.5] 0 # 价差回归后平仓 return signal, zscorez_entry取 2.0 意味着价差偏离历史均值两个标准差才入场0.5 是离场阈值——不等它回归到均值再平留一点缓冲这一细节能显著降低反复穿梭开平仓的手续费消耗。配对交易的窗口参数window决定了你用的“历史正常水平”是多久60 天适合中短期套利120 天以上更稳但反应迟钝而且如果两只股票的价差结构真的发生永久性漂移比如公司基本面重组导致的估值体系改变任何窗口都会失效。第十种单因子选股本质上把动量轮动扩展到了全市场几百只股票用 PE、PB、ROE 或过去 N 日涨跌幅任一因子排序取前 K 只等权买入。核心代码与动量轮动完全同构区别只在因子计算列动量因子用过去涨跌幅、价值因子用1 / pe_ttm、质量因子用roe。这部分的实现复用 3.4 节代码即可不需要单独写。网格策略比较特殊它没有“方向判断”只有价格区间。把当前价格上下各划 N 格每跌一格买入一份、每涨一格卖出一份靠波动率赚钱。它在单边下跌中必死但在区间震荡中是收益率最稳的策略。网格间距通常设为当前价格的标准差比例A 股 ETF 常用 2% 到 3%间距太小交易成本会吃光利润。4. 跑回测时参数怎么设才不骗自己四个关键决策点4.1 回测区间选择与过拟合识别参数设得好不好第一看回测区间。很多网上免费源码跑出的高收益图是拿了 2019 到 2020 年的数据——那两年消费医药单边上涨你闭着眼买任何趋势策略都是正的。我一般把回测区间劈成三段看单边上涨段、单边下跌段、宽幅震荡段。一个策略如果三段里至少两段能打平或盈利才算初步通过五五年的连续单边上涨不能证明任何事。区间长度至少需要覆盖一个完整的牛熊周期A 股大概需要 7 到 10 年日线数据。这直接否定了用分钟级数据做长周期回测的做法分钟数据 3 年回测区间根本说明不了策略在熊市里的表现。两个系统性的偏差来源要格外留意幸存者偏差——今天能看到的数据都是活到今天的股票退市的那些你根本没有历史数据前视偏差——用财报数据时报告发布日和资产负债表日之间的时滞必须考虑否则你用了当时看不到的数据。避免前视偏差的常见做法是把调仓日硬往后推 10 个交易日给数据发布留出时间差。4.2 交易费用与资金容量设定回测参数里最容易被浪漫化的是手续费。真实 A 股成本包含佣金、印花税、滑点和冲击成本四部分印花税只在卖出时收取千分之零点五券商佣金最低万二点五但小资金和量化账户实际拿到的佣金差别很大。我沿用了 2.2 节代码里的万三佣金加万二滑点这是偏保守的设定高频策略必须用自己的实盘成交记录校准。资金容量问题是线下交流会里大家最关心的因为换手高的策略网格、RSI小资金跑得欢资金量一大就滑到妈都不认识。日线级别策略单标的容纳 500 万以内问题不大分钟级策略超过 50 万就要小心冲击成本。如果你的策略资金上限大于这个数回测时要把滑点从万二上调至万五再跑一遍看收益曲线是否还能使用。4.3 基准对比与指标解读回测输出不能只有资金曲线必须同时输出年化收益、最大回撤、夏普比率和卡玛比率四个指标。年化收益 20% 对 A 股日线策略是合理目标超过 40% 就要怀疑是不是有偏差最大回撤超过 30% 的策略对散户心理是巨大考验实盘大概率在最大回撤处割肉离场纸上收益变成真实亏损这就是回测与实盘的心理学差距。夏普比率超过 1.5 说明策略的盈利质量好低于 1 说明收益是靠承担过度风险换来的。跑完指标后要和基准比。A 股基准可以用沪深 300 指数同期收益年化跑不赢基准的纯多策略没有存在意义。我习惯把策略净收益与策略最大回撤的比值卡玛比率放在最前面看——它直接回答“这段收益要用多大的煎熬换”比只看年化收益真实得多。def backtest_metrics(df, initial_cash1_000_000): 回测指标汇总 df, _ run_backtest(df, signal) equity df[equity] total_return equity.iloc[-1] / initial_cash - 1 years len(df) / 252 annual_return (1 total_return) ** (1 / years) - 1 rolling_max equity.cummax() drawdown (equity - rolling_max) / rolling_max max_drawdown drawdown.min() sharpe df[net_returns].mean() / df[net_returns].std() * np.sqrt(252) calmar annual_return / abs(max_drawdown) print(f年化收益: {annual_return:.2%}) print(f最大回撤: {max_drawdown:.2%}) print(f夏普比率: {sharpe:.2f}) print(f卡玛比率: {calmar:.2f}) return { annual_return: annual_return, max_drawdown: max_drawdown, sharpe: sharpe, calmar: calmar }252是 A 股一年大约的交易天数用 365 会把夏普算虚高。drawdown.min()给的是历史最惨时刻的浮亏比例这个数字一定要有心理预期实盘的最大回撤几乎是回测结果的 1.2 到 1.5 倍因为回测没有考虑黑天鹅导致的流动性瞬间枯竭和极端情绪下的延迟执行。5. 量化策略落地避坑指南这些坑我先替你踩过了5.1 数据复权不一致回测收益虚高的经典翻车现场现象同一套策略用某免费数据源跑年化 25%换另一个数据源年化变成 8%差距巨大。原因两个数据源的前复权算法不同增量复权和加权复权在除权除息日附近对价格曲线的重算方式不一样导致信号日期偏移买在了不同的价格位置上。更隐蔽的是有些数据源在复权基准日变化时整个历史序列都变了你上次跑的策略代码一行没改结果却变了。解决把数据按时点和版本做快照缓存或钉死一个数据源做日常回测只在最终验证时换源交叉比对。每次回测前记录数据拉取日期和数据源版本号我吃过这个教训后在数据管道里固定加了一列data_version字段回测结果可追溯。5.2 生存者偏差策略在历史上“不可能亏损”现象回测里选的股票池年年创新高但实盘买进去发现组合里的股票早就该退市的退市、该 ST 的 ST回测收益腰斩。原因用今天依然存活的股票做历史回测那些曾经上市但已经退市的股票不在数据里——它们的暴跌你根本看不到历史收益自然虚高。这是 4.1 节提过的幸存者偏差在落地层面的直接体现。解决把股票池扩充到包含历史退市股票点源类数据通常提供退市股票的历史行情。如果数据源不支持至少不要选近年刚被 ST 的股票做样本外测试再退一步就是用指数 ETF 代替个股池天然规避个股退市问题。5.3 参数平原识别同一个参数稍变就由盈转亏现象双均线策略用 5/20 回测赚钱把 slow 改成 21 之后年化收益从 12% 变成 -3%简单到令人窒息的参数敏感度。原因最优参数可能只是历史数据里的巧合峰点。策略在真实逻辑上并不随该参数单调变化只是某个历史瞬间恰好让这个数值表现特别好就像对着心电图找规律。解决做法是把参数在合理范围内做网格扫描看收益曲面的形态。如果最优值周围是一片高原说明策略稳健如果最优值是个孤峰两边都是悬崖说明过拟合这个参数组合不可信。以下是一个简单的参数敏感性检验代码def param_sensitivity(df, param_name, values): 扫描单一参数检查收益是否对参数敏感 results [] for v in values: if param_name fast: signal signal_ma_cross(df, fastv, slow20) elif param_name slow: signal signal_ma_cross(df, fast5, slowv) else: raise ValueError(未知参数) df[signal] signal metrics backtest_metrics(df) results.append((v, metrics[annual_return], metrics[max_drawdown])) return results # 检查 slow 在 15~30 之间的收益变化 for v in range(15, 31): print(v, backtest_metrics(df.assign(signalsignal_ma_cross(df, 5, v))))打印出来的表里如果相邻参数收益落差小于 20%策略是安全的落差超过 50%就说明这个参数组合只是历史巧合。不要迷信任何网上公开源码的“最优参数”下载源码后第一件事就是跑一遍这个敏感性测试。5.4 实盘盘中信号延迟回测与实战的时间差现象策略信号在收盘后 15 分钟才产生用收盘价成交的回测假设根本不存在次日开盘价直接高开跳过进场点位策略收益缩水明显。原因程序化实盘下单需要数据接收、信号计算、订单撮合三段延时回测里如果用收盘价成交等于免费送了一个未来函数。A 股次日开盘价和当日收盘价经常差出 0.5% 甚至更多这对薄利策略是致命打击。解决回测把成交价从close改成次日open。我的run_backtest代码里已经用了shift(1)加次日成交的写法这是对真实交易的最小模拟。如果仍然觉得落差大可以再加一个保守修正把次日开盘价加 0.2% 作为实际成交价这个误差参数来自统计真实下单时的平均偏差。很多平台数据号称“支持 tick 级回测”但免费级别的 tick 数据质量参差不建议在这个阶段上 tick 回测先做到日线级的保守估计就足够判断策略有效性。5.5 代码本身的分歧Pandas 版本迁移惹的祸现象从网上下载的旧源码多是 pand 1.x 之前写的直接跑报KeyAxisError或M频率不识别网上搜一下才发现是 API 变了。原因Pandas 2.x 改了resample的频段字符串规则M改成ME老源码全部失效同时fillna(methodffill)的写法被废弃统一成了df.ffill()。量化策略源码的社区更新往往追不上 pandas 的迭代速度。解决下载源码后先用print(pd.__version__)确认版本遇到报错优先考虑 API 迁移问题而不是策略逻辑问题。我维护策略代码时尽量不用被废弃的写法数据操作统一走最新的链式调用风格保证两年后重新拉出来还能一键跑通。6. 参数寻优不那么玄学的三种实用技巧最后一章聊参数怎么调但它不是让你去找最佳点而是让你找出参数高原区域的稳定策略。有三个技巧是我一直在用的比单纯网格搜索可靠得多。第一个技巧是滚动窗口样本外验证。把 10 年数据分成 5 段前 4 段用来选参数最后 1 段留作样本外验证。这里有一个大家容易操作错的细节选参数时不要看样本外那段数据不然样本外就名存实亡了。跑完样本外之后再把窗口向前滚动一段重新选参数用新样本外验证。滚动验证比一次性切分更能检验策略在时间维度上的稳定性因为它天然覆盖了不同市场状态。参数变化如果导致样本外收益忽高忽低说明策略不具备持续赚钱的来源。第二个技巧是给参数加“正则化”约束。不要选极端参数值趋势类策略不要把快线设成 2也不要把慢线设成 200。极端参数往往意味着它在拟合某一段特定历史行情。我会设置一个经验区间双均线的慢线设在 15 到 60 之间RSI 周期设在 9 到 21 之间超出这个区间的参数即使回测收益再高也不采用。这个约束不是理论推导出来的而是我跑了大量品种后总结出来的经验边界——翻车的参数组合几乎全在极端区域。第三个技巧是打印全部交易明细逐笔检查。网格策略和配对策略最依赖这个方法。逐笔看交易记录能发现积分式 bug比如策略在某一天同时出现买入和卖出信号仓位状态错乱这类问题收益率指标根本发现不了只有看交易明细才能看到异常。这是我的习惯每次参数改动后必须把trades列表导出成 CSV 人工扫一遍再跑大回测。# 导出交易明细进行人工检查 df, trades run_backtest(df.assign(signalsignal_ma_cross(df, 5, 20)), signal) import pandas as pd trades_df pd.DataFrame(trades) trades_df.to_csv(./check_trades.csv, indexFalse) # 重点看 trade 时间是否成对出现买卖是否交替持仓天数是否异常 print(trades_df.tail(20))看输出时关注三类异常连续两次 BUY 没有 SELL 夹在中间、持仓周期小于 2 天且频繁出现、买卖价格差超过当日振幅的 90%。这些异常通常指向信号计算里的边界条件问题比如shift方向弄反或fillna顺序不对。找到并修正后策略才具备可维护性。做量化这几年最深的教训是源码本身不值钱值钱的是你对每行代码的边界条件的理解。今天这 10 个策略的代码你能下载到一百个版本但能快速定位 bug、敢改参数、知道优化方向的人才有持续迭代的能力。不要拿到源码就急着实盘先在 4.1 节的三段行情里反复验证跑完参数敏感性测试再想办法改进。希望帮到你。本文还有配套的精品资源点击获取
返回列表