ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

日频/周频量化交易策略:五个经典模型与Python实战回测详解

日频/周频量化交易策略:五个经典模型与Python实战回测详解 刚接触量化那两三年我把大量精力花在“如何更快”上面看 tick 级数据、研究盘口挂单、试图捕捉分钟内的小波动回测曲线看着挺热闹一扔进实盘账户就被手续费和滑点磨得干干净净。后来我彻底想明白一件事——对于没有券商极速柜台、没有低延迟通道的个人交易者和中小团队日频或周频的量化策略才是性价比最高的战场。这篇文章要聊的就是我自己反复回测、实盘验证过的 5 个经典日频/周频策略。它们都不是什么神秘黑科技核心逻辑大多在二十年前就已经被系统化地研究过但直到今天依然有效。我会把每个策略的适用场景、Python 实现、参数选择的原因、以及回测时最容易踩的坑都拆开讲清楚。无论你是刚开始学 Python 量化交易策略代码的新手还是已经有一套自己的回测框架、想在策略池里补充几个稳健品种的老手这 5 个策略都值得认认真真跑一遍。1. 为什么我劝你从日频和周频开始而不是死磕高频1.1 不同频率下的“能力圈”差异先泼一盆冷水高频策略听起来很性感但它的真实门槛远超多数人的想象。硬件延迟、行情快照、券商接口速度、交易费用结构任何一个环节掉链子你的alpha就会被吞噬。而且高频策略的容量通常很小个人资金量进去没多久市场冲击成本就会吃掉额外收益。日频和周频策略恰好避开了这些硬伤。一天只做一次决策甚至一周只做一次决策意味着你完全不需要和机构拼速度你拼的是逻辑的稳定性。你可以在收盘后慢慢分析数据、检查信号、写代码然后在第二天开盘按计划执行。这种节奏下有充足的时间思考和复盘心态也完全不同。1.2 日频/周频的三个现实优势第一数据获取成本极低。日线级别数据在众多免费数据源里都能拿到基本不需要去买昂贵的 tick 级数据回测的复现门槛很低。第二交易成本占比可控。日频策略一个月可能只交易几次手续费和滑点对净值的影响是有限的但如果你在分钟级别频繁进出哪怕万几的手续费一年下来也能吃掉好几个百分点的收益。第三有足够的时间做人工干预。比如消息面突变、持仓个股出现明显的基本面恶化你可以手动调整策略参数或暂停开仓这在日频级别是完全来得及的。我自己目前维护的策略组合频率最高的也就是日频大部分仓位都是周频调仓。遇到过系统性大跌的时候也就是因为决策频率低反而比天天盯着盘的人拿得住。2. 双均线趋势跟踪最简单但绝不廉价的开场2.1 均线交叉背后的逻辑双均线策略应该是所有量化入门者的第一个策略短期均线上穿长期均线做多下穿做空。但很多人把它当作“新手玩具”放着不用这其实是误解。均线交叉的本质是趋势确认。短周期均线代表短期市场情绪长周期均线代表中期持仓成本分布。当短期均线突破长期均线说明近期的买方力量已经强于过去一段时间的平均成本市场正在形成向上的趋势。反过来说如果短期均线跌破长期均线说明近期卖方力量占据主导。选参数的时候5/20 是最经典的一组5 个交易日刚好是一周20 个交易日接近一个月分别代表了一周资金和一个月资金的成本对比。你也可以用 10/30 来降低交易频率用 3/10 来抓更敏感的趋势。参数没有绝对优劣关键是它要匹配你的持仓周期和可承受的回撤幅度。2.2 代码实现与“信号平移”这个关键一步用 Python 写一个最简版本核心代码大概是这样import pandas as pd import numpy as np 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() # 信号快线在上为1快线在下为0 df[signal] np.where(df[ma_fast] df[ma_slow], 1, 0) # 关键一步信号后移一天避免未来函数 df[position] df[signal].shift(1).fillna(0) return df这段代码里最容易被忽略的是shift(1)。为什么要平移因为我们用当天收盘价计算出均线信号时最早也只能在次日开盘按信号成交如果你直接用当天的收盘价去计算收益就等于偷偷用了未来数据回测收益会虚高得离谱。这一步没做对后面所有统计指标都没有意义。回测收益的计算也很简单df[ret] df[close].pct_change() df[strategy_ret] df[position] * df[ret] df[cum] (1 df[strategy_ret]).cumprod()双均线策略最大的风险在于震荡行情。价格来回穿越均线会产生大量假信号连续小亏损非常磨人。所以实际使用时很多人会加一个过滤条件只有当长期均线本身也在上行时才开多仓或者用 ADX 指标判断趋势强度。这些都是后话先把基础版本跑通再逐步加过滤条件是比较稳妥的学习路径。我在实盘里的经验是双均线策略单看胜率并不高可能只有 35% 到 40%但赚的时候能拿住一波趋势亏的时候截断得也快整体盈亏比是正的。它的价值不在于抓住每一次波动而在于让你在市场走出大行情时保证在场。3. 动量轮动把“强者恒强”做成可执行的规则3.1 动量因子为什么长期有效如果说双均线是在单一标的上做趋势跟踪那么动量轮动就是把趋势跟踪的思想应用到一个资产池上。核心逻辑只有一句话过去一段时间涨得好的资产未来一段时间大概率继续涨。动量效应是行为金融学里被反复验证的异象之一源于投资者的处置效应和羊群行为——人们倾向于过早卖出盈利品种、死扛亏损品种导致价格的趋势在短期内被延续。虽然理论上有效市场假说说价格已反映一切信息但实证数据表明月频或周频的动量效应在绝大多数市场里长期存在。3.2 排名、选标的与再平衡的实现假设你有 5 个候选标的比如不同行业 ETF 或者几只股票动量轮动的做法是每个调仓日计算各标的过去 N 日的涨幅选出涨幅最高的 Top 2等权买入其他标的清仓。def momentum_selection(price_df, lookback20, top_n2): # price_df: DataFrame每列是一个标的的收盘价 mom price_df.pct_change(lookback).iloc[-1] selected mom.nlargest(top_n).index.tolist() return selected def momentum_portfolio(price_df, lookback20, top_n2, rebalance_days5): # 简单模拟每5个交易日调仓一次 positions pd.DataFrame(indexprice_df.index, columnsprice_df.columns, data0.0) for i in range(rebalance_days, len(price_df), rebalance_days): sel momentum_selection(price_df.iloc[:i], lookback, top_n) weights 1.0 / top_n for s in sel: positions.iloc[i, price_df.columns.get_loc(s)] weights # 信号后移一天 positions positions.shift(1).fillna(0) return positions动量周期的选择上20 日是一个比较均衡的数值既不会因为太短而频繁交易也不会因为太长而错失转折。如果做周频策略也可以把 lookback 设置为 4 周即 20 个交易日调仓周期也是 4 周形成“月度再平衡”的节奏。动量轮动最容易出问题的地方是极端波动。比如某个标的过去 20 天涨了 30%这种暴涨往往伴随着高波动你冲进去很容易接盘。所以进阶版的动量轮动会同时考虑波动率用“收益率除以波动率”的动量分数来排名也就是所谓的风险调整动量。这一步能明显降低策略的回撤。我做周频轮动时的体会是动量因子在趋势明显的市场里非常舒服但在快速反转的猴市里会连续挨打。解决思路不是放弃动量而是加入一个市场状态过滤器——比如只用指数均线判断大环境环境不好时直接空仓等环境转好再重新启用动量轮动。4. 布林带均值回归抄底摸顶的正确姿势4.1 均值回归的边界条件如果说趋势跟踪赚的是“大行情”的钱那均值回归赚的就是“价格偏离后回归”的钱。布林带是这个思路里最直观的工具以 20 日均线为中轨上下各 2 倍标准差为边界。价格触碰到上轨被认为“偏贵”触碰到下轨被认为“偏便宜”。但均值回归策略有一个必须承认的前提价格并没有真正发生趋势性变化只是短期情绪波动把它推到了极端位置。在震荡市里这个策略表现极佳但一旦市场进入单边趋势抄底会变成接飞刀摸顶会变成早下车。所以布林带策略的第一原则不是“碰到就反手”而是先判断市场是否处于震荡环境。实战里我会叠加一个趋势过滤器如果中轨本身是走平的说明市场没有明显方向此时布林带回归才可靠如果中轨斜率明显向上或向下应该放弃反手信号甚至顺着趋势方向操作。4.2 代码实现与仓位约束def bollinger_reversion(df, window20, num_std2): df df.copy() df[mid] df[close].rolling(window).mean() df[std] df[close].rolling(window).std() df[upper] df[mid] num_std * df[std] df[lower] df[mid] - num_std * df[std] # 突破下轨做多突破上轨做空 df[signal] np.where(df[close] df[lower], 1, np.where(df[close] df[upper], -1, 0)) # 中轨走平时才启用信号 df[mid_slope] df[mid].diff(5) df[filter] np.where(df[mid_slope].abs() df[std] * 0.05, 1, 0) df[position] (df[signal] * df[filter]).shift(1).fillna(0) return df这里我额外加了一个mid_slope过滤判断中轨在过去 5 天里是否基本走平目的是把单边趋势行情过滤掉。仓位约束方面均值回归策略的单笔亏损风险天然比较低因为你是在偏离均值较远的位置入场止损可以设在通道外侧一点。但我非常不建议把仓位拉满因为“便宜”不等于“不会更便宜”极端行情下布林带可以持续扩张价格贴着下轨连续下跌很久。所以要么设一个固定比例的止损要么把单笔仓位控制在总资金的一小部分。从回测数据看布林带均值回归策略的胜率通常显著高于趋势跟踪但单笔盈利幅度较小。它适合作为组合中的“减震器”配置与趋势策略形成互补而不是单打独斗。5. RSI超买超卖被误解最多的经典指标5.1 RSI的算法与不同计算方式RSI相对强弱指标大概是交易软件里出场率最高的指标之一但使用者对它的误解也最多。RSI 不是简单地看“数值大于 70 就要跌小于 30 就要涨”它的本质是衡量一定周期内涨跌力量的对比。RSI 的计算方式是先算出每日涨跌幅取上涨日的平均涨幅和下跌日的平均跌幅两者的比值再映射到 0 到 100 区间。数值越高说明过去一段时间买方力量越强数值越低说明卖方力量越占优。传统用法是在 RSI 低于 30 时买入、高于 70 时卖出这是一个超买超卖反转逻辑。但在实际使用中更稳健的做法是结合市场状态来过滤信号在震荡市里做反转在趋势市里做顺势。具体来说当 RSI 在 50 上方回落但不跌破 50可以视为强势回调继续做多当 RSI 跌破 30 后反弹越过 30才考虑抄底。5.2 代码实现与趋势市/震荡市的选择def rsi_signal(df, period14, buy_level30, sell_level70): df df.copy() delta df[close].diff() gain delta.clip(lower0).rolling(period).mean() loss (-delta.clip(upper0)).rolling(period).mean() rs gain / loss df[rsi] 100 - 100 / (1 rs) # 经典反转信号 df[signal] np.where(df[rsi] buy_level, 1, np.where(df[rsi] sell_level, -1, 0)) # 可以叠加趋势过滤当收盘价在长期均线上方时只做多单反转 df[ma_long] df[close].rolling(60).mean() df[trend_filter] np.where(df[close] df[ma_long], 1, 0) df[position] (df[signal] * df[trend_filter]).shift(1).fillna(0) return df这里clip(lower0)会把负数变成 0只保留上涨幅度-delta.clip(upper0)会把正数变成 0只保留下跌幅度取正值。注意这里用的是简单移动平均处理涨跌幅如果你想要更平滑的 RSI可以使用 Wilder 的平滑算法但初学阶段用简单平均更容易理解。实战中我对 RSI 策略的体会是它非常依赖市场的波动状态。在震荡市里RSI 到 30 附近买入的成功率很高但在下跌趋势中RSI 可以长期低于 30每次“抄底”都会变成“接刀”。所以无论你怎么设计 RSI 策略趋势过滤永远比指标本身更重要。RSI 另一个常见用法是背离价格创新低但 RSI 没有创新低说明下跌动能在衰竭。但这个信号在日频级别比较稀少更适合作为人工复核的辅助工具而不是单纯的规则化信号。6. 唐奇安通道突破海龟法则的现代简化版6.1 为什么突破值得跟踪海龟交易法则大概是量化史上被公开讨论最多的一套规则它的核心工具就是唐奇安通道创出过去 N 日新高就买入创出过去 M 日新低就卖出。这套规则看起来简单得不像话但它背后有很深的逻辑——价格突破新高意味着市场已经消化了所有获利盘新的买入力量正在推动价格进入未知区域。这种突破往往伴随着趋势的启动。日频环境下的唐奇安通道比日内突破要可靠得多因为日线级别的假突破数量远少于分钟级别。还有一个优势是它自带止损机制入场后如果价格反向跌破短周期的低点就离场。这种“突破入场 反向突破离场”的设计让整个策略有明确的进出规则不需要主观判断。6.2 代码实现与ATR止损def donchian_breakout(df, entry_n20, exit_n10, atr_n20, atr_mult2.0): df df.copy() df[upper] df[high].rolling(entry_n).max() df[lower] df[low].rolling(entry_n).min() df[exit_lower] df[low].rolling(exit_n).min() df[tr] np.maximum( df[high] - df[low], np.maximum( (df[high] - df[close].shift(1)).abs(), (df[low] - df[close].shift(1)).abs() ) ) df[atr] df[tr].rolling(atr_n).mean() # 突破入场反向突破离场 df[signal] 0 df.loc[df[close] df[upper].shift(1), signal] 1 df.loc[df[close] df[exit_lower].shift(1), signal] -1 # 持仓状态延续直到出现反向信号 df[position] df[signal].replace(0, np.nan).ffill().fillna(0).shift(1) return df注意最后一行signal.replace(0, np.nan).ffill()的作用是保持持仓状态直到出现反向信号才改变仓位。如果不做这一步策略就会变成“每天换仓”完全违背了突破跟踪的初衷。ATR 止损和入场是分开管理的。入场用唐奇安通道止损用当前价格减去 2 倍 ATR。ATR 本质上是市场的平均波动幅度用它来止损可以做到“波动大时止损宽、波动小时止损窄”比固定百分比止损更贴合市场状态。唐奇安通道趋势策略最大的缺点是回撤时间长。市场没有趋势的时候价格会在通道上沿下方反复试探触发很多次假突破每次都很小亏损。所以在实际配置中我只会给这个策略分配固定比例的资金而不是把全部资金都押进去。胜率不重要重要的是在抓到那一两次大趋势时仓位还在能吃到足够的利润。7. 回测里最容易翻车的四个细节一次完整的排查链路7.1 未来函数从“收益离谱”到“信号错位”先讲一次我自己的经历。早期我用双均线策略回测一个标的年化收益率跑出来接近 80%最大回撤只有 6%。当时我以为发现了圣杯结果一个前辈看了一眼代码就指出问题我的信号没有做shift(1)等于用当天收盘价计算信号又以当天收盘价计算收益偷看了未来。排查链路是这样的先看回测指标是否“好得不真实”年化收益远超同策略公开报告的水平。抽查几笔交易记录看买入价是否恰好等于信号生成日的收盘价——这本身就值得怀疑因为实际中你不可能在收盘那一瞬间以收盘价买入。检查信号与收益的时间对齐方式确认position序列在信号生成当天是否已经生效。如果生效了把position整体后移一天再跑一遍对比两份结果。修复之后年化收益掉到 18%最大回撤上升到 12%。这才是合理的水平。所以说回测代码写完以后第一件事不是看收益而是检查有没有未来函数。7.2 手续费与滑点请不要用“理想世界”骗自己很多人回测时只按收盘价计算成交完全忽略手续费和滑点。日频周频策略交易次数不多这个问题会被很多人轻视但一年累计下来费用对净值的侵蚀仍然可观。我自己回测时会用一组现实参数作为起步默认值双边手续费按 0.03% 计算滑点按 0.02% 计算也就是每次换仓大约消耗 0.05% 的成本。对于动量轮动这种每周调仓的策略一年调仓 50 次光成本就会消耗掉 2.5 个百分点如果你回测时完全不计成本这 2.5% 就会被当成策略收益完全是幻觉。7.3 幸存者偏差你的“股票池”是不是早就被时代筛选过幸存者偏差是最隐蔽的坑。如果你用的是“当前仍然在市”的股票列表去回测过去 5 年那些早已经退市、跌幅巨大的标的完全不会进入样本你的回测等于天然排除了大输家。最后算出来的收益自然好看得像天上掉馅饼。排查方法是看数据源是否提供了退市股票的历史数据。如果没有至少在心里清楚一点——你回测的收益会系统性偏高。更稳妥的做法是从指数成份股的历史快照里取标的而不是用当前成份股倒推历史。7.4 参数过拟合滚动窗口和样本外验证每个策略都有参数均线的 5/20、布林带的 20/2、RSI 的 14/30/70这些数字不是神圣的。如果你在回测时不断微调参数让收益曲线变得好看那你只是在拟合历史噪声实盘一定会现出原形。我的习惯是把数据切成三段前 60% 做训练用来选参数中间 20% 做验证看参数是否在未参与拟合的数据段依然有效最后 20% 做样本外测试只跑一次跑完就是最终结果。如果样本外表现和训练段差距明显说明策略本身就不稳定需要重新设计逻辑而不是继续调参数。8. 从回测曲线到真实账户还差仓位和心态这两关8.1 仓位管理用半个凯利而不是满仓凯利公式在纯赌博场景下可以精确给出最优下注比例但金融市场存在不确定性分布完全按凯利公式满仓操作一次极端波动就可能导致毁灭性回撤。我个人的做法是用凯利公式算出参考仓位然后只取一半作为实际仓位也就是所谓的“半凯利”。简单版凯利可以用一个近似的公式估算f* W - (1 - W) / R其中 W 是历史胜率R 是平均盈利除以平均亏损的比例。比如某个趋势策略的胜率是 40%平均盈利 / 平均亏损 2那么f* 0.4 - 0.6 / 2 0.1也就是说每次只用 10% 的资金去承担风险。如果所有策略加在一起的风险敞口超过总资金的 20%我会强制降低仓位。8.2 回撤预案先想好最大亏损再谈收益实盘之前我会为每个策略写好一份“应急预案”策略回撤达到什么水平时降低仓位、达到什么水平时暂停交易、达到什么水平时直接下线。比如动量轮动策略连续三周亏损且累计回撤超过 8%就先停下来重新检查市场环境是否发生了结构性变化。这套预案不是写给别人看的是写给自己在最难受的时候看的。没有预案的人最容易在回撤最深的底部砍仓离场把浮亏变成实亏。8.3 执行纪律策略失效的识别方法与退出机制任何一个策略都不可能永远有效。当动量因子的超额收益持续走弱、双均线的假信号频率明显增加时要敢于承认策略进入了钝化期。我会按月记录策略的实盘表现与回测基准的偏差连续三个月出现显著负偏差就开始启动降仓和复盘流程。最后再分享一个我自己的执行习惯日频周频策略的决策次数很少所以每一笔交易都值得认真对待。我会把下单动作固定在每天收盘后的固定时间完成绝不盘中去追临时信号。这个习惯帮我躲过了很多次盘中冲动的交易也让策略的回测结果和实盘结果之间差距控制在可以接受的范围内。量化交易的本质不是预测市场而是用规则约束人性回测只是第一步真正拉开差距的是后面这些“看不见”的纪律。
返回列表