【量化】一个简版单档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_ratio


def timeformat(date)->datetime:
    """
    将各种字符串格式的日期数据转化为datetime数据类型
    """
    if isinstance(date,datetime):
        return date
    elif 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_data
        tick_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'] = name
        self.account.loc[name,'total_assets_lastday'] = total_assets
        self.account.loc[name,'total_assets'] = total_assets
        self.account.loc[name,'cash_useable'] = total_assets
        self.account.loc[name,'initial_cash'] = total_assets
        self.account.loc[name,'profit'] = 0
        self.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 = context
        context['name'] = name
        context['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

    @staticmethod
    def mkdir(path) -> None: 
        '''
        创建文件夹,用于保存回测相关的数据和日志。
        '''
        folder = os.path.exists(path)
        if not folder:                   #判断是否存在文件夹如果不存在则创建为文件夹
            os.makedirs(path)            #makedirs 创建文件时如果路径不存在会创建这个路径
        else:
            print("---  There is this folder!  ---")

    @staticmethod
    def 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)+1
        order_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'] = index
        order_toadd.loc[index,'stk_id'] = stk_id
        order_toadd['order_price'] = order_price
        order_toadd.loc[index,'order_num'] = order_num
        order_toadd.loc[index,'state'] = '未成'
        order_toadd.loc[index,'order_dt'] = self.now
        order_toadd.loc[index,'direction'] = direction
        order_toadd.loc[index,'order_method'] = order_method
        order_toadd.loc[index,'num_left'] = order_num
        self.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)+1
                        deal_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'] = index
                        deal_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.commissions
                        deal_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'] = 0
                                portfolio_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'] = 0
                                portfolio_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'])#持仓不足,撤卖单
                                continue
                            self.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_temp
                            
                            if(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']))
        return
    

    def 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=1
            while(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.now
            account_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')
        return
    
    def 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_list
    
    def 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.now
        self.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'] - 1
            account_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()
        return
    
    def before_close(self)-> None:
        '''
        function:定义收盘前(收盘前一分钟)应该做一些什么,留给用户来重写,如果持仓不过夜,请在这里实现清仓,会在每日收盘前一分钟调用调用.
        params:
            - 
        return:
            - 
        '''
        self.clear_allorder()

    def dosomethin_in_morning(self)-> None:
        '''
        function:定义早上要做些什么,除了已经定义的every_morning内的其他操作之外,用户可以重写此方法在早上马上开盘时进行操作。
        params:
            - 
        return:
            - 
        '''
        pass

    def 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 = False
            else:
                self.every_morning()
                if(self.now>self.end_dt):break
            while(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):break
            if(self.now>self.end_dt):break
            self.before_close()
        logging.info('info,'+str(self.context['name'])+'回测结束')
        return
    
    def 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)
        #return
            return_ = data.iloc[-1,:]['total_assets'] / data.iloc[-1,:]['initial_cash'] - 1
        #P&L
            PandL = data.iloc[-1,:]['total_assets'] - data.iloc[-1,:]['initial_cash']
        #volatility
            volatility = data['return'].std()
        #Sharpe
            Sharpe = sharpe_ratio(data['return'], risk_free=0, period='daily')
        #Maximum Drawdown
            max_drawdown_ = max_drawdown(data['return'])

        from prettytable import PrettyTable

        x = PrettyTable()
        x.padding_width = 2
        x.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_id
        self.context['signal_record'] = pd.DataFrame(columns=['time_stamp', 'contract', 'moving_average_1', 'moving_average_2', 'signal', 'return'])
        self.average_short_min = average_short_min
        self.average_long_min = average_long_min
        self.average_short_line = []
        self.average_long_line = []
        self.signal = 0
        self.trade_time = 0 
        self.trade_period_restriction = trade_period_restriction
        self.istrading_buy = 0
        self.istrading_sell = 0

    def 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_price
    
    def 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.now
                temp_signal_record.loc[self.now,'contract'] = self.stk_id
                temp_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'] = -1
                temp_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 = -1
                self.trade_time = 0 
                self.istrading_buy = 0
                self.istrading_sell = 1
        elif((self.istrading_sell) and (self.trade_time < self.trade_period_restriction)):
            self.signal = -1
        else:
            self.signal = 0
        #根据信号生成sell_id_list,sell_num_list
        if(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 + 1
        return sell_id_list,sell_num_list
    
    def 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.now
                temp_signal_record.loc[self.now,'contract'] = self.stk_id
                temp_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'] = 1
                temp_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 = 1
                self.trade_time = 0 
                self.istrading_buy = 1
                self.istrading_sell = 0
        elif((self.istrading_buy) and (self.trade_time < self.trade_period_restriction)):
            self.signal = 1
        else:
            self.signal = 0
        #根据信号生成buy_id_list,buy_num_list
        if(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 = 1
                while(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 += 1
                buy_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 + 1
        return buy_id_list,buy_num_list
    
    def before_close(self)-> None:
        '''
        function:定义收盘前(收盘前一分钟)应该做一些什么,1)撤掉所有未成订单;2)以1分钟内的平均价格清仓所有持仓
        params:
            - 
        return:
            - 
        '''
        #1) 撤掉所有未成订单;
        self.clear_allorder()
        #2)以1分钟内的平均价格清仓所有持仓;(假设全成)
        #如果没有持仓则直接pass
        if(len(self.portfolio)<1):return
        else:
            #更新self.portfolio
            for 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'] = 0
                self.portfolio.loc[ind,'port_amount'] = 0
                self.portfolio.loc[ind,'commission'] = 0
                hold_cost_temp = float(self.portfolio.loc[ind,'hold_cost'])
                self.portfolio.loc[ind,'hold_cost'] = 0
                self.portfolio.loc[ind,'price_now'] = '-'
                self.portfolio.loc[ind,'dynamic_equity'] = 0
                self.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'] = '已卖'
            #更新account
            self.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()

回测结果:

回测结果保存到本地:

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

逐字节讲解 Redis 持久化(RDB 和 AOF)的文件格式

前言 相信各位对 Redis 的这两种持久化机制都不陌生&#xff0c;简单来说&#xff0c;RDB 就是对数据的全量备份&#xff0c;AOF 则是增量备份&#xff0c;而从 4.0 版本开始引入了混合方式&#xff0c;以 7.2.3 版本为例&#xff0c;会生成三类文件&#xff1a;RDB、AOF 和记…

Mysql中正则表达式Regexp常见用法

Mysql中正则表达式Regexp常见用法_regexp不包含-CSDN博客

Uniapp扫码预览连接地址与手机不在同一网段

在开发Uniapp应用时&#xff0c;这里有一个扫码预览的功能&#xff0c;电脑与手机都是在一网络下&#xff0c;之前点开后预览地址一直是169.254.3.x的地址&#xff0c;通过WINR键输入cmd运行&#xff0c;然后ipconfig查看所有网络连接。发现有一个虚拟网络连接的地址是169.251.…

代码随想录Day51 完结篇 LeetCode T84 柱状图的最大矩形

前言 今天代码随想录一刷也告一段落了,没想到我居然坚持下来了,一节都没有落下,学习到了很多种不同的解题思路,也和大家一块交流了很多,哈哈也许不久以后我还得再次二刷代码随想录,希望这一系列的题解能给大家带来帮助,如想要系统学习,请参照代码随想录网站的题解以及b站的配套…

OpenLayers实战,WebGL图层根据Feature要素的变量动态渲染多种颜色和不同直径大小的圆形和圆点图形,适用于大量圆形圆点渲染不同颜色不同大小

专栏目录: OpenLayers实战进阶专栏目录 前言 本章使用OpenLayers根据Feature要素的变量动态渲染不同颜色和不同直径大小的圆形和圆点图形。 通过一个WebGL图层生成四种不同颜色和不同大小的圆形圆点图形要素,适用于WebGL图层需要根据大量点要素区分颜色区分不同大小显示圆形…

【开源】基于Vue.js的天然气工程业务管理系统的设计和实现

项目编号&#xff1a; S 021 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S021&#xff0c;文末获取源码。} 项目编号&#xff1a;S021&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块三、使用角色3.1 施工人员3.2 管理员 四…

51单片机LED灯渐明渐暗实验

51单片机LED灯渐明渐暗实验 1.概述 这篇文章介绍使用单片机控制两个LED彩灯亮度渐明渐暗效果&#xff0c;详细介绍了操作步骤以及完整的程序代码&#xff0c;动手就能制作的小实验。 2.操作步骤 2.1.硬件搭建 1.硬件准备 名称型号数量单片机STC12C2052AD1LED彩灯无2晶振1…

《golang设计模式》第三部分·行为型模式-06-备忘录模式(Memento)

文章目录 1. 概述1.1 角色1.2 类图 2. 代码示例2.1 设计2.2 代码2.3 类图 1. 概述 备忘录&#xff08;Memento&#xff09;用于在不破坏目标对象封装特性的基础上&#xff0c;将目标对象内部的状态存储到外部对象中&#xff0c;以备之后恢复状态时使用。 1.1 角色 Originato…

ABAP调用Https接口 Ssl证书导入

ABAP调用Https接口 Ssl证书导入 一、证书导入 谷歌浏览器打开对方系统URL地址&#xff0c;下载SSL Server certificate,步骤如下&#xff1a; 浏览器打开要导出certificate(证书)的网站&#xff0c;点击这个小锁的图标&#xff1a; 点击连接是安全的后面小播放按钮 点击证…

【教3妹学编程-算法题】最大异或乘积

3妹&#xff1a;2哥&#xff0c;你有没有看到新闻“18岁父亲为4岁儿子落户现身亲子鉴定” 2哥 : 啥&#xff1f;18岁就当爹啦&#xff1f; 3妹&#xff1a;确切的说是14岁好吧。 2哥 : 哎&#xff0c;想我30了&#xff0c; 还是个单身狗。 3妹&#xff1a;别急啊&#xff0c; 2…

化繁为简——2021版本Adobe InDesign

今天&#xff0c;我们来谈谈Id软件&#xff0c;它是一个定位于专业排版领域的设计软件&#xff0c;虽然出道时间比较晚&#xff0c;但是在功能上反而更加完美与成熟。InDesign可以将文档直接导出为Adobe的PDF格式&#xff0c;而且有多语言支持。它也是第一个支持Unicode文本处理…

TVS瞬态抑制二极管的工作原理和特点?|深圳比创达电子EMC

TVS二极管一般是用来防止端口瞬间的电压冲击造成后级电路的损坏。防止端口瞬间的电压冲击造成后级电路的损坏。有单向与双向之分&#xff0c;单向TVS一般应用于直流供电电路&#xff0c;双向TVS应用于交流供电电路。 TVS产品的额定瞬态功率应大于电路中可能出现的最大瞬态浪涌…

SpringCloud 微服务全栈体系(十六)

第十一章 分布式搜索引擎 elasticsearch 六、DSL 查询文档 elasticsearch 的查询依然是基于 JSON 风格的 DSL 来实现的。 1. DSL 查询分类 Elasticsearch 提供了基于 JSON 的 DSL&#xff08;Domain Specific Language&#xff09;来定义查询。常见的查询类型包括&#xff1…

初学者必读书籍——两个月速成Python

想学Python的你是不是一直被它生涩难懂的劝退&#xff1f;作为一个自学入门的程序员&#xff0c;依靠这样几本书&#xff0c;两个月就学会了python。不卖关子&#xff0c;我学的就是”python编程三剑客“系列。那么接下来就让我给你介绍介绍吧。 1.《Python编程&#xff1a;从入…

解析生成式人工智能 | 它真的有这么强大吗?

原创 | 文 BFT机器人 当人们说“生成式人工智能”时&#xff0c;你知道这代表着什么意思吗&#xff1f;为什么这些系统似乎正在覆盖所有涉及联想的应用程序&#xff1f;近日&#xff0c;麻省理工学院的人工智能专家帮助剖析了这种日益流行且无处不在的技术。 当你快速浏览一下头…

如何看待程序员领域内的“内卷”现象?

要搞清楚这个问题&#xff0c;我首先就来阐释一下“内卷”的概念。 内卷本身是从一个学术名词演化为网络流行词的&#xff0c;本是指文化模式因达到某种最终形态&#xff0c;既无法保持稳定也不能转化为更高级的新形态&#xff0c;而只能在这种文化模式内部无限变得复杂的现象。…

HTML+CSS+ElementUI搭建个人博客静态页面展示(纯前端)

网站演示 搭建过程 技术选取 HTML/CSSVUE2ElementUI(Version - 2.15.14) 环境配置与搭建 安装指令 1. 先确保你的电脑已经安装好了npm和node npm -vnode -v2. ElementUI下载&#xff0c;推荐使用 npm 的方式安装 npm i element-ui -S3. CDN引入 <!-- 引入样式 --> <…

Redis 与其他数据库的不同之处 | Navicat

Redis&#xff0c;即远程字典服务器&#xff08;Remote Dictionary Server&#xff09;&#xff0c;它是一个多功能且高性能的键值存储系统&#xff0c;在数据库领域中已获得广泛关注和认可。在处理简单数据结构方面&#xff0c;它因其快速和高效而著称。本文中&#xff0c;我们…

基于高质量训练数据,GPT-4 Turbo更出色更强大

11月7日消息&#xff0c;OpenAI在首届开发者大会上正式推出了GPT-4 Turbo。 与GPT-4相比&#xff0c;GPT-4 Turbo主要有6方面的提升&#xff1a; 1、扩展下文对话长度&#xff1a;GPT4最大只能支持8k的上下文长度&#xff08;约等于6000个单词&#xff09;&#xff0c;而GPT-4…

SOLIDWORKS实用技巧——工程图模板替换

概述 工程师常在出图时选择最佳模板&#xff0c;在编辑一段时间后&#xff0c;发现需要更改图纸大小&#xff0c;怎样更改图纸大小还不影响现有工作。你是否也有此类问题&#xff1f; 那么&#xff0c;新建工程图时的模板从哪里来&#xff1f;如何轻松替换已有工程图的图纸格…