问题描述
解决方案
这是一个基于PTrade量化交易平台的**经典海龟交易策略(Turtle Trading Strategy)**实现。
策略核心逻辑
海龟交易法则是一个著名的趋势跟随策略,主要包含以下几个核心要素:
- 入场(唐奇安通道突破):
- 当价格突破过去20个交易日的最高价时,买入(做多)。
- 离场(止盈):
- 当价格跌破过去10个交易日的最低价时,卖出平仓。
- 资金管理(ATR波动率):
- 使用ATR(平均真实波幅)来计算仓位大小(Unit)。
- 波动大时仓位小,波动小时仓位大,旨在让每笔交易的风险恒定(通常为总资金的1%)。
- 加仓(金字塔式):
- 如果价格在买入后继续上涨 0.5倍ATR,则加仓1个Unit,通常最多加仓4次。
- 止损:
- 当价格比最后一次买入价下跌 2倍ATR 时,全仓止损。
PTrade 策略代码
import numpy as np
import pandas as pd
def initialize(context):
"""
策略初始化函数
"""
# 1. 设置股票池 (这里以沪深300ETF为例,也可以替换为具体股票列表)
g.security_list = ['510300.SS']
set_universe(g.security_list)
# 2. 策略参数设置
g.N_window = 20 # ATR计算周期
g.entry_window = 20 # 入场周期 (突破20日高点)
g.exit_window = 10 # 离场周期 (跌破10日低点)
g.risk_ratio = 0.01 # 每次交易风险占总资金的比例 (1%)
g.max_units = 4 # 最大允许持仓单元数
# 3. 全局变量,用于记录交易状态
# 记录每只股票上一次的买入价格(用于加仓和止损判断)
g.last_entry_prices = {}
# 记录每只股票当前的持仓单元数量
g.hold_units = {}
# 初始化状态字典
for stock in g.security_list:
g.last_entry_prices[stock] = 0.0
g.hold_units[stock] = 0
def before_trading_start(context, data):
"""
盘前处理:可以在这里预计算一些指标,或者处理非交易逻辑
"""
pass
def get_atr(stock, window):
"""
计算ATR (平均真实波幅)
"""
# 获取历史数据,多取1天用于计算前一日收盘价
h = get_history(window + 1, '1d', ['high', 'low', 'close'], stock, fq='pre', include=False)
if len(h) < window + 1:
return None
high = h['high'].values
low = h['low'].values
close = h['close'].values
tr_list = []
# 从第二天开始计算TR
for i in range(1, len(close)):
hl = high[i] - low[i]
hc = abs(high[i] - close[i-1])
lc = abs(low[i] - close[i-1])
tr = max(hl, hc, lc)
tr_list.append(tr)
# 计算ATR (简单平均)
atr = np.mean(tr_list)
return atr
def handle_data(context, data):
"""
盘中每分钟/每日运行的逻辑
"""
# 获取当前账户总资产
total_value = context.portfolio.portfolio_value
cash = context.portfolio.cash
for stock in g.security_list:
# 1. 数据准备
# 获取过去 N 天的历史数据用于计算唐奇安通道
# 需要多取数据以确保计算准确,这里取 max(entry_window, exit_window) + 缓冲
hist_len = max(g.entry_window, g.exit_window) + 5
h = get_history(hist_len, '1d', ['high', 'low', 'close'], stock, fq='pre', include=False)
if len(h) < g.entry_window:
log.info("历史数据不足,跳过 %s" % stock)
continue
# 计算唐奇安通道上轨 (过去20天的最高价)
donchian_high = h['high'][-g.entry_window:].max()
# 计算唐奇安通道下轨 (过去10天的最低价)
donchian_low = h['low'][-g.exit_window:].min()
# 计算ATR
atr = get_atr(stock, g.N_window)
if atr is None or atr == 0:
continue
# 获取当前价格
current_price = data[stock]['close']
# 获取当前持仓
position = get_position(stock)
current_amount = position.amount
# ---------------------------------------------------
# 策略逻辑 A: 无持仓,检查是否满足入场条件
# ---------------------------------------------------
if current_amount == 0:
# 突破20日高点
if current_price > donchian_high:
# 计算 1个 Unit 的数量
# Unit = (总资金 * 1%) / ATR
# 含义:如果价格波动 1N (1个ATR),账户损失 1%
unit_value = total_value * g.risk_ratio
shares_per_unit = int(unit_value / atr / 100) * 100 # 向下取整到100股
if shares_per_unit >= 100 and cash >= shares_per_unit * current_price:
order(stock, shares_per_unit)
g.last_entry_prices[stock] = current_price
g.hold_units[stock] = 1
log.info("【开仓】%s 突破20日高点(%.2f),买入1单元(%d股),ATR=%.3f" % (
stock, donchian_high, shares_per_unit, atr))
# ---------------------------------------------------
# 策略逻辑 B: 有持仓,检查加仓、止损、止盈
# ---------------------------------------------------
else:
last_entry = g.last_entry_prices[stock]
units = g.hold_units[stock]
# 1. 止损检查:价格比最后买入价下跌 2N
stop_loss_price = last_entry - 2 * atr
if current_price < stop_loss_price:
order_target(stock, 0)
g.hold_units[stock] = 0
g.last_entry_prices[stock] = 0
log.info("【止损】%s 触发止损(现价%.2f < 止损价%.2f),清仓" % (
stock, current_price, stop_loss_price))
continue # 止损后跳过后续逻辑
# 2. 止盈离场:跌破10日低点
if current_price < donchian_low:
order_target(stock, 0)
g.hold_units[stock] = 0
g.last_entry_prices[stock] = 0
log.info("【止盈】%s 跌破10日低点(%.2f),清仓离场" % (
stock, donchian_low))
continue
# 3. 加仓逻辑:价格比最后买入价上涨 0.5N
# 且持仓未达到最大限制
add_entry_price = last_entry + 0.5 * atr
if current_price > add_entry_price and units < g.max_units:
# 计算加仓数量 (保持Unit定义一致)
unit_value = total_value * g.risk_ratio
shares_per_unit = int(unit_value / atr / 100) * 100
if shares_per_unit >= 100 and cash >= shares_per_unit * current_price:
order(stock, shares_per_unit)
g.last_entry_prices[stock] = current_price # 更新最后买入价
g.hold_units[stock] += 1
log.info("【加仓】%s 上涨0.5N(现价%.2f > 判定价%.2f),加仓第%d单元" % (
stock, current_price, add_entry_price, g.hold_units[stock]))
代码关键点解析
-
ATR计算 (
get_atr):- ATR是海龟策略的灵魂。它衡量了市场的波动性。
- 代码中手动计算了TR(真实波幅),然后取平均值得到ATR。
get_history获取的数据包含了最高价、最低价和收盘价,这是计算ATR必须的。
-
头寸规模 (Unit Sizing):
shares_per_unit = int(unit_value / atr / 100) * 100- 这是海龟法则的核心风控逻辑。当ATR(波动)大时,计算出的股数会变少;波动小时,股数变多。这保证了无论市场波动如何,每一笔交易承担的绝对风险金额(总资产的1%)是基本恒定的。
-
唐奇安通道 (Donchian Channel):
donchian_high:过去20天的最高价。donchian_low:过去10天的最低价。- 注意:计算通道时使用了
include=False或切片[:-1]的逻辑(代码中直接取历史数据,不包含当前时刻),这是为了避免“用到未来数据”或“当前价格瞬间突破即成交”的回测陷阱,通常突破是指突破过去N天的极值。
-
加仓与止损:
- 加仓依据:
current_price > last_entry + 0.5 * atr。 - 止损依据:
current_price < last_entry - 2 * atr。 - 这里使用了
g.last_entry_prices字典来专门记录上一次动作的价格,这是实现金字塔加仓的关键。
- 加仓依据:
常见问题 Q&A
Q: 为什么代码中要用 int(... / 100) * 100?
A: A股市场买入必须是100股的整数倍(1手)。如果不取整,下单接口可能会报错或被拒单。
Q: 这个策略适合所有股票吗?
A: 海龟策略是典型的趋势跟踪策略。它在有明显长期趋势的行情中表现最好(如大牛市或大熊市)。在震荡市(价格上下波动但无方向)中,该策略会频繁触发止损,导致连续小额亏损(俗称“来回打脸”)。因此,选股(选择波动性大、趋势性强的标的)非常重要。
Q: 如何在回测中查看效果?
A: 将此代码复制到PTrade的研究或回测界面,设置好回测时间段(建议包含牛熊周期,如2018-2022),设置初始资金,点击运行即可。
Q: g.security_list 可以放多只股票吗?
A: 可以。代码已经使用了 for stock in g.security_list: 循环结构,支持多只股票同时运行。但要注意资金分配,如果股票太多,可能导致资金不足以开新仓。