问题描述
解决方案
在PTrade量化交易平台中,虽然系统提供了MACD、KDJ等常见指标的计算函数,但并未直接提供计算ATR(平均真实波幅)的内置函数。我们需要利用numpy和pandas库基于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
代码核心逻辑解析
-
calculate_atr函数:- 数据获取:使用
get_history获取high,low,close数据。注意count设置为period + 2,因为计算 TR(真实波幅)涉及到前一日收盘价(PreClose),且计算移动平均需要足够的数据点。 - TR计算:TR 是以下三个值的最大值:
- 当日最高价 - 当日最低价
- |当日最高价 - 昨日收盘价|
- |当日最低价 - 昨日收盘价|
- ATR计算:对 TR 序列取最近
N天的简单移动平均(Mean)。
- 数据获取:使用
-
动态止损(Trailing Stop)逻辑:
- 初始化:在开仓买入时,立即设定初始止损价:
当前价格 - (ATR * 倍数)。倍数通常设为 2 到 3,意味着价格需要反向波动 2-3 倍的平均波动幅度才会触发止损,这能有效过滤市场噪音。 - 动态更新(只升不降):
- 在每个周期(
handle_data)中,根据当前价格重新计算一个“潜在止损价”。 - 如果
潜在止损价 > 当前记录的止损价,说明价格上涨了,我们将止损线上移,锁定利润。 - 如果价格下跌,潜在止损价会变低,但我们不更新止损价,保持原有的高位止损线不变。
- 在每个周期(
- 触发卖出:一旦当前价格跌破了记录的
g.stop_loss_price,立即执行清仓操作。
- 初始化:在开仓买入时,立即设定初始止损价:
如何在策略中使用
- 复制函数:将
calculate_atr函数复制到你的策略代码中。 - 设置参数:在
initialize中设置g.atr_period(通常为14)和g.atr_multiplier(通常为2.0或3.0)。 - 调用逻辑:在
handle_data或run_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 或者倍数,因为分钟数据的噪音通常比日线更大。