【量化】一个简版单档tick数据回测框架

这是一个简易的模拟实际交易流程的回测框架,所使用的行情数据是单档的tick成交数据。为了实现调用者可以实现自己的交易逻辑,本框架预留了几个函数予以调用者能够继承类后在子类中重写以实现买入卖出信号的生成(check_sell()和check_buy())。

因为最近比较忙,忙着实习、放假忙着骑车(找骑友,在上海),所以文档上的内容就没有写得很详细啦,如果想要进一步交流的欢迎私信我或者留言评论,如果需要数据来复线本文的话请私信。

这是一个简易的模拟实际交易流程的回测框架,所使用的行情数据是单档的tick成交数据

直接上图:

为了模拟实际交易流程,在属性中定义了账户信息、下单记录、成交记录和持仓信息这四个表来控制交易流程,交易流程如下所示:

为了实现调用者可以实现自己的交易逻辑,本框架预留了几个函数予以调用者能够继承类后在子类中重写以实现买入卖出信号的生成(check_sell()和check_buy())。

以下是Tickbacktest.py文件中的代码:

# -*- coding=utf-8 -*-
# --------------------------------
# @Time      : 2023年11月6日19:24:47
# @Author    : Noah Zhan
# @File      : tickbacktest.py
# @Project   : tickbacktest
# @Function  :tickbacktest类
# --------------------------------from datetime import datetime
import datetime as dt
import logging
#from typing_extensions import Self
import numpy as np
import pandas as pd
import os
from tqdm import tqdm
from empyrical import max_drawdown,sharpe_ratiodef timeformat(date)->datetime:"""将各种字符串格式的日期数据转化为datetime数据类型"""if isinstance(date,datetime):return dateelif isinstance(date,str):if("/" in date):return pd.to_datetime(datetime.strptime(date,'%Y/%m/%d %H:%M:%S'),format = '%Y-%m-%d %H:%M:%S')elif("-"in date):return pd.to_datetime(datetime.strptime(date,'%Y-%m-%d %H:%M:%S'),format = '%Y-%m-%d %H:%M:%S')else:return pd.to_datetime(datetime.strptime(date,'%Y%m%d %H:%M:%S'),format = '%Y-%m-%d %H:%M:%S')class tickbacktest(object):def __init__(self,name,start_dt,end_dt,now,total_assets = 1000*10000,commissions = 2,slip = 0.2,tick_data = dict(),context = dict()) -> None:'''初始化tickbacktest类。'''self.start_dt = timeformat(start_dt)self.end_dt = timeformat(end_dt)self.now = timeformat(now)self.commissions  = commissions if(tick_data):self.tick_data = tick_datatick_data['tick_all'] = tick_data['tick_all'][(tick_data['tick_all']['time_stamp']>=self.start_dt)&(tick_data['tick_all']['time_stamp']<=self.end_dt) ]#初始化accoun表self.account = pd.DataFrame(index = [name],columns = ['name','initial_cash','total_assets_lastday','total_assets','cash_useable','profit','total_mkt_cap'])self.account.loc[name,'name'] = nameself.account.loc[name,'total_assets_lastday'] = total_assetsself.account.loc[name,'total_assets'] = total_assetsself.account.loc[name,'cash_useable'] = total_assetsself.account.loc[name,'initial_cash'] = total_assetsself.account.loc[name,'profit'] = 0self.account.loc[name,'total_mkt_cap'] = 0#初始化order表self.order = pd.DataFrame(columns=['order_id','stk_id','order_price','order_num','state','order_dt','direction','order_method','num_left'])#初始化deal表self.deal = pd.DataFrame(columns=['deal_id','stk_id','deal_price','deal_num','deal_dt','commission','direction','deal_amount'])#初始化portfolio表self.portfolio = pd.DataFrame(columns=['stk_id','port_num','intraday_buy_num','intraday_sell_num','port_amount','commission','hold_cost','price_now','dynamic_equity','hold_profit','realized_profit','state'])#初始化contex(用于存储可能用到的全局变量参数或资料)self.context = contextcontext['name'] = namecontext['slip'] = 0.01*slip#创建文件夹保存相关文件self.mkdir(name)#配置日志输出logging.basicConfig(filename=name+'/log.txt',format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s-%(funcName)s',level=logging.INFO)self.firstday = True@staticmethoddef mkdir(path) -> None: '''创建文件夹,用于保存回测相关的数据和日志。'''folder = os.path.exists(path)if not folder:                   #判断是否存在文件夹如果不存在则创建为文件夹os.makedirs(path)            #makedirs 创建文件时如果路径不存在会创建这个路径else:print("---  There is this folder!  ---")@staticmethoddef weighted_price(value,volume) -> float:'''给定价格和数量,计算标的的平均价格。'''return np.average(value, weights=volume)def make_order(self,stk_id,order_price,order_num,direction,order_method) -> None:'''function:委托下单,将委托下单信息更新到order表中params:- stk_id: str,需要下单的标的id;- order_price: float,下单限价金额;- order_num: int,委托下单数量;- direction: str,候选值有'buy'和'sell',下单的方向;- order_method: 下单的类型,目前只支持'限价'单。return:- '''index = len(self.order)+1order_toadd = pd.DataFrame(index=[index],columns=['order_id','stk_id','order_price','order_num','state','order_dt','direction','order_method','num_left'])order_toadd.loc[index,'order_id'] = indexorder_toadd.loc[index,'stk_id'] = stk_idorder_toadd['order_price'] = order_priceorder_toadd.loc[index,'order_num'] = order_numorder_toadd.loc[index,'state'] = '未成'order_toadd.loc[index,'order_dt'] = self.noworder_toadd.loc[index,'direction'] = directionorder_toadd.loc[index,'order_method'] = order_methodorder_toadd.loc[index,'num_left'] = order_numself.order = pd.concat([self.order,order_toadd],axis=0)def clear_order(self,order_id) -> None:'''function:将未成的id为order_id单子撤掉,修改相应的单子的stata为'撤单'。params:- order_id: 要撤掉的单子的id。return:- '''self.order.loc[order_id,'state'] = '已撤'logging.info("info,撤单成功-id-"+str(order_id)+'-stkid-'+str(self.order.loc[order_id,'stk_id'])+"-time-"+str(self.order.loc[order_id,'order_dt'])+str(self.order.loc[order_id,'order_price'])+"-price-")def clear_allorder(self) -> None:'''function:将未成的单子全部撤掉,修改相应的单子的stata为'撤单'。params:- order_id: 要撤掉的单子的id。return:- '''order_ids = list(self.order[self.order['state']=='未成']['order_id'])for order_id in order_ids:self.clear_order(order_id)def excecute_deal(self,direction) -> None:'''function:将order表中未成的单子与当前tick的价格和数量进行比对,如果满足成交条件则成交,并修改account、portfolio、order、deal表的相关数据params:- direction:要执行的order的方向。return:- '''order_todo = self.order[(self.order['state']=='未成')&(self.order['direction']==direction)]for ind,data in order_todo.iterrows():dealable = self.tick_data['tick_now'][data['stk_id']]if(direction=='buy'):dealable = dealable[dealable['value']<=data['order_price']]elif(direction=='sell'):dealable = dealable[dealable['value']>=data['order_price']]# print('sell',dealable)if(not dealable.empty):dealable = pd.DataFrame(dealable)for inde,deal in dealable.iterrows():if(isinstance(self.order.loc[ind,'num_left'],pd.Series)):self.order = self.order.reset_index().drop_duplicates(subset=['index'], keep='last').set_index('index')if(((self.order.loc[ind,'num_left']>0) & (self.order.loc[ind,'state']=='未成'))):#判断可用金额是否足够,若不够,则自动调整下单量,以保证交易可执行,并输出日志if((direction=='buy')and(data['num_left'] * (self.weighted_price(list(self.tick_data['tick_now'][data['stk_id']]['value']),list(self.tick_data['tick_now'][data['stk_id']]['volume'])) + self.commissions) > self.account.loc[self.context['name'],'cash_useable'])):data['num_left'] = int(self.account.loc[self.context['name'],'cash_useable']/(self.weighted_price(list(self.tick_data['tick_now'][data['stk_id']]['value']),list(self.tick_data['tick_now'][data['stk_id']]['volume'])) + self.commissions))self.order.loc[inde,'num_left'] = data['num_left']logging.warning('warning,剩余现金不足,将order-'+str(ind)+"下单量自动调整为"+str(data['num_left']))#修改deal表index = len(self.deal)+1deal_toadd = pd.DataFrame(index=[index],columns=['deal_id','stk_id','deal_price','deal_num','deal_dt','commission','direction','deal_amount'])deal_toadd.loc[index,'deal_id'] = indexdeal_toadd.loc[index,'stk_id'] = data['stk_id']deal_toadd.loc[index,'deal_price'] = deal['value']deal_toadd.loc[index,'deal_num'] = min(deal['volume'],data['num_left'])deal_toadd.loc[index,'deal_dt'] = data['order_dt']deal_toadd.loc[index,'commission'] = deal_toadd.loc[index,'deal_num'] * self.commissionsdeal_toadd.loc[index,'direction'] = data['direction']deal_toadd.loc[index,'deal_amount'] = deal_toadd.loc[index,'deal_num'] * deal_toadd.loc[index,'deal_price']self.deal = pd.concat([self.deal,deal_toadd],axis=0)#修改order表self.order.loc[ind,'num_left'] = self.order.loc[ind,'num_left'] - deal_toadd.loc[index,'deal_num']if(self.order.loc[ind,'num_left']<=0): self.order.loc[ind,'state'] = '已成'#修改portfolio和account表if(data['direction']=='buy'):if (data['stk_id'] in list(self.portfolio['stk_id'])):self.portfolio.loc[self.portfolio[self.portfolio['stk_id'] == data['stk_id']].index[0],'intraday_buy_num'] += deal_toadd.loc[index,'deal_num'] self.portfolio.loc[self.portfolio[self.portfolio['stk_id'] == data['stk_id']].index[0],'port_num']=self.portfolio.loc[self.portfolio[self.portfolio['stk_id'] == data['stk_id']].index[0],'intraday_buy_num']-self.portfolio.loc[self.portfolio[self.portfolio['stk_id'] == data['stk_id']].index[0],'intraday_sell_num']self.portfolio.loc[self.portfolio[self.portfolio['stk_id'] == data['stk_id']].index[0],'port_amount'] += deal_toadd.loc[index,'deal_amount'] self.portfolio.loc[self.portfolio[self.portfolio['stk_id'] == data['stk_id']].index[0],'commission'] += deal_toadd.loc[index,'commission'] self.portfolio.loc[self.portfolio[self.portfolio['stk_id'] == data['stk_id']].index[0],'hold_cost'] = self.portfolio.loc[self.portfolio[self.portfolio['stk_id'] == data['stk_id']].index[0],'port_amount'] + self.portfolio.loc[self.portfolio[self.portfolio['stk_id'] == data['stk_id']].index[0],'commission']self.portfolio.loc[self.portfolio[self.portfolio['stk_id'] == data['stk_id']].index[0],'price_now'] = self.weighted_price(list(self.tick_data['tick_now'][data['stk_id']]['value']),list(self.tick_data['tick_now'][data['stk_id']]['volume']))self.portfolio.loc[self.portfolio[self.portfolio['stk_id'] == data['stk_id']].index[0],'dynamic_equity'] = self.portfolio.loc[self.portfolio[self.portfolio['stk_id'] == data['stk_id']].index[0],'port_num'] * self.portfolio.loc[self.portfolio[self.portfolio['stk_id'] == data['stk_id']].index[0],'price_now']self.portfolio.loc[self.portfolio[self.portfolio['stk_id'] == data['stk_id']].index[0],'hold_profit'] = self.portfolio.loc[self.portfolio[self.portfolio['stk_id'] == data['stk_id']].index[0],'dynamic_equity']-self.portfolio.loc[self.portfolio[self.portfolio['stk_id'] == data['stk_id']].index[0],'hold_cost']self.portfolio.loc[self.portfolio[self.portfolio['stk_id'] == data['stk_id']].index[0],'state'] = '持仓'self.account.loc[self.context['name'],'cash_useable'] -= (deal_toadd.loc[index,'deal_amount']+deal_toadd.loc[index,'commission'])self.account.loc[self.context['name'],'total_mkt_cap'] = sum(self.portfolio['dynamic_equity'])self.account.loc[self.context['name'],'total_assets'] =self.account.loc[self.context['name'],'cash_useable'] +self.account.loc[self.context['name'],'total_mkt_cap']self.account.loc[self.context['name'],'profit'] = self.account.loc[self.context['name'],'total_assets'] - self.account.loc[self.context['name'],'initial_cash']else:portfolio_toadd = pd.DataFrame(index=[len(self.portfolio)+1],columns=['stk_id','port_num','intraday_buy_num','intraday_sell_num','port_amount','commission','hold_cost','price_now','dynamic_equity','hold_profit','realized_profit','state'])portfolio_toadd.loc[len(self.portfolio)+1,'intraday_buy_num'] = deal_toadd.loc[index,'deal_num'] portfolio_toadd.loc[len(self.portfolio)+1,'intraday_sell_num'] = 0portfolio_toadd.loc[len(self.portfolio)+1,'stk_id'] = data['stk_id']portfolio_toadd.loc[len(self.portfolio)+1,'port_num'] = deal_toadd.loc[index,'deal_num'] portfolio_toadd.loc[len(self.portfolio)+1,'port_amount'] = deal_toadd.loc[index,'deal_amount'] portfolio_toadd.loc[len(self.portfolio)+1,'commission'] = deal_toadd.loc[index,'commission'] portfolio_toadd.loc[len(self.portfolio)+1,'hold_cost'] = deal_toadd.loc[index,'deal_amount'] + deal_toadd.loc[index,'commission'] portfolio_toadd.loc[len(self.portfolio)+1,'price_now'] = self.weighted_price(list(self.tick_data['tick_now'][data['stk_id']]['value']),list(self.tick_data['tick_now'][data['stk_id']]['volume']))portfolio_toadd.loc[len(self.portfolio)+1,'dynamic_equity'] = deal_toadd.loc[index,'deal_num'] * self.weighted_price(list(self.tick_data['tick_now'][data['stk_id']]['value']),list(self.tick_data['tick_now'][data['stk_id']]['volume']))portfolio_toadd.loc[len(self.portfolio)+1,'hold_profit'] = portfolio_toadd.loc[len(self.portfolio)+1,'dynamic_equity'] - portfolio_toadd.loc[len(self.portfolio)+1,'hold_cost']portfolio_toadd.loc[len(self.portfolio)+1,'realized_profit'] = 0portfolio_toadd.loc[len(self.portfolio)+1,'state'] = '持仓'self.portfolio = pd.concat([self.portfolio,portfolio_toadd],axis=0)#合并self.account.loc[self.context['name'],'cash_useable'] -=(deal_toadd.loc[index,'deal_amount']+deal_toadd.loc[index,'commission'])self.account.loc[self.context['name'],'total_mkt_cap'] = sum(self.portfolio['dynamic_equity'])self.account.loc[self.context['name'],'total_assets'] = self.account.loc[self.context['name'],'cash_useable'] +self.account.loc[self.context['name'],'total_mkt_cap']self.account.loc[self.context['name'],'profit'] = self.account.loc[self.context['name'],'total_assets'] - self.account.loc[self.context['name'],'initial_cash']elif(data['direction']=='sell'):#卖出则持仓中必须有该标的资产if(self.portfolio.loc[self.portfolio[self.portfolio['stk_id'] == data['stk_id']].index[0],'port_num']<=0):self.clear_order(order_id=data['order_id'])#持仓不足,撤卖单continueself.portfolio.loc[self.portfolio[self.portfolio['stk_id'] == data['stk_id']].index[0],'intraday_sell_num'] +=deal_toadd.loc[index,'deal_num'] self.portfolio.loc[self.portfolio[self.portfolio['stk_id'] == data['stk_id']].index[0],'port_num'] = self.portfolio.loc[self.portfolio[self.portfolio['stk_id'] == data['stk_id']].index[0],'intraday_buy_num'] - self.portfolio.loc[self.portfolio[self.portfolio['stk_id'] == data['stk_id']].index[0],'intraday_sell_num']amount_temp = float(self.portfolio.loc[self.portfolio[self.portfolio['stk_id'] == data['stk_id']].index[0],'port_amount'])self.portfolio.loc[self.portfolio[self.portfolio['stk_id'] == data['stk_id']].index[0],'port_amount'] *=(self.portfolio.loc[self.portfolio[self.portfolio['stk_id'] == data['stk_id']].index[0],'port_num']/(deal_toadd.loc[index,'deal_num'] +self.portfolio.loc[self.portfolio[self.portfolio['stk_id'] == data['stk_id']].index[0],'port_num']))self.portfolio.loc[self.portfolio[self.portfolio['stk_id'] == data['stk_id']].index[0],'commission'] -=deal_toadd.loc[index,'commission'] self.portfolio.loc[self.portfolio[self.portfolio['stk_id'] == data['stk_id']].index[0],'hold_cost'] = self.portfolio.loc[self.portfolio[self.portfolio['stk_id'] == data['stk_id']].index[0],'port_amount'] + self.portfolio.loc[self.portfolio[self.portfolio['stk_id'] == data['stk_id']].index[0],'commission']self.portfolio.loc[self.portfolio[self.portfolio['stk_id'] == data['stk_id']].index[0],'price_now'] = self.weighted_price(list(self.tick_data['tick_now'][data['stk_id']]['value']),list(self.tick_data['tick_now'][data['stk_id']]['volume']))self.portfolio.loc[self.portfolio[self.portfolio['stk_id'] == data['stk_id']].index[0],'dynamic_equity'] = self.portfolio.loc[self.portfolio[self.portfolio['stk_id'] == data['stk_id']].index[0],'port_num'] * self.portfolio.loc[self.portfolio[self.portfolio['stk_id'] == data['stk_id']].index[0],'price_now']self.portfolio.loc[self.portfolio[self.portfolio['stk_id'] == data['stk_id']].index[0],'hold_profit'] = self.portfolio.loc[self.portfolio[self.portfolio['stk_id'] == data['stk_id']].index[0],'dynamic_equity']-self.portfolio.loc[self.portfolio[self.portfolio['stk_id'] == data['stk_id']].index[0],'hold_cost']self.portfolio.loc[self.portfolio[self.portfolio['stk_id'] == data['stk_id']].index[0],'realized_profit'] += (deal_toadd.loc[index,'deal_num']*(self.portfolio.loc[self.portfolio[self.portfolio['stk_id'] == data['stk_id']].index[0],'price_now']-self.commissions))-(deal_toadd.loc[index,'deal_num']/(deal_toadd.loc[index,'deal_num'] +self.portfolio.loc[self.portfolio[self.portfolio['stk_id'] == data['stk_id']].index[0],'port_num']))*amount_tempif(self.portfolio.loc[self.portfolio[self.portfolio['stk_id'] == data['stk_id']].index[0],'port_num']==0):self.portfolio.loc[self.portfolio[self.portfolio['stk_id'] == data['stk_id']].index[0],'state'] = '已卖'self.account.loc[self.context['name'],'cash_useable'] +=(deal_toadd.loc[index,'deal_amount']-deal_toadd.loc[index,'commission'])self.account.loc[self.context['name'],'total_mkt_cap'] = sum(self.portfolio['dynamic_equity'])self.account.loc[self.context['name'],'total_assets']  = self.account.loc[self.context['name'],'cash_useable'] +self.account.loc[self.context['name'],'total_mkt_cap']self.account.loc[self.context['name'],'profit'] = self.account.loc[self.context['name'],'total_assets'] - self.account.loc[self.context['name'],'initial_cash']logging.info('info,'+str(data['stk_id'])+"-"+str(deal_toadd.loc[index,'deal_num'])+"-"+str(data['direction'])+"-deal id:"+str(deal_toadd.loc[index,'deal_id']))# print('info,'+str(data['stk_id'])+"-"+str(deal_toadd.loc[index,'deal_num'])+"-"+str(data['direction'])+"-deal id:"+str(deal_toadd.loc[index,'deal_id']))returndef every_tick(self) -> None:'''function:定义每一个新tick应该做一些什么,包括1)更新self.now,self.tick_data['tick_now'];2)更新portfolio表相关信息;3)更新account表相关信息;4)本tick要卖出的stk_id和数量list并下单、执行委托;5)本tick要买入的stk_id和数量list并下单、执行委托.params:- return:- '''#1)更新self.now,self.tick_data['tick_now']self.now = self.now  + dt.timedelta(seconds=1)self.tick_data['tick_now'] = dict()for stk_id in set(self.tick_data['tick_all']['security_id']):self.tick_data['tick_now'][stk_id] = self.tick_data['tick_all'][(self.tick_data['tick_all']['time_stamp']==self.now)&(self.tick_data['tick_all']['security_id']==stk_id)]i=1while(self.tick_data['tick_now'][stk_id].empty):self.tick_data['tick_now'][stk_id] = self.tick_data['tick_all'][(self.tick_data['tick_all']['time_stamp']==(self.now+dt.timedelta(seconds=-1*i)))&(self.tick_data['tick_all']['security_id']==stk_id)]i += 1# print(self.tick_data['tick_now'])#2)更新portfolio表相关信息for ind,row in self.portfolio.iterrows():if(not self.tick_data['tick_now'][row['stk_id']].empty):self.portfolio.loc[ind,'price_now'] = self.weighted_price(list(self.tick_data['tick_now'][row['stk_id']]['value']),list(self.tick_data['tick_now'][row['stk_id']]['volume']))self.portfolio.loc[ind,'dynamic_equity'] = self.portfolio.loc[ind,'price_now'] * self.portfolio.loc[ind,'port_num'] self.portfolio.loc[ind,'hold_profit'] = self.portfolio.loc[ind,'dynamic_equity'] - self.portfolio.loc[ind,'hold_cost']#3)更新account表相关信息self.account.loc[self.context['name'],'total_mkt_cap'] = sum(self.portfolio['dynamic_equity'])self.account.loc[self.context['name'],'total_assets'] = self.account.loc[self.context['name'],'total_mkt_cap'] + self.account.loc[self.context['name'],'cash_useable']self.account.loc[self.context['name'],'profit'] = self.account.loc[self.context['name'],'total_assets'] - self.account.loc[self.context['name'],'initial_cash']#4)本tick要卖出的stk_id和数量并下单、执行委托;sell_id_list,sell_num_list = self.check_sell()if(not(len(sell_id_list)==0 or len(sell_num_list)==0)):for sell_id,sell_num in zip(sell_id_list,sell_num_list):self.make_order(stk_id=sell_id, order_price = self.weighted_price(self.tick_data['tick_now'][sell_id]['value'],self.tick_data['tick_now'][sell_id]['volume'])*(1-self.context['slip']), order_num=sell_num, direction='sell', order_method='限价')self.excecute_deal(direction='sell')# 5)本tick要买入的stk_id和数量list并下单、执行委托;buy_id_list,buy_num_list = self.check_buy()if(not(len(buy_id_list)==0 or len(buy_num_list)==0)):for buy_id,buy_num in zip(buy_id_list,buy_num_list):self.make_order(stk_id=buy_id, order_price = self.weighted_price(self.tick_data['tick_now'][buy_id]['value'],self.tick_data['tick_now'][buy_id]['volume'])*(1+self.context['slip']), order_num=buy_num, direction='buy', order_method='限价')self.excecute_deal(direction='buy')#6)每一分钟,输出账户信息到文件中。if(self.now.second==0):account_toadd = self.account.copy()account_toadd['datetime'] = self.nowaccount_toadd.set_index(['datetime'])if os.path.exists(self.context['name'] + '/account_tick.csv'):account_toadd.to_csv(self.context['name'] + '/account_tick.csv', mode='a', header=False)else:account_toadd.to_csv(self.context['name'] + '/account_tick.csv')returndef check_sell(self) -> tuple:'''function: 用于生成当tick需要卖出的股票的列表和数量,同时这一方法是开放给使用时进行重写的,以实现调用者自己的策略逻辑;params:- return:- sell_id_list: 需要卖出的股票的列表;- sell_num_list: 需要卖出的股票对应的数量列表。'''sell_id_list,sell_num_list = [],[]return sell_id_list,sell_num_listdef check_buy(self)-> tuple:'''function: 用于生成当tick需要买入的股票的列表和数量,同时这一方法是开放给使用时进行重写的,以实现调用者自己的策略逻辑;params:- return:- buy_id_list: 需要买入的股票的列表;- buy_num_list: 需要买入的股票对应的数量列表。'''buy_id_list,buy_num_list  = [],[]return buy_id_list,buy_num_list def every_morning(self)-> None:'''function:定义每一个新的交易日应该做一些什么,包括1)更新self.now;2)保存上一日的持仓信息到表portfolio.csv中,并做相应更新;3)保存上一日的account信息到account.csv中,并做相应更新;4)进行使用者自行定义的早上要进行的操作。*注意params:- return:- '''#1)更新self.nowself.context['last_dt'] = datetime(self.now.year,self.now.month,self.now.day,14,30,0)self.now = datetime(self.now.year,self.now.month,self.now.day+1,8,59,59)while(not datetime(self.now.year,self.now.month,self.now.day) in list(self.tick_data['tick_all']['time_stamp'].apply(lambda x:datetime(x.year,x.month,x.day)))):#要确保是交易日self.now = datetime(self.now.year,self.now.month,self.now.day+1,8,59,59)#2)保存上一日的持仓信息到表portfolio.csv中,并做相应更新;if(not self.portfolio.empty):portfolio_toadd = self.portfolio.copy()portfolio_toadd['date'] = self.context['last_dt']portfolio_toadd.set_index(['date','stk_id'])if os.path.exists(self.context['name'] + '/portfolio.csv'):portfolio_toadd.to_csv(self.context['name'] + '/portfolio.csv', mode='a', header=False)else:portfolio_toadd.to_csv(self.context['name'] + '/portfolio.csv')self.portfolio = self.portfolio[~(self.portfolio['state']=='已卖')]#3)保存上一日的account信息到account.csv中,并做相应更新if(not self.account.empty):account_toadd = self.account.copy()account_toadd['date'] = self.context['last_dt']account_toadd['return'] = account_toadd['total_assets'] / account_toadd['total_assets_lastday'] - 1account_toadd.set_index(['date'])if os.path.exists(self.context['name'] + '/account.csv'):account_toadd.to_csv(self.context['name'] + '/account.csv', mode='a', header=False)else:account_toadd.to_csv(self.context['name'] + '/account.csv')#更新self.account['total_assets_lastday']self.account['total_assets_lastday'] = account_toadd['total_assets'] #4)进行使用者自行定义的早上要进行的操作self.dosomethin_in_morning()returndef before_close(self)-> None:'''function:定义收盘前(收盘前一分钟)应该做一些什么,留给用户来重写,如果持仓不过夜,请在这里实现清仓,会在每日收盘前一分钟调用调用.params:- return:- '''self.clear_allorder()def dosomethin_in_morning(self)-> None:'''function:定义早上要做些什么,除了已经定义的every_morning内的其他操作之外,用户可以重写此方法在早上马上开盘时进行操作。params:- return:- '''passdef pipline(self)-> None:'''function: 在这个函数中进行回测流程的控制,调用这个函数以进行回测;params:- return:- '''logging.info('info,'+str(self.context['name'])+'回测开始')pbar = tqdm(len(list(set(self.tick_data['tick_all']['time_stamp']))))while(self.now<=self.end_dt):if(self.firstday):self.firstday = Falseelse:self.every_morning()if(self.now>self.end_dt):breakwhile(not((self.now.hour==14) and (self.now.minute==29) and (self.now.second==0))):self.every_tick()pbar.update(1)if(self.now>self.end_dt):breakif(self.now>self.end_dt):breakself.before_close()logging.info('info,'+str(self.context['name'])+'回测结束')returndef summary(self)-> None:'''function: gross and net of commissions return, P&L, annualized volatility of the strategy, Sharpe, Maximum Drawdown and P-value.params:- return:- '''data = pd.read_csv(self.context['name'] + '/account.csv')if data.empty:print('请先进行回测。')else:data = data.reset_index()data = data.sort_values('date',ascending=True)#returnreturn_ = data.iloc[-1,:]['total_assets'] / data.iloc[-1,:]['initial_cash'] - 1#P&LPandL = data.iloc[-1,:]['total_assets'] - data.iloc[-1,:]['initial_cash']#volatilityvolatility = data['return'].std()#SharpeSharpe = sharpe_ratio(data['return'], risk_free=0, period='daily')#Maximum Drawdownmax_drawdown_ = max_drawdown(data['return'])from prettytable import PrettyTablex = PrettyTable()x.padding_width = 2x.add_column("回测", [self.context['name']])x.add_column("回测收益", [str(round(float(return_ * 100), 2)) + "%"])x.add_column("P&L", [str(round(float(PandL), 2))])x.add_column("年化波动率", [str(round(float(volatility), 2))])x.add_column("夏普", [str(round(Sharpe, 2))])x.add_column("最大回撤", [str(round(max_drawdown_*100, 2))+ "%"])print(x)

对上述代码进行实际应用,继承backtest方法并重写相关的方法,实现调用者自己的逻辑(check_sell,check_buy,before_close):

这里使用了简单的均线策略进行测试。

import pandas as pd
import numpy as np
from tickbacktest import *
import datetime as dt
#读取数据
data = pd.read_csv('Sample Tick Data.csv')
data = data.groupby(['time_stamp','security_id','value'])['volume'].apply(lambda x:sum(x)).reset_index()
data['time_stamp'] = pd.to_datetime(data['time_stamp'])#继承backtest方法并重写相关的方法,实现调用者自己的逻辑(check_sell,check_buy,before_close)
class my_tickbacktest(tickbacktest):def __init__(self,stk_id,average_short_min,average_long_min,trade_period_restriction,name,start_dt,end_dt,now,total_assets = 1000*10000,commissions = 2,slip = 0.2,tick_data = dict(),context = dict()):super().__init__(name,start_dt,end_dt,now,total_assets,commissions,slip,tick_data,context)self.stk_id = stk_idself.context['signal_record'] = pd.DataFrame(columns=['time_stamp', 'contract', 'moving_average_1', 'moving_average_2', 'signal', 'return'])self.average_short_min = average_short_minself.average_long_min = average_long_minself.average_short_line = []self.average_long_line = []self.signal = 0self.trade_time = 0 self.trade_period_restriction = trade_period_restrictionself.istrading_buy = 0self.istrading_sell = 0def moving_average(self,stk_id):short_panel = self.tick_data['tick_all'][(self.tick_data['tick_all']['time_stamp']>=self.now + dt.timedelta(seconds=-1*self.average_short_min*60))&(self.tick_data['tick_all']['time_stamp']<=self.now)]short_panel = short_panel[short_panel['security_id'] == stk_id]short_price = self.weighted_price(short_panel['value'],short_panel['volume'])long_panel = self.tick_data['tick_all'][(self.tick_data['tick_all']['time_stamp']>=self.now + dt.timedelta(seconds=-1*self.average_long_min*60))&(self.tick_data['tick_all']['time_stamp']<=self.now)]long_panel = long_panel[long_panel['security_id'] == stk_id]long_price = self.weighted_price(long_panel['value'],long_panel['volume'])return short_price,long_pricedef check_sell(self):'''function:重写check_sell的功能,实现卖出选股params:- return:- sell_id_list: 需要卖出的股票的列表;- sell_num_list: 需要卖出的股票对应的数量列表。'''short_price,long_price = self.moving_average(self.stk_id)self.average_short_line.append(short_price)self.average_long_line.append(long_price)if(not (self.stk_id in list(self.portfolio['stk_id']))):return [],[]sell_id_list,sell_num_list = [],[]#生成信号if((len(self.average_long_line)>=2 and len(self.average_short_line)>=2)and(not self.istrading_sell)):if(self.average_long_line[-2]>self.average_short_line[-2] and self.average_long_line[-1]<self.average_short_line[-1]): #输出日志logging.info('info,卖出信号-'+str(self.stk_id)+'-short Average:'+str(self.average_long_line[-1])+'-short Average:'+str(self.average_long_line[-1]))#更新信号记录表temp_signal_record = pd.DataFrame(index=[self.now],columns=['time_stamp', 'contract', 'moving_average_1', 'moving_average_2', 'signal', 'return'])temp_signal_record.loc[self.now,'time_stamp'] = self.nowtemp_signal_record.loc[self.now,'contract'] = self.stk_idtemp_signal_record.loc[self.now,'moving_average_1'] = self.average_short_line[-1]temp_signal_record.loc[self.now,'moving_average_2'] = self.average_long_line[-1]temp_signal_record.loc[self.now,'signal'] = -1temp_signal_record.loc[self.now,'return'] = self.account.loc[self.context['name'],'profit']/self.account.loc[self.context['name'],'initial_cash']self.context['signal_record'] = pd.concat([self.context['signal_record'],temp_signal_record],axis=0)if os.path.exists(self.context['name'] + '/signal_record.csv'):temp_signal_record.to_csv(self.context['name'] + '/signal_record.csv', mode='a', header=False)else:temp_signal_record.to_csv(self.context['name'] + '/signal_record.csv')#其他操作self.clear_allorder()self.signal = -1self.trade_time = 0 self.istrading_buy = 0self.istrading_sell = 1elif((self.istrading_sell) and (self.trade_time < self.trade_period_restriction)):self.signal = -1else:self.signal = 0#根据信号生成sell_id_list,sell_num_listif(self.signal==-1 and self.istrading_sell==1):sell_num = int(self.portfolio[self.portfolio['stk_id']==self.stk_id]['port_num'] / (self.trade_period_restriction-self.trade_time))sell_id_list.append(self.stk_id)sell_num_list.append(sell_num)self.trade_time = self.trade_time + 1return sell_id_list,sell_num_listdef check_buy(self):'''function:重写check_buy的功能,实现买入选股params:- return:- buy_id_list: 需要买入的股票的列表;- buy_num_list: 需要买入的股票对应的数量列表。'''buy_id_list,buy_num_list = [],[]#生成信号if((len(self.average_long_line)>=2 and len(self.average_short_line)>=2 and (not self.istrading_buy))):if(self.average_long_line[-2]<self.average_short_line[-2] and self.average_long_line[-1]>self.average_short_line[-1]): #更新日志logging.info('info,买入信号-'+str(self.stk_id)+'-short Average:'+str(self.average_long_line[-1])+'-short Average:'+str(self.average_long_line[-1]))#更新信号记录表temp_signal_record = pd.DataFrame(index=[self.now],columns=['time_stamp', 'contract', 'moving_average_1', 'moving_average_2', 'signal', 'return'])temp_signal_record.loc[self.now,'time_stamp'] = self.nowtemp_signal_record.loc[self.now,'contract'] = self.stk_idtemp_signal_record.loc[self.now,'moving_average_1'] = self.average_short_line[-1]temp_signal_record.loc[self.now,'moving_average_2'] = self.average_long_line[-1]temp_signal_record.loc[self.now,'signal'] = 1temp_signal_record.loc[self.now,'return'] = self.account.loc[self.context['name'],'profit']/self.account.loc[self.context['name'],'initial_cash']self.context['signal_record'] = pd.concat([self.context['signal_record'],temp_signal_record],axis=0)if os.path.exists(self.context['name'] + '/signal_record.csv'):temp_signal_record.to_csv(self.context['name'] + '/signal_record.csv', mode='a', header=False)else:temp_signal_record.to_csv(self.context['name'] + '/signal_record.csv')#其他操作self.clear_allorder()self.signal = 1self.trade_time = 0 self.istrading_buy = 1self.istrading_sell = 0elif((self.istrading_buy) and (self.trade_time < self.trade_period_restriction)):self.signal = 1else:self.signal = 0#根据信号生成buy_id_list,buy_num_listif(self.signal==1 and self.istrading_buy==1):try:buy_num = int(self.account.cash_useable / self.weighted_price(self.tick_data['tick_now'][self.stk_id]['value'],self.tick_data['tick_now'][self.stk_id]['volume']) / (self.trade_period_restriction-self.trade_time))except KeyError:temp_tick=pd.DataFrame()i = 1while(temp_tick.empty):temp_tick = self.tick_data['tick_all'][(self.tick_data['tick_all']['time_stamp']==(self.now+dt.timedelta(seconds=-1*i)))&(self.tick_data['tick_all']['security_id']==self.stk_id)]i += 1buy_num = int(self.account.cash_useable / self.weighted_price(temp_tick['value'],temp_tick['volume']) / (self.trade_period_restriction-self.trade_time))if(not buy_num==0):buy_id_list.append(self.stk_id)buy_num_list.append(buy_num)self.trade_time = self.trade_time + 1return buy_id_list,buy_num_listdef before_close(self)-> None:'''function:定义收盘前(收盘前一分钟)应该做一些什么,1)撤掉所有未成订单;2)以1分钟内的平均价格清仓所有持仓params:- return:- '''#1) 撤掉所有未成订单;self.clear_allorder()#2)以1分钟内的平均价格清仓所有持仓;(假设全成)#如果没有持仓则直接passif(len(self.portfolio)<1):returnelse:#更新self.portfoliofor ind,row in self.portfolio.iterrows():self.portfolio.loc[ind,'intraday_sell_num'] +=self.portfolio.loc[ind,'port_num']port_num_temp = self.portfolio.loc[ind,'port_num']self.portfolio.loc[ind,'port_num'] = 0self.portfolio.loc[ind,'port_amount'] = 0self.portfolio.loc[ind,'commission'] = 0hold_cost_temp = float(self.portfolio.loc[ind,'hold_cost'])self.portfolio.loc[ind,'hold_cost'] = 0self.portfolio.loc[ind,'price_now'] = '-'self.portfolio.loc[ind,'dynamic_equity'] = 0self.portfolio.loc[ind,'hold_profit'] = 0#计算一分钟内平均价temp_tick = self.tick_data['tick_all'][(self.tick_data['tick_all']['time_stamp']>=self.now)&(self.tick_data['tick_all']['time_stamp']<(self.now+dt.timedelta(minutes=1)))]temp_tick = temp_tick[temp_tick['security_id']==self.stk_id]value = temp_tick['value']volume = temp_tick['volume']self.portfolio.loc[ind,'realized_profit'] += (port_num_temp*(self.weighted_price(value,volume)-self.commissions) - hold_cost_temp)self.portfolio.loc[ind,'state'] = '已卖'#更新accountself.account.loc[self.context['name'],'cash_useable'] += (port_num_temp*(self.weighted_price(value,volume)-self.commissions))self.account.loc[self.context['name'],'total_mkt_cap'] = sum(self.portfolio['dynamic_equity'])self.account.loc[self.context['name'],'total_assets'] =self.account.loc[self.context['name'],'cash_useable']+self.account.loc[self.context['name'],'total_mkt_cap']self.account.loc[self.context['name'],'profit'] = self.account.loc[self.context['name'],'total_assets'] - self.account.loc[self.context['name'],'initial_cash']#更新日志#进行回测
tick_data = {'tick_all':data,'tick_now':dict()}
test = my_tickbacktest(stk_id='CLZ4',average_short_min=3,average_long_min=6,trade_period_restriction=60,name='CLZ4-3-6',start_dt='2014-11-03 9:00:00',end_dt='2014-11-03 11:00:00',now='2014-11-03 08:59:59',total_assets = 10*10000,commissions = 2,slip = 0.2,tick_data = tick_data,context = dict())
test.pipline()

买入卖出信号预览:

pd.DataFrame([test.average_long_line,test.average_short_line]).T.plot()

回测结果:

回测结果保存到本地:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/157928.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Docker Swarm总结

1、swarm 理论基础 1.1 简介 Docker Swarm 是由 Docker 公司推出的 Docker 的原生集群管理系统&#xff0c;它将一个 Docker 主机池变成了一个单独的虚拟主机&#xff0c;用户只需通过简单的 API 即可实现与 Docker 集群的通 信。Docker Swarm 使用 GO 语言开发。从 Docker 1.…

Ajax基础(应用场景|jquery实现Ajax|注意事项|Ajax发送json数据|Ajax携带文件数据)

文章目录 一、Ajax简介二、基于jquery实现Ajax三、使用Ajax注意的问题1.Ajax不要与form表单同时提交2.后端响应格式问题3、使用了Ajax作为请求后的注意事项 四、前后端数据传输的编码格式(content-Type)1.urlencoded2.formdata3.application/json 五、Ajax携带文件数据六、Ajax…

代码随想录第六十三天 | 单调栈:寻找 左边 / 右边 距离当前元素最近的 更小 元素的 下标(暴力,双指针,单调栈)(84);代码随想录主要题目结束

1、寻找 左边 / 右边 距离当前元素最近的 更小 元素的 下标 1.1 leetcode 84&#xff1a;柱状图中最大的矩形 第一遍代码思路错了&#xff0c;如&#xff1a;输入[2,1,2]&#xff0c;对于2&#xff0c;因为比栈顶元素1大&#xff0c;然后就会直接得出2&#xff08;1&#xff…

etoken是什么意思,有什么作用?

EToken是一种数字货币&#xff0c;它是由以太坊区块链平台发行的智能合约&#xff0c;旨在为以太坊生态系统提供一种安全、可靠、去中心化的交易媒介。EToken具有多种作用&#xff0c;下面将详细介绍。 一、EToken的定义和发行 EToken是由以太坊智能合约创建的数字货币&#xf…

渲染器——快速Diff算法

讨论第三种用于比较新旧两组子节点的方式&#xff1a;快速Diff 算法。正如其名&#xff0c;该算法的实测速度非常快。该算法最早应用于 ivi 和 inferno 这两个框架&#xff0c;Vue.js 3 借鉴并扩展了它。 下图比较了 ivi、inferno 以及 Vue.js 2 的性能&#xff1a; 上图来自…

Redis持久化机制详解

使用缓存的时候&#xff0c;我们经常需要对内存中的数据进行持久化也就是将内存中的数据写入到硬盘中。大部分原因是为了之后重用数据&#xff08;比如重启机器、机器故障之后恢复数据&#xff09;&#xff0c;或者是为了做数据同步&#xff08;比如 Redis 集群的主从节点通过 …

Qt程序的自定义安装卸载方案

前言 NSIS 是一个 Open Source 的 Windows 系统下安装程序制作程序&#xff1b; NSIS-UI-Plugin 是一个开源的NSIS UI插件&#xff1b; 0x0 环境搭建 https://www.cnblogs.com/NSIS/p/16581122.html https://github.com/sway913/NSIS-UI-Plugin 0x1 类图 0x2 二次开发 自定…

持续集成失败:hudson.plugins.git.GitException: Failed to delete workspace

持续集成环境(git gitlab jenkins pipeline maven harbor docker k8s)之前都是ok的&#xff0c;突然就报错了&#xff1a; Cloning the remote Git repository Cloning repository git192.168.117.180:qzcsbj/gift.git ERROR: Failed to clean the workspace jenkins.ut…

【开源】基于Vue和SpringBoot的高校宿舍调配管理系统

项目编号&#xff1a; S 051 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S051&#xff0c;文末获取源码。} 项目编号&#xff1a;S051&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能需求2.1 学生端2.2 宿管2.3 老师端 三、系统…

【C++】C++11(2)

文章目录 一、新的类功能二、可变参数模板&#xff08;了解&#xff09;三、lambda表达式1. C98中的一个例子2.lambda表达式3.lambda表达式语法4.函数对象与lambda表达式 四、包装器1.function包装器2.bind 五、线程库1.thread类的简单介绍2.线程函数参数3.原子性操作库(atomic…

【SEO学习】技术总结

我们已经涵盖了几乎所有与搜索引擎优化相关的主要概念。现在您也熟悉了最常用的 SEO 相关术语。 您已经学会了如何从 SEO 的角度优化关键字、标题、alt、元标签、锚和其他文本。您还了解了在您的网站中拥有优质内容的重要性。在“杂项技术”一章中&#xff0c;我们为您提供了其…

泛型概述(下):泛型实现机制

作者简介&#xff1a;大家好&#xff0c;我是smart哥&#xff0c;前中兴通讯、美团架构师&#xff0c;现某互联网公司CTO 联系qq&#xff1a;184480602&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗互联网寒冬 上篇提到泛型可以看做是…

kafka权限认证 topic权限认证 权限动态认证-亲测成功

kafka权限认证 topic权限认证 权限动态认证-亲测成功 kafka动态认证 自定义认证 安全认证-亲测成功 MacBook Linux安装Kafka Linux解压安装Kafka 介绍 1、Kafka的权限分类 身份认证&#xff08;Authentication&#xff09;&#xff1a;对client 与服务器的连接进行身份认证…

代码随想录算法训练营第五十天| 309.最佳买卖股票时机含冷冻期 714.买卖股票的最佳时机含手续费

文档讲解&#xff1a;代码随想录 视频讲解&#xff1a;代码随想录B站账号 状态&#xff1a;看了视频题解和文章解析后做出来了 309.最佳买卖股票时机含冷冻期 class Solution:def maxProfit(self, prices: List[int]) -> int:n len(prices)if n < 2:return 0dp [[0]*3…

vue2【axios请求】

1&#xff1a;axios作用 axios&#xff08;发音&#xff1a;艾克c奥斯&#xff09;是前端圈最火的&#xff0c;专注于数据请求的库。 Axios 是一个基于 promise 的 HTTP 库&#xff0c;可以用在浏览器和 node.js 中axios的github:https://github.com/axios/axios 中文官网地址…

【opencv】计算机视觉:停车场车位实时识别

目录 目标 整体流程 背景 详细讲解 目标 我们想要在一个实时的停车场监控视频中&#xff0c;看看要有多少个车以及有多少个空缺车位。然后我们可以标记空的&#xff0c;然后来车之后&#xff0c;实时告诉应该停在那里最方便、最近&#xff01;&#xff01;&#xff01;实现…

C++ 使用c++类模板实现动态数组-可实现自定义数据类型存储

.hpp文件 #include <iostream> #include <cstdlib> #include <cstring> using namespace std; template <class T> class arraylist { private:T* data ;//数组地址int size;//长度int count;//容量public:arraylist();~arraylist();void add(T t);T&…

GitHub 报告发布:TypeScript 取代 Java 成为第三受欢迎语言

GitHub发布的2023年度Octoverse开源状态报告发布&#xff0c;研究围绕AI、云和Git的开源活动如何改变开发人员体验&#xff0c;以及在开发者和企业中产生的影响。报告发现了三大趋势&#xff1a; 1、生成式AI的广泛应用&#xff1a; 开发人员大量使用生成式AI进行构建。越来越…

[Linux] 进程入门

&#x1f4bb;文章目录 &#x1f4c4;前言计算机的结构体系与概念冯诺依曼体系结构操作系统概念目的与定位 进程概念描述进程-PCBtask_struct检查进程利用fork创建子进程 进程状态进程状态查看僵尸进程孤儿进程 &#x1f4d3;总结 &#x1f4c4;前言 作为一名程序员&#xff0c…

Python 跨文件夹导入自定义包

一、问题再现 有时我们自己编写一些模块时&#xff0c;跨文件夹调用会出现ModuleNotFoundError: No module named XXX 二、解决方案 只需要在下层文件夹中的__init__.py文件中&#xff0c;添加如下代码即可&#xff1a; import sys from os import path sys.path.append(pa…