可视化页面搭建
1、基础页
在基础页面主要涉及以下功能:
- 1、展示初始投资资金
- 2、设置时间模式
- 2.1使用回测年数
- 2.2使用回测开始结束时间
- 3、回测结果设置
1、展示初始投资资金
在这个方法中可以让用户自定义设置初始投资资金,以及搭建一个基础页面的基础类
# 继承两个类,一个处理时间模式设置,一个处理回测结果设置
class WidgetRunTT(WidgetTrad, WidgetTime):
"""基础设置界面:初始资金,回测开始,结束周期等"""
def __init__(self):
"""初始化基础回测设置界面"""
# 初始资金
self.cash = widgets.BoundedIntText(
value=1000000,
min=10000,
max=999999999,
step=1,
description='初始资金:',
disabled=False
)
# 时间模式设置
tm_box = self.init_time_mode_ui()
# 回测结果设置
metrics_box = self.init_metrics_ui()
# 通过VBox功能将其展示到页面上
self.widget = widgets.VBox([self.cash, tm_box, metrics_box])
基础类已经完成,但是其中的时间模式设置以及回测结果设置还没有。
2、时间模式设置(init_time_mode_ui)
class WidgetTime(object):
"""
便于上层widgte使用self去获取设置,统一上层使用
混入类:基础时间模式设置:
1. 年数模式
2. 开始结束模式
"""
# noinspection PyProtectedMember
def init_time_mode_ui(self):
"""构建基础 return widgets.VBox"""
# 回测时间模式
self.time_mode = widgets.RadioButtons(
options={'使用回测年数': 0,
'使用回测开始结束日期': 1},
value=0,
description='时间模式:',
disabled=False
)
self.time_mode.observe(self.on_time_mode_change, names='value')
# 年数模式
self.run_years = widgets.BoundedIntText(
value=2,
min=1,
max=6,
step=1,
description='回测年数:',
disabled=False
)
# 开始结束模式
self.start = widgets.Text(
value='2015-01-01',
placeholder='年-月-日',
description='开始日期:',
disabled=False
)
self.end = widgets.Text(
value='2019-04-26',
placeholder='年-月-日',
description='结束日期:',
disabled=False
)
self.run_years.disabled = False
self.start.disabled = True
self.end.disabled = True
return widgets.VBox([self.time_mode, self.run_years, self.start, self.end])
def on_time_mode_change(self, change):
"""切换使用年数还是起始,结束时间做为回测参数"""
if change['new'] == 0:
self.run_years.disabled = False
self.start.disabled = True
self.end.disabled = True
else:
self.run_years.disabled = True
self.start.disabled = False
self.end.disabled = False
3、回测输出设置(init_metrics_ui)
class WidgetTrad(object):
"""
回测输出设置:
1. 输出度量对象:
1. 只输出交易单:orders_pd
2. 只输出行为单:action_pd
3. 只输出资金单:capital_pd
4. 同时输出交易单,行为单,资金单(orders_pd, action_pd, capital_pd)
2. 输出交易单最大行列显示设置:
1. 默认最大行显示50
2. 默认最大列显示50
3. 是否将交易单,行为单,资金单保存在本地output文件中
"""
def init_metrics_ui(self):
"""构建基础 return widgets.VBox"""
self.metrics_out_put = widgets.RadioButtons(
options={'只输出交易单:orders_pd': 0,
'只输出行为单:action_pd': 1,
'只输出资金单:capital_pd': 2,
'输出交易单,行为单,资金单': 3},
value=0,
description='输出对象:',
disabled=False
)
out_put_display_max_label1 = widgets.Label('输出显示最大行列数,最大100行,100列',
layout=widgets.Layout(width='300px', align_items='stretch'))
out_put_display_max_label2 = widgets.Label('如需查看更多输出表单,请选择保存输出至文件',
layout=widgets.Layout(width='300px', align_items='stretch'))
self.out_put_display_max_rows = widgets.IntSlider(
value=50,
min=1,
max=100,
step=1,
description='行数',
disabled=False,
orientation='horizontal',
readout=True,
readout_format='d'
)
self.out_put_display_max_columns = widgets.IntSlider(
value=50,
min=1,
max=100,
step=1,
description='列数',
disabled=False,
orientation='horizontal',
readout=True,
readout_format='d'
)
out_put_display = widgets.VBox([out_put_display_max_label1,
out_put_display_max_label2,
self.out_put_display_max_rows,
self.out_put_display_max_columns])
save_out_put_lable = widgets.Label('是否保存交易单,行为单,资金单到文件',
layout=widgets.Layout(width='300px', align_items='stretch'))
save_out_put_lable2 = widgets.Label('路径:{}'.format(os.path.join(settings.BASE_DIR, 'out_put')),
layout=widgets.Layout(width='300px', align_items='stretch'))
self.save_out_put = widgets.Checkbox(
value=False,
description='保存输出',
disabled=False,
)
save_out_put = widgets.VBox([save_out_put_lable,
save_out_put_lable2,
self.save_out_put])
accordion = widgets.Accordion()
accordion.children = [widgets.VBox([self.metrics_out_put, out_put_display, save_out_put])]
accordion.set_title(0, '回测度量结果设置')
return accordion
2、股池页
涉及功能:
- 1、股池区,用于存放选择的股票
- 点击选中的股票可以将其删除
- 2、选股区,选择需要回测股票
- 点击选择的股票可以将其添加到股池区
- 3、搜索区
- 输入股票代码返回对应股票信息,可以选择添加进入股票池
- 输入股票代码返回对应股票信息,可以选择添加进入股票池
1、股池区
class StockPool(WidgetBase):
"""股票池选股ui界面"""
# noinspection PyProtectedMember
def __init__(self):
"""构建股票池选股ui界面"""
label_layout = widgets.Layout(width='300px', align_items='stretch', justify_content='space-between')
# 股票池多选框
self.choice_symbols = widgets.SelectMultiple(
description='股池:',
disabled=False,
layout=widgets.Layout(width='300px', align_items='stretch', justify_content='space-between')
)
self.choice_symbols.observe(self.choice_symbols_select, names='value')
# 构建所有沙盒中的数据序列
market_title = ['A股'] # 因为目前国内股票历史数据只有A股是免费获取,其他例如美股、港股需要花钱获取,所以先以A股进行测试
cn_seed_symbol = [('{}:{}'.format(name[0], symbol))
for symbol, name in stock_list.get_stock_list(stock_list.K_SAND_BOX_CN).items()]
self.market_dict = {'A股': cn_seed_symbol,}
# 一个市场一个tab,tab中的symbol为沙盒中的symbol
self.market_widget_tab = widgets.Tab()
self.market_symbol_widget = []
for title in market_title:
market_symbol = widgets.SelectMultiple(
options=self.market_dict[title],
description=title,
disabled=False
)
market_symbol.observe(self.on_already_select, names='value')
self.market_symbol_widget.append(market_symbol)
self.market_widget_tab.children = self.market_symbol_widget
for ind, name in enumerate(market_title):
self.market_widget_tab.set_title(ind, name)
self.sc_box = WidgetSearchBox(self.on_already_select)()
self.widget = widgets.VBox([self.choice_symbols, self.market_widget_tab,self.sc_box])
2、选股区
def on_already_select(self, select):
"""搜索框或者内置沙盒symbol中点击放入股票池"""
st_symbol = [symbol.split(':')[1] if symbol.find(':') > 0
else symbol for symbol in list(select['new'])]
# 更新股票池中原有的symbol序列
self.choice_symbols.options = list(set(st_symbol + list(self.choice_symbols.options)))
def choice_symbols_select(self, select):
"""股票池中点击删除股票池中对应的symbol"""
self.choice_symbols.options = list(set(self.choice_symbols.options) - set(select['new']))
3、搜索区
class WidgetSearchBox(WidgetBase):
"""搜索框ui界面"""
# noinspection PyProtectedMember
def __init__(self, search_result_callable):
"""构建股票池选股ui界面"""
if not callable(search_result_callable):
raise TypeError('search_result_select_func must callable!')
# symbol搜索框构建
self.search_bt = widgets.Button(description='搜索:', layout=widgets.Layout(height='10%', width='7%'))
self.search_input = widgets.Text(
value='',
placeholder='请输入股票代码...',
description='',
disabled=False
)
# symbol搜索结果框
self.search_result = widgets.SelectMultiple(
options=[],
description='搜索结果:',
disabled=False,
layout=widgets.Layout(width='300px', align_items='stretch', justify_content='space-between')
)
self.search_result.observe(search_result_callable, names='value')
self.search_bt.on_click(self.do_search)
# 搜索框 + 按钮 + 结果框 box拼接
sc_hb = widgets.HBox([self.search_bt, self.search_input])
self.widget = widgets.VBox([sc_hb, self.search_result])
# noinspection PyUnusedLocal
def do_search(self, bt):
"""搜索框搜索执行函数"""
ts_code = self.search_input.value
data = stock_list.read_data() # data是通过tushare获取的股票信息列表
if ts_code[0] == '0':
ts_code += ".SZ"
elif ts_code[0] == '6':
ts_code += ".SH"
code_data = data[data['ts_code'].isin([ts_code])]
ts_name = code_data.name.values
ts_code = ts_code[:6]
for name in ts_name:
self.search_result.options = [f'{name}:{ts_code}']
3、策略页
涉及功能:
- 1、展示所有的策略
- 2、选择交易策略
- 3、展示已选中的策略
1、界面组件基类
class WidgetBase(object):
"""界面组件基类,限定最终widget为self.widget"""
def __call__(self):
return self.widget
def display(self):
"""显示使用统一display"""
display(self.widget)
2、策略组织类
class Strategy():
"""策略组织类"""
def __init__(self, show_add_buy=True, add_button_style='default'):
self.factor_dict = {}
self.factor_wg_array = []
# 策略候选池可x轴左右滚动
self.factor_layout = widgets.Layout(overflow_x='scroll',
# flex_direction='row',
display='flex')
self.selected_factors = widgets.SelectMultiple(
options=[],
description='已添加策略:',
disabled=False,
layout=widgets.Layout(width='100%', align_items='stretch')
)
# 已添加的全局策略可点击删除
self.selected_factors.observe(self.remove_factor, names='value')
# 全局策略改变通知接收序列
self.selected_factors_obs = set()
self.factor_box = None
# # 默认不启动可滚动因子界面,因为对外的widget版本以及os操作系统不统一
self.show_add_buy = show_add_buy
self.add_button_style = add_button_style
self._init_widget()
if self.factor_box is None:
raise RuntimeError('_init_widget must build factor_box!')
self.widget = widgets.VBox([self.factor_box, self.selected_factors])
def remove_factor(self, select):
"""点击从策略池中删除已选择的策略"""
for st_key in list(select['new']):
self.factor_dict.pop(st_key)
self.selected_factors.options = list(self.factor_dict.keys())
def add_factor(self, factor_dict, factor_desc_key, only_one=False):
"""根据具体策略提供的策略字典对象和策略描述构建上层策略序列"""
print("你执行了添加策略")
if factor_desc_key in self.factor_dict:
msg = '{}策略已经添加过,重复添加!'.format(factor_desc_key)
return
if only_one:
"""
非重复容器类型策略,如一个买入策略只能对应一个仓位管理策略
大多数为可复容器类型策略,如可以有多个买入因子,多个卖出,
多个选股因子
"""
# 对基础类型不要使用clear等函数,py2低版本不支持
self.factor_dict.clear()
self.factor_dict = {}
self.factor_dict[factor_desc_key] = factor_dict
print(self.factor_dict.keys())
self.selected_factors.options = list(self.factor_dict.keys())
def _init_widget(self):
self.sf_array = []
self.sf_array.append(MeanReversion(self))
self.sf_array.append(DoubleAverages(self))
self.sf_array.append(TurtleStrategy(self))
self.scroll_factor_box = False
children = [sf() for sf in self.sf_array]
self.factor_layout = widgets.Layout(overflow_x='scroll',
flex_direction='row',
display='flex')
self.factor_box = widgets.Box(children=children,
layout=self.factor_layout)
3、策略可视化基础类
class WidgetFactorBase(WidgetBase):
"""策略可视化基础类"""
def __init__(self, wg_manager):
self.wg_manager = wg_manager
self.widget = None
self.label_layout = widgets.Layout(width='300px', align_items='stretch')
self.description_layout = widgets.Layout(height='150px')
self.widget_layout = widgets.Layout(align_items='stretch', justify_content='space-between')
4、买入策略可视化基础类
class WidgetFactorBuyBase(WidgetFactorBase):
"""买入策略可视化基础类"""
def __init__(self, wg_manager):
super(WidgetFactorBuyBase, self).__init__(wg_manager)
self.add = widgets.Button(description='添加为全局策略', layout=widgets.Layout(width='98%'),
button_style='info')
self.add.on_click(self.add_buy_factor)
self._init_widget = getattr(self,'_init_widget')
self._init_widget()
def add_buy_factor(self, bt):
"""对应按钮添加策略,构建策略字典对象factor_dict以及唯一策略描述字符串factor_desc_key"""
self.make_buy_factor_unique = getattr(self,'make_buy_factor_unique')
factor_dict, factor_desc_key = self.make_buy_factor_unique()
self.wg_manager.add_factor(factor_dict, factor_desc_key)
5、均值回归策略
class MeanReversion(WidgetFactorBuyBase):
"""
均值回归策略:
1. 均值回归:“跌下去的迟早要涨上来”
2. 均值回归的理论基于以下观测:价格的波动一般会以它的均线为中心。也就是说,当标的价格由于波动而偏离移动均线时,它将调整并重新归于均线。
3. 偏离程度:(MA-P)/MA
4. 策略:在每个调仓日进行(每月调一次仓)
4.1 计算池内股票的N日移动均线;
4.2 计算池内所有股票价格与均线的偏离度;
4.3 选取偏离度最高的num_stocks支股票并进行调仓。
"""
def _init_widget(self):
self.label_layout = widgets.Layout(width='300px', align_items='stretch')
self.widget_layout = widgets.Layout(align_items='stretch', justify_content='space-between')
self.description = widgets.Textarea(
value='均值回归策略:\n'
'1. 均值回归:“跌下去的迟早要涨上来”'
'2. 均值回归的理论基于以下观测:价格的波动一般会以它的均线为中心。也就是说,当标的价格由于波动而偏离移动均线时,它将调整并重新归于均线。'
'3. 偏离程度:(MA-P)/MA'
'4. 策略:在每个调仓日进行(每月调一次仓)'
'4.1 计算池内股票的N日移动均线; '
'4.2 计算池内所有股票价格与均线的偏离度;'
'4.3 选取偏离度最高的num_stocks支股票并进行调仓',
description='均值回归',
disabled=False,
layout=widgets.Layout(height='150px')
)
self.average_label = widgets.Label('默认30日均线,可手动调节数值', layout=self.label_layout)
self.average_int = widgets.IntSlider(
value=30,
min=10,
max=120,
step=1,
description='手动',
disabled=False,
orientation='horizontal',
readout=True,
readout_format='d'
)
self.auto_average = widgets.Checkbox(
value=True,
description='均线',
disabled=False
)
def average_change(change):
self.average_int.disabled = change['new']
self.average_box = widgets.VBox([self.average_label, self.average_int, self.auto_average])
self.position_label = widgets.Label('默认持仓5支,可手动调节', layout=self.label_layout)
self.position_int = widgets.IntSlider(
value=5,
min=1,
max=20,
step=1,
description='手动',
disabled=False,
orientation='horizontal',
readout=True,
readout_format='d'
)
self.auto_position = widgets.Checkbox(
value=True,
description='持仓',
disabled=False,
)
def position_change(change):
self.position_int.disabled = change['new']
self.position_box = widgets.VBox([self.position_label, self.position_int, self.auto_position])
self.widget = widgets.VBox([self.description, self.average_box, self.position_box,self.add])
def make_buy_factor_unique(self):
"""对应按钮添加双均线策略,构建策略字典对象factor_dict以及唯一策略描述字符串factor_desc_key"""
factor_dict = { 'average_int': self.average_int.value,
'auto_average': self.auto_average.value, 'position_int': self.position_int.value}
factor_desc_key = '使用均值回归策略进行回测,{}均线,持仓{}'.format(
self.average_int.value, self.position_int.value)
return factor_dict, factor_desc_key
6、双均线策略
class DoubleAverages(WidgetFactorBuyBase):
"""
双均线策略:
1. 预设两条均线:如一个ma=5,一个ma=60, 5的均线被称作快线,60的均线被称作慢线
2. 择时买入策略中当快线上穿慢线(ma5上穿ma60)称为形成金叉买点信号,买入股票
3. 自适应动态慢线,不需要输入慢线值,根据走势震荡套利空间,寻找合适的ma慢线
4. 自适应动态快线,不需要输入快线值,根据慢线以及大盘走势,寻找合适的ma快线
"""
def _init_widget(self):
self.label_layout = widgets.Layout(width='300px', align_items='stretch')
self.widget_layout = widgets.Layout(align_items='stretch', justify_content='space-between')
self.description = widgets.Textarea(
value='双均线策略:\n'
'1. 预设两条均线:如一个ma=5,一个ma=60, 5的均线被称作快线,60的均线被称作慢线'
'2. 择时买入策略中当快线上穿慢线(ma5上穿ma60)称为形成金叉买点信号,买入股票'
'3. 自适应动态慢线,不需要输入慢线值,根据走势震荡套利空间,寻找合适的ma慢线'
'4. 自适应动态快线,不需要输入快线值,根据慢线以及大盘走势,寻找合适的ma快线',
description='双均线策略:',
disabled=False,
layout=widgets.Layout(height='150px')
)
self.slow_label = widgets.Label('默认动态慢线60日均线,可手动调节数值', layout=self.label_layout)
self.slow_int = widgets.IntSlider(
value=60,
min=10,
max=120,
step=1,
description='手动',
disabled=False,
orientation='horizontal',
readout=True,
readout_format='d'
)
self.auto_slow = widgets.Checkbox(
value=True,
description='动态慢线',
disabled=False
)
def slow_change(change):
self.slow_int.disabled = change['new']
self.auto_slow.observe(slow_change, names='value')
self.slow = widgets.VBox([self.auto_slow, self.slow_int])
self.slow_box = widgets.VBox([self.slow_label, self.slow])
self.fast_label = widgets.Label('默认动态快线5日均线,可手动调节数值', layout=self.label_layout)
self.fast_int = widgets.IntSlider(
value=5,
min=1,
max=90,
step=1,
description=u'手动',
disabled=False,
orientation='horizontal',
readout=True,
readout_format='d'
)
self.auto_fast = widgets.Checkbox(
value=True,
description='动态快线',
disabled=False,
)
def fast_change(change):
self.fast_int.disabled = change['new']
self.auto_fast.observe(fast_change, names='value')
self.fast = widgets.VBox([self.auto_fast, self.fast_int])
self.fast_box = widgets.VBox([self.fast_label, self.fast])
self.widget = widgets.VBox([self.description, self.slow_box, self.fast_box,self.add], # border='solid 1px',
layout=self.widget_layout)
def make_buy_factor_unique(self):
"""对应按钮添加AbuDoubleMaBuy策略,构建策略字典对象factor_dict以及唯一策略描述字符串factor_desc_key"""
slow_int = -1 if self.auto_slow.value else self.slow_int.value
fast_int = -1 if self.auto_fast.value else self.fast_int.value
factor_dict = {'slow': slow_int, 'fast': fast_int}
factor_desc_key = '动态双均慢{}快{}买入'.format(
'动态' if slow_int == -1 else slow_int, '动态' if fast_int == -1 else fast_int)
return factor_dict, factor_desc_key
7、海龟策略
class TurtleStrategy(WidgetFactorBuyBase):
"""海龟策略"""
def _init_widget(self):
"""构建AbuFactorBuyBreak策略参数界面"""
self.label_layout = widgets.Layout(width='300px', align_items='stretch')
self.widget_layout = widgets.Layout(align_items='stretch', justify_content='space-between')
self.description = widgets.Textarea(
value='海龟向上趋势突破买入策略:\n'
'趋势突破定义为当天收盘价格超过N天内的最高价,超过最高价格作为买入信号买入股票持有',
description='海龟策略',
disabled=False,
layout=widgets.Layout(height='150px')
)
self.xd_label = widgets.Label('突破周期参数:比如21,30,42天....突破', layout=self.label_layout)
self.xd = widgets.IntSlider(
value=21,
min=3,
max=120,
step=1,
description='周期',
disabled=False,
orientation='horizontal',
readout=True,
readout_format='d'
)
self.xd_box = widgets.VBox([self.xd_label, self.xd])
self.widget = widgets.VBox([self.description, self.xd_box,self.add], # border='solid 1px',
layout=self.widget_layout)
def make_buy_factor_unique(self):
"""对应按钮添加AbuFactorBuyBreak策略,构建策略字典对象factor_dict以及唯一策略描述字符串factor_desc_key"""
factor_dict = {'xd': self.xd.value}
factor_desc_key = '海龟{}天趋势突破买入'.format(self.xd.value)
return factor_dict, factor_desc_key
周期’,
disabled=False,
orientation=‘horizontal’,
readout=True,
readout_format=‘d’
)
self.xd_box = widgets.VBox([self.xd_label, self.xd])
self.widget = widgets.VBox([self.description, self.xd_box,self.add], # border=‘solid 1px’,
layout=self.widget_layout)
def make_buy_factor_unique(self):
"""对应按钮添加AbuFactorBuyBreak策略,构建策略字典对象factor_dict以及唯一策略描述字符串factor_desc_key"""
factor_dict = {'xd': self.xd.value}
factor_desc_key = '海龟{}天趋势突破买入'.format(self.xd.value)
return factor_dict, factor_desc_key
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200224210956631.gif)