598. Optiver - Trading at the Close | optiver-trading-at-the-close
感谢Optiver和Kaggle举办这次精彩的金融竞赛。感谢优秀的notebook和讨论,我学到了很多。我非常高兴能够第二次solo夺冠!😃😀😀
我的最终模型(CV/私有榜单得分 5.8117/5.4030)是CatBoost(5.8240/5.4165)、GRU(5.8481/5.4259)和Transformer(5.8619/5.4296)的组合,权重分别为0.5、0.3、0.2,这些权重来自验证集的搜索结果。这些模型共享相同的300个特征。
此外,在线学习(OL)和后处理(PP)在我的最终提交中也起到了重要作用。
| 模型名称 | 验证集(无PP) | 验证集(有PP) | 测试集(无OL,有PP) | 测试集(一次OL,有PP) | 测试集(五次OL,有PP) |
|---|---|---|---|---|---|
| CatBoost | 5.8287 | 5.8240 | 5.4523 | 5.4291 | 5.4165 |
| GRU | 5.8519 | 5.8481 | 5.4690 | 5.4368 | 5.4259 |
| Transformer | 5.8614 | 5.8619 | 5.4678 | 5.4493 | 5.4296 |
| GRU + Transformer | 5.8233 | 5.8220 | 5.4550 | 5.4252 | 5.4109 |
| CatBoost + GRU + Transformer | 5.8142 | 5.8117 | 5.4438 | 5.4157 | 5.4030*(超时) |
我的验证策略非常简单,使用前400天训练,最后81天作为验证集。CV得分与榜单得分非常吻合,这让我相信这场比赛不会波动太大。所以我大部分时间都专注于提升CV得分。
我的模型最终有300个特征。其中大多数是常用特征,如原始价格、中间价格、不平衡特征、滚动特征和目标历史特征。
我将介绍一些非常有帮助且其他团队尚未分享的特征。
pl.when(pl.col('seconds_in_bucket') < 300).then(0).when(pl.col('seconds_in_bucket') < 480).then(1).otherwise(2).cast(pl.Float32).alias('seconds_in_bucket_group'),
*[(pl.col(col).first() / pl.col(col)).over(['date_id', 'seconds_in_bucket_group', 'stock_id']).cast(pl.Float32).alias('{}_group_first_ratio'.format(col)) for col in base_features],
*[(pl.col(col).rolling_mean(100, min_periods=1) / pl.col(col)).over(['date_id', 'seconds_in_bucket_group', 'stock_id']).cast(pl.Float32).alias('{}_group_expanding_mean{}'.format(col, 100)) for col in base_features]
*[(pl.col(col).mean() / pl.col(col)).over(['date_id', 'seconds_in_bucket']).cast(pl.Float32).alias('{}_seconds_in_bucket_group_mean_ratio'.format(col)) for col in base_features],
*[(pl.col(col).rank(descending=True,method='ordinal') / pl.col(col).count()).over(['date_id', 'seconds_in_bucket']).cast(pl.Float32).alias('{}_seconds_in_bucket_group_rank'.format(col)) for col in base_features],
特征选择很重要,因为我们需要避免内存错误问题,并尽可能多地运行在线训练轮次。
我只是通过CatBoost模型的特征重要性来选择前300个特征。
out = out - out.mean(1, keepdim=True)
4 样本权重
我每12天重新训练一次模型,总共5次。
我认为如果采用在线学习策略,大多数团队训练GBDT时最多只能使用200个特征。因为在将历史数据与在线数据连接时,需要双倍的内存消耗。
数据加载技巧可以大大增加这个数字。为了实现这一点,你应该每天保存一个训练数据文件,并逐天加载。
数据加载技巧
def load_numpy_data(meta_data, features):
res = np.empty((len(meta_data), len(features)), dtype=np.float32)
all_date_id = sorted(meta_data['date_id'].unique())
data_index = 0
for date_id in tqdm(all_date_id):
tmp = h5py.File( '/path/to/{}.h5'.format(date_id), 'r')
tmp = np.array(tmp['data']['features'], dtype=np.float32)
res[data_index:data_index+len(tmp),:] = tmp
data_index += len(tmp)
return res
实际上,我最好的提交是在最后一次更新时超时的。如果总推理时间达到某个值,我就会跳过在线训练。
所以总共只有4次在线训练更新。我估计如果没有超时,最佳得分应该在5.400左右。
总之,我真的非常幸运!
根据评估指标的要求,减去加权平均值比平均平均值更好。
test_df['stock_weights'] = test_df['stock_id'].map(stock_weights)
test_df['target'] = test_df['target'] - (test_df['target'] * test_df['stock_weights']).sum() / test_df['stock_weights'].sum()
谢谢大家!