社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

用 Python 实现量化交易策略:时间序列动量完全指南

数据科学实战 • 8 月前 • 511 次点击  

欢迎加入专注于财经数据与量化投研的【数据科学实战】知识星球!在这里,您将获取持续更新的《财经数据宝典》和《量化投研宝典》,这两部宝典相辅相成,为您在量化投研道路上提供明确指引。 我们提供了精选的国内外量化投研的 220+ 篇高质量文章,并每日更新最新研究成果,涵盖策略开发、因子分析、风险管理等核心领域。 无论您是量化投资新手还是经验丰富的研究者,星球社区都能帮您少走弯路,事半功倍,共同探索数据驱动的投资世界!

引言

你是否想过,为什么有些交易策略能够持续跑赢市场?时间序列动量(Time Series Momentum,简称 TSMOM)就是量化金融领域最稳健的市场异象之一。本文将带你深入理解 TSMOM 策略的核心原理,并通过 Python 代码实现一个完整的回测框架。

无论你是量化交易的初学者,还是希望提升策略开发能力的 Python 开发者,这篇文章都会给你带来实质性的收获。

什么是时间序列动量策略

TSMOM 策略的核心思想非常简洁:

  • 当资产呈现正向动量时,做多
  • 当资产呈现负向动量时,做空
  • 通过波动率目标化来维持稳定的风险敞口

与传统的横截面动量策略不同,TSMOM 只关注资产自身的历史表现,而不与其他资产进行比较。

策略架构设计

一个专业的 TSMOM 策略需要遵循严格的时间顺序,以避免前视偏差(Look-Ahead Bias):

  1. 在第 t 天收盘时,使用截至 t-1 天的数据生成信号
  2. 在第 t+1 天开盘时执行交易
  3. 在第 t+1 天开盘时对组合进行估值
  4. 收益率计算公式为:R_t = Open[t+2] / Open[t+1] - 1

三种动量指标实现

1. 价格动量

最简单直接的动量计算方式:

class MomentumIndicators:
    """动量指标计算类"""
    
    @staticmethod
    def calculate_price_momentum(close_prices: pd.Series, window: int) -> pd.Series:
        """
        计算价格动量信号
        
        参数:
            close_prices: 收盘价序列
            window: 回看窗口期(天数)
        
        返回:
            动量信号序列(+1 做多,-1 做空,0 观望)
        """

        # 计算窗口期内的收益率,shift(1) 避免前视偏差
        momentum_return = close_prices.pct_change(window).shift(1)
        
        # 生成信号:正收益做多,负收益做空
        signal = np.sign(momentum_return)
        return signal

2. 均线交叉策略

通过快慢均线的交叉来判断趋势方向:

@staticmethod
def calculate_sma_crossover(close_prices: pd.Series,
                            fast_window: int,
                            slow_window: int)
 -> pd.Series:

    """
    计算均线交叉信号
    
    信号逻辑:
        - 快线上穿慢线(金叉):做多 (+1)
        - 快线下穿慢线(死叉):做空 (-1)
    
    参数:
        close_prices: 收盘价序列
        fast_window: 快速均线窗口
        slow_window: 慢速均线窗口
    
    返回:
        交易信号序列
    """

    # 计算快慢均线
    fast_sma = close_prices.rolling(window=fast_window).mean().shift(1)
    slow_sma = close_prices.rolling(window=slow_window).mean().shift(1)
    
    # 生成交叉信号
    signal = pd.Series(0, index=close_prices.index)
    signal[fast_sma > slow_sma] = 1   # 金叉做多
    signal[fast_sma < slow_sma] = -1  # 死叉做空
    
    return signal

3. 均线斜率策略

根据均线的变化率来判断趋势强度:

@staticmethod
def calculate_sma_slope(close_prices: pd.Series,
                        sma_window: int,
                        slope_window: int = 5)
 -> pd.Series:

    """
    计算基于均线斜率的交易信号
    
    信号逻辑:
        - 均线向上倾斜:做多 (+1)
        - 均线向下倾斜:做空 (-1)
    
    参数:
        close_prices: 收盘价序列
        sma_window: 均线计算窗口
        slope_window: 斜率计算窗口
    
    返回:
        交易信号序列
    """

    # 计算均线
    sma = close_prices.rolling(window=sma_window).mean().shift(1)
    
    # 计算斜率(变化率)
    sma_slope = sma.diff(slope_window) / slope_window
    
    # 根据斜率方向生成信号
    signal = np.sign(sma_slope)
    return signal

四种高级波动率估计器

波动率估计是 TSMOM 策略的核心组件之一。本文实现了四种不同的估计方法。

1. 滚动波动率(Rolling Volatility)

最经典的波动率计算方式:

class VolatilityEstimators:
    """高级波动率估计器类"""
    
    @staticmethod
    def calculate_rolling_volatility(close_prices: pd.Series, window: int) -> pd.Series:
        """
        经典滚动波动率计算
        
        使用对数收益率的标准差,并年化处理
        
        参数:
            close_prices: 收盘价序列
            window: 滚动窗口期
        
        返回:
            年化波动率序列
        """

        # 计算对数收益率
        log_returns = np.log(close_prices / close_prices.shift(1))
        
        # 计算滚动标准差,shift(1) 避免前视偏差
        rolling_std = log_returns.rolling(window=window).std().shift(1)
        
        # 年化波动率(假设一年 252 个交易日)
        annualized_vol = rolling_std * np.sqrt(252)
        return annualized_vol

2. Parkinson 波动率

利用日内最高最低价来估计波动率,效率是收盘价方法的 5.2 倍:

@staticmethod
def calculate_parkinson_volatility(high_prices: pd.Series,
                                   low_prices: pd.Series,
                                   window: int)
 -> pd.Series:

    """
    Parkinson 波动率估计器(1980)
    
    利用日内高低价范围估计波动率,比收盘价方法效率高 5 倍
    
    参数:
        high_prices: 最高价序列
        low_prices: 最低价序列
        window: 滚动窗口期
    
    返回:
        年化 Parkinson 波动率序列
    """

    # 计算 log(最高价/最低价) 的比值
    hl_ratio = np.log(high_prices / low_prices)
    
    # Parkinson 方差公式:(1/(4*ln(2))) * mean(ln(H/L)^2)
    parkinson_var = (1.0 / (4.0 * np.log(2))) * (hl_ratio ** 2)
    
    # 滚动均值方差
    rolling_var = parkinson_var.rolling(window=window).mean().shift(1)
    
    # 转换为波动率并年化
    parkinson_vol = np.sqrt(rolling_var) * np.sqrt(252)
    return parkinson_vol

3. Garman-Klass 波动率

综合使用 OHLC 四个价格,效率达到收盘价方法的 7.4 倍:

@staticmethod
def calculate_garman_klass_volatility(open_prices: pd.Series,
                                      high_prices: pd.Series,
                                      low_prices: pd.Series,
                                      close_prices: pd.Series,
                                      window: int)
 -> pd.Series:

    """
    Garman-Klass 波动率估计器(1980)
    
    使用 OHLC 四价数据,效率是收盘价方法的 7.4 倍
    
    参数:
        open_prices: 开盘价序列
        high_prices: 最高价序列
        low_prices: 最低价序列
        close_prices: 收盘价序列
        window: 滚动窗口期
    
    返回:
        年化 Garman-Klass 波动率序列
    """

    # 计算对数比值
    hl_ratio = np.log(high_prices / low_prices)
    co_ratio = np.log(close_prices / open_prices)
    
    # Garman-Klass 方差公式
    gk_var = 0.5 * (hl_ratio ** 2) - (2 * np.log(2) - 1) * (co_ratio ** 2)
    
    # 滚动均值方差
    rolling_var = gk_var.rolling(window=window).mean().shift(1)
    
    # 转换为波动率并年化
    gk_vol = np.sqrt(rolling_var) * np.sqrt(252)
    return gk_vol

4. Yang-Zhang 波动率

最先进的估计器,效率达到收盘价方法的 14 倍,能够捕捉隔夜跳空:

@staticmethod
def calculate_yang_zhang_volatility(open_prices: pd.Series,
                                    high_prices: pd.Series,
                                    low_prices: pd.Series,
                                    close_prices: pd.Series,
                                    window: int)
 -> pd.Series:

    """
    Yang-Zhang 波动率估计器(2000)
    
    最先进的 OHLC 波动率估计器,综合了:
    - 隔夜波动率(收盘到开盘)
    - 开盘跳空波动率
    - 日内漂移无关波动率(Rogers-Satchell)
    
    效率是收盘价方法的 14 倍
    
    参数:
        open_prices: 开盘价序列
        high_prices: 最高价序列
        low_prices: 最低价序列
        close_prices: 收盘价序列
        window: 滚动窗口期
    
    返回:
        年化 Yang-Zhang 波动率序列
    """

    # 隔夜收益率:昨日收盘到今日开盘
    overnight_ret = np.log(open_prices / close_prices.shift(1))
    
    # 开盘到收盘收益率
    oc_ret = np.log(close_prices / open_prices)
    
    # Rogers-Satchell 波动率组件
    rs1 = np.log(high_prices / close_prices) * np.log(high_prices / open_prices)
    rs2 = np.log(low_prices / close_prices) * np.log(low_prices / open_prices)
    
     # 计算各方差组件
    overnight_var = overnight_ret.rolling(window=window).var()
    open_var = oc_ret.rolling(window=window).var()
    rs_var = (rs1 + rs2).rolling(window=window).mean()
    
    # 计算权重因子 k
    k = 0.34 / (1.34 + (window + 1) / (window - 1))
    
    # Yang-Zhang 方差
    yz_var = overnight_var + k * open_var + (1 - k) * rs_var
    yz_var = yz_var.shift(1)
    
    # 转换为波动率并年化
    yz_vol = np.sqrt(yz_var) * np.sqrt(252)
    return yz_vol

仓位管理:波动率目标化

所有策略都采用波动率目标化方法来维持恒定的风险敞口(本例中为 15% 年化波动率):

def calculate_position_size(self, signal: pd.Series, realized_vol: pd.Series) -> pd.Series:
    """
    基于波动率目标化计算仓位大小
    
    仓位 = 信号 × (目标波动率 / 实现波动率)
    
    这种方法会在高波动时期减少敞口,在低波动时期增加敞口
    
    参数:
        signal: 交易信号(+1、-1 或 0)
        realized_vol: 已实现波动率
    
    返回:
        仓位大小序列(资本的比例)
    """

    # 避免除零错误
    realized_vol_safe = realized_vol.replace(0, np.nan)
    
    # 计算目标仓位
    position_size = signal * (self.target_volatility / realized_vol_safe)
    
    # 限制最大仓位在 ±100% 以内
    position_size = position_size.clip(-1.01.0)
    
    return position_size

完整的策略回测框架

下面是整合所有组件的策略类:

class TSMOMStrategy:
    """
    时间序列动量策略类
    
    策略逻辑:
    1. 使用选定的动量指标计算交易信号
    2. 使用选定的波动率估计器计算已实现波动率
    3. 在 Close[t] 时生成信号
    4. 在 Open[t+1] 执行交易
    5. 在 Open[t+1] 对组合估值
    6. 收益率 = Open[t+2]/Open[t+1] - 1
    """

    
    def __init__(self,
                 momentum_type: str,
                 momentum_params: Dict,
                 volatility_type: str,
                 volatility_window: int,
                 target_volatility: float,
                 commission_rate: float)
:

        """
        初始化策略
        
        参数:
            momentum_type: 动量指标类型
            momentum_params: 动量参数字典
            volatility_type: 波动率估计器类型
            volatility_window: 波动率计算窗口
            target_volatility: 目标波动率
            commission_rate: 交易佣金率
        """

        self.momentum_type = momentum_type
        self.momentum_params = momentum_params
        self.volatility_type = volatility_type
        self.volatility_window = volatility_window
        self.target_volatility = target_volatility
        self.commission_rate = commission_rate
    
    def  simulate_trading(self, ohlc_data: pd.DataFrame) -> pd.DataFrame:
        """
        模拟交易,采用真实的执行逻辑
        
        参数:
            ohlc_data: 包含 OHLC 数据的 DataFrame
        
        返回:
            包含信号、仓位和收益的 DataFrame
        """

        results = pd.DataFrame(index=ohlc_data.index)
        
        # 第一步:计算动量信号
        results["momentum_signal"] = self.calculate_momentum_signal(ohlc_data)
        
        # 第二步:计算已实现波动率
        results["realized_volatility"] = self.calculate_realized_volatility(ohlc_data)
        
        # 第三步:计算基于波动率目标的仓位
        results["position_size"] = self.calculate_position_size(
            results["momentum_signal"],
            results["realized_volatility"]
        )
        
        # 第四步:计算执行价格和收益
        results["open"] = ohlc_data["open"]
        results["close"] = ohlc_data["close"]
        
        # 执行:Close[t] 决定的仓位在 Open[t+1] 执行
        results["executed_position"] = results["position_size"].shift(1)
        
        # 持仓期收益:Open[t+1] 到 Open[t+2]
        results["open_to_open_return"] = ohlc_data["open"].pct_change()
        
        # 策略毛收益
        results["gross_return"] = results["executed_position"] * results["open_to_open_return"]
        
        # 计算佣金(仓位变化时收取)
        results["position_change"] = results["executed_position"].diff().abs()
        results["commission_cost"] = results["position_change"] * self.commission_rate
        
        # 净收益
        results["net_return"] = results["gross_return"] - results["commission_cost"]
        
        return results

实证结果

在 SPY(2020-2024)上测试 144 种不同配置后,我们发现:

表现最佳的策略组合包括:

  • 动量指标:20 日滚动窗口(均线交叉 20/200)
  • 波动率估计器:Yang-Zhang 和 Garman-Klass(40 日窗口)
  • 夏普比率:最高达 0.965
  • 总收益:约 90%(基准约 60%)

关键发现

估计器效率差异显著

基于价格范围的估计器(Parkinson、Garman-Klass、Yang-Zhang)能够捕捉日内信息,表现出理论上的效率优势。Yang-Zhang 在存在明显跳空的市场中表现尤为突出。

最优参数组合

短期动量窗口(20 日)配合中期波动率窗口(40 日)能够在响应速度和稳定性之间取得最佳平衡。

波动率目标化的重要性

波动率缩放是 TSMOM 成功的基础,能够在危机期间减少敞口,在平静期增加敞口。

交易成本的影响

本框架纳入了每笔交易 0.1% 的佣金。长周期均线交叉策略能够最大程度减少换仓频率和交易成本。

实践建议

避免前视偏差

所有信号计算都使用 shift(1) 确保 t 时刻的信号只使用 t-1 及之前的数据。

真实的执行逻辑

策略在信号生成后的下一个开盘价执行,收益按开盘价到开盘价计算。

风险管理

仓位上限控制在资本的 ±100%,并妥善处理零波动率的除法问题。

总结

本文详细介绍了 TSMOM 策略的完整实现,包括三种动量指标和四种波动率估计器的组合。通过 144 种配置的回测,我们验证了以下结论:

  1. TSMOM 策略有效:各策略持续跑赢买入持有基准
  2. 波动率估计器很重要:基于价格范围的估计器能提升信号质量
  3. 波动率目标化不可或缺:维持恒定风险并改善夏普比率
  4. 短周期参数占优:20-40 日参数组合表现最佳
  5. 实现细节至关重要:真实执行逻辑、佣金和偏差消除是基础

对于想要进入量化交易领域的 Python 开发者,TSMOM 代表了一个稳健、透明的策略框架。推荐的最优配置是均线交叉(20/200)配合 40 日 Rolling 或 Yang-Zhang 波动率估计器。

参考文章

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

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

核心权益如下:

  1. 赠送《财经数据宝典》完整文档,汇集多年财经数据维护经验
  2. 赠送《量化投研宝典》完整文档,汇集多年量化投研领域经验
  3. 赠送《PyBroker-入门及实战》视频课程,手把手学习量化策略开发
  4. 每日分享高质量量化投研文章(已更新 180+篇)、代码和相关资料
  5. 定期更新高频财经数据
  6. 参与年度不少于 10 次专属直播与录播课程
  7. 与核心开发者直接交流,解决实际问题
  8. 获取专业微信群交流机会和课程折扣

星球已有丰富内容积累,包括量化投研论文、财经高频数据、 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/191141