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']

默认的策略参数,初始资本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
}

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'},   
]

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)

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

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:测试模型分到达是否买入 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}

测试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}

测试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}]

测试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}

测试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}]

测试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

测试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}]

测试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

4 结论

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

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

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

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-03-16 11:08:03       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-03-16 11:08:03       100 阅读
  3. 在Django里面运行非项目文件

    2024-03-16 11:08:03       82 阅读
  4. Python语言-面向对象

    2024-03-16 11:08:03       91 阅读

热门阅读

  1. HTML前置基础

    2024-03-16 11:08:03       43 阅读
  2. 00342第一章 概述 思考题和练习题(C语言)

    2024-03-16 11:08:03       39 阅读
  3. ES清理索引镜像

    2024-03-16 11:08:03       36 阅读
  4. 在Pod中使用Golang构建并推送镜像到镜像仓库

    2024-03-16 11:08:03       41 阅读
  5. Golang常用web框架

    2024-03-16 11:08:03       39 阅读
  6. <爬虫部署,进阶Docker>----第一章 介绍Docker

    2024-03-16 11:08:03       42 阅读
  7. 浏览器 实现文件下载 完成回调 兼容ie11

    2024-03-16 11:08:03       47 阅读
  8. mysql笔记:20. 什么是数据库六大范式

    2024-03-16 11:08:03       45 阅读
  9. 如何在Flutter中实现网络请求

    2024-03-16 11:08:03       41 阅读