1. 原理1.1 起源海龜交易思想起源于上世紀八十年代的美國。理查德丹尼斯與好友比爾打賭,主題是一個成功的交易員是天生的還是后天的。理查德用十年時間證明了通過日常系統(tǒng)培訓(xùn),交易員可以通過后天培訓(xùn)成為一名優(yōu)秀的交易者。這套培訓(xùn)系統(tǒng)就是海龜交易系統(tǒng)。
海龜交易系統(tǒng)是一個完整的、機械的交易思想,可以系統(tǒng)地完成整個交易過程。它包括了買賣什么、頭寸規(guī)模、何時買賣、何時退出等一系列交易策略,是一個趨勢交易策略。它最顯著的特點是捕捉中長期趨勢,力求在短期內(nèi)獲得最大的收益。
1.2 建倉資金海龜交易法將建倉資金按照一定比例劃分為若干個小部分,每次建倉頭寸和加倉規(guī)模都與波動量N(又稱平均真實波動振幅average true range ATR)有關(guān)。ATR是日內(nèi)指數(shù)最大波動的平均振幅,由當(dāng)日最高、最低價和上一交易日的收盤價決定。
ATR
其中PDC是前一交易日的收盤價,ATR就是TR在N天內(nèi)的均值。
價值波動量
利用N值來體現(xiàn)價值波動量DV: DV = N * 合約每點價值
其中每點代表的價值量是指每一個指數(shù)點數(shù)所代表的價格。 每一次開倉交易合約數(shù)unit的確定是將總資產(chǎn)的1%除以DV得到。
1.3 入市信號海龜交易法使用的是以一個理查德唐奇安的通道突破系統(tǒng)為基礎(chǔ)的入市系統(tǒng)。唐奇安通道分為系統(tǒng)一和系統(tǒng)二,對應(yīng)短期突破和中長期突破。其中,短期突破系統(tǒng)是以20日(最高價或最低價)突破為基礎(chǔ),當(dāng)價格突破20日價格即為入市信號;中長期系統(tǒng)是當(dāng)盤中價格突破過去55日價格為入市信號。
1.4 加倉和止損海龜交易法的加倉規(guī)則是當(dāng)捕捉到入市信號后建立第一個交易單位的頭寸,市價繼續(xù)向盈利方向突破1/2N時加倉。
止損位為2N,同加倉一樣采用平均真實振幅N值為止損單位。每加倉一次,止損位就提高1/2N。
1.5 止盈短期:多頭頭寸在突破過去10日最低價處止盈離市,空頭頭寸在突破過去10日最高價處止盈離市。 中長期:多頭頭寸在突破過去20日最低價處止盈離市,空頭頭寸在突破過去20日最高價處止盈離市。
2. 策略思路第一步:獲取歷史數(shù)據(jù),計算唐奇安通道和ATR 第二步:當(dāng)突破唐奇安通道時,開倉。 第三步:計算加倉和止損信號。
回測標的:DCE.i2012 回測時間:2020-02-15 至 2020-09-01 回測初始資金:100萬
Tips: ATR值是不斷變化的,這就會導(dǎo)致在對期貨平倉時,可能出現(xiàn)平倉數(shù)量 > 持倉數(shù)量的現(xiàn)象。比如前一交易日的持倉為10,今日的ATR值為22.假設(shè)當(dāng)前價格觸發(fā)平倉條件,平倉1/2ATR。1/2ATR=11 > 10, 這樣就會導(dǎo)致委托失敗報錯。所以要加入一個變量volume_hold用來記錄當(dāng)前持倉量,與1/2*ATR作比較。
注意:若修改回測期,需要修改對應(yīng)的回測標的。
3. 策略代碼# coding=utf-8 from __future__ import print_function, absolute_import, unicode_literals import numpy as np import pandas as pd from gm.api import *
''' 以短期為例:20日線 第一步:獲取歷史數(shù)據(jù),計算唐奇安通道和ATR 第二步:當(dāng)突破唐奇安通道時,開倉。 第三步:計算加倉和止損信號。 '''
def init(context): # 設(shè)置計算唐奇安通道的參數(shù) context.n = 20 # 設(shè)置合約標的 context.symbol = 'DCE.i2012' # 設(shè)置交易最大資金比率 context.ratio = 0.8 # 訂閱數(shù)據(jù) subscribe(symbols=context.symbol, frequency='60s', count=2) # 獲取當(dāng)前時間 time = context.now.strftime('%H:%M:%S') # 如果策略執(zhí)行時間點是交易時間段,則直接執(zhí)行algo定義atr等參數(shù),以防直接進入on_bar()導(dǎo)致atr等未定義 if '09:00:00' < time < '15:00:00' or '21:00:00' < time < '23:00:00': algo(context) # 如果是交易時間段,等到開盤時間確保進入algo() schedule(schedule_func=algo, date_rule='1d', time_rule='09:00:00') schedule(schedule_func=algo, date_rule='1d', time_rule='21:00:00')
def algo(context): # 計算通道的數(shù)據(jù):當(dāng)日最低、最高、上一交易日收盤 # 注:由于talib庫計算ATR的結(jié)果與公式求得的結(jié)果不符,所以這里利用公式計算ATR # 如果是回測模式,當(dāng)天的數(shù)據(jù)直接用history取到 if context.mode == 2: data = history_n(symbol=context.symbol, frequency='1d', count=context.n+1, end_time=context.now, fields='close,high,low,bob', df=True) # 計算ATR tr_list = [] for i in range(0, len(data)-1): tr = max((data['high'].iloc[i] - data['low'].iloc[i]), data['close'].shift(-1).iloc[i] - data['high'].iloc[i], data['close'].shift(-1).iloc[i] - data['low'].iloc[i]) tr_list.append(tr) context.atr = int(np.floor(np.mean(tr_list))) context.atr_half = int(np.floor(0.5 * context.atr))
# 計算唐奇安通道 context.don_open = np.max(data['high'].values[-context.n:]) context.don_close = np.min(data['low'].values[-context.n:])
# 如果是實時模式,當(dāng)天的數(shù)據(jù)需要用current取到 if context.mode == 1: data = history_n(symbol=context.symbol, frequency='1d', count=context.n, end_time=context.now, fields='close,high,low', df=True) # 計算ATR current_data = current(symbols=context.symbol) # 最新一個交易日的最高、最低 tr_list = [] for i in range(1, len(data)): tr = max((data['high'].iloc[i] - data['low'].iloc[i]), data['close'].shift(-1).iloc[i] - data['high'].iloc[i], data['close'].shift(-1).iloc[i] - data['low'].iloc[i]) tr_list.append(tr)
# 把最新一期tr加入列表中 tr_new = max((current_data[0]['high'] - current_data[0]['low']), data['close'].iloc[-1] - current_data[0]['high'], data['close'].iloc[-1] - current_data[0]['low']) tr_list.append(tr_new) context.atr = int(np.floor(np.mean(tr_list))) context.atr_half = int(np.floor(0.5 * context.atr))
# 計算唐奇安通道 context.don_open = np.max(data['high'].values[-context.n:]) context.don_close = np.min(data['low'].values[-context.n:])
# 計算加倉點和止損點 context.long_add_point = context.don_open + context.atr_half context.long_stop_loss = context.don_open - context.atr_half context.short_add_point = context.don_close - context.atr_half context.short_stop_loss = context.don_close + context.atr_half
def on_bar(context, bars): # 提取數(shù)據(jù) symbol = bars[0]['symbol'] recent_data = context.data(symbol=context.symbol, frequency='60s', count=2, fields='close,high,low') close = recent_data['close'].values[-1]
# 賬戶倉位情況 position_long = context.account().position(symbol=symbol, side=PositionSide_Long) position_short = context.account().position(symbol=symbol, side=PositionSide_Short)
# 當(dāng)無持倉時 if not position_long and not position_short: # 如果向上突破唐奇安通道,則開多 if close > context.don_open: order_volume(symbol=symbol, side=OrderSide_Buy, volume=context.atr, order_type=OrderType_Market, position_effect=PositionEffect_Open) print('開多倉atr')
# 如果向下突破唐奇安通道,則開空 if close < context.don_close: order_volume(symbol=symbol, side=OrderSide_Sell, volume=context.atr, order_type=OrderType_Market, position_effect=PositionEffect_Open) print('開空倉atr')
# 有持倉時 # 持多倉,繼續(xù)突破(加倉) if position_long: # 當(dāng)突破1/2atr時加倉 if close > context.long_add_point: order_volume(symbol=symbol, volume=context.atr_half, side=OrderSide_Buy, order_type=OrderType_Market,position_effect=PositionEffect_Open) print('繼續(xù)加倉0.5atr') context.long_add_point += context.atr_half context.long_stop_loss += context.atr_half # 持多倉,止損位計算 if close < context.long_stop_loss: volume_hold = position_long['volume'] if volume_hold >= context.atr_half: order_volume(symbol=symbol, volume=context.atr_half, side=OrderSide_Sell, order_type=OrderType_Market, position_effect=PositionEffect_Close) else: order_volume(symbol=symbol, volume=volume_hold, side=OrderSide_Sell, order_type=OrderType_Market,position_effect=PositionEffect_Close) print('平多倉0.5atr') context.long_add_point -= context.atr_half context.long_stop_loss -= context.atr_half
# 持空倉,繼續(xù)突破(加倉) if position_short: # 當(dāng)?shù)萍觽}點時加倉 if close < context.short_add_point: order_volume(symbol = symbol, volume=context.atr_half, side=OrderSide_Sell, order_type=OrderType_Market, position_effect=PositionEffect_Open) print('繼續(xù)加倉0.5atr') context.short_add_point -= context.atr_half context.short_stop_loss -= context.atr_half
# 持多倉,止損位計算 if close > context.short_stop_loss: volume_hold = position_short['volume'] if volume_hold >= context.atr_half: order_volume(symbol=symbol, volume=context.atr_half, side=OrderSide_Buy, order_type=OrderType_Market, position_effect=PositionEffect_Close) else: order_volume(symbol=symbol, volume=volume_hold, side=OrderSide_Buy, order_type=OrderType_Market,position_effect=PositionEffect_Close) print('平空倉0.5atr') context.short_add_point += context.atr_half context.short_stop_loss += context.atr_half
if __name__ == '__main__': ''' strategy_id策略ID,由系統(tǒng)生成 filename文件名,請與本文件名保持一致 mode實時模式:MODE_LIVE回測模式:MODE_BACKTEST token綁定計算機的ID,可在系統(tǒng)設(shè)置-密鑰管理中生成 backtest_start_time回測開始時間 backtest_end_time回測結(jié)束時間 backtest_adjust股票復(fù)權(quán)方式不復(fù)權(quán):ADJUST_NONE前復(fù)權(quán):ADJUST_PREV后復(fù)權(quán):ADJUST_POST backtest_initial_cash回測初始資金 backtest_commission_ratio回測傭金比例 backtest_slippage_ratio回測滑點比例 ''' run(strategy_id='strategy_id', filename='main.py', mode=MODE_BACKTEST, token='token', backtest_start_time='2020-02-15 09:15:00', backtest_end_time='2020-09-01 15:00:00', backtest_adjust=ADJUST_PREV, backtest_initial_cash=1000000, backtest_commission_ratio=0.0001, backtest_slippage_ratio=0.0001)
4. 回測結(jié)果與穩(wěn)健性分析設(shè)定初始資金100萬,手續(xù)費率為0.01%,滑點比率為0.01%。回測結(jié)果如下圖所示。
回測期累計收益率為18.75%,年化收益率為6.42%,滬深300收益率為18.75%,策略跑輸滬深300指數(shù)。最大回撤為4.20%,勝率為48.15%。
為了檢驗策略的穩(wěn)健性,改變策略標的和計算唐奇安通道的參數(shù)n,回測結(jié)果如下。
標的 |
唐奇安通道參數(shù) |
年化收益率 |
最大回撤 |
DCE.i2012 |
20 |
6.42% |
4.20% |
DCE.i2012 |
25 |
3.45% |
4.99% |
DCE.i2012 |
30 |
-0.35% |
4.23% |
DCE.m2012 |
20 |
-0.28% |
1.50% |
DCE.m2012 |
25 |
1.19% |
0.52% |
DCE.m2012 |
30 |
1.23% |
0.47% |
SHFE.rb2012 |
20 |
-4.48% |
2.61% |
SHFE.rb2012 |
25 |
-2.80% |
2.84% |
SHFE.rb2012 |
30 |
-3.01% |
2.39% |
由上表可知,不同標的收益結(jié)果呈現(xiàn)差異。其中大商所的鐵礦石收益情況最好,其他兩個品種收益較差,整體收益情況較差。說明該策略在使用上存在一定風(fēng)險。
注:此策略只用于學(xué)習(xí)、交流、演示,不構(gòu)成任何投資建議。
|