2026年重磅升级已全面落地!欢迎加入专注财经数据与量化投研的【数据科学实战】知识星球!您将获取持续更新的《财经数据宝典》与《量化投研宝典》,双典协同提供系统化指引;星球内含300篇以上独有高质量文章,深度覆盖策略开发、因子分析、风险管理等核心领域,内容基本每日更新;同步推出的「量化因子专题教程」系列(含完整可运行代码与实战案例),系统详解因子构建、回测与优化全流程,并实现日更迭代。我们持续扩充独家内容资源,全方位赋能您的投研效率与专业成长。无论您是量化新手还是资深研究者,这里都是助您少走弯路、事半功倍的理想伙伴,携手共探数据驱动的投资未来!
引言 你有没有想过,能不能写一段 Python 代码,让它自动帮你盯盘、判断趋势、发出买卖信号,甚至回测 25 年的历史数据来验证效果?
今天要分享的,就是一个完整的量化交易策略项目—— CRL-SuperTrend Pullback Reversal Strategy 。这个策略专门针对美股 Charles River Laboratories(CRL)这只股票,利用 SuperTrend 指标和 OSMA 动量指标,构建了一套 纯规则驱动 的交易系统。没有主观判断,没有情绪干扰,一切由代码说了算。
对于正在学习 Python 的你来说,这个项目是一个绝佳的练手案例:它涉及数据获取、技术指标计算、信号生成、回测模拟等多个实战环节。
免责声明: 本文仅供学习交流,不构成任何投资建议。回测结果基于历史数据,不代表未来收益。
一、策略核心思路 这个策略的设计哲学可以用一句话概括: 在下跌中入场,在动量恢复时离场 。
听起来有点反直觉,对吧?大多数人习惯追涨杀跌,而这个策略偏偏要做"逆势交易者"——在市场处于下行趋势时买入,等到动量指标确认回暖时卖出。
它依赖两个技术指标:
SuperTrend(超级趋势指标) :负责判断当前市场处于上升趋势还是下降趋势。当 SuperTrend 判定为下降趋势时,策略发出入场信号。
OSMA(MACD 振荡器) :即 MACD 线与信号线的差值。当 OSMA 从负值穿越零线变为正值时,说明动量由弱转强,策略发出离场信号。
二、动手实现:从数据到信号 2.1 获取历史数据 第一步是用 yfinance 库下载 CRL 的日线数据,时间跨度从 2000 年到 2025 年底,覆盖了互联网泡沫、金融危机、新冠疫情等多个市场周期。
import pandas as pd import numpy as np import yfinance as yf import vectorbt as vbt # ------------------------- # 下载数据 # ------------------------- symbol = "CRL" # 股票代码 start_date = "2000-01-01" # 起始日期 end_date = "2026-01-01" # 截止日期 interval = "1d" # 日线级别 # 通过 yfinance 下载历史行情数据 df = yf.download(symbol, start=start_date, end=end_date, interval=interval) # 保存为 CSV 文件,方便后续复用 df.to_csv( "CRL_clean.csv" , index= False ) 这段代码会生成一个包含开盘价(Open)、最高价(High)、最低价(Low)、收盘价(Close)和成交量(Volume)的 DataFrame。后续所有的分析都基于这份数据,不依赖任何新闻或主观预测。
2.2 计算 SuperTrend 指标 SuperTrend 是策略的"趋势裁判",它通过 ATR(真实波幅均值)动态计算上下轨,然后判定当前处于上升还是下降趋势。
# SuperTrend 参数 SUPERTREND_MULTIPLIER = 3 # ATR 乘数 SUPERTREND_PERIOD = 14 # ATR 计算周期 def calculate_supertrend ( df, period=SUPERTREND_PERIOD, multiplier=SUPERTREND_MULTIPLIER ): """计算 SuperTrend 指标,包括上轨、下轨和趋势方向""" hl2 = (df[ 'High' ] + df[ 'Low' ]) / 2 # 最高价与最低价的中间值 # 计算真实波幅(True Range) tr1 = df[ 'High' ] - df[ 'Low' ] tr2 = abs (df[ 'High' ] - df[ 'Close' ].shift()) tr3 = abs (df[ 'Low' ] - df[ 'Close' ].shift()) tr = pd.concat([tr1, tr2, tr3], axis= 1 ). max (axis= 1 ) # 计算平均真实波幅(ATR) atr = tr.rolling(period).mean() # 计算基础上下轨 upper_basic = hl2 + multiplier * atr lower_basic = hl2 - multiplier * atr # 初始化最终上下轨 upper_band = upper_basic.copy() lower_band = lower_basic.copy() # 趋势列表:True 表示上升趋势,False 表示下降趋势 supertrend = [ True ] for i in range ( 1 , len (df)): # 上轨调整:如果前一天收盘价低于上轨,则取较小值 if df[ 'Close' ].iloc[i- 1 ] <= upper_band.iloc[i- 1 ]: upper_band.iloc[i] = min (upper_basic.iloc[i], upper_band.iloc[i- 1 ]) # 下轨调整:如果前一天收盘价高于下轨,则取较大值 if df[ 'Close' ].iloc[i- 1 ] >= lower_band.iloc[i- 1 ]: lower_band.iloc[i] = max (lower_basic.iloc[i], lower_band.iloc[i- 1 ]) # 判断趋势方向 if df[ 'Close' ].iloc[i] > upper_band.iloc[i- 1 ]: supertrend.append( True ) # 突破上轨,转为上升趋势 elif df[ 'Close' ].iloc[i] < lower_band.iloc[i- 1 ]: supertrend.append( False ) # 跌破下轨,转为下降趋势 else : supertrend.append(supertrend[- 1 ]) # 维持前一天的趋势 df[ 'SuperTrend' ] = supertrend df[ 'Upper_Band' ] = upper_band df[ 'Lower_Band' ] = lower_band return df def st_downtrend ( df ): """返回 SuperTrend 下降趋势的布尔序列(True = 下降趋势)""" df = calculate_supertrend(df) return ~df[ 'SuperTrend' ] 关键点: 这个策略的入场条件是 SuperTrend 判定为 下降趋势 ,这是一种逆向思维——在别人恐惧时贪婪。
2.3 计算 OSMA 动量指标 OSMA 是 MACD 与其信号线之间的差值,用来衡量动量的加速或减速。
# OSMA 参数 OSMA_FAST = 12 # 快速 EMA 周期 OSMA_SIGNAL = 9 # 信号线 EMA 周期 OSMA_SLOW = 26 # 慢速 EMA 周期 def calculate_macd_osma ( df, fast=OSMA_FAST, slow=OSMA_SLOW, signal_period=OSMA_SIGNAL ): """ 计算 MACD、信号线和 OSMA(MACD - 信号线) """ df = df.copy() # 计算快速和慢速指数移动平均线 df[ 'EMA_fast' ] = df[ 'Close' ].ewm(span=fast, adjust= False ).mean() df[ 'EMA_slow' ] = df[ 'Close' ].ewm(span=slow, adjust= False ).mean() # MACD = 快速 EMA - 慢速 EMA df[ 'MACD' ] = df[ 'EMA_fast' ] - df[ 'EMA_slow' ] # 信号线 = MACD 的 EMA df[ 'Signal' ] = df[ 'MACD' ].ewm(span=signal_period, adjust= False ).mean() # OSMA = MACD - 信号线 df[ 'OSMA' ] = df[ 'MACD' ] - df[ 'Signal' ] return df def osma_cross_above_0 ( df ): """判断 OSMA 是否从负值上穿零线(动量由弱转强)""" df = calculate_macd_osma(df) return (df[ 'OSMA' ].shift( 1 ) <= 0 ) & (df[ 'OSMA' ] > 0 ) 当 OSMA 从负值穿越到正值,意味着动量恢复,策略选择在此时获利了结。
三、生成交易信号并回测 3.1 入场与离场条件 信号逻辑非常简洁:
# ------------------------- # 入场条件:SuperTrend 处于下降趋势 # ------------------------- df[ "Supertrend_DownTrend" ] = st_downtrend(df) entry_conditions = [ 'Supertrend_DownTrend' , ] df[ 'entry_signal' ] = df[entry_conditions]. all (axis= 1 ) # ------------------------- # 离场条件:OSMA 上穿零线 # ------------------------- df[ "OSMA_Cross_Above_0" ] = osma_cross_above_0(df) exit_conditions = [ 'OSMA_Cross_Above_0' , ] df[ 'exit_signal' ] = df[exit_conditions]. all (axis= 1 ) 3.2 回测执行 为了避免"未来函数"(lookahead bias),所有信号都向后移动一根 K 线再执行。同时加入了手续费和滑点,模拟真实交易环境。
# ------------------------- # 回测设置 # ------------------------- # 信号延迟 1 天执行,避免使用当天信号当天交易 shift_entries = df[ 'entry_signal' ].shift( 1 ).astype( bool ).fillna( False ).to_numpy()
shift_exits = df[ 'exit_signal' ].shift( 1 ).astype( bool ).fillna( False ).to_numpy() # 使用 vectorbt 进行向量化回测 pf = vbt.Portfolio.from_signals( close=df[ 'Open' ], # 以开盘价成交 entries=shift_entries, # 入场信号 exits=shift_exits, # 离场信号 init_cash= 100_000 , # 初始资金 10 万美元 fees= 0.001 , # 手续费 0.1% slippage= 0.002 , # 滑点 0.2% freq= '1d' # 日线频率 ) # 打印策略统计信息 print (pf.stats()) # 绘制权益曲线 pf.plot().show() 3.3 对比买入持有策略 为了评估策略是否真的有效,还需要和最简单的"买入持有"进行对比:
# ------------------------- # 买入持有基准回测 # ------------------------- df_holding = df[ 'Open' ] pf_holding = vbt.Portfolio.from_holding(df_holding, init_cash= 100_000 , freq= '1d' ) print (pf_holding.stats()) 四、回测结果一览 根据原文给出的回测数据(2000 年 6 月 23 日至 2025 年 12 月 31 日):
这个策略在总收益上跑赢了买入持有,胜率高达 82%,但最大回撤也达到了 74%,说明在某些极端行情下策略仍然会经历较大的浮亏。
五、从这个项目中你能学到什么 对 Python 学习者而言 ,这个项目涵盖了以下实用技能:
数据处理 :使用 Pandas 进行时间序列数据的清洗、计算和转换。移动平均、指数平滑、布尔索引等操作贯穿始终。
技术指标实现 :从零手写 SuperTrend 和 MACD/OSMA 的计算逻辑,比直接调库更能帮你理解指标的本质。
回测框架使用 : vectorbt 是一个高性能的向量化回测库,适合快速验证策略想法。
工程化思维 :信号延迟执行、手续费和滑点模拟、与基准策略对比——这些细节体现了从"玩具代码"到"可信回测"的进阶。
六、策略的局限性 原文作者也坦诚指出,仅仅在历史数据上跑通 26 年的回测,并不足以证明策略在真实市场中能存活。还需要进行更深入的稳健性测试,比如:
Walk-Forward 分析 :将数据分成多个训练/测试窗口,验证策略在"未见数据"上的表现。
多标的测试 :目前策略只针对 CRL 一只股票,是否能迁移到其他标的需要验证。
风控增强 :74% 的最大回撤在实盘中很难承受,加入止损、仓位管理等风控层是必要的改进方向。
总结 这个 CRL-SuperTrend 回调反转策略用极简的规则(1 个入场条件 + 1 个离场条件)实现了一个完整的量化交易系统。它的核心理念是"逆势入场、顺势离场"——在 SuperTrend 判定下跌时买入,在 OSMA 动量恢复时卖出。
对于 Python 学习者来说,这个项目的价值不仅在于量化交易本身,更在于它展示了如何用 Python 构建一个完整的"数据驱动决策系统":获取数据、计算特征、生成信号、模拟执行、评估结果。这套思路可以迁移到很多领域。
当然,请务必记住:回测不等于实盘,历史不代表未来。如果你对量化交易感兴趣,一定要在充分学习和测试之后再做决策。
参考文章 加入专注于财经数据与量化投研的知识星球【数据科学实战】,获取本文完整研究解析、代码实现细节。
财经数据与量化投研知识社区 2026年全面升级已落地!【数据科学实战】知识星球核心权益如下:
1. 双典系统赋能:获赠《财经数据宝典》与《量化投研宝典》完整文档,凝练多年实战经验,构建系统化知识框架; 2. 量化因子日更教程(2026重磅新增):每日更新「量化因子专题教程」,配套完整可运行代码与实战案例,深度拆解因子构建、回测与优化全流程; 3. 量化文章专题教程库:300+篇星球独有高质量教程式文章,系统覆盖策略开发、因子研究、风险管理等核心领域,内容基本每日更新,并配套精选学习资料与实战参考; 4. PyBroker实战课程:赠送《PyBroker-入门及实战》视频课程,手把手教学,快速掌握量化策略开发技能; 5. 财经数据支持:定期更新国内外财经数据,为策略研发提供精准、可靠的数据基础; 6. 顶尖学者与行业专家分享:年度邀请学术界博士与业界资深专家开展前沿论文精讲与实战案例分享,不少于4场,直击研究前沿与产业实践; 专家直连答疑:与核心开发者及领域专家实时互动,高效解决投研实战难题; 7. 专业社群与专属福利:加入高质量交流社群,获取课程折扣及更多独家资源。 星球已沉淀丰富内容生态——涵盖量化文章专题教程库、因子日更系列、高频数据集、PyBroker实战课程、专家深度分享与实时答疑服务。无论您是初探量化的学习者,还是深耕领域的从业者,这里都是助您少走弯路、高效成长的理想平台。诚邀加入,共探数据驱动的投资未来!