必威体育Betway必威体育官网
当前位置:首页 > IT技术

海龟交易系统的实现

时间:2019-09-29 09:45:56来源:IT技术作者:seo实验室小编阅读:71次「手机版」
 

海龟交易

海龟交易系统的实现

前言

海龟交易系统本质上是一个趋势跟随的系统,但是最值得我们学习的,是资金管理尤其是分批建仓及动态止损的部分

一、趋势捕捉

** 唐奇安通道**

指标是有Richard Donchian发明的,是有3条不同颜色的曲线组成的,该指标用周期(一般都是20)内的最高价和最低价来显示市场价格的波动性,当其通道窄时表示市场波动较小,反之通道宽则表示市场波动比较大。 如图所示:

    该具体分析为:
    当价格冲冲破上轨是就是可能的买的信号;反之,冲破下轨时就是可能的卖的信号。
    该指标的计算方法为:
           
	上线=Max(最高低,n)
    下线=Min(最低价,n)
    中线=(上线+下线)/2

海龟交易就是利用唐奇安通道的价格突破来捕捉趋势。

不过我们在向下突破10日唐奇安下沿卖出。

二、资金管理

2.1、N值计算

N值是仓位管理的核心,涉及加仓及止损。另外,N值与技术指标平均真实波幅 ATR很相似

首先介绍真实波幅: 真实波幅是以下三个值中的最大值

	1、当前交易日最高价和最低价的波幅
    2、前一交易日的收盘价与当前交易日最高价的波幅
	3、前一交易日的收盘价与当前交易日最低价的波幅

用公式写就是:

TrueRange=Max(High−Low,abs(High−PreClose),abs(PreClose−Low))

接下来,N值计算公式为:

N=PreN[−19:]+TrueRange20

    其中 preN为前面N值,TrueRange为当前的真实波幅,此公式的真是含义为计算之前20天(包括今天在内)的N的平均值

另外,有些海龟交易系统用的是ATR来代替N值,ATR为真实波幅的20日平均。

2.2 买卖单位及首次建仓

先给出公式:

Unit=1N

首次建仓的时候,当捕捉到趋势,即价格突破唐奇安上轨时,买入1个unit。

其意义就是,让一个N值的波动与你总资金1%的波动对应,如果买入1unit单位的资产,当天震幅使得总资产的变化不超过1%。例如:

		现在你有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日唐奇安通道下沿,清空头寸结束本次交易

三、代码实现

本代码用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 pision

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 pision

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日最低卖。 这部分可以通过修改参数实现。

  • 原始的海龟交易采用唐奇安通道来捕捉趋势,虽然能捕捉到大趋势,但是在震荡的情况下表现不如人意,不过这也是所有趋势型策略的通病。

  • 海龟交易策略的核心在于资金管理,可以看出策略的回撤比较小,并且还有优化空间。资金管理不一定要与趋势型策略结合,是不是可以用到多因子策略上?动量反转?均值回归?这些就留给读者们自行尝试了~

posted @ 2019-05-20 16:09 独爱米粒 阅读(...) 评论(...) 编辑 收藏

相关阅读

分享到:

栏目导航

推荐阅读

热门阅读