在我上一篇文章的结尾,我提到了我渴望通过机器学习的眼镜“看到”股票市场。更具体地说,我想找到一种方法,通过历史数据学习,预测未来的演变。通过阅读几乎所有关于机器学习和金融市场交叉点的可用公共文章,我偶然发现了洛佩兹·德·普拉多的书《金融机器学习进展》。我对作者的科学正统感到印象深刻,这肯定有助于缩短我的学习曲线。然而,几乎所有在书中精心制作的最复杂的技术在我的案例中都没有产生成果。我不得不想出自己的一套特征。
最终,我根据自己对市场的理解开发了自己的特征组合,以正确预测股票回报的前一天。我还为更长时间跨度开发了一些模型。后者不在本文的范围之内。
我认为,在这段旅程中,最大的挑战是克服高信噪比的障碍。表现良好/聪明的市场参与者(聪明的钱),使用不同的技巧来掩盖他们在市场中的干预。他们不断制造假牛/熊触发器,以误导市场的其他人。制造不确定性,并利用这种不确定性长期获得丰厚的回报。
分离噪音和信号是新的前沿,机器学习在恰当应用时被证明能够完成这项工作。
动机:
由于上市金融资产数量的增加和复杂策略的采用,处理金融市场变得越来越复杂。除非你愿意把大部分时间都投入到其中,否则使用手动技术来监控大量股票已经变得不可能。
经典算法提供了一种方式来克服这些挑战。它们将交易者自己的市场理解转化为自动交易系统。它可以监控价格变化,并在检测到信号时执行订单。它们大多数时候很冗长,因为我们必须包括所有的情况。
重要问题: 机器学习是否能够超越交易者自己从市场学习和检测对人类大脑来说不可分辨的行为的能力?
我们将在本文中提供明确的答案。
数据收集:
我们需要为研究的每个股票收集准确的历史数据(开盘价、最高价、最低价、收盘价和成交量)。每个股票都展现出不同的行为。因此,每个股票都将有自己的机器学习模型。
你可以从不同的来源获取免费的历史数据:
1- 你可以在 Medium 上找到不同的文章,介绍了执行此操作的 API 和 Python 库。数据有时会很混乱。你需要执行各种操作来清理它。
2- 你还可以从允许爬取的网站收集数据。你可以找到许多解释如何执行此操作的文章。
一旦我们为每个股票获得了数据(时间序列),我们需要使它平稳。
时间序列平稳性:
在最直观的意义上,平稳性意味着生成时间序列的过程的统计属性不随时间变化而改变。它并不意味着序列不随时间变化而变化,只是它变化的方式本身不随时间变化。
如果股票时间序列是非平稳的,机器学习模型将无法随时间学习。股票的统计属性将随时间变化,因此将没有什么可以学习的。
我们所保留的平稳性定义是保持时间内恒定的平均值和方差。为使我们的数据平稳,我们需要执行分数差分:
import numpy as npImportinguseful toolsimport pandas as pdimport pywtimport matplotlib.pyplot as pltimport statsmodels.tsa.stattools as tsfrom sklearn.preprocessing import normalizefrom statsmodels.tsa.stattools import adfullerimport seaborn as snsdf = pd.read_csv(‘C:/Users/Desktop/yourstock.csv’, parse_dates=[‘Date’], index_col=[“Date”]) # opening historical data using pandadf_log=pd.DataFrame() # creating the log tabledf_fd=pd.DataFrame() # creating the fractional differentiation table
############# 应用对数变换以消除指数方差 ##########
df_log[‘Open’]= np.log(df.Open)df_log[‘High’]= np.log(df.High)df_log[‘Low’]= np.log(df.Low)df_log[‘Close’]= np.log(df.Close)df_fd=df # reinitializing the table to df
############# 生成分数差分时间序列 ###################
df_fd[[‘Open_dff’]]=fracDiff_FFD(df_log[[‘Open’]].resample(‘1D’).last().dropna(),d=0.21728515625,thres=0.00005)df_fd[[‘High_dff’]]=fracDiff_FFD(df_log[[‘High’]].resample(‘1D’).last().dropna(),d=0.22807397643208566 ,thres=0.00005)df_fd[[‘Low_dff’]]=fracDiff_FFD(df_log[[‘Low’]].resample(‘1D’).last().dropna(),d=0.2265625,thres=0.00005)df_fd[[‘Close_dff’]]=fracDiff_FFD(df_log[[‘Close’]].resample(‘1D’).last().dropna(),d=0.2265625,thres=0.00005)
d 是分数差分常数,使时间序列保持平稳,而不会删除太多的预测记忆。d 由以下代码生成:
def GetMin(Stock,thres,d_0,d_1,tick=’Close’):from statsmodels.tsa.stattools import adfullerpath,instName=’C:/Users/Desktop/’, Stock_name # you need to write your pathout0=pd.DataFrame(columns=[‘adfStat’,’pVal’,’lags’,’nObs’,’95% conf’,’corr’])df0=pd.read_csv(path+instName+’.csv’,index_col=[‘Date’],parse_dates=[‘Date’])df1=np.log(df0[[tick]]).resample(‘1D’).last()df1=df1.dropna()d0=d_0d1=d_1count=10delta =10while delta > 0.01:if count==10 or count==0:df2=fracDiff_FFD(df1,d0,thres)df2=adfuller(df2[tick+’_fd’],maxlag=1,regression=’c’,autolag=None)adS0= df2[0]if count==10 or count==1:df2=fracDiff_FFD(df1,d1,thres)df2=adfuller(df2[tick+’_fd’],maxlag=1,regression=’c’,autolag=None)adS1= df2[0]d2=(d0+d1)/2df2=fracDiff_FFD(df1,(d0+d1)/2,thres)corr=np.corrcoef(df1.loc[df2.index,tick],df2[tick+’_fd’])[0,1]df2=adfuller(df2[tick+’_fd’],maxlag=1,regression=’c’,autolag=None)adS3= df2[0]if adS3>df2[4][‘5%’]:d0 = (d0+d1)/2count=0else:d1= (d0+d1)/2count=1delta = abs(adS3-df2[4][‘5%’])return d2
你可以在上面提到的书中找到 fracDiff_FFD 和 getWeights 代码。
非常非常重要! :如果你处理的是非常长时间跨度的大型时间序列,起伏陡峭,分数差分将无法使你的数据平稳。模型将区分两个独立的群体,基本上学会偏爱其中一个时期而不是另一个时期。对于机器学习来说,时期的选择非常关键。我们不能盲目地应用理论,希望获得最佳结果。
生成特征:
现在,我们可以开始使用我们的时间序列,提取特征,为模型提供数据。为了本文的目的,我将介绍两个特征:
1- 弹簧效应:是股票价格与其小波变换之间的差(可以解释为价格偏离平均值的近似值)。对于那些不熟悉小波变换的人来说,它们比傅里叶变换更复杂,在某种程度上它们保留了时间。我们可以提取小波分量,删除与噪声相关的分量,然后重新组合剩余分量以重构初始时间序列(信号),而不带噪声。由于市场时间序列的高信噪比,删除一个或两个分量就会删除大部分的预测记忆。
要执行此变换,你需要以下代码:
df_fd[‘spring_effect’]=np.nandf_fd[‘spring_effect_Open’]=np.nandf_fd[‘spring_effect_High’]=np.nandf_fd[‘spring_effect_Low’]=np.nandf_fd.loc[264:,[‘spring_effect’]] = [[(df_fd[‘Close_dff’][i]-waveletfilter(df_fd[‘Close_dff’][i-264+1:i+1])[264–1])/waveletfilter(df_fd[‘Close_dff’][i+1–264:i+1])[264–1]] for i in range(264,len(df_fd))]df_fd.loc[264:,[‘spring_effect_Open’]] = [[(df_fd[‘Open_dff’][i]-waveletfilter(df_fd[‘Open_dff’][i-264+1:i+1])[264–1])/waveletfilter(df_fd[‘Open_dff’][i-264+1:i+1])[264–1]] for i in range(264,len(df_fd))]df_fd.loc[264:,[‘spring_effect_Low’]] = [[(df_fd[‘Low_dff’][i]-waveletfilter(df_fd[‘Low_dff’][i-264+1:i+1])[264–1])/waveletfilter(df_fd[‘Low_dff’][i-264+1:i+1])[264–1]] for i in range(264,len(df_fd))]df_fd.loc[264:,[‘spring_effect_High’]] = [[(df_fd[‘High_dff’][i]-waveletfilter(df_fd[‘High_dff’][i-264+1:i+1])[264–1])/waveletfilter(df_fd[‘High_dff’][i-264+1:i+1])[264–1]] for i in range(264,len(df_fd))]Where waveletfilter is given by the following code:def waveletfilter(signal, thresh = 0.2, wavelet=”sym5", mode=’symmetric’): # in addition to sync5, there are many other wavelets you can choose fromthresh = thresh*np.nanmax(signal)coeff = pywt.wavedec(signal, wavelet, mode)reconstructed_signal = pywt.waverec(coeff[:-4]+ [None] * 4, wavelet, mode)return reconstructed_signal
2- 第二个特征是笑容(波动率)。在不同的时间段内很容易编写:
df_fd[‘Smile_Close_2’]=df_fd[‘Close_dff’].rolling(2).std().diff(periods=1)df_fd[‘Smile_Close_3’]=df_fd[‘Close_dff’].rolling(3).std().diff(periods=1)df_fd[‘Smile_Close_4’]=df_fd[‘Close_dff’].rolling(4).std().diff(periods=1)df_fd[‘Smile_Close_5’]=df_fd[‘Close_dff’].rolling(5).std().diff(periods=1)df_fd[‘Smile_Close_6’]=df_fd[‘Close_dff’].rolling(6).std().diff(periods=1)df_fd[‘Smile_Close_7’]=df_fd[‘Close_dff’].rolling(7).std().diff(periods=1)df_fd[‘Smile_Close_8’]=df_fd[‘Close_dff’].rolling(8).std().diff(periods=1)
训练模型:
为了从时间序列中学习,深度学习是无用的,无论你添加多少层。最有效的模型是分类器。有装袋分类器和随机森林分类器(RF)。它们都有优缺点。例如,RF 的计算复杂度低于装袋分类器。你可以尝试两者并查看哪一个解决了你的技术问题并提供了最佳结果。
在这个例子中,我们将我们的特征提供给分类器,以学习在什么条件下,市场会产生每日超过1%的回报(标记为1),以及什么条件下不会(标记为-1)。
导入有用的工具
from sklearn.utils import resamplefrom joblib import dump, loadfrom imblearn.ensemble import BalancedBaggingClassifier,BalancedRandomForestClassifierfrom sklearn.multiclass import OneVsRestClassifier########### labelling daily returns above and below 1%###############df_fd[‘bin’]= np.nandf_fd.loc[:-1,[‘bin’]] = [[1] if (df_fd[‘Close_dff’][i+1]-df_fd[‘Close_dff’][i])>=0.01 else [-1] for i in range(0,len(df_fd)-1)]########### Training the model ###############df1_100=df_fd[[‘spring_effect’,’spring_effect_Open’,’spring_effect_High’, ‘spring_effect_Low’,‘Smile_Close_2’,’Smile_Close_3',’Smile_Close_4'’Smile_Close_5', ‘Smile_Close_6’,‘Smile_Close_7’, ‘Smile_Close_8’, ‘bin’]][:-48]df1_100_minority= df1_100[df1_100[‘bin’]==1] # we separate the two populations to match the sizedf1_100_majority= df1_100[df1_100[‘bin’]==-1]df1_100_majority_ = resample(df1_100_majority,replace=False, # sample with replacementn_samples=int(df1_100.bin.value_counts()[1]), # to match minority class.random_state=None)df1_100= pd.concat([df1_100_minority,df1_100_majority_]).sort_index()df1_100=df1_100.dropna()y_col = ‘bin’x_cols = list(df1_100.columns.values)x_cols.remove(y_col)X_train = df1_100[x_cols].valuesY_train = df1_100[y_col].valuescls = BalancedRandomForestClassifier(n_estimators=1000,class_weight=’balanced_subsample’, criterion=’entropy’)cls.fit(X_train, Y_train) # here you train your modeldump(cls, ‘models/model_name.joblib’) # here you save your model for future use.
现在你可以使用你生成的模型来预测未见过的数据。
预测:
现在我们可以加载模型并使用未见过的数据进行预测。可以这样执行:
from joblib import loadfrom imblearn.ensemble import BalancedRandomForestClassifiercls_rn_for = load(‘models/model_name.joblib’)x_cols = list(df_forecast.columns.values) # df_forecast being the unseen data features. we can generate them using the above methodsX_forecast = df_forecast[x_cols].valuesprobas = cls_rn_for.predict_proba(X_forecast) # using predictive power of the model to generate probabilitiesdf2[‘pro’]= probas[:,1] # add probability column to the initial tableTo add more precision, we could generate multiple models for each stock, use them to generate multiple predictions then average the results.df2[‘avg’] = df2.filter(like=’pro_’).mean(axis=1)df2 = df2[[‘vol_1’,’Close’,’spring_effect’,’rn’,’avg’]]print(df2)
以下表格显示结果:当我们看到这些结果时,可能会认为预测并不准确。但是当我们仔细检查这些数字时,我们会发现当市场波动很大(超过-4%)时,结果是相当不错的。例如,在9月25日,市场下跌了近8%,该模型显示出83%的概率,表明下一个日回报将高于1%。事实证明,第二天市场上涨了超过4%。我尝试了这种方法来处理不同的资产类别,每次都获得了一致的结果。
我的目的不是分享最赚钱的策略,而是为那些难以实现自己的机器学习模型的人提供一种先进的方法来有效地实现他们的策略。
现在,你有了生成分类器来预测未来发展的方法,你可以利用自己的技能和市场理解来创建自己的模型。
感谢你的关注。
译自:https://medium.com/@rafikrahoui/effective-use-of-machine-learning-to-trade-stocks-b0b8a304b6d7
评论(0)