打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

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

原创文章第284篇,专注“个人成长与财富自由、世界运作的逻辑与投资"。

今天继续,把回测引擎整合到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回测,主流程已经跑通了,后续要加上规则/模型的配置界面即可。

代码已经上传至星球,请大家请往下载。

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
用Python徒手撸一个股票回测框架搭建
东方财富 量化交易(自动)程序(1)
Python爬虫系列,采集好看视频网站视频
使用Python编写免安装运行时、以Windows后台服务形式运行的WEB服务器
python关键字UI自动化测试框架(3)-日志和断言封装
python知识巩固 | 自定义日志模块封装,将日志格式化打印到控制台或是输出到文件?
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服