🚀 AI 一键生成 ptrade 策略代码

PTrade 统计套利策略入门:如何编写一个配对交易策略?(附 Python 源码)

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

问题描述

请用 Python 给我写一个配对交易策略的例子

解决方案

这是一个基于 PTrade 量化交易平台的**配对交易(Pairs Trading)**策略示例。

策略原理

配对交易是一种统计套利策略。它寻找两只历史价格走势高度相关(协整)的股票。

  1. 计算价差/比率:计算两只股票价格的比率(Ratio = Price A / Price B)。
  2. 均值回归:假设这个比率会围绕一个均值波动。
  3. 交易信号
    • 当比率过高(偏离均值 +N 倍标准差)时,说明 A 贵 B 便宜,因此卖出 A,买入 B
    • 当比率过低(偏离均值 -N 倍标准差)时,说明 A 便宜 B 贵,因此买入 A,卖出 B
    • 当比率回归均值时,平仓或平衡持仓。

PTrade 策略代码

为了确保策略在普通股票账户(非融资融券账户)也能回测运行,本示例采用**“轮动持仓”**模式(即全仓持有相对低估的那一只股票,或者在均值附近各持一半),这是一种适合A股普通账户的配对交易变体。

import numpy as np
import pandas as pd

def initialize(context):
    """
    初始化函数,设置股票池和参数
    """
    # 1. 选取两只相关性较高的股票(这里以浦发银行和华夏银行为例)
    # 在实际实盘中,这对股票需要通过协整性检验来筛选
    g.stock1 = '600000.SS' # 浦发银行
    g.stock2 = '600015.SS' # 华夏银行
    g.security = [g.stock1, g.stock2]
    
    # 设置股票池
    set_universe(g.security)
    
    # 2. 策略参数设置
    g.window = 20        # 计算均值和标准差的移动窗口(过去20天)
    g.entry_std = 1.5    # 开仓阈值:偏离 1.5 倍标准差
    g.exit_std = 0.5     # 平仓/平衡阈值:回归到 0.5 倍标准差以内
    
    # 设置手续费(可选,回测默认有设置)
    # set_commission(commission_ratio=0.0003, min_commission=5.0)

def handle_data(context, data):
    """
    盘中运行函数,每日或每分钟执行
    """
    # 1. 获取历史数据
    # 分别获取两只股票过去 g.window 天的收盘价
    # include=True 表示包含当前周期的最新价格,保证信号实时性
    hist1 = get_history(g.window, '1d', 'close', g.stock1, include=True)
    hist2 = get_history(g.window, '1d', 'close', g.stock2, include=True)
    
    # 如果数据长度不足(例如刚上市或停牌),则不操作
    if len(hist1) < g.window or len(hist2) < g.window:
        return

    # 2. 计算统计指标
    # 提取收盘价序列 (numpy array)
    s1 = hist1['close'].values
    s2 = hist2['close'].values
    
    # 计算比率序列 (Stock1 / Stock2)
    ratios = s1 / s2
    
    # 计算比率的均值和标准差
    mean_ratio = ratios.mean()
    std_ratio = ratios.std()
    
    # 获取当前最新的比率
    current_ratio = ratios[-1]
    
    # 防止标准差为0导致除零错误
    if std_ratio == 0:
        return
        
    # 计算 Z-Score (标准分),衡量当前比率偏离均值的程度
    z_score = (current_ratio - mean_ratio) / std_ratio
    
    # 获取当前账户总资产价值
    total_value = context.portfolio.portfolio_value
    
    # 3. 交易逻辑 (基于 Z-Score 进行仓位轮动)
    
    # 情况 A: Z-Score > 1.5
    # 说明 Stock1 相对 Stock2 价格过高(Stock1 贵,Stock2 便宜)
    # 策略:清仓 Stock1,全仓买入 Stock2
    if z_score > g.entry_std:
        # 卖出 Stock1 (如果持有)
        order_target_value(g.stock1, 0)
        # 买入 Stock2
        order_target_value(g.stock2, total_value)
        
        log.info("Z-Score: %.2f > %.2f. Stock1偏贵. 持仓切换至: %s" % (z_score, g.entry_std, g.stock2))
        
    # 情况 B: Z-Score < -1.5
    # 说明 Stock1 相对 Stock2 价格过低(Stock1 便宜,Stock2 贵)
    # 策略:清仓 Stock2,全仓买入 Stock1
    elif z_score < -g.entry_std:
        # 卖出 Stock2 (如果持有)
        order_target_value(g.stock2, 0)
        # 买入 Stock1
        order_target_value(g.stock1, total_value)
        
        log.info("Z-Score: %.2f < -%.2f. Stock1偏宜. 持仓切换至: %s" % (z_score, g.entry_std, g.stock1))
        
    # 情况 C: -0.5 < Z-Score < 0.5
    # 说明比率回归正常区间
    # 策略:平衡持仓,各持有一半仓位 (或者可以选择空仓观望)
    elif abs(z_score) < g.exit_std:
        target_cash = total_value / 2
        order_target_value(g.stock1, target_cash)
        order_target_value(g.stock2, target_cash)
        
        # log.info("Z-Score: %.2f 回归正常区间. 平衡持仓." % z_score)

def before_trading_start(context, data):
    """
    盘前处理(可选)
    """
    pass

代码关键点解析

  1. 数据获取 (get_history):
    • 我们分别获取两只股票的历史收盘价。这里使用了 include=True,这意味着计算 Z-Score 时使用的是包含当前最新价格的数据,这对于日内回测或实盘非常重要。
  2. Z-Score 计算:
    • 公式:$Z = \frac{\text{当前比率} - \text{均值}}{\text{标准差}}$
    • 这是配对交易的核心。它将价格比率标准化,告诉我们当前两只股票的价差是否处于“异常”状态。
  3. 交易执行 (order_target_value):
    • 使用 order_target_value 是最方便的调仓方式。它会自动计算需要买入或卖出的数量,将持仓调整到目标金额。
    • 非融券模式:上述代码是“做多相对低估品种,卖出相对高估品种”。如果是融资融券账户,可以将逻辑修改为:Z分过高时,marginsec_open (融券卖出) Stock1 并 margincash_open (融资买入) Stock2。

Q&A

Q1: 如何在 PTrade 中进行真正的“多空”配对交易(即同时做多一只和做空另一只)?
A1: 如果你的账户开通了融资融券权限,可以使用信用交易接口。

  • 融券卖出:使用 marginsec_open(security, amount)
  • 买券还券:使用 marginsec_close(security, amount)
  • 融资买入:使用 margincash_open(security, amount)
  • 卖券还款:使用 margincash_close(security, amount)
    handle_data 中,你需要判断 Z-Score 方向,然后同时下达一笔融券卖出指令和一笔融资买入指令。

Q2: 为什么代码中分别调用两次 get_history 而不是一次获取所有股票?
A2: PTrade 的 get_history 接口在不同版本(Python 3.5 vs Python 3.11)以及传入单个股票 vs 股票列表时,返回的数据结构(DataFrame 的列索引结构)可能略有不同。为了保证代码在各种 PTrade 环境下的健壮性,分别获取单只股票的数据是最稳妥的做法。

Q3: 如何选择配对的股票?
A3: 在 initialize 中硬编码股票代码仅用于演示。在实际研究中,通常需要先在研究环境(Notebook)中进行协整性检验 (Cointegration Test)。常用的方法是使用 statsmodels 库中的 coint 函数来测试两只股票的历史价格序列是否具有长期稳定的线性关系。只有通过检验的股票对才适合做配对交易。