请 [注册] 或 [登录]  | 返回主站

量化交易吧 /  数理科学 帖子:3129637 新帖:1

随机森林在股价中的运用探讨

asjnajdja发表于:8 月 29 日 16:00回复(1)

目前机器学习已经广泛地应用在对于金融市场的分析、预测中,在资产配置上更是大显神威。但是,在浩如烟海的机器学习算法中,到底哪种算法能取得更优的预测效果呢?在目前主流的机器学习算法中,大致可以划分为两大类,一类是回归类算法,主要用于对于连续型数据的预测,第二类就是分类算法,主要是用于对离散型数据的预测,那么在股价预测上我们究竟运用那种类型的算法比较好呢?发表在《Applied Mathematical Finance》的这篇文章利用随机森林算法对股价d天之后的涨跌方向进行了预测。发现相比于SVM、线性判别分析等模型,随机森林可以取得更优秀的预测结果:能够达到85%-95%的准确率。本文笔者试图通过个股对应的大盘,行业和个股的三个维度成交量,价格(指数),运用随机森林算法进行分析研究。
摘要:
之前大部分人对于股价预测分析中,都把股价变化,看出是一个连续性的问题,采用回归模型进行预测,其结果总是不尽人意,因为影响股价的因素实在太多,而且每个因素的影响强度,在不同的时间点,也存在较大的差异性,因此我们可以化繁为简,把它转化成一个分类问题,从而最小化预测误差,本文章将预测股价的走势看做一个二分类问题(涨or跌),为了使预测更具有操作性,我们把股价涨幅划分为(f(x)>=2% or f(x)<2%),使用集成机器学习建模解决。文章里利用个股对应的大盘指数的涨跌和成交量的变化率,个股对应的行业指数的涨跌和成交量的变化,已经个股的涨跌和成交量的变化,作为分类的特征,对随机森林模型进行训练。最后发现,模型中决策树个数增加,模型准确率增加并有收敛趋势;并且,预测的时间窗口越长,模型越准确。 随机森林简介 在正式进入文章前,先对随机森林算法给出简单的介绍。 随机森林算法是一种非线性模型,顾名思义,是将多个决策树集成为森林的一种模型。理解随机森林的关键有两点:随机抽样和多数投票。 首先,对于每一个决策树,从全样本集中有放回地随机抽取训练集。本文决策树分类的标准是特征矩阵X里面的成交量变化率和价格变化率,一直利用特征分类直到基尼不纯度很小达到要求。 这些决策树独立预测,然后对每个决策树预测的结果进行投票,票数最多的成为随机森林的预测结果。这样避免了单个决策树的过拟合。 由于随机抽样,每个决策树使用的都不是全样本(大约只有2323的样本被抽到),没有被抽到的样本是这个决策树的非样本集(袋外样本 Out of Bag Sample)。对于所有决策树产生的袋外样本,对每个样本,计算它作为oob样本的树对它的分类情况(约1313的树),然后以简单多数投票作为该样本的分类结果,最后用误分个数占样本总数的比率作为随机森林的OOB误分率。所以,OOB偏差越小,说明误分类的比例越低,随机森林分类越准。 研究思路 我们先运用600519贵州茅台自2005/1/1到2019/8/1/以来的数据最为模型的训练种子数据,选择贵州茅台的作为训练集的主要原因是,股价连续性好,没有停牌、连续一字涨停或者跌停,这样训练出来的模型,更具有普适性。然后运用上证50的50只股票作为测试集,进行测试模型的效果。 数据的收集和预处理 在数据的收集上,我们收集了上证50的50只股票的2005/1/1到2019/8/1以来的价格和成交量数据,用来预测股价第二的涨幅是否大于2%,对于成交量和价格,我们并没有做太多的处理,仅仅是把它转变成变化率,因为我们主要是做短线的预测,要尽可能的保持数据信息的完整性。 线性可分性测试 在建立模型之前,笔者首先对(f(x)>=2% or f(x)<2%)这两类数据进行了线性可分测试,结果发现股票走势预测问题不是线性可分的(投影到二维空间发现凸包有大量重合),所以所有和线性判别分析有关的算法比如SVM都是不适用的。随机森林作为一种非线性算法,可以避免这种情况,在接下来的股票走势预测研究中有重要应用意义。
随机森林的建立
我们先建25棵树,进行十次十组交叉验证,测试的结果是平均成功率在83%左右。确定分类算法对于股价的预测具备显著的效果,随后我们对于最佳种树数量进行参数估计,
Img
先在range(0,200,10)的范围上,确定一个大致的范围,发现最高值在140附近,随后我们在130——150的范围内进行测试,最终确定最佳种树数量是140棵,交叉验证成功在0.8424。
Img
检查模型的普适性
我们保持上一步调优后的模型,随后我们用600519训练的模型,对上证50成分股,自2005/1/1到2019/8/1/以来的数据进行测试,验证学习到的模型具备普遍适用性。50只个股的最终成功率分布图如下,最大值为0.9492,最小值为0.7254,平均值为0.8425。
Img

对成功率进行排序,低于80%的成功率的只有十个,高于90%的有8个,大部分数据集中在中位数附近,显示模型的预测具有较强的稳定性。
Img
Img
模拟交易
本来想也做一个十年的模拟交易回测看看最后的效果,即使使用历史训练好的模型,在上证50中进行选股,无奈选股时间太久,就此作罢。理想状态应该是每天都要把新数据加进来训练新的模型,最好在14:50跑完选股结果,然后收盘前进行买进,第二天出局。以后有好的想法再进行改进!

运用随机森林进行股价预测¶

from jqdata import *
import numpy as np
import pandas as pd
import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.linear_model import LinearRegression as LR
from sklearn.model_selection import train_test_split
from sklearn.model_selection import cross_val_score
import warnings
import time
warnings.filterwarnings('ignore') 

数据预处理¶

#获取个股所属申万行业代码,因为目前只有申万行业行情
def secu_industry_code(code):
    a=[]
    aa=get_industry(code, date=None)
    for i in aa[code].keys():
        if 'sw' in i:
            a.append(aa[code][i]['industry_code'])
    return a
code="600008.XSHG"
#测试
bb=secu_industry_code(code)
bb
['801164', '801160', '851621']
#获取个股对应大盘的指数代码
def Correspondence_index(code):
    a=code[:2]
    if a=='60':
        b='000001.XSHG'
    elif a=='30':
        b='399006.XSHE'
    elif a=='00':
        if code[:3]=='000':
            b='399001.XSHE'
        else:
            b='399005.XSHE'
    return b           
def handle_table(code,num,a='g'):
    #num为获取数据的长度
    #a为修改列名称的标签,无特别意义
    secu=attribute_history(code, num, unit='1d',
            fields=[ 'close', 'volume',],
            skip_paused=True, df=True, fq='pre')
    #计算价格的涨跌幅,并删除空行
    secu_change = secu.close.pct_change()
    #新增涨跌幅列,乘以100转换成百分比
    secu['chg'+a]=secu_change*100
    returns=secu
    #returns['close']=secu.close
    returns.rename(columns={'close': 'close'+a},inplace=True)
    returns.rename(columns={'volume': 'volume'+a},inplace=True)
    #增加‘date’列,值为原来的索引
    returns['date']=returns.index
    #按日期降序排列
    returns=returns.sort_values('date', ascending=False)
    #重置索引,丢掉原来的索引
    returns=returns.reset_index(drop = True)
    return returns
#获取个股和行业以及对应大盘行情数据
def secu_industry_quotation(code,limit=None,date=None):
    #limit获取数据的行数,默认全部
    #date获取某日期之前的数据,默认当天
    #获取行业板块行情
    bb=secu_industry_code(code)
    a=1
    #如果时间没有指定,那么取当前时间
    if date:
        pass
    else:
        date=time.strftime('%Y-%m-%d',time.localtime())
    for i in bb:
        df1=finance.run_query(query(
            finance.SW1_DAILY_PRICE.date,
            finance.SW1_DAILY_PRICE.close,
            finance.SW1_DAILY_PRICE.volume,
            finance.SW1_DAILY_PRICE.change_pct
            ).filter(
            finance.SW1_DAILY_PRICE.code==i,
            finance.SW1_DAILY_PRICE.date<=date).order_by(
            finance.SW1_DAILY_PRICE.date.desc()
        ).limit(limit))
        #批量修改列名称
        df1=df1.rename(columns=lambda x: x + str(a))
        #把date列改回来,作为合并轴
        df1=df1.rename(columns={'date'+str(a):'date'})
        if a==1:
            df=df1
        else:
            df=df.merge(df1,on='date')
        a+=1
    #把‘date’列的数据的object类型,转换成datetime64[ns]类型
    df['date']=df['date'].astype('datetime64[ns]')
    num=len(df)
    #获取个股行情数据
    df1=handle_table(code,num,a='g')
    #个股行情和行业行情表合并
    df=df.merge(df1,on='date')
    #获取对应大盘行情
    c=Correspondence_index(code)
    df2=handle_table(c,num,a='d')
    df=df.merge(df2,on='date')
    #个股行情和行业行情表合并
    return df
df=secu_industry_quotation(code,date='2018-08-22')
df.head()
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
date close1 volume1 change_pct1 close2 volume2 change_pct2 close3 volume3 change_pct3 closeg volumeg chgg closed volumed chgd
0 2018-08-22 2850.90 44865023.0 -0.74 2017.62 9.458341e+08 -1.08 2863.96 44865023.0 -0.74 3.80 6713265.0 -1.298701 2714.61 9.366464e+09 -0.703043
1 2018-08-21 2872.25 48394439.0 0.72 2039.62 9.485238e+08 0.55 2885.40 48394439.0 0.72 3.85 7280464.0 0.522193 2733.83 1.135868e+10 1.310372
2 2018-08-20 2851.60 64515763.0 -0.21 2028.56 1.083873e+09 0.31 2864.66 64515763.0 -0.21 3.83 9113509.0 0.000000 2698.47 1.107842e+10 1.105295
3 2018-08-17 2857.49 68381665.0 -1.90 2022.20 1.152801e+09 -1.05 2870.58 68381665.0 -1.90 3.83 10027556.0 -1.288660 2668.97 1.124453e+10 -1.338908
4 2018-08-16 2912.92 68039217.0 -0.68 2043.57 1.078357e+09 -1.09 2926.27 68039217.0 -0.68 3.88 11763746.0 -1.772152 2705.19 1.202779e+10 -0.663543
#把成交量都变成变化率
def change_vol(df):
    #获取columns列表
    listdf=list(df)
    for i in listdf:
        if 'volume' in i:
            #计算变化率
            dfs1 = df[i].pct_change(-1)
            a='vol'+i[-1]
            df[a]=dfs1
    #删掉nan行
    df.dropna(inplace=True)
    return df
df1=change_vol(df)
df1
#删除指定的列
def del_columns(df,col):
    dfx=df
    for i in df.columns:
        if col in i:
            dfx.drop(i,axis=1,inplace=True)
    return dfx
df2=del_columns(df1,'close')
df2=del_columns(df2,'volume')
df2
#生成标签列,Y=secu_change的下一列值列。
def generate_label(df):
    #生成新列“y”等于‘secu_change’的下一个值
    df['y']=df['chgg'].shift(1)
    #生成第二天涨幅大于2的(0,1)标签
    df['y2']=np.where(df.y>2,1,0)
    #删除包含‘nan’的行
    df=df.dropna()
    df=df.reset_index(drop=True).dropna()
    return df  
df3=generate_label(df2)
df3
#获取数据X,Y数据集
def current_data(code):
    #获取个股、行业、大盘行情数据列表
    df=secu_industry_quotation(code)
    #把成交量转化成变化率
    df1=change_vol(df)
    #删除多余的列
    df2=del_columns(df1,'close')
    df2=del_columns(df2,'volume')
    #添加y标签
    df3=generate_label(df2)
    df3=df3.dropna()
    X,Y=df3.iloc[:,1:11],df3.y2
    return X,Y   

模型调参¶

#运用随机森林进行预测
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
code="600519.XSHG"
X,Y=current_data(code)
cfc_1=[]
#进行十次十组交叉验证
for i in range(10):
    cfc=RandomForestClassifier(n_estimators=25)
    cfc_s=cross_val_score(cfc,X,Y,cv=10).mean()
    cfc_1.append(cfc_s)
plt.plot(range(1,11),cfc_1,label='RondomForst')
plt.legend(loc='best')
plt.show()
#学习曲线,确定n_estimators参数,先range(0,200,10),确定范围,然后,再在最大值上下十个点,进行精确定位n_estimators
superpa=[]
for i in range(130,150):
    cfc=RandomForestClassifier(n_estimators=i+1,n_jobs=-1)
    cfc_s=cross_val_score(cfc,X,Y,cv=10).mean()
    superpa.append(cfc_s)
print(max(superpa),superpa.index(max(superpa)))
plt.figure(figsize=[20,5])
plt.plot(range(130,150),superpa)
plt.show()
0.8424104572764785 10

训练和保存调参后的模型¶

#训练调试好的参数的模型
#初始模型我们以600519的数据进行模型训练。
code="600519.XSHG"
cfc=RandomForestClassifier(n_estimators=140,n_jobs=-1)
X,Y=current_data(code)
Xtrain,Xtest,Ytrain,Ytest=train_test_split(X,Y,test_size=0.3,random_state=10)
cfc.fit(Xtrain,Ytrain)
cfc.score(Xtest,Ytest)
0.830945558739255
#保存训练好的模型
from sklearn.externals import joblib
save='tch'+'cfc'+'.pkl'
joblib.dump(cfc,save)
['tchcfc.pkl']

检查模型的普遍有效性¶

from sklearn.externals import joblib 
code="600835.XSHG"
X,Y=current_data(code)
model=joblib.load('tchcfc.pkl')
model.score(X,Y)
0.8310893512851897
#使用上证50中个股,对模型进行有效性测试
b=get_index_stocks('000016.XSHG', date=None)
score=[]
for code in b:
    X,Y=current_data(code)
    model=joblib.load('tchcfc.pkl')
    try:
        b=model.score(X,Y)
    except ValueError:
        pass
    score.append(b)
plt.plot(range(len(score)),score)
plt.scatter(range(len(score)),score,marker='o',label='成功率')
plt.legend(loc='best')
plt.show()
np.array(score).mean()
0.8426725519841562
np.array(score).max()
0.9493272258803321
np.array(score).min()
0.7254901960784313
b=np.array(score)
c=np.sort(b)
plt.plot(range(len(c)),c)
plt.scatter(range(len(c)),c,marker='o',label='成功率由小到大')
plt.legend(loc='best')
plt.show()
c
array([0.7254901960784313, 0.735202492211838, 0.7550415183867141,
       0.7736842105263158, 0.7853598014888338, 0.7853598014888338,
       0.7858726639214444, 0.792544109277177, 0.7955498226378588,
       0.7982101616628176, 0.8013201320132013, 0.8025078369905956,
       0.8034083992696287, 0.8095657276995305, 0.8100830707533658,
       0.8171296296296297, 0.8193493150684932, 0.81941431670282,
       0.8294044665012407, 0.8301260783012607, 0.8303091397849462,
       0.8313458262350937, 0.8357785139611926, 0.8358050847457628,
       0.8403483309143687, 0.8444173276388042, 0.84472049689441,
       0.8490510948905109, 0.8504901960784313, 0.8522694420352703,
       0.8533791523482245, 0.8550555230859147, 0.8568129330254042,
       0.8578401464307505, 0.8629903354178511, 0.8662420382165605,
       0.8679829655439412, 0.8704441913439636, 0.871216999356085,
       0.8734177215189873, 0.8883826879271071, 0.896910678307589,
       0.9107027724049, 0.9123050259965338, 0.9227586206896552,
       0.922915940006976, 0.9230769230769231, 0.9396170839469808,
       0.943089430894309, 0.9493272258803321])
#密度曲线
import seaborn as sns
sns.kdeplot(c, shade=True,label='密度曲线')
plt.legend()
plt.show()

调用保存模型进行预测¶

from sklearn.externals import joblib
#预测模型
def predict_data(code,date):
    df=secu_industry_quotation(code,limit=2,date=date)
    df1=change_vol(df)
    df1=del_columns(df,'close')
    df1=del_columns(df,'volume')
    x=df.iloc[:,1:11]
    model=joblib.load('tchcfc.pkl')
    a=model.predict(x)
    return a
#进行选股
def check_stock(date=None):
    #进行选股
    a=[]
    security=get_index_stocks('000016.XSHG', date=date)
    for code in security:
        try:
            if predict_data(code,date)==1:
                g.a.append(code)
        except :
            pass
    return a
 
a=get_trade_days(start_date='2010-8-1', end_date=None, count=None)
for i in a:
    a=check_stock(date=i)
    if len(a)>0:
        print(a)
 
'/home/jquser/个股行业分析'
 

全部回复

0/140

量化课程

    移动端课程