说明
从有这个想法,到勉强完工,整个过程还是持续了很长时间。主要原因有:
- 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模式。
另外,可以假设日数据可用(可盈利),试着以日收盘为周期建模,看效果。