海龜交易系統的實現

前言

海龜交易系統本質上是一個趨勢跟隨的系統,可是最值得咱們學習的,是資金管理尤爲是分批建倉及動態止損的部分

1、趨勢捕捉

** 唐奇安通道**

該指標是有Richard Donchian發明的,是有3條不一樣顏色的曲線組成的,該指標用週期(通常都是20)內的最高價和最低價來顯示市場價格的波動性,當其通道窄時表示市場波動較小,反之通道寬則表示市場波動比較大。 如圖所示:react

該具體分析爲:
    當價格沖沖破上軌是就是可能的買的信號;反之,衝破下軌時就是可能的賣的信號。
    該指標的計算方法爲:
           
	上線=Max(最高低,n)
    下線=Min(最低價,n)
    中線=(上線+下線)/2

海龜交易就是利用唐奇安通道的價格突破來捕捉趨勢。api

不過咱們在向下突破10日唐奇安下沿賣出。markdown

2、資金管理

2.一、N值計算

N值是倉位管理的核心,涉及加倉及止損。另外,N值與技術指標平均真實波幅 ATR很類似app

首先介紹真實波幅: 真實波幅是如下三個值中的最大值函數

一、當前交易日最高價和最低價的波幅
    二、前一交易日的收盤價與當前交易日最高價的波幅
	三、前一交易日的收盤價與當前交易日最低價的波幅

用公式寫就是:學習

TrueRange=Max(HighLow,abs(HighPreClose),abs(PreCloseLow))

 

接下來,N值計算公式爲:優化

 

N=PreN[19]+TrueRange20

 

其中 preN爲前面N值,TrueRange爲當前的真實波幅,此公式的真是含義爲計算以前20天(包括今天在內)的N的平均值

另外,有些海龜交易系統用的是ATR來代替N值,ATR爲真實波幅的20日平均。

2.2 買賣單位及首次建倉

先給出公式:ui

Unit=1N

 

首次建倉的時候,當捕捉到趨勢,即價格突破唐奇安上軌時,買入1個unit。atom

其意義就是,讓一個N值的波動與你總資金1%的波動對應,若是買入1unit單位的資產,當天震幅使得總資產的變化不超過1%。例如:spa

如今你有10萬元資金,1%波動就是1000元。假如標X的N值爲0.2元,1000元÷0.2元=5000股。也就是說,你的第一筆倉位應該是在其突破上軌(假設爲5元)時馬上買入5000股,耗資25000元。

2.3 加倉

若股價在上一次買入(或加倉)的基礎上上漲了0.5N,則加倉一個Unit。

接上面的例子:假如N值仍爲0.2。
    價格來到 5 + 0.2*0.5 = 5.1時,加倉1個Unit,買入5000股,耗資25500元,剩餘資金 49500元
    價格來到 5.1 + 0.2*0.5 = 5.2 時再加倉1個unit。買入5000股,耗資26000元,剩餘資金 23500元

2.4 動態止損

當價格比最後一次買入價格下跌2N時,則賣出所有頭寸止損。

接上面的例子,最後一次加倉價格爲5.2。假如此時N值0.2元。 當價格下跌到 5.2 - 2*0.2 = 4.8元時,清倉。
        持倉成本爲 (5+5.1+5.2)*5000/15000 = 5.1元。 此時虧損 (5.1-4.8)*15000 = 4500元 對於10萬來講 這波虧損4.5%

2.5 止盈

當股價跌破10日唐奇安通道下沿,清空頭寸結束本次交易

3、代碼實現

本代碼用ATR代替N值進行計算,其餘邏輯不變:

ATR=MA(TrueRange,20)
咱們以單隻股票爲標,創建海龜交易系統,固然,能夠將總資產均分爲n份,同時交易n個標。

計算ATR值用日線數據,監控價格突破採用分鐘線

0 初始化參數,在initialize(account)寫入

 
 
 
 
 
1
def initialize(account): 
2
    account.last_buy_prcie = 0  #上一次買入價
3
    account.hold_flag = False   # 是否持有頭寸標誌
4
    account.limit_unit = 4     # 限制最多買入的單元數
5
    account.unit = 0       # 如今買入1單元的股數
6
    
 
 
查看所有
 

1 唐奇安通道計算及判斷入場離場:

咱們設計個函數,傳入值爲回測中 account.get_history()取得的某單個股票的歷史數據、股票現價、T爲計算唐奇安通道的數據長度,轉化爲dataframe格式

 
 
 
 
 
1
def IN_OR_OUT(data,price,T):
2
    up = max(data['highPrice'].iloc[-T:])
3
    down = min(data['lowPrice'].iloc[-int(T/2):])  # 這裏是10日唐奇安下沿
4
    if price>up:
5
        return 1
6
    elif price<down:
7
        return -1
8
    else:
9
        return 0 
 
 
查看所有
 

2. ATR值計算:

 
 
 
 
 
1
def CalcATR(data):
2
    TR_List = []
3
    for i in range(1,21):
4
        TR = max(data['highPrice'].iloc[i]-data['lowPrice'].iloc[i],abs(data['highPrice'].iloc[i]-data['closePrice'].iloc[i-1]),abs(data['closePrice'].iloc[i-1]-data['lowPrice'].iloc[i]))
5
        TR_List.append(TR)
6
    ATR = np.array(TR_List).mean()
7
    return ATR
 
 
查看所有
 

3. 計算unit,注意股數爲100的整數倍

 
 
 
 
 
1
def CalcUnit(perValue,ATR):
2
    return int((perValue/ATR)/100)*100
 
 
查看所有
 

4.判斷是否加倉或止損:

當價格相對上個買入價上漲 0.5ATR時,再買入一個unit
    當價格相對上個買入價下跌 2ATR時,清倉
 
 
 
 
 
1
def Add_OR_Stop(price,lastprice,ATR):
2
    if price >= lastprice + 0.5*ATR:
3
        return 1
4
    elif price <= lastprice - 2*ATR:
5
        return -1
6
    else:
7
        return 0
 
 
查看所有
 

5 判斷上次賣出操做是否成功(可能出現當日買進,以後卻判斷須要賣出)

 
 
 
 
 
1
def SellComplete(hold_flag,security_position):
2
    if len(security_position)>0 and hold_flag==False:
3
        return True
4
    else:
5
        return False
 
 
查看所有
 

構建策略

分鐘線回測時間略長啊~

先把上面寫的函數集中下,方便微核充啓後運行函數

 
 
 
 
 
1
###################################################     計算、判斷函數       #####################################################################    
2
def IN_OR_OUT(data,price,T):
3
    up = max(data['highPrice'].iloc[-T:])
4
    down = min(data['lowPrice'].iloc[-int(T/2):])  # 這裏是10日唐奇安下沿
5
    if price>up:
6
        return 1
7
    elif price<down:
8
        return -1
9
    else:
10
        return 0 
11
12
def CalcATR(data):
13
    TR_List = []
14
    for i in range(1,21):
15
        TR = max(data['highPrice'].iloc[i]-data['lowPrice'].iloc[i],abs(data['highPrice'].iloc[i]-data['closePrice'].iloc[i-1]),abs(data['closePrice'].iloc[i-1]-data['lowPrice'].iloc[i]))
16
        TR_List.append(TR)
17
    ATR = np.array(TR_List).mean()
18
    return ATR
19
20
def CalcUnit(perValue,ATR):
21
    return int((perValue/ATR)/100)*100
22
23
def Add_OR_Stop(price,lastprice,ATR):
24
    if price >= lastprice + 0.5*ATR:
25
        return 1
26
    elif price <= lastprice - 2*ATR:
27
        return -1
28
    else:
29
        return 0
30
    
31
def SellComplete(hold_flag,security_position):
32
    if len(security_position)>0 and hold_flag==False:
33
        return True
34
    else:
35
        return False
 
 
查看所有
 
 
 
 
 
 
1
import numpy as np
2
import pandas as pd
3
from __future__ import division
4
from CAL.PyCAL import *
5
import matplotlib.pyplot as plt
6
7
start = '2012-01-01'                       # 回測起始時間
8
end = '2016-01-01'                         # 回測結束時間
9
benchmark = '000001.XSHE'                        
10
universe = ['000001.XSHE']
11
capital_base = 100000                      # 起始資金
12
freq = 'm'                                 # 策略類型,'d'表示日間策略使用日線回測,'m'表示日內策略使用分鐘線回測
13
refresh_rate = 1                           # 調倉頻率,表示執行handle_data的時間間隔,若freq = 'd'時間間隔的單位爲交易日,若freq = 'm'時間間隔爲分鐘
14
15
16
#-----------------------------------     記錄部分數據       -----------------------------
17
global record
18
record = {'break_up':{},'break_down':{},'stop_loss':{},'position':{},'ATR':{}}  # 記錄入場、離常、止損點、持倉比、ATR
19
#---------------------------------------------------------------------------------------
20
21
#******************************************   策略主體   ********************************************
22
23
def initialize(account):                   # 初始化虛擬帳戶狀態
24
    account.last_buy_prcie = 0  #上一次買入價
25
    account.hold_flag = False   # 是否持有頭寸標誌
26
    account.limit_unit = 4     # 限制最多買入的單元數
27
    account.unit = 0       # 如今買入1單元的股數
28
    account.add_time = 0   # 買入次數
29
    
30
31
def handle_data(account):                  # 每一個交易日的買入賣出指令
32
    T = 20
33
    data = account.get_daily_history(T+1)
34
    stk = universe[0]
35
    data = data[stk]
36
    data = pd.DataFrame(data)
37
    prices = account.reference_price[stk]
38
    today = Date.fromDateTime(account.current_date) 
39
    today = today.toISO()
40
    
41
    # 0 若是停牌,直接跳過
42
    if np.isnan(prices) or prices == 0:  # 停牌或是尚未上市等緣由不能交易
43
        return 
44
    
45
    # 1 計算ATR
46
    ATR = CalcATR(data)
47
    record['ATR'].update({today:ATR})
48
    
49
    # 2 判斷上次賣出是否成功,若不成功,再次賣出
50
    if SellComplete(account.hold_flag,account.security_position):
51
        for stk in account.security_position:
52
            order_to(stk,0)
53
            
54
    # 3 判斷加倉或止損
55
    if account.hold_flag==True and len(account.security_position)>0:   # 先判斷是否持倉
56
        temp = Add_OR_Stop(prices,account.last_buy_prcie,ATR)
57
        if temp ==1and account.add_time<account.limit_unit:  # 判斷加倉
58
            order_num = min(account.unit,account.cash)      # 不夠1unit時買入剩下所有
59
            order_to(stk,account.unit)
60
            account.last_buy_prcie = prices
61
            account.add_time += 1
62
        elif temp== -1:      # 判斷止損
63
            order_to(stk,0)
64
            initialize(account)   # 從新初始化參數 very important here!
65
            record['stop_loss'].update({today:prices})
66
            
67
    # 4 判斷入場離場
68
    out = IN_OR_OUT(data,prices,T)
69
    if out ==1 and account.hold_flag==False:  #入場
70
        value = account.reference_portfolio_value * 0.01
71
        account.unit = CalcUnit(value,ATR)
72
        order_to(stk,account.unit)
73
        account.add_time = 1
74
        account.hold_flag = True
75
        account.last_buy_prcie = prices
76
        record['break_up'].update({today:prices})
77
        
78
    elif out==-1 and account.hold_flag ==True: #離場
79
        order_to(stk,0)
80
        initialize(account)   # 從新初始化參數 very important here!
81
        record['break_down'].update({today:prices})
82
        
83
    # 5 計算持倉比
84
    ratio = 1 - account.cash/account.reference_portfolio_value
85
    record['position'].update({today:ratio})  # 雖然每分鐘重算,但由於key是日期,最後覆蓋爲當日最終持倉比
86
    
87
    return
88
 
 
查看所有
  • 年化收益率8.6%
  • 基準年化收益率17.1%
  • 阿爾法2.9%
  • 貝塔0.17
  • 夏普比率0.49
  • 收益波動率10.5%
  • 信息比率-0.43
  • 最大回撤11.8%
  • 年化換手率--
累計收益率策略基準2012-012012-072013-012013-072014-012014-072015-012015-072016-01-50.00%0.00%50.00%100.00%150.00%200.00%2015-03-12策略: 27.17%基準: 85.35%
WARNING: refresh_rate的值僅做用於分鐘線。若想對日線進行控制,請使用以下定義: refresh_rate = (日線refresh_rate, 分鐘線refresh_rate)
 

咱們發現,收益基本上處於階梯狀上升。可是幾年下來收益也並不高,咱們來看看記錄下來的數據,分析下整個過程:

 
 
 
 
 
1
r = pd.DataFrame(record)
2
adj_price = DataAPI.MktEqudAdjGet(secID=u"000001.XSHE",ticker=u"",beginDate='20120101',endDate='20160101',isOpen="",field=u"",pandas="1")
3
adj_price = adj_price.set_index('tradeDate')
 
 
查看所有
 

把圖畫出來:

紅色點爲入場點;
    藍色點爲離場點;
    綠色點位止損點
 
 
 
 
 
1
plt.figure(figsize=(20,10))
2
r['ATR'].plot(label='ATR')
3
adj_price['closePrice'].plot(label='adj price')
4
adj_price['highestPrice'].plot(label='high price')
5
adj_price['lowestPrice'].plot(label='low price')
6
for i in range(len(r)):
7
    plt.plot(i,r['break_up'].iloc[i],'.r',markersize=13)
8
    plt.plot(i,r['break_down'].iloc[i],'.b',markersize=13)
9
    plt.plot(i,r['stop_loss'].iloc[i],'.g',markersize=13)
10
plt.legend(loc=0)
 
 
查看所有
<matplotlib.legend.Legend at 0x468a6c50>

能夠發現:

ATR波形有些異常,有些地方會直線上升。分析後發現:由於quartz 中,account.get_daily_history()取得的最高最低價中,對停牌的狀況處理爲了0!

咱們調整下策略:

在計算ATR時,剔除最高最低爲0的部分,再作平均。
 
 
 
 
 
1
import numpy as np
2
import pandas as pd
3
from __future__ import division
4
from CAL.PyCAL import *
5
import matplotlib.pyplot as plt
6
7
start = '2012-01-01'                       # 回測起始時間
8
end = '2016-01-01'                         # 回測結束時間
9
benchmark = '000001.XSHE'                        
10
universe = ['000001.XSHE']
11
capital_base = 100000                      # 起始資金
12
freq = 'm'                                 # 策略類型,'d'表示日間策略使用日線回測,'m'表示日內策略使用分鐘線回測
13
refresh_rate = 1                           # 調倉頻率,表示執行handle_data的時間間隔,若freq = 'd'時間間隔的單位爲交易日,若freq = 'm'時間間隔爲分鐘
14
15
16
#-----------------------------------     記錄部分數據       -----------------------------
17
global record
18
record = {'break_up':{},'break_down':{},'stop_loss':{},'position':{},'ATR':{}}  # 記錄入場、離常、止損點、持倉比、ATR
19
#---------------------------------------------------------------------------------------
20
21
22
#******************************************   策略主體   ********************************************
23
24
def initialize(account):                   # 初始化虛擬帳戶狀態
25
    account.last_buy_prcie = 0  #上一次買入價
26
    account.hold_flag = False   # 是否持有頭寸標誌
27
    account.limit_unit = 4     # 限制最多買入的單元數
28
    account.unit = 0       # 如今買入1單元的股數
29
    account.add_time = 0   # 買入次數
30
    
31
32
def handle_data(account):                  # 每一個交易日的買入賣出指令
33
    T = 20
34
    data = account.get_daily_history(T+1)
35
    stk = universe[0]
36
    data = data[stk]
37
    #---------------------- 修改部分 ----------------------
38
    data = pd.DataFrame(data)
39
    data['highPrice']  = data['highPrice'].replace(0,np.nan)
40
    data = data.dropna()
41
    if len(data)<T+1:
42
        delta = T+1 - len(data)
43
        m = T+1+delta
44
        while delta > 0:   # 直到取滿20個不爲停牌的數據
45
            m += delta
46
            data = account.get_daily_history(m)
47
            data = data[stk]
48
            data = pd.DataFrame(data)
49
            data['highPrice']  = data['highPrice'].replace(0,np.nan)
50
            data = data.dropna()
51
            delta = T+1 - len(data)
52
    #---------------------------------------------------------
53
    prices = account.reference_price[stk]
54
    today = Date.fromDateTime(account.current_date) 
55
    today = today.toISO()
56
    
57
    # 0 若是停牌,直接跳過
58
    if np.isnan(prices) or prices == 0:  # 停牌或是尚未上市等緣由不能交易
59
        return 
60
    
61
    # 1 計算ATR
62
    ATR = CalcATR(data)
63
    record['ATR'].update({today:ATR})
64
    
65
    # 2 判斷上次賣出是否成功,若不成功,再次賣出
66
    if SellComplete(account.hold_flag,account.security_position):
67
        for stk in account.security_position:
68
            order_to(stk,0)
69
            
70
    # 3 判斷加倉或止損
71
    if account.hold_flag==True and len(account.security_position)>0:   # 先判斷是否持倉
72
        temp = Add_OR_Stop(prices,account.last_buy_prcie,ATR)
73
        if temp ==1and account.add_time<account.limit_unit:  # 判斷加倉
74
            order_num = min(account.unit,account.cash)      # 不夠1unit時買入剩下所有
75
            order_to(stk,account.unit)
76
            account.last_buy_prcie = prices
77
            account.add_time += 1
78
        elif temp== -1:      # 判斷止損
79
            order_to(stk,0)
80
            initialize(account)   # 從新初始化參數 very important here!
81
            record['stop_loss'].update({today:prices})
82
            
83
    # 4 判斷入場離場
84
    out = IN_OR_OUT(data,prices,T)
85
    if out ==1 and account.hold_flag==False:  #入場
86
        value = account.reference_portfolio_value * 0.01
87
        account.unit = CalcUnit(value,ATR)
88
        order_to(stk,account.unit)
89
        account.add_time = 1
90
        account.hold_flag = True
91
        account.last_buy_prcie = prices
92
        record['break_up'].update({today:prices})
93
        
94
    elif out==-1 and account.hold_flag ==True: #離場
95
        order_to(stk,0)
96
        initialize(account)   # 從新初始化參數 very important here!
97
        record['break_down'].update({today:prices})
98
        
99
    # 5 計算持倉比
100
    ratio = 1 - account.cash/account.reference_portfolio_value
101
    record['position'].update({today:ratio})  # 雖然每分鐘重算,但由於key是日期,最後覆蓋爲當日最終持倉比
102
    
103
    return
 
 
查看所有
  • 年化收益率8.7%
  • 基準年化收益率17.1%
  • 阿爾法2.9%
  • 貝塔0.17
  • 夏普比率0.48
  • 收益波動率10.9%
  • 信息比率-0.43
  • 最大回撤11.8%
  • 年化換手率--
累計收益率策略基準2012-012012-072013-012013-072014-012014-072015-012015-072016-01-50.00%0.00%50.00%100.00%150.00%200.00%
WARNING: refresh_rate的值僅做用於分鐘線。若想對日線進行控制,請使用以下定義: refresh_rate = (日線refresh_rate, 分鐘線refresh_rate)
 

累計收益相差很少,咱們再來看看記錄的數據。

紅色點爲入場點;
    藍色點爲離場點;
    綠色點位止損點
 
 
 
 
 
1
r = pd.DataFrame(record)
2
adj_price = DataAPI.MktEqudAdjGet(secID=u"000001.XSHE",ticker=u"",beginDate='20120101',endDate='20160101',isOpen="",field=u"",pandas="1")
3
adj_price = adj_price.set_index('tradeDate')
4
adj_price['highestPrice'] = adj_price['highestPrice'].replace(0,np.nan)
5
adj_price['lowestPrice'] = adj_price['lowestPrice'].replace(0,np.nan)
6
7
plt.figure(figsize=(20,10))
8
r['ATR'].plot(label='ATR')
9
adj_price['closePrice'].plot(label='adj price')
10
adj_price['highestPrice'].plot(label='high price')
11
adj_price['lowestPrice'].plot(label='low price')
12
for i in range(len(r)):
13
    plt.plot(i,r['break_up'].iloc[i],'.r',markersize=13)
14
    plt.plot(i,r['break_down'].iloc[i],'.b',markersize=13)
15
    plt.plot(i,r['stop_loss'].iloc[i],'.g',markersize=13)
16
plt.legend(loc=0)
 
 
查看所有
<matplotlib.legend.Legend at 0x54b4f490>
  • 此次發現,ATR波形比較正常,在波動劇烈的時候增大。

  • 觀察入場、離場、止損點發現,海龜交易系統捕捉到了大的上漲趨勢,在震盪市中不斷試錯止損。

  • 上漲過程當中出現回調容易震出,減小了回撤的同時也減少了收益。

再看看倉位狀況

 
 
 
 
 
1
r['position'].plot(kind='bar',figsize=(200,5))
 
 
查看所有
<matplotlib.axes._subplots.AxesSubplot at 0xb28f9cd0>
  • 能夠發現,大部分持有狀況下倉位在0.5左右,甚至低於半倉,少數高於半倉的狀況最高不超過0.8。所以,收益不高也是正常了。

總結

  • 本文主要介紹了海龜交易的細節,不過是面向一個投資目標的。當想投多隻股票時,能夠先設定幾個坑位,平分資金,而後對每一個坑位採用海龜交易策略。

  • 海龜交易系統一般會用兩個趨勢捕捉系統,不一樣之處在於價格突破的上下線計算。系統1:突破上線20日最高買,突破下線10日最低賣;系統2:突破上線55日最高買,突破下線20日最低賣。 這部分能夠經過修改參數實現。

  • 原始的海龜交易採用唐奇安通道來捕捉趨勢,雖然能捕捉到大趨勢,可是在震盪的狀況下表現不如人意,不過這也是全部趨勢型策略的通病。

  • 海龜交易策略的核心在於資金管理,能夠看出策略的回撤比較小,而且還有優化的空間。資金管理不必定要與趨勢型策略結合,是否是能夠用到多因子策略上?動量反轉?均值迴歸?這些就留給讀者們自行嘗試了~

相關文章
相關標籤/搜索