在机器学习领域,监督学习是一种很重要的学习模式。它指的是我们在数据有正确标签的情况下建立模型,并通过这一正确标签让模型进行自我修正,使其预测结果不断地朝着正确的方向迈进。
以解决信用卡欺诈问题为例,我们通过历史数据搭建模型以区分违约用户和信用良好的用户。那么在历史数据中,我们不光要知道用户的收入水平、家庭情况等特征信息,还要知道每个用户对应的标签信息,即在当前特征下,用户的标签是违约用户还是信用良好的用户。根据这些特征信息和标签信息所构建的分类模型,就属于监督学习模型。在使用监督学习构建量化交易策略时,我们同样可以采用这种思路,除每只股票的基本信息外,还要对历史数据中过去一段时间内股票的收益从大到小排序。将排名靠前、未来有较大概率表现好的股票记为“上涨股”,将其余的股票记为“非上涨股”,从而进行有监督的机器学习。
上述划分“上涨股”和“非上涨股”的过程就是监督学习中很重要的一步——提取标签。如下图所示,在监督学习建模的一般流程中,我们首先需要从所有可获取的数据源中拉取所需原始数据。获取完数据后,根据实际的建模需求确定并提取标签。接下来,将获得的原始数据进行清洗和加工,剔除脏数据,提取有效特征。紧接着,将加工好的数据输入搭建的模型中进行训练,根据模型评估指标表现的好坏不断地调节模型参数。最后,选择表现最好的模型存储,用它来开展后续的预测工作。
监督学习的一般流程
Step1数据获取
数据获取是机器学习建模的第一步,也是至关重要的一步。建模的根本目的是从历史数据中发现有价值的信息,以此来推演未来,帮助我们在解决实际问题时作出更好的决策。因此,越是丰富的优质数据,越能为我们提供更多的有效信息。常规的行情、财务数据可以通过一些标准化的数据库或者数据接口获取,如雅虎财经、新浪财经、万得等。除此之外,还可以尝试借助爬虫技术获取互联网上的舆情信息。数据的获取应根据实际的建模目标展开,即应尽可能地围绕着最终的模拟方向寻找满足我们需求的数据。
Step2数据预处理
收集完数据后,获取的数据往往来自不同的数据源。由于来自标准化数据库或者数据接口的数据可能是已经被清洗、整理好的数据,而现实中来自不同数据源的原始数据通常会出现缺失值、无效值及以不同标准表示同一变量等问题。所以我们需要评估获取到的数据,识别数据质量或结构中出现的任何问题,通过修改、替换或删除数据等方式来清理、加工数据,以确保处理后的数据集符合后续的机器学习要求。
Step3特征工程
被清洗干净且结构统一的数据是特征工程能成功进行的前提。在完成对数据的预处理之后,就可以展开特征工程部分的实践。特征工程主要是从原始数据中提取、创造输入机器学习模型的特征值,用来在提高模型精度的同时加速训练过程。
为达到满意的建模效果,特征工程往往是一个循环往复的过程。我们可以根据模型的训练效果,不断地修改、添加或删除部分特征。因此,特征工程是机器学习中最耗时和乏味的,但同时也是最具有创造性和“乐趣”的一部分。在这一过程中,具有投资方面专业知识的人士可以大放异彩,充分利用自己的专业知识从繁杂的原始数据中提取有价值的特征。
特征工程是一项具有创造性的工作,因而在多数情况下没有特殊的限制,可以让大家充分发挥自己的才能。但在量化交易领域,我们通常还是需要保证自己构造的特征不会向后窥探,包含记录创建时刻之后的信息。如使用整个时间段的平均值或者标准差来标准化数据时,往往会隐晦地将未来的信息泄露到我们创建的特征中。下面,我们将总结一些常用的处理方法,使用示例数据提供代码样例,方便大家根据需要选择合适的代码片段。
代码语言:javascript复制……
# 查询个股在 2022 年 7 月 1 日至 2022 年 9 月 1 日的行情数据
python
# 从 tushare 接口提取一些示例数据
import tushare as ts
import pandas as pd
ts.set_token('查取的 token 值')
api = ts.pro_api()
prices = api.daily(ts_code='000001.SZ,
600000.SH',start_date='20220701',end_date='20220901')[['ts_code','trad
e_date','open','high','low','close','vol']]
prices=prices.sort_values(by=['trade_date'],ascending=True).reset_inde
x(drop=True)
……
如下图所示,为监督学习特征工程。
监督学习特征工程
①特征变换
很多时候,成交量、市值、收益等特征会出现数据倾斜,大多数数据都集中在某一范围内,数据尾部拖曳很长,且尾部数据点的数值很大。而一些机器学习模型,如线性回归模型,会假设输入的变量数据是服从正态分布的。因此,我们需要应用一定的变换技巧,使倾斜的数据服从正态分布,以提高模型的表现能力。
参考代码如下所示:
代码语言:javascript复制# 对成交量进行对数变换
prices['vol_log'] = prices['vol'].apply(np.log)
……
②特征缩放
特征缩放可以消除特征间量纲的影响,使不同维度的特征被放在一起比较,从而大大提高模型的准确性。除了树模型,多数监督学习模型均需要进行特征缩放。常见的缩放方法有:标准化、归一化、最大最小值归一化、稳健归一化等。以标准化特征缩放为例,我们可以简单地定义一个 lambda 函数,并在任何需要进行特征缩放的地方应用该函数。
参考代码如下所示:
代码语言:javascript复制……python
# 对 close 价格进行标准化
zscore_scaling = lambda x: (x - x.rolling(window=100,
min_periods=40).mean())
/ x.rolling(window=100, min_periods=40).std() # 定义标准化的 lambda 函数
prices['z_close']
=prices.groupby('ts_code').close.apply(zscore_scaling)
……
在上述标准化的过程中有一点非常值得注意,我们定义了一个可以对每只股票分组应用的 lambda 函数。在 lambda 函数中,我们应用了滑动时间窗口这一技巧,使用当前记录时刻前 100 天的数据来计算该区间内的平均值和标准差,从而可以有效地避免向后窥探数据。
③技术分析指标
在挖掘特征的过程中,拥有量化经验的专业人士可能想根据需要添加一些股票、期货交易中的常用技术分析指标因子。实际操作中,可以根据情况自行编写代码计算相关分析因子,也可以借助开源的金融量化库。受大家喜爱的 TA 库(TechnicalAnalysis Library)就可以帮助我们在金融时间序列数据集(开盘、收盘、高、低、交易量)中进行特征工程。
参考代码如下所示:
代码语言:javascript复制……python
# 添加 TA 库所有特征示例
import ta
ta_exmdata = prices.loc[prices["ts_code"] == "600000.SH"].copy()
ta_exmdata = ta.add_all_ta_features(
ta_exmdata, "open", "high", "low", "close", "vol", fillna=False)
……
④其他表述形式
根据选择模型的需要,对一些常见的特征表述形式进行处理,如进行数据分箱,将连续数值转换为离散的类别变量,或者用独热编码、序号编码等编码格式对月份、星期等类别变量做处理。
参考代码如下所示:
代码语言:javascript复制……python
# 提取月份信息,并对其进行独热编码处理
prices['date_mon'] = pd.to_datetime(prices.trade_date).dt.month
one_hot_frame = pd.DataFrame(pd.get_dummies(prices['date_mon']))
month_names = ['mon_' str(num) for num in one_hot_frame.columns]
one_hot_frame.columns = month_names
prices = pd.concat([prices,one_hot_frame],axis=1)
……
Step4模型训练
完成特征加工后,我们就获得了模型的输入部分,可以据此开始构建量化模型。前面我们描述了通过划分“上涨股”和“非上涨股”来提取标签的过程,如此时待预测的标签为类别变量,就可以搭建一个分类模型来预测股票会上涨还是下跌。如果模型的标签是连续的数值型变量,比如直接预测股票的价格或是股票具体涨了多少,我们就可以采用回归模型来进行预测。如下图所示,为监督学习常用模型思维导图。
监督学习常用模型思维导图
根据学习目标的不同,首先确定要构建分类模型还是回归模型。而后考虑到具体的学习任务中,样本和特征个数的不同,以及各模型在不同应用场景中表现性能的差异,再进一步确定适合实际应用场景的模型进行建模。一般来说,当数据量较少、开发的特征数量较多时,我们倾向于选择高偏差、低方差的模型,如线性回归模型、朴素贝叶斯模型、逻辑回归模型,以及核函数为线性的支持向量机模型。而当遇到数据量较大、特征较少的数据集时,选择低偏差、高方差的模型通常会表现更好,如决策树模型、支持向量机模型、神经网络模型等。除此之外,挑选可用模型时,往往还需要考虑实际计算资源,涉及业务场景时的模型可解释性,以及模型对异常数据的敏感度等方面。
划定可用的模型范围后,可以从最简单的模型开始尝试。如果经过优化后,模型已经满足实际需求,那么即可采用该模型。或者也可以多选择几个模型,最后在计算效率、测试效果等诸多方面的比较下挑选最合适的模型。在建模过程中,为了确定模型的最优参数组合、查看模型的实际表现效果,通常需要将数据集划分为训练集、验证集和测试集。通过训练集训练模型,用验证集确定合适的模型参数,最后在测试集上查看模型最终的模拟效果。相比于其他的机器学习建模,如果构建的量化模型是回归模型,又由于市场交易具有时序性,那么目前常规的方法就是按照时间来划分数据集,避免数据集向后窥探,泄露未来信息。
回到实际的模型开发过程中,对于上述数据集划分、模型构建及寻找最优的模型参数组合,我们均可以利用相应的机器学习语言库进行开发。如前面罗列的大部分监督学习模型,相应开发环节都可以通过调用 Scikit-Learn 库相关的 API 接口实现。除此之外,一些被广泛应用、表现良好的集成模型,如 LightGBM、XGBoost 等也可以找到相应的 Python 开源框架。在这里,我们延续前文所述的分类思路,搭建一个分类模型,根据前 10 天的每只股票的收益情况,预测后 10 天收益排名前 200 的股票。
这里筛选出了在 2022 年 1 月 1 日前上市的 4639 只股票,将训练数据集的起始日期设为 2018 年 1 月 1 日,测试日期设为 2020 年 1 月 1 日至 2022 年 3 月 1 日。计算[ (t 2 天的收盘价)−(t 1 天的收盘价)]/(t 1 天的收盘价)作为每只股票第 t 天的收益,将前 10 天的每日收益作为输入特征,累加上后 10 天的每日收益,随后将后 10 天收益排在前 200 的股票计为“上涨股票”,将其余的股票计为“非上涨股票”。此部分参考代码所用的行情数据 stock_data 变量同样采用前文所述的 tushare 接口获取,获取代码不再复述。
参考代码如下所示:
代码语言:javascript复制……python
import pandas as pd
import numpy as np
from lightgbm import LGBMClassifier
from sklearn.metrics import roc_auc_score
import matplotlib.pyplot as plt
import seaborn as sns
# 直接读取获取的行情数据
stock_data = pd.read_csv(r"filename.csv")
# 计算每只股票的每日收益值
stock_data[['open','high','low','close','vol']] =
stock_data[['open','high','low','close','vol']].astype(float)
stock_data['trade_date'] = pd.to_datetime(stock_data.trade_date)
stock_data =
stock_data.sort_values(by=['trade_date'],ascending=True).reset_index(d
rop=True)
target_lambda1 = lambda x:x.shift(-1)
target_lambda2 = lambda x:x.shift(-2)
stock_data['close_1'] =
stock_data.groupby('ts_code').close.apply(target_lambda1)
stock_data['close_2'] =
stock_data.groupby('ts_code').close.apply(target_lambda2)
stock_data['target'] = stock_data.apply(lambda
x:(x['close_2']-x['close_1'])/x['close_1'],axis=1)
stock_data.target = stock_data.target.fillna(0)
pivot_stock_data = stock_data.pivot(index = 'trade_date', values =
'target', columns = 'ts_code')
pivot_stock_data = pivot_stock_data.ffill().fillna(0) # 存在 2017 年还没有
开放证券的公司,填充空值为零
def generate_fe(data,p1,time_horizon,test_horizon,up=200):
"""
生成模型的输入数据,取前 10 条数据的收益值作为特征,后 10 条数据的累计收益作为是否为
“上涨股"的评判依据。
取前 200 只股票标记为“上涨股",作为此次训练的标签。
:param data:每只股票每天的收益值
:param p1: 交易日期
:param time_horizon:特征数据时间长度
:param test_horizon:累计收益时间长度
:param up: 划定“上涨股"的数量
:return: 加工好的特征、标签及当前数据切片的最后一个交易日
"""
train_df = data.loc[:p1].iloc[-time_horizon - 1:-1]
test_df = data.loc[p1:].iloc[:test_horizon]
cum_val = test_df.cumsum()
sort_li = cum_val.iloc[-1].sort_values() # 按照最后的日期的累计和排序
up_index = sort_li.iloc[-up:].index
y = [1 if ii in up_index else 0 for ii in test_df.T.index] # 打标签,累
计收益靠前的股票记为 1,其他记为 0
X = train_df.T
last_date = test_df.index[-1] # 获取测试集最终日期
return X, y, last_date
while True:
X1, y1, p1 =
generate_fe(pivot_stock_data,p1,time_horizon,test_horizon,up)
# x:前 10 天的 target,y:后 10 天的累计收益,pf:后 10 天的最后一天
p1 = p1 pd.offsets.Day(1)
if p1 > start_test:
break
Xtrain.append(X1)
ytrain.append(y1)
# 模型训练
Xtrain = np.vstack(Xtrain)
ytrain = np.hstack(ytrain)
model = LGBMClassifier(num_leaves=25, n_estimators=100)
model.fit(Xtrain, ytrain)
# 查看模型训练情况
print(f'训练数据上的 auc 值:
{round(roc_auc_score(ytrain, model.predict_proba(Xtrain)[:,1]),3)}')
……
Step5模型选择
模型选择涉及 2 个方面:一方面,在构建模型的过程中会涉及一些模型参数,我们要选择当前模型表现最优时的参数组合;另一方面,如果搭建了多个模型,我们就需要选择模拟效果最好的模型作为最终模型。
这就需要我们客观、真实地评估自己搭建的模型。对量化模型的评估通常可以从 2 个方面展开:一是可以通过模型在测试集上的表现来评估,回归模型可以采用均方误差来衡量,分类模型则可使用分类的正确率、召回率、AUC 等指标来衡量;二是可以通过模型构建的策略组合的实际收益情况来进行评价。下面我们将此次分类模型预测股票的平均收益情况与所有股票收益的均值做了对比,可以看出随着时间的推移,预测结果的收益情况较基线收益还是存在较为显著的提升的。
参考代码如下所示:
代码语言:javascript复制……python
predict = []
while True:
X = pivot_stock_data.loc[:start_test].iloc[-time_horizon - 1:-1].T
future_variation =
pivot_stock_data.loc[start_test:].iloc[:test_horizon]
# 预测
pro = pd.Series(model.predict_proba(X)[:, 1], index=X.index)
# 根据排名,计算筛选出优质股票的预估收益
goods = future_variation.loc[:, pro.sort_values().index[-200:]]
goods = goods * pd.Series(np.arange(200) / 199 1, index=goods.columns)
predict.append((goods.sum(axis=1)) / 200)
start_test = future_variation.index[-1] pd.offsets.Day(1)
if start_test > pd.to_datetime(end_test):
break
predict_results = pd.concat(predict)
# 绘图
plt.figure(figsize=(10,5))
plt.plot(benchmark.index, benchmark.cumsum(),color='b',label='基线收益')
plt.plot(test_results.index, test_results.cumsum(),color='g',label='预测
股票累计收益')
plt.legend(loc='upper left')
plt.xlabel('时间')
plt.ylabel('收益')
plt.show()
……
如下图 所示,为模型预测累计收益与基线收益的对比情况。
模型预测累计收益与基线收益对比图