🚀 AI 一键生成 ptrade 策略代码

PTrade 分钟级策略:如何编写一个日内均值回归交易策略?

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

问题描述

我想写一个在股票价格偏离日内均线时进行反向交易的策略,应该怎么做?

解决方案

这是一个非常经典的日内均值回归策略(Mean Reversion Strategy)

策略逻辑分析

该策略的核心假设是:价格会围绕日内均线(通常使用VWAP,即成交量加权平均价)波动。当价格大幅偏离均线时,有较大概率回归均值。

  1. 基准指标:日内成交量加权平均价 (VWAP) = 当日累计成交额 / 当日累计成交量。
  2. 买入信号:当前价格 < 日内均线 * (1 - 阈值) -> 认为超卖,买入。
  3. 卖出信号:当前价格 > 日内均线 * (1 + 阈值) -> 认为超买,卖出。
  4. 风控与清算
    • T+1 限制:A股市场实行T+1制度,当日买入的股票当日无法卖出。要实现日内交易(T+0),你需要预先持有底仓(底仓套利),或者交易ETF/可转债等支持T+0的品种。
    • 尾盘清仓:如果是做日内波段,通常会在尾盘(如14:55)强制平掉当日的投机仓位,或者恢复到底仓数量。

PTrade 策略代码实现

以下代码实现了一个基于 分钟级别 的日内均线偏离策略。

注意:为了演示方便,本策略假设你交易的是支持T+0的标的(如可转债、ETF),或者你已有底仓进行T+0操作。如果是普通A股且无底仓,当日买入后无法卖出。

import numpy as np

def initialize(context):
    """
    初始化函数,设置策略参数和股票池
    """
    # 设置要操作的标的,这里以恒生电子为例
    # 如果是做T+0,建议选择波动大且成交活跃的标的
    g.security = '600570.SS'
    set_universe(g.security)
    
    # 偏离阈值设置 (例如 1.5%)
    # 当价格偏离均线超过这个比例时触发交易
    g.threshold = 0.015 
    
    # 每次交易的数量
    g.trade_amount = 500
    
    # 设置每日尾盘定时任务,用于平仓或恢复仓位
    run_daily(context, close_intraday_positions, time='14:55')

    # 记录是否已经开仓的标记,避免同一方向重复下单
    g.has_open_position = False

def before_trading_start(context, data):
    """
    盘前处理
    """
    # 每日开盘前重置开仓标记
    g.has_open_position = False
    log.info("盘前准备完毕,等待开盘...")

def handle_data(context, data):
    """
    盘中每分钟运行一次
    """
    # 1. 时间过滤:
    # 开盘前几分钟数据波动剧烈且均线不稳定,跳过不交易
    # 尾盘留给 close_intraday_positions 处理,此处不交易
    current_time = context.blotter.current_dt.strftime('%H:%M')
    if current_time < '09:40' or current_time >= '14:50':
        return

    security = g.security
    
    # 2. 获取当日历史分钟数据,用于计算日内VWAP
    # 获取从开盘到现在的数据,最大取240根分钟K线
    # include=True 表示包含当前这一分钟的数据
    hist_df = get_history(240, frequency='1m', field=['volume', 'money'], security_list=security, include=True)
    
    # 过滤出今天的数据(PTrade返回的DataFrame索引是datetime)
    today_date = context.blotter.current_dt.date()
    today_data = hist_df[hist_df.index.date == today_date]
    
    if len(today_data) == 0:
        return

    # 3. 计算日内成交量加权平均价 (VWAP)
    total_money = today_data['money'].sum()
    total_volume = today_data['volume'].sum()
    
    if total_volume == 0:
        return
        
    vwap = total_money / total_volume
    
    # 4. 获取当前最新价格
    current_price = data[security]['close']
    
    # 计算上下轨
    upper_bound = vwap * (1 + g.threshold)
    lower_bound = vwap * (1 - g.threshold)
    
    # 5. 交易逻辑
    # 获取当前持仓
    position = get_position(security)
    curr_amount = position.amount
    enable_amount = position.enable_amount # 可用持仓(T+1限制下,只有昨仓可用)

    # 情况A: 价格高于上轨 -> 卖出 (做空/止盈)
    if current_price > upper_bound:
        # 如果我们持有仓位(无论是底仓还是今日买入的),尝试卖出
        # 注意:如果是A股,必须有 enable_amount > 0 才能卖
        if enable_amount >= g.trade_amount:
            log.info("价格 %.2f 高于日内均线 %.2f 超过阈值,触发卖出" % (current_price, vwap))
            order(security, -g.trade_amount)
            g.has_open_position = True
        else:
            # 如果没有可用持仓,说明可能是T+1限制或者空仓
            pass

    # 情况B: 价格低于下轨 -> 买入 (做多/抄底)
    elif current_price < lower_bound:
        # 检查资金是否足够
        if context.portfolio.cash > current_price * g.trade_amount:
            log.info("价格 %.2f 低于日内均线 %.2f 超过阈值,触发买入" % (current_price, vwap))
            order(security, g.trade_amount)
            g.has_open_position = True

def close_intraday_positions(context):
    """
    尾盘处理函数:14:55 运行
    如果是做日内T+0,这里需要把当日多出来的仓位卖掉,或者把卖出的仓位买回来。
    这里演示简单的逻辑:如果有持仓,且想保持空仓过夜,则全部卖出。
    """
    log.info("尾盘时间,开始检查仓位...")
    security = g.security
    position = get_position(security)
    
    # 示例逻辑:假设我们不留隔夜仓(适用于ETF/可转债等T+0品种)
    # 如果是股票做T+0,这里应该恢复到昨天的底仓数量
    if position.amount > 0 and position.enable_amount > 0:
        log.info("尾盘平仓: 卖出 %s" % security)
        order_target(security, 0)

代码关键点解释

  1. get_history 获取当日数据

    • 我们使用 get_history(240, ...) 获取足够长的分钟线,然后通过 hist_df.index.date == today_date 筛选出仅属于今天的数据。这是计算日内VWAP(成交量加权均价)的标准方法。
    • VWAP = sum(money) / sum(volume):相比于简单的算术平均(MA),VWAP更能代表市场的平均持仓成本,是机构常用的日内基准。
  2. 阈值 (g.threshold)

    • 代码中设置为 0.015 (1.5%)。这意味着价格必须偏离均线 1.5% 才会触发交易。
    • 如果阈值太小,交易会过于频繁,手续费会吞噬利润;如果阈值太大,可能一天都没有交易机会。建议根据标的的历史波动率进行调整。
  3. 时间过滤

    • 09:40 之前不交易:开盘初期的均线计算样本太少,VWAP 极不稳定,容易产生误导信号。
    • 14:50 之后不交易:临近收盘,防止产生新的被套仓位,准备进入清算阶段。
  4. close_intraday_positions

    • 这是日内策略的重要组成部分。如果你做的是纯日内投机,必须在收盘前平仓。
    • 特别提示:如果你交易的是普通股票(T+1),你不能在买入当天卖出。这种策略通常用于底仓T+0(即你昨天持有1000股,今天低买500股,高卖500股,收盘时持仓仍为1000股,赚取差价)。

常见问题 Q&A

Q: 为什么我回测时没有成交?
A:

  1. 检查 g.threshold 是否设置过大,导致价格从未触及触发线。
  2. 检查 g.security 是否处于停牌状态。
  3. 如果是股票回测,检查是否有可用资金或可用持仓(卖出时需要昨仓)。

Q: 如何将此策略改为简单的算术平均线(MA)而不是VWAP?
A:
handle_data 中计算 VWAP 的部分修改为:

# 计算简单的日内均价
vwap = today_data['close'].mean()

Q: 如何在实盘中提高成交速度?
A:
上述代码是分钟级别的。如果需要更灵敏的反应,可以使用 tick_data 函数进行 Tick 级别的开发,或者在 initialize 中使用 run_interval(context, func, seconds=3) 来每3秒检测一次价格。

Q: 股票T+1怎么解决?
A:
你需要预先在 initialize 中设置底仓(回测中可用 set_yesterday_position),或者在实盘账户中预先买入股票。交易逻辑需要修改为:

  • 高抛:卖出底仓。
  • 低吸:买回底仓。
  • 收盘:检查持仓数量是否等于底仓数量,如果不等则补齐或卖出。