wxpyton加回测引擎主流程跑通,使用pandas_bokeh作回测结果可视化(代码下载)

今天继续,把回测引擎整合到gui中。

这里最大的难点在于,由于回测过程需要的时间比较耗时,不能直接跑在gui的主线程中,需要起子线程。不过子线程的状态,要在gui里显示出来,直接跨线程操作界面会出问题。

我们使用wxpython内置的startWorker启动一个后台工作线程。

job_id = 100
startWorker(self._end_backtest, self._start_backtest, jobID=job_id)

def _append_logs(self, message):
self.panel_backtest.text_logs.AppendText(message + '\r\n')

def _start_backtest(self):
self.panel_backtest.btn_backtest.Enable(False)
try:
name = self.combo_proj.GetValue()
if name not in self.loader.proj_dict.keys():
wx.MessageBox("策略不存在")
return
self.do_backtest(name)
except:
self._append_logs('执行回测出错')

def _end_backtest(self, something):
self._append_logs('回测成功完成!')
self.panel_backtest.btn_backtest.Enable(True)
self.panel_backtest.gauge_backtest.SetValue(0)

我们看一下一个典型的策略配置文件,toml格式:

name = '静待花开的聚宝盘'

[data]
start_date = '20100101'
end_date = ''
symbols = [
        '511220.SH', #城投债
        '512010.SH', # 医药
        '518880.SH', #黄金
        '163415.SZ', #兴全商业
        '159928.SZ', # 消费
        '161903.SZ', # 万家行业优选
        '513100.SH' # 纳指
    ]
fields = ['roc(close,20)']
names = ['roc_20']

benchmarks=['000300.SH']

[[algos]]
name = 'RunDays'# 运行周期与再平衡
days=5

[[algos]]
name = 'SelectBySignal'
buy_rules=['ind(roc_20)>0.02']
sell_rules=['ind(roc_20)<-0.02']


[[algos]]
name = 'WeightEqually'

toml格式与dict字典是等价的。

全局的消息转发:

class GlobalHandler:
    def __init__(self):
        self.observers_fns = []

    def add_observer_fn(self, fn):
        self.observers_fns.append(fn)

    def notify(self, data: dict):
        for o in self.observers_fns:
            o(data)


g = GlobalHandler()


def my_logger_notify(data):
    g.notify({'msg_type': 'LOGGER', 'data': data})


from loguru import logger

logger.add(my_logger_notify)

注意最后这两句,loguru可以转logger的日志传给GlobalHandler。

而界面“观察”全局处理器,进而把logger显示在gui上。

图片

进度条更新:

图片

同样的使用event_handler,影响on_bar事件即可,这样代码的耦合最低。

def _event_handler(self, data: dict):
    # print('logger......')
    if 'msg_type' in data.keys() and data['msg_type'] == 'LOGGER':
        self._append_logs(data['data'])
    if data['msg_type'] == 'ON_BAR':
        self.panel_backtest.gauge_backtest.SetValue(data['step'])

我们使用bokeh来显现回测结果可视化。

要特别注意一点是,在计算线程里,如果调用browser的SetPage是无效的。这里“浪费”了我1个小时,应该是在call_back里调用,然后更新回测结果:

_end_backtest

图片

from bokeh.plotting import figure, show
from bokeh.layouts import row
from bokeh.models import HoverTool
from bokeh.models import ColumnDataSource
import numpy as np
import pandas as pd
from datetime import datetime
from engine.datafeed.dataloader import Hdf5Dataloader

import pandas_bokeh

from bokeh.models.widgets import DataTable, TableColumn
from bokeh.models import ColumnDataSource

np.random.seed(55)


class BokehMgr:
    def plot_line(self, df, y_col, **kwargs):
        df.plot_bokeh(kind="line", y=[y_col, 'open'], **kwargs)

    def _calc_metrics(self, portfolio_df):
        returns = portfolio_df['market_value'].pct_change()
        loader = Hdf5Dataloader(['399006.SZ'], start_date="20100101")
        bench_df = loader.load()
        returns.name = '策略'
        # if self.benchmarks_df is not None and len(self.benchmarks_df):

        benchmark_returns = bench_df.pivot_table(index='date', columns='symbol', values='return_0')
        returns = pd.concat([returns, benchmark_returns], axis=1)

        from empyrical import max_drawdown, sharpe_ratio, annual_return

        returns.dropna(inplace=True)

        print(annual_return(returns))
        print(max_drawdown(returns))

        from engine.performance import PerformanceUtils
        df_ratio, df_corr = PerformanceUtils().calc_rates(returns)
        print(df_ratio)
        df_ratio['名称'] = df_ratio.index
        return df_ratio, df_corr

    def _show_table(self, df):
        data_table = DataTable(
            columns=[TableColumn(field=Ci, title=Ci) for Ci in df.columns],
            source=ColumnDataSource(df),
            height=300,
        )
        return data_table

    def show(self, backtest_h5, plot=False, return_html=True):
        with pd.HDFStore(backtest_h5) as s:
            portfolio_df = s['portfolio_df']
            orders_df = s['orders_df']
            orders_df['date'] = orders_df['date'].apply(lambda x: x.strftime('%Y-%m-%d'))

        portfolio_df['equity'] = (portfolio_df['market_value'].pct_change() + 1).cumprod()

        df_metrics, df_corr = self._calc_metrics(portfolio_df=portfolio_df)
        table_metrics = self._show_table(df_metrics)
        table_orders = self._show_table(orders_df)
        table_corr = self._show_table(df_corr)

        # 创建散点图:
        p_equity = portfolio_df.plot_bokeh.line(
            y="equity",
            # category="species",
            title="投资万元波动图",
            show_figure=False,
            rangetool=False,
        )

        p_market_value = portfolio_df.plot_bokeh.line(
            y="market_value",
            title="投资组合市值",
            show_figure=False,
            # rangetool=True,
        )

        # Combine Table and Scatterplot via grid layout:
        html = pandas_bokeh.plot_grid([[p_equity, table_metrics], [table_orders, table_corr]], show_plot=plot,
                                      return_html=return_html)
        return html


if __name__ == '__main__':
    from engine.datafeed.dataloader import Hdf5Dataloader
    from engine.config import etfs_indexes

    symbols = etfs_indexes.values()
    loader = Hdf5Dataloader(['000300.SH'], start_date="20100101")
    fields = ["roc(close,20)", "shift(close, -5)/shift(open, -1) - 1",
              "qcut(label_c, 10)"
              ]
    names = ["roc_20", "label_c",
             'label'
             ]

    # df = loader.load(fields=fields, names=names)
    # df.dropna(inplace=True)
    # df.index = pd.to_datetime(df.index)
    # df['equity'] = (1 + df['return_0']).cumprod()

    from engine.config import DATA_RESULTS

    html = BokehMgr().show(backtest_h5=DATA_RESULTS.joinpath('等权策略.h5').resolve(), plot=True, return_html=False)
    # print(html)

通过gui回测,主流程已经跑通了,后续要加上规则/模型的配置界面即可。

发布者:股市刺客,转载请注明出处:https://www.95sca.cn/archives/104023
站内所有文章皆来自网络转载或读者投稿,请勿用于商业用途。如有侵权、不妥之处,请联系站长并出示版权证明以便删除。敬请谅解!

(0)
股市刺客的头像股市刺客
上一篇 2024 年 7 月 29 日
下一篇 2024 年 7 月 29 日

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注