python 股票量化盘后分析系统V0.45

前言:
先放效果先吧:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
一:这次更新主要是增加了策略文件这一功能,主要目的是为了方便切换策略测试。增添了弹出菜单新建策略、编辑、运行该策略、重命名、删除、刷新功能。
二:其中在点击回测是会在你项目的路径增添策略文件这一新的文件夹,之后的新建策略也会是在这一文件夹内,新建的文件格式是txt,在新建文件后最好能重命名一下,如果不重命名则最多可以新建两个文件相同的文件,新建文件后要手动点击菜单的刷新才会有显示,或者重新运行程序。重命名是最好加上.txt后缀。
三:每次点击Mplfinance或者其他功能回测按钮时,请先选中策略选中运行该策略功能在进行回测,否则会弹窗提示。
四:文件txt策略内容的缩进如图所示,全部的缩进是原来格式一个’shift‘ + ‘Tab’,前面不能有空格。最好在pycharm编辑策略跟缩进再放到txt文件内。
其他代码不变,修改的只是stock_backtrader.py文件,其他代码请到V0.42版本复制

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 mplfinance as mpf
import os
import sys
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)  # 使用后端TkAgg

# pd.set_option()就是pycharm输出控制显示的设置
pd.set_option('expand_frame_repr', False)  # True就是可以换行显示。设置成False的时候不允许换行
pd.set_option('display.max_columns', None)  # 显示所有列
# pd.set_option('display.max_rows', None)  # 显示所有行
pd.set_option('colheader_justify', 'centre')  # 显示居中

pro = ts.pro_api('数据用的是tushare,没权限自己去注册个吧')


# 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])  # 输出打印收盘价
#         # self.log('持仓 %.2f' % self.position.size)  # 输出持仓
#         # 检查是否有订单发送当中,如果有则不再发送第二个订单
#         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, bg='#353535', bd=5, borderwidth=4)
    backtrader_left_frame.pack(fill=tk.BOTH, expand=1)

    # ******************************************************************************************************************
    # path = sys.path[0]  # 启动的py文件所在的路径
    File_Path = os.getcwd() + '\\策略文件'  # 获取到当前文件的目录,并检查是否有‘策略文件’文件夹,如果不存在则自动新建‘策略文件’文件夹
    if not os.path.exists(File_Path):
        os.makedirs(File_Path)
    pathList = os.path.split(File_Path)  # 分别获取得到绝对路径跟文件夹名称,得到的是个列表
    pathlist_name = pathList[-1]  # 获取文件夹名称,如果是[0]则是获取绝对路径

    strategy_list_tree = ttk.Treeview(backtrader_left_frame, show='tree')
    father_treeview = strategy_list_tree.insert("", "end", text=pathlist_name, open=True)  # 写入父节名称
    for filepath in os.listdir(File_Path):
        strategy_list_tree.insert(father_treeview, "end", text=filepath)  # 写入子节名称

    strategy_list_tree.pack(fill=tk.BOTH, expand=1)
    backtrader_window.add(backtrader_left_frame, width=tk_window.screenHeight / 5.5)

    def create_newfile():
        if not os.path.exists(File_Path + '\\' + '新建策略.txt'):
            txt_file = open(File_Path + '\\' + '新建策略.txt', 'ab+')
            txt_file.close()
        else:
            txt_file = open(File_Path + '\\' + '新建策略1.txt', 'ab+')
            txt_file.close()

    def rename_newfile():
        rename_input_frame = tk.Toplevel()
        rename_input_frame.title('重命名')
        rename_input_frame.geometry('{}x{}+{}+{}'.format(180, 35, int(tk_window.screenWidth / 4),
                                                         int(tk_window.screenHeight / 4)))
        rename_input_var = tk.StringVar()
        rename_input_widget = tk.Entry(rename_input_frame, textvariable=rename_input_var, justify=tk.CENTER)
        rename_input_widget.pack(side=tk.LEFT)

        def rename_now():
            for item in strategy_list_tree.selection():
                item_text = strategy_list_tree.item(item, "text")  # 获取选中树形条目的名称
                os.rename(File_Path + '\\' + item_text, File_Path + '\\' + rename_input_var.get())

        rename_button = tk.Button(rename_input_frame, text='rename', height=1, command=rename_now)
        rename_button.pack(side=tk.RIGHT)

    def edit_file():
        for item in strategy_list_tree.selection():
            item_text = strategy_list_tree.item(item, "text")  # 获取选中树形条目的名称
            select_filepath = File_Path + '\\' + item_text  # 得到选中项目的绝对路径
            os.startfile(select_filepath)  # 打开文件,如果不是txt格式的文件会弹出窗口让你选择打开方式

    # 设置获取策略文件txt内容的函数,首先通过read获取内容,然后通过exec(use_strategy())将本函数返回的str格式文本内容转换成可执行的代码
    # 在每个策略运行前的代码先写class my_strategy(bt.Strategy):,然后再写exec(use_strategy())
    # 在运行策略是先选中你建立的txt策略文件,然后右键鼠标在弹出的菜单中选中使用该策略选项,之后再运行回测
    def use_strategy():
        for item in strategy_list_tree.selection():
            item_text = strategy_list_tree.item(item, "text")  # 获取选中树形条目的名称
            select_filepath = File_Path + '\\' + item_text  # 得到选中项目的绝对路径
            txt_file = open(select_filepath, 'r')
            txt_data = txt_file.read()
            txt_file.close()
            return txt_data

    def delect_file():
        for item in strategy_list_tree.selection():
            item_text = strategy_list_tree.item(item, "text")  # 获取选中树形条目的名称
            select_filepath = File_Path + '\\' + item_text  # 得到选中项目的绝对路径
            delect_confirm = tk.messagebox.askokcancel('提示', '要执行此操作吗?文件直接删除不放回收站!')
            if delect_confirm:  # 如果返回True,则执行删除,
                os.remove(select_filepath)  # 打开文件,如果不是txt格式的文件会弹出窗口让你选择打开方式

    # 创建弹出菜单,为后面功能开发做准备
    strategy_menu = tk.Menu(backtrader_left_frame, tearoff=False)  # tearoff=True显示分割线
    strategy_menu.add_command(label='新建策略', command=create_newfile)  # 弹出菜单内容
    strategy_menu.add_command(label='编辑', command=edit_file)  # 弹出菜单内容
    strategy_menu.add_command(label='运行该策略', command=use_strategy)  # 弹出菜单内容
    strategy_menu.add_command(label='重命名', command=rename_newfile)  # 弹出菜单内容
    strategy_menu.add_command(label='删除', command=delect_file)
    strategy_menu.add_separator()
    strategy_menu.add_command(label='刷新', command=run_cerebro)

    def pop(event):
        strategy_menu.post(event.x_root, event.y_root)  # #设置弹出的位置

    strategy_list_tree.bind('<Button-3>', pop)  # 设置右键弹出菜单

    # ******************************************************************************************************************
    # 创建右边图形输出框架,主要放回测分析显示跟用户输入的股票代码跟日期
    backtrader_plot_window = tk.PanedWindow(orient='vertical', opaqueresize=False)
    backtrader_window.add(backtrader_plot_window)

    backtrader_plot_window_top = tk.PanedWindow(opaqueresize=False)
    backtrader_plot_window.add(backtrader_plot_window_top)
    # ******************************************************************************************************************
    backtrader_top_left_frame = tk.Frame(backtrader_plot_window_top, width=tk_window.screenWidth,
                                         height=tk_window.screenHeight, relief=tk.SUNKEN, bg='#353535', bd=5,
                                         borderwidth=4)
    backtrader_top_left_frame.pack(fill=tk.BOTH)
    # 在主框架下创建股票代码输入子框架
    code_frame = tk.Frame(backtrader_top_left_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_left_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_top.add(backtrader_top_left_frame, height=tk_window.screenHeight / 10,
                                   width=tk_window.screenHeight / 1.4)
    # ******************************************************************************************************************
    backtrader_top_right_frame = tk.Frame(backtrader_plot_window_top, width=tk_window.screenWidth,
                                          height=tk_window.screenHeight, relief=tk.SUNKEN, bg='#353535', bd=5,
                                          borderwidth=4)
    backtrader_top_right_frame.pack(fill=tk.BOTH)

    # 在主框架右边创建股票代码输入子框架
    multi_code_frame = tk.Frame(backtrader_top_right_frame, borderwidth=1, bg='#353535')
    multi_code_frame.pack()
    # 创建标签‘股票代码’
    multi_stock_label = tk.Label(multi_code_frame, text='多股回测股票代码', bd=1)
    multi_stock_label.pack(side=tk.LEFT)
    # 创建股票代码输入框
    input_multi_code_var = tk.StringVar()
    multi_code_widget = tk.Entry(multi_code_frame, textvariable=input_multi_code_var, borderwidth=1, justify=tk.CENTER)
    # input_code_get = input_code_var.set(input_code_var.get())  # 获取输入的新值
    multi_code_widget.pack(side=tk.LEFT, padx=4)

    # 在主框架下创建股票日期输入框子框架
    multi_input_date_frame = tk.Frame(backtrader_top_right_frame, borderwidth=1, bg='#353535')
    multi_input_date_frame.pack()
    # 创建标签‘开始日期’
    multi_date_start_label = tk.Label(multi_input_date_frame, text='开始日期', bd=1)
    multi_date_start_label.pack(side=tk.LEFT)
    # 创建开始日期代码输入框
    multi_input_startdate_var = tk.StringVar()
    multi_startdate_widget = tk.Entry(multi_input_date_frame, textvariable=multi_input_startdate_var, borderwidth=1,
                                      justify=tk.CENTER)
    input_startdate_get = multi_input_startdate_var.set(multi_input_startdate_var.get())  # 获取输入的新值
    multi_startdate_widget.pack(side=tk.LEFT, padx=4)
    # 创建标签‘结束日期’
    multi_date_end_label = tk.Label(multi_input_date_frame, text='结束日期', bd=1)
    multi_date_end_label.pack(side=tk.LEFT)
    # 创建结束日期代码输入框
    multi_input_enddate_var = tk.StringVar()
    multi_enddate_widget = tk.Entry(multi_input_date_frame, textvariable=multi_input_enddate_var, borderwidth=1,
                                    justify=tk.CENTER)
    multi_input_enddate_get = multi_input_enddate_var.set(multi_input_enddate_var.get())  # 获取输入的新值
    multi_enddate_widget.pack(side=tk.LEFT, padx=4)

    # 先把部件布局好了再backtrader_top_frame用.add()添加到backtrader_plot_window
    backtrader_plot_window_top.add(backtrader_top_right_frame, height=tk_window.screenHeight / 10,
                                   width=tk_window.screenWidth / 2)
    # ******************************************************************************************************************

    backtrader_plot_window_bottom = tk.PanedWindow(opaqueresize=False)
    backtrader_plot_window.add(backtrader_plot_window_bottom)
    # 创建底部窗口框架,用来放图形输出
    backtrader_bottom_frame = tk.Frame(backtrader_plot_window_bottom, 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_bottom.add(backtrader_bottom_frame)

    # ******************************************************************************************************************

    def mplfinance_go():  # 图形输出渲染
        # 在backtrader_bottom_frame的原有基础上再创建一个框架,目的方便在更新股票股票回测时防止图形重叠
        for widget_backtrader_bottom_frame in backtrader_bottom_frame.winfo_children():
            widget_backtrader_bottom_frame.destroy()
        # 创建左右两个frame框架方便管理布局大小跟刷新,framed大小跟控件的长高有关
        backtrader_bottomleft_frame = tk.Frame(backtrader_bottom_frame, bg='#353535', bd=5, borderwidth=4)
        backtrader_bottomleft_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
        backtrader_bottomright_frame = tk.Frame(backtrader_bottom_frame, bg='#353535', bd=5, borderwidth=4)
        backtrader_bottomright_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=0)

        # 以下函数作用是省略输入代码后缀.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

        # 交互数据的获取跟处理
        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()
        try:
            class my_strategy(bt.Strategy):
                exec(use_strategy())
        except Exception as e_class:
            tk.messagebox.showwarning(title='错误', message='请先选择运行策略再进行回测')
            print('请先选择运行策略再进行回测')

        try:
            # 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)

            # 设置用于backtrader的数据
            df_back = df.rename(columns={'vol': 'volume'})
            df_back.set_index('trade_date', inplace=True)  # 设置索引覆盖原来的数据
            df_back = df_back.sort_index(ascending=True)  # 将时间顺序升序,符合时间序列
            df_back['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_back = bt.feeds.PandasData(dataname=df_back,
                                            fromdate=back_start_time,
                                            todate=back_end_time)

            # 设置用于mpf图形的数据
            data_mpf = df.loc[:, ['trade_date', 'open', 'close', 'high', 'low', 'vol']]  # :取所有行数据,后面取date列,open列等数据
            data_mpf = data_mpf.rename(
                columns={'trade_date': 'Date', 'open': 'Open', 'close': 'Close', 'high': 'High', 'low': 'Low',
                         'vol': 'Volume'})  # 更换列名,为后面函数变量做准备
            data_mpf.set_index('Date', inplace=True)  # 设置date列为索引,覆盖原来索引,这个时候索引还是 object 类型,就是字符串类型。
            # 将object类型转化成 DateIndex 类型,pd.DatetimeIndex 是把某一列进行转换,同时把该列的数据设置为索引 index。
            data_mpf.index = pd.DatetimeIndex(data_mpf.index)
            data_mpf = data_mpf.sort_index(ascending=True)  # 将时间顺序升序,符合时间序列
            # print(data_mpf)

            # 创建策略容器
            cerebro = bt.Cerebro()
            # 添加自定义的策略my_strategy
            cerebro.addstrategy(my_strategy)
            # 添加数据
            cerebro.adddata(data_back)
            # 设置资金
            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')

            cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='SharpeRatio')
            cerebro.addanalyzer(bt.analyzers.DrawDown, _name='DW')
            cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name='TradeAnalyzer')
            cerebro.addanalyzer(bt.analyzers.Transactions, _name='Transactions')

            # 运行策略
            # stdstats=False不显示回测的统计结果
            result = cerebro.run(stdstats=True, optreturn=False)
            backtrader_analysis = result[0]
            # print(backtrader_analysis.analyzers.SharpeRatio.get_analysis())
            # print(backtrader_analysis.analyzers.DW.get_analysis())
            # print(backtrader_analysis.analyzers.TradeAnalyzer.get_analysis())
            # 在下面的占位符后面不能有空格,否则空格后面的输入信息是输不进treeview的单元格
            startcash_value = '初始资金:%.2f' % startcash
            endcash_value = '期末资金:%.2f' % cerebro.broker.getvalue()
            try:
                completed_net = '已完成盈亏:%.2f' % backtrader_analysis.analyzers.TradeAnalyzer.get_analysis()['pnl']['net'][
                    'total']
            except Exception as e0:
                completed_net = '已完成盈亏:%s' % None
            try:
                float_profit = '浮动盈亏:%.2f' % (cerebro.broker.getvalue() - startcash -
                                              backtrader_analysis.analyzers.TradeAnalyzer.get_analysis()['pnl']['net'][
                                                  'total'])
            except Exception as e1:
                float_profit = '浮动盈亏:%s' % None
            try:
                completed_commission = '手续费用:%.2f' % (
                        backtrader_analysis.analyzers.TradeAnalyzer.get_analysis()['pnl']['gross']['total'] -
                        backtrader_analysis.analyzers.TradeAnalyzer.get_analysis()['pnl']['net']['total'])
            except Exception as e2:
                completed_commission = '手续费用:%s' % None
            start_backtrade_date = '回测开始时间:%s' % d1
            end_backtrade_date = '回测结束时间:%s' % d2
            try:
                sharpeRatio_value = '夏普比例:%.2f' % backtrader_analysis.analyzers.SharpeRatio.get_analysis()[
                    'sharperatio']
            except Exception as e3:
                sharpeRatio_value = '夏普比例:%s' % None
            try:
                drawdown_value = '最大回撤:%.2f' % backtrader_analysis.analyzers.DW.get_analysis()['max']['drawdown']
            except Exception as e4:
                drawdown_value = '最大回撤:%s' % None
            try:
                moneydown_value = '最大资金回撤:%.2f' % backtrader_analysis.analyzers.DW.get_analysis()['max']['moneydown']
            except Exception as e5:
                moneydown_value = '最大资金回撤:%s' % None
            try:
                max_drawdown_lastday = '最大回撤持续天数:%d' % backtrader_analysis.analyzers.DW.get_analysis()['max']['len']
            except Exception as e6:
                max_drawdown_lastday = '最大回撤持续天数:%s' % None
            try:
                total_value = '交易次数:%d' % backtrader_analysis.analyzers.TradeAnalyzer.get_analysis()['total']['total']
            except Exception as e7:
                total_value = '交易次数:%s' % None
            try:
                uncompleted_trade = '未完成交易数量:%d' % backtrader_analysis.analyzers.TradeAnalyzer.get_analysis()['total'][
                    'open']
            except Exception as e8:
                uncompleted_trade = '未完成交易数量:%s' % None
            try:
                completed_trade = '已完成交易数量:%d' % backtrader_analysis.analyzers.TradeAnalyzer.get_analysis()['total'][
                    'closed']
            except Exception as e9:
                completed_trade = '已完成交易数量:%s' % None
            try:
                win_value = '盈利次数:%d' % backtrader_analysis.analyzers.TradeAnalyzer.get_analysis()['won']['total']
            except Exception as e10:
                win_value = '盈利次数:%s' % None
            try:
                lost_value = '亏损次数:%d' % backtrader_analysis.analyzers.TradeAnalyzer.get_analysis()['lost']['total']
            except Exception as e11:
                lost_value = '亏损次数:%s' % None
            try:
                win_rate = '胜率:%.2f' % (backtrader_analysis.analyzers.TradeAnalyzer.get_analysis()['won']['total'] /
                                        backtrader_analysis.analyzers.TradeAnalyzer.get_analysis()['total']['total'])
            except Exception as e12:
                win_rate = '胜率:%s' % None
            try:
                lost_rate = '败率:%.2f' % (backtrader_analysis.analyzers.TradeAnalyzer.get_analysis()['lost']['total'] /
                                         backtrader_analysis.analyzers.TradeAnalyzer.get_analysis()['total']['total'])
            except Exception as e13:
                lost_rate = '败率:%s' % None

            analysis_log = []  # 设置空列表用来接收回测记录
            history_trade_buy_date_list = []  # 设置空列表用来接收买点标记的时间日期,下面的空列表都是为标记做准备
            history_trade_sell_date_list = []
            history_trade_buy_vol_list = []
            history_trade_sell_vol_list = []
            history_trade_buy_price_list = []
            history_trade_sell_price_list = []
            trade_signal_buy = pd.DataFrame(columns=['Date', 'buy_price', 'buy_vol'])  # 创建买点dataframe
            trade_signal_sell = pd.DataFrame(columns=['Date', 'sell_price', 'sell_vol'])
            analysis_log.extend([startcash_value, endcash_value, float_profit, completed_net, completed_commission,
                                 start_backtrade_date, end_backtrade_date, sharpeRatio_value, drawdown_value,
                                 moneydown_value, max_drawdown_lastday, total_value, uncompleted_trade, completed_trade,
                                 win_value, lost_value, win_rate, lost_rate])
            for key, value in backtrader_analysis.analyzers.Transactions.get_analysis().items():
                trade_log = '日期:%s,价格:%.2f,数量:%d,市值:%.2f' % (key.strftime('%Y-%m-%d'), value[0][1], value[0][0],
                                                             value[0][4])
                analysis_log.extend([trade_log])

                history_trade_date = key.strftime('%Y-%m-%d')
                history_trade_price = value[0][1]
                history_trade_vol = value[0][0]

                if history_trade_vol > 0:
                    history_trade_buy_date_list.append(history_trade_date)
                    history_trade_buy_price_list.append(history_trade_price)
                    history_trade_buy_vol_list.append(history_trade_vol)
                elif history_trade_vol < 0:
                    history_trade_sell_date_list.append(history_trade_date)
                    history_trade_sell_price_list.append(history_trade_price)
                    history_trade_sell_vol_list.append(history_trade_vol)

            trade_signal_buy['Date'] = history_trade_buy_date_list
            trade_signal_buy['buy_price'] = history_trade_buy_price_list
            trade_signal_buy['buy_vol'] = history_trade_buy_vol_list
            trade_signal_buy.set_index('Date', inplace=True)
            trade_signal_buy.index = pd.DatetimeIndex(trade_signal_buy.index)

            trade_signal_sell['Date'] = history_trade_sell_date_list
            trade_signal_sell['sell_price'] = history_trade_sell_price_list
            trade_signal_sell['sell_vol'] = history_trade_sell_vol_list
            trade_signal_sell.set_index('Date', inplace=True)
            trade_signal_sell.index = pd.DatetimeIndex(trade_signal_sell.index)

            backtrader_treeview = ttk.Treeview(backtrader_bottomright_frame, columns=['1'], show='headings')
            # 在treeview布局钱先布局横竖滚动条,不然会出现布局问题,你可以试着将滚动条代码放在最后试下
            VScroll1 = ttk.Scrollbar(backtrader_bottomright_frame, orient='vertical', command=backtrader_treeview.yview)
            backtrader_treeview.configure(yscrollcommand=VScroll1.set)
            VScroll1.pack(side=tk.RIGHT, fill=tk.Y)

            backtrader_treeview.column('1', width=int(tk_window.screenWidth / 4), anchor='w')
            backtrader_treeview.heading('1', text='回测记录')
            backtrader_treeview.pack(side=tk.LEFT, fill=tk.BOTH, expand=0)
            for i in range(len(analysis_log)):  # 写入回测记录
                backtrader_treeview.insert('', 'end', values=analysis_log[i])

            # 合并前面的买卖数据dataframe,为绘图做准备
            trade_all = pd.merge(left=data_mpf, right=trade_signal_buy, left_index=True, right_index=True, how='outer')
            trade_all = pd.merge(left=trade_all, right=trade_signal_sell, left_index=True, right_index=True,
                                 how='outer')
            # print(trade_all)
            # grid = False不显示分割线
            # cerebro.plot(style='candlestick', grid=False, iplot=False)
            colors_set = mpf.make_marketcolors(up='red', down='green', edge='i', wick='i', volume='in', inherit=True)
            # 设置图形风格,gridaxis:设置网格线位置,gridstyle:设置网格线线型,y_on_right:设置y轴位置是否在右
            mpf_style = mpf.make_mpf_style(gridaxis='horizontal', gridstyle='-.', y_on_right=False, facecolor='white',
                                           figcolor='white', marketcolors=colors_set)
            # 添加买卖点附图
            try:  # 设置try语句是预防当只有一个买信号没有卖信号发生报错的情况,比如002978 605388
                add_plot = [
                    mpf.make_addplot(trade_all['buy_price'], scatter=True, markersize=130, marker='^', color='r'),
                    mpf.make_addplot(trade_all['sell_price'], scatter=True, markersize=130, marker='v', color='g')]
                daily_fig, axlist = mpf.plot(data_mpf, type='candle', mav=(21, 55), volume=True, show_nontrading=False,
                                             style=mpf_style, addplot=add_plot, returnfig=True)
                canvas_stock_daily_basic = FigureCanvasTkAgg(daily_fig, master=backtrader_bottomleft_frame)
                canvas_stock_daily_basic.draw()
                toolbar_stock_daily_basic = NavigationToolbar2Tk(canvas_stock_daily_basic, backtrader_bottomleft_frame)
                toolbar_stock_daily_basic.update()  # 显示图形导航工具条
                canvas_stock_daily_basic._tkcanvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
                plt.cla()  # 清除axes,即当前 figure 中的活动的axes,但其他axes保持不变。
            except Exception as e_plot1:
                try:
                    add_plot = [
                        mpf.make_addplot(trade_all['buy_price'], scatter=True, markersize=130, marker='^', color='r')]
                    daily_fig, axlist = mpf.plot(data_mpf, type='candle', mav=(21, 55), volume=True,
                                                 show_nontrading=False,
                                                 style=mpf_style, addplot=add_plot, returnfig=True)
                    canvas_stock_daily_basic = FigureCanvasTkAgg(daily_fig, master=backtrader_bottomleft_frame)
                    canvas_stock_daily_basic.draw()
                    toolbar_stock_daily_basic = NavigationToolbar2Tk(canvas_stock_daily_basic,
                                                                     backtrader_bottomleft_frame)
                    toolbar_stock_daily_basic.update()  # 显示图形导航工具条
                    canvas_stock_daily_basic._tkcanvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
                    plt.cla()
                except Exception as e_plot2:
                    daily_fig, axlist = mpf.plot(data_mpf, type='candle', mav=(21, 55), volume=True,
                                                 show_nontrading=False,
                                                 style=mpf_style, returnfig=True)
                    canvas_stock_daily_basic = FigureCanvasTkAgg(daily_fig, master=backtrader_bottomleft_frame)
                    canvas_stock_daily_basic.draw()
                    toolbar_stock_daily_basic = NavigationToolbar2Tk(canvas_stock_daily_basic,
                                                                     backtrader_bottomleft_frame)
                    toolbar_stock_daily_basic.update()  # 显示图形导航工具条
                    canvas_stock_daily_basic._tkcanvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
                    plt.cla()
        except Exception as e_cerebro:
            tk.messagebox.showwarning(title='错误',
                                      message='%s 数据不足!请查看股票策略指标的参数跟回测日期的数据是否相符以支持回测' % code_name)
            print('%s 数据不足!请查看股票策略指标的参数跟回测日期的数据是否相符以支持回测' % code_name)

    # ******************************************************************************************************************

    def backtrader_go():
        plt.close('all')  # 先关闭下plt,不关闭的话会在你点完mpl回测后再点backtrader回测报错,可以试着去掉看下有什么BUG

        # 以下函数作用是省略输入代码后缀.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

        # 交互数据的获取跟处理
        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()

        class my_strategy(bt.Strategy):
            exec(use_strategy())

        try:
            # 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)

            # 设置用于backtrader的数据
            df_back = df.rename(columns={'vol': 'volume'})
            df_back.set_index('trade_date', inplace=True)  # 设置索引覆盖原来的数据
            df_back = df_back.sort_index(ascending=True)  # 将时间顺序升序,符合时间序列
            df_back['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_back = bt.feeds.PandasData(dataname=df_back,
                                            fromdate=back_start_time,
                                            todate=back_end_time)

            # 创建策略容器
            cerebro_single = bt.Cerebro()
            # 添加自定义的策略my_strategy
            cerebro_single.addstrategy(my_strategy)
            # 添加数据
            cerebro_single.adddata(data_back)
            # 设置资金
            startcash_single = 100000
            cerebro_single.broker.setcash(startcash_single)
            # 设置每笔交易交易的股票数量
            cerebro_single.addsizer(bt.sizers.FixedSize, stake=100)
            # 设置手续费
            cerebro_single.broker.setcommission(commission=0.0005)
            # 运行策略,stdstats=False不显示回测的统计结果
            cerebro_single.run(stdstats=True, optreturn=False)
            # grid = False不显示分割线
            cerebro_single.plot(style='candlestick', grid=False, iplot=False)
        except Exception as e_cerebro:
            tk.messagebox.showwarning(title='错误',
                                      message='%s 数据不足!请查看股票策略指标的参数跟回测日期的数据是否相符以支持回测' % code_name)
            print('%s 数据不足!请查看股票策略指标的参数跟回测日期的数据是否相符以支持回测' % code_name)

    # ******************************************************************************************************************

    def multibacktrader_go():
        # 在backtrader_bottom_frame的原有基础上再创建一个框架,目的方便在更新股票股票回测时防止图形重叠
        for widget_backtrader_bottom_frame in backtrader_bottom_frame.winfo_children():
            widget_backtrader_bottom_frame.destroy()

        multi_stock_list = []

        # 以下函数作用是省略输入代码后缀.sz .sh
        def multi_code_name_transform(get_stockcode):  # 输入的数字股票代码转换成字符串股票代码
            str_stockcode = str(get_stockcode).split(',')  # 分隔符是小写,不是大写,逗号
            for s in str_stockcode:
                s = s.strip()  # 删除前后空格字符
                if 6 > len(s) > 0:
                    s = s.zfill(6) + '.SZ'  # zfill()函数返回指定长度的字符串,原字符串右对齐,前面填充0
                if len(s) == 6:
                    if s[0:1] == '0':
                        s = s + '.SZ'
                    if s[0:1] == '3':
                        s = s + '.SZ'
                    if s[0:1] == '6':
                        s = s + '.SH'
                multi_stock_list.append(s)
            return multi_stock_list

        # 交互数据的获取跟处理
        stock_name = input_multi_code_var.get()
        df_basic_all = pro.stock_basic(exchange='', list_status='L')  # 获取所有上市公司的信息为全部上市公司回测做准备

        class my_strategy(bt.Strategy):
            exec(use_strategy())

        if not stock_name:  # 如果输入的股票代码为空值
            multi_code_name = df_basic_all['ts_code']
        else:
            multi_code_name = multi_code_name_transform(stock_name)
        # print(multi_code_name)
        start_date = multi_input_startdate_var.get()
        end_date = multi_input_enddate_var.get()
        multi_df = pd.DataFrame(columns=['股票代码', '股票名称', '初始资金', '期末资金', '浮动盈亏', '已完成盈亏', '手续费',
                                         '夏普', '最大回撤', '资金回撤', '已回撤天数', '交易次数', '未完成交易', '已完成交易',
                                         '盈亏次数', '亏损次数', '胜率', '败率'])
        # 先设置表的列名有哪些
        multi_area = ('股票代码', '股票名称', '初始资金', '期末资金', '浮动盈亏', '已完成盈亏', '手续费', '夏普', '最大回撤',
                      '资金回撤', '已回撤天数', '交易次数', '未完成交易', '已完成交易', '盈亏次数', '亏损次数', '胜率', '败率')

        multi_stock_treeview = ttk.Treeview(backtrader_bottom_frame, columns=multi_area, show='headings')
        # 在treeview布局钱先布局横竖滚动条,不然会出现布局问题,你可以试着将滚动条代码放在最后试下
        VScroll1 = ttk.Scrollbar(backtrader_bottom_frame, orient='vertical', command=multi_stock_treeview.yview)
        multi_stock_treeview.configure(yscrollcommand=VScroll1.set)
        VScroll1.pack(side=tk.RIGHT, fill=tk.Y)

        HScroll1 = ttk.Scrollbar(backtrader_bottom_frame, orient='horizontal', command=multi_stock_treeview.xview)
        multi_stock_treeview.configure(xscrollcommand=HScroll1.set)
        HScroll1.pack(side=tk.BOTTOM, fill=tk.X)

        for i in range(len(multi_area)):  # 命名列表名
            multi_stock_treeview.column(multi_area[i], anchor='center')
            multi_stock_treeview.heading(multi_area[i], text=multi_area[i])
        multi_stock_treeview.pack(fill=tk.BOTH, expand=1)

        for multi_c in multi_code_name:  # 循环获取输入的股票代码
            try:
                # adj='qfq'向前复权,freq='D 数据频度:日K线
                df = ts.pro_bar(ts_code=multi_c, start_date=start_date, end_date=end_date, adj='qfq', freq='D')
                multi_stock_basic = pro.stock_basic(ts_code=multi_c, list_status='L')
                df['trade_date'] = pd.to_datetime(df['trade_date'])
                # df = df.drop(['change', 'pre_close', 'pct_chg', 'amount'], axis=1)
                # 设置用于backtrader的数据
                df_back = df.rename(columns={'vol': 'volume'})
                df_back.set_index('trade_date', inplace=True)  # 设置索引覆盖原来的数据
                df_back = df_back.sort_index(ascending=True)  # 将时间顺序升序,符合时间序列
                df_back['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_back = bt.feeds.PandasData(dataname=df_back,
                                                fromdate=back_start_time,
                                                todate=back_end_time)

                # 创建策略容器
                cerebro = bt.Cerebro()
                # 添加自定义的策略my_strategy
                cerebro.addstrategy(my_strategy)
                # 添加数据
                cerebro.adddata(data_back)
                # 设置资金
                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')

                cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='SharpeRatio')
                cerebro.addanalyzer(bt.analyzers.DrawDown, _name='DW')
                cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name='TradeAnalyzer')
                cerebro.addanalyzer(bt.analyzers.Transactions, _name='Transactions')
                # 运行策略
                # stdstats=False不显示回测的统计结果
                result = cerebro.run(stdstats=True, optreturn=False)
                backtrader_analysis = result[0]
                multi_df['股票代码'] = multi_stock_basic['symbol']
                multi_df['股票名称'] = multi_stock_basic['name']
                multi_df['初始资金'] = startcash
                try:
                    multi_df['期末资金'] = round(cerebro.broker.getvalue(), 2)
                except Exception as e1:
                    multi_df['期末资金'] = None
                try:
                    multi_df['浮动盈亏'] = round((cerebro.broker.getvalue() - startcash -
                                              backtrader_analysis.analyzers.TradeAnalyzer.get_analysis()['pnl']['net']
                                              ['total']), 2)
                except Exception as e2:
                    multi_df['浮动盈亏'] = None
                try:
                    multi_df['已完成盈亏'] = round(backtrader_analysis.analyzers.TradeAnalyzer.get_analysis()['pnl']['net']
                                              ['total'], 2)
                except Exception as e3:
                    multi_df['已完成盈亏'] = None
                try:
                    multi_df['手续费'] = round((backtrader_analysis.analyzers.TradeAnalyzer.get_analysis()['pnl']['gross']
                                             ['total'] -
                                             backtrader_analysis.analyzers.TradeAnalyzer.get_analysis()['pnl']
                                             ['net']['total']), 2)
                except Exception as e4:
                    multi_df['手续费'] = None
                try:
                    multi_df['夏普'] = round(backtrader_analysis.analyzers.SharpeRatio.get_analysis()['sharperatio'], 2)
                except Exception as e5:
                    multi_df['夏普'] = None
                try:
                    multi_df['最大回撤'] = round(backtrader_analysis.analyzers.DW.get_analysis()['max']['drawdown'], 2)
                except Exception as e6:
                    multi_df['最大回撤'] = None
                try:
                    multi_df['资金回撤'] = round(backtrader_analysis.analyzers.DW.get_analysis()['max']['moneydown'], 2)
                except Exception as e7:
                    multi_df['资金回撤'] = None
                try:
                    multi_df['已回撤天数'] = backtrader_analysis.analyzers.DW.get_analysis()['max']['len']
                except Exception as e8:
                    multi_df['已回撤天数'] = None
                try:
                    multi_df['交易次数'] = backtrader_analysis.analyzers.TradeAnalyzer.get_analysis()['total']['total']
                except Exception as e9:
                    multi_df['交易次数'] = None
                try:
                    multi_df['未完成交易'] = backtrader_analysis.analyzers.TradeAnalyzer.get_analysis()['total']['open']
                except Exception as e10:
                    multi_df['未完成交易'] = None
                try:
                    multi_df['已完成交易'] = backtrader_analysis.analyzers.TradeAnalyzer.get_analysis()['total']['closed']
                except Exception as e11:
                    multi_df['已完成交易'] = None
                try:
                    multi_df['盈亏次数'] = backtrader_analysis.analyzers.TradeAnalyzer.get_analysis()['won']['total']
                except Exception as e12:
                    multi_df['盈亏次数'] = None
                try:
                    multi_df['亏损次数'] = backtrader_analysis.analyzers.TradeAnalyzer.get_analysis()['lost']['total']
                except Exception as e13:
                    multi_df['亏损次数'] = None
                try:
                    multi_df['胜率'] = round((backtrader_analysis.analyzers.TradeAnalyzer.get_analysis()['won']['total'] /
                                            backtrader_analysis.analyzers.TradeAnalyzer.get_analysis()['total'][
                                                'total']), 2)
                except Exception as e14:
                    multi_df['胜率'] = None
                try:
                    multi_df['败率'] = round(
                        (backtrader_analysis.analyzers.TradeAnalyzer.get_analysis()['lost']['total'] /
                         backtrader_analysis.analyzers.TradeAnalyzer.get_analysis()['total']['total']), 2)
                except Exception as e15:
                    multi_df['败率'] = None

                for i in range(len(multi_df.index)):  # 导入插入股票数据
                    # 插入的值数组格式用.tolist()转化成list格式,否则显示会多出‘跟[这种字符串
                    multi_stock_treeview.insert('', 'end', values=multi_df.values[i].tolist())
                print(multi_df)
            except Exception as e_cerebro:
                print('%s 数据不足!请查看股票策略指标的参数跟回测日期的数据是否相符以支持回测' % multi_c)
                continue

        def stock_treeview_sort(tv, col, reverse):  # Treeview、列名、排列方式
            try:
                # tv.set指定item,如果不设定column和value,则返回他们的字典,如果设定了column,则返回该column的value,如果value也设定了,则作相应更改。
                # get_children()函数,其返回的是treeview中的记录号
                # 参照网上的treeview排序方法函数,由于股票的价格排序数据类型是浮点数字,在排序钱将价格类型有str转换成float,否则排序会不正确
                stockdata_list = [(float(tv.set(k, col)), k) for k in tv.get_children('')]
            except Exception:
                stockdata_list = [(tv.set(k, col), k) for k in tv.get_children('')]
                # print(tv.get_children(''))
                # print(stockdata_list)
            stockdata_list.sort(reverse=reverse)  # 排序方式
            # rearrange items in sorted positions
            # 根据排序后索引移动,enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标
            for index, (val, k) in enumerate(stockdata_list):
                tv.move(k, '', index)
                # print(k)
            tv.heading(col, command=lambda col=col: stock_treeview_sort(tv, col, not reverse))  # 重写标题,使之成为再点倒序的标题

        for col in multi_area:
            multi_stock_treeview.column(col, width=70, anchor='center')
            multi_stock_treeview.heading(col, text=col,
                                         command=lambda col=col: stock_treeview_sort(multi_stock_treeview, col, False))

    # ******************************************************************************************************************

    # 在主框架下创建回测按钮子框架
    backtrade_left_button_frame = tk.Frame(backtrader_top_left_frame, borderwidth=1, bg='#353535')
    backtrade_left_button_frame.pack()

    backtrade_right_button_frame = tk.Frame(backtrader_top_right_frame, borderwidth=1, bg='#353535')
    backtrade_right_button_frame.pack()

    # 创建查询按钮并设置功能
    mplfinance_button = tk.Button(backtrade_left_button_frame, text='Mplfinance', height=1, command=mplfinance_go)
    mplfinance_button.pack(side=tk.LEFT, padx=4)
    backtrader_button = tk.Button(backtrade_left_button_frame, text='BackTrader', height=1, command=backtrader_go)
    backtrader_button.pack(side=tk.RIGHT)

    multi_backtrader_button = tk.Button(backtrade_right_button_frame, text='MultiBackTrade', height=1,
                                        command=multibacktrader_go)
    multi_backtrader_button.pack(side=tk.LEFT, padx=4)

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wilburzzz

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值