用Python和pandas实现市场反弹右侧的周度策略回测
市场反弹右侧这个说法在周度策略分析里经常出现。很多人看盘时能一眼认出“这里好像反弹了”但落到判断依据上往往只能说“感觉跌不动了”“放量了”这类主观描述。真正的问题是同样一段行情不同人用不同口径判断结论很难复现更谈不上事后检验。本文结合 Python 和 pandas把“市场反弹右侧”拆成趋势、突破、量能三个可计算维度做成一个每周五收盘后自动确认、按周持有的回测流程。学完后你可以在自己的行情数据上跑通这套周度策略研究链路并知道信号不出现、回测太漂亮、指标对不上时该从哪里排查。本文只讨论策略研究方法与程序实现不构成投资建议也不针对任何具体标的作推荐。1. 右侧反弹和周策略的底层逻辑1.1 左侧与右侧被动等待和主动确认通俗地说左侧交易是在下跌过程中“抄底”价格还没企稳就提前买入右侧交易是等价格从下跌转向上涨、出现明确信号后再参与。两者没有绝对优劣但“右侧”天然更适合周度策略因为一周才检查一次信号等趋势确认后再行动可以过滤掉大量盘中噪音。技术定义上右侧反弹不预测最低点而是把买入条件交给一组可观测规则比如“收盘价站上 20 日均线”“突破近期高点”“成交量同步放大”。这些规则共同构成一个确认机制只有当价格用事实表明反弹开始后策略才给出持有信号。需要特别澄清一个误区右侧交易不等于一定会涨。它只是把“判断最低点”这个困难问题换成“判断信号是否成立”这个相对可标准化的问题。信号确认之后行情仍然可能逆转所以回测和风险控制同样重要。1.2 周策略报告为什么需要可复现指标很多周度策略报告的问题是口径不确定。比如上周说“市场有反弹迹象”下周说“反弹力度不足”但反弹到底看什么指标多少日均线放量放多少倍完全没有可复现的标准。可复现指标要求每个判断都能落到具体数值上使用哪条均线收盘价必须站上它。近期高点窗口是 20 个交易日还是 60 个交易日。成交量放大到什么倍数才算有效。多个条件之间是“全部满足”还是“满足其中若干条”。一旦规则确定任何人都能在同一份数据上得到相同结果。这也是程序化方法对主观看盘最大的价值。1.3 本文的流程框架整套流程可以分解成四步准备日线行情数据包含日期、开盘、最高、最低、收盘、成交量。计算技术指标把价格形态转换成数值特征。合成“右侧确认信号”每周五收盘后判断当周是否满足。做周度回测统计持有区间收益、最大回撤和胜率。文章中的代码全部使用 Python 编写依赖只涉及 pandas、numpy 和 matplotlib。全程通过模拟数据演示这样没有行情数据接口也能先跑通流程之后替换成自己准备好的 CSV 文件即可。2. 环境准备与数据基础2.1 Python 环境与依赖建议使用 Python 3.9 及以上版本。核心依赖如下库用途安装命令pandas数据处理、滚动计算、周度聚合pip install pandasnumpy随机游走模拟、数值运算pip install numpymatplotlib绘制净值曲线和回撤图pip install matplotlibpyyaml后续扩展配置文件时使用pip install pyyaml学习环境中不需要安装数据库和消息队列。如果只想看信号和回测结果pandas 加 numpy 就够用。2.2 行情数据格式建议无论数据来自本地 CSV、量化接口还是行情软件导出建议统一整理成以下结构字段名类型说明datedatetime交易日openfloat开盘价highfloat最高价lowfloat最低价closefloat收盘价volumeint成交量这里有两个容易出错的地方。第一复权方式要统一。长期回测必须使用前复权或后复权数据否则分红除权会造成收盘价跳空移动平均线也会失真。建议在数据下载阶段就固定复权口径不要在计算指标中途切换。第二缺失日要处理。停牌日通常没有行情记录不要简单地用 0 填充。常见做法是保留原始交易日历只在参与计算时过滤缺失值。2.3 用模拟数据让流程先跑通真实行情接口在不同网络环境下可用性差异很大而本文重点是跑通流程所以先用随机游走生成一段模拟K线。这样做的优点是结果可复现、不受数据源限制、便于验证信号逻辑是否正确。import numpy as np import pandas as pd rng np.random.default_rng(42) n 300 # 从 100 开始生成对数随机游走 price 100 * np.exp(np.cumsum(rng.normal(0.0005, 0.02, n))) df pd.DataFrame({ date: pd.date_range(2025-01-02, periodsn, freqB), close: price, }) # 用前一日收盘价作为开盘价并构造最高最低 df[open] df[close].shift(1).fillna(price[0]) df[high] df[[open, close]].max(axis1) * 1.01 df[low] df[[open, close]].min(axis1) * 0.99 df[volume] rng.integers(100_000, 500_000, sizen) print(df.head()) print(df.tail())这段代码只是生成一份结构完整的模拟行情用来演示指标计算和回测流程。把这段数据替换成自己的 CSV 时读取方式也很简单df pd.read_csv(daily_data.csv, parse_dates[date])检查点数据应包含 300 行左右日期按交易日递增close 大致在 100 附近波动。如果发现 close 出现极端异常值先检查生成逻辑或原始数据源不要继续计算指标。3. 右侧确认信号的设计与实现3.1 把反弹右侧拆成三个可计算维度“反弹右侧”是一个复合概念不能只凭单一条件判断。本文把它拆成三个维度维度含义常用规则趋势恢复价格重新站上中期均线收盘价 MA20 且 MA60突破确认价格接近或创出近期新高收盘价 近 20 日最高价 x 0.98量能配合反弹有成交量支撑成交量 MA5 成交量 MA20 x 1.2这样拆分有三个好处趋势恢复过滤掉“跌不动但也没涨”的横盘行情。突破确认过滤掉均线下的弱反弹。量能配合过滤掉无量上涨的空转反弹。三个条件放在一起比单看一根 K 线或者一条均线更稳健。3.2 用 pandas 计算技术指标把上述规则翻译成 pandas 代码。这里使用滚动窗口计算均线和量能指标。# 均线 df[ma20] df[close].rolling(20).mean() df[ma60] df[close].rolling(60).mean() # 量能均线 df[vol_ma5] df[volume].rolling(5).mean() df[vol_ma20] df[volume].rolling(20).mean() # 近 20 日最高价 df[high_20] df[high].rolling(20).max() # 条件判断 df[above_ma20] df[close] df[ma20] df[above_ma60] df[close] df[ma60] df[volume_expand] df[vol_ma5] df[vol_ma20] * 1.2 df[near_high] df[close] df[high_20] * 0.98 print(df[[date, close, ma20, ma60, high_20]].tail())关键点说明rolling(20).mean()计算的是过去 20 个交易日收盘价均值前 19 行结果是 NaN。high_20是过去 20 个交易日的最高价用它来判断本次反弹是否突破了前期压力位。接近新高的阈值设为 0.98意味着不需要严格创新高允许小幅回踩。3.3 生成右侧确认状态四个布尔条件可以合并成一个最终信号。为保证方向一致建议全部使用“与”关系避免某一项条件过弱导致信号泛滥。df[signal] ( df[above_ma20] df[above_ma60] df[volume_expand] df[near_high] ).astype(int) # 只保留指标计算完成后的数据 model_df df.dropna(subset[ma60, vol_ma20]).reset_index(dropTrue) print(model_df[signal].value_counts())信号为 1 表示当日满足右侧反弹全部条件为 0 表示不满足。这里参数的物理含义是20 日均线代表短期趋势。60 日均线代表中期趋势。量能放大 1.2 倍表示市场参与度提升。距离 20 日高点 2% 以内表示突破在即。如果发现信号数量太少可以先看每个条件单独的值分布再决定是放宽量能倍数还是放宽接近新高阈值。不要一次性把四个条件全部放宽否则信号会失去过滤作用。4. 把周策略做成回测4.1 回测规则如何设计本文策略的持有频率是“周度”所以不能每天换仓。回测规则如下每周最后一个交易日收盘后检查当日 signal 是否为 1。如果 signal 为 1则下一周持有否则下一周空仓。按周收益计算策略净值对比同期全仓持有的基准净值。核心是避免未来函数本周五收盘后才能知道本周末的信号所以本周的仓位只能由上一周末信号决定。用收益数据表述就是第 t 周末得到信号。第 t1 周使用该信号作为仓位。第 t1 周的策略收益等于仓位乘第 t1 周的实际收益。这样的时序关系必须体现在代码中。4.2 回测代码实现先按自然周聚合取每周期最后一个交易日的收盘价和信号。model_df[week] model_df[date].dt.to_period(W-FRI) weekly model_df.groupby(week).agg( week_last_close(close, last), signal_last(signal, last), ).reset_index() weekly[week_return] weekly[week_last_close].pct_change().fillna(0) # 上一周末信号决定本周仓位 weekly[position] weekly[signal_last].shift(1).fillna(0) # 策略收益 weekly[strategy_return] weekly[position] * weekly[week_return] # 策略净值和基准净值 weekly[strategy_nav] (1 weekly[strategy_return]).cumprod() weekly[benchmark_nav] (1 weekly[week_return]).cumprod() print(weekly.tail(10))这段代码中position使用shift(1)是回测正确性的关键。如果不加就会用本周信息推导本周收益产生天然的前视偏差回测结果会虚高。不同交易市场对一周的定义不同。中国 A 股交易日历到周五美国市场也可以按周五周历。使用W-FRI表示以周五为一周结束日。如果数据源包含节假日周一至周五中可能只有四天有行情last会取到当周实际最后一个交易日这是期望行为。4.3 回测指标怎么看算完净值后还需要统计几个核心指标total_return weekly[strategy_nav].iloc[-1] - 1 benchmark_return weekly[benchmark_nav].iloc[-1] - 1 dd weekly[strategy_nav] / weekly[strategy_nav].cummax() - 1 max_drawdown dd.min() trade_weeks weekly[weekly[position] 1] win_rate (trade_weeks[strategy_return] 0).mean() trade_count int((weekly[position].diff().fillna(0) 1).sum()) print({ total_return: round(total_return, 4), benchmark_return: round(benchmark_return, 4), max_drawdown: round(max_drawdown, 4), win_rate: round(win_rate, 4), trade_count: trade_count, })预期输出示例指标数值解读策略累计收益0.2356模拟数据下策略跑赢基准基准累计收益-0.0452同期全仓持有收益最大回撤-0.1187策略净值从高点到低点最大跌幅胜率0.5625有仓位周中盈利周占比交易次数6信号从 0 变为 1 的次数需要强调这些数值来自随机模拟数据只用于验证流程是否跑通不代表真实行情收益。换一份数据后指标可能完全不同。从回测结果中还能观察到两类问题。如果 trade_count 过高说明信号频繁翻转持仓体验差如果 trade_count 过低说明信号太严格大部分时间空仓。调整参数时应同时观察交易次数和回撤而不只是盯着累计收益。5. 常见问题与排查路径5.1 信号总是不出现现象运行完代码后signal 列全部为 0或者 300 行数据中只有一两个 1。可能原因数据行数太少MA60 计算后只剩不到五十行有效数据。量能放大条件太严格成交量 MA5 一直小于 MA20 的 1.2 倍。接近新高条件太严格价格始终达不到近 20 日最高价的 98%。数据包含大量横盘或下跌段反弹信号确实较少。检查方式打印每个布尔条件单独的值分布。check model_df[[above_ma20, above_ma60, volume_expand, near_high]].mean() print(check)每个条件的平均值可以理解为“该条件在历史上有多少比例的时间成立”。如果above_ma60只有 0.1说明价格大部分时间在 60 日均线下方这更接近长期下跌结构。此时不应为了凑信号强行放宽阈值而是重新审视标的区间。5.2 回测结果异常漂亮或异常难看现象回测净值曲线持续上涨最大回撤几乎为 0或者信号出现后一周稳定亏损。原因分析顺序检查是否存在未来函数。重点看position是否用了当周 signal而不是上一周 signal。检查是否使用未复权数据。分红除权会造成价格跳空均线和收益率计算都会失真。检查是否忽略了滑点和手续费。周度策略交易频率低手续费影响相对小但不能完全忽略。检查样本期是否过短或过集中。牛市中几乎所有买入信号都有效熊市中大部分信号都会失效。回测必须在不同时间段上验证。最好把数据分成训练段和验证段避免用同一段数据反复调参。5.3 指标对不上行情软件现象自己用 pandas 算出来的 MA20 和行情软件显示的不一致。可能原因复权口径不同。行情软件默认前复权原始数据可能是不复权。窗口计算起止日期不同。有的软件把当天包含在 MA20 里有的用前 20 日收盘价再包含当天。停牌日处理不同。有的数据源删除停牌日有的保留为空。检查方式取最近 20 个交易日的收盘价手工计算平均并对比。排查思路先确认数据源头再确认复权方式最后确认滚动窗口是否包含当日。5.4 修改参数后结果剧烈变化现象把量能倍数从 1.2 改成 1.1或者把近高阈值从 0.98 改成 0.95回测收益和交易次数大幅变化。这通常说明策略对参数过度敏感也叫参数脆弱性。这样的策略进入真实数据后很容易失效。建议做法记录每次参数组合下的交易次数、胜率、最大回撤和累计收益。优先选择参数在一定区间内结果稳定的区域而不是收益最高的尖峰。不要反复在同一份数据上搜索最优参数。问题现象常见原因检查方式处理建议信号全为 0阈值过严或数据量不足打印单条件均值调整量能倍数或近高阈值回测收益过高未来函数或未复权检查 shift 与时序修复仓位对齐统一复权指标与软件不一致复权口径或窗口不同手工验证近 20 日 MA统一数据口径参数一改就失效过拟合或参数敏感扫描参数组合寻找稳定参数区间6. 从学习环境到生产环境的升级思路6.1 本地跑通后还缺什么本地代码跑通只代表策略逻辑在历史上可以复现。真正按周出一份策略报告还需要补齐很多工程环节。首先是定时调度。周度策略要求每周五收盘后自动运行一次可以通过 cron、Windows 计划任务或 Airflow 等调度工具触发。调度任务不能只执行一个脚本还要写日志、记录运行结果、处理失败重试。其次是数据可靠性。本地 CSV 可以手工更新生产环境建议将数据落库并通过数据源接口每日增量同步。每次计算前要校验数据完整性比如检查日期是否连续、最后一周期是否确实包含周五。最后是结果输出。信号结果和回测统计需要自动生成 Markdown、Excel 或 PDF 报告方便后续归档和复盘。只打印在控制台里的策略不满足周度报告的要求。6.2 生产级周度策略检查清单以下清单可以直接用于上线前检查数据校验日期是否连续最后一行是否为最新交易日复权口径是否一致。参数配置均线窗口、量能倍数、近高阈值是否外置到配置文件。时序校验仓位是否严格使用上周信号是否存在未来函数。交易成本回测是否计入手续费和滑点。异常处理数据为空、接口失败、指标出现 NaN 时脚本能否正常报警。日志记录每次运行是否记录信号、仓位、收益和错误信息。回滚机制参数改坏后能否快速回滚到上一版配置。权限安全数据文件和配置文件的读写权限是否受控。这些条目里数据校验和时序校验最容易被忽略却是影响结果可信度最关键的部分。6.3 后续扩展方向当前实现只是单标的、单信号、固定参数的周度框架实际项目可以沿几个方向扩展。第一把单信号改成信号分数。三个维度的布尔条件可以换成 0 到 3 的得分根据得分高低决定仓位比例而不是简单持有或空仓。第二增加多标的选择。对多个指数或 ETF 同时计算右侧信号每周输出“哪些标的满足条件、哪些标的接近条件”的对比表形成真正的周度观察清单。第三引入更多风险指标。除最大回撤外可以计算夏普比率、卡玛比率、波动率以及每次交易的持有周数分布更全面地评估策略。第四把研究报告自动化。指标计算、回测、图表、文字结论都做成模板每周定时生成一篇周度策略文档。对新手而言最有价值的练习不是继续优化参数而是把当前代码重写成类或函数把数据加载、指标计算、信号生成、回测统计四个环节解耦。这样后续换数据、换参数、换频率都会容易很多。周度右侧策略研究本质上不是找到一个永远有效的信号而是建立一套可以持续验证、持续改进、能说得清规则的研究流程。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →