问题描述
解决方案
这是一个基于聚宽(JoinQuant)API 实现的海龟交易策略(Turtle Trading Strategy)。
策略核心逻辑
海龟交易法则是一个著名的趋势跟随策略,其核心要素包含:
- 入场(唐奇安通道突破):当价格突破过去 N 天(通常为20天)的最高价时买入。
- 头寸规模(ATR倒数):根据市场的绝对波动幅度(ATR)来调整仓位大小。波动大买得少,波动小买得多。
- 加仓(金字塔式):当价格在上次买入价的基础上上涨 0.5 * ATR 时,进行加仓。
- 止损:当价格比最后一次买入价下跌 2 * ATR 时,清仓止损。
- 离场:当价格跌破过去 M 天(通常为10天)的最低价时,止盈离场。
策略代码
# -*- coding: utf-8 -*-
import numpy as np
import pandas as pd
from jqdata import *
def initialize(context):
"""
初始化函数
"""
# 1. 设定基准
set_benchmark('000300.XSHG')
# 2. 开启动态复权模式(真实价格)
set_option('use_real_price', True)
# 3. 过滤掉order系列API产生的比error级别低的log
log.set_level('order', 'error')
# --- 海龟策略参数设置 ---
# 入场周期(突破过去多少天的最高价)
g.enter_window = 20
# 离场周期(跌破过去多少天的最低价)
g.exit_window = 10
# ATR计算周期
g.atr_window = 20
# 单笔交易风险占总资金的比例 (1%)
g.risk_ratio = 0.01
# 最大持仓单位限制 (海龟法则通常限制为4个单位)
g.max_units = 4
# 设定要操作的股票池 (这里示例使用沪深300成分股的前5只,实际使用可替换)
g.security_list = get_index_stocks('000300.XSHG')[:5]
# 记录持仓详情的字典
# 格式: {security: {'last_price': float, 'units': int, 'atr': float}}
g.position_details = {}
# 每天运行一次
run_daily(trade_logic, time='open')
def get_atr(security, days):
"""
计算ATR (平均真实波幅)
"""
# 获取过去 days + 1 天的数据,因为计算TR需要前一日收盘价
data = attribute_history(security, days + 1, '1d', ['high', 'low', 'close'])
if len(data) < 2:
return None
high = data['high'].values
low = data['low'].values
close = data['close'].values
tr_list = []
for i in range(1, len(close)):
# TR = max(H-L, abs(H-PDC), abs(L-PDC))
hl = high[i] - low[i]
h_pdc = abs(high[i] - close[i-1])
l_pdc = abs(low[i] - close[i-1])
tr = max(hl, h_pdc, l_pdc)
tr_list.append(tr)
# 计算ATR
atr = np.mean(tr_list)
return atr
def trade_logic(context):
"""
每日交易主逻辑
"""
# 获取当前账户总资产
total_value = context.portfolio.total_value
for security in g.security_list:
# 获取历史数据用于计算突破
# 取 max(enter_window, exit_window) + 1 数据
hist_len = max(g.enter_window, g.exit_window) + 1
data = attribute_history(security, hist_len, '1d', ['high', 'low', 'close'])
if len(data) < hist_len:
continue
# 计算关键价格指标
current_price = data['close'][-1]
# 过去N天的最高价(不包含今天,用于入场)
high_n = data['high'][-(g.enter_window+1):-1].max()
# 过去M天的最低价(不包含今天,用于离场)
low_m = data['low'][-(g.exit_window+1):-1].min()
# 计算ATR
atr = get_atr(security, g.atr_window)
if atr is None or atr == 0:
continue
# --- 检查持仓状态 ---
if security in context.portfolio.positions and context.portfolio.positions[security].closeable_amount > 0:
# 已有持仓,检查 止损、离场 或 加仓
# 获取记录的上次买入价格和持仓单位数
record = g.position_details.get(security)
if not record:
# 如果因为某些原因丢失记录,重置为当前成本
record = {'last_price': context.portfolio.positions[security].avg_cost, 'units': 1, 'atr': atr}
g.position_details[security] = record
last_entry_price = record['last_price']
units = record['units']
# 使用建仓时的ATR还是最新的ATR?海龟法则通常使用建仓时的ATR来计算止损,这里简化使用动态ATR
# 1. 止损检查: 价格下跌 2 * ATR
stop_loss_price = last_entry_price - 2 * atr
# 2. 离场检查: 跌破过去M天最低价 (唐奇安通道下轨)
exit_signal = current_price < low_m
if current_price <= stop_loss_price:
log.info(f"{security} 触发止损 (现价: {current_price}, 止损价: {stop_loss_price}),清仓。")
order_target(security, 0)
if security in g.position_details:
del g.position_details[security]
elif exit_signal:
log.info(f"{security} 跌破{g.exit_window}日低点 (现价: {current_price}, 低点: {low_m}),止盈离场。")
order_target(security, 0)
if security in g.position_details:
del g.position_details[security]
else:
# 3. 加仓检查: 价格上涨 0.5 * ATR 且 未达到最大单位限制
if units < g.max_units and current_price > last_entry_price + 0.5 * atr:
# 计算加仓数量 (1个Unit)
# Unit = (1% 总资金) / ATR
shares_per_unit = int((total_value * g.risk_ratio) / atr / 100) * 100
if shares_per_unit >= 100:
log.info(f"{security} 触发加仓 (现价: {current_price}, 上次: {last_entry_price})。")
order(security, shares_per_unit)
# 更新记录
g.position_details[security]['last_price'] = current_price
g.position_details[security]['units'] += 1
g.position_details[security]['atr'] = atr # 更新ATR
else:
# 无持仓,检查入场信号
# 价格突破过去N天最高价
if current_price > high_n:
# 计算初始建仓数量 (1个Unit)
shares_per_unit = int((total_value * g.risk_ratio) / atr / 100) * 100
if shares_per_unit >= 100:
log.info(f"{security} 突破{g.enter_window}日高点 (现价: {current_price}, 高点: {high_n}),开仓。")
order(security, shares_per_unit)
# 记录开仓信息
g.position_details[security] = {
'last_price': current_price,
'units': 1,
'atr': atr
}
def after_code_changed(context):
"""
模拟交易更新代码后执行
"""
g.security_list = get_index_stocks('000300.XSHG')[:5]
代码详解
-
初始化 (
initialize):- 设置了海龟法则的经典参数:20日突破入场,10日跌破离场。
- 定义了风险参数:每次交易只承担总资金 1% 的波动风险。
g.position_details:这是一个非常关键的全局字典,用于记录每只股票上一次的买入价格和当前的加仓次数(Unit),这是判断是否加仓和止损的依据。
-
ATR 计算 (
get_atr):- ATR(平均真实波幅)是海龟策略的核心。它衡量了市场的波动性。
- 代码中手动计算了 TR(真实波幅),然后取平均值。
-
交易逻辑 (
trade_logic):- 入场:如果当前没有持仓,且价格高于过去20天的最高价(
high_n),则买入 1 个 Unit。- Unit 计算公式:
Unit数量 = (总资金 * 1%) / ATR。这意味着如果股票波动大(ATR大),买的数量就少;波动小,买的数量就多。
- Unit 计算公式:
- 止损:如果持有仓位,且价格低于
上次买入价 - 2 * ATR,则强制平仓。这是海龟法则保护本金的核心规则。 - 离场(止盈):如果价格跌破过去10天的最低价(
low_m),说明趋势可能结束,平仓离场。 - 加仓:如果价格上涨了
0.5 * ATR,并且当前持仓单位少于 4 个,则继续加仓 1 个 Unit,并更新“上次买入价”。
- 入场:如果当前没有持仓,且价格高于过去20天的最高价(
如何在聚宽使用
- 登录聚宽官网,进入“我的策略”。
- 新建一个“股票策略”。
- 将上述代码复制粘贴到代码编辑区,覆盖原有代码。
- 点击“编译运行”进行回测。
注意事项
- 股票池:代码中默认使用了沪深300的前5只股票作为示例。在实盘或正式回测中,建议根据您的需求修改
g.security_list,或者使用get_index_stocks获取整个指数成分股(注意资金容量)。 - 资金量:海龟策略由于基于 ATR 计算仓位,如果资金量太小(例如小于10万),可能计算出的
shares_per_unit不足 100 股,导致无法开仓。 - 适用性:海龟策略在明显的趋势行情中表现最好,但在震荡市中可能会频繁止损(由于突破即买入,假突破会导致亏损)。
Q&A
Q: 为什么海龟策略要用 ATR 来计算仓位?
A: ATR 代表了股票的波动性。通过 (总资金 * 1%) / ATR 计算仓位,可以保证无论买入波动大的股票还是波动小的股票,其承担的单日波动风险金额是基本一致的(都是总资金的1%)。这实现了风险的均等化。
Q: 代码中的 g.position_details 是做什么用的?
A: 这是一个全局字典,用于“记忆”状态。海龟策略的加仓和止损都依赖于上一次买入的价格。普通的持仓信息(context.portfolio)只记录平均成本,无法知道最后一次加仓是在什么价格,所以需要用这个变量单独记录。
Q: 这个策略可以做空吗?
A: 原版海龟策略是双向交易的(做多和做空)。但由于 A 股市场对普通投资者主要是单向做多机制(融券门槛高且券源不稳定),上述代码实现的是仅做多版本。如果需要做期货回测,需要修改 order 函数并增加开空单的逻辑。