🚀 AI 一键生成 ptrade 策略代码

PTrade 策略实战:从零开始编写并应用平均真实波幅 (ATR) 指标

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

问题描述

请帮我编写一个计算平均真实波幅(ATR)的函数,并说明如何在策略中使用它来设置动态止损。

解决方案

在PTrade量化交易平台中,虽然系统提供了MACD、KDJ等常见指标的计算函数,但并未直接提供计算ATR(平均真实波幅)的内置函数。我们需要利用numpypandas库基于get_history获取的行情数据自行编写计算逻辑。

ATR(Average True Range)主要用于衡量市场的波动性。在策略中,它常被用来构建动态追踪止损(Trailing Stop),即随着价格向有利方向变动,止损线也随之移动,从而锁定利润;当价格反向波动超过一定幅度的ATR时,触发止损。

以下是完整的策略代码,包含自定义的ATR计算函数以及基于ATR的动态止损逻辑。

策略代码实现

import numpy as np
import pandas as pd

def initialize(context):
    """
    初始化函数
    """
    # 设置我们要操作的股票,这里以恒生电子为例
    g.security = '600570.SS'
    set_universe(g.security)
    
    # 策略参数设置
    g.atr_period = 14       # ATR计算周期
    g.atr_multiplier = 2.0  # 止损倍数(止损距离 = ATR * 倍数)
    
    # 记录动态止损价
    g.stop_loss_price = 0.0
    
    # 设置手续费等(回测用)
    set_commission(commission_ratio=0.0003, min_commission=5.0)

def calculate_atr(security, period):
    """
    自定义计算ATR指标的函数
    参数:
        security: 标的代码
        period: 计算周期
    返回:
        ATR数值 (float)
    """
    # 获取历史数据
    # 我们需要获取 period + 1 天的数据,因为计算TR需要前一日收盘价
    # include=False 表示不包含当前正在进行的这一根K线,使用历史收盘数据更稳定
    count = period + 2 
    h_data = get_history(count, '1d', ['high', 'low', 'close'], security, fq='pre', include=False)
    
    # 数据长度不足时返回None
    if len(h_data) < count:
        return None
    
    # 将数据转换为DataFrame以便处理
    df = h_data
    
    # 计算TR (真实波幅)
    # TR = Max(High - Low, abs(High - PreClose), abs(Low - PreClose))
    
    # 1. 当前最高价 - 当前最低价
    hl = df['high'] - df['low']
    
    # 2. abs(当前最高价 - 前一日收盘价)
    # shift(1) 是将数据向下移动一行,即获取前一日数据
    hc = (df['high'] - df['close'].shift(1)).abs()
    
    # 3. abs(当前最低价 - 前一日收盘价)
    lc = (df['low'] - df['close'].shift(1)).abs()
    
    # 组合成一个DataFrame取最大值
    tr_df = pd.concat([hl, hc, lc], axis=1)
    tr = tr_df.max(axis=1)
    
    # 移除因为shift产生的NaN值
    tr = tr.dropna()
    
    # 计算ATR:通常使用简单移动平均(SMA)或者Wilder平滑
    # 这里使用简单移动平均作为示例,取最后period天的均值
    if len(tr) < period:
        return None
        
    atr = tr.tail(period).mean()
    
    return atr

def handle_data(context, data):
    """
    盘中运行函数
    """
    security = g.security
    
    # 1. 获取当前价格
    current_price = data[security]['close']
    
    # 2. 计算ATR
    atr_value = calculate_atr(security, g.atr_period)
    
    if atr_value is None:
        log.info("数据不足,无法计算ATR,跳过本次循环")
        return

    # 获取当前持仓
    position = get_position(security)
    
    # ---------------------------------------------------
    # 交易逻辑部分
    # ---------------------------------------------------
    
    # 情况A:当前无持仓,进行买入判断(这里仅作演示,使用简单的买入逻辑)
    if position.amount == 0:
        # 演示逻辑:直接买入
        order(security, 1000)
        
        # 【关键步骤】开仓时初始化止损价
        # 初始止损价 = 开仓价 - (ATR * 倍数)
        g.stop_loss_price = current_price - (atr_value * g.atr_multiplier)
        
        log.info("买入 %s, 价格: %.2f, 初始止损价: %.2f, 当前ATR: %.3f" % (
            security, current_price, g.stop_loss_price, atr_value))
            
    # 情况B:当前有持仓,进行动态止损维护
    elif position.amount > 0:
        # 1. 计算基于当前价格的潜在新止损价
        # 吊灯止损逻辑:随着价格上涨,止损线跟着上涨;价格下跌,止损线不变
        potential_new_stop = current_price - (atr_value * g.atr_multiplier)
        
        # 2. 更新止损价:只有当新的止损价高于旧的止损价时才更新(只升不降)
        if potential_new_stop > g.stop_loss_price:
            old_stop = g.stop_loss_price
            g.stop_loss_price = potential_new_stop
            log.info("价格上涨,上移止损价。旧止损: %.2f -> 新止损: %.2f" % (old_stop, g.stop_loss_price))
            
        # 3. 检查是否触发止损
        if current_price < g.stop_loss_price:
            log.info("触发ATR动态止损!当前价: %.2f < 止损价: %.2f. 卖出平仓。" % (current_price, g.stop_loss_price))
            order_target(security, 0)
            # 重置止损价
            g.stop_loss_price = 0.0

代码核心逻辑解析

  1. calculate_atr 函数

    • 数据获取:使用 get_history 获取 high, low, close 数据。注意 count 设置为 period + 2,因为计算 TR(真实波幅)涉及到前一日收盘价(PreClose),且计算移动平均需要足够的数据点。
    • TR计算:TR 是以下三个值的最大值:
      1. 当日最高价 - 当日最低价
      2. |当日最高价 - 昨日收盘价|
      3. |当日最低价 - 昨日收盘价|
    • ATR计算:对 TR 序列取最近 N 天的简单移动平均(Mean)。
  2. 动态止损(Trailing Stop)逻辑

    • 初始化:在开仓买入时,立即设定初始止损价:当前价格 - (ATR * 倍数)。倍数通常设为 2 到 3,意味着价格需要反向波动 2-3 倍的平均波动幅度才会触发止损,这能有效过滤市场噪音。
    • 动态更新(只升不降)
      • 在每个周期(handle_data)中,根据当前价格重新计算一个“潜在止损价”。
      • 如果 潜在止损价 > 当前记录的止损价,说明价格上涨了,我们将止损线上移,锁定利润。
      • 如果价格下跌,潜在止损价会变低,但我们不更新止损价,保持原有的高位止损线不变。
    • 触发卖出:一旦当前价格跌破了记录的 g.stop_loss_price,立即执行清仓操作。

如何在策略中使用

  1. 复制函数:将 calculate_atr 函数复制到你的策略代码中。
  2. 设置参数:在 initialize 中设置 g.atr_period(通常为14)和 g.atr_multiplier(通常为2.0或3.0)。
  3. 调用逻辑:在 handle_datarun_daily 调用的函数中,按照示例代码的逻辑,在开仓时初始化止损价,在持仓期间不断尝试抬高止损价并检查是否跌破。

Q&A

Q1: 为什么计算ATR时要使用 include=False
A1: include=False 表示获取的数据不包含当前正在交易的这一天(或这一分钟)。在计算止损基准时,使用已经收盘确认的历史数据(昨日ATR)通常比使用正在变动的当日ATR更稳定,能避免盘中剧烈波动导致止损线频繁跳变。

Q2: ATR倍数(Multiplier)应该设置多少?
A2: 这取决于你的交易风格和标的波动性。

  • 短线交易:通常设置 1.5 到 2.0,止损更紧,反应更快。
  • 趋势跟踪:通常设置 2.5 到 3.0,给予价格更多的波动空间,避免被正常的市场噪音震出局。

Q3: 这个函数支持分钟级别回测吗?
A3: 支持。get_history 的第二个参数 frequency 如果设置为 '1m''5m',该函数计算的就是分钟级别的ATR。但在分钟级别策略中,建议适当增大 period 或者倍数,因为分钟数据的噪音通常比日线更大。