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

前言,此次主要是更新了backtrader.py文件的代码,其他的文件代码没动,所以这次只放出backtrader.py的代码,其他的就不放了。更改后的效果如下(暂定):
在这里插入图片描述
还有些细节内容没有写跟完善,先记录下

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)
import datetime
import pandas as pd
import backtrader as bt
import tushare as ts
import tk_window
import tkinter as tk
from tkinter import ttk
import matplotlib.pyplot as plt
import matplotlib as mpl  # 用于设置曲线参数
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)  # 使用后端TkAgg

mpl.use('TkAgg')
ts.set_token('去tushare官网注册个账号吧,token我就不提供了')


class my_strategy(bt.Strategy):
    # 设置简单均线周期,以备后面调用
    params = (
        ('maperiod21', 21),
        ('maperiod55', 55),)

    def log(self, txt, dt=None):
        # 日记记录输出
        dt = dt or self.datas[0].datetime.date(0)
        print('%s, %s' % (dt.isoformat(), txt))

    def __init__(self):
        # 初始化数据参数
        # 设置当前收盘价为dataclose
        self.dataclose = self.datas[0].close

        self.order = None
        self.buyprice = None
        self.buycomm = None

        # 添加简单均线, subplot=False是否单独子图显示
        self.sma21 = bt.indicators.SimpleMovingAverage(self.datas[0], period=self.params.maperiod21, plotname='mysma')
        self.sma55 = bt.indicators.SimpleMovingAverage(self.datas[0], period=self.params.maperiod55, subplot=False)

    def next(self):
        # self.log('Close, %.2f' % self.dataclose[0])  # 输出打印收盘价
        # 检查是否有订单发送当中,如果有则不再发送第二个订单
        if self.order:
            return

        # 检查是否已经有仓位
        if not self.position:
            # 如果没有则可以执行一下策略了
            if self.sma21[0] > self.sma55[0] and self.sma21[-1] < self.sma55[-1]:
                # 记录输出买入价格
                self.log('买入信号产生的价格: %.2f' % self.dataclose[0])
                # 跟踪已经创建好的订单避免重复第二次交易
                self.order = self.buy()

        else:
            if self.sma21[0] < self.sma55[0] and self.sma21[-1] > self.sma55[-1]:
                self.log('卖入信号产生的价格: %.2f' % self.dataclose[0])
                self.order = self.sell()

    # 记录交易执行情况,输出打印
    def notify_order(self, order):
        if order.status in [order.Submitted, order.Accepted]:
            # 如果有订单提交或者已经接受的订单,返回退出
            return
        # 主要是检查有没有成交的订单,如果有则日志记录输出价格,金额,手续费。注意,如果资金不足是不会成交订单的
        if order.status in [order.Completed]:
            if order.isbuy():
                self.log(
                    '实际买入价格: %.2f, 市值: %.2f, 手续费 %.2f' %
                    (order.executed.price,
                     order.executed.value,
                     order.executed.comm))

                self.buyprice = order.executed.price
                self.buycomm = order.executed.comm
            else:  # Sell
                self.log('实际卖出价格: %.2f, 市值: %.2f, 手续费 %.2f' %
                         (order.executed.price,
                          order.executed.value,
                          order.executed.comm))
            # len(self)是指获取截至当前数据一共有多少根bar
            # 以下代码就是指当交易发生时立刻记录下了当天有多少根bar
            # 如果要表示当成交后过了5天卖,则可以这样写 if len(self) >= (self.bar_executed + 5):
            self.bar_executed = len(self)

        elif order.status in [order.Canceled, order.Margin, order.Rejected]:
            self.log('Order Canceled/Margin/Rejected')

        self.order = None

    # 记录交易收益情况
    def notify_trade(self, trade):
        if not trade.isclosed:  # 如果交易还没有关闭,则退出不输出显示盈利跟手续费
            return
        self.log('策略收益 %.2f, 净收益 %.2f' %
                 (trade.pnl, trade.pnlcomm))

    def stop(self):
        # 策略停止输出结果
        total_funds = self.broker.getvalue()
        print('MA均线: %2d日,总资金: %.2f' % (self.params.maperiod21, total_funds))


def run_cerebro():  # 策略回测
    for widget_backtrader_window in tk_window.centre_frame.winfo_children():
        widget_backtrader_window.destroy()
    backtrader_window = tk.PanedWindow(tk_window.centre_frame, opaqueresize=False)
    backtrader_window.pack(fill=tk.BOTH, expand=1)
    # 创建左边frame框架,主要放回测策略文件代码
    backtrader_left_frame = tk.Frame(backtrader_window, relief=tk.SUNKEN, bg='#353535', bd=5, borderwidth=4)
    backtrader_left_frame.pack(fill=tk.BOTH, expand=1)
    top_label = tk.Label(backtrader_left_frame, text='回测策略文件', bd=1)
    top_label.pack()
    backtrader_window.add(backtrader_left_frame)

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

    backtrader_top_frame = tk.Frame(backtrader_plot_window, width=tk_window.screenWidth, height=tk_window.screenHeight,
                                    relief=tk.SUNKEN, bg='#353535', bd=5, borderwidth=4)
    backtrader_top_frame.pack(fill=tk.BOTH)

    # 在主框架下创建股票代码输入子框架
    code_frame = tk.Frame(backtrader_top_frame, borderwidth=1, bg='#353535')
    code_frame.pack()
    # 创建标签‘股票代码’
    stock_label = tk.Label(code_frame, text='股票代码', bd=1)
    stock_label.pack(side=tk.LEFT)
    # 创建股票代码输入框
    input_code_var = tk.StringVar()
    code_widget = tk.Entry(code_frame, textvariable=input_code_var, borderwidth=1, justify=tk.CENTER)
    # input_code_get = input_code_var.set(input_code_var.get())  # 获取输入的新值
    code_widget.pack(side=tk.LEFT, padx=4)

    # 在主框架下创建股票日期输入框子框架
    input_date_frame = tk.Frame(backtrader_top_frame, borderwidth=1, bg='#353535')
    input_date_frame.pack()
    # 创建标签‘开始日期’
    date_start_label = tk.Label(input_date_frame, text='开始日期', bd=1)
    date_start_label.pack(side=tk.LEFT)
    # 创建开始日期代码输入框
    input_startdate_var = tk.StringVar()
    startdate_widget = tk.Entry(input_date_frame, textvariable=input_startdate_var, borderwidth=1, justify=tk.CENTER)
    input_startdate_get = input_startdate_var.set(input_startdate_var.get())  # 获取输入的新值
    startdate_widget.pack(side=tk.LEFT, padx=4)
    # 创建标签‘结束日期’
    date_end_label = tk.Label(input_date_frame, text='结束日期', bd=1)
    date_end_label.pack(side=tk.LEFT)
    # 创建结束日期代码输入框
    input_enddate_var = tk.StringVar()
    enddate_widget = tk.Entry(input_date_frame, textvariable=input_enddate_var, borderwidth=1, justify=tk.CENTER)
    input_enddate_get = input_enddate_var.set(input_enddate_var.get())  # 获取输入的新值
    enddate_widget.pack(side=tk.LEFT, padx=4)
    # 先把部件布局好了再backtrader_top_frame用.add()添加到backtrader_plot_window
    backtrader_plot_window.add(backtrader_top_frame, height=tk_window.screenHeight / 10)

    # 创建底部窗口框架,用来放图形输出
    backtrader_bottom_frame = tk.Frame(backtrader_plot_window, width=tk_window.screenWidth,
                                       height=tk_window.screenHeight, relief=tk.SUNKEN, bg='#353535', bd=5,
                                       borderwidth=4)
    backtrader_bottom_frame.pack(fill=tk.BOTH)

    backtrader_plot_window.add(backtrader_bottom_frame)

    def backtrader_go():  # 图形输出渲染
        # 以下函数作用是省略输入代码后缀.sz .sh
        def code_name_transform(get_stockcode):  # 输入的数字股票代码转换成字符串股票代码
            str_stockcode = str(get_stockcode)
            str_stockcode = str_stockcode.strip()  # 删除前后空格字符
            if 6 > len(str_stockcode) > 0:
                str_stockcode = str_stockcode.zfill(6) + '.SZ'  # zfill()函数返回指定长度的字符串,原字符串右对齐,前面填充0
            if len(str_stockcode) == 6:
                if str_stockcode[0:1] == '0':
                    str_stockcode = str_stockcode + '.SZ'
                if str_stockcode[0:1] == '3':
                    str_stockcode = str_stockcode + '.SZ'
                if str_stockcode[0:1] == '6':
                    str_stockcode = str_stockcode + '.SH'
            return str_stockcode
        # 先设置下框架的清理,放置按下回测按钮时重复生成图形覆盖
        for widget_plot in backtrader_bottom_frame.winfo_children():
            widget_plot.destroy()
        for widget_plot1 in backtrader_bottom_frame.winfo_children():
            widget_plot1.destroy()
        # 交互数据的获取跟处理
        stock_name = input_code_var.get()
        code_name = code_name_transform(stock_name)
        start_date = input_startdate_var.get()
        end_date = input_enddate_var.get()

        # adj='qfq'向前复权,freq='D 数据频度:日K线
        df = ts.pro_bar(ts_code=code_name, start_date=start_date, end_date=end_date, adj='qfq', freq='D')
        df['trade_date'] = pd.to_datetime(df['trade_date'])
        # df = df.drop(['change', 'pre_close', 'pct_chg', 'amount'], axis=1)
        df = df.rename(columns={'vol': 'volume'})
        df.set_index('trade_date', inplace=True)  # 设置索引覆盖原来的数据
        df = df.sort_index(ascending=True)  # 将时间顺序升序,符合时间序列
        df['openinterest'] = 0
        # 喂养数据到backtrader当中去
        back_start_time = datetime.datetime.strptime(start_date, "%Y%m%d")  # str转换成时间格式2015-01-01 00:00:00
        back_end_time = datetime.datetime.strptime(end_date, '%Y%m%d')
        # print(back_start_time)
        data = bt.feeds.PandasData(dataname=df,
                                   fromdate=back_start_time,
                                   todate=back_end_time
                                   )

        # 创建策略容器
        cerebro = bt.Cerebro()
        # 添加自定义的策略my_strategy
        cerebro.addstrategy(my_strategy)
        # 添加数据
        cerebro.adddata(data)
        # 设置资金
        startcash = 100000
        cerebro.broker.setcash(startcash)
        # 设置每笔交易交易的股票数量
        cerebro.addsizer(bt.sizers.FixedSize, stake=100)
        # 设置手续费
        cerebro.broker.setcommission(commission=0.0005)
        # 输出初始资金
        d1 = back_start_time.strftime('%Y%m%d')
        d2 = back_end_time.strftime('%Y%m%d')
        print('初始资金: %.2f' % startcash)
        print('回测开始时间: %s' % d1)
        print('回测结束时间: %s' % d2)
        # 运行策略
        # stdstats=False不显示回测的统计结果
        cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='SharpeRatio')
        cerebro.addanalyzer(bt.analyzers.DrawDown, _name='DW')
        cerebro.addanalyzer(bt.analyzers.PyFolio, _name='pyfolio')
        cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name='TradeAnalyzer')
        result = cerebro.run(stdstats=True)
        backtrader_analysis = result[0]
        print(backtrader_analysis.analyzers.SharpeRatio.get_analysis())
        print(backtrader_analysis.analyzers.SharpeRatio.get_analysis()['sharperatio'])
        print(backtrader_analysis.analyzers.DW.get_analysis())
        print(backtrader_analysis.analyzers.DW.get_analysis()['max']['drawdown'])
        # print(backtrader_analysis.analyzers.pyfolio.get_analysis())
        print(backtrader_analysis.analyzers.TradeAnalyzer.get_analysis())
        SharpeRatio_a = '夏普:%.2f' % backtrader_analysis.analyzers.SharpeRatio.get_analysis()['sharperatio']
        drawdown_a = '最大回撤:%.2f' % backtrader_analysis.analyzers.DW.get_analysis()['max']['drawdown']

        net_profit = cerebro.broker.getvalue() - startcash
        print('净收益: %.2f' % net_profit)
        
        # grid = False不显示分割线
        # cerebro.plot(style='candlestick', grid=False, iplot=False)

        plofit_show = plt.figure('Figure5')
        df.close.plot()
        # df.close[0]指的是测试开始日期收盘价,df.close[-1]指的是数据结束日期收盘价
        plt.annotate('期间累计涨幅: %.2f' % ((df.close[-1] / df.close[0] - 1) * 100) + '%', xy=(df.index[-150],
                     df.close.mean()), xytext=(df.index[-500], df.close.min()),
                     bbox=dict(boxstyle='round,pad=0.5', fc='yellow', alpha=0.5),
                     arrowprops=dict(facecolor='green', shrink=0.05), fontsize=12)
        print(df.close[0])
        print(df.close[-1])
        print(df)

        canvas_stock_daily_basic = FigureCanvasTkAgg(plofit_show, master=backtrader_bottom_frame)
        canvas_stock_daily_basic.draw()
        toolbar_stock_daily_basic = NavigationToolbar2Tk(canvas_stock_daily_basic, backtrader_bottom_frame)
        toolbar_stock_daily_basic.update()  # 显示图形导航工具条
        canvas_stock_daily_basic._tkcanvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)

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

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

        backtrader_treeview.column('1', width=70, anchor='center')
        backtrader_treeview.heading('1', text='回测日记')
        backtrader_treeview.pack(side=tk.RIGHT, fill=tk.BOTH, expand=1)
        backtrader_treeview.insert('', 'end', values=SharpeRatio_a)
        backtrader_treeview.insert('', 'end', values=drawdown_a)
    # 在主框架下创建回测按钮子框架
    search_frame = tk.Frame(backtrader_top_frame, borderwidth=1, bg='#353535', relief=tk.SUNKEN)
    search_frame.pack()
    # 创建查询按钮并设置功能
    stock_find = tk.Button(search_frame, text='回测', width=5, height=1, command=backtrader_go)
    stock_find.pack()

# 策略参数优化函数
class my_optimization(bt.Strategy):
    # 设置简单均线周期,以备后面调用
    params = (
        ('maperiod21', 21),
        ('maperiod55', 55),)

    def log(self, txt, dt=None):
        # 日记记录输出
        dt = dt or self.datas[0].datetime.date(0)
        print('%s, %s' % (dt.isoformat(), txt))

    def __init__(self):
        # 初始化数据参数
        # 设置当前收盘价为dataclose
        self.dataclose = self.datas[0].close

        self.order = None
        self.buyprice = None
        self.buycomm = None

        # 添加简单均线, subplot=False是否单独子图显示
        self.sma21 = bt.indicators.SimpleMovingAverage(self.datas[0], period=self.params.maperiod21, plotname='mysma')
        self.sma55 = bt.indicators.SimpleMovingAverage(self.datas[0], period=self.params.maperiod55, subplot=False)

    def next(self):
        # self.log('Close, %.2f' % self.dataclose[0])  # 输出打印收盘价
        # 检查是否有订单发送当中,如果有则不再发送第二个订单
        if self.order:
            return

        # 检查是否已经有仓位
        if not self.position:
            # 如果没有则可以执行一下策略了
            if self.sma21[0] > self.sma55[0] and self.sma21[-1] < self.sma55[-1]:
                # 记录输出买入价格
                # self.log('买入信号产生的价格: %.2f' % self.dataclose[0])
                # 跟踪已经创建好的订单避免重复第二次交易
                self.order = self.buy()

        else:
            if self.sma21[0] < self.sma55[0] and self.sma21[-1] > self.sma55[-1]:
                # self.log('卖入信号产生的价格: %.2f' % self.dataclose[0])
                self.order = self.sell()

    # 记录交易执行情况,输出打印
    def notify_order(self, order):
        if order.status in [order.Submitted, order.Accepted]:
            # 如果有订单提交或者已经接受的订单,返回退出
            return
        # 主要是检查有没有成交的订单,如果有则日志记录输出价格,金额,手续费。注意,如果资金不足是不会成交订单的
        # if order.status in [order.Completed]:
        #     if order.isbuy():
        #         self.log(
        #             '买入价格: %.2f, 市值: %.2f, 手续费 %.2f' %
        #             (order.executed.price,
        #              order.executed.value,
        #              order.executed.comm))
        #
        #         self.buyprice = order.executed.price
        #         self.buycomm = order.executed.comm
        #     else:  # Sell
        #         self.log('卖出价格: %.2f, 市值: %.2f, 手续费 %.2f' %
        #                  (order.executed.price,
        #                   order.executed.value,
        #                   order.executed.comm))
        #     # len(self)是指获取截至当前数据一共有多少根bar
        #     # 以下代码就是指当交易发生时立刻记录下了当天有多少根bar
        #     # 如果要表示当成交后过了5天卖,则可以这样写 if len(self) >= (self.bar_executed + 5):
        #     self.bar_executed = len(self)

        elif order.status in [order.Canceled, order.Margin, order.Rejected]:
            self.log('Order Canceled/Margin/Rejected')

        self.order = None

    # 记录交易收益情况
    def notify_trade(self, trade):
        if not trade.isclosed:  # 如果交易还没有关闭,则退出不输出显示盈利跟手续费
            return
        # self.log('策略收益 %.2f, 净收益 %.2f' %
        # (trade.pnl, trade.pnlcomm))

    def stop(self):
        # 策略停止输出结果
        total_funds = self.broker.getvalue()
        print('MA均线: %2d日,总资金: %.2f' % (self.params.maperiod21, total_funds))


def run_optimization():
    stock_code = '000001.SZ'
    stock_start_date = '20150101'
    stock_end_date = '20200828'
    df = ts.pro_bar(ts_code=stock_code, start_date=stock_start_date, end_date=stock_end_date, adj='qfq', freq='D')
    df['trade_date'] = pd.to_datetime(df['trade_date'])
    # df = df.drop(['change', 'pre_close', 'pct_chg', 'amount'], axis=1)
    df = df.rename(columns={'vol': 'volume'})
    df.set_index('trade_date', inplace=True)  # 设置索引覆盖原来的数据
    df = df.sort_index(ascending=True)  # 将时间顺序升序,符合时间序列
    df['openinterest'] = 0
    # 喂养数据到backtrader当中去
    back_start_time = datetime.datetime(2015, 1, 1)
    back_end_time = datetime.datetime(2020, 8, 28)
    data = bt.feeds.PandasData(dataname=df,
                               fromdate=back_start_time,
                               todate=back_end_time
                               )

    # 创建策略容器
    cerebro = bt.Cerebro()
    # 添加自定义的策略my_strategy
    cerebro.optstrategy(my_optimization, maperiod21=range(3, 54))
    # 添加数据
    cerebro.adddata(data)
    # 设置资金
    startcash = 100000
    cerebro.broker.setcash(startcash)
    # 设置每笔交易交易的股票数量
    cerebro.addsizer(bt.sizers.FixedSize, stake=100)
    # 设置手续费
    cerebro.broker.setcommission(commission=0.01)
    print('期初总资金: %.2f' %
          cerebro.broker.getvalue())
    cerebro.run(maxcpus=1)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Wilburzzz

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

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

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

打赏作者

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

抵扣说明:

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

余额充值