AI量化模型预测挑战赛

一、赛事背景

量化金融在国外已经有数十年的历程,而在国内兴起还不到十年。这是一个极具挑战的领域。 量化金融结合了数理统计、金融理论、社会学、心理学等多学科的精华,同时特别注重实践。由于市场博弈参与个体的差异性和群体效应的复杂性,量化金融极具挑战与重大的机遇的特点。

本赛事通过大数据与机器学习的方法和工具,理解市场行为的原理,通过数据分析和模型创建量化策略,采用历史数据,验证量化策略的有效性,并且通过实时数据进行评测。

二、赛事任务

给定数据集: 给定训练集(含验证集), 包括10只(不公开)股票、79个交易日的L1snapshot数据(前64个交易日为训练数据,用于训练;后15个交易日为测试数据,不能用于训练), 数据已进行规范化和隐藏处理,包括5档量/价,中间价,交易量等数据(具体可参考后续数据说明)。

预测任务:利用过往及当前数据预测未来中间价的移动方向,在数据上进行模型训练与预测

输入数据:

行情频率:3秒一个数据点(也称为1个tick的snapshot);

每个数据点包括当前最新成交价/五档量价/过去3秒内的成交金额等数据;

训练集中每个数据点包含5个预测标签的标注; 允许利用过去不超过100tick(包含当前tick)的数据,预测未来N个tick后的中间价移动方向。

预测时间跨度:5、10、20、40、60个tick,5个预测任务;

即在t时刻,分别预测t+5tick,t+10tick,t+20tick,t+40tick,t+60tick以后: 最新中间价相较t时刻的中间价:下跌/不变/上涨。

三、评审规则

1.数据说明

中间价说明:

  - 当买一卖一均不为0时,

  - 当有一方为0时,中间价取不为0的价格

  - 否则置为NA

价格移动方向标注说明:

  - 和通常定义不同,为了计算方便,这里以涨跌幅为基准

  - 认定方法:

   - 若N个tick之后的价格较当前tick价格的涨跌幅上升超过a,则认为上涨,标注为2;

   - 若下降幅度超过a,则认为下跌,标注为0;

   - 否则认为价格不变,标注为1

  - 计算:

   -

   - 其中

   -当N=5,10时,a = 0.0005

   -当N=20,40,60时,a = 0.001

字段含义说明
date日期sequantial标号:既保留跨标的的可比性,也隐去实际时间
time时间戳保留实际时间戳,3s一档行情
sym标的(仅序号)
close最新价/收盘价以涨跌幅表示
amount_delta成交量变化从上个tick到当前tick发生的成交金额
n_midprice中间价标准化后的中间价,以涨跌幅表示
n_bid1买一价标准化后的买一价,以下类似
n_bsize1买一量
n_bid2买二价
n_bsize2买二量
n_bid3买三价
n_bsize3买三量
n_bid4买四价
n_bsize4买四量
n_bid5买五价
n_bsize5买五量
n_ask1卖一价
n_asize1卖一量
n_ask2卖二价
n_asize2卖二量
n_ask3卖三价
n_asize3卖三量
n_ask4卖四价
n_asize4卖四量
n_ask5卖五价
n_asize5卖五量
label55tick价格移动方向5tick之后中间价相对于当前tick的移动方向,0为下跌,1为不变,2为上涨
label1010tick价格移动方向
label2020tick价格移动方向
label4040tick价格移动方向
label6060tick价格移动方向

2.评估指标

本模型依据提交的结果文件,采用macro-F1 score进行评价,取label_5, label_10, label_20, label_40, label_60五项中的最高分作为最终得分。

四、代码详解

使用设备:AI Studio BML Codelab V100

numpy用于数值运算

pandas用于数据处理

catboost库中的CatBoostClassifier,用于CatBoost分类算法

sklearn.model_selection中的各种函数和类,用于交叉验证

sklearn.metrics中的各种指标,用于评估模型性能(例如准确率、F1分数、ROC AUC分数、对数损失、均方对数误差)

其他库,如tqdm用于显示进度条,sys和os用于系统操作,gc用于垃圾回收,argparse用于解析命令行参数,warnings用于处理警告。 设置警告过滤器,以忽略警告信息

!pip install catboost -i https://pypi.tuna.tsinghua.edu.cn/simple  # 下载包

# 导入必要的库
import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
from catboost import CatBoostClassifier
# import xgboost as xgb
# import lightgbm as lgb
from sklearn.model_selection import StratifiedKFold, KFold, GroupKFold
from sklearn.metrics import accuracy_score, f1_score, roc_auc_score, log_loss, mean_squared_log_error
import tqdm, sys, os, gc, argparse, warnings
warnings.filterwarnings('ignore')
# 读取数据
path = 'data/data233687/dataset/'

train_files = os.listdir(path+'train')
train_files = train_files[:1000]
train_df = pd.DataFrame()
for filename in tqdm.tqdm(train_files):
    tmp = pd.read_csv(path+'train/'+filename)
    tmp['file'] = filename
    train_df = pd.concat([train_df, tmp], axis=0, ignore_index=True)

test_files = os.listdir(path+'test')
test_df = pd.DataFrame()
for filename in tqdm.tqdm(test_files):
    tmp = pd.read_csv(path+'test/'+filename)
    tmp['file'] = filename
    test_df = pd.concat([test_df, tmp], axis=0, ignore_index=True)

特征工程:

主要构建了当前时间特征、历史平移特征、差分特征、和窗口统计特征;

具体说明如下:

(1)当前时间特征:围绕买卖价格和买卖量进行构建,暂时只构建买一卖一和买二卖二相关特征,进行优化时可以加上其余买卖信息;

(2)历史平移特征:通过历史平移获取上个阶段的信息;

(3)差分特征:可以帮助获取相邻阶段的增长差异,描述数据的涨减变化情况。在此基础上还可以构建相邻数据比值变化、二阶差分等;

(4)窗口统计特征:窗口统计可以构建不同的窗口大小,然后基于窗口范围进统计均值、最大值、最小值、中位数、方差的信息,可以反映最近阶段数据的变化情况。

# 时间相关特征
train_df['hour'] = train_df['time'].apply(lambda x:int(x.split(':')[0]))
test_df['hour'] = test_df['time'].apply(lambda x:int(x.split(':')[0]))

train_df['minute'] = train_df['time'].apply(lambda x:int(x.split(':')[1]))
test_df['minute'] = test_df['time'].apply(lambda x:int(x.split(':')[1]))

# 为了保证时间顺序的一致性,故进行排序
train_df = train_df.sort_values(['file', 'time'])
test_df = test_df.sort_values(['file', 'time'])

# 当前时间特征
# 围绕买卖价格和买卖量进行构建
# 暂时只构建买一卖一和买二卖二相关特征,进行优化时可以加上其余买卖信息
# 计算wap1:(买一价 * 买一量 + 卖一价 * 卖一量) / (买一量 + 卖一量)
train_df['wap1'] = (train_df['n_bid1'] * train_df['n_bsize1'] + train_df['n_ask1'] * train_df['n_asize1']) / (train_df['n_bsize1'] + train_df['n_asize1'])
test_df['wap1'] = (test_df['n_bid1'] * test_df['n_bsize1'] + test_df['n_ask1'] * test_df['n_asize1']) / (test_df['n_bsize1'] + test_df['n_asize1'])

# 计算wap2:(买二价 * 买二量 + 卖二价 * 卖二量) / (买二量 + 卖二量)
train_df['wap2'] = (train_df['n_bid2'] * train_df['n_bsize2'] + train_df['n_ask2'] * train_df['n_asize2']) / (train_df['n_bsize2'] + train_df['n_asize2'])
test_df['wap2'] = (test_df['n_bid2'] * test_df['n_bsize2'] + test_df['n_ask2'] * test_df['n_asize2']) / (test_df['n_bsize2'] + test_df['n_asize2'])

# 计算wap_balance:wap1 与 wap2 之间的差值的绝对值
train_df['wap_balance'] = abs(train_df['wap1'] - train_df['wap2'])
# 计算price_spread:(卖一价 - 买一价) / ((卖一价 + 买一价) / 2)
train_df['price_spread'] = (train_df['n_ask1'] - train_df['n_bid1']) / ((train_df['n_ask1'] + train_df['n_bid1']) / 2)
# 计算bid_spread:买一价 - 买二价
train_df['bid_spread'] = train_df['n_bid1'] - train_df['n_bid2']
# 计算ask_spread:卖一价 - 卖二价
train_df['ask_spread'] = train_df['n_ask1'] - train_df['n_ask2']
# 计算total_volume:买一量 + 买二量 + 卖一量 + 卖二量
train_df['total_volume'] = (train_df['n_asize1'] + train_df['n_asize2']) + (train_df['n_bsize1'] + train_df['n_bsize2'])
# 计算volume_imbalance:(卖一量 + 卖二量) - (买一量 + 买二量) 的绝对值
train_df['volume_imbalance'] = abs((train_df['n_asize1'] + train_df['n_asize2']) - (train_df['n_bsize1'] + train_df['n_bsize2']))

# 对测试集进行同样的特征构建
test_df['wap_balance'] = abs(test_df['wap1'] - test_df['wap2'])
test_df['price_spread'] = (test_df['n_ask1'] - test_df['n_bid1']) / ((test_df['n_ask1'] + test_df['n_bid1']) / 2)
test_df['bid_spread'] = test_df['n_bid1'] - test_df['n_bid2']
test_df['ask_spread'] = test_df['n_ask1'] - test_df['n_ask2']
test_df['total_volume'] = (test_df['n_asize1'] + test_df['n_asize2']) + (test_df['n_bsize1'] + test_df['n_bsize2'])
test_df['volume_imbalance'] = abs((test_df['n_asize1'] + test_df['n_asize2']) - (test_df['n_bsize1'] + test_df['n_bsize2']))

# 历史平移
# 获取历史信息
# 对于每个特征,构建1、5、10、20、40和60个时间步之前的历史值,并保存到新的特征中
for val in ['wap1', 'wap2', 'wap_balance', 'price_spread', 'bid_spread', 'ask_spread', 'total_volume', 'volume_imbalance']:
    for loc in [1, 5, 10, 20, 40, 60]:
        train_df[f'file_{val}_shift{loc}'] = train_df.groupby(['file'])[val].shift(loc)
        test_df[f'file_{val}_shift{loc}'] = test_df.groupby(['file'])[val].shift(loc)

# 差分特征
# 获取与历史数据的增长关系
# 对于每个特征,计算当前值与其在历史时间步之间的差分,并保存到新的特征中
for val in ['wap1', 'wap2', 'wap_balance', 'price_spread', 'bid_spread', 'ask_spread', 'total_volume', 'volume_imbalance']:
    for loc in [1, 5, 10, 20, 40, 60]:
        train_df[f'file_{val}_diff{loc}'] = train_df.groupby(['file'])[val].diff(loc)
        test_df[f'file_{val}_diff{loc}'] = test_df.groupby(['file'])[val].diff(loc)

# 窗口统计
# 获取历史信息分布变化信息
# 对于每个特征,计算窗口大小为9的滑动均值和标准差,并保存到新的特征中
for val in ['wap1', 'wap2', 'wap_balance', 'price_spread', 'bid_spread', 'ask_spread', 'total_volume', 'volume_imbalance']:
    train_df[f'file_{val}_win7_mean'] = train_df.groupby(['file'])[val].transform(lambda x: x.rolling(window=9, min_periods=3).mean())
    train_df[f'file_{val}_win7_std'] = train_df.groupby(['file'])[val].transform(lambda x: x.rolling(window=9, min_periods=3).std())

    test_df[f'file_{val}_win7_mean'] = test_df.groupby(['file'])[val].transform(lambda x: x.rolling(window=9, min_periods=3).mean())
    test_df[f'file_{val}_win7_std'] = test_df.groupby(['file'])[val].transform(lambda x: x.rolling(window=9, min_periods=3).std())

CatBoost是一种梯度提升树模型,专门用于解决多分类问题,是一种强大且高效的机器学习算法。 CatBoost模型通过使用梯度提升树的方法,将特征与目标类别之间的关联建模,从而对新样本进行分类。

CatBoost模型的优势在于它可以处理高维度的特征,对缺失数据有很好的鲁棒性,并且能够自动处理类别型特征,减少了特征工程的负担。同时,CatBoost还具有较快的训练速度和更好的预测准确性,使得它成为多分类问题中的一种流行选择。

本赛题为一个多分类问题,在此使用CatBoost模型进行训练和预测。注意此处设置GPU提升速度

# 入模特征
cols = [f for f in test_df.columns if f not in ['uuid','time','file']]

# 定义交叉验证模型函数
def cv_model(clf, train_x, train_y, test_x, clf_name, seed = 42):
    # 定义折数为5
    folds = 5
    # 创建KFold交叉验证对象
    kf = KFold(n_splits=folds, shuffle=True, random_state=seed)
    # 初始化用于存储交叉验证结果的数组
    oof = np.zeros([train_x.shape[0], 3])
    # 初始化用于存储测试集预测结果的累积值
    test_predict = np.zeros([test_x.shape[0], 3])
    # 初始化用于存储每个折的F1得分的列表
    cv_scores = []

    # 对每个折进行循环训练和预测
    for i, (train_index, valid_index) in enumerate(kf.split(train_x, train_y)):
        # 打印当前折数
        print('************************************ {} ************************************'.format(str(i+1)))
        # 根据索引划分训练集和验证集
        trn_x, trn_y, val_x, val_y = train_x.iloc[train_index], train_y[train_index], train_x.iloc[valid_index], train_y[valid_index]

        # 判断分类器类型是否为CatBoost
        if clf_name == "cat":
            # 定义CatBoost分类器的参数
            params = {'learning_rate': 0.2, 'depth': 6, 'bootstrap_type':'Bernoulli','random_seed':42,
                      'od_type': 'Iter', 'od_wait': 100, 'allow_writing_files': False,
                      'loss_function': 'MultiClass'}
            
            # 创建CatBoost分类器模型
            model = clf(iterations=10000,task_type="GPU", **params)
            # 在训练集上进行训练,同时验证集上进行验证,获取验证集的预测结果
            model.fit(trn_x, trn_y, eval_set=(val_x, val_y),
                      metric_period=1000,
                      use_best_model=True, 
                      cat_features=[],
                      verbose=1)
            
            # 预测验证集和测试集的结果(概率值)
            val_pred  = model.predict_proba(val_x)
            test_pred = model.predict_proba(test_x)
        
        # 将当前折的验证集预测结果保存到oof数组的相应索引位置
        oof[valid_index] = val_pred
        # 将当前折的测试集预测结果加权累积到test_predict数组
        test_predict += test_pred / kf.n_splits
        
        # 计算当前折的F1得分
        F1_score = f1_score(val_y, np.argmax(val_pred, axis=1), average='macro')
        # 将F1得分保存到cv_scores列表中
        cv_scores.append(F1_score)
        # 打印当前所有折的F1得分列表
        print(cv_scores)
    
    # 返回交叉验证结果
    return oof, test_predict

# 对每个标签进行循环训练和预测
for label in ['label_5','label_10','label_20','label_40','label_60']:
    # 打印当前标签名
    print(f'=================== {label} ===================')
    # 使用CatBoost分类器进行交叉验证
    cat_oof, cat_test = cv_model(CatBoostClassifier, train_df[cols], train_df[label], test_df[cols], 'cat')
    # 将交叉验证的预测结果转换为类别标签,并存储到train_df和test_df的相应列中
    train_df[label] = np.argmax(cat_oof, axis=1)
    test_df[label] = np.argmax(cat_test, axis=1)

保存结果

# 指定输出文件夹路径
output_dir = './submit'

# 如果文件夹不存在则创建
if not os.path.exists(output_dir):
    os.makedirs(output_dir)

# 首先按照'file'字段对 dataframe 进行分组
grouped = test_df.groupby('file')

# 对于每一个group进行处理
for file_name, group in grouped:
    # 选择你所需要的列
    selected_cols = group[['uuid', 'label_5', 'label_10', 'label_20', 'label_40', 'label_60']]
    
    # 将其保存为csv文件,file_name作为文件名
    selected_cols.to_csv(os.path.join(output_dir, f'{file_name}'), index=False)

]
_ = !zip -r submit.zip submit/

五、学习心得

1.k折交叉验证

K折交叉验证(K-fold cross-validation)是一种常用的评估机器学习模型性能的方法。它可以有效地利用有限的数据集,并且更客观地评估模型的泛化能力。K折交叉验证将数据集分成K个子集(也称为折叠),其中K通常是一个整数,比如5或10。步骤如下:

  1. 数据集划分:将原始数据集随机分成K个大小相似的子集。每个子集称为一个折叠。

  2. 模型训练和评估:重复K次,每次使用其中的K-1个折叠作为训练数据,留下一个折叠作为验证数据。在每一次迭代中,使用K-1个折叠训练模型,并使用留下的一个折叠进行模型评估。

  3. 性能度量:在每次迭代中,记录模型的性能度量(例如准确率、精确度、召回率等)。

  4. 平均性能:完成K次迭代后,计算K次性能度量的平均值,得到最终模型的性能评估。

K折交叉验证的主要优点是:在数据有限的情况下,能够更好地利用数据,降低模型评估结果对数据集划分的依赖性。同时,它也可以减轻因为特定数据划分方式而导致的模型性能波动问题。

然而,K折交叉验证也有一些缺点:由于需要训练K次模型,它可能需要更长的计算时间和计算资源。同时,对于某些数据集,K折交叉验证可能无法捕捉到数据的时间相关性,特别是在时间序列数据上的应用。在这种情况下,应该考虑使用时间序列交叉验证的方法。

总结一下,K折交叉验证是一种重要的模型评估方法,可以更好地评估机器学习模型的泛化性能,并且在数据集较小或者不平衡的情况下特别有用。

2.CatBoost模型

CatBoost是一种集成学习方法,通过将多个决策树组合起来形成强大的预测模型,常用于分类和回归任务。

CatBoost的主要特点和优势如下:

  1. 自动处理分类特征:CatBoost在训练过程中自动处理分类特征,无需进行额外的数据预处理。它能够直接处理类别型特征,并将其转换成数值型特征,避免了手动编码或独热编码等处理步骤。

  2. 处理缺失值:CatBoost可以自动处理缺失值,无需对缺失值进行填充。它会将缺失值作为一个独立的类别来处理。

  3. 避免过拟合:CatBoost使用一种称为“Ordered Boosting”的技术,通过对训练样本的顺序进行排序来避免过拟合。这有助于提高模型的泛化能力。

  4. 支持GPU加速:CatBoost支持在GPU上进行训练,可以显著加快训练速度,特别是在处理大规模数据集时。

  5. 优秀的性能:CatBoost在性能上表现出色,通常能够取得较好的预测效果。CatBoost在很多实际应用中都表现良好,特别是在具有分类特征和缺失值的数据集上。

3.学习率和迭代次数

学习率是训练机器学习算法中的一个重要超参数,用于控制模型在每一次参数更新时的步长或者大小。在梯度下降优化算法中,学习率决定了参数在每次迭代中更新的幅度。较小的学习率意味着每次更新参数的步子较小,使得优化过程更加稳定,但可能需要更多的迭代次数才能收敛到最优解。而较大的学习率可能会导致参数在更新时跳过最优解,导致优化过程不稳定。

迭代次数是在机器学习和优化算法中经常遇到的一个重要概念。它表示模型或算法在训练过程中更新参数的次数。在训练过程中,我们通过反复迭代来优化模型的参数,使其逐渐逼近或收敛到最优解。

在不同的机器学习算法中,迭代次数可能指的是不同的内容。在梯度下降类的优化算法中,迭代次数指的是参数更新的次数。在集成学习中,迭代次数可能指的是基础学习器的个数。在一些迭代优化算法中,迭代次数还可能指的是迭代过程中迭代变量的更新次数。

迭代次数的选择在训练模型时非常重要,过少的迭代次数可能导致模型未能充分学习数据的特征,而过多的迭代次数可能会导致过拟合,使得模型在训练集上表现良好,但在测试集上表现较差。

一般来说,迭代次数的选择需要进行实验和调优。通常的做法是观察模型在训练集和验证集上的性能随着迭代次数的变化情况。当模型在验证集上的性能开始下降或趋于稳定时,可以认为模型已经收敛,此时可以停止迭代,避免过拟合。如果模型在验证集上的性能仍在提升,可以继续增加迭代次数,直到性能达到满意的水平或计算资源的限制。

在本赛题中,我通过将学习率、迭代次数调小和调大,都得到了截然不同的结果,感受到了这两个的影响和重要性。考虑使用网格搜索找到最优参数设置,但苦于资源不够。

4.总结与反思

根据得分,结果在训练集上表现得很好,但在测试集或实际应用中表现不佳,泛化能力差

数据量有240万多条,是足够的,猜测问题可能是采用的交叉验证方式不当特征选择不当,在未来将继续不断尝试、改进优化。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值