海龟交易策略
最近看到一个很牛逼的策略,海龟交易策略,其实这个策略也是老古董了,但是它是一套极为完整的交易系统,经过时间的考验后依旧经久不衰,太棒了。
海龟交易策略
总有人说这是个垃圾,但它提供了一套交易思路,最亮眼的就是仓位管理机制。原版的策略里,直观可以理解为:如果从日线的角度来看,通过计算ATR,每一次建仓,每天最极端的变动会使得总资金的变动幅度不超过所设定的risk value。
策略核心理念
趋势跟踪:利用价格突破信号捕捉市场趋势。
仓位管理:通过计算波动性,确保每次建仓时单笔风险不超过预设比例(例如1%)。
严格规则:制定明确的加仓、止损和止盈规则,保证策略的执行纪律。
Average True Range, ATR, 平均真实波幅
下面是原版海龟的ATR计算方法
真实波幅(TR)计算公式:
$$
TR = \max(H - L,, |H - PDC|,, |PDC - L|)
$$
- H:当日最高价
- L:当日最低价
- PDC:前一日收盘价
ATR 计算公式:
$$
ATR = \frac{19 \times PATR + TR}{20}
$$
- PATR:前一日的ATR值
- 注意:首次计算ATR时,不能使用递推公式,而是用最近20日真实波幅的简单平均值。
从ATR的计算过程可以感受到,如果我想让每一个仓位(Unit,做建仓的股数)每天的变动幅度所带来的影响不超过总资金的百分之一,可以设置risk value为0.01,从而计算:
$$
Unit = \frac{risk\ value \times Total_net}{ATR}
$$
其中,Total_net 表示总资产净值。
Donchian Channel, 唐奇安通道
唐奇安通道规则为:当最高价高于前X个K的最大最高价时,做多;当最低价低于前X个K的最小最低价时,做空。如果你想对往后回溯多少K进行优化,你会发现在不同市场会得到不同的结果,甚至同一市场不同时期最优值也是不同的。但是一般默认值为20。
该指标的计算方法,设定常用参数为20日,则:
上轨(Upper Channel):20日最高价
$$
Upper\ Channel = 20\ Day\ High
$$下轨(Lower Channel):20日最低价
$$
Lower\ Channel = 20\ Day\ Low
$$中轨(Middle Channel):上轨与下轨均值
$$
Middle\ Channel = \frac{20\ Day\ High + 20\ Day\ Low}{2}
$$
交易流程
海龟策略的交易流程分为建仓、加仓、止损与止盈四个部分。
建仓时机
- 突破信号:
- 当前价格突破上轨时,产生买入信号;
- 当前价格跌破下轨时,产生卖空信号。
- 初始仓位:建仓时先买入/卖空1个Unit。
加仓规则
- 多仓加仓:当价格在上一次建仓或加仓后上涨了0.5ATR时,加仓1个Unit。
- 空仓加仓:当价格在上一次建仓或加仓后下跌了0.5ATR时,加仓1个Unit。
海龟策略本质上是一种“追涨杀跌”的交易方法,通过不断加仓来顺应趋势。
止损策略
- 多仓止损:若价格在上一次建仓或加仓基础上下跌2N,则卖出所有多仓以止损。
- 空仓止损:若价格在上一次建仓或加仓基础上上涨2N,则平掉所有空仓以止损。
止盈策略
利用10日唐奇安通道判断止盈时机:
- 多仓止盈:当价格跌破10日通道的下轨时,清空所有多仓,结束策略。
- 空仓止盈:当价格突破10日通道的上轨时,清空所有空仓,结束策略。
QuantConnect上简单回测
这是一个量化交易平台,就不介绍了,用这个平台回测2024全年,上述策略在AAPL股票上的表现。
#
# _ooOoo_
# o8888888o
# 88" . "88
# (| -_- |)
# O\ = /O
# ____/`---'\____
# . ' \\| |// `.
# / \\||| : |||// \
# / _||||| -:- |||||- \
# | | \\\ - /// | |
# | \_| ''\---/'' | |
# \ .-\__ `-` ___/-. /
# ___`. .' /--.--\ `. . __
# ."" '< `.___\_<|>_/___.' >'"".
# | | : `- \`.;`\ _ /`;.`/ - ` : | |
# \ \ `-. \_ __\ /__ _/ .-` / /
# ======`-.____`-.___\_____/___.-`____.-'======
# `=---='
#
# .............................................
# 佛祖保佑 永无BUG
#
# region imports
from AlgorithmImports import *
from datetime import timedelta
# endregion
class TurtleTrading(QCAlgorithm):
def initialize(self):
self.set_start_date(2024, 1, 1)
self.set_end_date(2025, 1, 31)
self.set_cash(10000000)
self.aapl = self.add_equity('AAPL', Resolution.DAILY, data_normalization_mode=DataNormalizationMode.RAW).symbol
self.entry_length = 20
self.exit_length = 10
self.atr_length = 14
self.risk_percent = 0.01
self.max_position = 10
self.donchian_entry = self.dch(self.aapl, self.entry_length, self.entry_length)
self.donchian_exit = self.dch(self.aapl, self.exit_length, self.exit_length)
self.long_entry = float('inf')
self.short_entry = 0
self.long_exit = 0
self.short_exit = float('inf')
self.set_warm_up(50)
self._atr = self.atr(self.aapl, self.atr_length, MovingAverageType.Simple, Resolution.DAILY)
self.entry_price = 0
self.position_count = 0
self.stop_loss = 0
self.consolidate(self.aapl, timedelta(days=1), self.on_consolidated)
def on_consolidated(self, bar: TradeBar):
# Plot the candlestick data
self.plot("DonchianChannel", "AAPL", bar)
def on_data(self, data: Slice):
if not (self.donchian_entry.is_ready and self.donchian_exit.is_ready and self._atr.is_ready):
return
current_price = self.securities[self.aapl].price
if self.donchian_entry.is_ready:
# self.debug('dc is ready')
# The current value of self._dch is represented by self._dch.current.value
self.plot("DonchianChannel", "dch", self.donchian_entry.current.value)
# Plot all attributes of self._dch
self.plot("DonchianChannel", "upper_band", self.donchian_entry.upper_band.current.value)
self.plot("DonchianChannel", "lower_band", self.donchian_entry.lower_band.current.value)
if self._atr.is_ready:
# The current value of self._atr is represented by self._atr.current.value
self.plot("AverageTrueRange", "atr", self._atr.current.value)
holdings = self.portfolio[self.aapl].quantity
dollar_risk = self.portfolio.total_portfolio_value * self.risk_percent
atr_value = self._atr.current.value
contract_value = current_price
position_size = dollar_risk / (atr_value)
if holdings == 0 and current_price > self.long_entry:
self.debug('long')
self.entry_price = current_price
self.stop_loss = current_price - 2 * atr_value
self.position_count = 1
self.market_order(self.aapl, position_size)
elif holdings == 0 and current_price < self.short_entry:
self.debug('short')
self.entry_price = current_price
self.stop_loss = current_price + 2 * atr_value
self.position_count = 1
self.market_order(self.aapl, -position_size)
elif abs(holdings) < self.max_position * position_size:
if (holdings > 0 and current_price > self.entry_price + 0.5 * atr_value) or \
(holdings < 0 and current_price < self.entry_price - 0.5 * atr_value):
self.position_count += 1
self.entry_price = current_price
self.market_order(self.aapl, position_size if holdings > 0 else -position_size)
if (holdings > 0 and current_price < self.stop_loss) or \
(holdings < 0 and current_price > self.stop_loss):
self.liquidate(self.aapl)
self.position_count = 0
if holdings > 0 and current_price < self.long_exit:
self.liquidate(self.aapl)
elif holdings < 0 and current_price > self.short_exit:
self.liquidate(self.aapl)
self.long_entry = self.donchian_entry.upper_band.current.value
self.short_entry = self.donchian_entry.lower_band.current.value
self.long_exit = self.donchian_exit.lower_band.current.value
self.short_exit = self.donchian_exit.upper_band.current.value
可以看到,原版的策略表现并不好,但依旧提供了一个不错的思路以及在仓位管理上的启发,理解整体思路后完全可以自己继续改进策略,我们以后再见。