版本 v1.0
### 深度学习策略的交易规则
### 策略构建步骤
### 策略的实现
在画布左侧模块列表中依次拖入输入层模块、Reshape层模块、Conv2D层模块、Reshape层模块、LSTM层模块、Dropout层模块和全连接层模块(两组),构成深度学习网络构架,
最后通过“构建(深度学习)”模块组装各层。这里需要注意:
输入层的shape参数是 窗口滚动数据集的大小 X 因子数量 , 本例为 50 行 X 5个因子
ReShape层的参数是 窗口滚动数据集的大小 X 因子数量 X 1 ,本例为 50 行 X 5个因子 X1
Conv2D层中的 kernel_size参数是滑动窗口的尺寸,本例中使用 3行 X 5列 的窗口, 每次滑动的步长为 1行 X 1列 , 卷积核数目为32,这里的窗口设置决定了后面ReShape层的参数
ReShape层中的target_shape 参数,这是由 窗口滚动数据集 X 因子数量 和 Conv2D层中设置的窗口尺寸以及步长决定的。本例中 50行 X 5因子 的输入数据,使用 3行 X5列 的窗口滑动取数据,
每次移动1行,共计可以得到48次数据(即可以通过滑动3行 X 5列的窗口48次来获取完整的数据),因此target_shape= 48 X 卷积核数32
LSTM层的输出空间维度设置为卷积核数32,并设置激活函数
Dropout层是防止过度拟合采用的主动裁剪数据技术,这里设置rate 为0.8
全连接层共两层,第一层的输出空间维度与LSTM的输出维度保持一致为32,第二层将第一层的32维数据转变为1维数据输出,即获取预测的label值,此例为0到1之间的连续值,可以认为是上涨的概率。
如果当日预测的上涨概率大于0.5,则保持持仓或买入
如果当日预测的上涨概率小于0.5,则卖出股票或保持空仓。
通过 trade 模块中的初始化函数定义交易手续费和滑点,通过 context.prediction 获取每日的上涨概率预测结果;
通过 trade 模块中的主函数(handle函数)查看每日的买卖交易信号,按照买卖原则执行相应的买入/卖出操作。
可视化策略实现如下:
# 本代码由可视化策略环境自动生成 2022年9月15日 15:12
# 本代码单元只能在可视化模式下编辑。您也可以拷贝代码,粘贴到新建的代码单元或者策略,然后修改。
# 用户的自定义层需要写到字典中,比如
# {
# "MyLayer": MyLayer
# }
m6_custom_objects_bigquant_run = {
}
# Python 代码入口函数,input_1/2/3 对应三个输入端,data_1/2/3 对应三个输出端
def m2_run_bigquant_run(input_1, input_2, input_3):
test_data = input_2.read_pickle()
pred_label = input_1.read_pickle()
pred_result = pred_label.reshape(pred_label.shape[0])
dt = input_3.read_df()['date'][-1*len(pred_result):]
pred_df = pd.Series(pred_result, index=dt)
ds = DataSource.write_df(pred_df)
return Outputs(data_1=ds)
# 后处理函数,可选。输入是主函数的输出,可以在这里对数据做处理,或者返回更友好的outputs数据格式。此函数输出不会被缓存。
def m2_post_run_bigquant_run(outputs):
return outputs
# 回测引擎:初始化函数,只执行一次
def m1_initialize_bigquant_run(context):
# 加载预测数据
context.prediction = context.options['data'].read_df()
# 系统已经设置了默认的交易手续费和滑点,要修改手续费可使用如下函数
context.set_commission(PerOrder(buy_cost=0.0003, sell_cost=0.0013, min_cost=5))
# 回测引擎:每日数据处理函数,每天执行一次
def m1_handle_data_bigquant_run(context, data):
# 按日期过滤得到今日的预测数据
try:
prediction = context.prediction[data.current_dt.strftime('%Y-%m-%d')]
except KeyError as e:
return
instrument = context.instruments[0]
sid = context.symbol(instrument)
cur_position = context.portfolio.positions[sid].amount
# 交易逻辑
if prediction > 0.5 and cur_position == 0:
context.order_target_percent(context.symbol(instrument), 1)
print(data.current_dt, '买入!')
elif prediction < 0.5 and cur_position > 0:
context.order_target_percent(context.symbol(instrument), 0)
print(data.current_dt, '卖出!')
# 回测引擎:准备数据,只执行一次
def m1_prepare_bigquant_run(context):
pass
# 回测引擎:每个单位时间开始前调用一次,即每日开盘前调用一次。
def m1_before_trading_start_bigquant_run(context, data):
pass
g = T.Graph({
'm3': 'M.dl_layer_input.v1',
'm3.shape': '50,5',
'm3.batch_shape': '',
'm3.dtype': 'float32',
'm3.sparse': False,
'm3.name': '',
'm13': 'M.dl_layer_reshape.v1',
'm13.inputs': T.Graph.OutputPort('m3.data'),
'm13.target_shape': '50,5,1',
'm13.name': '',
'm14': 'M.dl_layer_conv2d.v1',
'm14.inputs': T.Graph.OutputPort('m13.data'),
'm14.filters': 32,
'm14.kernel_size': '3,5',
'm14.strides': '1,1',
'm14.padding': 'valid',
'm14.data_format': 'channels_last',
'm14.dilation_rate': '1,1',
'm14.activation': 'relu',
'm14.use_bias': True,
'm14.kernel_initializer': 'glorot_uniform',
'm14.bias_initializer': 'Zeros',
'm14.kernel_regularizer': 'None',
'm14.kernel_regularizer_l1': 0,
'm14.kernel_regularizer_l2': 0,
'm14.bias_regularizer': 'None',
'm14.bias_regularizer_l1': 0,
'm14.bias_regularizer_l2': 0,
'm14.activity_regularizer': 'None',
'm14.activity_regularizer_l1': 0,
'm14.activity_regularizer_l2': 0,
'm14.kernel_constraint': 'None',
'm14.bias_constraint': 'None',
'm14.name': '',
'm15': 'M.dl_layer_reshape.v1',
'm15.inputs': T.Graph.OutputPort('m14.data'),
'm15.target_shape': '48,32',
'm15.name': '',
'm4': 'M.dl_layer_lstm.v1',
'm4.inputs': T.Graph.OutputPort('m15.data'),
'm4.units': 32,
'm4.activation': 'tanh',
'm4.recurrent_activation': 'hard_sigmoid',
'm4.use_bias': True,
'm4.kernel_initializer': 'glorot_uniform',
'm4.recurrent_initializer': 'Orthogonal',
'm4.bias_initializer': 'Ones',
'm4.unit_forget_bias': True,
'm4.kernel_regularizer': 'None',
'm4.kernel_regularizer_l1': 0,
'm4.kernel_regularizer_l2': 0,
'm4.recurrent_regularizer': 'None',
'm4.recurrent_regularizer_l1': 0,
'm4.recurrent_regularizer_l2': 0,
'm4.bias_regularizer': 'None',
'm4.bias_regularizer_l1': 0,
'm4.bias_regularizer_l2': 0,
'm4.activity_regularizer': 'None',
'm4.activity_regularizer_l1': 0,
'm4.activity_regularizer_l2': 0,
'm4.kernel_constraint': 'None',
'm4.recurrent_constraint': 'None',
'm4.bias_constraint': 'None',
'm4.dropout': 0,
'm4.recurrent_dropout': 0,
'm4.return_sequences': False,
'm4.implementation': '2',
'm4.name': '',
'm11': 'M.dl_layer_dropout.v1',
'm11.inputs': T.Graph.OutputPort('m4.data'),
'm11.rate': 0.4,
'm11.noise_shape': '',
'm11.name': '',
'm10': 'M.dl_layer_dense.v1',
'm10.inputs': T.Graph.OutputPort('m11.data'),
'm10.units': 32,
'm10.activation': 'tanh',
'm10.use_bias': True,
'm10.kernel_initializer': 'glorot_uniform',
'm10.bias_initializer': 'Zeros',
'm10.kernel_regularizer': 'None',
'm10.kernel_regularizer_l1': 0,
'm10.kernel_regularizer_l2': 0,
'm10.bias_regularizer': 'None',
'm10.bias_regularizer_l1': 0,
'm10.bias_regularizer_l2': 0,
'm10.activity_regularizer': 'None',
'm10.activity_regularizer_l1': 0,
'm10.activity_regularizer_l2': 0,
'm10.kernel_constraint': 'None',
'm10.bias_constraint': 'None',
'm10.name': '',
'm12': 'M.dl_layer_dropout.v1',
'm12.inputs': T.Graph.OutputPort('m10.data'),
'm12.rate': 0.8,
'm12.noise_shape': '',
'm12.name': '',
'm9': 'M.dl_layer_dense.v1',
'm9.inputs': T.Graph.OutputPort('m12.data'),
'm9.units': 1,
'm9.activation': 'sigmoid',
'm9.use_bias': True,
'm9.kernel_initializer': 'glorot_uniform',
'm9.bias_initializer': 'Zeros',
'm9.kernel_regularizer': 'None',
'm9.kernel_regularizer_l1': 0,
'm9.kernel_regularizer_l2': 0,
'm9.bias_regularizer': 'None',
'm9.bias_regularizer_l1': 0,
'm9.bias_regularizer_l2': 0,
'm9.activity_regularizer': 'None',
'm9.activity_regularizer_l1': 0,
'm9.activity_regularizer_l2': 0,
'm9.kernel_constraint': 'None',
'm9.bias_constraint': 'None',
'm9.name': '',
'm5': 'M.dl_model_init.v1',
'm5.inputs': T.Graph.OutputPort('m3.data'),
'm5.outputs': T.Graph.OutputPort('m9.data'),
'm8': 'M.input_features.v1',
'm8.features': """(close_0/close_1-1)*10
(high_0/high_1-1)*10
(low_0/low_1-1)*10
(open_0/open_1-1)*10
(volume_0/volume_1-1)*10""",
'm24': 'M.instruments.v2',
'm24.start_date': '2017-06-02',
'm24.end_date': '2017-10-30',
'm24.market': 'CN_STOCK_A',
'm24.instrument_list': '600009.SHA',
'm24.max_count': 0,
'm21': 'M.advanced_auto_labeler.v2',
'm21.instruments': T.Graph.OutputPort('m24.data'),
'm21.label_expr': """# #号开始的表示注释
# 0. 每行一个,顺序执行,从第二个开始,可以使用label字段
# 1. 可用数据字段见 https://bigquant.com/docs/develop/datasource/deprecated/history_data.html
# 添加benchmark_前缀,可使用对应的benchmark数据
# 2. 可用操作符和函数见 `表达式引擎 <https://bigquant.com/docs/develop/bigexpr/usage.html>`_
# 计算收益:5日收盘价(作为卖出价格)除以明日开盘价(作为买入价格)
where(shift(close, -10) / close -1>0,1,0)
# 过滤掉一字涨停的情况 (设置label为NaN,在后续处理和训练中会忽略NaN的label)
where(shift(high, -1) == shift(low, -1), NaN, label)
""",
'm21.start_date': '',
'm21.end_date': '',
'm21.benchmark': '000300.SHA',
'm21.drop_na_label': True,
'm21.cast_label_int': True,
'm21.user_functions': {},
'm22': 'M.general_feature_extractor.v7',
'm22.instruments': T.Graph.OutputPort('m24.data'),
'm22.features': T.Graph.OutputPort('m8.data'),
'm22.start_date': '',
'm22.end_date': '',
'm22.before_start_days': 90,
'm23': 'M.derived_feature_extractor.v3',
'm23.input_data': T.Graph.OutputPort('m22.data'),
'm23.features': T.Graph.OutputPort('m8.data'),
'm23.date_col': 'date',
'm23.instrument_col': 'instrument',
'm23.drop_na': False,
'm23.remove_extra_columns': False,
'm23.user_functions': {},
'm17': 'M.join.v3',
'm17.data1': T.Graph.OutputPort('m21.data'),
'm17.data2': T.Graph.OutputPort('m23.data'),
'm17.on': 'date',
'm17.how': 'inner',
'm17.sort': True,
'm18': 'M.dropnan.v1',
'm18.input_data': T.Graph.OutputPort('m17.data'),
'm25': 'M.dl_convert_to_bin.v2',
'm25.input_data': T.Graph.OutputPort('m18.data'),
'm25.features': T.Graph.OutputPort('m8.data'),
'm25.window_size': 50,
'm25.feature_clip': 5,
'm25.flatten': False,
'm25.window_along_col': '',
'm6': 'M.dl_model_train.v1',
'm6.input_model': T.Graph.OutputPort('m5.data'),
'm6.training_data': T.Graph.OutputPort('m25.data'),
'm6.optimizer': 'Adam',
'm6.loss': 'binary_crossentropy',
'm6.metrics': 'accuracy',
'm6.batch_size': 2048,
'm6.epochs': 10,
'm6.custom_objects': m6_custom_objects_bigquant_run,
'm6.n_gpus': 1,
'm6.verbose': '1:输出进度条记录',
'm28': 'M.instruments.v2',
'm28.start_date': T.live_run_param('trading_date', '2019-02-11'),
'm28.end_date': T.live_run_param('trading_date', '2019-08-01'),
'm28.market': 'CN_STOCK_A',
'm28.instrument_list': '600009.SHA',
'm28.max_count': 0,
'm16': 'M.general_feature_extractor.v7',
'm16.instruments': T.Graph.OutputPort('m28.data'),
'm16.features': T.Graph.OutputPort('m8.data'),
'm16.start_date': '',
'm16.end_date': '',
'm16.before_start_days': 90,
'm26': 'M.derived_feature_extractor.v3',
'm26.input_data': T.Graph.OutputPort('m16.data'),
'm26.features': T.Graph.OutputPort('m8.data'),
'm26.date_col': 'date',
'm26.instrument_col': 'instrument',
'm26.drop_na': False,
'm26.remove_extra_columns': False,
'm26.user_functions': {},
'm20': 'M.dropnan.v1',
'm20.input_data': T.Graph.OutputPort('m26.data'),
'm27': 'M.dl_convert_to_bin.v2',
'm27.input_data': T.Graph.OutputPort('m20.data'),
'm27.features': T.Graph.OutputPort('m8.data'),
'm27.window_size': 50,
'm27.feature_clip': 5,
'm27.flatten': False,
'm27.window_along_col': '',
'm7': 'M.dl_model_predict.v1',
'm7.trained_model': T.Graph.OutputPort('m6.data'),
'm7.input_data': T.Graph.OutputPort('m27.data'),
'm7.batch_size': 10240,
'm7.n_gpus': 0,
'm7.verbose': '2:每个epoch输出一行记录',
'm2': 'M.cached.v3',
'm2.input_1': T.Graph.OutputPort('m7.data'),
'm2.input_2': T.Graph.OutputPort('m27.data'),
'm2.input_3': T.Graph.OutputPort('m20.data'),
'm2.run': m2_run_bigquant_run,
'm2.post_run': m2_post_run_bigquant_run,
'm2.input_ports': '',
'm2.params': '{}',
'm2.output_ports': '',
'm1': 'M.trade.v4',
'm1.instruments': T.Graph.OutputPort('m28.data'),
'm1.options_data': T.Graph.OutputPort('m2.data_1'),
'm1.start_date': '',
'm1.end_date': '',
'm1.initialize': m1_initialize_bigquant_run,
'm1.handle_data': m1_handle_data_bigquant_run,
'm1.prepare': m1_prepare_bigquant_run,
'm1.before_trading_start': m1_before_trading_start_bigquant_run,
'm1.volume_limit': 0.025,
'm1.order_price_field_buy': 'open',
'm1.order_price_field_sell': 'close',
'm1.capital_base': 1000000,
'm1.auto_cancel_non_tradable_orders': True,
'm1.data_frequency': 'daily',
'm1.price_type': '真实价格',
'm1.product_type': '股票',
'm1.plot_charts': True,
'm1.backtest_only': False,
'm1.benchmark': '000300.HIX',
})
# g.run({})
def m19_run_bigquant_run(
bq_graph,
inputs,
trading_days_market='CN', # 使用那个市场的交易日历, TODO
train_instruments_mid='m24', # 训练数据 证券代码列表 模块id
test_instruments_mid='m28', # 测试数据 证券代码列表 模块id
predict_mid='m2', # 预测 模块id
trade_mid='m1', # 回测 模块id
start_date='2016-01-01', # 数据开始日期
end_date=T.live_run_param('trading_date', '2018-01-01'), # 数据结束日期
train_update_days=250, # 更新周期,按交易日计算,每多少天更新一次
train_update_days_for_live=None, #模拟实盘模式下的更新周期,按交易日计算,每多少天更新一次。如果需要在模拟实盘阶段使用不同的模型更新周期,可以设置这个参数
train_data_min_days=250, # 最小数据天数,按交易日计算,所以第一个滚动的结束日期是 从开始日期到开始日期+最小数据天数
train_data_max_days=250, # 最大数据天数,按交易日计算,0,表示没有限制,否则每一个滚动的开始日期=max(此滚动的结束日期-最大数据天数, 开始日期
rolling_count_for_live=1, #实盘模式下滚动次数,模拟实盘模式下,取最后多少次滚动。一般在模拟实盘模式下,只用到最后一次滚动训练的模型,这里可以设置为1;如果你的滚动训练数据时间段很短,以至于期间可能没有训练数据,这里可以设置大一点。0表示没有限制
):
def merge_datasources(input_1):
df_list = [ds[0].read_df().set_index('date').loc[ds[1]:].reset_index() for ds in input_1]
df = pd.concat(df_list)
instrument_data = {
'start_date': df['date'].min().strftime('%Y-%m-%d'),
'end_date': df['date'].max().strftime('%Y-%m-%d'),
'instruments': list(set(df['instrument'])),
}
return Outputs(data=DataSource.write_df(df), instrument_data=DataSource.write_pickle(instrument_data))
def gen_rolling_dates(trading_days_market, start_date, end_date, train_update_days, train_update_days_for_live, train_data_min_days, train_data_max_days, rolling_count_for_live):
# 是否实盘模式
tdays = list(D.trading_days(market=trading_days_market, start_date=start_date, end_date=end_date)['date'])
is_live_run = T.live_run_param('trading_date', None) is not None
if is_live_run and train_update_days_for_live:
train_update_days = train_update_days_for_live
rollings = []
train_end_date = train_data_min_days
while train_end_date < len(tdays):
if train_data_max_days is not None and train_data_max_days > 0:
train_start_date = max(train_end_date - train_data_max_days, 0)
else:
train_start_date = 0
rollings.append({
'train_start_date': tdays[train_start_date].strftime('%Y-%m-%d'),
'train_end_date': tdays[train_end_date - 1].strftime('%Y-%m-%d'),
'test_start_date': tdays[train_end_date].strftime('%Y-%m-%d'),
'test_end_date': tdays[min(train_end_date + train_update_days, len(tdays)) - 1].strftime('%Y-%m-%d'),
})
train_end_date += train_update_days
if not rollings:
raise Exception('没有滚动需要执行,请检查配置')
if is_live_run and rolling_count_for_live:
rollings = rollings[-rolling_count_for_live:]
return rollings
g = bq_graph
rolling_dates = gen_rolling_dates(
trading_days_market, start_date, end_date, train_update_days, train_update_days_for_live, train_data_min_days, train_data_max_days, rolling_count_for_live)
# 训练和预测
results = []
for rolling in rolling_dates:
parameters = {}
# 先禁用回测
parameters[trade_mid + '.__enabled__'] = False
parameters[train_instruments_mid + '.start_date'] = rolling['train_start_date']
parameters[train_instruments_mid + '.end_date'] = rolling['train_end_date']
parameters[test_instruments_mid + '.start_date'] = rolling['test_start_date']
parameters[test_instruments_mid + '.end_date'] = rolling['test_end_date']
# print('------ rolling_train:', parameters)
results.append(g.run(parameters))
# 合并预测结果并回测
mx = M.cached.v3(run=merge_datasources, input_1=[[result[predict_mid].predictions, result[test_instruments_mid].data.read_pickle()['start_date']] for result in results])
parameters = {}
parameters['*.__enabled__'] = False
parameters[trade_mid + '.__enabled__'] = True
parameters[trade_mid + '.instruments'] = mx.instrument_data
parameters[trade_mid + '.options_data'] = mx.data
trade = g.run(parameters)
return {'rollings': results, 'trade': trade}
m19 = M.hyper_rolling_train.v1(
run=m19_run_bigquant_run,
run_now=True,
bq_graph=g
)