当前位置:   article > 正文

Python 算法交易实验68 回测对象重构

Python 算法交易实验68 回测对象重构

说明

从有这个想法,到勉强完工,整个过程还是持续了很长时间。主要原因有:

  • 1 去年12月没有遵守【原则】,手工乱下了一堆单子,然后满仓了。等这些单子“解冻”估计还要一阵子,所以也没有很急。
  • 2 在做的过程中,想做一些工具层面的升级,所以会花时间做一些依赖服务。

最后觉得还是要尽快完成一版,所以才想写本篇文章。
在这个版本中,不去考虑回撤、或者平均模型分的问题。

内容

1 样例数据

假设是分钟数据,但我想甚至是按天的数据可能也行。嗯,后续可以试一下天级别数据建模。有时候我也在想,是不是我一开始把问题搞的过于复杂了,分钟级别的判断是否需要?

言归正传,还是回到分钟级数据。未来,等到国内允许(非常简单的,普遍的那种)用接口进行交易,那么其实还是要用分钟级数据的。秒级的倒真没必要,又不搞高频。

# 排序好的
rec_data_list = [{'data_dt': '2013-03-15 09:31:00',
'open': 3.03,
'close': 2.9,
'high': 3.04,
'low': 2.8,
'decision_score':111
},
{'data_dt': '2013-03-15 09:32:00',
'open': 3.13,
'close': 3.0,
'high': 3.14,
'low': 2.9,
'decision_score':610}
,
{'data_dt': '2013-03-15 09:33:00',
'open': 3.13,
'close': 3.0,
'high': 3.14,
'low': 2.9,
'decision_score':300
},
{'data_dt': '2013-03-16 09:33:00',
'open': 3.13,
'close': 3.0,
'high': 3.14,
'low': 2.9,
'decision_score':300
},
{'data_dt': '2013-03-16 13:33:00',
'open': 3.13,
'close': 3.0,
'high': 3.14,
'low': 2.9,
'decision_score':601
},
{'data_dt': '2013-03-20 13:33:00',
'open': 3.13,
'close': 3.0,
'high': 3.14,
'low': 2.9,
'decision_score':601
},
{'data_dt': '2013-03-20 14:33:00',
'open': 3.13,
'close': 3.0,
'high': 3.14,
'low': 2.9,
'decision_score':601
},
{'data_dt': '2013-03-21 14:33:00',
'open': 3.13,
'close': 13.0,
'high': 3.14,
'low': 2.9,
'decision_score':601
},
{'data_dt': '2013-03-21 14:59:00',
'open': 3.13,
'close': 13.1,
'high': 3.14,
'low': 2.9,
'decision_score':610
},
{'data_dt': '2013-03-22 14:00:00',
'open': 3.13,
'close': 0.1,
'high': 3.14,
'low': 2.9,
'decision_score':610
}

]

rec_dt_list = ['2013-03-15 09:31:00','2013-03-15 09:32:00','2013-03-15 09:33:00',
'2013-03-16 09:33:00','2013-03-16 13:33:00','2013-03-20 13:33:00',
'2013-03-20 14:33:00','2013-03-21 14:33:00','2013-03-21 14:59:00',
'2013-03-22 14:00:00']

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79

默认的策略参数,初始资本6000,每单金额5000,最大容许资产损失10%,最大持有天数为3。
买入的分值下限是600,卖出的分值上限是500。
费率设置为千5,订单的盈利终止是1个点,损失终止是2个点。

strategy_para = {
'init_cap': 6000,
'per_order_amt':5000,
'max_cap_loss_rate': -0.1,
'max_hold_order_num':1,
'max_order_hold_days':3,
'model_singal_score_buy':600,
'model_singal_score_sell':500,
'fee_rate': 0.005,
'etf_code': '510300',
'order_win_stop_rate':0.01,
'order_loss_stop_rate': -0.02
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

2 对象

对象依赖于两个服务:

  • 1 global_buffer: 这个服务提供了一个简单的访问redis变量的方法,提供set和get两种方法。一个核心的应用是访问/维护一个全局变量,限制在一段时间内可用的资金总量。这个服务未来可被更多的程序使用,提供全局缓存(元数据)。
  • 2 gfgo_lite: 这个服务提供了参数化的函数处理服务。在这里,用于计算时间间隔,例如订单的最长持久时间和最短禁止售卖时间。gfgo_lite的时间处理方式不同于time或者datetime这样的包,而是采用字典查询+偏移推算的方法,速度要快的多。而这个服务更大的作用在于确保global_func这个庞大而理想化的项目可以持续下去,最终实现完全的参数化调用函数以及弹性拓展算力的目的。再往后则是Agent相关的内容。

对象使用了transitions包的有限状态机模块,从而使各状态的变化更加清晰。虽然最后并不是理想的结果,但是也似乎可用。

另外本想在规则这块想写的高级点的,但是,就这样吧…

import copy
import requests as req 
import numpy as np 
import pandas as pd 
wan_ip ='****'

class BackTest2:
    def __init__(self,name = None, global_buffer_ip = None, space_name = None):
        # 元数据 flip步需要基于数据,更新元数据  ~ update meta | data
        self.meta = {}
        # 数据 flop步需要基于元数据,作出action ~ update data | meta
        self.data = {}
        # 行动,也就是变化
        self.action = None
        # 全局缓存ip
        self.global_buffer_ip = global_buffer_ip
        self.space_name  = space_name
        self.close_orders = []

    # 接受策略参数,并进行初始化
    def _init_para(self, para_dict = None):
        '''
        初始资本 init_cap
        每笔交易金额(初始资本 > 每笔交易金额 * (1+资本回撤率)) per_order_amt
        最大资本亏损率 max_cap_loss_rate
        最大持有订单 max_hold_order_num
        最大订单持有(交易)时隙 max_order_hold_trade_slots
        订单买入模型分下限 model_singal_score_buy
        订单卖出模型分上限 model_singal_score_sell
        '''
        for x in ['init_cap', 'per_order_amt','max_cap_loss_rate','max_hold_order_num','max_order_hold_days',
        'model_singal_score_buy','model_singal_score_sell','etf_code','order_win_stop_rate','order_loss_stop_rate' ] :
            self.meta[x] = para_dict[x]
        self.meta['cash'] = para_dict['init_cap']
        self.meta['hold'] = 0
    # 获取全局数据
    # BT2同时还需要收到全局参数的控制;这意味着若干BT2可以同时进行测试
    def _get_global_control_meta(self,global_buffer_ip = None, space_name = None,varname = None):
        # flip
        global_buffer_ip = global_buffer_ip or self.global_buffer_ip
        space_name = space_name or self.space_name
        para_dict  ={'space':space_name,
            'varname':varname,
            'ttl': 86400 # 可以不写,默认86400
        }
        return req.post('http://%s:24088/getx/' % global_buffer_ip,json = para_dict).json()

    def _set_global_control_meta(self,global_buffer_ip = None, space_name = None,varname = None, value = None,persist='yes'):
        # flip
        global_buffer_ip = global_buffer_ip or self.global_buffer_ip
        space_name = space_name or self.space_name
        para_dict  ={'space':space_name,
            'varname':varname,
            'value':value,
            'ttl': 86400, # 
            'persist':persist
        }
        return req.post('http://%s:24088/setx/' % global_buffer_ip,json = para_dict).json()

    
    def _get_time_gap(self,global_buffer_ip = None,start_dt = None, end_dt =None, time_unit = None,bias_hours = -8):

        global_buffer_ip = global_buffer_ip or self.global_buffer_ip
        some_dict = {}
        some_dict['start_dt'] = start_dt
        some_dict['end_dt'] = end_dt
        some_dict['time_unit'] = time_unit
        some_dict['bias_hours'] = bias_hours
        res = req.post('http://%s:24090/time_gap/' % global_buffer_ip, json = some_dict).json()
        return res 

    # 从外部获取数据
    def get_data(self,dt_list = None, rec_list = None):
        last_dt = self.meta.get('last_dt') or ''
        # 找到第一个大于 last_dt 的日期的索引
        pos = np.argwhere(np.array(dt_list) > last_dt)

        # 如果找到了满足条件的日期
        if len(pos) > 0:
            # 获取索引的第一个元素
            pos = pos[0][0]
            # 使用索引获取相应的日期和记录
            dt = dt_list[pos]
            rec = rec_list[pos]
            print("Date:", dt)
            print("Record:", rec)
            return  dt, rec
        else:
            print("No date found after", last_dt)
            return None 

    # 买
    def _buy(self, price = None, dt = None):
        per_order_amt = self.meta['per_order_amt']
        stocks = int(per_order_amt /(100 * price)) * 100
        self.data['code'] = self.meta['etf_code']
        self.data['buy_price'] = price 
        self.data['buy_dt'] = dt 
        self.data['stocks'] = stocks
        self.data['buy_amt'] = price * stocks

        self.meta['cash'] = self.meta['cash'] - self.data['buy_amt'] 
        self.meta['hold'] = self.data['buy_amt']


        # 改动全局总量
        monthly_quota = self._get_global_control_meta(varname='monthly_quota')
        monthly_quota -= self.data['buy_amt'] 
        self._set_global_control_meta(varname='monthly_quota', value=monthly_quota)


        return True

    # 卖
    def _sell(self, price = None, dt = None, fee=0.005):
        self.data['sell_price'] = price 
        self.data['sell_dt'] = dt 
        self.data['sell_amt'] = price * self.data['stocks']
        self.data['gp'] = self.data['sell_amt'] - self.data['buy_amt']
        self.data['np'] = self.data['sell_amt'] * (1-fee) - self.data['buy_amt']
        self.data['npr'] = round(self.data['np']/self.data['buy_amt'],4)
        
        self.meta['cash'] = self.meta['cash'] + self.data['sell_amt'] * (1-fee)
        self.meta['hold'] = 0

        monthly_quota = self._get_global_control_meta(varname='monthly_quota')
        monthly_quota += self.data['buy_amt'] 
        self._set_global_control_meta(varname='monthly_quota', value=monthly_quota)

        self.close_orders.append(copy.deepcopy(self.data))
        self.data = {}

        return True

    # 规则集将会直接更改元数据
    def ruleset(self, data = None):
        # input set : 收盘价,模型分,时间 |  init_cap , per_order_amt ,max_cap_loss_rate, max_hold_order_num ,max_order_hold_trade_slots , model_singal_score_buy , model_singal_score_sell
        # -9, 
        close = data['close']
        dt = data['data_dt']
        decision_score = data['decision_score']
        cash = self.meta['cash']
        init_cap = self.meta['init_cap']
        model_singal_score_buy = self.meta['model_singal_score_buy']
        max_order_hold_days = self.meta['max_order_hold_days']
        model_singal_score_sell = self.meta['model_singal_score_sell']
        order_win_stop_rate = self.meta['order_win_stop_rate']
        order_loss_stop_rate = self.meta['order_loss_stop_rate']
        max_cap_loss_rate = self.meta['max_cap_loss_rate']
        per_order_amt = self.meta['per_order_amt']
        # 空仓
        if self.state == 'Init':
            the_event = 'unchange'
            self.trigger(the_event)
            return True

        if self.state.startswith('E'):
            
            # 如果收到全局控制,就不能再买入
            monthly_quota = self._get_global_control_meta(varname='monthly_quota')
            if monthly_quota < per_order_amt:
                self.trigger('unchange')
                return True
            # 判断买卖
            if decision_score >= model_singal_score_buy:
                the_event = 'buy'
                self._buy(price =close, dt = dt )
            else:
                the_event = 'unchange'
            self.trigger(the_event)
            return True


        if self.state.startswith('H'):

            hold_value = close * self.data['stocks']
            self.meta['hold'] = hold_value

            cur_cap = cash + hold_value
            rate = (cur_cap - init_cap)/init_cap
            print('rate: ',rate)

            # 判断涨跌

            if rate >=-0.03 and rate <0.03:
                if self.state in ['HL1','HL2']:
                    the_event ='up'
                elif self.state in ['HW1','HW2']:
                    the_event = 'down'
                else:
                    the_event = 'unchange' 
            elif rate >=-0.09 and rate <-0.03:
                if self.state in ['HB','HW1','HW2']:
                    the_event  = 'down'
                elif self.state in ['HL2']:
                    the_event = 'up'
                else:
                    the_event = 'unchange' 
            elif rate <-0.09:
                if self.state in ['HL1','HB','HW1','HW2']:
                    the_event = 'down'
                else:
                    the_event = 'unchange'
            elif rate >=0.03 and rate <0.09:
                if self. state in ['HL2','HL1','HB']:
                    the_event = 'up'
                elif self.state in ['HW2']:
                    the_event = 'down'
                else:
                    the_event = 'unchange'
            else:
                if self.state in ['HL2','HL1','HB','HW1']:
                    the_event = 'up'
                else:
                    the_event = 'unchange'

            self.trigger(the_event)

            # 当资产损失超过阈值,会被停止
            if rate < max_cap_loss_rate:
                self._sell(price = close, dt =dt)
                self.trigger('stop')
                return True
            
            # 判断买卖
            ## 时间限制
            buy_dt = self.data['buy_dt']
            time_gap = self._get_time_gap(start_dt =buy_dt ,end_dt =dt , time_unit ='hours')
            if time_gap < 8 :
                the_event = 'unchange'
                return True          
            else:
                if time_gap/24 >= max_order_hold_days:
                    self._sell(price = close, dt =dt)
                    self.trigger('sell')
                    print('a')
                else:
                    # 模型控制
                    if decision_score < model_singal_score_sell:
                        self._sell(price = close, dt =dt)
                        self.trigger('sell')
                        print('b')
                    # 订单交易控制
                    else:
                        order_float_rev = (close - self.data['buy_price'])/self.data['buy_price']
                        if order_float_rev >= order_win_stop_rate:
                            self._sell(price = close, dt =dt)
                            self.trigger('sell')
                            print('c')
                        elif order_float_rev < order_loss_stop_rate:
                            self._sell(price = close, dt =dt)
                            self.trigger('sell')
                            print('d')
                        else:
                            self.trigger('unchange')
        return True                    


# B是0+-3个点, W1是 6+-3个点,W2是大于9个点
# 11个状态
states = ['Init','EB','HB','HW1','HW2','EW1','EW2','HL1','HL2','EL1','EL2','Stop']
transitions = [
    # unchange事件
    {'trigger': 'unchange', 'source': 'Init', 'dest': 'EB'},
    {'trigger': 'unchange', 'source': 'EB', 'dest': 'EB'},
    {'trigger': 'unchange', 'source': 'HB', 'dest': 'HB'},
    {'trigger': 'unchange', 'source': 'HW1', 'dest': 'HW1'},
    {'trigger': 'unchange', 'source': 'HW2', 'dest': 'HW2'},
    {'trigger': 'unchange', 'source': 'EW1', 'dest': 'EW1'},
    {'trigger': 'unchange', 'source': 'EW2', 'dest': 'EW2'},
    {'trigger': 'unchange', 'source': 'HL1', 'dest': 'HL1'},
    {'trigger': 'unchange', 'source': 'HL2', 'dest': 'HL2'},
    {'trigger': 'unchange', 'source': 'EL1', 'dest': 'EL1'},
    {'trigger': 'unchange', 'source': 'EL2', 'dest': 'EL2'},
    # up事件:比上一个level高3个点, Init有一个InitCap,约定 B_center = InitCap,  B的Band定为3个点, B+3pt =  W1的下界以此类推
    {'trigger': 'up', 'source': 'Init', 'dest': 'EB'},
    # up对E无影响
    {'trigger': 'up', 'source': 'EB', 'dest': 'EB'},
    {'trigger': 'up', 'source': 'EW1', 'dest': 'EW1'},
    {'trigger': 'up', 'source': 'EW2', 'dest': 'EW2'},
    {'trigger': 'up', 'source': 'EL1', 'dest': 'EL1'},
    {'trigger': 'up', 'source': 'EL2', 'dest': 'EL2'},
    # up对H有影响
    {'trigger': 'up', 'source': 'HB', 'dest': 'HW1'},
    {'trigger': 'up', 'source': 'HW1', 'dest': 'HW2'},
    {'trigger': 'up', 'source': 'HW2', 'dest': 'Stop'},
    {'trigger': 'up', 'source': 'HL1', 'dest': 'HB'},
    {'trigger': 'up', 'source': 'HL2', 'dest': 'HL1'},    
    
    
    # down事件:类似up事件
    {'trigger': 'down', 'source': 'Init', 'dest': 'EB'},
    
    {'trigger': 'down', 'source': 'EB', 'dest': 'EB'},
    {'trigger': 'down', 'source': 'EW1', 'dest': 'EW1'},
    {'trigger': 'down', 'source': 'EW2', 'dest': 'EW2'},
    {'trigger': 'down', 'source': 'EL1', 'dest': 'EL1'},
    {'trigger': 'down', 'source': 'EL2', 'dest': 'EL2'},
    # down对H有影响
    {'trigger': 'down', 'source': 'HB', 'dest': 'HL1'},
    {'trigger': 'down', 'source': 'HW1', 'dest': 'HB'},
    {'trigger': 'down', 'source': 'HW2', 'dest': 'HW1'},
    {'trigger': 'down', 'source': 'HL1', 'dest': 'EL2'},
    {'trigger': 'down', 'source': 'HL2', 'dest': 'Stop'},    
    
    # buy事件,仅对E类生效
    {'trigger': 'buy', 'source': 'EB', 'dest': 'HB'},
    {'trigger': 'buy', 'source': 'EW1', 'dest': 'HW1'},
    {'trigger': 'buy', 'source': 'EW2', 'dest': 'HW2'},
    {'trigger': 'buy', 'source': 'EL1', 'dest': 'HL1'},
    {'trigger': 'buy', 'source': 'EL2', 'dest': 'HL2'},
    # sell事件,仅对H类生效
    {'trigger': 'sell', 'source': 'HB', 'dest': 'EB'},
    {'trigger': 'sell', 'source': 'HW1', 'dest': 'EW1'},
    {'trigger': 'sell', 'source': 'HW2', 'dest': 'EW2'},
    {'trigger': 'sell', 'source': 'HL1', 'dest': 'EL1'},
    {'trigger': 'sell', 'source': 'HL2', 'dest': 'EL2'}, 
    # stop事件
    {'trigger': 'stop', 'source': 'Init', 'dest': 'Stop'},
    {'trigger': 'stop', 'source': 'HB', 'dest': 'Stop'}, 
    {'trigger': 'stop', 'source': 'HL1', 'dest': 'Stop'},
    {'trigger': 'stop', 'source': 'HL2', 'dest': 'Stop'},
    {'trigger': 'stop', 'source': 'HW1', 'dest': 'Stop'},
    {'trigger': 'stop', 'source': 'HW2', 'dest': 'Stop'},
    {'trigger': 'stop', 'source': 'EB', 'dest': 'Stop'},
    {'trigger': 'stop', 'source': 'EL1', 'dest': 'Stop'},
    {'trigger': 'stop', 'source': 'EL2', 'dest': 'Stop'},
    {'trigger': 'stop', 'source': 'EW1', 'dest': 'Stop'},
    {'trigger': 'stop', 'source': 'EW2', 'dest': 'Stop'},
    # init事件
    {'trigger': 'init', 'source': 'Stop', 'dest': 'Init'},   
]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332

3 使用测试

先使用状态机对对象进行封装

from transitions import Machine
# 创建状态机
machine = Machine(model=BackTest2, states=states, transitions=transitions, initial='Init')

bt2 = BackTest2(name='bt2',global_buffer_ip = wan_ip, space_name ='sp_qtv.bt001')
bt2._init_para(para_dict = strategy_para)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

开始逐次运行测试,在实际使用时没个时隙唤起处理,然后再对具体的功能微调就可以了

res_tuple = bt2.get_data(dt_list = rec_dt_list, rec_list=rec_data_list)
if res_tuple is not None:
    print(bt2.meta['cash'] , bt2.meta['hold'])
    print(bt2.state)
    bt2.meta['last_dt'] = res_tuple[0]
    cur_data = res_tuple[1]
    bt2.ruleset(data=cur_data)

else:
    print('next block')

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

测试1:测试模型分到达是否买入 9:32

Date: 2013-03-15 09:32:00
Record: {'data_dt': '2013-03-15 09:32:00', 'open': 3.13, 'close': 3.0, 'high': 3.14, 'low': 2.9, 'decision_score': 610}
6000 0
EB

In [155]: bt2.data
Out[155]:
{'code': '510300',
 'buy_price': 3.0,
 'buy_dt': '2013-03-15 09:32:00',
 'stocks': 1600,
 'buy_amt': 4800.0}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

测试2:测试在8小时内,模型是否会hold住9没有在9:33卖出

Date: 2013-03-15 09:33:00
Record: {'data_dt': '2013-03-15 09:33:00', 'open': 3.13, 'close': 3.0, 'high': 3.14, 'low': 2.9, 'decision_score': 300}
1200.0 4800.0
HB
rate:  0.0

In [157]: bt2.data
Out[157]:
{'code': '510300',
 'buy_price': 3.0,
 'buy_dt': '2013-03-15 09:32:00',
 'stocks': 1600,
 'buy_amt': 4800.0}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

测试3:测试在8小时后,达到模型卖出分是否会卖出2013-03-16 09:33:00

Date: 2013-03-16 09:33:00
Record: {'data_dt': '2013-03-16 09:33:00', 'open': 3.13, 'close': 3.0, 'high': 3.14, 'low': 2.9, 'decision_score': 300}
1200.0 4800.0
HB
rate:  0.0
b

In [159]: bt2.data
Out[159]: {}

In [160]: bt2.close_orders
Out[160]:
[{'code': '510300',
  'buy_price': 3.0,
  'buy_dt': '2013-03-15 09:32:00',
  'stocks': 1600,
  'buy_amt': 4800.0,
  'sell_price': 3.0,
  'sell_dt': '2013-03-16 09:33:00',
  'sell_amt': 4800.0,
  'gp': 0.0,
  'np': -24.0,
  'npr': -0.005}]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

测试4:测试卖出当日是否会再次买入2013-03-16 13:33:00

Date: 2013-03-16 13:33:00
Record: {'data_dt': '2013-03-16 13:33:00', 'open': 3.13, 'close': 3.0, 'high': 3.14, 'low': 2.9, 'decision_score': 601}
5976.0 0
EB
In [162]: bt2.data
Out[162]:
{'code': '510300',
 'buy_price': 3.0,
 'buy_dt': '2013-03-16 13:33:00',
 'stocks': 1600,
 'buy_amt': 4800.0}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

测试5:测试模型达到持有时间上限后是否会卖出 2013-03-20 13:33:00

Date: 2013-03-20 13:33:00
Record: {'data_dt': '2013-03-20 13:33:00', 'open': 3.13, 'close': 3.0, 'high': 3.14, 'low': 2.9, 'decision_score': 601}
1176.0 4800.0
HB
rate:  -0.004
a

In [164]: bt2.data
Out[164]: {}

In [165]: bt2.close_orders
Out[165]:
[{'code': '510300',
  'buy_price': 3.0,
  'buy_dt': '2013-03-15 09:32:00',
  'stocks': 1600,
  'buy_amt': 4800.0,
  'sell_price': 3.0,
  'sell_dt': '2013-03-16 09:33:00',
  'sell_amt': 4800.0,
  'gp': 0.0,
  'np': -24.0,
  'npr': -0.005},
 {'code': '510300',
  'buy_price': 3.0,
  'buy_dt': '2013-03-16 13:33:00',
  'stocks': 1600,
  'buy_amt': 4800.0,
  'sell_price': 3.0,
  'sell_dt': '2013-03-20 13:33:00',
  'sell_amt': 4800.0,
  'gp': 0.0,
  'np': -24.0,
  'npr': -0.005}]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

测试6:在突然疯狂增长时,其状态可能不准确 2013-03-21 14:33:00 (EW1 - EW2)

Date: 2013-03-20 14:33:00
Record: {'data_dt': '2013-03-20 14:33:00', 'open': 3.13, 'close': 3.0, 'high': 3.14, 'low': 2.9, 'decision_score': 601}
5952.0 0
EB

In [169]: bt2.data
Out[169]:
{'code': '510300',
 'buy_price': 3.0,
 'buy_dt': '2013-03-20 14:33:00',
 'stocks': 1600,
 'buy_amt': 4800.0}

同时可以看到全局资金也变少了
In [168]: bt2._get_global_control_meta(varname='monthly_quota')
Out[168]: 95200.0
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

测试7:止盈卖出(从打印c可以看到是订单本身的止盈卖出)

In [173]: bt2.close_orders
Out[173]:
[{'code': '510300',
  'buy_price': 3.0,
  'buy_dt': '2013-03-15 09:32:00',
  'stocks': 1600,
  'buy_amt': 4800.0,
  'sell_price': 3.0,
  'sell_dt': '2013-03-16 09:33:00',
  'sell_amt': 4800.0,
  'gp': 0.0,
  'np': -24.0,
  'npr': -0.005},
 {'code': '510300',
  'buy_price': 3.0,
  'buy_dt': '2013-03-16 13:33:00',
  'stocks': 1600,
  'buy_amt': 4800.0,
  'sell_price': 3.0,
  'sell_dt': '2013-03-20 13:33:00',
  'sell_amt': 4800.0,
  'gp': 0.0,
  'np': -24.0,
  'npr': -0.005},
 {'code': '510300',
  'buy_price': 3.0,
  'buy_dt': '2013-03-20 14:33:00',
  'stocks': 1600,
  'buy_amt': 4800.0,
  'sell_price': 13.0,
  'sell_dt': '2013-03-21 14:33:00',
  'sell_amt': 20800.0,
  'gp': 16000.0,
  'np': 15896.0,
  'npr': 3.3117}]

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

测试8:订单止损卖出

Date: 2013-03-21 14:59:00
Record: {'data_dt': '2013-03-21 14:59:00', 'open': 3.13, 'close': 13.1, 'high': 3.14, 'low': 2.9, 'decision_score': 610}
21848.0 0
EW1

In [175]: bt2.data
Out[175]:
{'code': '510300',
 'buy_price': 13.1,
 'buy_dt': '2013-03-21 14:59:00',
 'stocks': 300,
 'buy_amt': 3930.0}

In [176]: res_tuple = bt2.get_data(dt_list = rec_dt_list, rec_list=rec_data_list)
     ...: if res_tuple is not None:
     ...:     print(bt2.meta['cash'] , bt2.meta['hold'])
     ...:     print(bt2.state)
     ...:     bt2.meta['last_dt'] = res_tuple[0]
     ...:     cur_data = res_tuple[1]
     ...:     bt2.ruleset(data=cur_data)
     ...:
     ...: else:
     ...:     print('next block')
     ...:
Date: 2013-03-22 14:00:00
Record: {'data_dt': '2013-03-22 14:00:00', 'open': 3.13, 'close': 0.1, 'high': 3.14, 'low': 2.9, 'decision_score': 610}
17918.0 3930.0
HW1
rate:  1.9913333333333334
d
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

4 结论

整体上,这个回测对象是可以使用的。

不必纠结于细节,可以直接进入下一步工程:主要是block规范的实现。数据的请求均是以block为单位,通过block manager实现。所以回测对象还需要被上一层的对象调用,形成worker - player模式。

另外,可以假设日数据可用(可盈利),试着以日收盘为周期建模,看效果。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/251519
推荐阅读
相关标签
  

闽ICP备14008679号