class QA_Backtest_stock_day(): account = QA_Account() market = QA_Market() bid = QA_QAMarket_bid() setting = QA_Setting() clients = setting.client user = setting.QA_setting_user_name market_data = [] def __init__(self): self.account = QA_Account() self.market = QA_Market() self.bid = QA_QAMarket_bid() self.setting = QA_Setting() self.clients = self.setting.client self.user = self.setting.QA_setting_user_name @classmethod def backtest_init(__backtest_cls, func, *arg, **kwargs): def __init_backtest(__backtest_cls, *arg, **kwargs): __backtest_cls.QA_backtest_init(__backtest_cls) func(*arg, **kwargs) __backtest_cls.__QA_backtest_init_inside(__backtest_cls) return __init_backtest(__backtest_cls) @classmethod def before_backtest(__backtest_cls, func, *arg, **kwargs): # yield __backtest_cls.cash func(*arg, **kwargs) __backtest_cls.QA_backtest_start(__backtest_cls) @classmethod def end_backtest(__backtest_cls, func, *arg, **kwargs): # yield __backtest_cls.cash __backtest_cls.__end_of_backtest(__backtest_cls, func, *arg, **kwargs) return func(*arg, **kwargs) def __QA_backtest_set_bid_model(self): if self.backtest_bid_model == 'market_price': self.bid.bid['price'] = 'market_price' self.bid.bid['bid_model'] = 'auto' elif self.backtest_bid_model == 'close_price': self.bid.bid['price'] = 'close_price' self.bid.bid['bid_model'] = 'auto' elif self.backtest_bid_model == 'strategy': self.bid.bid['price'] = 0 self.bid.bid['bid_model'] = 'strategy' else: QA_util_log_info('support bid model') sys.exit() def __QA_backtest_set_save_model(self): pass def QA_backtest_init(self): """既然是被当做装饰器使用,就需要把变量设置放在装饰函数的前面,把函数放在装饰函数的后面""" # 设置回测的开始结束时间 self.strategy_start_date = str('2017-01-05') self.strategy_end_date = str('2017-07-01') # 设置回测标的,是一个list对象,不过建议只用一个标的 # gap是回测时,每日获取数据的前推日期(交易日) self.strategy_gap = int(60) # 设置全局的数据库地址,回测用户名,密码,并初始化 self.setting.QA_util_sql_mongo_ip = str('127.0.0.1') self.setting.QA_setting_user_name = str('admin') self.setting.QA_setting_user_password = str('admin') self.setting.QA_setting_init() # 回测的名字 self.strategy_name = str('example') # 股票的交易日历,真实回测的交易周期,和交易周期在交易日历中的id self.trade_list = QA_fetch_trade_date( self.setting.client.quantaxis.trade_date) self.benchmark_code = 'hs300' """ 这里会涉及一个区间的问题,开始时间是要向后推,而结束时间是要向前推,1代表向后推,-1代表向前推 """ self.start_real_date = QA_util_get_real_date(self.strategy_start_date, self.trade_list, 1) self.start_real_id = self.trade_list.index(self.start_real_date) self.end_real_date = QA_util_get_real_date(self.strategy_end_date, self.trade_list, -1) self.end_real_id = self.trade_list.index(self.end_real_date) self.strategy_stock_list = ['000001', '000002', '000004'] self.account.init_assest = 1000000 self.backtest_bid_model = 'market_price' def __QA_backtest_init_inside(self): """ 这是模型内部的 初始化,主要是初始化一些账户和市场资产 写成了私有函数 @yutiansut 2017/7/20 """ # 重新初始账户资产 self.setting.QA_setting_init() self.account.init() # 重新初始化账户的cookie self.account.account_cookie = str(random.random()) # 初始化股票池的市场数据 self.market_data = QA_fetch_stocklist_day( self.strategy_stock_list, self.setting.client.quantaxis.stock_day, [ self.trade_list[self.start_real_id - int(self.strategy_gap)], self.trade_list[self.end_real_id] ]) def QA_backtest_start(self, *args, **kwargs): """ 这个是回测流程开始的入口 """ assert len(self.strategy_stock_list) > 0 assert len(self.trade_list) > 0 assert isinstance(self.start_real_date, str) assert isinstance(self.end_real_date, str) #self.__QA_backtest_init_inside() assert len(self.market_data) == len(self.strategy_stock_list) QA_util_log_info('QUANTAXIS Backtest Engine Initial Successfully') QA_util_log_info('Basical Info: \n' + tabulate( [[str(__version__), str(self.strategy_name)]], headers=('Version', 'Strategy_name'))) QA_util_log_info('Stock_List: \n' + tabulate([self.strategy_stock_list])) # 初始化报价模式 self.__QA_backtest_set_bid_model(self) self.__messages = [] """ try: # 在末尾增加一个回调给策略 outside_handle.on_start(self) except: pass # 加载外部策略 self.__QA_backest_handle_data(outside_handle) """ @classmethod def load_strategy(__backtest_cls, func, *arg, **kwargs): '策略加载函数' # 首先判断是否能满足回测的要求` _info = {} _info['stock_list'] = __backtest_cls.strategy_stock_list __messages = {} __backtest_cls.__init_cash_per_stock = int( float(__backtest_cls.account.init_assest) / len(__backtest_cls.strategy_stock_list)) # 策略的交易日循环 for i in range(int(__backtest_cls.start_real_id), int(__backtest_cls.end_real_id) - 1, 1): __backtest_cls.running_date = __backtest_cls.trade_list[i] QA_util_log_info( '=================daily hold list====================') QA_util_log_info('in the begining of ' + __backtest_cls.running_date) QA_util_log_info( tabulate( __backtest_cls.account.message['body']['account']['hold'])) func(*arg, **kwargs) #最后一天 __backtest_cls.__end_of_trading(__backtest_cls) def __start_of_every_day(self, func, *arg, **kwargs): func(self, *arg, **kwargs) def __end_of_every_day(self, func, *arg, **kwargs): func(self, *arg, **kwargs) def __backtest_every_day_trading(self, i, func, *arg, **kwargs): # 正在进行的交易日期 __running_date = self.trade_list[i] QA_util_log_info( '=================daily hold list====================') QA_util_log_info('in the begining of ' + __running_date) QA_util_log_info( tabulate(self.account.message['body']['account']['hold'])) for __j in range(0, len(self.strategy_stock_list)): if __running_date in [l[6] for l in self.market_data[__j]] and \ [l[6] for l in self.market_data[__j]].index(__running_date) \ > self.strategy_gap + 1: __data = self.__QA_data_handle([ __l[6] for __l in self.market_data[__j] ].index(__running_date), __j) __amount = 0 for item in __data['account']['body']['account']['hold']: if self.strategy_stock_list[__j] in item: __amount = __amount + item[3] if __amount > 0: __hold = 1 else: __hold = 0 __result = func(self, *arg, **kwargs) if float(self.account.message['body']['account']['cash'] [-1]) > 0: self.QA_backtest_excute_bid( __result, __running_date, __hold, str(self.strategy_stock_list[__j])[0:6], __amount) else: QA_util_log_info('not enough free money') else: pass def __end_of_trading(self, *arg, **kwargs): # 在回测的最后一天,平掉所有仓位(回测的最后一天是不买入的) while len(self.account.hold) > 1: __hold_list = self.account.hold[1::] pre_del_id = [] for item_ in range(0, len(__hold_list)): if __hold_list[item_][3] > 0: __last_bid = self.bid.bid __last_bid['amount'] = int(__hold_list[item_][3]) __last_bid['order_id'] = str(random.random()) __last_bid['price'] = 'close_price' __last_bid['code'] = str(__hold_list[item_][1]) __last_bid['date'] = self.trade_list[self.end_real_id] __last_bid['towards'] = -1 __last_bid['user'] = self.setting.QA_setting_user_name __last_bid['strategy'] = self.strategy_name __last_bid['bid_model'] = 'auto' __last_bid['status'] = '0x01' __last_bid['amount_model'] = 'amount' __message = self.market.receive_bid( __last_bid, self.setting.client) _remains_day = 0 while __message['header']['status'] == 500: # 停牌状态,这个时候按停牌的最后一天计算价值(假设平仓) __last_bid['date'] = self.trade_list[self.end_real_id - _remains_day] _remains_day += 1 __message = self.market.receive_bid( __last_bid, self.setting.client) # 直到市场不是为0状态位置,停止前推日期 self.__messages = self.account.QA_account_receive_deal( __message) else: pre_del_id.append(item_) pre_del_id.sort() pre_del_id.reverse() for item_x in pre_del_id: __hold_list.pop(item_x) def __end_of_backtest(self, *arg, **kwargs): # 开始分析 QA_util_log_info('start analysis====\n' + str(self.strategy_stock_list)) QA_util_log_info('=' * 10 + 'Trade History' + '=' * 10) QA_util_log_info('\n' + tabulate(self.account.history, headers=('date', 'code', 'price', 'towards', 'amounts', 'order_id', 'trade_id', 'commission'))) QA_util_log_info( tabulate(self.account.detail, headers=('date', 'code', 'price', 'amounts', 'order_id', 'trade_id', 'sell_price', 'sell_order_id', 'sell_trade_id', 'sell_date', 'left_amount', 'commission'))) __exist_time = int(self.end_real_id) - int(self.start_real_id) + 1 self.__benchmark_data = QA_fetch_index_day(self.benchmark_code, self.start_real_date, self.end_real_date) if len(self.__messages) > 1: performace = QA_backtest_analysis_start( self.setting.client, self.strategy_stock_list, self.__messages, self.trade_list[self.start_real_id:self.end_real_id + 1], self.market_data, self.__benchmark_data) _backtest_mes = { 'user': self.setting.QA_setting_user_name, 'strategy': self.strategy_name, 'stock_list': performace['code'], 'start_time': self.strategy_start_date, 'end_time': self.strategy_end_date, 'account_cookie': self.account.account_cookie, 'annualized_returns': performace['annualized_returns'], 'benchmark_annualized_returns': performace['benchmark_annualized_returns'], 'assets': performace['assets'], 'benchmark_assets': performace['benchmark_assets'], 'trade_date': performace['trade_date'], 'total_date': performace['total_date'], 'win_rate': performace['win_rate'], 'alpha': performace['alpha'], 'beta': performace['beta'], 'sharpe': performace['sharpe'], 'vol': performace['vol'], 'benchmark_vol': performace['benchmark_vol'], 'max_drop': performace['max_drop'], 'exist': __exist_time, 'time': datetime.datetime.now() } QA_SU_save_backtest_message(_backtest_mes, self.setting.client) QA_SU_save_account_message(self.__messages, self.setting.client) QA_SU_save_account_to_csv(self.__messages) # QA.QA_SU_save_backtest_message(analysis_message, self.setting.client) def QA_backtest_excute_bid(self, __result, __date, __hold, __code, __amount): """ 这里是处理报价的逻辑部分 2017/7/19 修改 """ """ 2017/8/4再次修改: 我们需要的是一个简化的报单api 买入/卖出 股票代码 买入/卖出数量 参考实盘的下单接口: tradex: nCategory - 委托业务的种类 0 买入 1 卖出 2 融资买入 3 融券卖出 4 买券还券 5 卖券还款 6 现券还券 nOrderType - 委托报价方式 0 限价委托; 上海限价委托/ 深圳限价委托 1 市价委托(深圳对方最优价格) 2 市价委托(深圳本方最优价格) 3 市价委托(深圳即时成交剩余撤销) 4 市价委托(上海五档即成剩撤/ 深圳五档即成剩撤) 5 市价委托(深圳全额成交或撤销) 6 市价委托(上海五档即成转限价) sAccount - 股东代码 sStockCode - 证券代码 sPrice - 价格 sVolume - 委托证券的股数 返回值: errinfo - 出错时函数抛出的异常信息; result - 查询到的数据。 然后去检查 1.当前账户是否持仓该股票 2.当前账户现金是否足以支持购买该股票 返回 3.是否委托(委托id) 4.是否成交(成交id) 5.成交笔数 """ self.__QA_backtest_set_bid_model() if self.bid.bid['bid_model'] == 'strategy': __bid_price = __result['price'] else: __bid_price = self.bid.bid['price'] __bid = self.bid.bid __bid['order_id'] = str(random.random()) __bid['user'] = self.setting.QA_setting_user_name __bid['strategy'] = self.strategy_name __bid['code'] = __code __bid['date'] = __date __bid['price'] = __bid_price __bid['amount'], __bid['amount_model'] = self.__QA_bid_amount( __result['amount'], __amount) if __result['if_buy'] == 1: # 这是买入的情况 __bid['towards'] = 1 __message = self.market.receive_bid(__bid, self.setting.client) if float(self.account.message['body']['account']['cash'][-1]) > \ float(__message['body']['bid']['price']) * \ float(__message['body']['bid']['amount']): # 这里是买入资金充足的情况 # 不去考虑 pass else: # 如果买入资金不充足,则按照可用资金去买入 __message['body']['bid']['amount'] = int( float(self.account.message['body']['account']['cash'][-1]) / float( float(str(__message['body']['bid']['price'])[0:5]) * 100)) * 100 if __message['body']['bid']['amount'] > 0: # 这个判断是为了 如果买入资金不充足,所以买入报了一个0量单的情况 #如果买入量>0, 才判断为成功交易 self.account.QA_account_receive_deal(__message) elif __result['if_buy'] == 0: # 如果买入状态为0,则不进行任何买入操作 pass # 下面是卖出操作,这里在卖出前需要考虑一个是否有仓位的问题: # 因为在股票中是不允许卖空操作的,所以这里是股票的交易引擎和期货的交易引擎的不同所在 if __result['if_sell'] == 1 and __hold == 1: __bid['towards'] = -1 __message = self.market.receive_bid(__bid, self.setting.client) self.account.QA_account_receive_deal(__message) def __QA_bid_amount(self, __strategy_amount, __amount): if __strategy_amount == 'mean': return float( float(self.account.message['body']['account']['cash'][-1]) / len(self.strategy_stock_list)), 'price' elif __strategy_amount == 'half': return __amount * 0.5, 'amount' elif __strategy_amount == 'all': return __amount, 'amount' def __QA_get_data_from_market(self, __id, stock_id): # x=[x[6] for x in self.market_data] if __id > self.strategy_gap + 1: index_of_day = __id index_of_start = index_of_day - self.strategy_gap + 1 return self.market_data[stock_id][index_of_start:index_of_day + 1] # 从账户中更新数据 def __QA_data_handle(self, __id, __stock_id): market_data = self.__QA_get_data_from_market(__id, __stock_id) __message = self.account.message return {'market': market_data, 'account': __message}
class QA_Backtest(): account = QA_Account() market = QA_Market() bid = QA_QAMarket_bid() setting = QA_Setting() clients = setting.client user = setting.QA_setting_user_name """ backtest 类不应该只是一个简单的构造函数,他应该包含一个回测框架常用的方法和一些定制化的需求实现 @yutiansut 2017/6/19 需要做一个内部的setting表,以dict格式,value是函数名 @yutiansut 2017/6/27 """ def QA_backtest_load_strategy(self, ): __f_strategy_path = self.__backtest_setting['strategy']['file_path'] __f_strategy_path = __f_strategy_path.split(':') if __f_strategy_path[0] == 'file' and __f_strategy_path[1] == 'py': if __f_strategy_path[2] == 'local': __current_dir = os.getcwd() try: if os.path.exists( os.path.exists( str(__current_dir) + '\\backtest_strategy.py')): __file_path = os.path.exists( str(__current_dir) + '\\backtest_strategy.py') except: return "wrong with strategy file in current dir" elif os.path.exists(__f_strategy_path[2]): __file_path = __f_strategy_path[2] else: QA_util_log_info('error with loading strategy file') sys.exit() try: import __file_path as QUANTAXIS_Strategy QA_util_log_info(dir(QUANTAXIS_Strategy)) except: QA_util_log_info('wrong with loading strategy') def QA_backtest_import_setting(self, __setting_file_path='10x'): if __setting_file_path == '10x': __current_dir = os.getcwd() try: if os.path.exists( os.path.exists( str(__current_dir) + '\\backtest_setting.ini')): __file_path = str(__current_dir) + '\\backtest_setting.ini' except: return "wrong with config file in current dir" elif os.path.exists(__setting_file_path): __file_path = os.path.exists( str(__current_dir) + '\\backtest_setting.ini') self.__backtest_setting = configparser.ConfigParser() self.__backtest_setting.read(__file_path) def __QA_backtest_set_stock_list(self): self.strategy_stock_list = [] __t_strategy_stock_list = self.__backtest_setting['account'][ 'stock_list'] __t_strategy_stock_list = __t_strategy_stock_list.split(':') if __t_strategy_stock_list[0] == 'file': if __t_strategy_stock_list[1] == 'csv': if __t_strategy_stock_list[2] == 'local': __current_dir = os.getcwd() try: if os.path.exists( os.path.exists( str(__current_dir) + '\\stock_list.csv')): __stock_list_file_path = str(__current_dir) + \ '\\stock_list.csv' else: QA_util_log_info( "wrong with csv file in current dir, \ the name should be \\stock_list.csv") except: QA_util_log_info("wrong with csv file in current dir, \ the name should be \\stock_list.csv") else: try: if os.path.exists(__t_strategy_stock_list[2]): __stock_list_file_path = __t_strategy_stock_list[2] else: QA_util_log_info( "wrong with csv file in current dir, \ the name should be \\stock_list.csv") except: QA_util_log_info("wrong with csv file in current dir, \ the name should be \\stock_list.csv") with open(__stock_list_file_path, 'r') as csv_file: __data = csv.reader(csv_file) for item in __data: self.strategy_stock_list.append(item[0]) elif __t_strategy_stock_list[1] == 'json': if __t_strategy_stock_list[2] == 'local': __current_dir = os.getcwd() try: if os.path.exists( os.path.exists( str(__current_dir) + '\\stock_list.json')): __stock_list_file_path = str(__current_dir) + \ '\\stock_list.json' except: return "wrong with csv file in current dir, \ the name should be \\stock_list.json" else: try: if os.path.exists(__t_strategy_stock_list[2]): __stock_list_file_path = __t_strategy_stock_list[2] else: return "wrong with csv file in current dir, \ the name should be \\stock_list.json" except: return "wrong with csv file in current dir, \ the name should be \\stock_list.json" elif __t_strategy_stock_list[0] == 'mongo': try: import pymongo coll = pymongo.MongoClient().__t_strategy_stock_list[1].split( '-')[0].__t_strategy_stock_list[1].split['-'][1] assert isinstance(__t_strategy_stock_list[2], str) return coll.find(__t_strategy_stock_list[2]) except: QA_util_log_info( 'something wrong with import stock_list from mongodb') elif __t_strategy_stock_list[0] == 'data': if __t_strategy_stock_list[1] == 'list': self.strategy_stock_list = __t_strategy_stock_list[2] def __QA_backtest_set_bid_model(self): if self.__backtest_setting['bid']['bid_model'] == 'market_price': self.bid.bid['price'] = 'market_price' self.bid.bid['bid_model'] = 'auto' elif self.__backtest_setting['bid']['bid_model'] == 'close_price': self.bid.bid['price'] = 'close_price' self.bid.bid['bid_model'] = 'auto' elif self.__backtest_setting['bid']['bid_model'] == 'strategy': self.bid.bid['price'] = 0 self.bid.bid['bid_model'] = 'strategy' else: QA_util_log_info('support bid model') sys.exit() def __QA_backtest_set_save_model(self): pass def QA_backtest_init(self): # 设置回测的开始结束时间 try: self.QA_backtest_import_setting() except: sys.exit() self.strategy_start_date = str( self.__backtest_setting['backtest']['strategy_start_date']) self.strategy_end_date = str( self.__backtest_setting['backtest']['strategy_end_date']) # 设置回测标的,是一个list对象,不过建议只用一个标的 # gap是回测时,每日获取数据的前推日期(交易日) self.strategy_gap = int( self.__backtest_setting['backtest']['strategy_gap']) # 设置全局的数据库地址,回测用户名,密码,并初始化 self.setting.QA_util_sql_mongo_ip = str( self.__backtest_setting['backtest']['database_ip']) self.setting.QA_setting_user_name = str( self.__backtest_setting['backtest']['username']) self.setting.QA_setting_user_password = str( self.__backtest_setting['backtest']['password']) self.setting.QA_setting_init() # 回测的名字 self.strategy_name = str( self.__backtest_setting['backtest']['strategy_name']) # 股票的交易日历,真实回测的交易周期,和交易周期在交易日历中的id self.trade_list = QA_fetch_trade_date( self.setting.client.quantaxis.trade_date) self.benchmark_code = self.__backtest_setting['backtest'][ 'benchmark_code'] """ 这里会涉及一个区间的问题,开始时间是要向后推,而结束时间是要向前推,1代表向后推,-1代表向前推 """ self.start_real_date = QA_util_get_real_date(self.strategy_start_date, self.trade_list, 1) self.start_real_id = self.trade_list.index(self.start_real_date) self.end_real_date = QA_util_get_real_date(self.strategy_end_date, self.trade_list, -1) self.end_real_id = self.trade_list.index(self.end_real_date) self.__QA_backtest_set_stock_list() self.account.init_assest = self.__backtest_setting['account'][ 'account_assets'] def __QA_backtest_init_inside(self): """ 这是模型内部的 初始化,主要是初始化一些账户和市场资产 写成了私有函数 @yutiansut 2017/7/20 """ # 重新初始账户资产 self.account.init() # 重新初始化账户的cookie self.account.account_cookie = str(random.random()) # 初始化股票池的市场数据 self.market_data = QA_fetch_stocklist_day( self.strategy_stock_list, self.setting.client.quantaxis.stock_day, [ self.trade_list[self.start_real_id - int(self.strategy_gap)], self.trade_list[self.end_real_id] ]) def QA_backtest_start(self, outside_handle, *args, **kwargs): """ 这个是回测流程开始的入口 """ assert len(self.strategy_stock_list) > 0 assert len(self.trade_list) > 0 assert isinstance(self.start_real_date, str) assert isinstance(self.end_real_date, str) self.__QA_backtest_init_inside() assert len(self.market_data) == len(self.strategy_stock_list) QA_util_log_info('QUANTAXIS Backtest Engine Initial Successfully') QA_util_log_info('Basical Info: \n' + tabulate( [[str(__version__), str(self.strategy_name)]], headers=('Version', 'Strategy_name'))) QA_util_log_info('Stock_List: \n' + tabulate([self.strategy_stock_list])) # 初始化报价模式 self.__QA_backtest_set_bid_model() try: # 在末尾增加一个回调给策略 outside_handle.on_start(self) except: pass # 加载外部策略 self.__QA_backest_handle_data(outside_handle) def __QA_backest_handle_data(self, outside_handle): '这个outside_handle就是一个外部的注入函数' # 首先判断是否能满足回测的要求` _info = {} _info['stock_list'] = self.strategy_stock_list __messages = {} self.__init_cash_per_stock = int( float(self.account.init_assest) / len(self.strategy_stock_list)) # 策略的交易日循环 for i in range(int(self.start_real_id), int(self.end_real_id) - 1, 1): # 正在进行的交易日期 __running_date = self.trade_list[i] QA_util_log_info( '=================daily hold list====================') QA_util_log_info('in the begining of ' + __running_date) QA_util_log_info( tabulate(self.account.message['body']['account']['hold'])) for __j in range(0, len(self.strategy_stock_list)): if __running_date in [l[6] for l in self.market_data[__j]] and \ [l[6] for l in self.market_data[__j]].index(__running_date) \ > self.strategy_gap + 1: __data = self.__QA_data_handle([ __l[6] for __l in self.market_data[__j] ].index(__running_date), __j) __amount = 0 for item in __data['account']['body']['account']['hold']: if self.strategy_stock_list[__j] in item: __amount = __amount + item[3] if __amount > 0: __hold = 1 else: __hold = 0 __result = outside_handle.predict(__data['market'], __data['account'], __hold, _info) if float(self.account.message['body']['account']['cash'] [-1]) > 0: self.__QA_backtest_excute_bid( __result, __running_date, __hold, str(self.strategy_stock_list[__j])[0:6], __amount) else: QA_util_log_info('not enough free money') else: pass # 在回测的最后一天,平掉所有仓位(回测的最后一天是不买入的) while len(self.account.hold) > 1: __hold_list = self.account.hold[1::] pre_del_id = [] for item_ in range(0, len(__hold_list)): if __hold_list[item_][3] > 0: __last_bid = self.bid.bid __last_bid['amount'] = int(__hold_list[item_][3]) __last_bid['order_id'] = str(random.random()) __last_bid['price'] = 'close_price' __last_bid['code'] = str(__hold_list[item_][1]) __last_bid['date'] = self.trade_list[self.end_real_id] __last_bid['towards'] = -1 __last_bid['user'] = self.setting.QA_setting_user_name __last_bid['strategy'] = self.strategy_name __last_bid['bid_model'] = 'auto' __last_bid['status'] = '0x01' __last_bid['amount_model'] = 'amount' __message = self.market.receive_bid( __last_bid, self.setting.client) _remains_day = 0 while __message['header']['status'] == 500: # 停牌状态,这个时候按停牌的最后一天计算价值(假设平仓) __last_bid['date'] = self.trade_list[self.end_real_id - _remains_day] _remains_day += 1 __message = self.market.receive_bid( __last_bid, self.setting.client) # 直到市场不是为0状态位置,停止前推日期 __messages = self.account.QA_account_receive_deal( __message) else: pre_del_id.append(item_) pre_del_id.sort() pre_del_id.reverse() for item_x in pre_del_id: __hold_list.pop(item_x) try: # 在末尾增加一个回调给策略 outside_handle.on_end(__data['market'], __data['account'], __hold, _info) except: pass # 开始分析 QA_util_log_info('start analysis====\n' + str(self.strategy_stock_list)) QA_util_log_info('=' * 10 + 'Trade History' + '=' * 10) QA_util_log_info('\n' + tabulate(self.account.history, headers=('date', 'code', 'price', 'towards', 'amounts', 'order_id', 'trade_id', 'commission'))) QA_util_log_info( tabulate(self.account.detail, headers=('date', 'code', 'price', 'amounts', 'order_id', 'trade_id', 'sell_price', 'sell_order_id', 'sell_trade_id', 'sell_date', 'left_amount', 'commission'))) __exist_time = int(self.end_real_id) - int(self.start_real_id) + 1 self.__benchmark_data = QA_fetch_index_day(self.benchmark_code, self.start_real_date, self.end_real_date) performace = QA_backtest_analysis_start( self.setting.client, self.strategy_stock_list, __messages, self.trade_list[self.start_real_id:self.end_real_id + 1], self.market_data, self.__benchmark_data) _backtest_mes = { 'user': self.setting.QA_setting_user_name, 'strategy': self.strategy_name, 'stock_list': performace['code'], 'start_time': self.strategy_start_date, 'end_time': self.strategy_end_date, 'account_cookie': self.account.account_cookie, 'annualized_returns': performace['annualized_returns'], 'benchmark_annualized_returns': performace['benchmark_annualized_returns'], 'assets': performace['assets'], 'benchmark_assets': performace['benchmark_assets'], 'trade_date': performace['trade_date'], 'total_date': performace['total_date'], 'win_rate': performace['win_rate'], 'alpha': performace['alpha'], 'beta': performace['beta'], 'sharpe': performace['sharpe'], 'vol': performace['vol'], 'benchmark_vol': performace['benchmark_vol'], 'max_drop': performace['max_drop'], 'exist': __exist_time, 'time': datetime.datetime.now() } QA_SU_save_backtest_message(_backtest_mes, self.setting.client) QA_SU_save_account_message(__messages, self.setting.client) QA_SU_save_account_to_csv(__messages) # QA.QA_SU_save_backtest_message(analysis_message, self.setting.client) def __QA_backtest_excute_bid(self, __result, __date, __hold, __code, __amount): """ 这里是处理报价的逻辑部分 2017/7/19 修改 __result传进来的变量重新区分: 现在需要有 if_buy, if_sell 因为需要对于: 持仓状态下继续购买进行进一步的支持*简单的情形就是 浮盈加仓 if_buy, if_sell都需要传入 现在的 买卖状态 和 持仓状态 是解耦的 """ # 为了兼容性考虑,我们会在开始的时候检查是否有这些变量 if 'if_buy' not in list(__result.keys()): __result['if_buy'] = 0 if 'if_sell' not in list(__result.keys()): __result['if_sell'] = 0 self.__QA_backtest_set_bid_model() if self.bid.bid['bid_model'] == 'strategy': __bid_price = __result['price'] else: __bid_price = self.bid.bid['price'] __bid = self.bid.bid __bid['order_id'] = str(random.random()) __bid['user'] = self.setting.QA_setting_user_name __bid['strategy'] = self.strategy_name __bid['code'] = __code __bid['date'] = __date __bid['price'] = __bid_price __bid['amount'], __bid['amount_model'] = self.__QA_bid_amount( __result['amount'], __amount) if __result['if_buy'] == 1: # 这是买入的情况 __bid['towards'] = 1 __message = self.market.receive_bid(__bid, self.setting.client) if float(self.account.message['body']['account']['cash'][-1]) > \ float(__message['body']['bid']['price']) * \ float(__message['body']['bid']['amount']): # 这里是买入资金充足的情况 # 不去考虑 pass else: # 如果买入资金不充足,则按照可用资金去买入 __message['body']['bid']['amount'] = int( float(self.account.message['body']['account']['cash'][-1]) / float( float(str(__message['body']['bid']['price'])[0:5]) * 100)) * 100 if __message['body']['bid']['amount'] > 0: # 这个判断是为了 如果买入资金不充足,所以买入报了一个0量单的情况 #如果买入量>0, 才判断为成功交易 self.account.QA_account_receive_deal(__message) elif __result['if_buy'] == 0: # 如果买入状态为0,则不进行任何买入操作 pass # 下面是卖出操作,这里在卖出前需要考虑一个是否有仓位的问题: # 因为在股票中是不允许卖空操作的,所以这里是股票的交易引擎和期货的交易引擎的不同所在 if __result['if_sell'] == 1 and __hold == 1: __bid['towards'] = -1 __message = self.market.receive_bid(__bid, self.setting.client) self.account.QA_account_receive_deal(__message) def __QA_bid_amount(self, __strategy_amount, __amount): if __strategy_amount == 'mean': return float( float(self.account.message['body']['account']['cash'][-1]) / len(self.strategy_stock_list)), 'price' elif __strategy_amount == 'half': return __amount * 0.5, 'amount' elif __strategy_amount == 'all': return __amount, 'amount' def __QA_get_data_from_market(self, __id, stock_id): # x=[x[6] for x in self.market_data] if __id > self.strategy_gap + 1: index_of_day = __id index_of_start = index_of_day - self.strategy_gap + 1 return self.market_data[stock_id][index_of_start:index_of_day + 1] # 从账户中更新数据 def __QA_data_handle(self, __id, __stock_id): market_data = self.__QA_get_data_from_market(__id, __stock_id) __message = self.account.message return {'market': market_data, 'account': __message}
class QA_Backtest_stock_day(): '因为涉及很多不继承类的情况,所以先单列出来' account = QA_Account() market = QA_Market() bid = QA_QAMarket_bid() setting = QA_Setting() clients = setting.client user = setting.QA_setting_user_name market_data = [] today = '' def __init__(self): self.account = QA_Account() self.market = QA_Market() self.bid = QA_QAMarket_bid() self.setting = QA_Setting() self.clients = self.setting.client self.user = self.setting.QA_setting_user_name self.market_data = [] self.today = '' def __QA_backtest_init(self): """既然是被当做装饰器使用,就需要把变量设置放在装饰函数的前面,把函数放在装饰函数的后面""" # 设置回测的开始结束时间 self.strategy_start_date = str('2017-01-05') self.strategy_end_date = str('2017-07-01') # 设置回测标的,是一个list对象,不过建议只用一个标的 # gap是回测时,每日获取数据的前推日期(交易日) self.strategy_gap = int(60) # 设置全局的数据库地址,回测用户名,密码,并初始化 self.setting.QA_util_sql_mongo_ip = str('127.0.0.1') self.setting.QA_setting_user_name = str('admin') self.setting.QA_setting_user_password = str('admin') self.setting.QA_setting_init() # 回测的名字 self.strategy_name = str('example') # 股票的交易日历,真实回测的交易周期,和交易周期在交易日历中的id self.trade_list = QA_fetch_trade_date( self.setting.client.quantaxis.trade_date) self.benchmark_code = 'hs300' """ 这里会涉及一个区间的问题,开始时间是要向后推,而结束时间是要向前推,1代表向后推,-1代表向前推 """ self.strategy_stock_list = ['000001', '000002', '000004'] self.account.init_assest = 1000000 self.backtest_bid_model = 'market_price' def __QA_backtest_init_class(self): """ 这是模型内部的 初始化,主要是初始化一些账户和市场资产 写成了私有函数 @yutiansut 2017/7/20 """ # 重新初始账户资产 self.setting.QA_setting_init() self.account.init() self.start_real_date = QA_util_get_real_date(self.strategy_start_date, self.trade_list, 1) self.start_real_id = self.trade_list.index(self.start_real_date) self.end_real_date = QA_util_get_real_date(self.strategy_end_date, self.trade_list, -1) self.end_real_id = self.trade_list.index(self.end_real_date) # 重新初始化账户的cookie self.account.account_cookie = str(random.random()) # 初始化股票池的市场数据 self.market_data = QA_fetch_stocklist_day( self.strategy_stock_list, self.setting.client.quantaxis.stock_day, [ self.trade_list[self.start_real_id - int(self.strategy_gap)], self.trade_list[self.end_real_id] ]) def __QA_backtest_start(self, *args, **kwargs): """ 这个是回测流程开始的入口 """ assert len(self.strategy_stock_list) > 0 assert len(self.trade_list) > 0 assert isinstance(self.start_real_date, str) assert isinstance(self.end_real_date, str) assert len(self.market_data) == len(self.strategy_stock_list) QA_util_log_info('QUANTAXIS Backtest Engine Initial Successfully') QA_util_log_info('Basical Info: \n' + tabulate( [[str(__version__), str(self.strategy_name)]], headers=('Version', 'Strategy_name'))) QA_util_log_info('Stock_List: \n' + tabulate([self.strategy_stock_list])) # 初始化报价模式 self.__QA_backtest_set_bid_model(self) self.__messages = [] def __QA_backtest_set_bid_model(self): if self.backtest_bid_model == 'market_price': self.bid.bid['price'] = 'market_price' self.bid.bid['bid_model'] = 'auto' elif self.backtest_bid_model == 'close_price': self.bid.bid['price'] = 'close_price' self.bid.bid['bid_model'] = 'auto' elif self.backtest_bid_model == 'strategy': self.bid.bid['price'] = 0 self.bid.bid['bid_model'] = 'strategy' else: QA_util_log_info('support bid model') sys.exit() def __check_state(self, bid_price, bid_amount): pass def __QA_bid_amount(self, __strategy_amount, __amount): if __strategy_amount == 'mean': return float( float(self.account.message['body']['account']['cash'][-1]) / len(self.strategy_stock_list)), 'price' elif __strategy_amount == 'half': return __amount * 0.5, 'amount' elif __strategy_amount == 'all': return __amount, 'amount' def __QA_get_data_from_market(self, __id, stock_id): if __id > self.strategy_gap + 1: index_of_day = __id index_of_start = index_of_day - self.strategy_gap + 1 return self.market_data[stock_id][index_of_start:index_of_day + 1] else: return self.market_data[stock_id][0:__id + 1] def __QA_data_handle(self, __id, __stock_id): "已经废弃" market_data = self.__QA_get_data_from_market(__id, __stock_id) __message = self.account.message return {'market': market_data, 'account': __message} def __backtest_every_day_trading(self, i, func, *arg, **kwargs): # 正在进行的交易日期 __running_date = self.trade_list[i] QA_util_log_info( '=================daily hold list====================') QA_util_log_info('in the begining of ' + __running_date) QA_util_log_info( tabulate(self.account.message['body']['account']['hold'])) for __j in range(0, len(self.strategy_stock_list)): if __running_date in [l[6] for l in self.market_data[__j]] and \ [l[6] for l in self.market_data[__j]].index(__running_date) \ > self.strategy_gap + 1: __data = self.__QA_data_handle([ __l[6] for __l in self.market_data[__j] ].index(__running_date), __j) __amount = 0 for item in __data['account']['body']['account']['hold']: if self.strategy_stock_list[__j] in item: __amount = __amount + item[3] if __amount > 0: __hold = 1 else: __hold = 0 __result = func(self, *arg, **kwargs) if float(self.account.message['body']['account']['cash'] [-1]) > 0: self.QA_backtest_excute_bid( __result, __running_date, __hold, str(self.strategy_stock_list[__j])[0:6], __amount) else: QA_util_log_info('not enough free money') else: pass def __end_of_trading(self, *arg, **kwargs): # 在回测的最后一天,平掉所有仓位(回测的最后一天是不买入的) # 回测最后一天的交易处理 while len(self.account.hold) > 1: __hold_list = self.account.hold[1::] pre_del_id = [] for item_ in range(0, len(__hold_list)): if __hold_list[item_][3] > 0: __last_bid = self.bid.bid __last_bid['amount'] = int(__hold_list[item_][3]) __last_bid['order_id'] = str(random.random()) __last_bid['price'] = 'close_price' __last_bid['code'] = str(__hold_list[item_][1]) __last_bid['date'] = self.trade_list[self.end_real_id] __last_bid['towards'] = -1 __last_bid['user'] = self.setting.QA_setting_user_name __last_bid['strategy'] = self.strategy_name __last_bid['bid_model'] = 'auto' __last_bid['status'] = '0x01' __last_bid['amount_model'] = 'amount' __message = self.market.receive_bid( __last_bid, self.setting.client) _remains_day = 0 while __message['header']['status'] == 500: # 停牌状态,这个时候按停牌的最后一天计算价值(假设平仓) __last_bid['date'] = self.trade_list[self.end_real_id - _remains_day] _remains_day += 1 __message = self.market.receive_bid( __last_bid, self.setting.client) # 直到市场不是为0状态位置,停止前推日期 self.__messages = self.account.QA_account_receive_deal( __message) else: pre_del_id.append(item_) pre_del_id.sort() pre_del_id.reverse() for item_x in pre_del_id: __hold_list.pop(item_x) def __end_of_backtest(self, *arg, **kwargs): # 开始分析 QA_util_log_info('start analysis====\n' + str(self.strategy_stock_list)) QA_util_log_info('=' * 10 + 'Trade History' + '=' * 10) QA_util_log_info('\n' + tabulate(self.account.history, headers=('date', 'code', 'price', 'towards', 'amounts', 'order_id', 'trade_id', 'commission'))) QA_util_log_info( tabulate(self.account.detail, headers=('date', 'code', 'price', 'amounts', 'order_id', 'trade_id', 'sell_price', 'sell_order_id', 'sell_trade_id', 'sell_date', 'left_amount', 'commission'))) __exist_time = int(self.end_real_id) - int(self.start_real_id) + 1 self.__benchmark_data = QA_fetch_index_day(self.benchmark_code, self.start_real_date, self.end_real_date) if len(self.__messages) > 1: performace = QA_backtest_analysis_start( self.setting.client, self.strategy_stock_list, self.__messages, self.trade_list[self.start_real_id:self.end_real_id + 1], self.market_data, self.__benchmark_data) _backtest_mes = { 'user': self.setting.QA_setting_user_name, 'strategy': self.strategy_name, 'stock_list': performace['code'], 'start_time': self.strategy_start_date, 'end_time': self.strategy_end_date, 'account_cookie': self.account.account_cookie, 'annualized_returns': performace['annualized_returns'], 'benchmark_annualized_returns': performace['benchmark_annualized_returns'], 'assets': performace['assets'], 'benchmark_assets': performace['benchmark_assets'], 'trade_date': performace['trade_date'], 'total_date': performace['total_date'], 'win_rate': performace['win_rate'], 'alpha': performace['alpha'], 'beta': performace['beta'], 'sharpe': performace['sharpe'], 'vol': performace['vol'], 'benchmark_vol': performace['benchmark_vol'], 'max_drop': performace['max_drop'], 'exist': __exist_time, 'time': datetime.datetime.now() } QA_SU_save_backtest_message(_backtest_mes, self.setting.client) QA_SU_save_account_message(self.__messages, self.setting.client) QA_SU_save_account_to_csv(self.__messages) # QA.QA_SU_save_backtest_message(analysis_message, self.setting.client) def QA_backtest_get_market_data(self, code, date): '这个函数封装了关于获取的方式' index_of_date = 0 index_of_code = self.strategy_stock_list.index(code) if date in [l[6] for l in self.market_data[index_of_code]]: index_of_date = [l[6] for l in self.market_data[index_of_code] ].index(date) return self.__QA_get_data_from_market(self, index_of_date, index_of_code) def QA_backtest_hold_amount(self, __code): __amount_hold = 0 for item in self.account.hold: if __code in item: __amount_hold += item[3] return __amount_hold def QA_backtest_get_OHLCV(self, __data): '快速返回 OHLCV格式' return (__data.T[1].astype(float).tolist(), __data.T[2].astype(float).tolist(), __data.T[3].astype(float).tolist(), __data.T[4].astype(float).tolist(), __data.T[5].astype(float).tolist()) def QA_backtest_send_order(self, __code: str, __amount: int, __towards: int, __order: dict): """ 2017/8/4 委托函数 在外部封装的一个报价接口,尽量满足和实盘一样的模式 输入 ============= 买入/卖出 股票代码 买入/卖出数量 委托模式* 0 限价委托 LIMIT ORDER 1 市价委托 MARKET ORDER 2 严格模式(买入按最高价 卖出按最低价) STRICT ORDER 输出 ============= 返回: 委托状态/委托id 成交状态/成交id/成交量/成交价 错误/错误id/ return bid_status,trade_status,error """ # 必须是100股的倍数 __amount = int(__amount / 100) * 100 # self.__QA_backtest_set_bid_model() if __order['bid_model'] in [ 'limit', 'Limit', 'Limited', 'limited', 'l', 'L', 0, '0' ]: # 限价委托模式 __bid_price = __order['price'] elif __order['bid_model'] in [ 'Market', 'market', 'MARKET', 'm', 'M', 1, '1' ]: __bid_price = 'market_price' elif __order['bid_model'] in ['strict', 'Strict', 's', 'S', '2', 2]: __bid_price = 'strict_price' elif __order['bid_model'] in [ 'close', 'close_price', 'c', 'C', '3', 3 ]: __bid_price = 'close_price' __bid = self.bid.bid __bid['order_id'] = str(random.random()) __bid['user'] = self.setting.QA_setting_user_name __bid['strategy'] = self.strategy_name __bid['code'] = __code __bid['date'] = self.running_date __bid['price'] = __bid_price __bid['amount'] = __amount if __towards == 1: # 这是买入的情况 买入的时候主要考虑的是能不能/有没有足够的钱来买入 __bid['towards'] = 1 __message = self.market.receive_bid(__bid, self.setting.client) # 先扔进去买入,再通过返回的值来判定是否成功 if float(self.account.message['body']['account']['cash'][-1]) > \ float(__message['body']['bid']['price']) * \ float(__message['body']['bid']['amount']): # 这里是买入资金充足的情况 # 不去考虑 pass else: # 如果买入资金不充足,则按照可用资金去买入 # 这里可以这样做的原因是在买入的时候 手续费为0 __message['body']['bid']['amount'] = int( float(self.account.message['body']['account']['cash'][-1]) / float( float(str(__message['body']['bid']['price'])[0:5]) * 100)) * 100 if __message['body']['bid']['amount'] > 0: # 这个判断是为了 如果买入资金不充足,所以买入报了一个0量单的情况 #如果买入量>0, 才判断为成功交易 self.account.QA_account_receive_deal(__message) return __message # 下面是卖出操作,这里在卖出前需要考虑一个是否有仓位的问题: # 因为在股票中是不允许卖空操作的,所以这里是股票的交易引擎和期货的交易引擎的不同所在 elif __towards == -1: # 如果是卖出操作 检查是否有持仓 # 股票中不允许有卖空操作 # 检查持仓面板 __amount_hold = self.QA_backtest_hold_amount(self, __code) if __amount_hold > 0: __bid['towards'] = -1 if __amount_hold >= __amount: pass else: __bid['amount'] = __amount_hold __message = self.market.receive_bid(__bid, self.setting.client) if __message['header']['status'] == 200: self.account.QA_account_receive_deal(__message) return __message else: err_info = 'Error: Not Enough amount for code %s in hold list' % str( __code) QA_util_log_expection(err_info) return err_info else: return "Error: No buy/sell towards" def QA_backtest_sell_all(self): while len(self.account.hold) > 1: __hold_list = self.account.hold[1::] pre_del_id = [] for item_ in range(0, len(__hold_list)): if __hold_list[item_][3] > 0: __last_bid = self.bid.bid __last_bid['amount'] = int(__hold_list[item_][3]) __last_bid['order_id'] = str(random.random()) __last_bid['price'] = 'close_price' __last_bid['code'] = str(__hold_list[item_][1]) __last_bid['date'] = self.today __last_bid['towards'] = -1 __last_bid['user'] = self.setting.QA_setting_user_name __last_bid['strategy'] = self.strategy_name __last_bid['bid_model'] = 'auto' __last_bid['status'] = '0x01' __last_bid['amount_model'] = 'amount' __message = self.market.receive_bid( __last_bid, self.setting.client) _remains_day = 0 while __message['header']['status'] == 500: # 停牌状态,这个时候按停牌的最后一天计算价值(假设平仓) __last_bid['date'] = self.trade_list[self.end_real_id - _remains_day] _remains_day += 1 __message = self.market.receive_bid( __last_bid, self.setting.client) # 直到市场不是为0状态位置,停止前推日期 self.__messages = self.account.QA_account_receive_deal( __message) else: pre_del_id.append(item_) pre_del_id.sort() pre_del_id.reverse() for item_x in pre_del_id: __hold_list.pop(item_x) @classmethod def load_strategy(__backtest_cls, func, *arg, **kwargs): '策略加载函数' # 首先判断是否能满足回测的要求` _info = {} _info['stock_list'] = __backtest_cls.strategy_stock_list __messages = {} __backtest_cls.__init_cash_per_stock = int( float(__backtest_cls.account.init_assest) / len(__backtest_cls.strategy_stock_list)) # 策略的交易日循环 for i in range(int(__backtest_cls.start_real_id), int(__backtest_cls.end_real_id) - 1, 1): __backtest_cls.running_date = __backtest_cls.trade_list[i] QA_util_log_info( '=================daily hold list====================') QA_util_log_info('in the begining of ' + __backtest_cls.running_date) QA_util_log_info( tabulate( __backtest_cls.account.message['body']['account']['hold'])) __backtest_cls.today = __backtest_cls.running_date func(*arg, **kwargs) # 最后一天 __backtest_cls.__end_of_trading(__backtest_cls) @classmethod def backtest_init(__backtest_cls, func, *arg, **kwargs): def __init_backtest(__backtest_cls, *arg, **kwargs): __backtest_cls.__QA_backtest_init(__backtest_cls) func(*arg, **kwargs) __backtest_cls.__QA_backtest_init_class(__backtest_cls) return __init_backtest(__backtest_cls) @classmethod def before_backtest(__backtest_cls, func, *arg, **kwargs): func(*arg, **kwargs) __backtest_cls.__QA_backtest_start(__backtest_cls) @classmethod def end_backtest(__backtest_cls, func, *arg, **kwargs): # yield __backtest_cls.cash __backtest_cls.__end_of_backtest(__backtest_cls, func, *arg, **kwargs) return func(*arg, **kwargs)
class QA_Backtest_stock_day(): '最终目的还是实现一个通用的回测类' backtest_type = 'day' account = QA_Account() market = QA_Market() bid = QA_QAMarket_bid() setting = QA_Setting() clients = setting.client user = setting.QA_setting_user_name market_data = [] now = '' today = '' def __init__(self): self.backtest_type = 'day' self.account = QA_Account() self.market = QA_Market() self.bid = QA_QAMarket_bid() self.setting = QA_Setting() self.clients = self.setting.client self.user = self.setting.QA_setting_user_name self.market_data = [] self.now = '' self.today = '' def __QA_backtest_init(self): """既然是被当做装饰器使用,就需要把变量设置放在装饰函数的前面,把函数放在装饰函数的后面""" # 设置回测的开始结束时间 self.strategy_start_date = str('2017-01-05') self.strategy_end_date = str('2017-07-01') # 设置回测标的,是一个list对象,不过建议只用一个标的 # gap是回测时,每日获取数据的前推日期(交易日) self.strategy_gap = int(60) # 设置全局的数据库地址,回测用户名,密码,并初始化 self.setting.QA_util_sql_mongo_ip = str('127.0.0.1') self.setting.QA_setting_user_name = str('admin') self.setting.QA_setting_user_password = str('admin') self.setting.QA_setting_init() # 回测的名字 self.strategy_name = str('example_min') # 股票的交易日历,真实回测的交易周期,和交易周期在交易日历中的id self.trade_list = QA_fetch_trade_date( self.setting.client.quantaxis.trade_date) self.benchmark_code = 'hs300' """ 这里会涉及一个区间的问题,开始时间是要向后推,而结束时间是要向前推,1代表向后推,-1代表向前推 """ self.strategy_stock_list = ['000001', '000002', '000004'] self.account.init_assest = 1000000 self.backtest_bid_model = 'market_price' def __QA_backtest_prepare(self): """ 这是模型内部的 初始化,主要是初始化一些账户和市场资产 写成了私有函数 @yutiansut 2017/7/20 """ # 重新初始账户资产 self.setting.QA_setting_init() self.account.init() self.start_real_date = QA_util_get_real_date( self.strategy_start_date, self.trade_list, 1) self.start_real_id = self.trade_list.index(self.start_real_date) self.end_real_date = QA_util_get_real_date( self.strategy_end_date, self.trade_list, -1) self.end_real_id = self.trade_list.index(self.end_real_date) # 重新初始化账户的cookie self.account.account_cookie = str(random.random()) # 初始化股票池的市场数据 if self.backtest_type in ['day', 'd', '0x00']: self.market_data = QA_fetch_stocklist_day( self.strategy_stock_list, [self.trade_list[self.start_real_id - int(self.strategy_gap)], self.trade_list[self.end_real_id]]) elif self.backtest_type in ['min', 'm', '0x01']: self.market_data = QA_fetch_stocklist_min( self.strategy_stock_list, [self.trade_list[ self.start_real_id - int(self.strategy_gap)], self.trade_list[self.end_real_id]]) def __QA_backtest_start(self, *args, **kwargs): """ 这个是回测流程开始的入口 """ assert len(self.strategy_stock_list) > 0 assert len(self.trade_list) > 0 assert isinstance(self.start_real_date, str) assert isinstance(self.end_real_date, str) assert len(self.market_data) == len(self.strategy_stock_list) QA_util_log_info('QUANTAXIS Backtest Engine Initial Successfully') QA_util_log_info('Basical Info: \n' + tabulate( [[str(__version__), str(self.strategy_name)]], headers=('Version', 'Strategy_name'))) QA_util_log_info('BACKTEST Cookie_ID is: ' + str(self.account.account_cookie)) QA_util_log_info('Stock_List: \n' + tabulate([self.strategy_stock_list])) # 初始化报价模式 self.__QA_backtest_set_bid_model(self) self.__messages = [] def __QA_backtest_set_bid_model(self): if self.backtest_bid_model == 'market_price': self.bid.price = 'market_price' self.bid.bid_model = 'auto' elif self.backtest_bid_model == 'close_price': self.bid.price = 'close_price' self.bid.bid_model = 'auto' elif self.backtest_bid_model == 'strategy': self.bid.price = 0 self.bid.bid_model = 'strategy' else: QA_util_log_info('support bid model') sys.exit() def __check_state(self, bid_price, bid_amount): pass def __QA_bid_amount(self, __strategy_amount, __amount): if __strategy_amount == 'mean': return float(float(self.account.message['body']['account']['cash'][-1]) / len(self.strategy_stock_list)), 'price' elif __strategy_amount == 'half': return __amount * 0.5, 'amount' elif __strategy_amount == 'all': return __amount, 'amount' def __end_of_trading(self, *arg, **kwargs): # 在回测的最后一天,平掉所有仓位(回测的最后一天是不买入的) # 回测最后一天的交易处理 while len(self.account.hold) > 1: __hold_list = self.account.hold[1::] pre_del_id = [] for item_ in range(0, len(__hold_list)): if __hold_list[item_][3] > 0: __last_bid = self.bid __last_bid.amount = int(__hold_list[item_][3]) __last_bid.order_id = str(random.random()) __last_bid.price = 'close_price' __last_bid.code = str(__hold_list[item_][1]) __last_bid.date = self.trade_list[self.end_real_id] __last_bid.towards = -1 __last_bid.user = self.setting.QA_setting_user_name __last_bid.strategy = self.strategy_name __last_bid.bid_model = 'auto' __last_bid.type = '0x01' __last_bid.amount_model = 'amount' __message = self.market.receive_bid( __last_bid) _remains_day = 0 while __message['header']['status'] == 500: # 停牌状态,这个时候按停牌的最后一天计算价值(假设平仓) __last_bid.date = self.trade_list[self.end_real_id - _remains_day] _remains_day += 1 __message = self.market.receive_bid( __last_bid) # 直到市场不是为0状态位置,停止前推日期 self.__messages = self.account.QA_account_receive_deal( __message) else: pre_del_id.append(item_) pre_del_id.sort() pre_del_id.reverse() for item_x in pre_del_id: __hold_list.pop(item_x) def __end_of_backtest(self, *arg, **kwargs): # 开始分析 QA_util_log_info('start analysis====\n' + str(self.strategy_stock_list)) QA_util_log_info('=' * 10 + 'Trade History' + '=' * 10) QA_util_log_info('\n' + tabulate(self.account.history, headers=('date', 'code', 'price', 'towards', 'amounts', 'order_id', 'trade_id', 'commission'))) QA_util_log_info('\n' + tabulate(self.account.detail, headers=('date', 'code', 'price', 'amounts', 'order_id', 'trade_id', 'sell_price', 'sell_order_id', 'sell_trade_id', 'sell_date', 'left_amount', 'commission'))) __exist_time = int(self.end_real_id) - int(self.start_real_id) + 1 self.__benchmark_data = QA_fetch_index_day( self.benchmark_code, self.start_real_date, self.end_real_date) if len(self.__messages) > 1: performace = QA_backtest_analysis_start( self.setting.client, self.strategy_stock_list, self.__messages, self.trade_list[self.start_real_id:self.end_real_id + 1], self.market_data, self.__benchmark_data) _backtest_mes = { 'user': self.setting.QA_setting_user_name, 'strategy': self.strategy_name, 'stock_list': performace['code'], 'start_time': self.strategy_start_date, 'end_time': self.strategy_end_date, 'account_cookie': self.account.account_cookie, 'annualized_returns': performace['annualized_returns'], 'benchmark_annualized_returns': performace['benchmark_annualized_returns'], 'assets': performace['assets'], 'benchmark_assets': performace['benchmark_assets'], 'trade_date': performace['trade_date'], 'total_date': performace['total_date'], 'win_rate': performace['win_rate'], 'alpha': performace['alpha'], 'beta': performace['beta'], 'sharpe': performace['sharpe'], 'vol': performace['vol'], 'benchmark_vol': performace['benchmark_vol'], 'max_drop': performace['max_drop'], 'exist': __exist_time, 'time': datetime.datetime.now() } QA_SU_save_backtest_message(_backtest_mes, self.setting.client) QA_SU_save_account_message(self.__messages, self.setting.client) QA_SU_save_account_to_csv(self.__messages) # QA.QA_SU_save_backtest_message(analysis_message, self.setting.client) def QA_backtest_get_market_data(self, code, date, type_='numpy'): '这个函数封装了关于获取的方式' index_of_code = self.strategy_stock_list.index(code) __res = self.market_data[index_of_code][:date].tail(self.strategy_gap) if type_ in ['l', 'list', 'L']: return np.asarray(__res).tolist() elif type_ in ['pd', 'pandas', 'p']: return __res else: return np.asarray(__res) def QA_backtest_hold_amount(self, __code): return sum(list(map(lambda item: item[3] if __code in item else 0, self.account.hold))) def QA_backtest_get_OHLCV(self, __data): '快速返回 OHLCV格式' return (__data.T[1].astype(float).tolist(), __data.T[2].astype(float).tolist(), __data.T[3].astype(float).tolist( ), __data.T[4].astype(float).tolist(), __data.T[5].astype(float).tolist()) def QA_backtest_send_order(self, __code, __amount, __towards, __order): """ 2017/8/4 委托函数 在外部封装的一个报价接口,尽量满足和实盘一样的模式 输入 ============= 买入/卖出 股票代码 买入/卖出数量 委托模式* 0 限价委托 LIMIT ORDER 1 市价委托 MARKET ORDER 2 严格模式(买入按最高价 卖出按最低价) STRICT ORDER 输出 ============= 返回: 委托状态/委托id 成交状态/成交id/成交量/成交价 错误/错误id/ return bid_status,trade_status,error """ # 必须是100股的倍数 __amount = int(__amount / 100) * 100 # self.__QA_backtest_set_bid_model() if __order['bid_model'] in ['limit', 'Limit', 'Limited', 'limited', 'l', 'L', 0, '0']: # 限价委托模式 __bid_price = __order['price'] elif __order['bid_model'] in ['Market', 'market', 'MARKET', 'm', 'M', 1, '1']: __bid_price = 'market_price' elif __order['bid_model'] in ['strict', 'Strict', 's', 'S', '2', 2]: __bid_price = 'strict_price' elif __order['bid_model'] in ['close', 'close_price', 'c', 'C', '3', 3]: __bid_price = 'close_price' __bid = self.bid __bid.order_id = str(random.random()) __bid.user = self.setting.QA_setting_user_name __bid.strategy = self.strategy_name __bid.code = __code __bid.date = self.running_date __bid.datetime = self.running_date __bid.sending_time = self.running_date __bid.price = __bid_price __bid.amount = __amount if __towards == 1: # 这是买入的情况 买入的时候主要考虑的是能不能/有没有足够的钱来买入 __bid.towards = 1 __message = self.market.receive_bid( __bid) # 先扔进去买入,再通过返回的值来判定是否成功 if float(self.account.message['body']['account']['cash'][-1]) > \ float(__message['body']['bid']['price']) * \ float(__message['body']['bid']['amount']): # 这里是买入资金充足的情况 # 不去考虑 pass else: # 如果买入资金不充足,则按照可用资金去买入 # 这里可以这样做的原因是在买入的时候 手续费为0 __message['body']['bid']['amount'] = int(float( self.account.message['body']['account']['cash'][-1]) / float( float(str(__message['body']['bid']['price'])[0:5]) * 100)) * 100 if __message['body']['bid']['amount'] > 0: # 这个判断是为了 如果买入资金不充足,所以买入报了一个0量单的情况 #如果买入量>0, 才判断为成功交易 self.messages=self.account.QA_account_receive_deal(__message) return __message # 下面是卖出操作,这里在卖出前需要考虑一个是否有仓位的问题: # 因为在股票中是不允许卖空操作的,所以这里是股票的交易引擎和期货的交易引擎的不同所在 elif __towards == -1: # 如果是卖出操作 检查是否有持仓 # 股票中不允许有卖空操作 # 检查持仓面板 __amount_hold = self.QA_backtest_hold_amount(self, __code) if __amount_hold > 0: __bid.towards = -1 __bid.amount = __amount_hold if __amount_hold < __amount else __bid.amount __message = self.market.receive_bid( __bid) if __message['header']['status'] == 200: self.messages=self.account.QA_account_receive_deal(__message) return __message else: err_info = 'Error: Not Enough amount for code %s in hold list' % str( __code) QA_util_log_expection(err_info) return err_info else: return "Error: No buy/sell towards" def QA_backtest_check_order(self, order): '用于检查委托单的状态' """ 委托单被报入交易所会有一个回报,回报状态就是交易所返回的字段: 字段目前 2xx 是成功 4xx是失败 5xx是交易所无数据(停牌) 随着回测框架的不断升级,会有更多状态需要被管理: 200 委托成功,交易成功 203 委托成功,待成交 20 """ pass def QA_backtest_sell_all(self): while len(self.account.hold) > 1: __hold_list = self.account.hold[1::] pre_del_id = [] def __sell(id_): if __hold_list[id_][3] > 0: __last_bid = self.bid __last_bid.amount = int(__hold_list[id_][3]) __last_bid.order_id = str(random.random()) __last_bid.price = 'close_price' __last_bid.code = str(__hold_list[id_][1]) __last_bid.date = self.now __last_bid.towards = -1 __last_bid.user = self.setting.QA_setting_user_name __last_bid.strategy = self.strategy_name __last_bid.bid_model = 'auto' __last_bid.type = '0x01' __last_bid.amount_model = 'amount' __message = self.market.receive_bid( __last_bid) _remains_day = 0 while __message['header']['status'] == 500: # 停牌状态,这个时候按停牌的最后一天计算价值(假设平仓) __last_bid.date = self.trade_list[self.end_real_id - _remains_day] _remains_day += 1 __message = self.market.receive_bid( __last_bid) # 直到市场不是为0状态位置,停止前推日期 self.__messages = self.account.QA_account_receive_deal( __message) else: pre_del_id.append(id_) return pre_del_id pre_del_id = reduce(lambda _, x: __sell(x), range(len(__hold_list))) pre_del_id.sort() pre_del_id.reverse() for item_x in pre_del_id: __hold_list.pop(item_x) @classmethod def load_strategy(__backtest_cls, func, *arg, **kwargs): '策略加载函数' # 首先判断是否能满足回测的要求` __messages = {} __backtest_cls.__init_cash_per_stock = int( float(__backtest_cls.account.init_assest) / len(__backtest_cls.strategy_stock_list)) # 策略的交易日循环 for i in range(int(__backtest_cls.start_real_id), int(__backtest_cls.end_real_id) - 1, 1): __backtest_cls.running_date = __backtest_cls.trade_list[i] QA_util_log_info( '=================daily hold list====================') QA_util_log_info('in the begining of ' + __backtest_cls.running_date) QA_util_log_info( tabulate(__backtest_cls.account.message['body']['account']['hold'])) __backtest_cls.now = __backtest_cls.running_date __backtest_cls.today = __backtest_cls.running_date func(*arg, **kwargs) # 最后一天 __backtest_cls.__end_of_trading(__backtest_cls) @classmethod def backtest_init(__backtest_cls, func, *arg, **kwargs): def __init_backtest(__backtest_cls, *arg, **kwargs): __backtest_cls.__QA_backtest_init(__backtest_cls) func(*arg, **kwargs) __backtest_cls.__QA_backtest_prepare(__backtest_cls) return __init_backtest(__backtest_cls) @classmethod def before_backtest(__backtest_cls, func, *arg, **kwargs): func(*arg, **kwargs) __backtest_cls.__QA_backtest_start(__backtest_cls) @classmethod def end_backtest(__backtest_cls, func, *arg, **kwargs): # yield __backtest_cls.cash __backtest_cls.__end_of_backtest(__backtest_cls, func, *arg, **kwargs) return func(*arg, **kwargs)
class QA_Backtest(): '最终目的还是实现一个通用的回测类' backtest_type = 'day' account = QA_Account() market = QA_Market() bid = QA_QAMarket_bid() setting = QA_Setting() clients = setting.client user = setting.QA_setting_user_name market_data = [] now = '' today = '' def __init__(self): self.backtest_type = 'day' self.account = QA_Account() self.market = QA_Market() self.bid = QA_QAMarket_bid() self.setting = QA_Setting() self.clients = self.setting.client self.user = self.setting.QA_setting_user_name self.market_data = [] self.now = '' self.today = '' def __QA_backtest_init(self): """既然是被当做装饰器使用,就需要把变量设置放在装饰函数的前面,把函数放在装饰函数的后面""" # 设置回测的开始结束时间 self.strategy_start_date = str('2017-01-05') self.strategy_end_date = str('2017-07-01') # 设置回测标的,是一个list对象,不过建议只用一个标的 # gap是回测时,每日获取数据的前推日期(交易日) self.strategy_gap = int(60) # 设置全局的数据库地址,回测用户名,密码,并初始化 self.setting.QA_util_sql_mongo_ip = str('127.0.0.1') self.setting.QA_setting_user_name = str('admin') self.setting.QA_setting_user_password = str('admin') self.setting.QA_setting_init() # 回测的名字 self.strategy_name = str('example_min') # 股票的交易日历,真实回测的交易周期,和交易周期在交易日历中的id self.trade_list = QA_fetch_trade_date( self.setting.client.quantaxis.trade_date) self.benchmark_code = 'hs300' """ 这里会涉及一个区间的问题,开始时间是要向后推,而结束时间是要向前推,1代表向后推,-1代表向前推 """ self.strategy_stock_list = ['000001', '000002', '000004'] self.account.init_assest = 1000000 self.backtest_bid_model = 'market_price' def __QA_backtest_prepare(self): """ 这是模型内部的 初始化,主要是初始化一些账户和市场资产 写成了私有函数 @yutiansut 2017/7/20 """ # 重新初始账户资产 self.setting.QA_setting_init() self.account.init() self.start_real_date = QA_util_get_real_date(self.strategy_start_date, self.trade_list, 1) self.start_real_id = self.trade_list.index(self.start_real_date) self.end_real_date = QA_util_get_real_date(self.strategy_end_date, self.trade_list, -1) self.end_real_id = self.trade_list.index(self.end_real_date) # 重新初始化账户的cookie self.account.account_cookie = str(random.random()) # 初始化股票池的市场数据 if self.backtest_type in ['day', 'd', '0x00']: self.market_data = QA_fetch_stocklist_day( self.strategy_stock_list, [ self.trade_list[self.start_real_id - int(self.strategy_gap)], self.trade_list[self.end_real_id] ]) elif self.backtest_type in ['min', 'm', '0x01']: self.market_data = QA_fetch_stocklist_min( self.strategy_stock_list, [ self.trade_list[self.start_real_id - int(self.strategy_gap)], self.trade_list[self.end_real_id] ]) def __QA_backtest_start(self, *args, **kwargs): """ 这个是回测流程开始的入口 """ assert len(self.strategy_stock_list) > 0 assert len(self.trade_list) > 0 assert isinstance(self.start_real_date, str) assert isinstance(self.end_real_date, str) assert len(self.market_data) == len(self.strategy_stock_list) QA_util_log_info('QUANTAXIS Backtest Engine Initial Successfully') QA_util_log_info('Basical Info: \n' + tabulate( [[str(__version__), str(self.strategy_name)]], headers=('Version', 'Strategy_name'))) QA_util_log_info('Stock_List: \n' + tabulate([self.strategy_stock_list])) # 初始化报价模式 self.__messages = [] def __check_state(self, bid_price, bid_amount): pass def __QA_bid_amount(self, __strategy_amount, __amount): if __strategy_amount == 'mean': return float( float(self.account.message['body']['account']['cash'][-1]) / len(self.strategy_stock_list)), 'price' elif __strategy_amount == 'half': return __amount * 0.5, 'amount' elif __strategy_amount == 'all': return __amount, 'amount' def __end_of_trading(self, *arg, **kwargs): # 在回测的最后一天,平掉所有仓位(回测的最后一天是不买入的) # 回测最后一天的交易处理 while len(self.account.hold) > 1: __hold_list = self.account.hold[1::] pre_del_id = [] for item_ in range(0, len(__hold_list)): if __hold_list[item_][3] > 0: __last_bid = self.bid __last_bid.amount = int(__hold_list[item_][3]) __last_bid.order_id = str(random.random()) __last_bid.price = 'close_price' __last_bid.code = str(__hold_list[item_][1]) __last_bid.date = self.trade_list[self.end_real_id] __last_bid.towards = -1 __last_bid.user = self.setting.QA_setting_user_name __last_bid.strategy = self.strategy_name __last_bid.bid_model = 'auto' __last_bid.status = '0x01' __last_bid.amount_model = 'amount' __message = self.market.receive_bid(__last_bid) _remains_day = 0 while __message['header']['status'] == 500: # 停牌状态,这个时候按停牌的最后一天计算价值(假设平仓) __last_bid.date = self.trade_list[self.end_real_id - _remains_day] _remains_day += 1 __message = self.market.receive_bid(__last_bid) # 直到市场不是为0状态位置,停止前推日期 self.__messages = self.account.QA_account_receive_deal( __message) else: pre_del_id.append(item_) pre_del_id.sort() pre_del_id.reverse() for item_x in pre_del_id: __hold_list.pop(item_x) def __warp_bid(self, __bid, __order): __market_data_for_backtest = self.QA_backtest_get_market_data( self, __bid.code, __bid.date, 'np', 1) __O, __H, __L, __C, __V = self.QA_backtest_get_OHLCV( self, __market_data_for_backtest) if __order['bid_model'] in [ 'limit', 'Limit', 'Limited', 'limited', 'l', 'L', 0, '0' ]: # 限价委托模式 __bid.price = __order['price'] elif __order['bid_model'] in [ 'Market', 'market', 'MARKET', 'm', 'M', 1, '1' ]: __bid.price = 0.5 * (float(__O[0]) + float(__C[0])) elif __order['bid_model'] in ['strict', 'Strict', 's', 'S', '2', 2]: __bid.price = float(__H[0]) if __bid.towards == 1 else float( __L[0]) elif __order['bid_model'] in [ 'close', 'close_price', 'c', 'C', '3', 3 ]: __bid.price = float(__C[0]) __bid.price = float('%.2f' % __bid.price) return __bid def __end_of_backtest(self, *arg, **kwargs): # 开始分析 QA_util_log_info('start analysis====\n' + str(self.strategy_stock_list)) QA_util_log_info('=' * 10 + 'Trade History' + '=' * 10) QA_util_log_info('\n' + tabulate(self.account.history, headers=('date', 'code', 'price', 'towards', 'amounts', 'order_id', 'trade_id', 'commission'))) QA_util_log_info('\n' + tabulate(self.account.detail, headers=('date', 'code', 'price', 'amounts', 'order_id', 'trade_id', 'sell_price', 'sell_order_id', 'sell_trade_id', 'sell_date', 'left_amount', 'commission'))) __exist_time = int(self.end_real_id) - int(self.start_real_id) + 1 self.__benchmark_data = QA_fetch_index_day(self.benchmark_code, self.start_real_date, self.end_real_date) if len(self.__messages) > 1: performace = QA_backtest_analysis_start( self.setting.client, self.strategy_stock_list, self.__messages, self.trade_list[self.start_real_id:self.end_real_id + 1], self.market_data, self.__benchmark_data) _backtest_mes = { 'user': self.setting.QA_setting_user_name, 'strategy': self.strategy_name, 'stock_list': performace['code'], 'start_time': self.strategy_start_date, 'end_time': self.strategy_end_date, 'account_cookie': self.account.account_cookie, 'annualized_returns': performace['annualized_returns'], 'benchmark_annualized_returns': performace['benchmark_annualized_returns'], 'assets': performace['assets'], 'benchmark_assets': performace['benchmark_assets'], 'trade_date': performace['trade_date'], 'total_date': performace['total_date'], 'win_rate': performace['win_rate'], 'alpha': performace['alpha'], 'beta': performace['beta'], 'sharpe': performace['sharpe'], 'vol': performace['vol'], 'benchmark_vol': performace['benchmark_vol'], 'max_drop': performace['max_drop'], 'exist': __exist_time, 'time': datetime.datetime.now() } QA_SU_save_backtest_message(_backtest_mes, self.setting.client) QA_SU_save_account_message(self.__messages, self.setting.client) QA_SU_save_account_to_csv(self.__messages) def QA_backtest_get_market_data(self, code, date, type_='numpy', gap_=None): '这个函数封装了关于获取的方式' gap_ = self.strategy_gap if gap_ is None else gap_ index_of_code = self.strategy_stock_list.index(code) __res = self.market_data[index_of_code][:date].tail(gap_) if type_ in ['l', 'list', 'L']: return np.asarray(__res).tolist() elif type_ in ['pd', 'pandas', 'p']: return res else: return np.asarray(__res) def QA_backtest_hold_amount(self, __code): # return sum(list(map(lambda item: item[3] if __code in item else 0, self.account.hold))) try: return self.account.hold_available[__code] except: return 0 def QA_backtest_get_OHLCV(self, __data): '快速返回 OHLCV格式' return (__data.T[1].astype(float).tolist(), __data.T[2].astype(float).tolist(), __data.T[3].astype(float).tolist(), __data.T[4].astype(float).tolist(), __data.T[5].astype(float).tolist()) def QA_backtest_send_order(self, __code: str, __amount: int, __towards: int, __order: dict): """ 2017/8/4 委托函数 在外部封装的一个报价接口,尽量满足和实盘一样的模式 输入 ============= 买入/卖出 股票代码 买入/卖出数量 委托模式* 0 限价委托 LIMIT ORDER 1 市价委托 MARKET ORDER 2 严格模式(买入按最高价 卖出按最低价) STRICT ORDER """ # 只需要维护未成交队列即可,无非是一个可用资金和可卖股票数量的调整 # 必须是100股的倍数 __amount = int(__amount / 100) * 100 # self.__QA_backtest_set_bid_model() __bid = self.bid (__bid.order_id, __bid.user, __bid.strategy, __bid.code, __bid.date, __bid.datetime, __bid.sending_time, __bid.amount, __bid.towards) = (str(random.random()), self.setting.QA_setting_user_name, self.strategy_name, __code, self.running_date, self.running_date, self.running_date, __amount, __towards) __bid = self.__warp_bid(self, __bid, __order) return self.__QA_backtest_send_bid(self, __bid) # 先进行处理: def __QA_backtest_send_bid(self, __bid): if __bid.towards == 1: # 扣费以下这个订单时的bar的open扣费 if self.account.cash_available > (__bid.price * __bid.amount): # 这是买入的情况 买入的时候主要考虑的是能不能/有没有足够的钱来买入 __message = self.market.receive_bid(__bid) self.account.cash_available -= (__bid.price * __bid.amount) # 先扔进去买入,再通过返回的值来判定是否成功 if __message['header']['status'] == 200 and __message['body'][ 'bid']['amount'] > 0: # 这个判断是为了 如果买入资金不充足,所以买入报了一个0量单的情况 # 如果买入量>0, 才判断为成功交易 self.account.QA_account_receive_deal(__message) return __message else: # self.account.cash_available += (__bid.price * __bid.amount) #报单还挂着 不能恢复 self.account.order_queue.append(__bid) # 下面是卖出操作,这里在卖出前需要考虑一个是否有仓位的问题: # 因为在股票中是不允许卖空操作的,所以这里是股票的交易引擎和期货的交易引擎的不同所在 elif __bid.towards == -1: # 如果是卖出操作 检查是否有持仓 # 股票中不允许有卖空操作 # 检查持仓面板 __amount_hold = self.QA_backtest_hold_amount(self, __bid.code) if __amount_hold > 0: __bid.amount = __amount_hold if __amount_hold < __bid.amount else __bid.amount self.account.hold_available[__bid.code] -= __bid.amount __message = self.market.receive_bid(__bid) print(__message) if __message['header']['status'] == 200: self.account.QA_account_receive_deal(__message) return __message else: self.account.order_queue.append(__bid) return __message else: err_info = 'Error: Not Enough amount for code %s in hold list' % str( __bid.code) QA_util_log_expection(err_info) return err_info else: return "Error: No buy/sell towards" def QA_backtest_check_order(self, order): '用于检查委托单的状态' """ 委托单被报入交易所会有一个回报,回报状态就是交易所返回的字段: 字段目前 2xx 是成功 4xx是失败 5xx是交易所无数据(停牌) 随着回测框架的不断升级,会有更多状态需要被管理: 200 委托成功,交易成功 203 委托成功,待成交 20 """ pass def QA_backtest_sell_all(self): while len(self.account.hold) > 1: __hold_list = self.account.hold[1::] pre_del_id = [] def __sell(id_): if __hold_list[item_][3] > 0: __last_bid = self.bid __last_bid.amount = int(__hold_list[id_][3]) __last_bid.order_id = str(random.random()) __last_bid.price = 'close_price' __last_bid.code = str(__hold_list[id_][1]) __last_bid.date = self.now __last_bid.towards = -1 __last_bid.user = self.setting.QA_setting_user_name __last_bid.strategy = self.strategy_name __last_bid.bid_model = 'auto' __last_bid.status = '0x01' __last_bid.amount_model = 'amount' __message = self.market.receive_bid(__last_bid) _remains_day = 0 while __message['header']['status'] == 500: # 停牌状态,这个时候按停牌的最后一天计算价值(假设平仓) __last_bid.date = self.trade_list[self.end_real_id - _remains_day] _remains_day += 1 __message = self.market.receive_bid(__last_bid) # 直到市场不是为0状态位置,停止前推日期 self.__messages = self.account.QA_account_receive_deal( __message) else: pre_del_id.append(id_) return pre_del_id pre_del_id = reduce(lambda _, x: __sell(x), range(len(__hold_list))) pre_del_id.sort() pre_del_id.reverse() for item_x in pre_del_id: __hold_list.pop(item_x) @classmethod def load_strategy(__backtest_cls, func, *arg, **kwargs): '策略加载函数' # 首先判断是否能满足回测的要求` __messages = {} __backtest_cls.__init_cash_per_stock = int( float(__backtest_cls.account.init_assest) / len(__backtest_cls.strategy_stock_list)) # 策略的交易日循环 for i in range(int(__backtest_cls.start_real_id), int(__backtest_cls.end_real_id) - 1, 1): __backtest_cls.running_date = __backtest_cls.trade_list[i] QA_util_log_info( '=================daily hold list====================') QA_util_log_info('in the begining of ' + __backtest_cls.running_date) QA_util_log_info( tabulate( __backtest_cls.account.message['body']['account']['hold'])) __backtest_cls.now = __backtest_cls.running_date __backtest_cls.today = __backtest_cls.running_date # 交易前同步持仓状态 __backtest_cls.account.cash_available = __backtest_cls.account.cash[ -1] __temp_hold = pd.DataFrame(__backtest_cls.account.hold[1::], columns=__backtest_cls.account.hold[0]) __temp_hold = __temp_hold.set_index('code', drop=False) __backtest_cls.account.hold_available = __temp_hold[ 'amount'].groupby('code').sum() if __backtest_cls.backtest_type in ['day', 'd']: for item in __backtest_cls.account.order_queue: item.date, item.datetime = (__backtest_cls.today, __backtest_cls.now) __backtest_cls.__QA_backtest_send_bid(__backtest_cls, item) # input() func(*arg, **kwargs) # 发委托单 elif __backtest_cls.backtest_type in ['min', 'm']: func(*arg, **kwargs) # 发委托单 # 队列循环批量发单 # 最后一天 __backtest_cls.__end_of_trading(__backtest_cls) @classmethod def backtest_init(__backtest_cls, func, *arg, **kwargs): def __init_backtest(__backtest_cls, *arg, **kwargs): __backtest_cls.__QA_backtest_init(__backtest_cls) func(*arg, **kwargs) __backtest_cls.__QA_backtest_prepare(__backtest_cls) return __init_backtest(__backtest_cls) @classmethod def before_backtest(__backtest_cls, func, *arg, **kwargs): func(*arg, **kwargs) __backtest_cls.__QA_backtest_start(__backtest_cls) @classmethod def end_backtest(__backtest_cls, func, *arg, **kwargs): # yield __backtest_cls.cash __backtest_cls.__end_of_backtest(__backtest_cls, func, *arg, **kwargs) return func(*arg, **kwargs)