机器学习中特征工程在中国股票市场的应用——基于沪深300指数日度数据

The algorithms we used are very standard for Kagglers. […] We spent most of our efforts in feature engineering. [...] We were also very careful to discard features likely to expose us to the risk of over-fitting our model.

— Xavier Conort, "Q&A with Xavier Conort"(Kaggle首席数据科学家)

“Coming up with features is difficult, time-consuming, requires expert knowledge. "Applied machine learning" is basically feature engineering.”

—Andrew Ng, (吴恩达,斯坦福教授,Coursera中著名的机器学习课程主讲人,前百度人工智能实验室负责人)

简介:

在机器学习中,运用特征工程对于预测结果的准确性至关重要。本文利用Wind资讯金融终端平台获取的沪深300指数日度数据(10年以上,约3000个),通过对原始数据的提取,总共构建67个特征变量。本文运用特征工程主要目的是利用第t-1个交易日的特征维度信息来尽可能提高第t个交易日走势判断的准确率。对67个特征变量经过特征选择后, 形成集成特征打分器,选中排名前3个特征变量作为K-平均聚类方法的输入值。对聚类的结果进行收益率和波动率的统计,证明得到的聚类存在两个明显不同的收益率分布,并基于此提出一个基准择时策略,该策略拥有17%的平均年化收益率,说明了运用特征工程的有效性。

1.背景知识

1.1 特征工程(Feature Engineering)

简单来说,特征工程是将原始数据转化为特征,并运用这些特征来提升预测目标变量的准确性。

在日常的数据分析中,我们会把数据整理成(观测值,特征维度)的两维数据列表的形式。例如,对于一个人数为50的班级,我们可以从 1 到 50, 对学生进行编号,并选定姓名,性别,身高,体重,成绩 5 个维度作为分析的特征变量。此时我们的得到的就是一个 50 乘 5 的数据框(dataframe)。

1.1.1 特征构建(Feature Construction)

一般来说在目标问题的特定领域分析上,对原特征维度进行组合变换产生新特征维度,就是指特征构建,加入这些新特征会增强模型的解释力(explanatory power)。

例如,如果我们感兴趣的问题是以上班级学生的健康状况,我们可以通过身高和体重计算学生的的BMI(body mass index)值:

BMI(体质指数)=体重(kg)÷ 身高²(m)

通常认为 BMI 在18.5~25之间为合理体重。在这里,BMI 值就是对身高和体重两个维度的组合变换。这种对原特征维度进行变换,来寻找新特征维度的做法,便是特征构建。

1.1.2 特征选择(Feature Subset Selection)

当获得大体量的特征数据集后,由于变量特征的庞大必然存在冗余(redundant)和噪声不相关(irrelevant),需要我们选择有意义的特征输入机器学习的算法和模型进行训练,这就是特征选择。

一般一个完整的特征选择过程包括四个步骤,如下图所示:

1.子集搜索(subset generation/search):按照一定的搜索策略产生候选特征子集;

2.子集评估(subset evaluation):通过某个评价函数评估特征子集的优劣;

3.停止条件(stopping criterion):决定特征选择算法什么时候停止;

4.子集验证(subset validity):用于验证最终所选的特征子集的有效性。

通常选择特征变量从两个方面考虑:

根据特征选择的具体过程又可分为3种:

1.1.3 集成学习方法(ensemble learning method):

通过特征工程产生了大量的特征变量,数据集中包含噪音,共线性,非线性相关等问题。通过集成学习方法,组合特征选择的多个模型,以获得更好的预测效果,使集成的模型具有更强的泛化能力。可以尽量减少数据和单一特征选择方法引起的问题,并改善特征选择的效果。

1.1.4 特征抽取(feature extraction)

特征抽取也称作特征降维,与特征选择只是对特征进行筛选不同,特征提取是指利用已有的特征计算出一个抽象程度更高的特征集,从原有的较多维度特征数据降低为较低维度特征数据。以此估计的模型,往往有比原来更好的预测效果。

1.2股票市场(Stock Market)

1.2.1 股票指数

我们经常能听到股市怎么怎么好,大盘又涨了XX点,其实说的对象就是股票市场中的指数,在国内经常提到的是上证指数(000001)和深证成指(399001),它们分别反映了上海证券交易所和深圳证券交易所上市股票的整体走势。

1.2.2 沪深300指数

本文主要的研究对象是沪深300指数(000300),是在上海和深圳证券交易所中选取300只A股作为样本编制而成的成份股指数。

沪深300指数主要优势是反映沪深两个市场整体走势的“晴雨表”。指数样本选自沪深两个证券市场,覆盖了沪深市场六成左右的市值。成份股为市场中市场代表性好,流动性高,交易活跃,规模大的主流投资股票,能够反映市场主流投资的收益情况。

2.数据初步探索性分析

2.1数据获取

本文所研究的对象是沪深300指数,原始数据是通过Wind资讯金融数据终端的行情序列导出所得,原始数据如下表所示:

总共3006行*9列数据,时间跨度接近12年(每年大概250个交易日),以2014年12月31日的收盘价为基准(1000点),主要数据包括开盘价,收盘价,最高价,最低价,成交量(股),成交金额(元)。

 
AخA
import pandas as pd
 
xxxxxxxxxx
#read the excel table
hs300 = pd.read_excel('沪深300.xlsx')
hs300
代码简称日期开盘价最高价最低价收盘价成交量(股)成交金额(元)
0399300.SZ沪深30012/31/2004------1000----
1399300.SZ沪深3001/4/2005994.769994.769980.658982.7947412868944431977418
2399300.SZ沪深3001/5/2005981.577997.323979.877992.5647119108984529208214
3399300.SZ沪深3001/6/2005993.331993.788980.33983.1746288029053921015420
4399300.SZ沪深3001/7/2005983.045995.711979.812983.9587298694094737469399
5399300.SZ沪深3001/10/2005983.76993.959979.789993.8795791697993762932890
..............................
3001399300.SZ沪深3005/12/20173350.943387.273349.173385.3787933864660097522124500
3002399300.SZ沪深3005/15/20173391.593406.583391.593399.1937829776110092183332800
3003399300.SZ沪深3005/16/20173390.933428.853373.553428.6491105242416001.12814E+11
3004399300.SZ沪深3005/17/20173423.193433.973408.153409.965699736837001.10553E+11
3005399300.SZ沪深3005/18/20173387.673409.933383.933398.112785759589008809116600

3006 rows × 9 columns

2.2 数据预处理

2.2.1 生成对数收益率

原本这一节的内容应放在下一章的特征构建中,但是收益率是股票市场量化研究的重要目标特征变量对它进行探索性分析非常必要,所以在这一章来讲。

对数收益率公式

Rt=ln(Pt/Pt-1)

对数收益率(log return)相比普通百分比收益率(simple return)的好处:

2.2.2 数据清洗

该步骤主要用于清洗异常样本和缺失值。在本文的实际应用中由于获取的沪深300指数日数据都是标准的且没有缺失值(除第一行外),所以基本不需要对原始数据进行处理,删去第一行的数据,最终获得基础数据为3005行 ×10列的数据集。

2.3 收益率数据探索性分析

本节对沪深300指数的涨跌情况和对数收益率进行了描述性统计,并作出相关的统计图以更好地展示和说明该数据的情况。

2.3.1 指数涨跌统计和饼图

官方的涨跌概念,是认为第t个交易日的指数收盘价大于第t-1个交易日的收盘价,就可说明指数第t个交易日上涨(这可能与普通大众认为的第t个交易日的指数收盘价大于第t个交易日的开盘价就为上涨不同)。

最终的统计结果如下图所示:总体来看,沪深300指数从2005-01-04到2017-05-18时段内,上涨天数(1614天)多于下跌天数(1391天)。

 
xxxxxxxxxx
#statistics for daily  fluctuation
up_and_down = hs300['对数收益率'] > 0
up_and_down_statistic = up_and_down.value_counts()
#pie plot
label = ['up','down']
plt.pie(up_and_down_statistic, labels=label, autopct='%1.2f%%' )
plt.show()

2.3.2 对数收益率描述性统计和箱形图

对数收益率的描述性统计结果和箱形图如下所示,相关代码也已列出。我们把对数收益率分成三组,一组是全部数据,一组是上涨的组别,最后一组是下跌的组别,把统计表格与箱形图结合来看:

总体的平均收益率大于0且非常接近于0,然而又发现下跌的平均收益率绝对值大于上涨的平均收益率值,这也间接说明上涨的天数大于下跌的天数。

同时对数收益率整体大于0,上涨的对数收益率与下跌的对数收益率的箱形图基本一致,平均收益率上涨的稍低。

 
xxxxxxxxxx
#descriptive statistics and box plot
ln_yield  = pd.Series(return_rate)
up_ln_yield = ln_yield[ln_yield > 0]
down_ln_yield = ln_yield[ln_yield < 0]
s1 = ln_yield.describe()
s2 = up_ln_yield.describe()
s3 = down_ln_yield.describe()
pd.concat({'all':s1, 'up':s2, 'down':s3}, axis=1)
alldownup
count300513911614
mean0.000407-0.0132160.0121480
std0.0182110.0144780.01178978
min-0.096949-0.0969490.000000355
25%-0.007703-0.0174090.003421797
50%0.000969-0.0086320.008487688
75%0.009449-0.0035550.01742251
max0.089310-0.0000060.08931021
 
xxxxxxxxxx
#box plot
s4 = pd.DataFrame([np.array(ln_yield), np.array(up_ln_yield), np.array(-down_ln_yield)]).T
s4.columns = ['all', 'up', 'down']
s4.plot(kind='box', figsize=(15,10), title = 'box plot of daily log return(the down data is absolute value)')
plt.ylim(-0.04, 0.04)
plt.show()

2.3.3 对数收益率的时间序列变化图

如下图所示:可以明显看到对数收益率的变化在两段时期波动剧烈(2007年,2015年,都是大牛市的开端并且达到顶峰后迅速下跌,波动率异常的大)。

再看最近时期的波动率变化,2016年开始收益率变化程度降低,基本上涨跌幅控制在1%范围内,也正说明了股市在证监会的强势监管下逐步趋稳

 
xxxxxxxxxx
#Log yield Variation in the past 
ln_yield.plot(kind='line', style='k--', figsize=(15, 10), title='Daily Yield Changes Over Time Series')
plt.show()

2.3.4 条形图和密度曲线

如下图所示:通过条形图和密度曲线的展示,对数收益率基本符合正态分布,但仔细看会发现均值稍微大于0,主要是因为上涨的天数更多。尤其是收益率在[-0.05,0.05]内上涨大于下跌。

而在收益率绝对值大于0.05 的范围内,下跌的会明显多于上涨的,可以看到小于-0.05收益率这块曲线有一个明显的凸起,而对应上涨部分则是正常曲线。这说明了微涨天数大于微跌天数,而暴跌天数大于暴涨天数,表达了股票市场更倾向于慢慢持久上涨,而下跌时则会短期内迅速下跌

 
xxxxxxxxxx
#Frequency distribution of up and down
ln_yield.hist(bins=100, alpha=0.3, color='g', normed=True)
#Kernel Density Estimate
ln_yield.plot(kind='kde', xlim=[-0.1, 0.1], style='r', grid=True, 
figsize=(15, 10), title='Frequency Distribution Of Up And Down & Kernel Density Estimate Curve')
plt.show()

3.特征变量构建和可视化分析

本章讲述特征变量的构建,即如何通过原有的特征数据组合计算得到新的特征变量。本文中特征变量的获取方式,一部分纯依靠数据挖掘的方式获得,一部分是为了解决目标问题而依托专业和常识性知识所获得,也有一部分是结合两者特点所获得的特征变量,最终我们把得到的所有特征变量分为三种:基础变量经典技术指标变量阿尔法变量

得到特征变量后,我们通过一系列条形图、箱形图、密度曲线,相关系数热力图等可视化操作分析方式呈现各个特征变量的分布。初步探讨了特征变量的分布以及与目标变量的相关性,为后续进行的特征选择提供参考。

然后我们汇总合并了每个特征数据,并使得它们的时间戳保持一致。最后对每一列特征变量分别进行标准化处理,使得模型估计的系数保持在一定范围内。

由于篇幅有限,每个特征变量的具体公式和概念不在这章里一一列举,具体见附录。

3.1 10个基础变量

开盘价(open),收盘价(close),日内最高价(high),日内最低价(low),成交量(volume),成交总额(amount),成交均价(price),日内涨跌幅(daily_gain),日内区间(daily_region),日内开盘涨幅(daily_opengain)。

3.1.1 基础变量描述性统计和箱形图

如下表和图所示 我们一共得到10个基础变量,其中每个变量长度都统一控制为2047个(与所有变量中的最小值保持一致),时间戳全部对应为2005-04-05——2017-05-18,箱形图中有个别几个区间较小,是因为存在较大的异常值压缩了正常值的显示空间。

openclosehighlowvolumeamount
count294729472947294729472947
mean2788.8311192791.7991812819.4554572757.6942528.557676e+091.009196e+11
std1006.9976161006.5936341019.906328989.6461048.664803e+091.178769e+11
min816.546000818.033000823.860000807.7840005.962465e+082.901827e+09
25%2262.3100002265.5895002290.3460002236.1450003.928517e+094.227502e+10
50%2753.6880002758.4950002784.9160002725.3620006.246805e+097.062610e+10
75%3349.0270003354.4875003377.1744503327.2485009.408543e+091.131326e+11
max5862.3780005877.2020005891.7230005815.6090006.864391e+109.494980e+11
pricedaily_gaindaily_regiondaily_opengain
count2947294729472947
mean10.9691911.0001591.0026950.999901
std3.5200240.0021280.0017570.000983
min3.9434990.9872671.0004370.991165
25%9.0282420.9991781.0015260.999634
50%10.6508311.0001531.0021860.999962
75%12.9212561.0012461.0033291.000250
max23.5176101.0106371.0139761.011364

3.1.2 基础变量的条形图和密度曲线

特征变量基本都呈现正态分布,大部分数值集中在一个区间。有一个有趣的现象,基础变量中的四个价格,open,close,high,low 在1000-2000点之间呈现一个最低点,这可能说明沪深300指数较快的跨越了这个区间,并且以后下跌也很难跌到这个区间里

3.1.3 基础变量的相关系数表和热力图

以下就是10个基础变量加上目标变量(第二天的对数收益率)的相关系数表和热力图。按绝对值大小来比较,之前提到的四个价格的基础变量加上price与对数收益率的相关系数的绝对值在 0.08-0.09 范围,属于较高的水平,其余的差一些。但是这五个基础变量,本身也存在较为严重的共线性。

openclosehighlowvolumeamount...ret
open1.0000000.9985810.9994990.9992370.4431390.545025...-0.082633
close0.9985811.0000000.9992810.9993070.4454900.546691...-0.079924
high0.9994990.9992811.0000000.9990140.4486380.549896...-0.081173
low0.9992370.9993070.9990141.0000000.4392850.540714...-0.081665
volume0.4431390.4454900.4486380.4392851.0000000.974043...-0.008074
amount0.5450250.5466910.5498960.5407140.9740431.000000...-0.025198
price0.9377530.9371140.9384980.9366920.2311880.360854...-0.090391
daily_gain-0.051597-0.002042-0.029678-0.0264130.0388380.021587...0.052516
daily_region0.1137760.1097640.1304150.0887840.2398050.235414...0.001494
daily_opengain0.0187350.0169850.0132850.0188440.0334360.040383...-0.024746
ret-0.082633-0.079924-0.081173-0.081665-0.008074-0.025198...1.000000

3.2 衍生经典技术指标变量(常识性的股票市场经典技术指标):

我们把技术指标变量细分为以下5类:(具体公式和概念详见附录

趋势指标:MACD,AMA,TRIX,VHF,RVI

震荡指标:KDJ,BIAS,ForceIndex

超买超卖指标:VR,DPO,NVI,PVI,ROC

能量指标:OBV,PSY

动量指标:MTM6,MTM12,CMO

3.2.1 技术指标变量描述性统计和箱形图

一共20个技术指标变量,基本都是在正常的取值范围内,由于无量纲,不同变量之间在进行标准化之前无法相互比较,个别变量如ForceIndex,VR,CMO变量中存在个别较大的极端值引起箱形图的长度较窄。

MACDAMATRIXVHF...MTM_6MTM_12CMO
count2947294729472947...294729472947
mean-0.01784816.5911020.0371540.414883...4.9686159.8677544.581011
std41.232976222.6659381.0713440.122342...150.479777217.0082496.696297
min-244.320432-714.074876-6.9135730.193536...-1034.945-1178.904-17.352639
25%-15.243229-77.372235-0.4748440.323869...-53.5505-78.6491.968651
50%2.0429616.4225780.0855510.394415...10.18717.42082.611093
75%19.480999108.2753990.6478350.48071...75.4275110.75554.348366
max185.662115778.417415.5013240.899901...662.842896.98730.332605

8 rows × 20 columns

3.2.2 技术指标变量的条形图和密度曲线

基本呈现的都是正态分布,有个别变量的数值会更加集中在小的区间内。

3.2.3 技术指标变量的相关系数表和热力图

20个指标与第二天对数收益率的相关系数绝对值多集中在(0.03-0.05),比之前的五个价格变量低一些。而且从热力图中可以看出20个不同指标间存在明显的共线性。

MACDAMATRIXVHF...MTM_12CMOret
MACD1-0.2007960.4219410.08263...0.788774-0.0064580.01672
AMA-0.20079610.0899560.229946...0.3571060.34410.041914
TRIX0.4219410.08995610.117086...0.4909510.1169870.025143
VHF0.082630.2299460.1170861...0.2003560.1886190.057175
RVI0.6416270.1946010.284620.264723...0.7297140.1810050.051294
K0.6411030.2625120.4806060.211701...0.720540.2414230.033573
D0.5899840.3640850.3015510.23664...0.7414810.271280.040021
J0.5890250.0900340.6152130.142905...0.5652840.161220.019587
BIAS0.753980.2158850.7746950.21108...0.8123380.2016670.03325
ForceIndex0.1482260.0238680.5275030.057149...0.2027530.021340.029512
VR0.3885040.2151260.308030.302972...0.5148270.2198940.018097
DPO0.5274090.6989320.4270810.237592...0.8541240.278020.049276
NVI-0.0352880.1413270.0364690.137088...0.0578650.6882420.012894
PVI-0.001064-0.000646-0.013698-0.077327...-0.001345-0.259608-0.023317
ROC0.729060.3495140.5192530.252519...0.9401460.2450850.043113
OBV-0.016637-0.045237-0.040668-0.105039...-0.042494-0.233653-0.030358
PSY0.4684510.3439170.3799630.250157...0.6697930.3098830.046945
MTM_60.7428790.1497550.6871050.160197...0.7211560.1476010.014734
MTM_120.7887740.3571060.4909510.200356...10.2036930.037635
CMO-0.0064580.34410.1169870.188619...0.20369310.031895
ret0.016720.0419140.0251430.057175...0.0376350.0318951

21 rows × 21 columns

3.3 6个阿尔法变量(通过纯数据挖掘得到的阿尔法因子):

alpha#6,alpha#12,alpha#23,alpha#28,alpha#54,alpha#101(主要参考了《WorldQuant Formulaic 101 Alphas》研究报告中给出的阿尔法因子计算公式,具体见附录

3.3.1 阿尔法变量描述性统计和箱形图

6个阿尔法变量,其中3个变量 alpha12,alpha23,alpha28由于存在异常值,导致箱形图长度较窄。

alpha_6alpha_12alpha_23alpha_28alpha_54alpha_101
count294729472947294729472947
mean-0.343985-4.692617-14.759806-0.00005-0.5331610.068314
std0.38509658.73901847.0811240.0005110.3126090.581572
min-0.977364-378.179-479.929-0.003782-0.993079-0.999979
25%-0.650779-27.06-22.3865-0.000269-0.826494-0.427046
50%-0.399601-3.41650-0.000038-0.5733290.090392
75%-0.09595715.718500.000158-0.2411670.592086
max0.92915391.866302.5410.00313500.999993

3.3.2 阿尔法变量的条形图和密度曲线

如下两图所示,其中3个之前提到的阿尔法变量alpha12,alpha23,alpha28,呈现明显的正态分布,另外三个在区间内则分布比较均匀。

3.3.3 阿尔法变量的相关系数表和热力图

6个阿尔法变量与对数收益率的相关系数绝对值有高有低,最高0.07,最低0.007。6个相关系数之间也存在一定的共线性。

alpha_6alpha_12alpha_23alpha_28alpha_54alpha_101ret
alpha_61-0.050080.164580.0418020.058835-0.056188-0.027319
alpha_12-0.0500810.166860.1261170.117021-0.195410.002532
alpha_230.164580.1668610.4117320.137108-0.197584-0.037156
alpha_280.0418020.1261170.4117321-0.0607920.043740.07416
alpha_540.0588350.1170210.137108-0.0607921-0.859041-0.007192
alpha_101-0.056188-0.19541-0.1975840.04374-0.85904110.04816
ret-0.0273190.002532-0.0371560.07416-0.0071920.048161

3.4 其他特征变量

除前面一共36个特征变量外,在接下来的特征选择中,我们增加其他衍生变量: 1.定义变量在前d个交易日的变化率

其中x的参数,我们分别输入之前的10个基本变量,d参数分别输入1,5,10。这样我们分别计算了10个基础变量的前1 个,5个和10个交易日的变化率,共另外得到30个特征变量。

2.定义一个 adv20 变量,即指前20个交易日的平均交易量。

3.5 数据合并和标准化处理

本章最后部分,我们把所有生成的67个特征变量与目标变量第二天对数收益率,按时间戳保持一致进行合并。然后对每列数据进行标准化处理,使得最终模型的系数无量纲化,具体公式为: ,其中S为该列数据变量标准差。

4.特征选择

特征选择本质上是一个变量的组合优化(combinatorial optimization)问题。现在我们已经获得了67个特征变量,在建模过程中,每个特征变量有两个可能的状态:"保留"和"被剔除"。

那么保留下来的特征变量可能的集合个数是2^67,如果通过穷举法的方式进行求解,需要耗费大量时间和计算资源,如下图所示:

所以我们需要较好的特征选择方法,既快速又高效地完成变量的选择,在前面一章我们已经获取所需的全部变量特征并进行了标准化的预处理,用可视化的工具初步分析了各个特征变量的分散性和与目标变量的相关性。

本章进行特征选择,我们会使用pythonscikit-learn模块提供的部分方法进行特征选择,相关代码也会列出。

4.1 Filter过滤法(单变量特征筛选)

4.1.1 Pearson相关系数

Pearson 相关系数是最常用的判断特征和响应变量(response variable)之间的线性关联的标准。在上一章中,我们用计算当前交易日的特征变量与第二天交易日的对数收益率的Pearson相关系数。

 
xxxxxxxxxx
from sklearn.feature_selection import f_regression
###correlation
f, pval = f_regression(hs300.data, hs300.target, center=True)
ranks['Corr.'] = rank_to_dict(f, names)

4.1.2 距离相关系数(Distance Correlation Coefficient)

距离相关系数是针对 Pearson 相关系数只能表征线性关系的缺点而提出的。其思想是分别构建特征变量和响应变量的欧氏距离矩阵,并由此计算特征变量和响应变量的距离相关系数。详细的定义和计算过程可参考维基百科,由于python中没有提供相关的模块计算距离相关系数,故参考了其他资料,使用了自定义函数。

 
xxxxxxxxxx
###calculation the distance correlation
dis_corr = []
for i in names:
    dc, dr, dvx, dvy = dcov_all(np.array(hs300.data[i]), np.array(hs300.target))
    dis_corr.append(dr)
ranks['Dis_Corr'] = rank_to_dict(dis_corr, names)

4.2 Wrapper封装法

4.2.1 基础模型:简单线性回归

 
xxxxxxxxxx
from sklearn.linear_model import LinearRegression
###simple linear regression
lr = LinearRegression(normalize=True)
lr.fit(hs300.data,hs300.target)
ranks['LR'] = rank_to_dict(np.abs(lr.coef_), names)

4.2.2 递归特征消除法(Recursive Feature Elimination, RFE)

递归消除特征法使用一个基模型来进行多轮训练,每轮训练后,消除若干权值系数的特征,再基于新的特征集进行下一轮训练。虽然该方法,名称中带有递归,但实质上并没有递归存在,我们使用feature_selection库的RFE类来进行特征选择,具体代码如下:

 
xxxxxxxxxx
from sklearn.feature_selection import RFE
#stop the search when 5 features are left(they will get equal scores)
#这里基础模型使用的简单线性回归,目标函数筛选到5个变量为止
###calculate the Recursive Feature Elimination
rfe = RFE(lr, n_features_to_select=5)
rfe.fit(hs300.data,hs300.target)
ranks['RFE'] = rank_to_dict(rfe.ranking_.astype(float), names, order=-1)

4.3 Embedded 嵌入法

4.3.1 随机森林(Random Forest)

随机森林算法是把决策树(decision tree)和自助重抽样法(bootstrapping resampling)结合起来的分类或者回归算法。其思想是通过对训练集进行重抽样的方法生成大量的决策树,再把决策树组合起来,从而减少噪音的干扰和过拟合的可能。在 scikit-learn 模块的 ensemble 类中,用随机森林算法进行回归的目标函数是均方差误差函数(Mean Square Error)。

 
xxxxxxxxxx
from sklearn.ensemble import RandomForestRegressor
###Random Forest
rf = RandomForestRegressor(random_state=101) #加入随机种子数,确保每次输出结果相同
rf.fit(hs300.data,hs300.target)
ranks['RF'] = rank_to_dict(rf.feature_importances_, names)

4.3.2 基于正则化的线性回归(Regularization-Based Linear Regression)

首先介绍两个模型结果的评判标准:

赤池信息准则(Akaike information criterion, AIC)贝叶斯信息准则(Bayesian Information Criterions, BIC)

AIC和BIC有相近的数学表达式:

AIC = 2k - 2ln(L) BIC = ln(n)*k - 2ln(L)

其中k为参数数目,L是似然函数(likelihood function), n是数据中观测值的数量。

AIC 和 BIC 的表达式中均包含了模型复杂度惩罚项(2k和ln(n)* k)和 最大似然函数项(ln(L))。不同的地方在于,在 BIC 的表达式中,惩罚项的权重随观测值的增加而增加。因此当观测值数量较大时,只有显著关联的特征变量才会被保留,从而降低模型的复杂性。在 AIC 的维基百科词条中,提到了 AIC 在实践中的效果一般优于 BIC

在建模时,我们可以通过最小化 AIC 或 BIC 来选择模型的最优参数。由表达式可以看出,AIC 和 BIC 倾向于复杂度低(k越小越好)和符合先验假设(L越大越好)的模型。在简单线性回归中,似然函数L是依据残差服从正态分布的先验假设构建的,即如果特征变量的加入能够使残差更接近正态分布,则认为这个特征能够显著改善线性回归模型。

回归模型介绍:

Lasso回归(Least absolute shrinkage and selection operator)岭回归(Ridge)

在优化理论(optimization theory)中,正则化(regularization)是一类通过对解施加先验约束把不适定问题(ill-posed problem)转化为适定问题的常用技巧。例如,在线性回归模型中,当用最小二乘法估计线性回归的系数β时,如果自变量存在共线性,系数的估计值将具有较大的方差,因而会影响后续参数的统计检验。如果在最小二乘法的参数估计表达式中添加L1正则项 λ|β|,则称为Lasso线性回归模型:

如果添加L2正则项λβ^Tβ ,则称为岭回归模型(Ridge Regression):

在线性回归的系数估计中,正则化处理在改善问题的适定性的同时,也会使得系数的估计有偏(biased estimation),因此在选择正则化项的权重λ时,一般的原则是在问题足够适定的前提下,λ应该尽可能小。在接下来的实现中,我们使用上面提到的AIC和BIC来确定正则化项的权重。另外,L2正则化和L1正则化对解施加的是不同的先验约束:L2正则化会令解出现集中分布的特性,而L1正则化则会令解出现稀疏的特性。在Lasso回归中,因为L1正则化会令部分的系数趋近于0,因此也是一种常用的特征选择方法。

随机Lasso回归(RandomizedLasso)

在统计学中,我们通常把这类和自变量和响应变量均存在显著相关性的变量称为混淆变量(confounding variable)。在Lasso回归中,如果存在自变量共线性的问题,则哪一个特征维度被剔除将取决于特征选择子集构建的顺序,从而造成特征选择结果的不确定性。

为了减少 Lasso 回归中特征选择顺序的影响,Python的 scikit-learn 模块 linear_model类 中提供了一个 RandomizedLasso 类。其思路是对训练集的数据进行多次重抽样,从而得到一系列特征选择的子集,再对子集中各个特征变量出现的频率进行统计,剔除掉出现频率低的特征变量。

 
x
from sklearn.linear_model import LassoLarsIC
###lasso regression based on AIC
lasso_aic = LassoLarsIC(criterion='aic', max_iter=50000)
lasso_aic.fit(hs300.data,hs300.target)
ranks['Lasso_AIC'] = rank_to_dict(np.abs(lasso_aic.coef_), names)
###lasso regression based on BIC
lasso_bic = LassoLarsIC(criterion='bic', max_iter=50000)
lasso_bic.fit(hs300.data,hs300.target)
ranks['Lasso_BIC'] = rank_to_dict(np.abs(lasso_bic.coef_), names)

-

 
xxxxxxxxxx
from sklearn.linear_model import RandomizedLasso
###randomized lasso regression
rlasso = RandomizedLasso(alpha=0.07,  random_state=22)
rlasso.fit(hs300.data,hs300.target)
ranks['Stability'] = rank_to_dict(np.abs(rlasso.scores_), names)

-

 
xxxxxxxxxx
from sklearn.linear_model import Ridge
###ridge regression
ridge = Ridge(alpha=7)
ridge.fit(hs300.data,hs300.target)
ranks['Ridge'] = rank_to_dict(np.abs(ridge.coef_), names)

由于未知原因,Lasso_BIC 方法中的所有特征变量前的系数为 0,故删去。

4.4 集成特征打分器(Ensemble Feature Grader, 以下简称EFG)

特征选择的本质上是求解一个计算量随特征变量个数呈指数增长的组合优化问题。基于不同的子集搜索和评价标准,不同的方法给出的都只是一个近似最优解,而解的合理性也将受方法本身的局限性所影响。在真实的数据分析中,如果我们通过特征工程产生大量的特征变量,数据集中必然包含噪音,共线性,非线性相关等问题。

因此,为了系统化地进行特征选择,获得更为合理的相关特征变量子集,在这里我们借鉴机器学习里面的集成学习(ensemble learning)的思想,提出一个集成特征打分器(以下称EFG)。其主要思路是,根据以上特征选择的方法对特征进行打分(分数的取值范围为0到1),观察特征变量在不同方法下的得分,进而计算其总得分,以尽量减少数据和单一特征选择方法引起的问题,并改善特征选择的效果。

最终得到的结果如下表所示,各特征变量按Mean这一列从大到小排列。

Corr.Dis_CorrLRLasso_AICRFRFERidgeMean
alpha_2810.320.0210.620.9410.7
daily_gain_10.220.180.030.660.520.920.60.45
AMA0.320.570.020.020.950.740.450.44
DPO0.440.570.010.180.760.630.190.4
close0.420.18100.1610.070.4
close_50.360.360.030.190.2910.510.39
alpha_1010.420.050.010.590.430.790.430.39
daily_region_10.760.190.010.130.850.530.20.38
CMO0.180.550.010.30.630.680.220.37
volume_10.010.050.030.590.350.820.760.37
price0.640.30.010.380.40.660.20.37
daily_opengain_50.150.20.010.280.840.840.230.36
BIAS0.20.340.010.20.720.710.210.34
daily_region0.5100.070.650.150.030.34
daily_gain_50.610.1700.320.720.310.160.33
high_50.230.420.0200.580.760.280.33
MACD0.050.330.0300.710.690.530.33
daily_opengain00.220.010.390.570.850.230.32
VHF0.590.1800.10.970.260.060.31
ROC0.340.390.010.050.740.440.220.31
volume0.030.140.020.060.470.90.520.31
high0.410.20.500.0610.020.31
amount_10.020.060.030.440.20.810.640.31
alpha_540.0100.010.450.460.770.30.29
low0.450.170.370010.010.29
price_100.20.150.010.270.460.580.280.28
close_10.220.210.0300.260.870.370.28
daily_gain0.220.180.0200.470.730.30.27
price_10.1500.010.180.720.560.190.26
low_50.070.370.0100.210.980.210.26
...........................
volume_100.090.050.020.080.370.60.370.23
close_100.120.450.0200.320.470.20.23
daily_gain_100.010.120.010.030.890.450.090.23
RVI0.480.1500.110.480.320.080.23
low_100.060.430.0200.280.480.30.22
high_10.030.20.010.20.30.520.220.21
low_10.210.19000.530.190.370.21
daily_region_100.350.21000.540.270.040.2
daily_region_50.320.28000.650.160.010.2
adv_2000.150.0100.560.550.120.2
NVI0.030.47000.450.350.080.2
OBV0.170.260.010.010.30.40.210.19
ForceIndex0.160.2400.070.610.180.050.19
daily_opengain_10.050.2200.090.840.1300.19
daily_opengain_100.010.24000.940.060.010.18
alpha_230.250.1800.150.260.340.10.18
VR0.060.090.010.160.410.370.140.18
PVI0.10.310.0100.230.420.160.18
alpha_60.140.0500.011000.17
MTM_120.260.47000.220.10.040.16
PSY0.40.1100.1200.390.110.16
volume_50.270.1100.080.310.230.030.15
opn_100.070.4800.140.220.020.080.14
TRIX0.110.220.0100.20.290.120.14
amount_50.270.14000.270.240.070.14
D0.290.12000.260.110.040.12
price_500.0200.040.470.210.050.11
alpha_1200.23000.450.050.010.11
K0.20.11000.330.0300.1
J0.070.0900.030.410.080.040.1

67 rows × 8 columns

5.模型和策略

5.1 K-means 聚类分析和样本内检验

我们使用最经典的非监督学习方式——K-means聚类分析,来进行对目标变量的预测判断(简单来说只要知道涨跌情况这一个二元分类变量),所以K-means 需要输出的聚类个数为2个。至于输入的变量,我们依据之前的EFG情况,从大到小分别选取1个到67个特征变量,并进行了在样本内的检验。最后聚类内较高收益率按特征数量分布的情况,如下图所示:

最高值出现在图中最前面,在选3个特征变量(alpha_28AMAdaily_gain_1)时,对数收益率到达最高的2.048, 说明了EFG的有效性,提供了较好的特征变量。总体来看收益率随特征变量的个数变化呈V字型,前期到达最高值后下降到谷底后又迅速上升。个别数值出现异常偏离,可能是特征变量之间存在严重的共线性,至于后面的随着特征个数增加收益率逐渐上升无法做出合理解释

5.2 策略

基于之前分析结果,选择前3个特征变量alpha_28AMAdaily_gain_1作为K-means聚类分析的输入值,把目标变量分为两类,并对聚类的结果进行收益率和波动率的统计,证明得到的聚类存在两个明显不同的收益率分布,如下表所示:

可以发现两个聚类平均收益率存在明显差距,而平均的标准差差别较小。

trade_daysRc.meanRc.std
all29460.000430.01828
cluster112790.001630.01859
cluster21667-0.000490.01798

我们选取cluster1聚类中的目标变量作为买入指标,即输入当天交易日的三个特征alpha_28AMAdaily_gain_1,如果被聚类输出为cluster1,则选择买入或继续全仓持有,否则被归类到cluster2,选择卖出。具体的收益率曲线图如下所示:

可以从图中看出,该策略有着10年以上持续稳定的收益并且经历了牛市和熊市的多重洗礼,平均年化收益率接近17%,该策略发生的最大回撤是在2008-05-28至2008-11-24 对数收益率下降了0.2713,转换为普通收益率就是收益下降了31.17%,这个回撤稍微有点大。 最终策略的总收益率是2.08(对数收益率)简单来说相当于你2005年4月6日持有指数1000点,到了2017年5月18日持有指数已经变为8056点

同期基于cluster2中的目标变量的策略收益率如下:

6.模型稳健性检验

6.1 交叉验证与样本外检验

6.1.1 交叉验证(Cross Validation)

交叉验证亦称为循环估计, 是用来验证分类器/回归/聚类算法的性能的一种统计分析方法。它用于分析机器学习算法的泛化能力(generalization)。其基本思想是将原始数据(data set)进行分组,一部分作为训练集(training set),一部分作为测试集 (testing set)。首先利用训练集对算法进行训练,再利用测试集来检验得到的模型(model),以此为指标来评价算法的性能。

K折交叉验证(K-folder cross-validation) 将原始数据分成K个子集(一般是均分),将每个子集数据分别做一次测试集 (testing test),其余的K-1组子集数据作为训练集(training test),这样会得到K个模型,用这K个模型最终的验证集的分类准确率的平均数作为此K-CV下分类器的性能指标。交叉验证重复k次,每次选择一个子集作为测试集,并将k次的平均交叉验证识别正确率作为结果。

优点:所有的样本都被作为了训练集和测试集,每个样本都被验证一次。通常使用ten-folder。

实际操作——十折交叉验证 由于聚类是把数据集分成两类,我们将目标变量对数收益率这一连续变量转换为简单的二元分类变量(binary variable)。

 
xxxxxxxxxx
#把收益率这一连续变量转换为分类变量
def is_up(n):
    if n > 0:
        return 1
    else:
        return 0
    
hs300.target_c = hs300.target.map(is_up)

全部特征变量加入的十折交叉验证

 
xxxxxxxxxx
from sklearn.cross_validation import cross_val_score
from sklearn import cluster
k_means = cluster.KMeans(n_clusters=2, max_iter = 5000)  
scores1 = pd.Series(cross_val_score(k_means, hs300.data, hs300.target_c, cv=10, scoring='accuracy'))
scores1.plot(kind='line', style='r-')
plt.title('accuracy changed by different subsets',fontsize=20)
plt.show()

Top3特征变量加入的十折交叉验证

 
xxxxxxxxxx
hs300.data3 = pd.concat([hs300['AMA'],hs300['alpha_28'],hs300['daily_gain_1']], axis=1)
k_means = cluster.KMeans(n_clusters=2, max_iter = 5000)  
scores2 = pd.Series(cross_val_score(k_means, hs300.data3, hs300.target_c, cv=10, scoring='accuracy'))
scores2.plot(kind='line', style='r-',ylim=[0.40,0.65])
plt.title('accuracy changed by different subsets',fontsize=20)
plt.show()

通过上述两张图对比可以明显看出,加入top3特征变量的十折交叉验证准确度的标准差(0.02578)明显小于加入全部特征变量的标准差(0.05666),说明通过特征选择EFG最后筛选出来的特征变量,能有效提升模型的预测准确率的稳定性

6.1.2 样本外检验(Out of Sample Test)

样本外检验是防止曲线拟合的一种实际检验方法,因为我们不知道市场将来走向会如何。我们最终交易的策略依靠的是时刻变化的动态数据,而不是拿历史数据来回测。

样本外检验的作用:首先回测是在给定的测试区间内。然后同样的测试运行在一个新的测试周期并使用不同的样本数据,因此得名样本外检验。如果参数或设置在第一个区间内过度优化了,那么在新的区间性能表现不会很优异。

实际操作——样本外检验

加入所有特征变量的样本外检验

 
xxxxxxxxxx
from sklearn import cluster
k_means = cluster.KMeans(n_clusters=2, max_iter = 5000)  
k_means.fit(hs300.data)
y_pred1 = k_means.predict(out_hs300.data)
from sklearn.metrics import accuracy_score
accuracy_score(out_hs300.target_c, y_pred1)

加入Top3特征变量的样本外检验

 
xxxxxxxxxx
out_hs300.data3 = pd.concat([out_hs300['AMA'], out_hs300['alpha_28'], out_hs300['daily_gain_1']], axis=1)
k_means = cluster.KMeans(n_clusters=2, max_iter = 5000)  
k_means.fit(hs300.data3)
y_pred2 = k_means.predict(out_hs300.data3)
accuracy_score(out_hs300.target_c, y_pred2)

后者经过集成学习筛选的变量的聚类分析预测准确度(0.6522)明显高于前者(0.4348),说明特征选择EFG最后筛选出来的特征变量,能有效提升模型的预测准确率

6.2 集成学习中各子模型的预测效果,与集成学习的区别

在这一节中,我们来比较之前集成学习中各个子模型的预测效果。主要思路是取各模型下的前三个变量作为聚类分析的输入值,分别比较各模型的累积收益率和十折交叉验证的结果。

 
xxxxxxxxxx
from sklearn import cluster
yield_cluster = []
for i in range(len(col_names)):
    temp = EFG[col_names[i]].sort_values(ascending=False).index[:3]
    input_data = hs300[temp]
    k_means = cluster.KMeans(n_clusters=2, max_iter = 5000)   
    k_means.fit(input_data)
    test_labels = k_means.predict(input_data)
    long, short = ( test_labels == 1), ( test_labels == 0)
    original_data = pd.read_excel('沪深300.xlsx')
    date = pd.to_datetime(pd.Series(original_data['日期'][60:3006].values))
    res = pd.DataFrame({'date':date,'return':hs300.target}).set_index('date')
    predicted_return1 = res['return'].multiply(long) 
    predicted_return2 = res['return'].multiply(short)
    yield_cluster.append([predicted_return1.cumsum()[len(date)-1], predicted_return2.cumsum()[len(date)-1]])
cluster1cluster2
Corr.-1.541842.810479
Dis_Corr1.685098-0.41646
LR-0.1294711.398109
Lasso_AIC-0.8423572.110995
RF1.1382990.130339
RFE-0.2068971.475535
Ridge0.5171170.751521
Stability-0.3041271.572765
Mean-0.891732.160368

上表和上图表示的是各个子模型聚类分析后的两个聚类下的累积收益率,可以明显看到最后一行Mean(集成学习)2.16的对数收益率处于所有模型中的第二,说明集成学习后的模型收益率较高

accuracystd
Corr.0.5230460.040418
Dis_Corr0.4748970.0687
LR0.5006560.032538
Lasso_AIC0.4996330.025259
RF0.4640480.043179
RFE0.4894530.031453
Ridge0.5149110.050615
Stability0.509510.055883
Mean0.5040640.025649

上表和上图表示的是各个模型十折交叉验证下准确率的平均值和标准差,可以明显看到最后一行Mean的平均准确率(蓝色条形图)稍大于0.5 排名各模型第四,且它的准确率标准差(红线)0.0256 排名第二,从十折交叉验证结果来看,集成学习后的模型准确率和稳定性都较高

6.3 目标变量转换为分类变量

在这一节中,我们把原先的预测变量即第二天的收益率(数值型)转换为二元分类变量(上涨为1,下跌为0),然后进行之前类似的集成学习训练。

 
xxxxxxxxxx
#logstic回归
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression(random_state=101)
lr.fit(hs300.data,hs300.target_c)
ranks['LR'] = rank_to_dict(np.abs(lr.coef_[0]), names)
 
xxxxxxxxxx
#SVM向量支持机
from sklearn.svm import SVC  
svm = SVC(kernel='linear')  
svm.fit(hs300.data, hs300.target_c)  
ranks['SVM'] = rank_to_dict(np.abs(svm.coef_[0]), names)

新的集成特征打分器(EFG)结果如下表所示,其中Corr.与Dis_Corr都表示的是变量之间的相关系数,只是把目标变量从数值型转换分类变量,RF(Random Forest)也是一样。而LR、RFE则从普通线性回归(OLS)转换为逻辑回归(Logistic Regression)。另外新加入了支持向量机SVM(Support Vector Machine)模型。

与先前的EFG相比较,之前排在前三位的特征变量 alpha_28AMAdaily_gain_1,现分别排名7、24、19,说明之前集成学习筛选出来的特征变量具有效度。

Corr.Dis_CorrLRRFRFESVMMean
D10.670.250.570.940.320.62
low_50.150.1410.29110.6
DPO0.990.940.190.530.530.30.58
alpha_230.8210.130.530.770.080.55
CMO0.950.690.230.460.810.110.55
alpha_540.120.080.51110.610.54
alpha_28
0.10.120.750.520.980.610.52
ROC0.890.830.210.430.390.260.5
daily_region_10.740.410.240.720.690.240.5
alpha_1010.040.240.490.610.560.48
close_100.350.510.310.190.920.520.47
daily_gain_50.140.250.330.760.950.330.45
price_100.390.390.250.70.760.230.45
high_100.60.650.390.170.550.260.44
RVI0.950.680.060.530.340.080.44
close_50.370.330.250.2510.310.42
BIAS0.490.580.10.250.850.20.42
daily_gain_100.140.280.190.780.890.220.41
daily_gain_1 00.120.280.8510.170.4
low_100.350.510.110.150.90.270.39
daily_region_50.360.350.190.630.60.130.38
daily_region0.70.530.060.730.290.020.38
opn_50.160.160.340.190.970.410.38
AMA0.690.550.060.390.240.180.37
MTM_120.660.630.190.20.370.110.36
amount_10.090.270.230.630.730.240.36
volume_10.130.290.30.480.740.260.36
high_50.450.480.170.40.480.110.35
K0.670.480.070.450.310.10.35
NVI0.450.320.110.530.630.090.35
........................
TRIX0.140.470.110.150.840.180.32
opn_100.50.570.090.090.350.220.31
price0.080.070.20.570.790.180.31
daily_opengain00.110.230.560.820.130.3
volume0.210.250.110.360.660.170.3
amount0.120.250.170.20.650.320.29
close_10.030.310.270.180.610.260.28
OBV0.330.370.010.760.10.040.26
volume_100.170.110.170.560.450.130.26
PSY0.690.60.0200.150.010.24
daily_opengain_5000.130.660.50.130.24
adv_200.060.10.170.270.470.230.23
price_500.060.10.760.440.060.23
daily_region_100.30.230.020.680.130.030.23
volume_50.320.330.020.390.180.080.22
MACD0.120.210.080.410.260.140.21
alpha_120.030.290.040.730.1900.21
amount_50.310.3500.480.020.070.2
alpha_60.220.1400.8900.010.2
high0.010.060.150.130.580.210.19
high_10.150.270.010.410.050.10.17
amount_100.230.210.060.250.270.010.17
low0.020.060.10.070.560.150.16
daily_opengain_10.010.040.050.460.320.050.16
PVI0.170.210.020.370.110.030.15
daily_gain0.020.410.010.310.080.060.14
ForceIndex0.010.40.010.330.030.010.13
daily_opengain_1000.090.010.390.0600.1
open0.020.060.020.080.160.040.07
close0.010.060.030.10.210.020.07

67 rows × 8 columns

下图是根据新的EFG得出的收益率变化图,可以看出收益率波动较之前稳定,基本在1.9以上,说明利用分类变量的集成学习模型能更加有效地提升收益率且更加稳定

6.4 特征提取(降维)

6.4.1 主成分分析PCA(Principal Component Analysis)

主成分分析旨在利用降维的思想,把多个特征变量转化为少数几个综合指标。其中每个主成分都能够反映原始变量的大部分信息,且所含信息互不重复。

 
xxxxxxxxxx
from sklearn.decomposition import PCA
from sklearn import cluster
h = []
for i in range(1, 68):
    pca = PCA(n_components= i, copy=True, whiten=False)
    p = pca.fit_transform(hs300.data) 
    input_data = p
    k_means = cluster.KMeans(n_clusters=2, max_iter = 5000)   
    k_means.fit(input_data)
    test_labels = k_means.predict(input_data)
    training_cluster_1_return_rate = []
    training_cluster_2_return_rate = []
    for s in range(len(hs300.target)):
        if k_means.labels_[s] == 1 :
            training_cluster_1_return_rate.append(hs300.target[s])
        else:
            training_cluster_2_return_rate.append(hs300.target[s])
    if sum(training_cluster_1_return_rate) > sum(training_cluster_2_return_rate):
        h.append(sum(training_cluster_1_return_rate))
    else:
        h.append(sum(training_cluster_2_return_rate))

如上图所示,这是经过PCA方法处理后的按不同数量主成分组成的数据集得到的收益率数据,可以看到收益率在20个主成分之前有些许波动,而后收益率始终稳定在1.92。与之前根据特征变量数量的收益率图做对比,明显主成分分析方法得到的收益率更加稳定波动较小。主成分分析方法平均1.92的收益率,也说明了EFG筛选后的特征变量最高2.08收益率的有效性

6.4.2 线性判别式分析LDA(Linear Discriminant Analysis)

LDA与PCA非常相似也是通过对历史数据进行投影,以保证投影后同一类别的数据尽量靠近,不同类别的数据尽量分开。并生成线性判别模型对新生成的数据进行分离和预测。最大的区别是LDA在训练数据时加入了结果变量标签,而PCA是无监督学习

 
xxxxxxxxxx
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn import cluster
h = []
for i in range(1, 68):
    lda = LinearDiscriminantAnalysis(n_components= i)
    l = lda.fit_transform(hs300.data, hs300.target_c) 
    input_data = l
    k_means = cluster.KMeans(n_clusters=2, max_iter = 5000)   
    k_means.fit(input_data)
    test_labels = k_means.predict(input_data)
    training_cluster_1_return_rate = []
    training_cluster_2_return_rate = []
    for s in range(len(hs300.target)):
        if k_means.labels_[s] == 1 :
            training_cluster_1_return_rate.append(hs300.target[s])
        else:
            training_cluster_2_return_rate.append(hs300.target[s])
    if sum(training_cluster_1_return_rate) > sum(training_cluster_2_return_rate):
        h.append(sum(training_cluster_1_return_rate))
    else:
        h.append(sum(training_cluster_2_return_rate))
    print(i , h[i-1])

如上图所示,这是经过LDA方法处理后的按不同数量主成分组成的数据集得到的收益率数据,整体来看收益率非常高,最低的也大于4。对于这一结果,在代码运行的过程中报出了变量存在共线性的警告,所以该结果不具备有效性

6.5 考虑滑点和交易费用的实际交易策略

交易费用主要包括券商手续费和印花税。券商手续费方面,中国A股市场目前为双边收费,券商手续费系默认值为万分之三,即0.03%,最少5元。印花税方面,印花税对卖方单边征收,对买方不再征收,系统默认为千分之一,即0.1%。(相关代码为set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock'))

滑点是指下单的点位和最后成交的点位有差距。在实战交易中,往往最终成交价和预期价格有一定偏差,因此我们加入了滑点模式来更好地模拟真实市场的表现。在实际策略执行中,我们采用回测平台的默认参数PriceRelatedSlippage(0.00246),表示实际成交价与预期的价差为当时价格的正负0.123%。

下面列出的是在股票量化回测平台joinquant执行策略的代码,回测时间为2005-04-01至2017-05-18,模拟资金为1000万,运行频率为每天

 
xxxxxxxxxx
# 导入函数库
import jqdata
# 初始化函数,设定基准等等
def initialize(context):
    # 设定沪深300作为基准
    set_benchmark('000300.XSHG')
    # 开启动态复权模式(真实价格)
    set_option('use_real_price', True)
    # 输出内容到日志 log.info()
    log.info('初始函数开始运行且全局只运行一次')
    # 过滤掉order系列API产生的比error级别低的log
    # log.set_level('order', 'error')
    
    ### 股票相关设定 ###
    # 股票类每笔交易时的手续费是:买入时佣金万分之三,卖出时佣金万分之三加千分之一印花税, 每笔交易佣金最低扣5块钱
    set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
    #设置滑点 
    set_slippage(PriceRelatedSlippage(0.00246))
    ## 运行函数(reference_security为运行时间的参考标的;传入的标的只做种类区分,因此传入'000300.XSHG'或'510300.XSHG'是一样的)
      # 开盘前运行
    run_daily(before_market_open, time='before_open', reference_security='000300.XSHG') 
      # 开盘时运行
    run_daily(market_open, time='open', reference_security='000300.XSHG')
      # 收盘后运行
    run_daily(after_market_close, time='after_close', reference_security='000300.XSHG')
    g.buy = 买入日期
    g.sell = 卖出日期
## 开盘前运行函数     
def before_market_open(context):
    # 输出运行时间
    log.info('函数运行时间(before_market_open):'+str(context.current_dt.time()))
    # 要操作的股票:沪深300指数(g.为全局变量)
    g.security = '000300.XSHG'
## 开盘时运行函数
def market_open(context):
    log.info('函数运行时间(market_open):'+str(context.current_dt.time()))
    security = g.security
    # 取得当前的现金
    cash = context.portfolio.available_cash
    dt=context.current_dt
    # 如果上一时间点价格高出五天平均价1%, 则全仓买入
    if str(dt)[:10] in g.buy:
        # 记录这次买入
        log.info("买入 %s" % (security))
        # 用所有 cash 买入股票
        order_value(security, cash)
    # 如果上一时间点价格低于五天平均价, 则空仓卖出
    if str(dt)[:10] in g.sell:
        # 记录这次卖出
        log.info("卖出 %s" % (security))
        # 卖出所有股票,使这只股票的最终持有量为0
        order_target(security, 0)
 
## 收盘后运行函数  
def after_market_close(context):
    log.info(str('函数运行时间(after_market_close):'+str(context.current_dt.time())))
    #得到当天所有成交记录
    trades = get_trades()
    for _trade in trades.values():
        log.info('成交记录:'+str(_trade))
    log.info('一天结束')
    log.info('##############################################################')

下图为该策略(考虑交易费用和滑点)的实际回测结果,可以看出结果极为不理想,累积收益率是负的,而同期基准——沪深300指数的收益率达到238.64%。说明这个策略的实际收益率比一直持有沪深300的最简单策略都差距甚远

这张图表示的是不考虑交易费用和滑点的策略实际回测结果,可以看出较之前的回测结果,收益率显著提升。由于该策略进出操作频繁,实际收益容易受到滑点和交易费用的影响,所以当不考虑时收益率上升明显,但是其累积收益率也只是高出基准收益9%不到。这与之前预估的对数收益率2.08(转换到实际收益率为700%,即从1000点升到8000点)。所以该策略的实际可行性并不是很高

7.结论

本文创新性地把特征工程的知识运用到股票市场中,在基于沪深300指数原始数据中组合计算挖掘出总共67个特征变量,并经过特征选择方法中的集成特征打分器筛选出3个特征变量,最终基于K-means聚类分析后的结果,提出了基准的持续稳定盈利择时策略。并对该策略模型进行了一系列的稳定性检验,说明集成学习的模型能有效提升收益率和稳定性,但是策略的实际可行性还较低,需要未来做更进一步的相关研究。

目前据资料所得,国内市场量化交易策略只占市场份额1%不到,而美国市场已经发展到交易额的30%,这说明国内金融交易市场未来的量化交易非常可期,势必会迎来一轮大发展。最近微软AI首席科学家、IEEE Fellow邓力从微软离职加盟对冲基金巨头Citadel任首席人工智能官,也预示着人工智能在股票市场应用,各种量化交易研究平台,各个交易策略,各种alpha量化因子库势必造就一波别开生面的景象。

参考资料

  1. jasonfreak, 使用sklearn做单机特征工程, 博客园 http://www.cnblogs.com/jasonfreak/p/5448385.html
  2. JasonDing, 【特征工程】特征选择与特征学习, 简书 http://www.jianshu.com/p/ab697790090f
  3. 李斌等, ML-TEA: 一套基于机器学习和技术分析的量化投资算法, 系统工程理论与实践,forthcoming
  4. Distance correlation, wikipedia https://en.wikipedia.org/wiki/Distance_correlation
  5. 江嘉键, 特征选择方法探析-沪深300指数的集成特征选择和聚类分析,ricequanthttps://www.ricequant.com/community/topic/1603/特征选择方法探析-沪深300指数的集成特征选择和聚类分析
  6. 江嘉键, 沪深300指数的特征工程和聚类分析-以WorldQuant Formulaic 101 Alphas为例,ricequanthttps://zhuanlan.zhihu.com/p/21337419
  7. 陆东旭, Ipython Notebook Research Alpha下机器学习一瞥,关于跌跌涨涨的思考,ricequant https://www.ricequant.com/community/topic/103
  8. Edwin Jarvis, 干货:结合Scikit-learn介绍几种常用的特征选择方法 http://dataunion.org/14072.html
  9. Kakashadze Z. 101 Formulaic Alphas[J]. Social Science Electronic Publishing, 2016, 2016(84):72–81.

疑问

  1. 各个基模型的调参问题
  2. 聚类分析的合理性以及为什么呈现V型
  3. 集成特征打分器的原理可行性而且随机森林自身包含了集成学习
  4. Lasso_BIC 的系数全为0问题
  5. 策略在实际执行结果与聚类分析结果相差巨大

附录

附录一 —— 基础变量的名称和公式

指标名称计算公式(O:开盘价;C:收盘价;L:最低价;H:最高价;V:交易量;A:交易总额)
priceA/V
daily_gainln(C)-ln(O)
daily_regionln(H)-ln(L)
daily_opengainln(O)-前一日ln(O)

附录二 —— 衍生经典技术指标的名称和公式

指标类型指标名称计算公式(O:开盘价;C:收盘价;L:最低价;H:最高价;)指标参数
趋势指标MACDEMA(n)=前一日 EMA(n)(n-l)/(n+l)+C2/(n+l);q=12;
DIF=EMA(q)-EMA(p);p=26;
DEA=前一日 DEA(t-1)/(t+1)+DIF2/(t+1);t=9;
AMADMA(n)=n日平均值一m日平均值;n=10;
AMA(n)=n日DMA 平均值;m=50;
TRIXTRIX=(EMA(n)-前一日 EMA(n))/EMA(n)*100;n=3;
VHFVHF=(H(n)-L(n))/SUM(ABS(C-前一日 C),n);n=28;
RVICO=C-O;HL=H-L; V1=(CO+2前一日CO+2前两日CO+前三日CO)/6; V1=(HL+2前一日HL+2前两日HL+前三日HL)/6; S1=SUM(V1, n); S2=SUM(V2, n); RVI=S1/S2n=10;
震荡指标KDJK=(q-1)/q* 前一日K+1/q*RSV;q=3;
D=(p-1)/p* 前一日D+1/p*K;p=4;
J=3K-2D;
BIASMA(n) = ∑Ci/n;BIAS(n) = (C - MA(n))/MA(n)n=12;
ForceIndexForceIndex=(C-前一日 C)* 成交量;
超买超卖指标VRVR(n)=SUM(上升日成交量,n)ASUM(下降日成交量,n);n=12;
DPODPO=C-前(n/2+1)日的MA(n);n=20;
NVI如果今日成交量大于昨日成交量: NVI=前一日 NVI;
PVI如果昨日成交量大于今日成交量: NVI=前一日 NVI(1+(C-前一日 C)/前一日 C) PVI(n+1)=PVI(n)+sign(n+1)RC(n+1); RC(n+1) = [C(n+1)-C(n)]/C(n);
ROCAX=C —前N天C; BX=前N天C; ROC=AX/BX;N=12;
能量指标OBV如果C>前一日C:基期OBV加上本日成交量为本日OBV;否则,基期OBV减去本日成交量为本日OBV。
PSYPSY=n日内的上涨天数/n* 100%;n=12
动量MTMMTM(n)=C-前Lag日CLag=12;
CMOCMO=(Su-Sd)*100/(Su+Sd);其中:Su是今日收盘价与昨日收盘价(上涨日)差值加总.若当日下跌,则增加值为0; Sd是今日收盘价 与昨日收盘价(下跌日)差值的绝对值加总.若当日上涨,则增加值为0;

附录三 —— 阿尔法变量的名称和公式

指标名称计算公式(O:开盘价;C:收盘价;L:最低价;H:最高价;V:交易量)
alpha#6(-1 * correlation(O, V, 10))
alpha#12(sign(delta(V, 1)) * (-1 * delta(C, 1)))
alpha#23(((sum(H, 20) / 20) < H) ? (-1 * delta(H, 2)) : 0)
alpha#28scale(((correlation(adv20, L, 5) + ((H + L) / 2)) - C))
alpha#54((-1 * ((L - C) * (O^5))) / ((L- H) * (C^5)))
alpha#101((C - O) / ((H - L) + .001))

其中: