海龟交易系统本质上是一个趋势跟随的系统,但是最值得学习的是资金管理尤其是分批建仓及动态止损的部分。
一个典型的交易系统需要关注如下几个问题:
- 建仓:什么时候买,买入多少
- 加仓: 什么时候买,买入多少
- 止损:什么时候卖,卖多少
- 清仓:什么时候退出
建仓
海龟交易系统利用[[唐奇安通道]],作为趋势跟踪指标, 其买入的时机是当前闭市价格大于前20个交易日最高价格时即可买入。我们用如下代码即可实现:
self._high = High(feed[instrument].getHighDataSeries(), 20, 5)
那么买入多少,海龟交易系统引入了一个分批建仓的思想。 其用了N值仓位管理法,其引入了一个核心概念头寸规模单位。 一个 头寸规模单位是指账户资金规模的1%与市场的绝对波动幅度(ATR)的比值。 其公式为 unit = (1%总资金)/ATR
举个列子: 假设有10万的本金,当前ATR为0.1,股票价格10元。那么风险承受能力1%对应的资金规模是1000块,则需要买入1w股,耗资10w元,对应的代码:
quantity = self.getBroker().getCash() * 0.01 / self._atr[-2]
quantity = int(quantity / 100) * 100
这里再解释下市场的绝对波动幅度(ATR)和真实波幅(TR)的概念: TR是当前交易日最高价和最低价之差 、前一交易日收盘价与当前交易日最高价之差、前一交易日收盘价与当前交易日最低价之差三者中的最大值,用公式表示为:TR=Max(High−Low,abs(High−PreClose),abs(PreClose−Low)), ATR则表示真实波幅TR的20日平均值,而
我们使用pyalgotrade的话可以直接用其包装好的
from pyalgotrade.technical.atr import ATR
self._atr = ATR(feed[instrument], 14)
我们可以看下其内部实现:
def _calculateTrueRange(self, value):
ret = None
if self.__prevClose is None:
ret = value.getHigh(self.__useAdjustedValues) - value.getLow(self.__useAdjustedValues)
else:
tr1 = value.getHigh(self.__useAdjustedValues) - value.getLow(self.__useAdjustedValues)
tr2 = abs(value.getHigh(self.__useAdjustedValues) - self.__prevClose)
tr3 = abs(value.getLow(self.__useAdjustedValues) - self.__prevClose)
ret = max(max(tr1, tr2), tr3)
return ret
def onNewValue(self, dateTime, value):
tr = self._calculateTrueRange(value)
super(ATREventWindow, self).onNewValue(dateTime, tr)
self.__prevClose = value.getClose(self.__useAdjustedValues)
if value is not None and self.windowFull():
if self.__value is None:
self.__value = self.getValues().mean()
else:
self.__value = (self.__value * (self.getWindowSize() - 1) + tr) / float(self.getWindowSize())
先算tr, 然后根据TR,计算平均值
加仓
海龟们在入市1单位头寸后,然后间隔1/2N的价格间隔逐步扩大头寸,直至达到上限。举个列子: 假设有10万的本机,当前ATR为0.1,股票价格10元。那么第一次买入的价格是10,第二次是10+1的价格买入一个单位,第三次是11+1的价格买入一个单位。
止损
止损标准,即任何一笔交易的风险程度不得超过2%
清仓
突破10日最低价格的时候清仓。
完整代码
借用PyAlgoTrade, 我们只需要实现一个Strategy即可:
class SeaturtleStrategy(strategy.BacktestingStrategy):
def __init__(self, feed, instrument):
super(SeaturtleStrategy, self).__init__(feed, const_init_totle)
self._instrument = instrument
self._buyprice = 0.0
self._sma = ma.SMA(feed[instrument].getCloseDataSeries(), 4)
self._high = High(feed[instrument].getHighDataSeries(), 20, 5)
self._low = Low(feed[instrument].getLowDataSeries(), 10)
self._atr = ATR(feed[instrument], 14)
# self._middle = (self._high + self._low)/2
def onBars(self, bars):
if len(self._high) < 2:
return
pre_highest = self._high[-2]
if pre_highest is None:
return
pre_lowest = self._low[-2]
if pre_lowest is None:
return
bar = bars[self._instrument]
quantity = self.getBroker().getShares(self._instrument)
# 建仓
if bar.getClose() > pre_highest:
self._buy(bar, pre_highest)
# 加仓
elif quantity > 0 and bar.getClose() > self._buyprice + 0.5 * pre_highest:
self._buy(bar, pre_highest)
print('加仓')
# 清仓
elif quantity > 0 and bar.getClose() < pre_lowest:
self.enterShort(code, quantity)
self._log(bar, 'enterShort {},{},{}'.format(bar.getClose(), pre_lowest, quantity))
# 止损
elif quantity > 0 and bar.getClose() < (self._buyprice - 2 * pre_highest):
self.enterShort(code, quantity)
self._log(bar, 'enterShort2 {},{},{}'.format(bar.getClose(), pre_lowest, quantity))
def _buy(self, bar, pre_highest):
quantity = self.getBroker().getCash() * 0.01 / self._atr[-2]
quantity = int(quantity / 100) * 100
if quantity > 0:
self.enterLong(self._instrument, quantity, True)
self._buyprice = bar.getClose()
self._log(bar, 'enterLong {},{},{}'.format(bar.getClose(), pre_highest, quantity))
def _log(self, bar, txt):
time = bar.getDateTime()
print('{}, {}'.format(time,txt))
整体用上证30,从2017-04-03,进行回归测试, 入市金额100w, 节奏时间到2022-03-01, 结果金额:1054753.38, 五年收益5%,收益率不高 。