前言:
学习遇到瓶颈,暂停一段时间更新代码去充更多的电。目前写的这个系统,细节很多方面都没有处理好,比如回测模块的涨跌停处理,多股投资组合处理,代码的简洁优化等等方面,因为细节多,目前感觉无从下手,凌乱中。写了差不多半年了,感谢一路访问并点赞收藏的朋友们,因为你们的支持才能让我更有毅力坚持学习下去。
以下是我更新的代码功能,主要是把全景的功能大盘K线图砍掉了,变成如下功能,日期的查询只能查询昨天的数据,也就是所说假如你要查询1月5号的数据,你就要输入1月6号的日期。效果图如下:
(代码太多,分两篇文章发布,另外的代码在我的博客里)


全套代码如下:
main.py

import tk_window
import graphic
import function
import stock_backtrader
import index_data

tk_window.py

import tkinter as tk
import graphic
import function
import stock_backtrader
import index_dataroot = tk.Tk()  # 创建主窗口
s = graphic.Show()  # Show实例化
screenWidth = root.winfo_screenwidth()  # 获取屏幕宽的分辨率
screenHeight = root.winfo_screenheight()
x, y = int(screenWidth / 4), int(screenHeight / 4)  # 初始运行窗口屏幕坐标(x, y),设置成在左上角显示
width = int(screenWidth / 2)  # 初始化窗口是显示器分辨率的1.2分之一
height = int(screenHeight / 2)
root.geometry('{}x{}+{}+{}'.format(width, height, x, y))  # 窗口的大小跟初始运行位置
root.title('Wilbur量化分析软件')
# root.resizable(0, 0)  # 固定窗口宽跟高,不能调整大小,无法最大窗口化
root.iconbitmap('ZHY.ico')  # 窗口左上角图标设置,需要自己放张图标为icon格式的图片文件在项目文件目录下
root.state('zoomed')    # 窗口最大化处理
# 构建上方菜单栏目框架
# top_frame = tk.Frame(root, width=screenWidth, height=screenHeight, relief=tk.SUNKEN, bg='#353535', bd=5,
#                      borderwidth=4)
# top_frame.pack(fill=tk.BOTH, side=tk.TOP, expand=0)# 构建底部状态栏目框架
bottom_frame = tk.Frame(root, width=screenWidth, height=screenHeight, relief=tk.SUNKEN, bg='#353535', bd=5,borderwidth=4)
bottom_frame.pack(fill=tk.BOTH, side=tk.BOTTOM, expand=0)# 构建左边功能栏目框架
left_frame = tk.Frame(root, width=screenWidth, height=screenHeight, relief=tk.SUNKEN, bg='#353535', bd=5, borderwidth=4)
left_frame.pack(fill=tk.BOTH, side=tk.LEFT, expand=0)# 构建中间显示栏目框架
centre_frame = tk.Frame(root, width=screenWidth, height=screenHeight, relief=tk.SUNKEN, bg='#353535', bd=5,borderwidth=4)
centre_frame.pack(fill=tk.BOTH, expand=1)# # 构建各个框架的标签或按钮
# top_label = tk.Label(top_frame, text='菜单栏目', bd=1)
# top_label.pack()# bottom_label = tk.Label(bottom_frame, text='状态栏目', bd=1)
# bottom_label.pack(side=tk.LEFT)# 在状态栏目添加系统时钟功能
function.time_clock()left_button1 = tk.Button(left_frame, text='全景', bd=1, command=index_data.stockindex_function)
left_button1.pack()
left_button2 = tk.Button(left_frame, text='查询', bd=1, command=s.stock_query_function)
left_button2.pack()
left_button3 = tk.Button(left_frame, text='股票', bd=1, command=s.show_stocklist_function)
left_button3.pack()
left_button4 = tk.Button(left_frame, text='回测', bd=1, command=stock_backtrader.run_cerebro)
left_button4.pack()centre_label = tk.Label(centre_frame, text='显示栏目', bd=1)
centre_label.pack()root.mainloop()

graphic.py

from __future__ import (absolute_import, division, print_function, unicode_literals)
import pandas as pd
import tushare as ts
import mplfinance as mpf
import tkinter as tk
import tkinter.tix as tix
from tkinter import ttk
import tkinter.font as tf
from tkinter.constants import *
import matplotlib.pyplot as plt
import matplotlib.dates as mdates  # 处理日期
import matplotlib as mpl  # 用于设置曲线参数
import datetime
import tk_window
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)  # 使用后端TkAggmpl.use('TkAgg')
pro = ts.pro_api('数据用的是tushare,没权限自己去注册个吧')
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 解决mplfinance绘制输出中文乱码
s = mpf.make_mpf_style(base_mpf_style='yahoo', rc={'font.family': 'SimHei'})
# 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')  # 显示居中class Show:def stock_query_function(self):  # 查询功能代码# 必须添加以下控件销毁代码,不然点击一次按钮框架长生一次,显示的画面会多一次,你可以将下面的代码删除测试看下for widget_graphic_main_frame in tk_window.centre_frame.winfo_children():widget_graphic_main_frame.destroy()# 在主框架下创建股票代码输入子框架code_frame = tk.Frame(tk_window.centre_frame, borderwidth=1, bg='#353535')code_frame.pack()# 创建标签‘股票代码’stock_label = tk.Label(code_frame, text='股票代码', bd=1)stock_label.pack(side=LEFT)# 创建股票代码输入框input_code_var = tk.StringVar()code_widget = tk.Entry(code_frame, textvariable=input_code_var, borderwidth=1, justify=CENTER)# input_code_get = input_code_var.set(input_code_var.get())  # 获取输入的新值code_widget.pack(side=LEFT, padx=4)# 在主框架下创建股票日期输入框子框架input_date_frame = tk.Frame(tk_window.centre_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=LEFT)# 创建开始日期代码输入框input_startdate_var = tk.StringVar()startdate_widget = tk.Entry(input_date_frame, textvariable=input_startdate_var, borderwidth=1, justify=CENTER)input_startdate_get = input_startdate_var.set(input_startdate_var.get())  # 获取输入的新值startdate_widget.pack(side=LEFT, padx=4)# 创建标签‘结束日期’date_end_label = tk.Label(input_date_frame, text='结束日期', bd=1)date_end_label.pack(side=LEFT)# 创建结束日期代码输入框input_enddate_var = tk.StringVar()enddate_widget = tk.Entry(input_date_frame, textvariable=input_enddate_var, borderwidth=1, justify=CENTER)input_enddate_get = input_enddate_var.set(input_enddate_var.get())  # 获取输入的新值enddate_widget.pack(side=LEFT, padx=4)# 创建Notebook标签选项卡tabControl = ttk.Notebook(tk_window.centre_frame)# 增加新选项卡日K线图stock_graphics_daily = tk.Frame(tk_window.centre_frame, borderwidth=1, bg='#353535', relief=tk.RAISED)# stock_graphics_daily.pack(expand=1, fill=tk.BOTH, anchor=tk.CENTER)stock_graphics_daily_basic = tk.Frame(tk_window.centre_frame, borderwidth=1, bg='#353535',relief=tk.RAISED)  # 增加新选项卡基本面指标stock_graphics_week = tk.Frame(tk_window.centre_frame, borderwidth=1, bg='#353535', relief=tk.RAISED)stock_graphics_month = tk.Frame(tk_window.centre_frame, borderwidth=1, bg='#353535', relief=tk.RAISED)company_information = tk.Frame(tk_window.centre_frame, borderwidth=1, bg='#353535', relief=tk.RAISED)tabControl.add(stock_graphics_daily, text='日K线图')  # 把新选项卡日K线框架增加到NotebooktabControl.add(stock_graphics_daily_basic, text='基本面指标')tabControl.add(stock_graphics_week, text='周K线图')tabControl.add(stock_graphics_month, text='月K线图')tabControl.add(company_information, text='公司信息')tabControl.pack(expand=1, fill="both")  # 设置选项卡布局tabControl.select(stock_graphics_daily)  # 默认选定日K线图开始def go():  # 图形输出渲染# 以下函数作用是省略输入代码后缀.sz .shdef 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()函数返回指定长度的字符串,原字符串右对齐,前面填充0if 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_graphics_daily框架中的控件内容,winfo_children()返回的项是一个小部件列表,# 以下代码作用是为每次点击查询按钮时更新图表内容,如果没有以下代码句,则每次点击查询会再生成一个图表for widget_daily in stock_graphics_daily.winfo_children():widget_daily.destroy()for widget_daily_basic in stock_graphics_daily_basic.winfo_children():widget_daily_basic.destroy()for widget_week in stock_graphics_week.winfo_children():widget_week.destroy()for widget_month in stock_graphics_month.winfo_children():widget_month.destroy()for widget_company_information in company_information.winfo_children():widget_company_information.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()# 获取股票数据stock_data = pro.daily(ts_code=code_name, start_date=start_date, end_date=end_date)stock_daily_basic = pro.daily_basic(ts_code=code_name, start_date=start_date, end_date=end_date,fields='close,trade_date,turnover_rate,volume_ratio,pe,pb')stock_week_data = pro.weekly(ts_code=code_name, start_date=start_date, end_date=end_date)stock_month_data = pro.monthly(ts_code=code_name, start_date=start_date, end_date=end_date)stock_name_change = pro.namechange(ts_code=code_name, fields='ts_code,name')stock_information = pro.stock_company(ts_code=code_name, fields='introduction,main_business,business_scope')# 日数据处理# :取所有行数据,后面取date列,open列等数据data = stock_data.loc[:, ['trade_date', 'open', 'close', 'high', 'low', 'vol']]data = data.rename(columns={'trade_date': 'Date', 'open': 'Open', 'close': 'Close', 'high': 'High', 'low': 'Low','vol': 'Volume'})  # 更换列名,为后面函数变量做准备data.set_index('Date', inplace=True)  # 设置date列为索引,覆盖原来索引,这个时候索引还是 object 类型,就是字符串类型。# 将object类型转化成 DateIndex 类型,pd.DatetimeIndex 是把某一列进行转换,同时把该列的数据设置为索引 index。data.index = pd.DatetimeIndex(data.index)data = data.sort_index(ascending=True)  # 将时间顺序升序,符合时间序列# 基本面指标数据处理# 设置date列为索引,覆盖原来索引,这个时候索引还是 object 类型,就是字符串类型。stock_daily_basic.set_index('trade_date', inplace=True)# 将object类型转化成 DateIndex 类型,pd.DatetimeIndex 是把某一列进行转换,同时把该列的数据设置为索引 index。stock_daily_basic.index = pd.DatetimeIndex(stock_daily_basic.index)stock_daily_basic = stock_daily_basic.sort_index(ascending=True)  # 将时间顺序升序,符合时间序列print(stock_daily_basic)# 周数据处理week_data = stock_week_data.loc[:, ['trade_date', 'open', 'close', 'high', 'low', 'vol']]week_data = week_data.rename(columns={'trade_date': 'Date', 'open': 'Open', 'close': 'Close', 'high': 'High','low': 'Low', 'vol': 'Volume'})  # 更换列名,为后面函数变量做准备# 设置date列为索引,覆盖原来索引,这个时候索引还是 object 类型,就是字符串类型。week_data.set_index('Date', inplace=True)# 将object类型转化成 DateIndex 类型,pd.DatetimeIndex 是把某一列进行转换,同时把该列的数据设置为索引 index。week_data.index = pd.DatetimeIndex(week_data.index)week_data = week_data.sort_index(ascending=True)  # 将时间顺序升序,符合时间序列# 月数据处理month_data = stock_month_data.loc[:, ['trade_date', 'open', 'close', 'high', 'low', 'vol']]month_data = month_data.rename(columns={'trade_date': 'Date', 'open': 'Open', 'close': 'Close', 'high': 'High','low': 'Low', 'vol': 'Volume'})  # 更换列名,为后面函数变量做准备# 设置date列为索引,覆盖原来索引,这个时候索引还是 object 类型,就是字符串类型。month_data.set_index('Date', inplace=True)# 将object类型转化成 DateIndex 类型,pd.DatetimeIndex 是把某一列进行转换,同时把该列的数据设置为索引 index。month_data.index = pd.DatetimeIndex(month_data.index)month_data = month_data.sort_index(ascending=True)  # 将时间顺序升序,符合时间序列# 公司信息处理stock_company_code = stock_name_change.at[0, 'ts_code']stock_company_name = stock_name_change.at[0, 'name']stock_introduction = stock_information.at[0, 'introduction']stock_main_business = stock_information.at[0, 'main_business']stock_business_scope = stock_information.at[0, 'business_scope']# K线图图形输出daily_fig, axlist = mpf.plot(data, type='candle', mav=(5, 10, 20), volume=True,show_nontrading=False, returnfig=True)# 基本面指标图形输出# 注意必须按照选项卡的排列顺序渲染图形输出,假如你把matplotlib的图形放到最后,# 则会出现图像错位现象,不信你可以把以下的代码放到month_fig后试下plt_stock_daily_basic = plt.figure(facecolor='white')plt.suptitle('Daily Basic Indicator', size=10)# 创建网格子绘图,按行切分成3份,列切分成2分,位置(0,0),横向占用2列fig_close = plt.subplot2grid((3, 2), (0, 0), colspan=2)fig_close.set_title('Close Price')plt.xticks(stock_daily_basic.index, rotation=45)  # 设置x轴时间显示方向,放在这跟放在最后显示效果不一样fig_close.plot(stock_daily_basic.index, stock_daily_basic['close'])plt.xlabel('Trade Day')plt.ylabel('Close')plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m'))  # 设置X轴主刻度显示的格式plt.gca().xaxis.set_major_locator(mdates.MonthLocator(interval=1))  # 设置X轴主刻度的间距fig_turnover_rate = plt.subplot2grid((3, 2), (1, 0))  # 创建网格子绘图,按行切分成3份,列切分成2分,位置(1,0)fig_turnover_rate.set_title('Turnover Rate')plt.xticks(stock_daily_basic.index, rotation=45)  # 设置x轴时间显示方向,放在这跟放在最后显示效果不一样fig_turnover_rate.bar(stock_daily_basic.index, stock_daily_basic['turnover_rate'], facecolor='red')plt.xlabel('Trade Day')plt.ylabel('Turnover Rate')plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m'))  # 设置X轴主刻度显示的格式plt.gca().xaxis.set_major_locator(mdates.MonthLocator(interval=2))  # 设置X轴主刻度的间距fig_volume_ratio = plt.subplot2grid((3, 2), (2, 0))  # 创建网格子绘图,按行切分成3份,列切分成2分,位置(1,2)fig_volume_ratio.set_title('Volume Ratio')plt.xticks(stock_daily_basic.index, rotation=45)  # 设置x轴时间显示方向,放在这跟放在最后显示效果不一样fig_volume_ratio.bar(stock_daily_basic.index, stock_daily_basic['volume_ratio'])plt.xlabel('Trade Day')plt.ylabel('Volume Ratio')plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%m'))  # 设置X轴主刻度显示的格式plt.gca().xaxis.set_major_locator(mdates.MonthLocator(interval=2))  # 设置X轴主刻度的间距fig_pe = plt.subplot2grid((3, 2), (1, 1))  # 创建网格子绘图,按行切分成3份,列切分成2分,位置在第3行,第1列fig_pe.set_title('PE')plt.xticks(stock_daily_basic.index, rotation=45)  # 设置x轴时间显示方向,放在这跟放在最后显示效果不一样fig_pe.plot(stock_daily_basic.index, stock_daily_basic['pe'])plt.xlabel('Trade Day')plt.ylabel('PE')plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%m'))  # 设置X轴主刻度显示的格式plt.gca().xaxis.set_major_locator(mdates.MonthLocator(interval=2))  # 设置X轴主刻度的间距fig_pb = plt.subplot2grid((3, 2), (2, 1))  # 创建网格子绘图,按行切分成3份,列切分成2分,位置在第3行,第2列fig_pb.set_title('PB')plt.xticks(stock_daily_basic.index, rotation=45)  # 设置x轴时间显示方向,放在这跟放在最后显示效果不一样fig_pb.plot(stock_daily_basic.index, stock_daily_basic['pb'])plt.xlabel('Trade Day')plt.ylabel('PB')plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%m'))  # 设置X轴主刻度显示的格式plt.gca().xaxis.set_major_locator(mdates.MonthLocator(interval=2))  # 设置X轴主刻度的间距plt_stock_daily_basic.tight_layout(h_pad=-2, w_pad=0)  # 解决子图图形重叠问题# 周K线图形输出week_fig, axlist = mpf.plot(week_data, type='candle', mav=(5, 10, 20), volume=True,show_nontrading=False, returnfig=True)# 月k线图形输出month_fig, axlist = mpf.plot(month_data, type='candle', mav=(5, 10, 20), volume=True,show_nontrading=False, returnfig=True)# 将获得的图形渲染到画布上# 日K线图形渲染到tkinter画布上canvas_daily = FigureCanvasTkAgg(daily_fig, master=stock_graphics_daily)  # 设置tkinter绘制区canvas_daily.draw()toolbar_daily = NavigationToolbar2Tk(canvas_daily, stock_graphics_daily)toolbar_daily.update()  # 显示图形导航工具条canvas_daily._tkcanvas.pack(side=BOTTOM, fill=BOTH, expand=1)# 基本面指标图形渲染到tkinter画布上canvas_stock_daily_basic = FigureCanvasTkAgg(plt_stock_daily_basic, master=stock_graphics_daily_basic)canvas_stock_daily_basic.draw()toolbar_stock_daily_basic = NavigationToolbar2Tk(canvas_stock_daily_basic, stock_graphics_daily_basic)toolbar_stock_daily_basic.update()  # 显示图形导航工具条canvas_stock_daily_basic._tkcanvas.pack(side=BOTTOM, fill=BOTH, expand=1)plt.close()# 周K线图形渲染到tkinter画布上canvas_week = FigureCanvasTkAgg(week_fig, master=stock_graphics_week)  # 设置tkinter绘制区canvas_week.draw()toolbar_week = NavigationToolbar2Tk(canvas_week, stock_graphics_week)toolbar_week.update()  # 显示图形导航工具条canvas_week._tkcanvas.pack(side=BOTTOM, fill=BOTH, expand=1)# 月K线图形渲染到tkinter画布上canvas_month = FigureCanvasTkAgg(month_fig, master=stock_graphics_month)  # 设置tkinter绘制区canvas_month.draw()toolbar_month = NavigationToolbar2Tk(canvas_month, stock_graphics_month)toolbar_month.update()  # 显示图形导航工具条canvas_month._tkcanvas.pack(side=BOTTOM, fill=BOTH, expand=1)# 在company_information框架下设置文字选项卡功能内容company_text = tk.Text(company_information, bg='white', undo=True, wrap=tix.CHAR)# 在文本框第一行添加股票代码,文字红色,居中显示company_text.insert(tk.INSERT, stock_company_code)company_text.tag_add('tag1', '1.0', '1.9')  # 设置选定的内容,company_text.tag_config('tag1', foreground='red', justify=CENTER)company_text.insert(tk.INSERT, '\n')company_text.insert(tk.INSERT, stock_company_name)company_text.tag_add('tag2', '2.0', '2.9')company_text.tag_config('tag2', foreground='red', justify=CENTER)company_text.insert(tk.INSERT, '\n')company_text.insert(tk.INSERT, '    ')company_text.insert(tk.INSERT, '公司简介:')company_text.tag_add('tag3', '3.3', '3.9')company_text.tag_config('tag3', foreground='red', font=tf.Font(family='SimHei', size=12))company_text.insert(tk.INSERT, stock_introduction)company_text.tag_add('tag4', '3.9', 'end')company_text.tag_config('tag4', foreground='black', spacing1=20, spacing2=10,font=tf.Font(family='SimHei', size=12))company_text.insert(tk.INSERT, '\n')company_text.insert(tk.INSERT, '    ')company_text.insert(tk.INSERT, '主要业务及产品:')company_text.tag_add('tag5', '4.4', '4.12')company_text.tag_config('tag5', foreground='blue')company_text.insert(tk.INSERT, stock_main_business)company_text.tag_add('tag6', '4.12', 'end')company_text.tag_config('tag6', spacing1=20, spacing2=10,font=tf.Font(family='SimHei', size=12))company_text.insert(tk.INSERT, '\n')company_text.insert(tk.INSERT, '    ')company_text.insert(tk.INSERT, '经营范围:')company_text.tag_add('tag7', '5.4', '5.9')company_text.tag_config('tag7', foreground='#cc6600')company_text.insert(tk.INSERT, stock_business_scope)company_text.tag_add('tag8', '5.9', 'end')company_text.tag_config('tag8', spacing1=20, spacing2=10,font=tf.Font(family='SimHei', size=12))company_text.insert(tk.INSERT, '\n')company_text.pack(fill=BOTH, expand=1)# 在主框架下创建查询按钮子框架search_frame = tk.Frame(tk_window.centre_frame, borderwidth=1, bg='#353535', relief=tix.SUNKEN)search_frame.pack(before=tabControl)  # 必须加上before,否则控件则会出现在底部,除非tabControl设置了bottom布局属性# 创建查询按钮并设置功能stock_find = tk.Button(search_frame, text='查询', width=5, height=1, command=go)stock_find.pack()def show_stocklist_function(self):  # 股票功能代码for widget_graphic_main_frame in tk_window.centre_frame.winfo_children():widget_graphic_main_frame.destroy()# 首先获取今天时间now_time = datetime.datetime.now()# 转化成tushare的时间格式strf_time = now_time.strftime('%Y%m%d')# 获取上交所上一个交易日日期,PS:tushare指数的数据信息好像当天只能获取上一个交易日的数据pre_trade_date = pro.trade_cal(exchange='SSE', is_open='1', start_date=strf_time, fields='pretrade_date')pre_trade_d = pre_trade_date.at[0, 'pretrade_date']# print(pre_trade_d)df_basic = pro.stock_basic(exchange='', list_status='L')  # 再获取所有股票的基本信息df_daily = pro.daily(trade_date=pre_trade_d)  # 先获得所有股票的行情数据,成交额单位是千元,成交量是手df_daily_basic = pro.daily_basic(ts_code='', trade_date=pre_trade_d,fields='ts_code, turnover_rate, turnover_rate_f,'' volume_ratio, pe, pe_ttm, pb, ps, ps_ttm,'' total_share, float_share,'' free_share, total_mv, circ_mv ')  # 获取每日指标,单位是万股,万元df_first = pd.merge(left=df_basic, right=df_daily, on='ts_code',how='outer')  # on='ts_code'以ts_code为索引,合并数据,how='outer',取并集df_all = pd.merge(left=df_first, right=df_daily_basic, on='ts_code', how='outer')# 数据清洗,删除symbol列数据,跟ts_code数据重复df_all = df_all.drop('symbol', axis=1)df_all = df_all[df_all['trade_date'].notna()]  # 删除当天不交易的股票df_all = df_all[df_all['list_date'].notna()]  # 删除退市或将要退市的股票for w in ['name', 'area', 'industry', 'market']:  # 在'name', 'area', 'industry', 'market'列内循环填充NaN值df_all[w].fillna('退市股', inplace=True)# print(type(df_all.at[1, 'pb']))# df_all['amount'] = df_all['amount'] / 100000  # 千转亿# df_all['circ_mv'] = df_all['circ_mv'] / 10000  # 万转亿# df_all['total_mv'] = df_all['total_mv'] / 10000  # 万转亿# 对获取的股票数据列名称进行重命名以方便阅读df_all = df_all.rename(columns={'ts_code': '股票代码', 'name': '股票名称', 'area': '所在地域', 'industry': '行业','market': '市场类型', 'list_date': '上市日期', 'trade_date': '交易日期','open': '开盘价', 'high': '最高价', 'low': '最低价', 'close': '收盘价','pre_close': '昨日价', 'change': '涨跌额', 'pct_chg': '涨跌幅','vol': '成交量(手)', 'amount': '成交额(千元)', 'turnover_rate': '换手率(%)','turnover_rate_f': '流通换手率', 'volume_ratio': '量比', 'pe': '市盈率','pe_ttm': '滚动市盈率', 'pb': '市净率', 'ps': '市销率', 'ps_ttm': '滚动市销率','total_share': '总股本(万股)', 'float_share': '流通股本 (万股)','free_share': '自由流通股本(万股)', 'total_mv': '总市值 (万元)','circ_mv': '流通市值(万元)'})print(df_all)# 亏损的为空值# 先设置表的列名有哪些area = ('股票代码', '股票名称', '所在地域', '行业', '市场类型', '上市日期', '交易日期', '开盘价', '最高价', '最低价','收盘价', '昨日价', '涨跌额', '涨跌幅', '成交量(手)', '成交额(千元)', '换手率(%)', '流通换手率', '量比','市盈率', '滚动市盈率', '市净率', '市销率', '滚动市销率', '总股本(万股)','流通股本 (万股)', '自由流通股本(万股)', '总市值 (万元)', '流通市值(万元)')stock_treeview = ttk.Treeview(tk_window.centre_frame, columns=area, show='headings')# 在treeview布局钱先布局横竖滚动条,不然会出现布局问题,你可以试着将滚动条代码放在最后试下VScroll1 = ttk.Scrollbar(tk_window.centre_frame, orient='vertical', command=stock_treeview.yview)stock_treeview.configure(yscrollcommand=VScroll1.set)VScroll1.pack(side=RIGHT, fill=Y)HScroll1 = ttk.Scrollbar(tk_window.centre_frame, orient='horizontal', command=stock_treeview.xview)stock_treeview.configure(xscrollcommand=HScroll1.set)HScroll1.pack(side=BOTTOM, fill=X)for i in range(len(area)):  # 命名列表名stock_treeview.column(area[i], width=70, anchor='center')stock_treeview.heading(area[i], text=area[i])stock_treeview.pack(fill=BOTH, expand=1)j = 0for i in range(len(df_all.index)):  # 导入插入股票数据# 插入的值数组格式用.tolist()转化成list格式,否则显示会多出‘跟[这种字符串stock_treeview.insert('', 'end', values=df_all.values[i].tolist())# print(df_all.values[i].tolist())j += 1print('已经插入到第%s个股票了,请耐心等待' % j)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 area:stock_treeview.column(col, width=70, anchor='center')stock_treeview.heading(col, text=col,command=lambda col=col: stock_treeview_sort(stock_treeview, col, False))# 创建选中股票显示K线图形功能函数def stock_k_show():for item in stock_treeview.selection():item_text = stock_treeview.item(item, "values")print(item_text[0])  # 输出所选行的第一列的值stock_kline_window = tk.Toplevel()stock_kline_window.geometry('{}x{}'.format(800, 600))code_name = item_text[0]start_date = '20150101'end_date = pre_trade_dstock_data = pro.daily(ts_code=code_name, start_date=start_date, end_date=end_date)print(stock_data)# :取所有行数据,后面取date列,open列等数据data = stock_data.loc[:, ['trade_date', 'open', 'close', 'high', 'low', 'vol']]data = data.rename(columns={'trade_date': 'Date', 'open': 'Open', 'close': 'Close', 'high': 'High','low': 'Low', 'vol': 'Volume'})  # 更换列名,为后面函数变量做准备data.set_index('Date', inplace=True)  # 设置date列为索引,覆盖原来索引,这个时候索引还是 object 类型,就是字符串类型# 将object类型转化成 DateIndex 类型,pd.DatetimeIndex 是把某一列进行转换,同时把该列的数据设置为索引 index。data.index = pd.DatetimeIndex(data.index)data = data.sort_index(ascending=True)  # 将时间顺序升序,符合时间序列fig, axlist = mpf.plot(data, title=item_text[1], type='candle', mav=(5, 10, 20), volume=True,show_nontrading=False, returnfig=True, style=s)canvas = FigureCanvasTkAgg(fig, master=stock_kline_window)  # 设置tkinter绘制区canvas.draw()toolbar = NavigationToolbar2Tk(canvas, stock_kline_window)toolbar.update()  # 显示图形导航工具条canvas._tkcanvas.pack(side=BOTTOM, fill=BOTH, expand=1)# 创建弹出菜单,为后面功能开发做准备stocklist_menu = tk.Menu(tk_window.centre_frame, tearoff=False)  # tearoff=True显示分割线def pop(event):stocklist_menu.post(event.x_root, event.y_root)stock_treeview.bind('<Button-3>', pop)stocklist_menu.add_command(label='K线图', command=stock_k_show)

stock_backtrader.py

# coding=utf-8
from __future__ import (absolute_import, division, print_function,unicode_literals)
import datetime
import time
import pandas as pd
import backtrader as bt
import tushare as ts
import tk_window
import tkinter as tk
import tkinter.messagebox
from tkinter import ttk
import matplotlib.pyplot as plt
import mplfinance as mpf
import os
import threading
import inspect
import ctypes
import function
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')  # 显示居中
# 保存token到本地,不进行本地保存可能出现ts.pro_bar()通用接口无法使用
ts.set_token('数据用的是tushare,没权限自己去注册个吧')
#  初始化pro接口
pro = ts.pro_api()# 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]则是获取绝对路径# 设置treeview背景# strategystyle = ttk.Style()# strategystyle.configure('TFrame', background='#353535', foreground='white')# 写入父节名称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)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', encoding='UTF-8', errors='ignore')txt_data = txt_file.read()txt_file.close()return txt_datadef 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)  # 设置右键弹出菜单# ******************************************************************************************************************# 设置多股回测股票筛选条件功能,所有的筛选指标的值是昨日的数据值,当然也可以指定某一日的数据,此功能目前还不是必须,Mark一下# PE 市盈率(总市值/净利润, 亏损的PE为空)backtrader_pe_frame = tk.Frame(backtrader_left_frame, bg='#353535', bd=5, borderwidth=4)backtrader_pe_frame.pack()input_pe_leftvar = tk.StringVar()pe_leftwidget = tk.Entry(backtrader_pe_frame, textvariable=input_pe_leftvar, borderwidth=1, justify=tk.CENTER,width=6)pe_leftwidget.pack(side=tk.LEFT, padx=4)multi_pe_label = tk.Label(backtrader_pe_frame, text='< PE <', height=1, bg='#353535', fg='white')multi_pe_label.pack(side=tk.LEFT)input_pe_rightvar = tk.StringVar()pe_rightwidget = tk.Entry(backtrader_pe_frame, textvariable=input_pe_rightvar, borderwidth=1, justify=tk.CENTER,width=6)pe_rightwidget.pack(side=tk.LEFT, padx=4)# PB 市净率(总市值/净资产)backtrader_pb_frame = tk.Frame(backtrader_left_frame, bg='#353535', bd=5, borderwidth=4)backtrader_pb_frame.pack()input_pb_leftvar = tk.StringVar()pb_leftwidget = tk.Entry(backtrader_pb_frame, textvariable=input_pb_leftvar, borderwidth=1, justify=tk.CENTER,width=6)pb_leftwidget.pack(side=tk.LEFT, padx=4)multi_pb_label = tk.Label(backtrader_pb_frame, text='< PB <', height=1, bg='#353535', fg='white')multi_pb_label.pack(side=tk.LEFT)input_pb_rightvar = tk.StringVar()pb_rightwidget = tk.Entry(backtrader_pb_frame, textvariable=input_pb_rightvar, borderwidth=1, justify=tk.CENTER,width=6)pb_rightwidget.pack(side=tk.LEFT, padx=4)# PS 市销率backtrader_ps_frame = tk.Frame(backtrader_left_frame, bg='#353535', bd=5, borderwidth=4)backtrader_ps_frame.pack()input_ps_leftvar = tk.StringVar()ps_leftwidget = tk.Entry(backtrader_ps_frame, textvariable=input_ps_leftvar, borderwidth=1, justify=tk.CENTER,width=6)ps_leftwidget.pack(side=tk.LEFT, padx=4)multi_ps_label = tk.Label(backtrader_ps_frame, text='< PS <', height=1, bg='#353535', fg='white')multi_ps_label.pack(side=tk.LEFT)input_ps_rightvar = tk.StringVar()ps_rightwidget = tk.Entry(backtrader_ps_frame, textvariable=input_ps_rightvar, borderwidth=1, justify=tk.CENTER,width=6)ps_rightwidget.pack(side=tk.LEFT, padx=4)# volume_ratio 量比backtrader_volume_ratio_frame = tk.Frame(backtrader_left_frame, bg='#353535', bd=5, borderwidth=4)backtrader_volume_ratio_frame.pack()input_volume_ratio_leftvar = tk.StringVar()volume_ratio_leftwidget = tk.Entry(backtrader_volume_ratio_frame, textvariable=input_volume_ratio_leftvar,borderwidth=1, justify=tk.CENTER, width=6)volume_ratio_leftwidget.pack(side=tk.LEFT, padx=4)multi_volume_ratio_label = tk.Label(backtrader_volume_ratio_frame, text='< 量比 <', height=1, bg='#353535',fg='white')multi_volume_ratio_label.pack(side=tk.LEFT)input_volume_ratio_rightvar = tk.StringVar()volume_ratio_rightwidget = tk.Entry(backtrader_volume_ratio_frame, textvariable=input_volume_ratio_rightvar,borderwidth=1, justify=tk.CENTER, width=6)volume_ratio_rightwidget.pack(side=tk.LEFT, padx=4)# turnover_rate 换手率(%)backtrader_turnover_rate_frame = tk.Frame(backtrader_left_frame, bg='#353535', bd=5, borderwidth=4)backtrader_turnover_rate_frame.pack()input_turnover_rate_leftvar = tk.StringVar()turnover_rate_leftwidget = tk.Entry(backtrader_turnover_rate_frame, textvariable=input_turnover_rate_leftvar,borderwidth=1, justify=tk.CENTER, width=6)turnover_rate_leftwidget.pack(side=tk.LEFT, padx=4)multi_turnover_rate_label = tk.Label(backtrader_turnover_rate_frame, text='< 换手率 <', height=1, bg='#353535',fg='white')multi_turnover_rate_label.pack(side=tk.LEFT)input_turnover_rate_rightvar = tk.StringVar()turnover_rate_rightwidget = tk.Entry(backtrader_turnover_rate_frame, textvariable=input_turnover_rate_rightvar,borderwidth=1, justify=tk.CENTER, width=6)turnover_rate_rightwidget.pack(side=tk.LEFT, padx=4)backtrader_window.add(backtrader_left_frame, width=tk_window.screenHeight / 5.2)# total_share 总股本(万股)backtrader_total_share_frame = tk.Frame(backtrader_left_frame, bg='#353535', bd=5, borderwidth=4)backtrader_total_share_frame.pack()input_total_share_leftvar = tk.StringVar()total_share_leftwidget = tk.Entry(backtrader_total_share_frame, textvariable=input_total_share_leftvar,borderwidth=1, justify=tk.CENTER, width=6)total_share_leftwidget.pack(side=tk.LEFT, padx=4)multi_total_share_label = tk.Label(backtrader_total_share_frame, text='< 总股本(亿股) <', height=1, bg='#353535',fg='white')multi_total_share_label.pack(side=tk.LEFT)input_total_share_rightvar = tk.StringVar()total_share_rightwidget = tk.Entry(backtrader_total_share_frame, textvariable=input_total_share_rightvar,borderwidth=1, justify=tk.CENTER, width=6)total_share_rightwidget.pack(side=tk.LEFT, padx=4)# total_mv 总市值(万元)backtrader_total_mv_frame = tk.Frame(backtrader_left_frame, bg='#353535', bd=5, borderwidth=4)backtrader_total_mv_frame.pack()input_total_mv_leftvar = tk.StringVar()total_mv_leftwidget = tk.Entry(backtrader_total_mv_frame, textvariable=input_total_mv_leftvar,borderwidth=1, justify=tk.CENTER, width=6)total_mv_leftwidget.pack(side=tk.LEFT, padx=4)multi_total_mv_label = tk.Label(backtrader_total_mv_frame, text='< 总市值(亿元) <', height=1, bg='#353535',fg='white')multi_total_mv_label.pack(side=tk.LEFT)input_total_mv_rightvar = tk.StringVar()total_mv_rightwidget = tk.Entry(backtrader_total_mv_frame, textvariable=input_total_mv_rightvar,borderwidth=1, justify=tk.CENTER, width=6)total_mv_rightwidget.pack(side=tk.LEFT, padx=4)# float_share 流通股本(万股)backtrader_float_share_frame = tk.Frame(backtrader_left_frame, bg='#353535', bd=5, borderwidth=4)backtrader_float_share_frame.pack()input_float_share_leftvar = tk.StringVar()float_share_leftwidget = tk.Entry(backtrader_float_share_frame, textvariable=input_float_share_leftvar,borderwidth=1, justify=tk.CENTER, width=6)float_share_leftwidget.pack(side=tk.LEFT, padx=4)multi_float_share_label = tk.Label(backtrader_float_share_frame, text='< 流通股本(亿股) <', height=1, bg='#353535',fg='white')multi_float_share_label.pack(side=tk.LEFT)input_float_share_rightvar = tk.StringVar()float_share_rightwidget = tk.Entry(backtrader_float_share_frame, textvariable=input_float_share_rightvar,borderwidth=1, justify=tk.CENTER, width=6)float_share_rightwidget.pack(side=tk.LEFT, padx=4)# circ_mv 流通市值(万元)backtrader_circ_mv_frame = tk.Frame(backtrader_left_frame, bg='#353535', bd=5, borderwidth=4)backtrader_circ_mv_frame.pack()input_circ_mv_leftvar = tk.StringVar()circ_mv_leftwidget = tk.Entry(backtrader_circ_mv_frame, textvariable=input_circ_mv_leftvar,borderwidth=1, justify=tk.CENTER, width=6)circ_mv_leftwidget.pack(side=tk.LEFT, padx=4)multi_circ_mv_label = tk.Label(backtrader_circ_mv_frame, text='< 流通市值(亿元) <', height=1, bg='#353535',fg='white')multi_circ_mv_label.pack(side=tk.LEFT)input_circ_mv_rightvar = tk.StringVar()circ_mv_rightwidget = tk.Entry(backtrader_circ_mv_frame, textvariable=input_circ_mv_rightvar,borderwidth=1, justify=tk.CENTER, width=6)circ_mv_rightwidget.pack(side=tk.LEFT, padx=4)# free_share 自由流通股本(万股)backtrader_free_share_frame = tk.Frame(backtrader_left_frame, bg='#353535', bd=5, borderwidth=4)backtrader_free_share_frame.pack()input_free_share_leftvar = tk.StringVar()free_share_leftwidget = tk.Entry(backtrader_free_share_frame, textvariable=input_free_share_leftvar,borderwidth=1, justify=tk.CENTER, width=6)free_share_leftwidget.pack(side=tk.LEFT, padx=4)multi_free_share_label = tk.Label(backtrader_free_share_frame, text='< 自由流通股本(亿) <', height=1, bg='#353535',fg='white')multi_free_share_label.pack(side=tk.LEFT)input_free_share_rightvar = tk.StringVar()free_share_rightwidget = tk.Entry(backtrader_free_share_frame, textvariable=input_free_share_rightvar,borderwidth=1, justify=tk.CENTER, width=6)free_share_rightwidget.pack(side=tk.LEFT, padx=4)backtrader_window.add(backtrader_left_frame, width=tk_window.screenHeight / 4)# ******************************************************************************************************************# 创建右边图形输出框架,主要放回测分析显示跟用户输入的股票代码跟日期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, bg='#353535', fg='red')stock_label.pack(side=tk.LEFT)# 创建股票代码输入框input_code_frame = tk.Frame(backtrader_top_left_frame, borderwidth=1, bg='#353535')input_code_frame.pack()input_code_var = tk.StringVar()code_widget = tk.Entry(input_code_frame, textvariable=input_code_var, borderwidth=1, justify=tk.CENTER)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, bg='#353535', fg='red')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)startdate_widget.pack(side=tk.LEFT, padx=4)# 创建标签‘结束日期’date_end_label = tk.Label(input_date_frame, text='结束日期', bd=1, bg='#353535', fg='red')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)enddate_widget.pack(side=tk.LEFT, padx=4)# 先把部件布局好了再backtrader_top_frame用.add()添加到backtrader_plot_windowbacktrader_plot_window_top.add(backtrader_top_left_frame, height=tk_window.screenHeight / 8,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_var = tk.IntVar()# 设置默认选项为1,默认全市股票回测multi_var.set(1)# 在主框架右边创建股票代码输入子框架multi_code_frame = tk.Frame(backtrader_top_right_frame, borderwidth=1, bg='#353535')multi_code_frame.pack()# 创建全市回测选择项all_stock_radiobutton = tk.Radiobutton(multi_code_frame, text='全市股票回测', variable=multi_var, value=1,bg='#353535', fg='red')all_stock_radiobutton.pack(side=tk.LEFT)# 创建标签‘股票代码’multi_stock_radiobutton = tk.Radiobutton(multi_code_frame, text='多股回测股票代码', variable=multi_var, value=2,bg='#353535', fg='red')multi_stock_radiobutton.pack(side=tk.LEFT)# 创建股票代码输入框input_multi_code_frame = tk.Frame(backtrader_top_right_frame, borderwidth=1, bg='#353535')input_multi_code_frame.pack()input_multi_code_var = tk.StringVar()multi_code_widget = tk.Entry(input_multi_code_frame, textvariable=input_multi_code_var, borderwidth=1,justify=tk.CENTER)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, bg='#353535', fg='red')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)multi_startdate_widget.pack(side=tk.LEFT, padx=4)# 创建标签‘结束日期’multi_date_end_label = tk.Label(multi_input_date_frame, text='结束日期', bd=1, bg='#353535', fg='red')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_enddate_widget.pack(side=tk.LEFT, padx=4)# 先把部件布局好了再backtrader_top_frame用.add()添加到backtrader_plot_windowbacktrader_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 .shdef 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()函数返回指定长度的字符串,原字符串右对齐,前面填充0if 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:00back_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图形的数据# :取所有行数据,后面取date列,open列等数据data_mpf = df.loc[:, ['trade_date', 'open', 'close', 'high', 'low', 'vol']]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_strategycerebro.addstrategy(my_strategy)# 添加数据cerebro.adddata(data_back)# 设置资金startcash = 100000cerebro.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' % startcashendcash_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' % Nonetry:float_profit = '浮动盈亏:%.2f' % \(cerebro.broker.getvalue() - startcash -backtrader_analysis.analyzers.TradeAnalyzer.get_analysis()['pnl']['net']['total'])except Exception as e1:float_profit = '浮动盈亏:%s' % Nonetry: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' % Nonestart_backtrade_date = '回测开始时间:%s' % d1end_backtrade_date = '回测结束时间:%s' % d2try:sharpeRatio_value = '夏普比例:%.2f' % \backtrader_analysis.analyzers.SharpeRatio.get_analysis()['sharperatio']except Exception as e3:sharpeRatio_value = '夏普比例:%s' % Nonetry:drawdown_value = '最大回撤:%.2f' % backtrader_analysis.analyzers.DW.get_analysis()['max']['drawdown']except Exception as e4:drawdown_value = '最大回撤:%s' % Nonetry:moneydown_value = '最大资金回撤:%.2f' % \backtrader_analysis.analyzers.DW.get_analysis()['max']['moneydown']except Exception as e5:moneydown_value = '最大资金回撤:%s' % Nonetry:max_drawdown_lastday = '最大回撤持续天数:%d' % \backtrader_analysis.analyzers.DW.get_analysis()['max']['len']except Exception as e6:max_drawdown_lastday = '最大回撤持续天数:%s' % Nonetry:total_value = '交易次数:%d' % \backtrader_analysis.analyzers.TradeAnalyzer.get_analysis()['total']['total']except Exception as e7:total_value = '交易次数:%s' % Nonetry:uncompleted_trade = '未完成交易数量:%d' % \backtrader_analysis.analyzers.TradeAnalyzer.get_analysis()['total']['open']except Exception as e8:uncompleted_trade = '未完成交易数量:%s' % Nonetry:completed_trade = '已完成交易数量:%d' % \backtrader_analysis.analyzers.TradeAnalyzer.get_analysis()['total']['closed']except Exception as e9:completed_trade = '已完成交易数量:%s' % Nonetry:win_value = '盈利次数:%d' % backtrader_analysis.analyzers.TradeAnalyzer.get_analysis()['won']['total']except Exception as e10:win_value = '盈利次数:%s' % Nonetry:lost_value = '亏损次数:%d' % backtrader_analysis.analyzers.TradeAnalyzer.get_analysis()['lost']['total']except Exception as e11:lost_value = '亏损次数:%s' % Nonetry: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' % Nonetry: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' % Noneanalysis_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'])  # 创建买点dataframetrade_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_listtrade_signal_buy['buy_price'] = history_trade_buy_price_listtrade_signal_buy['buy_vol'] = history_trade_buy_vol_listtrade_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_listtrade_signal_sell['sell_price'] = history_trade_sell_price_listtrade_signal_sell['sell_vol'] = history_trade_sell_vol_listtrade_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 605388add_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 .shdef 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()函数返回指定长度的字符串,原字符串右对齐,前面填充0if 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())# noinspection PyBroadExceptiontry:# 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:00back_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_strategycerebro_single.addstrategy(my_strategy)# 添加数据cerebro_single.adddata(data_back)# 设置资金startcash_single = 100000cerebro_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: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()# 底部回测刷新状态栏专用for widget_state_label in tk_window.bottom_frame.winfo_children():widget_state_label.destroy()function.time_clock()# ******************************************************************************************************************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], width=8, anchor='center')multi_stock_treeview.heading(multi_area[i], text=multi_area[i])multi_stock_treeview.pack(fill=tk.BOTH, expand=1)# ******************************************************************************************************************# 将获取的txt文字转换成pycharm可执行代码class my_strategy(bt.Strategy):exec(use_strategy())# 先设置一个用来接收回测股票代码的列表multi_stock_list = []# 以下函数作用是省略输入代码后缀.sz .shdef 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()函数返回指定长度的字符串,原字符串右对齐,前面填充0if 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')  # 获取所有上市公司的信息为全部上市公司回测做准备if multi_var.get() == 1:  # 如果输入的股票代码为空值# 全市场回测股票筛选功能代码,不适用于自己输入的多个股票筛选# 首先获取今天时间now_time = datetime.datetime.now()# 转化成tushare的时间格式strf_time = now_time.strftime('%Y%m%d')# 获取上交所上一个交易日日期,PS:tushare指数的数据信息好像当天只能获取上一个交易日的数据pre_trade_date = pro.trade_cal(exchange='SSE', is_open='1', start_date=strf_time, fields='pretrade_date')pre_trade_d = pre_trade_date.at[0, 'pretrade_date']# 获取每日指标数据,单位是万股,万元df_screen = pro.daily_basic(ts_code='', trade_date=pre_trade_d,fields='ts_code, turnover_rate, volume_ratio, pe, pb, ps, total_share, ''float_share, free_share, total_mv, circ_mv ')# noinspection PyBroadExceptiontry:  # PEif input_pe_leftvar.get() and input_pe_rightvar.get():df_screen = df_screen[(df_screen['pe'] > float(input_pe_leftvar.get())) &(df_screen['pe'] < float(input_pe_rightvar.get()))]elif input_pe_leftvar.get() and not input_pe_rightvar.get():df_screen = df_screen[df_screen['pe'] > float(input_pe_leftvar.get())]elif input_pe_rightvar.get() and not input_pe_leftvar.get():# 这里将PE的空值设置为0是因为tushare将PE为负的数值设置成NaN,只有设置成0,我们才好对输入的小于数值进行筛选df_screen['pe'].fillna(0, inplace=True)df_screen = df_screen[df_screen['pe'] < float(input_pe_rightvar.get())]elif not input_pe_rightvar.get() and not input_pe_leftvar.get():df_screen = df_screenexcept Exception as pe_error:tk.messagebox.showwarning(title='pe_error', message='PE数据输入错误,该筛选功能不运行')try:  # PBif input_pb_leftvar.get() and input_pb_rightvar.get():df_screen = df_screen[(df_screen['pb'] > float(input_pb_leftvar.get())) &(df_screen['pb'] < float(input_pb_rightvar.get()))]elif input_pb_leftvar.get() and not input_pb_rightvar.get():df_screen = df_screen[df_screen['pb'] > float(input_pb_leftvar.get())]elif input_pb_rightvar.get() and not input_pb_leftvar.get():# 这里将PE的空值设置为0是因为tushare将PE为负的数值设置成NaN,只有设置成0,我们才好对输入的小于数值进行筛选df_screen['pb'].fillna(0, inplace=True)df_screen = df_screen[df_screen['pb'] < float(input_pb_rightvar.get())]elif not input_pb_rightvar.get() and not input_pb_leftvar.get():df_screen = df_screenexcept Exception as pb_error:tk.messagebox.showwarning(title='pb_error', message='PB数据输入错误,该筛选功能不运行')try:  # PSif input_ps_leftvar.get() and input_ps_rightvar.get():df_screen = df_screen[(df_screen['ps'] > float(input_ps_leftvar.get())) &(df_screen['ps'] < float(input_ps_rightvar.get()))]elif input_ps_leftvar.get() and not input_ps_rightvar.get():df_screen = df_screen[df_screen['ps'] > float(input_ps_leftvar.get())]elif input_ps_rightvar.get() and not input_ps_leftvar.get():# 这里将PE的空值设置为0是因为tushare将PE为负的数值设置成NaN,只有设置成0,我们才好对输入的小于数值进行筛选df_screen['ps'].fillna(0, inplace=True)df_screen = df_screen[df_screen['ps'] < float(input_ps_rightvar.get())]elif not input_ps_rightvar.get() and not input_ps_leftvar.get():df_screen = df_screenexcept Exception as ps_error:tk.messagebox.showwarning(title='ps_error', message='PS数据输入错误,该筛选功能不运行')try:  # 量比if input_volume_ratio_leftvar.get() and input_volume_ratio_rightvar.get():df_screen = df_screen[(df_screen['volume_ratio'] > float(input_volume_ratio_leftvar.get())) &(df_screen['volume_ratio'] < float(input_volume_ratio_rightvar.get()))]elif input_volume_ratio_leftvar.get() and not input_volume_ratio_rightvar.get():df_screen = df_screen[df_screen['volume_ratio'] > float(input_volume_ratio_leftvar.get())]elif input_volume_ratio_rightvar.get() and not input_volume_ratio_leftvar.get():df_screen = df_screen[df_screen['volume_ratio'] < float(input_volume_ratio_rightvar.get())]elif not input_volume_ratio_rightvar.get() and not input_volume_ratio_leftvar.get():df_screen = df_screenexcept Exception as volume_ratio_error:tk.messagebox.showwarning(title='volume_ratio_error', message='量比数据输入错误,该筛选功能不运行')try:  # 换手率%if input_turnover_rate_leftvar.get() and input_turnover_rate_rightvar.get():df_screen = df_screen[(df_screen['turnover_rate'] > float(input_turnover_rate_leftvar.get())) &(df_screen['turnover_rate'] < float(input_turnover_rate_rightvar.get()))]elif input_turnover_rate_leftvar.get() and not input_turnover_rate_rightvar.get():df_screen = df_screen[df_screen['turnover_rate'] > float(input_turnover_rate_leftvar.get())]elif input_turnover_rate_rightvar.get() and not input_turnover_rate_leftvar.get():df_screen = df_screen[df_screen['volume_ratio'] < float(input_volume_ratio_rightvar.get())]elif not input_turnover_rate_rightvar.get() and not input_turnover_rate_leftvar.get():df_screen = df_screenexcept Exception as turnove_error:tk.messagebox.showwarning(title='ERROR', message='换手率数据输入错误,该筛选功能不运行')try:  # 总股本(亿股)if input_total_share_leftvar.get() and input_total_share_rightvar.get():df_screen = df_screen[(round(df_screen['total_share']/10000, 2) > float(input_total_share_leftvar.get())) &(round(df_screen['total_share']/10000, 2) < float(input_total_share_rightvar.get()))]elif input_total_share_leftvar.get() and not input_total_share_rightvar.get():df_screen = df_screen[(round(df_screen['total_share']/10000, 2) > float(input_total_share_leftvar.get()))]elif input_total_share_rightvar.get() and not input_total_share_leftvar.get():df_screen = df_screen[(round(df_screen['total_share']/10000, 2) < float(input_total_share_rightvar.get()))]elif not input_total_share_rightvar.get() and not input_total_share_leftvar.get():df_screen = df_screenexcept Exception as total_share_error:tk.messagebox.showwarning(title='total_share_error', message='总股本数据输入错误,该筛选功能不运行')try:  # total_mv 总市值(亿元)if input_total_mv_leftvar.get() and input_total_mv_rightvar.get():df_screen = df_screen[(round(df_screen['total_mv']/10000, 2) > float(input_total_mv_leftvar.get())) &(round(df_screen['total_mv']/10000, 2) < float(input_total_mv_rightvar.get()))]elif input_total_mv_leftvar.get() and not input_total_mv_rightvar.get():df_screen = df_screen[round(df_screen['total_mv']/10000, 2) > float(input_total_mv_leftvar.get())]elif input_total_mv_rightvar.get() and not input_total_mv_leftvar.get():df_screen = df_screen[round(df_screen['total_mv']/10000, 2) < float(input_total_mv_rightvar.get())]elif not input_total_mv_rightvar.get() and not input_total_mv_leftvar.get():df_screen = df_screenexcept Exception as total_mv_error:tk.messagebox.showwarning(title='total_mv_error', message='总市值数据输入错误,该筛选功能不运行')try:  # float_share 流通股本(亿股)if input_float_share_leftvar.get() and input_float_share_rightvar.get():df_screen = df_screen[(round(df_screen['float_share']/10000, 2) > float(input_float_share_leftvar.get())) &(round(df_screen['float_share']/10000, 2) < float(input_float_share_rightvar.get()))]elif input_float_share_leftvar.get() and not input_float_share_rightvar.get():df_screen = df_screen[round(df_screen['float_share']/10000, 2) > float(input_float_share_leftvar.get())]elif input_float_share_rightvar.get() and not input_float_share_leftvar.get():df_screen = df_screen[round(df_screen['float_share']/10000, 2) < float(input_float_share_rightvar.get())]elif not input_float_share_rightvar.get() and not input_float_share_leftvar.get():df_screen = df_screenexcept Exception as float_share_error:tk.messagebox.showwarning(title='float_share_error', message='流通股本数据输入错误,该筛选功能不运行')try:  # circ_mv 流通市值(万元)if input_circ_mv_leftvar.get() and input_circ_mv_rightvar.get():df_screen = df_screen[(round(df_screen['circ_mv']/10000, 2) > float(input_circ_mv_leftvar.get())) &(round(df_screen['circ_mv']/10000, 2) < float(input_circ_mv_rightvar.get()))]elif input_circ_mv_leftvar.get() and not input_circ_mv_rightvar.get():df_screen = df_screen[round(df_screen['circ_mv']/10000, 2) > float(input_circ_mv_leftvar.get())]print(df_screen)elif input_circ_mv_rightvar.get() and not input_circ_mv_leftvar.get():df_screen = df_screen[round(df_screen['circ_mv']/10000, 2) < float(input_circ_mv_rightvar.get())]elif not input_circ_mv_rightvar.get() and not input_circ_mv_leftvar.get():df_screen = df_screenexcept Exception as circ_mv_error:tk.messagebox.showwarning(title='circ_mv_error', message='流通市值数据输入错误,该筛选功能不运行')try:  # free_share 自由流通股本(万股)if input_free_share_leftvar.get() and input_free_share_rightvar.get():df_screen = df_screen[(round(df_screen['free_share']/10000, 2) > float(input_free_share_leftvar.get())) &(round(df_screen['free_share']/10000, 2) < float(input_free_share_rightvar.get()))]elif input_free_share_leftvar.get() and not input_free_share_rightvar.get():df_screen = df_screen[round(df_screen['free_share']/10000, 2) > float(input_free_share_leftvar.get())]elif input_free_share_rightvar.get() and not input_free_share_leftvar.get():df_screen = df_screen[round(df_screen['free_share']/10000, 2) < float(input_free_share_rightvar.get())]elif not input_free_share_rightvar.get() and not input_free_share_leftvar.get():df_screen = df_screenexcept Exception as free_share_mv_error:tk.messagebox.showwarning(title='free_share_mv_error', message='自由流通股本数据输入错误,该筛选功能不运行')multi_code_name = df_screen['ts_code']if multi_var.get() == 2:multi_code_name = multi_code_name_transform(stock_name)start_date = multi_input_startdate_var.get()end_date = multi_input_enddate_var.get()# *************************************************************************************************************# 设置间计数器j,方便统计运行次数跟导出CSV格式数据做准备j = 0multi_state_label = tk.Label(tk_window.bottom_frame,text='此次回测一共有%d个股票,目前已经回测到第%d个股票' % (len(multi_code_name), j),height=1, bg='#353535', fg='white')multi_state_label.pack()multi_df_csv = pd.DataFrame(columns=['股票代码', '股票名称', '初始资金', '期末资金', '浮动盈亏', '已完成盈亏', '手续费','夏普', '最大回撤', '资金回撤', '已回撤天数', '交易次数', '未完成交易', '已完成交易','盈亏次数', '亏损次数', '胜率', '败率'], index=range(len(multi_code_name)))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:00back_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_strategycerebro.addstrategy(my_strategy)# 添加数据cerebro.adddata(data_back)# 设置资金startcash = 100000cerebro.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# 回测得不到的数据统一设置成-9999,方便回测结束后排序功能正常运行,后面设置的排序函数没有针对含有空值的处理代码try:multi_df['期末资金'] = round(cerebro.broker.getvalue(), 2)except Exception as e1:multi_df['期末资金'] = -9999try:multi_df['浮动盈亏'] = round((cerebro.broker.getvalue() - startcash -backtrader_analysis.analyzers.TradeAnalyzer.get_analysis()['pnl']['net']['total']), 2)except Exception as e2:multi_df['浮动盈亏'] = -9999try:multi_df['已完成盈亏'] = round(backtrader_analysis.analyzers.TradeAnalyzer.get_analysis()['pnl']['net']['total'], 2)except Exception as e3:multi_df['已完成盈亏'] = -9999try: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['手续费'] = -9999try:multi_df['夏普'] = round(backtrader_analysis.analyzers.SharpeRatio.get_analysis()['sharperatio'], 2)except Exception as e5:multi_df['夏普'] = -9999try:multi_df['最大回撤'] = round(backtrader_analysis.analyzers.DW.get_analysis()['max']['drawdown'], 2)except Exception as e6:multi_df['最大回撤'] = -9999try:multi_df['资金回撤'] = round(backtrader_analysis.analyzers.DW.get_analysis()['max']['moneydown'], 2)except Exception as e7:multi_df['资金回撤'] = -9999try:multi_df['已回撤天数'] = backtrader_analysis.analyzers.DW.get_analysis()['max']['len']except Exception as e8:multi_df['已回撤天数'] = -9999try:multi_df['交易次数'] = backtrader_analysis.analyzers.TradeAnalyzer.get_analysis()['total']['total']except Exception as e9:multi_df['交易次数'] = -9999try:multi_df['未完成交易'] = backtrader_analysis.analyzers.TradeAnalyzer.get_analysis()['total']['open']except Exception as e10:multi_df['未完成交易'] = -9999try:multi_df['已完成交易'] = backtrader_analysis.analyzers.TradeAnalyzer.get_analysis()['total']['closed']except Exception as e11:multi_df['已完成交易'] = -9999try:multi_df['盈亏次数'] = backtrader_analysis.analyzers.TradeAnalyzer.get_analysis()['won']['total']except Exception as e12:multi_df['盈亏次数'] = -9999try:multi_df['亏损次数'] = backtrader_analysis.analyzers.TradeAnalyzer.get_analysis()['lost']['total']except Exception as e13:multi_df['亏损次数'] = -9999try: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['胜率'] = -9999try: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['败率'] = -9999for 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# 将获取得到的数据插入到multi_df_csv中,为导出CSV格式文件内容做准备multi_df_csv.iloc[j] = multi_df.values[0]# print(multi_df_csv)j += 1multi_state_label.config(text='此次回测一共有%d个股票,目前已经回测到第%d个股票' % (len(multi_code_name), j))print('此次回测一共有%d个股票,目前已经回测到第%d个股票了,请耐心等待' % (len(multi_code_name), j))def stock_treeview_sort(tv, col, reverse):  # Treeview、列名、排列方式# tv.set指定item,如果不设定column和value,则返回他们的字典,如果设定了column,则返回该column的value,# 如果value也设定了,则作相应更改。# get_children()函数,其返回的是treeview中的记录号# 参照网上的treeview排序方法函数,由于股票的价格排序数据类型是浮点数字,在排序钱将价格类型由str转换成float,否则排序会不正确try: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('')]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, anchor='center')multi_stock_treeview.heading(col, text=col,command=lambda col=col: stock_treeview_sort(multi_stock_treeview, col, False))# *************************************************************************************************************def result_tocsv():for item in strategy_list_tree.selection():item_text = strategy_list_tree.item(item, "text")  # 获取选中树形条目的名称csv_strategy_name = item_text.split('.')[0]  # 获取策略名称,作用是命名新建的CSV文件变量# 获取到当前项目文件的目录,并检查是否有‘策略结果’文件夹,如果不存在则自动新建‘策略结果’文件夹result_csv_Path = os.getcwd() + '\\策略结果' + '\\' + csv_strategy_nameif not os.path.exists(result_csv_Path):os.makedirs(result_csv_Path)# 创建回测策略结果csv,首先获取时间str格式,用来对结果进行命名,这样文件的名称就不会重复了,还能知道时间csv_get_time = time.strftime('%Y-%m-%d %H%M%S', time.localtime(time.time()))if not os.path.exists(result_csv_Path + '\\' + csv_strategy_name + csv_get_time + '.csv'):multi_df_csv.to_csv(result_csv_Path + '\\' + csv_strategy_name + csv_get_time + '.csv', index=False,encoding='utf_8_sig')else:multi_df_csv.to_csv(result_csv_Path + '\\' + csv_strategy_name + csv_get_time + 'A.csv', index=False,encoding='utf_8_sig')# *************************************************************************************************************# 多股回测弹出菜单输出回测图形函数def backtrader_show_plot():plt.close('all')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()函数返回指定长度的字符串,原字符串右对齐,前面填充0if 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_stockcodefor multi_item in multi_stock_treeview.selection():multi_item_text = multi_stock_treeview.item(multi_item, "values")# print(multi_item_text[0])  # 输出所选行的第一列的值plot_code_name = code_name_transform(multi_item_text[0])# adj='qfq'向前复权,freq='D 数据频度:日K线df = ts.pro_bar(ts_code=plot_code_name, start_date=start_date, end_date=end_date, adj='qfq',freq='D')df['trade_date'] = pd.to_datetime(df['trade_date'])# 设置用于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当中去# str转换成时间格式2015-01-01 00:00:00back_start_time = datetime.datetime.strptime(start_date, '%Y%m%d')back_end_time = datetime.datetime.strptime(end_date, '%Y%m%d')data_back = bt.feeds.PandasData(dataname=df_back,fromdate=back_start_time,todate=back_end_time)# 创建策略容器cerebro_single = bt.Cerebro()# 添加自定义的策略my_strategycerebro_single.addstrategy(my_strategy)# 添加数据cerebro_single.adddata(data_back)# 设置资金startcash_single = 100000cerebro_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)strategy_plot_menu = tk.Menu(backtrader_bottom_frame, tearoff=False)  # tearoff=True显示分割线strategy_plot_menu.add_command(label='回测图形', command=backtrader_show_plot)  # 弹出菜单内容strategy_plot_menu.add_command(label='回测结果导出', command=result_tocsv)  # 弹出菜单内容def pop_cerebro(event):strategy_plot_menu.post(event.x_root, event.y_root)  # #设置弹出的位置multi_stock_treeview.bind('<Button-3>', pop_cerebro)  # 设置右键弹出菜单# ******************************************************************************************************************# 线程管理,对运行中的策略进行结束功能def thread_start(func, *args):global tt = threading.Thread(target=func, args=args)t.setDaemon(True)t.start()def _async_raise(tid, exctype):tid = ctypes.c_long(tid)if not inspect.isclass(exctype):exctype = type(exctype)res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype))if res == 0:raise ValueError("invalid thread id")elif res != 1:ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)raise SystemError("PyThreadState_SetAsyncExc failed")def stop_thread(thread):_async_raise(thread.ident, SystemExit)# ******************************************************************************************************************# 在主框架下创建回测按钮子框架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, bg='#353535', fg='red',command=mplfinance_go)mplfinance_button.pack(side=tk.LEFT, padx=4, pady=4)backtrader_button = tk.Button(backtrade_left_button_frame, text='BackTrader', height=1, bg='#353535', fg='red',command=backtrader_go)backtrader_button.pack(side=tk.RIGHT, padx=4, pady=4)multi_backtrader_button = tk.Button(backtrade_right_button_frame, text='MultiBackTrade', height=1,bg='#353535', fg='red', command=lambda: thread_start(multibacktrader_go))multi_backtrader_button.pack(side=tk.LEFT, padx=4, pady=4)multi_pause_button = tk.Button(backtrade_right_button_frame, text='Stop', height=1,bg='#353535', fg='red', command=lambda: stop_thread(t))multi_pause_button.pack(side=tk.LEFT, padx=4, pady=4)

function.py

import tkinter as tk
import time
import tk_windowdef time_clock():tk_clock_var = tk.StringVar()tk_clock = tk.Label(tk_window.bottom_frame, textvariable=tk_clock_var, bg='#353535', fg='white')tk_clock.pack(side=tk.RIGHT)def tk_clock_trickit():tk_clock_var.set(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())))tk_window.bottom_frame.update()tk_clock.after(0, tk_clock_trickit)tk_clock.after(0, tk_clock_trickit)

python 金融量化盘后分析系统V0.5相关推荐

  1. python 金融量化盘后分析系统V0.48.5

    前言: 添加了多股回测结果导出CSV功能,点击回测结果导出将自动在项目的文件夹下自动创建 '策略结果' 文件夹,之后会根据你策略文件的名称在'策略结果'文件夹下再次创建一个以策略文件名命名的文件夹,最 ...

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

    前言: 思考了段时间,分析股票回测最终还是选择了backtrader,大体写了个框架,目前的效果图如下(后期还会改): 这次新添加了两个py文件,分别是stock_backtrader.py跟func ...

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

    前言:先放效果图吧 开发的过程中因为知识水平有限,且对backtrader的各种不熟悉,相关的属性跟对象操作资料较少,实在写的很吃力,看官方那个API,水平有限,实在看得头晕. 效果图的图形输出用的是 ...

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

    前言: 先放效果先吧: 一:这次更新主要是增加了策略文件这一功能,主要目的是为了方便切换策略测试.增添了弹出菜单新建策略.编辑.运行该策略.重命名.删除.刷新功能. 二:其中在点击回测是会在你项目的路 ...

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

    距离更新这个系统框架过了差不多半个多月了,在此期间学习了点基础知识,决定把这个系统分为三个.py文件,于是得到了如下效果图 这三个文件目前的思路是这样的: main.py 负责总程序的运行 tk_wi ...

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

    前言: 就只更新了一个多股策略运行线程关闭功能按钮,由于添加了线程管理代码,多股回测的结果实时显示在tkinter,回测一个显示一个.只更改了stock_backtrader.py这个文件,其他不变. ...

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

    前言: 先放效果图: 这次更新主要是增加了多股跟全市股票回测,策略方面还是跟上篇文章的一样,21日跟55日均线相交的策略.多股方面考虑到了均线的周期跟实际股票的数据数量是否适合问题,例如假设55日的均 ...

  8. python金融量化分析 | 闲杂笔记

    最近事情好像有点多,处理得心不在焉.之前国庆计划把张五常老师的经济解释卷二看完,但也是只把第三章生产的成本看了一下,哈哈~ 这是一篇python金融量化分析的闲杂且入门的笔记,感觉学习价值较低,我只是 ...

  9. 【Python金融量化】财经新闻文本分析

    内容来自:微信公众号:python金融量化 关注可了解更多的金融与Python干货. 引言 "80%的商业信息来自非结构化数据,主要是文本数据"(Seth Grimes),这一说法 ...

最新文章

  1. Nature子刊评论:2020年后,微生物组将如何发展?
  2. 独家 | 感悟注意力机制
  3. DNA Sorting(排序)
  4. Phpmyadmin安装过程
  5. java支持闭包_JAVA 需要引入闭包吗
  6. 从中关村的小小柜台,但目前市值千亿元的公司
  7. 无线网络(4) 无线网络基本工作原理
  8. 【Gym-101775 J】Straight Master【差分、贪心】
  9. 预定系统 mysql_Jsp+Ssm+Mysql实现的酒店预定管理系统毕设指导思路模板
  10. 用户购物行为数据分析
  11. PHP TP5框架 发送短信验证码
  12. python中如何判断词性_Python自然语言处理 5 分类和标注词汇
  13. linux系统漏洞firefart,记:从文件上传漏洞到获得root权限
  14. 摄像头模组 Golden Sample
  15. spring boot启动后控制台没有端口信息打印日志也很少
  16. 通过在线制图工具绘制阿里云部署图
  17. 【Python代码实践】使用Python批量获取雨课堂图片
  18. 利用PHP编程生成蜘蛛访问记录txt文档
  19. AMBA协议笔记(APB)
  20. 电力电子技术填空题(80+道),适合期末复习、面试等

热门文章

  1. 金港赢简述大基建大金融和三赛道
  2. 区块链架构下,智慧城市加速发展
  3. jquery选项卡切换功能实现
  4. AE平时作业——文字动画
  5. 408中数据的表示和运算【超强总结】
  6. 【iPhone】手机上接收短信同步转发到iPad和Mac,为什么没有短信转发解决办法
  7. 基于 iris 数据集采用感知器 Perceptron 实现线性二分类
  8. GITHUB开源SpringBoot2.0权限管理系统
  9. 法拉第笑了,麦克斯韦哭了
  10. 【Python爬虫案例学习16】pyspider煎蛋无聊图爬取