前言,此次主要是更新了backtrader.py文件的代码,其他的文件代码没动,所以这次只放出backtrader.py的代码,其他的就不放了。更改后的效果如下(暂定):
还有些细节内容没有写跟完善,先记录下
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import datetime
import pandas as pd
import backtrader as bt
import tushare as ts
import tk_window
import tkinter as tk
from tkinter import ttk
import matplotlib.pyplot as plt
import matplotlib as mpl # 用于设置曲线参数
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk) # 使用后端TkAgg
mpl.use('TkAgg')
ts.set_token('去tushare官网注册个账号吧,token我就不提供了')
class my_strategy(bt.Strategy):
# 设置简单均线周期,以备后面调用
params = (
('maperiod21', 21),
('maperiod55', 55),)
def log(self, txt, dt=None):
# 日记记录输出
dt = dt or self.datas[0].datetime.date(0)
print('%s, %s' % (dt.isoformat(), txt))
def __init__(self):
# 初始化数据参数
# 设置当前收盘价为dataclose
self.dataclose = self.datas[0].close
self.order = None
self.buyprice = None
self.buycomm = None
# 添加简单均线, subplot=False是否单独子图显示
self.sma21 = bt.indicators.SimpleMovingAverage(self.datas[0], period=self.params.maperiod21, plotname='mysma')
self.sma55 = bt.indicators.SimpleMovingAverage(self.datas[0], period=self.params.maperiod55, subplot=False)
def next(self):
# self.log('Close, %.2f' % self.dataclose[0]) # 输出打印收盘价
# 检查是否有订单发送当中,如果有则不再发送第二个订单
if self.order:
return
# 检查是否已经有仓位
if not self.position:
# 如果没有则可以执行一下策略了
if self.sma21[0] > self.sma55[0] and self.sma21[-1] < self.sma55[-1]:
# 记录输出买入价格
self.log('买入信号产生的价格: %.2f' % self.dataclose[0])
# 跟踪已经创建好的订单避免重复第二次交易
self.order = self.buy()
else:
if self.sma21[0] < self.sma55[0] and self.sma21[-1] > self.sma55[-1]:
self.log('卖入信号产生的价格: %.2f' % self.dataclose[0])
self.order = self.sell()
# 记录交易执行情况,输出打印
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.buyprice = order.executed.price
self.buycomm = order.executed.comm
else: # Sell
self.log('实际卖出价格: %.2f, 市值: %.2f, 手续费 %.2f' %
(order.executed.price,
order.executed.value,
order.executed.comm))
# len(self)是指获取截至当前数据一共有多少根bar
# 以下代码就是指当交易发生时立刻记录下了当天有多少根bar
# 如果要表示当成交后过了5天卖,则可以这样写 if len(self) >= (self.bar_executed + 5):
self.bar_executed = len(self)
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
self.log('Order Canceled/Margin/Rejected')
self.order = None
# 记录交易收益情况
def notify_trade(self, trade):
if not trade.isclosed: # 如果交易还没有关闭,则退出不输出显示盈利跟手续费
return
self.log('策略收益 %.2f, 净收益 %.2f' %
(trade.pnl, trade.pnlcomm))
def stop(self):
# 策略停止输出结果
total_funds = self.broker.getvalue()
print('MA均线: %2d日,总资金: %.2f' % (self.params.maperiod21, total_funds))
def run_cerebro(): # 策略回测
for widget_backtrader_window in tk_window.centre_frame.winfo_children():
widget_backtrader_window.destroy()
backtrader_window = tk.PanedWindow(tk_window.centre_frame, opaqueresize=False)
backtrader_window.pack(fill=tk.BOTH, expand=1)
# 创建左边frame框架,主要放回测策略文件代码
backtrader_left_frame = tk.Frame(backtrader_window, relief=tk.SUNKEN, bg='#353535', bd=5, borderwidth=4)
backtrader_left_frame.pack(fill=tk.BOTH, expand=1)
top_label = tk.Label(backtrader_left_frame, text='回测策略文件', bd=1)
top_label.pack()
backtrader_window.add(backtrader_left_frame)
# 创建右边图形输出框架,主要放回测分析显示跟用户输入的股票代码跟日期
backtrader_plot_window = tk.PanedWindow(orient='vertical', opaqueresize=False)
backtrader_window.add(backtrader_plot_window)
backtrader_top_frame = tk.Frame(backtrader_plot_window, width=tk_window.screenWidth, height=tk_window.screenHeight,
relief=tk.SUNKEN, bg='#353535', bd=5, borderwidth=4)
backtrader_top_frame.pack(fill=tk.BOTH)
# 在主框架下创建股票代码输入子框架
code_frame = tk.Frame(backtrader_top_frame, borderwidth=1, bg='#353535')
code_frame.pack()
# 创建标签‘股票代码’
stock_label = tk.Label(code_frame, text='股票代码', bd=1)
stock_label.pack(side=tk.LEFT)
# 创建股票代码输入框
input_code_var = tk.StringVar()
code_widget = tk.Entry(code_frame, textvariable=input_code_var, borderwidth=1, justify=tk.CENTER)
# input_code_get = input_code_var.set(input_code_var.get()) # 获取输入的新值
code_widget.pack(side=tk.LEFT, padx=4)
# 在主框架下创建股票日期输入框子框架
input_date_frame = tk.Frame(backtrader_top_frame, borderwidth=1, bg='#353535')
input_date_frame.pack()
# 创建标签‘开始日期’
date_start_label = tk.Label(input_date_frame, text='开始日期', bd=1)
date_start_label.pack(side=tk.LEFT)
# 创建开始日期代码输入框
input_startdate_var = tk.StringVar()
startdate_widget = tk.Entry(input_date_frame, textvariable=input_startdate_var, borderwidth=1, justify=tk.CENTER)
input_startdate_get = input_startdate_var.set(input_startdate_var.get()) # 获取输入的新值
startdate_widget.pack(side=tk.LEFT, padx=4)
# 创建标签‘结束日期’
date_end_label = tk.Label(input_date_frame, text='结束日期', bd=1)
date_end_label.pack(side=tk.LEFT)
# 创建结束日期代码输入框
input_enddate_var = tk.StringVar()
enddate_widget = tk.Entry(input_date_frame, textvariable=input_enddate_var, borderwidth=1, justify=tk.CENTER)
input_enddate_get = input_enddate_var.set(input_enddate_var.get()) # 获取输入的新值
enddate_widget.pack(side=tk.LEFT, padx=4)
# 先把部件布局好了再backtrader_top_frame用.add()添加到backtrader_plot_window
backtrader_plot_window.add(backtrader_top_frame, height=tk_window.screenHeight / 10)
# 创建底部窗口框架,用来放图形输出
backtrader_bottom_frame = tk.Frame(backtrader_plot_window, width=tk_window.screenWidth,
height=tk_window.screenHeight, relief=tk.SUNKEN, bg='#353535', bd=5,
borderwidth=4)
backtrader_bottom_frame.pack(fill=tk.BOTH)
backtrader_plot_window.add(backtrader_bottom_frame)
def backtrader_go(): # 图形输出渲染
# 以下函数作用是省略输入代码后缀.sz .sh
def code_name_transform(get_stockcode): # 输入的数字股票代码转换成字符串股票代码
str_stockcode = str(get_stockcode)
str_stockcode = str_stockcode.strip() # 删除前后空格字符
if 6 > len(str_stockcode) > 0:
str_stockcode = str_stockcode.zfill(6) + '.SZ' # zfill()函数返回指定长度的字符串,原字符串右对齐,前面填充0
if len(str_stockcode) == 6:
if str_stockcode[0:1] == '0':
str_stockcode = str_stockcode + '.SZ'
if str_stockcode[0:1] == '3':
str_stockcode = str_stockcode + '.SZ'
if str_stockcode[0:1] == '6':
str_stockcode = str_stockcode + '.SH'
return str_stockcode
# 先设置下框架的清理,放置按下回测按钮时重复生成图形覆盖
for widget_plot in backtrader_bottom_frame.winfo_children():
widget_plot.destroy()
for widget_plot1 in backtrader_bottom_frame.winfo_children():
widget_plot1.destroy()
# 交互数据的获取跟处理
stock_name = input_code_var.get()
code_name = code_name_transform(stock_name)
start_date = input_startdate_var.get()
end_date = input_enddate_var.get()
# adj='qfq'向前复权,freq='D 数据频度:日K线
df = ts.pro_bar(ts_code=code_name, start_date=start_date, end_date=end_date, adj='qfq', freq='D')
df['trade_date'] = pd.to_datetime(df['trade_date'])
# df = df.drop(['change', 'pre_close', 'pct_chg', 'amount'], axis=1)
df = df.rename(columns={'vol': 'volume'})
df.set_index('trade_date', inplace=True) # 设置索引覆盖原来的数据
df = df.sort_index(ascending=True) # 将时间顺序升序,符合时间序列
df['openinterest'] = 0
# 喂养数据到backtrader当中去
back_start_time = datetime.datetime.strptime(start_date, "%Y%m%d") # str转换成时间格式2015-01-01 00:00:00
back_end_time = datetime.datetime.strptime(end_date, '%Y%m%d')
# print(back_start_time)
data = bt.feeds.PandasData(dataname=df,
fromdate=back_start_time,
todate=back_end_time
)
# 创建策略容器
cerebro = bt.Cerebro()
# 添加自定义的策略my_strategy
cerebro.addstrategy(my_strategy)
# 添加数据
cerebro.adddata(data)
# 设置资金
startcash = 100000
cerebro.broker.setcash(startcash)
# 设置每笔交易交易的股票数量
cerebro.addsizer(bt.sizers.FixedSize, stake=100)
# 设置手续费
cerebro.broker.setcommission(commission=0.0005)
# 输出初始资金
d1 = back_start_time.strftime('%Y%m%d')
d2 = back_end_time.strftime('%Y%m%d')
print('初始资金: %.2f' % startcash)
print('回测开始时间: %s' % d1)
print('回测结束时间: %s' % d2)
# 运行策略
# stdstats=False不显示回测的统计结果
cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='SharpeRatio')
cerebro.addanalyzer(bt.analyzers.DrawDown, _name='DW')
cerebro.addanalyzer(bt.analyzers.PyFolio, _name='pyfolio')
cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name='TradeAnalyzer')
result = cerebro.run(stdstats=True)
backtrader_analysis = result[0]
print(backtrader_analysis.analyzers.SharpeRatio.get_analysis())
print(backtrader_analysis.analyzers.SharpeRatio.get_analysis()['sharperatio'])
print(backtrader_analysis.analyzers.DW.get_analysis())
print(backtrader_analysis.analyzers.DW.get_analysis()['max']['drawdown'])
# print(backtrader_analysis.analyzers.pyfolio.get_analysis())
print(backtrader_analysis.analyzers.TradeAnalyzer.get_analysis())
SharpeRatio_a = '夏普:%.2f' % backtrader_analysis.analyzers.SharpeRatio.get_analysis()['sharperatio']
drawdown_a = '最大回撤:%.2f' % backtrader_analysis.analyzers.DW.get_analysis()['max']['drawdown']
net_profit = cerebro.broker.getvalue() - startcash
print('净收益: %.2f' % net_profit)
# grid = False不显示分割线
# cerebro.plot(style='candlestick', grid=False, iplot=False)
plofit_show = plt.figure('Figure5')
df.close.plot()
# df.close[0]指的是测试开始日期收盘价,df.close[-1]指的是数据结束日期收盘价
plt.annotate('期间累计涨幅: %.2f' % ((df.close[-1] / df.close[0] - 1) * 100) + '%', xy=(df.index[-150],
df.close.mean()), xytext=(df.index[-500], df.close.min()),
bbox=dict(boxstyle='round,pad=0.5', fc='yellow', alpha=0.5),
arrowprops=dict(facecolor='green', shrink=0.05), fontsize=12)
print(df.close[0])
print(df.close[-1])
print(df)
canvas_stock_daily_basic = FigureCanvasTkAgg(plofit_show, master=backtrader_bottom_frame)
canvas_stock_daily_basic.draw()
toolbar_stock_daily_basic = NavigationToolbar2Tk(canvas_stock_daily_basic, backtrader_bottom_frame)
toolbar_stock_daily_basic.update() # 显示图形导航工具条
canvas_stock_daily_basic._tkcanvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
backtrader_treeview = ttk.Treeview(backtrader_bottom_frame, columns=['1'], show='headings')
# 在treeview布局钱先布局横竖滚动条,不然会出现布局问题,你可以试着将滚动条代码放在最后试下
VScroll1 = ttk.Scrollbar(backtrader_bottom_frame, orient='vertical', command=backtrader_treeview.yview)
backtrader_treeview.configure(yscrollcommand=VScroll1.set)
VScroll1.pack(side=tk.RIGHT, fill=tk.Y)
HScroll1 = ttk.Scrollbar(backtrader_bottom_frame, orient='horizontal', command=backtrader_treeview.xview)
backtrader_treeview.configure(xscrollcommand=HScroll1.set)
HScroll1.pack(side=tk.BOTTOM, fill=tk.X)
backtrader_treeview.column('1', width=70, anchor='center')
backtrader_treeview.heading('1', text='回测日记')
backtrader_treeview.pack(side=tk.RIGHT, fill=tk.BOTH, expand=1)
backtrader_treeview.insert('', 'end', values=SharpeRatio_a)
backtrader_treeview.insert('', 'end', values=drawdown_a)
# 在主框架下创建回测按钮子框架
search_frame = tk.Frame(backtrader_top_frame, borderwidth=1, bg='#353535', relief=tk.SUNKEN)
search_frame.pack()
# 创建查询按钮并设置功能
stock_find = tk.Button(search_frame, text='回测', width=5, height=1, command=backtrader_go)
stock_find.pack()
# 策略参数优化函数
class my_optimization(bt.Strategy):
# 设置简单均线周期,以备后面调用
params = (
('maperiod21', 21),
('maperiod55', 55),)
def log(self, txt, dt=None):
# 日记记录输出
dt = dt or self.datas[0].datetime.date(0)
print('%s, %s' % (dt.isoformat(), txt))
def __init__(self):
# 初始化数据参数
# 设置当前收盘价为dataclose
self.dataclose = self.datas[0].close
self.order = None
self.buyprice = None
self.buycomm = None
# 添加简单均线, subplot=False是否单独子图显示
self.sma21 = bt.indicators.SimpleMovingAverage(self.datas[0], period=self.params.maperiod21, plotname='mysma')
self.sma55 = bt.indicators.SimpleMovingAverage(self.datas[0], period=self.params.maperiod55, subplot=False)
def next(self):
# self.log('Close, %.2f' % self.dataclose[0]) # 输出打印收盘价
# 检查是否有订单发送当中,如果有则不再发送第二个订单
if self.order:
return
# 检查是否已经有仓位
if not self.position:
# 如果没有则可以执行一下策略了
if self.sma21[0] > self.sma55[0] and self.sma21[-1] < self.sma55[-1]:
# 记录输出买入价格
# self.log('买入信号产生的价格: %.2f' % self.dataclose[0])
# 跟踪已经创建好的订单避免重复第二次交易
self.order = self.buy()
else:
if self.sma21[0] < self.sma55[0] and self.sma21[-1] > self.sma55[-1]:
# self.log('卖入信号产生的价格: %.2f' % self.dataclose[0])
self.order = self.sell()
# 记录交易执行情况,输出打印
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.buyprice = order.executed.price
# self.buycomm = order.executed.comm
# else: # Sell
# self.log('卖出价格: %.2f, 市值: %.2f, 手续费 %.2f' %
# (order.executed.price,
# order.executed.value,
# order.executed.comm))
# # len(self)是指获取截至当前数据一共有多少根bar
# # 以下代码就是指当交易发生时立刻记录下了当天有多少根bar
# # 如果要表示当成交后过了5天卖,则可以这样写 if len(self) >= (self.bar_executed + 5):
# self.bar_executed = len(self)
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
self.log('Order Canceled/Margin/Rejected')
self.order = None
# 记录交易收益情况
def notify_trade(self, trade):
if not trade.isclosed: # 如果交易还没有关闭,则退出不输出显示盈利跟手续费
return
# self.log('策略收益 %.2f, 净收益 %.2f' %
# (trade.pnl, trade.pnlcomm))
def stop(self):
# 策略停止输出结果
total_funds = self.broker.getvalue()
print('MA均线: %2d日,总资金: %.2f' % (self.params.maperiod21, total_funds))
def run_optimization():
stock_code = '000001.SZ'
stock_start_date = '20150101'
stock_end_date = '20200828'
df = ts.pro_bar(ts_code=stock_code, start_date=stock_start_date, end_date=stock_end_date, adj='qfq', freq='D')
df['trade_date'] = pd.to_datetime(df['trade_date'])
# df = df.drop(['change', 'pre_close', 'pct_chg', 'amount'], axis=1)
df = df.rename(columns={'vol': 'volume'})
df.set_index('trade_date', inplace=True) # 设置索引覆盖原来的数据
df = df.sort_index(ascending=True) # 将时间顺序升序,符合时间序列
df['openinterest'] = 0
# 喂养数据到backtrader当中去
back_start_time = datetime.datetime(2015, 1, 1)
back_end_time = datetime.datetime(2020, 8, 28)
data = bt.feeds.PandasData(dataname=df,
fromdate=back_start_time,
todate=back_end_time
)
# 创建策略容器
cerebro = bt.Cerebro()
# 添加自定义的策略my_strategy
cerebro.optstrategy(my_optimization, maperiod21=range(3, 54))
# 添加数据
cerebro.adddata(data)
# 设置资金
startcash = 100000
cerebro.broker.setcash(startcash)
# 设置每笔交易交易的股票数量
cerebro.addsizer(bt.sizers.FixedSize, stake=100)
# 设置手续费
cerebro.broker.setcommission(commission=0.01)
print('期初总资金: %.2f' %
cerebro.broker.getvalue())
cerebro.run(maxcpus=1)