# tushare ID:409200 # 【PY从0到1】 bt完整框架 # 本节介绍 # 优化了开关逻辑 # 取消了输出信息中无效的部分 # 添加策略的时候增加了策略参数变量直接修改params # 完整的策略框架搭建完成 #导入库 import datetime as dt import pandas as pd import tushare as ts import backtrader as bt import seaborn as sns sns.set() # 本案例用到的函数源码 #---------------------- def data_obtain(key, code, start, end): ''' 获取相应股票的数据 基础函数不做过多注释 时间格式举例:20180101 ''' pro = ts.pro_api(key) df = pro.daily(ts_code=code, start_date=start, end_date=end, fields='trade_date,open,high,low,close,vol') df.rename(columns={'trade_date':'date', 'vol':'volume'}, inplace=True) df['date'] = pd.to_datetime(df['date']) df.set_index('date', inplace=True) code = code[:6] df.to_csv('data/stock/' + code + '.csv') def pools_get4fn(fnam,tim0str,tim9str,fgSort=True): ''' 数据读取函数,及标准化函数 ''' df = pd.read_csv(fnam, index_col=0, parse_dates=True) # 读取数据 df.sort_index(ascending=fgSort,inplace=True) # 正序排列 tim0 = None if tim0str == '' else dt.datetime.strptime(tim0str,'%Y-%m-%d') # 改变时间格式 tim9 = None if tim9str == '' else dt.datetime.strptime(tim9str,'%Y-%m-%d') # 改变时间格式 df['openinterest'] = 0 # 目前没有作用,等到以后的课程解释 data = bt.feeds.PandasData(dataname=df,fromdate=tim0,todate=tim9) # 转化为bt内部格式 return data #---------------------- # 策略加载 #---------------------- class TySta001(bt.Strategy): ''' 这是一个储存策略框架的类 ''' params = ( ('maperiod', 20), ('fgprint', True), ) # 定义策略使用的默认参数 def log(self, txt, dt=None, fgprint=True): ''' 记录函数,打印一些交易记录 ''' if self.params.fgprint and fgprint: # 打印开关 dt = dt or self.datas[0].datetime.date(0) # dt为空就运行or后面的,后面的为获取当天日期 print('\t%s, %s' % (dt.isoformat(), txt)) # 打印当天日期和txt def __init__(self): ''' 初始化参数 运行频率:与数据周期一致 ''' self.dataclose = self.datas[0].close # 当天获取收盘价 self.order = None # 订单交易情况清零 self.buyprice = None # 买入价格清零 self.buycomm = None # 交易佣金额清零 self.sma = bt.indicators.SimpleMovingAverage(self.datas[0], period=self.params.maperiod) # 加载SMA指标 self.macd = bt.indicators.MACDHisto(self.datas[0], subplot=True) # 加载MACD指标 # self.em = bt.indicators.ExponentialMovingAverage(self.datas[0], # subplot=True) # 加载滑动平均 # self.rsi = bt.indicators.RSI(self.datas[0], subplot=True) # 加载RSI指标 # self.atr = bt.indicators.ATR(self.datas[0], plot=True) # 加载ATR指标,注意默认plot=False def notify_order(self, order): ''' 订单状态检查函数 运行频率:与数据周期一致 ''' if order.status in [order.Submitted, order.Accepted]: # 订单状态为提交或接受 return # 暂不做任何操作 if order.status in [order.Completed]: # 订单完成 if order.isbuy(): # 为买单 self.log('买单执行成功,成交价为:%.2f,小计:%.2f,佣金:%.2f' % (order.executed.price, order.executed.value, order.executed.comm)) self.bar_executed = len(self) # 完成订单持续的周期 self.buyprice = order.executed.price # 记录买入价格 self.buycomm = order.executed.comm # 记录佣金 elif order.issell(): # 为卖单 self.log('卖单设置成功,成交价为:%.2f,小计:%.2f,佣金:%.2f' % (order.executed.price, order.executed.value, order.executed.comm)) elif order.status in [order.Canceled, order.Margin, order.Rejected]: # 订单状态为取消、保证金和拒绝 self.log('订单状态异常:取消/保证金/拒绝。') self.order = None # 订单检测完成,将订单状态清零 def notify_trade(self, trade): ''' 检查订单是否关闭 若订单关闭,则打印利润情况 运行频率:与数据周期一致 ''' if not trade.isclosed: return else: # 订单关闭时统计盈利情况 self.log('交易利润,毛利:%.2f,净利:%.2f' % (trade.pnl, trade.pnlcomm)) def next(self): ''' 核心策略 移动平均线策略 运行频率:与数据周期一致 ''' self.log(txt='收盘价为:%.2f' % self.dataclose[0] + '元', fgprint=False) # 调用log函数 if not self.position: # 没有持仓 if self.dataclose[0] > self.sma[0]: # 收盘价大于均线 self.order = self.buy() # 设置买单 self.log(txt='设置买单BUY!:%.2f, 代码为:%s' % (self.dataclose[0], self.datas[0]._name)) # 调用log else: # 有持仓 if self.dataclose[0] < self.sma[0]: # 收盘价小于均线 self.order = self.sell() # 设置卖单 self.log(txt='设置卖单SELL!:%.2f, 代码为:%s' % (self.dataclose[0], self.datas[0]._name)) # 调用log def stop(self): ''' 在策略执行完毕后输出参数周期以及最终的 运行频率:只有在最后的回测期输出 ''' self.log('MA均线周期 =%2d, 最终资产总值:%.2f' % (self.params.maperiod, self.broker.getvalue()), fgprint=True) # 打印结束状态 #--------------------- # 颜色形状配置库 #--------------------- # K线和量能bar tq10_corUp,tq10_corDown=['#7F7F7F','#17BECF'] tq09_corUp,tq09_corDown=['#B61000','#0061B3'] tq08_corUp,tq08_corDown=['#FB3320','#020AF0'] tq07_corUp,tq07_corDown=['#B0F76D','#E1440F'] tq06_corUp,tq06_corDown=['#FF3333','#47D8D8'] tq05_corUp,tq05_corDown=['#FB0200','#007E00'] tq04_corUp,tq04_corDown=['#18DEF5','#E38323'] tq03_corUp,tq03_corDown=['black','blue'] tq02_corUp,tq02_corDown=['red','blue'] tq01_corUp,tq01_corDown=['red','lime'] tq_ksty01=dict(volup=tq01_corUp,voldown=tq01_corDown,barup=tq01_corUp,bardown=tq01_corDown) tq_ksty02=dict(volup=tq02_corUp,voldown=tq02_corDown,barup=tq02_corUp,bardown=tq02_corDown) tq_ksty03=dict(volup=tq03_corUp,voldown=tq03_corDown,barup=tq03_corUp,bardown=tq03_corDown) tq_ksty04=dict(volup=tq04_corUp,voldown=tq04_corDown,barup=tq04_corUp,bardown=tq04_corDown) tq_ksty05=dict(volup=tq05_corUp,voldown=tq05_corDown,barup=tq05_corUp,bardown=tq05_corDown) tq_ksty06=dict(volup=tq06_corUp,voldown=tq06_corDown,barup=tq06_corUp,bardown=tq06_corDown) tq_ksty07=dict(volup=tq07_corUp,voldown=tq07_corDown,barup=tq07_corUp,bardown=tq07_corDown) tq_ksty08=dict(volup=tq08_corUp,voldown=tq08_corDown,barup=tq08_corUp,bardown=tq08_corDown) tq_ksty09=dict(volup=tq09_corUp,voldown=tq09_corDown,barup=tq09_corUp,bardown=tq09_corDown) tq_ksty10=dict(volup=tq10_corUp,voldown=tq10_corDown,barup=tq10_corUp,bardown=tq10_corDown) # 买卖符号 class MyBuySell(bt.observers.BuySell): ''' 从三个里面选一个 屏蔽其他两个 ''' # plotlines = dict(buy=dict(marker='$\u21E7$', markersize=12.0), # sell=dict(marker='$\u21E9$', markersize=12.0)) # plotlines = dict(buy=dict(marker='$++$', markersize=12.0), # sell=dict(marker='$--$', markersize=12.0)) plotlines = dict(buy=dict(marker='$✔$', markersize=12.0), sell=dict(marker='$✘$', markersize=12.0)) #--------------------- # 开始回测 # 加载cerebro 大脑 cerebro = bt.Cerebro() # 加载大脑 print('\n#1,回测大脑加载完成!') # 设置初始资金 dmoney0 = 100000.0 # 初始资金设定 cerebro.broker.setcash(dmoney0) # 加载入大脑 dcash0 = cerebro.broker.startingcash # 获取初始资金 print('\n#2,初始资金设置完成,载入大脑!') # 用函数抓取数据 # data_obtain('', # '000001.SZ', # '20170101', # '20200101') # 读取数据 rs0 = 'data/stock/' # 数据存储地址 xcod = '000001' # 需要处理的股票代码 fdat = rs0+xcod+'.csv' # 数据完整路径 print('\t 数据载入完成,数据路径为:',fdat) # 数据切割并标准化 t0str,t9str = '2018-01-01','2018-04-15' # 数据切割的时间 data = pools_get4fn(fdat,t0str,t9str) # 数据切割、标准化 print('\t 数据切割完毕,成功标准化。') # 平均K线图 # data.addfilter(bt.filters.HeikinAshi) # 添加一个过滤器 # 将数据加入大脑 cerebro.adddata(data, name=xcod) # 数据载入大脑 print('\t 数据载入大脑。') # 加载策略 cerebro.addstrategy(TySta001, maperiod=21, fgprint=True) # 添加策略,params中的参数可以直接在此修改 print('\t 策略添加完成。') # 佣金设置 cerebro.broker.setcommission(commission=0.001) # 添加佣金 print('\t 交易费用设置完成。') # 交易数量设置 cerebro.addsizer(bt.sizers.FixedSize, stake=800) # 使用固定的交易手数 print('\t 交易数目设置成功。') # 加载策略分析指标 # SQN cerebro.addanalyzer(bt.analyzers.SQN, _name='SqnAnz') # TimeReturn tframes = dict(days=bt.TimeFrame.Days, weeks=bt.TimeFrame.Weeks, months=bt.TimeFrame.Months, years=bt.TimeFrame.Years) # 时间周期字典 cerebro.addanalyzer(bt.analyzers.TimeReturn, timeframe=tframes['years'], _name='TimeAnz') # timeframe时间周期选择 # sharpe cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='SharpeRatio', legacyannual=True) # AnnualReturun cerebro.addanalyzer(bt.analyzers.AnnualReturn, _name='AnnualReturn') # 投资回报率 # TradeAnalyzer cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name='TradeAnalyzer') # DrawDown cerebro.addanalyzer(bt.analyzers.DrawDown, _name='DW') # VWR cerebro.addanalyzer(bt.analyzers.VWR, _name='VWR') # 买卖符号设定 bt.observers.BuySell = MyBuySell # 加载预设符号 # 进行回测 print('\n#3,大脑正在分析数据。') results = cerebro.run() # 运行策略 # 分析结果 dval9 = cerebro.broker.getvalue() # 获得最后的资金数 dget = dval9 - dcash0 kret = dget / dcash0 * 100 # 计算投资回报率 print('\n#4,大脑处理完成。\n 分析结果如下:') print('\t起始资金 : %.2f' % dcash0 + '元') print('\t资产总值 : %.2f' % dval9 + '元') print('\t利润总额 :%.2f' % dget + '元') print('\t投资回报率(ROI):%.2f %%' % kret) # 评估策略质量 print('\n#5,大脑正在评估策略质量。') strat = results[0] # 获取第一个策略的测试结果 anzs = strat.analyzers # 获取实例 dsharp = anzs.SharpeRatio.get_analysis()['sharperatio'] # 获得夏普比率结果 trade_info = anzs.TradeAnalyzer.get_analysis() # 交易统计信息 dw = anzs.DW.get_analysis() # 获得回撤数据 max_drowdown_len = dw['max']['len'] # 最大周期 max_drowdown = dw['max']['drawdown'] # 最大回撤 max_drowdown_money = dw['max']['moneydown'] # 最大回撤金额 print('\t夏普指数为:', dsharp) print('\t最大回撤周期为:', max_drowdown) print('\t最大回测金额为:', max_drowdown_money) # 加载所有加载的评估指标 print('完整的评估指标如下:') for alyzer in strat.analyzers: alyzer.print() # 可视化 # style为K线风格参数,支持line candle bar ohlc。**tq_ksty为K线与成交量颜色设定(01~10) cerebro.plot(style='candle', **tq_ksty08) # volume成交量参数开关,voloverlay子图开关 cerebro.plot(volume=True, voloverlay=True) # numfigs切分K线图 cerebro.plot(numfigs=1) print('\n#6,绘制处理结果。') # 回测完成 print('\nBackTrader回测完毕。')