
获取市场优势的秘诀(第二部分):预测技术指标
引言
在电子交易环境中应用机器学习的投资者面临着诸多挑战,而现实情况是,许多投资者并未能实现他们预期的结果。本文旨在强调在我看来,为什么有志于成为算法交易者的人可能无法实现与他们策略复杂性相匹配的满意回报。我将展示为什么预测金融证券的价格往往难以超过50%的准确性,以及如何通过转而预测技术指标值来提高准确性至约70%。本指南将提供时间序列分析最佳实践的分步指导。
阅读完本文后,您将能够深刻理解如何提升机器学习模型的准确性,并比使用Python和MQL5的其他参与者更有效地发现市场变化的领先指标。
预测指标值
我们将从MetaTrader 5终端获取历史数据,并使用标准的Python库进行分析。这一分析将表明,预测指标值的变化比预测证券价格的变化更有效。这是因为我们只能部分观察到影响证券价格的因素。实际上,由于影响证券价格的因素数量众多且复杂,我们无法为每一个因素建模。然而,我们可以完全观察到影响技术指标值的所有因素。
首先,我将展示这一原理,然后在讨论结束时解释为什么这种方法效果更好。通过先看到原理在实践中发挥作用,理论解释将更容易理解。让我们从选择图表上方菜单中的交易对象列表图标开始。
我们在这里的目标是获取数据:
- 打开您的MetaTrader 5终端。
- 在图表上方的菜单中选择交易对象列表图标。
- 选择您希望分析的交易对象和时间框架。
- 将历史数据导出为以逗号分隔各值的文件(csv)。
图1:获取历史数据
搜索您想要建模的交易品种。
图2:搜索你想要的交易品种
之后,在菜单中选择“bars”选项,并确保请求尽可能多的数据。
图3:请求历史数据
在底部菜单中选择导出K线图数据,这样我们就可以开始在Python中分析我们的数据了。
,
图4:导出我们的历史数据
通常,我们首先导入所需的库。
#Load libraries import pandas as pd import numpy as np import matplotlib.pyplot as plt
“接下来,我们读取历史市场数据。请注意,MetaTrader 5 终端导出的 csv 文件是以制表符分隔的,因此,我们在调用 pandas 的 read_csv 函数时,需要将制表符标记传递给 separator 参数。
#Read the data csv = pd.read_csv("/content/Volatility 75 Index_M1_20190101_20240131.csv",sep="\t") csv
在读取历史数据后,它看起来会像这样。我们需要对列标题进行一些重新格式化,并添加一个技术指标。
图5:MetaTrader 5 终端的历史数据
我们将重新命名每列。
#Format the data csv.rename(columns={"<DATE>":"date","<TIME>":"time","<TICKVOL>":"tickvol","<VOL>":"vol","<SPREAD>":"spread","<OPEN>":"open","<HIGH>":"high","<LOW>":"low","<CLOSE>":"close"},inplace=True) csv.ta.sma(length= 60,append=True) csv.dropna(inplace=True) csv
图6:格式化我们的数据
现在我们可以定义输入参数了。
#Define the inputs predictors = ["open","high","low","close","SMA_60"]
接下来,我们将对数据进行缩放,以确保模型能够得到充分的训练。
#Scale the data
csv["open"] = csv["open"] /csv.loc[0,"open"]
csv["high"] = csv["high"] /csv.loc[0,"high"]
csv["low"] = csv["low"] /csv.loc[0,"low"]
csv["close"] = csv["close"] /csv.loc[0,"close"]
csv["SMA_60"] = csv["SMA_60"] /csv.loc[0,"SMA_60"]
我们将把这项任务当作一个分类问题来处理。我们的目标是分类。目标值为1表示该品种的价格在接下来的60根蜡烛图(或时间单位)内上涨,而目标值为0则表示在相同的时间范围内价格下跌。请注意,我们有两个目标。一个目标是监测收盘价的变化,而另一个目标是监测移动平均线的变化。
对于移动平均线变化,我们将采用相同的编码模式,即目标值为1表示未来60根蜡烛图内的移动平均值将上升,相反,目标值为0则表示在接下来的60根蜡烛图内移动平均值将下降。
#Define the close csv["Target Close"] = 0 csv["Target MA"] = 0
确定您想要预测的未来时间范围。
#Define the forecast horizon look_ahead = 60
对目标值进行编码。
#Set the targets
csv.loc[csv["close"] > csv["close"].shift(-look_ahead) ,"Target Close"] = 0
csv.loc[csv["close"] < csv["close"].shift(-look_ahead) ,"Target Close"] = 1
csv.loc[csv["SMA_60"] > csv["SMA_60"].shift(-look_ahead) ,"Target MA"] = 0
csv.loc[csv["SMA_60"] < csv["SMA_60"].shift(-look_ahead) ,"Target MA"] = 1
csv = csv[:-look_ahead]
我们将在相同的数据集上应用同一组模型,请记住,唯一的区别是,第一次我们的模型将尝试预测收盘价的变化,而在第二次测试中,它们将尝试预测技术指标(在我们的例子中为移动平均线)的变化。
在定义了我们的目标之后,我们可以继续导入分析所需的模型。
#Get ready from sklearn.discriminant_analysis import LinearDiscriminantAnalysis from sklearn.linear_model import LogisticRegression from xgboost import XGBClassifier from sklearn.neural_network import MLPClassifier from sklearn.metrics import accuracy_score from sklearn.decomposition import PCA from sklearn.model_selection import TimeSeriesSplit
我们将准备一个时间序列分割来评估验证误差较低的位置。此外,我们将使用sklearn中的主成分分析(PCA)函数来转换我们的输入数据。这一步是必要的,因为我们的输入列可能存在相关性,这可能会阻碍模型的学习过程。通过执行PCA,我们将数据集转换为一种形式,确保输入之间不存在相关性,从而提高模型的性能。
#Time series split splits = 10 gap = look_ahead models_close = ["Logistic Regression","LDA","XGB","Nerual Net Simple","Nerual Net Large"] models_ma = ["Logistic Regression","LDA","XGB","Nerual Net Simple","Nerual Net Large"] #Prepare the data pca = PCA() csv_reduced = pd.DataFrame(pca.fit_transform(csv.loc[:,predictors]))
现在,让我们观察使用神经网络直接预测收盘价变化时的准确率水平。
#Fit the neural network predicting close price model_close = MLPClassifier(solver='lbfgs',alpha=1e-5,hidden_layer_sizes=(5, 2), random_state=1) model_close.fit(csv_reduced.loc[0:300000,:],csv.loc[0:300000,"Target Close"]) print("Close accuracy: ",accuracy_score(csv.loc[300070:,"Target Close"], model_close.predict(csv_reduced.loc[300070:,:])))
我们在预测收盘价变化时的准确率是49.9%。考虑到我们所接受的复杂性程度,这个准确率并不令人印象深刻。我们本可以使用一个更简单、更易于维护和理解的模型来达到同样的准确率水平。而且,如果我们只有49%的正确率,那么我们将处于无利可图的位置。让我们来对比一下我们在预测移动平均指标变化时的准确率。
#Fit the model predicting the moving average model_ma = MLPClassifier(solver='lbfgs',alpha=1e-5,hidden_layer_sizes=(5, 2), random_state=1) model_ma.fit(csv_reduced.loc[0:300000,:],csv.loc[0:300000,"Target MA"]) print("MA accuracy: ",accuracy_score(csv.loc[300070:,"Target MA"], model_ma.predict(csv_reduced.loc[300070:,:])))
在预测移动平均变化时,我们的模型准确率为68.8%,而在预测价格变化时,准确率仅为49.9%。相对于我们所使用的建模技术的复杂性而言,这是一个可接受的准确率水平。
现在,我们将拟合多种模型,并观察哪种模型最能预测价格变化,哪种模型最能预测移动平均的变化。
#Error metrics tscv = TimeSeriesSplit(n_splits=splits,gap=gap) error_close_df = pd.DataFrame(index=np.arange(0,splits),columns=models_close) error_ma_df = pd.DataFrame(index=np.arange(0,splits),columns=models_ma)
我们将首先评估我们所选的每个模型在预测收盘价时的准确率。
#Training each model to predict changes in the close price for i,(train,test) in enumerate(tscv.split(csv)): model= MLPClassifier(solver='lbfgs',alpha=1e-5,hidden_layer_sizes=(20, 10), random_state=1) model.fit(csv_reduced.loc[train[0]:train[-1],:],csv.loc[train[0]:train[-1],"Target Close"]) error_close_df.iloc[i,4] = accuracy_score(csv.loc[test[0]:test[-1],"Target Close"],model.predict(csv_reduced.loc[test[0]:test[-1],:]))
图7:不同模型在尝试分类价格变化时的准确率
图8:我们每个模型表现的可视化结果
我们可以评估每个模型在预测收盘价时所记录的最高准确率。
for i in enumerate(np.arange(0,error_close_df.shape[1])): print(error_close_df.columns[i[0]]," ", error_close_df.iloc[:,i[0]].max())
LDA 0.5192457894678943
XGB 0.5119523008041539
简单神经网络 0.5234700724948571
大型神经网络 0.5186627504042771
我们可以看到,我们的模型中没有一个表现得特别出色。它们的准确率都在50%左右,但在这组模型中,线性判别分析(LDA)模型的表现最佳。
另一方面,我们已经确定,当预测某些技术指标的变化时,我们的模型将展现出更高的准确率。现在,我们想在候选模型组中确定,哪个模型在预测移动平均变化时表现最佳。
#Training each model to predict changes in a technical indicator (in this example simple moving average) instead of close price. for i,(train,test) in enumerate(tscv.split(csv)): model= MLPClassifier(solver='lbfgs',alpha=1e-5,hidden_layer_sizes=(20, 10), random_state=1) model.fit(csv_reduced.loc[train[0]:train[-1],:],csv.loc[train[0]:train[-1],"Target MA"]) error_ma_df.iloc[i,4] = accuracy_score(csv.loc[test[0]:test[-1],"Target MA"],model.predict(csv_reduced.loc[test[0]:test[-1],:]))
图9:我们模型在尝试预测移动平均变化时的准确率,
图10:我们模型在预测移动平均变化时准确率的可视化展示。
我们将评估每种模型类型所记录的最高准确率。
for i in enumerate(np.arrange(0,error_ma_df.shape[1])): print(error_ma_df.columns[i[0]]," ", error_ma_df.iloc[:,i[0]].max())
逻辑回归 0.6927054112625546
LDA 0.696401658911147
XGB 0.6932664488520731
简单神经网络 0.6947955513019373
大型神经网络 0.6965006655445914
值得注意的是,尽管大型神经网络直接达到了最高的准确率水平,但由于其性能不稳定,我们并不希望在生产环境中使用它。我们可以从大型神经网络性能图中远低于平均水平的两个点观察到这一点。因此,从结果中我们可以观察到,就我们当前的数据集而言,理想的模型应该比简单的逻辑回归更复杂,但比大型神经网络要简单。
接下来,我们将构建一个以移动平均指标未来变动为交易信号的交易策略。我们将选择小型神经网络作为模型,因为它看起来更加稳定。
我们首先导入所需的库。
#Import the libraries we need import MetaTrader5 as mt5 import pandas_ta as ta import pandas as pd
接下来,我们设置交易环境。
#Trading global variables MARKET_SYMBOL = 'Volatility 75 Index' #This data frame will store the most recent price update last_close = pd.DataFrame() #We may not always enter at the price we want, how much deviation can we tolerate? DEVIATION = 10000 #We will always enter at the minimum volume VOLUME = 0 #How many times the minimum volume should our positions be LOT_MUTLIPLE = 1 #What timeframe are we working on? TIMEFRAME = mt5.TIMEFRAME_M1 #Which model have we decided to work with? neural_network_model= MLPClassifier(solver='lbfgs',alpha=1e-5,hidden_layer_sizes=(5, 2), random_state=1)
让我们确定希望交易品种的最小交易量。
#Determine the minimum volume for index,symbol in enumerate(symbols): if symbol.name == MARKET_SYMBOL: print(f"{symbol.name} has minimum volume: {symbol.volume_min}") VOLUME = symbol.volume_min * LOT_MULTIPLE
现在,我们可以创建一个函数来为我们执行市场订单。
# function to send a market order def market_order(symbol, volume, order_type, **kwargs): #Fetching the current bid and ask prices tick = mt5.symbol_info_tick(symbol) #Creating a dictionary to keep track of order direction order_dict = {'buy': 0, 'sell': 1} price_dict = {'buy': tick.ask, 'sell': tick.bid} request = { "action": mt5.TRADE_ACTION_DEAL, "symbol": symbol, "volume": volume, "type": order_dict[order_type], "price": price_dict[order_type], "deviation": DEVIATION, "magic": 100, "comment": "Indicator Forecast Market Order", "type_time": mt5.ORDER_TIME_GTC, "type_filling": mt5.ORDER_FILLING_FOK, } order_result = mt5.order_send(request) print(order_result) return order_result
此外,我们还需要另一个函数来帮助我们平仓。
# Closing our order based on ticket id def close_order(ticket): positions = mt5.positions_get() for pos in positions: tick = mt5.symbol_info_tick(pos.symbol) #validating that the order is for this symbol type_dict = {0: 1, 1: 0} # 0 represents buy, 1 represents sell - inverting order_type to close the position price_dict = {0: tick.ask, 1: tick.bid} #bid ask prices if pos.ticket == ticket: request = { "action": mt5.TRADE_ACTION_DEAL, "position": pos.ticket, "symbol": pos.symbol, "volume": pos.volume, "type": type_dict[pos.type], "price": price_dict[pos.type], "deviation": DEVIATION, "magic": 10000, "comment": "Indicator Forecast Market Order", "type_time": mt5.ORDER_TIME_GTC, "type_filling": mt5.ORDER_FILLING_FOK, } order_result = mt5.order_send(request) print(order_result) return order_result return 'Ticket does not exist'
此外,我们必须明确我们想要请求数据的日期范围。
#Update our date from and date to date_from = datetime(2024,1,1) date_to = datetime.now()
在将向经纪商请求的数据传递之前,我们必须先对数据进行预处理,使其格式与模型训练时所使用的格式保持一致。
#Let's create a function to preprocess our data def preprocess(df): #Calculating 60 period Simple Moving Average df.ta.sma(length=60,append=True) #Drop any rows that have missing values df.dropna(axis=0,inplace=True)
接下来,我们必须能够从神经网络中获得预测结果,并将该预测结果解释为买入(做多)或卖出(做空)的交易信号。
#Get signals from our model def ai_signal(): #Fetch OHLC data df = pd.DataFrame(mt5.copy_rates_range(market_symbol,TIMEFRAME,date_from,date_to)) #Process the data df['time'] = pd.to_datetime(df['time'],unit='s') df['target'] = (df['close'].shift(-1) > df['close']).astype(int) preprocess(df) #Select the last row last_close = df.iloc[-1:,1:] #Remove the target column last_close.pop('target') #Use the last row to generate a forecast from our moving average forecast model #Remember 1 means buy and 0 means sell forecast = neural_network_model.predict(last_close) return forecast[0]最后,我们将所有这些要素整合起来,以创建我们的交易策略。
#Now we define the main body of our trading algorithm if __name__ == '__main__': #We'll use an infinite loop to keep the program running while True: #Fetching model prediction signal = ai_signal() #Decoding model prediction into an action if signal == 1: direction = 'buy' elif signal == 0: direction = 'sell' print(f'AI Forecast: {direction}') #Opening A Buy Trade #But first we need to ensure there are no opposite trades open on the same symbol if direction == 'buy': #Close any sell positions for pos in mt5.positions_get(): if pos.type == 1: #This is an open sell order, and we need to close it close_order(pos.ticket) if not mt5.positions_totoal(): #We have no open positions market_order(MARKET_SYMBOL,VOLUME,direction) #Opening A Sell Trade elif direction == 'sell': #Close any buy positions for pos in mt5.positions_get(): if pos.type == 0: #This is an open buy order, and we need to close it close_order(pos.ticket) if not mt5.positions_get(): #We have no open positions market_order(MARKET_SYMBOL,VOLUME,direction) print('time: ', datetime.now()) print('-------\n') time.sleep(60)
图11:运行我们的模型
理论解释
在作者看来,当我们预测技术指标变化时能够观察到更高准确率的原因之一,可能是因为我们永远无法观察到影响一个证券价格的所有变量。充其量,我们只能部分地观察到这些变量,而当我们预测技术指标的变化时,我们完全了解所有影响该技术指标的输入因素。回想一下,我们甚至知道任何技术指标的精确公式。
图12:我们知道所有技术指标的数学描述,但收盘价却没有数学公式。
例如,资金流量乘数(MFM)技术指标就是使用上述公式计算得出的。因此,如果我们想预测MFM的变化,我们只需要知道其公式的组成部分:收盘价、最低价和最高价。
相比之下,在预测收盘价时,我们没有具体的公式来告诉我们哪些输入因素会影响它。这通常会导致预测准确率较低,这表明我们当前的输入集合可能不包含有用的信息,或者我们选择了糟糕的输入,从而引入了太多的噪声。
从根本上说,机器学习的目标是找到由一组输入确定的目标值。当我们将技术指标作为目标时,我们实际上是在说技术指标受到开盘价、收盘价、最低价和最高价的影响,这是正确的。然而,作为算法开发者,我们通常反其道而行之。我们使用一系列价格数据和技术指标来预测价格,这暗示着技术指标会影响价格,但事实并非如此,也永远不会如此。
当尝试学习一个其底层函数未知的目标时,我们可能会陷入所谓的伪回归的陷阱,我们在之前的讨论中详细讨论过这一点。简而言之,你的模型可能会学习到现实中不存在的关系。此外,这个缺陷可能会在验证时通过欺骗性的低错误率被掩盖,使得模型看起来似乎已经充分学习,但实际上它并没有学到任何关于现实世界的知识。
为了说明什么是伪回归,想象一下你和我正在下山,而在地平线那边我们看到一个模糊的形状。我们离得太远,看不清那是什么,但基于我所看到的,我大喊道:“那边有一只狗。”现在我们到了那里,发现是一丛灌木,但灌木后面确实有一只狗。
图13:我能看到那只狗吗?
你是否已经发现了问题?我当然想声称自己的胜利是视力20/20完美的证明,但你知道,从根本上说,当我说出那句话时,从我们所在的位置根本不可能看到那只狗。我们离得太远,从那里看那个形状太模糊了。
我在山顶看到的输入数据与我得出的结论之间根本没有关系。也就是说,输入数据和输出数据是相互独立的。每当模型查看与输出数据无关的输入数据但却能得出正确答案时,我们就称之为伪回归。伪回归经常发生!
由于我们没有技术公式来概述哪些因素影响证券价格,因此我们很容易使用对目标(即收盘价)没有影响的输入数据来制造伪回归。试图证明一个回归不是伪回归可能是具有挑战性的,使用与输入数据有已知关系的目标会更容易。
结论
这篇文章已经证明了为什么直接预测收盘价的做法应该被逐渐摒弃,转而支持预测技术指标的变化。有必要进行进一步研究,以找出是否存在其他技术指标,其预测准确性能够高于移动平均线。然而,读者也应该注意,尽管我们对移动平均线的预测准确性相对较高,但移动平均线的变化与价格变化之间仍然存在滞后。
换句话说,移动平均线有可能在下降,而价格却在上升。但是,如果我们MQL5社区共同努力改进这个算法,那么我有信心我们最终可以达到新的准确性水平。
本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/14936


