接上一篇文章(期货ML策略(一)数据获取),这篇文章开始构建机器学习模型。
如何构建一个机器学习模型,其最本质还是要看你需要机器学习为你做什么贡献。我这边希望能够利用机器学习帮助我预测未来一段时间上涨还是下跌,这样我就可以对应进行做多或者做空。太高频的策略一般会比较耗费手续费,太低频的策略又容易出现很多意想不到的巨额损失,因此需要设置一个合理的预测时间,这个时间我设置的是20分钟(期货是T+0操作)。通过模型预测20分钟后的涨跌情况,策略进行相应的做多或做空(有点像我之前的超短策略)。
训练一个传统的机器学习的模型主要有如下步:(1)数据获取;(2)标签制作;(3)特征构建;(4)模型选择及模型训练;(5)结果分析。对于数据获取,上一篇文章(期货ML策略(一)数据获取)已经简要的阐述过了,这篇文章将会介绍后面四步。
因为我是需要知道20分钟后的涨跌情况。所以我的标签制作规则为:如果20分钟后的价格大于等于当前价格,标签为1否则为0。但是考虑到实际情况中,当我们计算完涨跌之后,往往并不能马上买入,因此这边我加了1分钟的延迟时间,即20分钟后的价格大于等于下一分钟的价格,标签为1否则为0。
核心代码如下:
def get_label(df_min):
# 20 分钟后的涨幅,且日内交易
delta_t = 20
df_label = []
tmp_col = ['ts_code', 'trade_date', 'datetime', 'mean', 'low', 'high']
for i, g in tqdm(df_min[tmp_col].groupby(['ts_code', 'trade_date'])):
g = g.sort_values('datetime', ascending=True).reset_index(drop=True)
g['mean_shift1'] = g['mean'].shift(-1)
g['mean_latter'] = g['mean'].shift(-delta_t)
g['lowest'] = g.rolling(delta_t)['low'].min().shift(-delta_t)
g['highest'] = g.rolling(delta_t)['high'].max().shift(-delta_t)
df_label.append(g)
df_label = pd.concat(df_label)
return df_label
if os.path.exists(os.path.join(base_path, 'label.csv')):
df_label = pd.read_csv(os.path.join(base_path, 'label.csv'))
else:
df_label = get_label(df_min)
df_label.to_csv(os.path.join(base_path, 'label.csv'), index=None)
df_label['datetime'] = pd.to_datetime(df_label['datetime'], infer_datetime_format=True)
# df_label['label'] = df_label['mean_latter'] >= df_label['mean']
df_label['label'] = df_label['mean_latter'] >= df_label['mean_shift1']
df_label['return'] = (df_label['mean_latter'] - df_label['mean_shift1']) / df_label['mean_shift1']
特征构建就完全取决于大家对于期货市场的理解了。这边我主要构建了两方面的特征,一个是指数特征,一个是主力合约各自特征(github上的代码我剔除了部分特征)。大家如果有兴趣的话可以在我的基础上尝试添加其他新特征。
核心代码如下:
指数特征(指数涨跌幅偏移特征):
index_df = index_df.sort_values(['datetime'], ascending=[True])
index_df.columns = ['datetime', 'index_rate']
for day in [1, 2, 3, 4]:
index_df ['index_rate_shift'+str(day)] = (index_df['index_rate'].shift(day) - index_df['index_rate']) / index_df['index_rate']
index_df['datetime'] = pd.to_datetime(index_df['datetime'], infer_datetime_format=True)
主力合约各自特征(价格和成交量的统计特征):
col = ['ts_code', 'datetime']
for day in [5, 10, 15, 30, 60]:
g['max_' + str(day)] = g['high'].rolling(day).max()
g['min_' + str(day)] = g['low'].rolling(day).min()
g['mean_' + str(day)] = g['mean'].rolling(day).mean()
g['volume_' + str(day)] = g['volume'].rolling(day).sum()
col.append('max_' + str(day))
col.append('min_' + str(day))
col.append('mean_' + str(day))
col.append('volume_' + str(day))
# 成交量再处理
for day in [5, 10, 15, 30, 60]:
g['max_' + str(day)] = (g['mean'] - g['max_' + str(day)]) / g['max_' + str(day)]
g['min_' + str(day)] = (g['mean'] - g['min_' + str(day)]) / g['min_' + str(day)]
g['mean_' + str(day)] = (g['mean'] - g['mean_' + str(day)]) / g['mean_' + str(day)]
g['volume_' + str(day)] = (g['volume'] - g['volume_' + str(day)]) / g['volume_' + str(day)]
for day in [1, 2, 3]:
g['high_shift' + str(day)] = (g['high'].shift(day) - g['mean']) / g['mean']
g['low_shift' + str(day)] = (g['low'].shift(day) - g['mean']) / g['mean']
g['mean_shift' + str(day)] = (g['mean'].shift(day) - g['mean']) / g['mean']
g['high_shift2' + str(day)] = (g['high'].shift(day) - g['pre_close']) / g['pre_close']
g['low_shift2' + str(day)] = (g['low'].shift(day) - g['pre_close']) / g['pre_close']
g['mean_shift2' + str(day)] = (g['mean'].shift(day) - g['pre_close']) / g['pre_close']
col.append('high_shift' + str(day))
col.append('low_shift' + str(day))
col.append('mean_shift' + str(day))
col.append('high_shift2' + str(day))
col.append('low_shift2' + str(day))
col.append('mean_shift2' + str(day))
模型选择上,我还是选择了LightGBM,这金融市场的预测问题上我还没尝试过XGBoost等之类的模型,不过我还是比较相信LGB。
训练时间:20190101->20190901(训练集中)
测试时间:20190902->20200501(测试集中剔除了涨停或跌停的样本)
在训练集中,我剔除了涨幅为0的样本;在测试集中我剔除了涨停或跌停的样本(在涨停或跌停做多做空风险过大)。期货的涨停和跌停规则比较复杂,这边如果1分钟内的最高价等于最低级,并且等于当日最高价或当日最低价我认为是涨停或跌停。
核心代码如下:
涨停或跌停:
# 假设 分钟行情的最低价、最高价、都等于当日最高价视为涨停或跌停
df_min['limit'] = 0
idx = (df_min['low']==df_min['high'])&((df_min['low']==df_min['today_low'])|(df_min['high']==df_min['today_high']))
df_min.loc[idx, 'limit'] = 1
模型训练:
label_col = 'label'
trn_date_min = 20190101
trn_date_max = 20190901
test_date_min = 20190902
test_date_max = 20200501
# 训练集中需要剔除涨幅为0(可能为涨停或跌停)
trn_idx = (df_min['trade_date'] >= trn_date_min) & (df_min['trade_date'] <= trn_date_max) & (df_min['return']!=0)
test_idx = (df_min['trade_date'] >= test_date_min) & (df_min['trade_date'] <= test_date_max) & (df_min['limit']==0)
trn = df_min[trn_idx][feature_col].values
trn_label = df_min[trn_idx][label_col].values
test = df_min[test_idx][feature_col].values
test_label = df_min[test_idx][label_col].values
param = {'num_leaves': 31,
'min_data_in_leaf': 20,
'objective': 'binary',
'learning_rate': 0.06,
"boosting": "gbdt",
"metric": 'None',
"verbosity": -1}
trn_data = lgb.Dataset(trn, trn_label)
clf = lgb.train(param, trn_data, 500, verbose_eval=300)
假设设置预测涨跌概率大于0.8,对主力合约做多。结果如下图:
从结果上来看,预测上涨的准确率已超过50%,但收益率达到了万分之7。我百度了一下,这个万分之7的收益率基本能cover手续费了,理论上是能赚钱的。
假设设置预测涨跌概率小于0.2,对主力合约做空。结果如下图:
从结果上来看,预测下跌的准确率也超过了50%,并且平均收益率达到了千分之3。但预测出来的样本数很少。还没有想通为什么两种情况预测出来的情况差异这么大,理论上来说应该是差不多的。
从上面的结果显示,机器学习在期货市场上还是很有用的。我这边只是简单的做了一个demo,如果深究进去相信效果只会更好。下一篇文章将会利用预测出来的结果进行回测分析。
对量化、数据挖掘、深度学习感兴趣的可以关注公众号,本人不定期分享有关这些方面的研究。
个人知乎:
https://www.zhihu.com/people/e-zhe-shi-wo/activities