Py学习  »  Python

用 Python 把一只冷门股票的收益翻倍:ZBRA 量化策略全解析

数据科学实战 • 1 周前 • 57 次点击  


 

 用 Python 揭秘均值回归策略:你的收益从何而来?



2026年重磅升级已全面落地!欢迎加入专注财经数据与量化投研的【数据科学实战】知识星球!您将获取持续更新的《财经数据宝典》与《量化投研宝典》,双典协同提供系统化指引;星球内含 500 篇以上独有高质量文章,深度覆盖策略开发、因子分析、风险管理等核心领域,内容基本每日更新;同步推出的「量化因子专题教程」系列(含完整可运行代码与实战案例),系统详解因子构建、回测与优化全流程,并实现日更迭代。我们持续扩充独家内容资源,全方位赋能您的投研效率与专业成长。无论您是量化新手还是资深研究者,这里都是助您少走弯路、事半功倍的理想伙伴,携手共探数据驱动的投资未来!

引言

当大多数散户盯着热门科技股、meme 股追涨杀跌时,有一只叫 Zebra Technologies(代码 ZBRA)的中等市值公司,正在做着枯燥的条码扫描器和仓储自动化生意。谁能想到,就是这样一只"无聊"的股票,藏着一个 30 年的量化交易机会?

最近读到 Kryptera 发布的一篇策略文章,作者用 Python 构建了一套基于 TEMA + Regime RSI 的量化策略,在 1995 至 2026 年的 32 个滚动窗口中,实现了 5300% 的总收益,而同期买入持有只有 2658%。更难得的是,整套代码用的都是 pandas、numpy、yfinance、vectorbt 这些开源库,任何学 Python 的朋友都能复现。

今天这篇文章,我就带大家拆解这套策略的核心思路和 Python 实现,顺便讲讲量化回测里最容易翻车的几个坑。


一、策略的两大核心:进场看 TEMA,出场看 Regime RSI

这套策略只做两件事:

  • • 进场:当 TEMA(三重指数移动平均线)下跌时做多
  • • 出场:当 RSI 持续处于强势区间时平仓

看起来简单,但背后的逻辑值得琢磨。

1. 什么是 TEMA ?

TEMA(Triple Exponential Moving Average)是在普通 EMA 基础上做了三层平滑,用来消除滞后。公式如下:

相比传统 EMA,TEMA 对价格反应更快,噪声更少。

Python 实现非常简洁:

import pandas as pd
import
 numpy as np

def
 calculate_tema(prices, period=20):
    """
    计算三重指数移动平均线 TEMA
    :param prices: 价格序列(pandas Series)
    :param period: 平滑周期,默认 20
    :return: TEMA 序列
    """

    # 第一层 EMA 平滑

    ema1 = prices.ewm(span=period, adjust=False).mean()
    # 第二层 EMA:对 ema1 再做一次平滑

    ema2 = ema1.ewm(span=period, adjust=False).mean()
    # 第三层 EMA:对 ema2 再做一次平滑

    ema3 = ema2.ewm(span=period, adjust=False).mean()
    # TEMA 公式:减少滞后,放大趋势信号

    tema = 3 * ema1 - 3 * ema2 + ema3
    return
 tema

# 使用示例

# close 是收盘价序列

# tema = calculate_tema(close, period=20)

# 进场信号:当前 TEMA 小于 N 根 K 线之前的 TEMA

# entry_signal = tema < tema.shift(5)

2. Regime RSI:把 RSI 当成"趋势分类器"

传统 RSI 用法是超买超卖(大于 70 卖出,小于 30 买入),但这种用法在实战中经常失效。

这套策略的创新在于:把 RSI 当作市场状态分类器,而且要求状态必须持续足够长时间才触发出场。

  • • 看多状态:RSI 在 50 到 75 之间,且前 3 根 K 线 RSI 都大于 46
  • • 看空状态:RSI 在 25 到 50 之间,且前 3 根 K 线 RSI 都小于 54
  • • 只有当看多状态连续持续 N 根 K 线后,才触发出场

Python 实现如下:

def calculate_rsi(prices, period=120):
    """
    计算 RSI 指标
    :param prices: 价格序列
    :param period: RSI 回看周期,这里用 120,属于长周期
    :return: RSI 序列
    """

    # 计算价格变动

    delta = prices.diff()
    # 上涨和下跌分别处理

    gain = delta.where(delta > 0, 0)
    loss = -delta.where(delta < 0, 0)
    # 指数加权平均

    avg_gain = gain.ewm(alpha=1/period, adjust=False).mean()
    avg_loss = loss.ewm(alpha=1/period, adjust=False).mean()
    # 计算相对强度与 RSI

    rs = avg_gain / avg_loss
    rsi = 100 - (100 / (1 + rs))
    return
 rsi

def
 regime_rsi_exit_signal(rsi, persistence=8):
    """
    基于持续性的 Regime RSI 出场信号
    :param rsi: RSI 序列
    :param persistence: 看多状态需要连续持续的 K 线数
    :return: 布尔序列,True 表示触发出场
    """

    # 判断当前是否处于看多区间

    bullish_now = (rsi >= 50) & (rsi <= 75)
    # 前 3 根 K 线全部高于 46(确保不是短暂反弹)

    prev_support = (rsi.shift(1) > 46) & (rsi.shift(2) > 46) & (rsi.shift(3) > 46)
    bullish_zone = bullish_now & prev_support
    # 要求看多状态连续持续 persistence 根 K 线

    exit_signal = bullish_zone.rolling(persistence).sum() >= persistence
    return
 exit_signal

这里的核心思想是:进场抓短期弱势(低点),出场等确认强势(顶部前),吃掉中间那段肉最多的行情。


二、为什么要用 Walk-Forward Optimization ?

这是整篇文章最值得学习的部分。

过拟合的陷阱

很多新手写回测都会犯一个错:在全部历史数据上找"最好的参数",然后得到一份漂亮的业绩报告。结果一上实盘就亏钱,因为参数是对着过去拟合出来的,对未来没有泛化能力。

WFO 的做法

Walk-Forward Optimization(滚动窗口优化)的思路是:

  1. 1. 用前 4 年数据训练,找出最优参数
  2. 2. 把这组参数立刻应用到下一年(从未见过的数据)
  3. 3. 窗口向前滚动一年,重复第 1 步
  4. 4. 把所有"下一年"的样本外结果拼接起来

本策略一共跑了 32 个滚动窗口,最终那 5300% 的收益,全部来自样本外测试,没有一根训练数据的 K 线混入其中。

Python 伪代码:

from itertools import product

def
 walk_forward_optimization(data, train_years=4, test_years=1):
    """
    滚动窗口优化回测
    :param data: 全部历史数据(含日期索引)
    :param train_years: 训练窗口年数
    :param test_years: 测试窗口年数
    :return: 所有样本外测试结果的合集
    """

    # 参数网格:RSI 回看、TEMA 周期、持续性阈值等

    param_grid = {
        'rsi_lookback'
: [80, 100, 120],
        'rsi_persistence'
: [5, 8, 10],
        'tema_period'
: [16, 20, 24],
        'tema_shift'
: [3, 5, 7],
    }
    
    all_oos_results = []  # 存储所有样本外结果
    years = data.index.year.unique()
    
    # 滚动窗口

    for
 i in range(len(years) - train_years - test_years + 1):
        # 切分训练集和测试集

        train_start = years[i]
        train_end = years[i + train_years - 1]
        test_year = years[i + train_years]
        
        train_data = data[str(train_start):str(train_end)]
        test_data = data[str(test_year): str(test_year)]
        
        # 网格搜索最优参数

        best_params = None
        best_score = -np.inf
        for
 params in product(*param_grid.values()):
            param_dict = dict(zip(param_grid.keys(), params))
            score = backtest(train_data, param_dict)  # 在训练集评估
            if
 score > best_score:
                best_score = score
                best_params = param_dict
        
        # 用最优参数跑样本外测试

        oos_result = backtest(test_data, best_params)
        all_oos_results.append(oos_result)
    
    # 拼接所有样本外结果作为最终业绩

    return
 combine_results(all_oos_results)

记住一句话:样本外表现才是真实表现,样本内漂亮的曲线都是幻觉。


三、业绩数据到底有多强?

作者给出了详细的业绩指标(1995 年 1 月至 2026 年 4 月):

指标
策略
买入持有
总收益率
5300.55%
2657.92%
夏普比率
0.63
0.56
Calmar 比率
0.28
0.22
Sortino 比率
0.94
0.82
最大回撤
73.33%
74.19%
胜率
70.67%
盈亏比(Profit Factor)
1.92

注意:30 年只交易了 76 次,平均一年 2.5 笔。这不是高频策略,而是一个耐心游戏


四、蒙特卡洛模拟:验证策略的稳健性

只跑一次回测不够,作者又做了 1000 次蒙特卡洛模拟(块自助法,block size=5),结果显示:

  • • 中位数收益率:4705%
  • • 最差 5% 的模拟:收益 71.69%
  • • 中位数最大回撤:75.86%
def monte_carlo_simulation(returns, n_simulations=1000, block_size=5):


    

    """
    块自助法蒙特卡洛模拟
    :param returns: 原始日收益率序列
    :param n_simulations: 模拟次数
    :param block_size: 块大小,保留短期自相关结构
    :return: 所有模拟路径的最终收益
    """

    results = []
    n_blocks = len(returns) // block_size
    
    for
 _ in range(n_simulations):
        # 随机抽取块并拼接,保留短期相关性

        sampled_blocks = []
        for
 _ in range(n_blocks):
            start = np.random.randint(0, len(returns) - block_size)
            sampled_blocks.append(returns[start:start + block_size])
        # 拼接成一条新的收益路径

        simulated = np.concatenate(sampled_blocks)
        # 累计收益

        total_return = (1 + simulated).prod() - 1
        results.append(total_return)
    
    return
 np.array(results)

# 使用示例

# mc_results = monte_carlo_simulation(daily_returns, n_simulations=1000)

# print(f"中位数收益: {np.median(mc_results):.2%}")

# print(f"5% 分位数: {np.percentile(mc_results, 5):.2%}")

这说明策略的风险调整后收益不是靠运气,而是结构性存在


五、策略的局限性(作者坦诚说明)

  1. 1. 不是低回撤策略:73% 的最大回撤是真实的,心理承受力不够的人会被洗出去
  2. 2. 不是高频策略:一年只 2.5 笔,必须耐得住寂寞
  3. 3. 不是未来保证:WFO 降低了过拟合风险,但无法预测黑天鹅和结构性变化

总结

这篇策略文章给 Python 量化学习者的启发有三点:

  1. 1. 指标不是孤立的,组合使用才有价值。TEMA 看短期动量,Regime RSI 看长期趋势状态,一进一出,逻辑互补。
  2. 2. 回测方法比回测结果更重要。如果你只会在全量数据上找最优参数,那得到的所有数字都不可信。Walk-Forward + Monte Carlo 才是工业级的做法。
  3. 3. 好的策略往往藏在冷门标的里。机构主导、散户少的中小盘票,趋势更干净,更适合系统化交易。

如果你正在学 Python 量化,建议先从 pandas、vectorbt 入手,把 TEMA 和 RSI 这两个指标亲手实现一遍,再尝试搭建自己的 WFO 流程。技术不是复杂的,思路才是。


参考文章

加入专注于财经数据与量化投研的知识星球【数据科学实战】,获取本文完整研究解析、代码实现细节。

财经数据与量化投研知识社区

2026年全面升级已落地!【数据科学实战】知识星球核心权益如下:

  1. 1. 双典系统赋能:获赠《财经数据宝典》与《量化投研宝典》完整文档,凝练多年实战经验,构建系统化知识框架;
  2. 2. 量化因子日更教程(2026重磅新增):每日更新「量化因子专题教程」,配套完整可运行代码与实战案例,深度拆解因子构建、回测与优化全流程;
  3. 3. 量化文章专题教程库:300+篇星球独有高质量教程式文章,系统覆盖策略开发、因子研究、风险管理等核心领域,内容基本每日更新,并配套精选学习资料与实战参考;
  4. 4. 量化投研实战课程:赠送《AKQuant-入门及实战》《PyBroker-入门及实战》视频课程,手把手教学,快速掌握量化策略开发技能;
  5. 5. 财经数据支持:定期更新国内外财经数据,为策略研发提供精准、可靠的数据基础;
  6. 6. 顶尖学者与行业专家分享:年度邀请学术界博士与业界资深专家开展前沿论文精讲与实战案例分享,不少于4场,直击研究前沿与产业实践;
    专家直连答疑:与核心开发者及领域专家实时互动,高效解决投研实战难题;
  7. 7. 专业社群与专属福利:加入高质量交流社群,获取课程折扣及更多独家资源。

星球已沉淀丰富内容生态——涵盖量化文章专题教程库、因子日更系列、高频数据集、PyBroker实战课程、专家深度分享与实时答疑服务。无论您是初探量化的学习者,还是深耕领域的从业者,这里都是助您少走弯路、高效成长的理想平台。诚邀加入,共探数据驱动的投资未来!

 

好文推荐

1. 用 Python 打造股票预测系统:Transformer 模型教程(一)

2. 用 Python 打造股票预测系统:Transformer 模型教程(二)

3. 用 Python 打造股票预测系统:Transformer 模型教程(三)

4. 用 Python 打造股票预测系统:Transformer 模型教程(完结)

5. 揭秘隐马尔可夫模型:因子投资的制胜武器

6. YOLO 也能预测股市涨跌?计算机视觉在股票市场预测中的应用

7. 金融 AI 助手:FinGPT 让你轻松掌握市场分析

8. 量化交易秘籍:为什么专业交易员都在用对数收益率?

9. Python 量化投资利器:Ridge、Lasso 和 Elastic Net 回归详解

10. 掌握金融波动率模型:完整 Python 实现指南

好书推荐


Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/196197