🚀 AI 一键生成 ptrade 策略代码

PTrade 统计套利实战:基于 Z-Score 的配对交易策略编写指南

ptrade | 发布时间: 2025/12/24
以下内容由 EasyQuant 生成。

问题描述

请帮我写一个基于 Z-Score 的配对交易策略

解决方案

这是一个基于 Z-Score(标准分数) 模型的配对交易(Pairs Trading)策略。

策略原理

配对交易是一种基于均值回归的市场中性策略。

  1. 选取标的:选取两只历史走势高度相关(协整)的股票(例如:浦发银行 和 招商银行)。
  2. 计算价差比率:计算两只股票价格的比值(Ratio = Price_A / Price_B)。
  3. 计算 Z-Score:基于过去 N 天的历史数据,计算当前比值的 Z-Score。
    • $Z = \frac{\text{当前比值} - \text{比值均值}}{\text{比值标准差}}$
  4. 交易信号
    • 做空价差(Z > 阈值):比值过高,说明 A 贵 B 便宜。卖出 A,买入 B。
    • 做多价差(Z < -阈值):比值过低,说明 A 便宜 B 贵。买入 A,卖出 B。
    • 平仓(|Z| < 平仓阈值):比值回归正常水平,平掉所有仓位。

策略代码

import numpy as np
import pandas as pd

def initialize(context):
    """
    初始化函数
    """
    # 设定两只拟合度较高的股票(这里以银行股为例:浦发银行 和 招商银行)
    g.stock1 = '600000.SS' 
    g.stock2 = '600036.SS'
    g.security_list = [g.stock1, g.stock2]
    
    # 设置股票池
    set_universe(g.security_list)
    
    # 策略参数
    g.window = 20           # 计算均值和标准差的窗口期(天)
    g.entry_threshold = 2.0 # 开仓阈值(Z-Score绝对值)
    g.exit_threshold = 0.5  # 平仓阈值(Z-Score绝对值)
    
    # 每次交易使用的资金比例(每只股票占总资金的比例)
    g.position_pct = 0.4 

def handle_data(context, data):
    """
    盘中运行函数,每日或每分钟调用
    """
    # 1. 获取历史收盘价数据
    # 注意:获取数量需要包含窗口期,这里取 g.window
    # 当 field 为单个字段时,返回的 DataFrame 列索引为股票代码
    hist = get_history(g.window, '1d', 'close', g.security_list, fq='pre')
    
    # 检查数据长度是否足够
    if len(hist) < g.window:
        log.info("历史数据不足,跳过计算")
        return

    # 2. 提取两只股票的价格序列
    # 注意:需确保数据对齐,处理停牌导致的 NaN
    price_s1 = hist[g.stock1].values
    price_s2 = hist[g.stock2].values
    
    # 简单的清洗:如果有 NaN (停牌),则不进行计算
    if np.isnan(price_s1).any() or np.isnan(price_s2).any():
        log.info("历史数据包含无效值(可能停牌),跳过")
        return

    # 3. 计算比价 (Ratio)
    ratios = price_s1 / price_s2
    
    # 4. 计算 Z-Score
    # Z = (当前比值 - 均值) / 标准差
    current_ratio = ratios[-1]
    mean_ratio = np.mean(ratios)
    std_ratio = np.std(ratios)
    
    if std_ratio == 0:
        return
        
    z_score = (current_ratio - mean_ratio) / std_ratio
    
    # 打印日志方便调试
    log.info("当前 Z-Score: %.4f, 比值: %.4f" % (z_score, current_ratio))
    
    # 5. 获取当前持仓和资金情况
    position_s1 = get_position(g.stock1)
    position_s2 = get_position(g.stock2)
    total_value = context.portfolio.portfolio_value
    target_value = total_value * g.position_pct
    
    # 6. 交易逻辑
    
    # --- 平仓逻辑 (回归均值) ---
    if abs(z_score) < g.exit_threshold:
        # 如果持有仓位,则平仓
        if position_s1.amount != 0 or position_s2.amount != 0:
            log.info("Z-Score 回归 (%.4f),执行平仓" % z_score)
            order_target_value(g.stock1, 0)
            order_target_value(g.stock2, 0)
            
    # --- 开仓逻辑 ---
    
    # 情况 A: Z-Score > 阈值 (Stock1 相对 Stock2 贵) -> 卖 S1,买 S2
    elif z_score > g.entry_threshold:
        # 如果当前没有持有正确的方向(即没有持有S2),则开仓
        # 这里简化逻辑:只要不符合目标持仓就调整
        if position_s2.amount == 0:
            log.info("Z-Score > 阈值 (%.4f),做空价差:卖 %s,买 %s" % (z_score, g.stock1, g.stock2))
            # 卖出 S1 (做空或清仓) -> PTrade A股通常不能直接融券做空,这里模拟为清仓S1
            # 如果是两融账户可以使用 marginsec_open / margincash_open
            # 此处演示普通账户逻辑:持有S1则卖出,买入S2
            order_target_value(g.stock1, 0) 
            order_target_value(g.stock2, target_value)

    # 情况 B: Z-Score < -阈值 (Stock1 相对 Stock2 便宜) -> 买 S1,卖 S2
    elif z_score < -g.entry_threshold:
        # 如果当前没有持有正确的方向(即没有持有S1)
        if position_s1.amount == 0:
            log.info("Z-Score < -阈值 (%.4f),做多价差:买 %s,卖 %s" % (z_score, g.stock1, g.stock2))
            # 买入 S1,卖出 S2
            order_target_value(g.stock1, target_value)
            order_target_value(g.stock2, 0)

def after_trading_end(context, data):
    """
    盘后处理
    """
    log.info("今日交易结束")

代码要点解析

  1. 数据获取 (get_history):

    • 我们使用了 get_history(g.window, '1d', 'close', g.security_list)
    • 在 PTrade 中,当 field 为单个字符串且 security_list 为列表时,返回的 DataFrame 索引是时间,列名是股票代码。这非常方便直接进行向量化计算。
  2. Z-Score 计算:

    • 使用了 numpy 库来计算均值 (np.mean) 和标准差 (np.std)。
    • 这是统计套利中最核心的指标,用于衡量当前价差偏离历史均值的程度。
  3. 交易执行 (order_target_value):

    • 使用 order_target_value 可以让系统自动计算需要买卖的数量以达到目标持仓金额。
    • 注意:上述代码是基于普通股票账户编写的(只能做多,不能做空)。
      • 当信号提示“卖出 A,买入 B”时,代码逻辑是:清空 A 的持仓,买入 B。
      • 如果你使用的是两融账户(信用账户),可以将 order_target_value 替换为 marginsec_open (融券卖出) 和 margincash_open (融资买入) 来实现真正的多空对冲。
  4. 兼容性:

    • 代码遵循 Python 3.5 语法规范(例如使用 % 进行字符串格式化,而不是 f-string),确保在 PTrade 旧版本环境中也能运行。

常见问题 (Q&A)

Q: 如何选择配对的股票?
A: 通常选择同行业、业务相似、市值接近的股票。可以通过协整检验(Cointegration Test)来筛选标的,但在 PTrade 策略中,通常在 initialize 之前通过研究环境选好,直接填入代码。

Q: 为什么使用 order_target_value 而不是 order
A: order_target_value 更适合这种策略,因为它会自动处理调仓。例如,如果当前已经持有 10000 元的股票 A,策略判断需要继续持有,该函数会自动忽略或微调,避免重复下单导致仓位过重。

Q: 这个策略在回测和实盘中有什么区别?
A: 在回测中,get_history 获取的是复权后的历史数据。在实盘中,如果遇到除权除息,价格比率可能会发生跳跃,导致 Z-Score 异常。建议在实盘代码中加入对除权除息的处理,或者使用复权数据(fq='pre')。上述代码已默认使用前复权。