English Русский Español Deutsch 日本語 Português
preview
用Python重塑经典策略:移动平均线交叉

用Python重塑经典策略:移动平均线交叉

MetaTrader 5交易系统 | 8 一月 2025, 10:58
483 0
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

引言

许多当今的交易策略都是在截然不同的市场环境中构思出来的。评估这些策略在以算法为主导的当代市场中的相关性至关重要。本文深入探讨了移动平均线交叉策略,以评估其在当今金融环境中的有效性。

本文将涵盖以下内容:

  • 是否有定量证据支持该策略的继续使用?
  • 与直接价格分析相比,该策略提供了哪些优势?
  • 在现代算法交易中,该策略是否仍然有效?
  • 是否有其他指标可以提高该策略的准确性?
  • 人工智能是否可以有效地用来预测移动平均线交叉的发生?

几十年来,使用移动平均线交叉的技术已被广泛研究。尽管其确切起源尚不确定,但利用这些平均值来检测趋势和交易信号的基本概念一直是技术分析的中流砥柱。

移动平均线交叉策略通常涉及两个不同周期的移动平均线,但关键条件是其中一个周期长于另一个。当短期移动平均线上穿长期移动平均线时,它发出潜在的牛市趋势信号,反之则为熊市趋势信号。

几十年来,技术分析师一直利用这一策略来确定入场和出场点、衡量市场情绪以及进行各种其他应用。为了确定其当前的有效性,我们将对该策略进行现代量化测试。我们的方法如下所示。


移动平均交叉

图1:CADJPY货币对上移动平均线交叉的应用示例

概览

我们即将踏上一段激动人心的旅程,将MetaTrader5交易平台与我们的Python环境连接起来。首先,我们将请求从2020年1月1日至2024年6月25日的EURUSD货币对的M15(15分钟)数据。这一庞大的数据集将为我们提供近期市场行为的全面视图。

我们的下一步是设定两个目标。第一个目标是衡量我们预测直接价格变动的准确性,这将作为我们的基准。这一基准将有助于我们比较在预测移动平均线交叉时表现如何。在此过程中,我们将寻找其他技术指标来提高我们的准确性。最后,我们将让计算机模型识别预测移动平均线交叉的关键变量。如果模型没有优先考虑我们使用的两条移动平均线,这可能表明我们的初步假设是错误的。

在深入数据之前,让我们考虑可能的结果:

  1. 直接价格预测优越性:如果直接预测价格变动的准确性高于或等于预测移动平均线交叉的准确性,这表明预测交叉可能不会带来任何额外优势,从而质疑该策略的有效性。

  2. 交叉预测优越性:如果我们在预测移动平均线交叉方面实现了更高的准确性,这将激励我们寻找更多数据来进一步增强我们的预测,凸显该策略的潜在价值。

  3. 移动平均线的不相关性:如果我们的模型没有将任何一条移动平均线识别为预测交叉的关键,这意味着其他变量可能更重要,表明两条移动平均线之间假设的关系不成立。

  4. 移动平均线的相关性:如果一条或两条移动平均线被标记为预测交叉的重要变量,这证实了它们之间存在实质性关系,使我们能够构建可靠的模型进行有根据的预测。

这一分析将帮助我们了解在交易策略中使用移动平均线交叉的优缺点,指导我们采用更有效的预测方法。


实验:移动平均线交叉是否仍然可靠?

让我们首先导入我们所需的标准Python库。

import pandas as pd
import pandas_ta as ta
import numpy as np
import MetaTrader5 as mt5
from   datetime import datetime
import seaborn as sns
import time

接下来,我们输入登录信息。

account = 123436536
password = "Enter Your Password"
server = "Enter Your Broker"

接着,尝试登录我们的交易账户。

if(mt5.initialize(login=account,password=password,server=server)):
    print("Logged in succesfully")
else:
    print("Failed to login")

登录成功

接下来我们定义有些全局变量。

timeframe = mt5.TIMEFRAME_M15
deviation = 1000
volume = 0
lot_multiple = 10
symbol = "EURUSD"

然后获取想要交易的品种的市场数据

#Setup trading volume
symbols = mt5.symbols_get()
for index,symbol in enumerate(symbols):
    if symbol.name == "EURUSD":
        print(f"{symbol.name} has minimum volume: {symbol.volume_min}")
        volume = symbol.volume_min * lot_multiple

EURUSD最小交易量是:0.01

现在我们准备好获取交易数据了。

#Specify date range of data to be modelled
date_start = datetime(2020,1,1)
date_end = datetime.now()

接着,我们将定义想要预测的数据深度。

#Define how far ahead we are looking
look_ahead = 20

接下来,我们可以从MetaTrader5交易平台获取市场数据,并对这些数据进行标记。我们的标记方案使用“1”来表示上涨趋势,使用“0”来表示下跌趋势。 

#Fetch market data
market_data = pd.DataFrame(mt5.copy_rates_range("EURUSD",timeframe,date_start,date_end))
market_data["time"] = pd.to_datetime(market_data["time"],unit='s')
#Add simple moving average technical indicator
market_data.ta.sma(length=5,append=True)
#Add simple moving average technical indicator
market_data.ta.sma(length=50,append=True)
#Delete missing rows
market_data.dropna(inplace=True)

#Add a column for the target
market_data["target"] = 0
market_data["close_target"] = 0

#Encoding the target
ma_cross_conditions = [
    (market_data["SMA_5"].shift(-look_ahead) > market_data["SMA_50"].shift(-look_ahead)),
    (market_data["SMA_5"].shift(-look_ahead) < market_data["SMA_50"].shift(-look_ahead))
]
#Encoding pattern
ma_cross_choices = [
    #Fast MA above Slow MA
    1,
    #Fast MA below Slow MA
    0
]

price_conditions = [
    (market_data["close"] > market_data["close"].shift(-look_ahead)),
    (market_data["close"] < market_data["close"].shift(-look_ahead))
]

#Encoding pattern
price_choices = [
    #Price fell
    0,
    #Price rose
    1
]

market_data["target"] = np.select(ma_cross_conditions,ma_cross_choices)
market_data["close_target"] = np.select(price_conditions,price_choices)

#The last rows do not have answers
market_data = market_data[:-look_ahead]
market_data


市场数据

图2:当前格式下的市场数据数据帧

接下来,我们将导入所需的机器学习库。

#XGBoost
from xgboost import XGBClassifier
#Catboost
from catboost import CatBoostClassifier
#Random forest
from sklearn.ensemble import RandomForestClassifier
#LDA and QDA
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis , QuadraticDiscriminantAnalysis
#Logistic regression
from sklearn.linear_model import LogisticRegression
#Neural network
from sklearn.neural_network import MLPClassifier
#Time series split
from sklearn.model_selection import TimeSeriesSplit
#Accuracy metrics
from sklearn.metrics import accuracy_score
#Visualising performance
from sklearn.metrics import confusion_matrix
import seaborn as sns
import matplotlib.pyplot as plt
from sklearn.model_selection import learning_curve

准备对数据集进行时间序列分割。

#Time series split
splits = 10
gap = look_ahead
models = ["Logistic Regression","Linear Discriminant Analysis","Quadratic Discriminant Analysis","Random Forest Classifier","XGB Classifier","Cat Boost Classifier","Neural Network Small","Neural Network Large"]

我们将评估许多不同模型的准确性,并将每个模型所获得的准确性存储在一个数据帧中。一个数据帧将存储我们在预测移动平均线交叉时的准确性,而第二个数据帧则衡量我们在直接预测价格变动时的准确性。

error_ma_crossover = pd.DataFrame(index=np.arange(0,splits),columns=models)
error_price = pd.DataFrame(index=np.arange(0,splits),columns=models)

接下来,我们将测量每个模型的准确性。但在此之前,我们必须定义模型将使用的输入。

predictors = ["open","high","low","close","tick_volume","spread","SMA_5","SMA_50"]

为了测量每个模型的准确性,我们将在数据集的一部分上训练模型,然后在训练期间未见过的数据集的其余部分上进行测试。TimeSeriesSplit库为我们划分数据框,从而简化了这一过程。

tscv = TimeSeriesSplit(n_splits=splits,gap=gap)
#Training each model to predict changes in the moving average cross over
for i,(train,test) in enumerate(tscv.split(market_data)):
    model = MLPClassifier(solver='lbfgs',alpha=1e-5,hidden_layer_sizes=(20, 10), random_state=1,early_stopping=True)
    model.fit( market_data.loc[train[0]:train[-1],predictors] , market_data.loc[train[0]:train[-1],"target"] ) 
    error_ma_crossover.iloc[i,7] = accuracy_score(market_data.loc[test[0]:test[-1],"target"],model.predict(market_data.loc[test[0]:test[-1],predictors]))

#Training each model to predict changes in the close price
for i,(train,test) in enumerate(tscv.split(market_data)):
    model = MLPClassifier(solver='lbfgs',alpha=1e-5,hidden_layer_sizes=(20, 10), random_state=1,early_stopping=True)
    model.fit( market_data.loc[train[0]:train[-1],predictors] , market_data.loc[train[0]:train[-1],"close_target"] ) 
    error_price.iloc[i,7] = accuracy_score(market_data.loc[test[0]:test[-1],"close_target"],model.predict(market_data.loc[test[0]:test[-1],predictors]))

让我们首先看看在直接预测价格变动时衡量准确性的数据帧。

error_price

预测价格误差

图3:直接预测价格变动时的准确性

在继续之前,让我们先解释一下这些结果。首先,我们可以发现,我们在执行这个任务时,没有一个模型表现得很好。一些模型在直接预测价格时的准确性甚至低于50%。这个表现相当糟糕,这意味着我们如果只是随机猜测,效果可能也差不了多少。我们的模型按照复杂度递增的顺序排列,左边是简单的逻辑回归,右边是深度神经网络。我们可以观察到,在直接预测价格时,增加模型的复杂度并没有提高我们的准确性。现在,让我们看看在预测移动平均线交叉时是否有任何改进。

error_ma_crossover

预测移动平均线交叉时的误差

图4:预测移动平均线交叉时的准确性

从上面的数据框中我们可以看到,线性判别分析(LDA)在这项任务中表现得极为出色。它是我们考察的所有模型中表现最好的,远超其他模型。此外,当你将LDA在改进后的模型性能与它在第一个任务中的糟糕表现进行对比时,我们可以清楚地看到,预测移动平均线交叉可能比直接预测价格变动更可靠。在这种情况下,预测移动平均线交叉的好处是不容置疑的。

可视化结果

让我们将上面得到的结果进行可视化。 

可视化结果

图5:可视化获得的结果

LDA算法在箱线图中的改进效果十分明显,这表明我们的模型有了显著的学习成果。此外,逻辑回归的性能也有轻微但可察觉的提升。值得注意的是,在预测移动平均线交叉时,LDA生成的分数在箱线图中始终紧密聚集,显示出令人满意的准确性和一致性。这种聚集现象表明模型的预测是稳定的,且残差可能具有平稳性,这意味着模型已经学习到了可靠的关系。

现在,我们来分析模型所犯的错误类型。我们的目标是确定模型在识别向上运动、向下运动方面的表现是否更好,还是在这两项任务中的表现保持平衡。

LDA混淆矩阵

图6:我们的LDA模型性能的混淆矩阵

上面的混淆矩阵展示了左侧的真实分类和底部的模型预测。数据告诉我们,我们的模型在预测上涨走势时犯了更多错误,有47%的时间将上涨走势误判为下跌走势。另一方面,我们的模型在预测下跌走势时表现非常出色,只有25%的时间将真正的下跌走势误判为上涨走势。因此,我们可以清楚地看到,我们的模型在预测下跌走势方面比预测上涨走势方面表现得更好。 

随着模型遇到越来越多的训练数据,我们可以可视化模型的学习进度。下面的图表用于评估我们的模型是否对训练数据过拟合或欠拟合。当模型从数据中学习到噪声,而未能捕捉到有意义的关系时,就会发生过拟合。另一方面,欠拟合在图表上表现为训练准确率(蓝色线表示)和验证准确率(橙色线表示)之间存在显著差距。在我们当前的图表中,我们观察到训练分数和验证分数之间存在明显但不太大的差距,这表明我们的LDA模型确实对训练数据存在一定的过拟合。然而,左侧的刻度表明这种过拟合并不严重。

线性判别分析的学习曲线

图7:我们的LDA分类器的学习曲线

另一方面,欠拟合的特点是训练准确率和验证准确率都较低。作为例子,我们展示了其中一个表现不佳的模型——小型神经网络的学习曲线。在下面的图表中,我们观察到模型的性能与它接触到的训练数据量之间的关系不稳定。最初,随着数据量的增加,模型的验证性能恶化,直到达到一个转折点,当训练样本数量接近10000时,验证性能开始改善。随后,尽管训练数据量继续大幅增加,但模型的性能改善变得平缓,只有边际上的提升。

小型神经网络的学习曲线

图8:我们的小型神经网络的学习曲线

特征消除

在大多数机器学习项目中,并非所有输入都直接与目标变量相关,这是不常见的。通常,只有可用输入的一个子集与预测目标相关。消除不相关输入有几个优点,如:

  1. 在模型训练和特征工程过程中提高计算效率。
  2. 如果移除的特征包含噪声,则可以提高模型准确性。

接下来,我们需要确定移动平均线之间是否存在有意义的关系。我们将使用特征消除算法来验证这种假设关系。如果这些算法未能从输入列表中消除移动平均线,则表明存在有意义的关系。相反,如果它们成功移除了这些特征,则表明移动平均线与移动平均线交叉之间不存在显著关系。

我们将采用一种称为向后选择的特征选择技术。该方法首先使用所有可用输入拟合线性模型,然后测量模型的准确性。随后,每次移除一个特征,并记录对模型准确性的影响。在每一步中,消除导致准确性下降最小的特征,直到没有特征剩余。在这个阶段,算法会自动选择它已识别的最重要特征,并建议使用它们。

值得提及的是特征消除的一个显著缺点是,当数据集中存在噪声和不重要的列时,重要列可能看起来不提供信息。因此,由于系统中的噪声,向后选择算法可能会无意中消除一个重要特征,因为它看起来不提供信息。

现在,让我们继续查看计算机认为哪些列是重要的。我们首先导入一个名为mlxtend的库,它包含向后选择算法的实现。

from mlxtend.feature_selection import SequentialFeatureSelector

然后,我们在数据集上应用该算法。让我们特别注意我们传递的三个参数:

  1. "k_features=" 指示算法选择多少列。我们可以通过传递一个从1开始到数据集中总列数的区间,来指示算法仅选择它认为必要的列。
  2. "forward=" 指示算法是否应使用向前选择或向后选择,我们希望使用向后选择,因此将此参数设置为"False"。
  3. "n_jobs=" 指示算法是否应并行执行计算,我们传递"-1"以允许算法使用所有可用内核,这将显著减少花费的时间。 

backward_feature_selector = SequentialFeatureSelector(LinearDiscriminantAnalysis(),
                                                      k_features=(1,market_data.loc[:,predictors].shape[1]),
                                                      forward=False,
                                                      verbose=2,
                                                      scoring="accuracy",
                                                      cv=5,
						      n_jobs=-1
                                                     ).fit(market_data.loc[:,predictors],market_data.loc[:,"target"])

[Parallel(n_jobs=-1)]:使用后向LokyBackend,启用8个并发工作进程。

[Parallel(n_jobs=-1)]:完成八分之三 | 耗时:    8.0s 剩余:   13.3s

[Parallel(n_jobs=-1)]:完成八分之八 | 耗时:    8.0s 剩余:    0.0s

[Parallel(n_jobs=-1)]:完成八分之八 | 耗时:    8.0s 完成

一旦该过程完成,我们就可以使用以下命令来获取算法认为重要的输入特征列表。

backward_feature_selector.k_feature_names_

('open', 'high', 'close', 'SMA_5', 'SMA_50')

正如我们所见,后向选择算法将我们的两个移动平均线纳入了其重要特征列表。这对我们来说是个极好的消息,因为它证实了我们的交易策略并非仅仅源于一个虚假的回归结果。

特征工程

既然我们已经确定了两个移动平均线之间存在显著关系,这值得我们进一步努力改进,那么接下来让我们探讨一下,是否还有其他技术指标能够提高我们预测移动平均线交叉点的准确性。在这方面,机器学习更倾向于艺术而非科学,因为预先预测哪些输入会有益是具有挑战性的。我们的方法将包括添加我们认为可能有用的几个特征,并评估它们的实际影响。

我们将从与之前相同的市场收集市场数据,但这次我们将纳入其他指标:

  1. 移动平均收敛发散指标(MACD):MACD是一种非常强大的趋势确认技术指标,它可能有助于我们更好地观察基础市场格局的变化。
  2. 神奇振荡器:神奇振荡器以提供非常可靠的退出信号而闻名,它可以清晰地显示任何趋势何时改变动量。
  3. Aroon:阿伦指标用于识别新趋势的开始。
  4. Chaikins商品指数:蔡金商品指数作为衡量金融工具是否超买或超卖的晴雨表。
  5. 百分比回报率:百分比回报率指标有助于我们观察价格的增长情况,以及它是正增长还是负增长。 
让我们继续添加上面概述的指标,以及我们原始的两个移动平均线。

#Fetch market data
market_data = pd.DataFrame(mt5.copy_rates_range("EURUSD",timeframe,date_start,date_end))
market_data["time"] = pd.to_datetime(market_data["time"],unit='s')
#Add simple moving average technical indicator
market_data.ta.sma(length=5,append=True)
#Add simple moving average technical indicator
market_data.ta.sma(length=50,append=True)
#Add macd
market_data.ta.macd(append=True)
#Add awesome oscilator
market_data.ta.ao(append=True)
#Add aroon
market_data.ta.aroon(append=True)
#Add chaikins comodity index
market_data.ta.cci(append=True)
#Add percent return
market_data.ta.percent_return(append=True)
#Delete missing rows
market_data.dropna(inplace=True)
#Add the target
market_data["target"] = 0
market_data.loc[market_data["SMA_5"].shift(-look_ahead) > market_data["SMA_50"].shift(-look_ahead),"target"] = 1
market_data.loc[market_data["SMA_5"].shift(-look_ahead) < market_data["SMA_50"].shift(-look_ahead),"target"] = 0
#The last rows do not have answers
market_data = market_data[:-look_ahead]
market_data

我们的新数据帧

图9:我们向数据帧中添加的一些新行。


进行特征选择后,我们的后向选择算法确定了以下变量为重要变量。

backward_feature_selector = SequentialFeatureSelector(LinearDiscriminantAnalysis(),
                                                      k_features=(1,market_data.loc[:,predictors].shape[1]),
                                                      forward=False,
                                                      verbose=2,
                                                      scoring="accuracy",
                                                      cv=5
                                                     ).fit(market_data.iloc[:,1:-1],market_data.loc[:,"target"])
backward_feature_selector.k_feature_names_

('close', 'tick_volume', 'spread', 'SMA_5', 'SMA_50', 'MACDh_12_26_9', 'AO_5_34')

构建我们的交易策略

现在我们准备将迄今为止所学的一切整合到一个交易策略中。

我们首先使用所有可用的训练数据,并且仅使用我们已确定有用的列来训练我们的模型。

predictors = ['close','tick_volume','spread','SMA_5','SMA_50','MACDh_12_26_9','AO_5_34']
model = LinearDiscriminantAnalysis()
model.fit(market_data.loc[:,predictors],market_data.loc[:,"target"])

接下来,我们定义从MetaTrader5终端获取市场数据的函数。

def get_prices():
    start = datetime(2024,6,1)
    end   = datetime.now()
    data  = pd.DataFrame(mt5.copy_rates_range("EURUSD",timeframe,start,end))
    #Add simple moving average technical indicator
    data.ta.sma(length=5,append=True)
    data.ta.sma(length=50,append=True)
    #Add awesome oscilator
    data.ta.ao(append=True)
    #Add macd
    data.ta.macd(append=True)
    #Delete missing rows
    data.dropna(inplace=True)
    data['time'] = pd.to_datetime(data['time'],unit='s')
    data.set_index('time',inplace=True)
    data = data.loc[:,['close','tick_volume','spread','SMA_5','SMA_50','MACDh_12_26_9','AO_5_34']]
    data = data.iloc[-2:,:]
    return(data)

随后,我们需要另一种方法从我们的LDA(线性判别分析)模型中获取预测结果。

#Get signals LDA model
def ai_signal(input_data,_model):
    #Get a forecast
    forecast = _model.predict(input_data)
    return forecast[1]

现在我们可以构建我们的交易策略了。

#Now we define the main body of our Python Moving Average Crossover Trading Bot
if __name__ == '__main__':
    #We'll use an infinite loop to keep the program running
    while True:
        #Fetching model prediction
        signal = ai_signal(get_prices(),model)
        
        #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
                mt5.Buy(symbol,volume)
        
        #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
                mt5.sell(symbol,volume)
        
        print('time: ', datetime.now())
        print('-------\n')
        time.sleep(60)

AI预测:卖出

时间: 2024-06-25 14:35:37.954923

-------


运行我们的交易策略

图10:运行我们的交易策略


以 MQL5 实现

接下来,让我们利用MQL5 API从头开始开发我们自己的分类器。在MQL5中创建自定义分类器有诸多优势。作为作者,我坚信原生的MQL5解决方案提供了无与伦比的灵活性。

如果我们要将模型导出为ONNX格式,那么我们需要为每个希望交易的市场分别创建一个模型。此外,在不同时间框架上进行交易将需要为每个市场准备多个ONNX模型。通过在MQL5中直接构建我们的分类器,我们获得了在不受这些限制的情况下交易任何市场的能力。

那么,让我们创建一个新项目吧。

MQL5 EA

图11:创建一个EA来实现我们的策略


我们的第一个任务是定义一些将在整个程序中使用的全局变量。

//Global variables
int ma_5,ma_50;
double bid, ask;
double min_volume;
double ma_50_reading[],ma_5_reading[];
int size;
double current_prediction;
int state = -1;
matrix ohlc;
vector target;
double b_nort = 0;
double b_one = 0;
double b_two = 0;
long min_distance,atr_stop;

我们还将创建一些终端用户可以调整的参数。

//Inputs
int input lot_multiple = 20;
int input positions = 2;
double input sl_width = 0.4;

最后,我们将导入交易库来帮助我们管理仓位。

//Libraries
#include <Trade\Trade.mqh>
CTrade Trade;

接下来,我们需要定义一些辅助函数,这些函数将帮助我们获取数据、为训练数据打标签、训练模型以及从模型中获取预测结果。让我们首先定义一个函数,用于获取训练数据并为我们的分类器的目标打标签。 

//+----------------------------------------------------------------------+
//|This function is responsible for getting our training data ready      |
//+----------------------------------------------------------------------+
void get_training_data(void)
  {
//How much data are we going to use?
   size = 100;
//Copy price data
   ohlc.CopyRates(_Symbol,PERIOD_CURRENT,COPY_RATES_CLOSE,1,size);
//Get indicator data
   ma_50 = iMA(_Symbol,PERIOD_CURRENT,50,0,MODE_EMA,PRICE_CLOSE);
   ma_5 = iMA(_Symbol,PERIOD_CURRENT,5,0,MODE_EMA,PRICE_CLOSE);
   CopyBuffer(ma_50,0,0,size,ma_50_reading);
   CopyBuffer(ma_5,0,0,size,ma_5_reading);
   ArraySetAsSeries(ma_50_reading,true);
   ArraySetAsSeries(ma_5_reading,true);
//Label the target
   target = vector::Zeros(size);
   for(int i = 0; i < size; i++)
     {
      if(ma_5_reading[i] > ma_50_reading[i])
        {
         target[i] = 1;
        }

      else
         if(ma_5_reading[i] < ma_50_reading[i])
           {
            target[i] = 0;
           }
     }

//Feedback
   Print("Done getting training data.");
  }

我们的模型使用三个系数来进行预测。这些系数需要被优化。我们将使用一个对初学者友好的更新方程来调整这些系数。通过测量模型预测中的误差,我们将迭代地修改系数以最小化误差并提高系统的准确性。但是,在开始优化模型之前,我们首先需要定义模型如何进行预测。 

//+----------------------------------------------------------------------+
//|This function is responsible for making predictions using our model   |
//+----------------------------------------------------------------------+
double model_predict(double input_one,double input_two)
  {
//We simply return the probability that the shorter moving average will rise above the slower moving average
   double prediction = 1 / (1 + MathExp(-(b_nort + (b_one * input_one) + (b_two * input_two))));
   return prediction;
  }

既然我们的模型能够做出预测,我们就可以测量其预测中的误差,并开始优化过程。最初,所有三个系数都将被设置为0。然后,我们将以小的步长迭代地调整这些系数,以最小化我们系统中的总误差。 

//+----------------------------------------------------------------------+
//|This function is responsible for  training our model                  |
//+----------------------------------------------------------------------+
bool train_model(void)
  {
//Update the coefficients
   double learning_rate = 0.3;
   for(int i = 0; i < size; i++)
     {
      //Get a prediction from the model
      current_prediction = model_predict(ma_5_reading[i],ma_50_reading[i]);
      //Update each coefficient
      b_nort = b_nort + learning_rate * (target[i] - current_prediction) * current_prediction * (1 - current_prediction) * 1;
      b_one = b_one + learning_rate * (target[i] - current_prediction) * current_prediction * (1-current_prediction) * ma_5_reading[i];
      b_two = b_two + learning_rate * (target[i] - current_prediction) * current_prediction * (1-current_prediction) * ma_50_reading[i];
      Print(current_prediction);
     }

//Show updated coefficient values
   Print("Updated coefficient values");
   Print(b_nort);
   Print(b_one);
   Print(b_two);
   return(true);
  }

成功训练模型后,拥有一个从模型中获取预测结果的函数将非常有益。这些预测将作为我们的交易信号。回顾一下,预测值为1表示买入信号,意味着我们的模型预期短期移动平均线将上升至长期移动平均线之上。相反,预测值为0表示卖出信号,意味着我们的模型预期短期移动平均线将下降至长期移动平均线之下。

//Get the model's current forecast
void current_forecast()
  {
//Get indicator data
   ma_50 = iMA(_Symbol,PERIOD_CURRENT,50,0,MODE_EMA,PRICE_CLOSE);
   ma_5 = iMA(_Symbol,PERIOD_CURRENT,5,0,MODE_EMA,PRICE_CLOSE);
   CopyBuffer(ma_50,0,0,1,ma_50_reading);
   CopyBuffer(ma_5,0,0,1,ma_5_reading);
//Get model forecast
   model_predict(ma_5_reading[0],ma_50_reading[0]);
   interpret_forecast();
  }

我们希望我们的智能交易系统(Expert Advisor)能够根据模型的预测来行动。因此,我们将编写一个函数来解释模型的预测,并采取相应的行动:当模型预测为1时买入,当模型预测为0时卖出。 

//+----------------------------------------------------------------------+
//|This function is responsible for taking action on our model's forecast|
//+----------------------------------------------------------------------+
void interpret_forecast(void)
  {
   if(current_prediction > 0.5)
     {
      state = 1;
      Trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,min_volume * lot_multiple,ask,0,0,"Volatitlity Doctor AI");
     }

   if(current_prediction < 0.5)
     {
      state = 0;
      Trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,min_volume * lot_multiple,bid,0,0,"Volatitlity Doctor AI");
     }
  }

既然我们的应用程序能够从数据中学习、做出预测并根据这些预测采取行动,我们就需要创建额外的函数来管理任何已开仓位。具体来说,我们希望我们的程序为每个仓位添加追踪止损和止盈来管理我们的风险水平。我们不希望在没有定义风险限制的情况下持有仓位。大多数交易策略建议设置一个固定的100点的止损大小,但我们希望确保我们的止损和止盈水平能够根据当前的市场波动性动态设置。因此,我们将使用平均真实范围(ATR)来计算我们的止损应该多宽或多窄。我们将使用ATR的倍数来确定这些水平。

//+----------------------------------------------------------------------+
//|This function is responsible for calculating our SL & TP values       |
//+----------------------------------------------------------------------+
void CheckAtrStop()
  {

//First we iterate over the total number of open positions
   for(int i = PositionsTotal() -1; i >= 0; i--)
     {

      //Then we fetch the name of the symbol of the open position
      string symbol = PositionGetSymbol(i);

      //Before going any furhter we need to ensure that the symbol of the position matches the symbol we're trading
      if(_Symbol == symbol)
        {
         //Now we get information about the position
         ulong ticket = PositionGetInteger(POSITION_TICKET); //Position Ticket
         double position_price = PositionGetDouble(POSITION_PRICE_OPEN); //Position Open Price
         long type = PositionGetInteger(POSITION_TYPE); //Position Type
         double current_stop_loss = PositionGetDouble(POSITION_SL); //Current Stop loss value

         //If the position is a buy
         if(type == POSITION_TYPE_BUY)
           {

            //The new stop loss value is just the ask price minus the ATR stop we calculated above
            double atr_stop_loss = NormalizeDouble(ask - ((min_distance * sl_width)/2),_Digits);
            //The new take profit is just the ask price plus the ATR stop we calculated above
            double atr_take_profit = NormalizeDouble(ask + (min_distance * sl_width),_Digits);

            //If our current stop loss is less than our calculated ATR stop loss
            //Or if our current stop loss is 0 then we will modify the stop loss and take profit
            if((current_stop_loss < atr_stop_loss) || (current_stop_loss == 0))
              {
               Trade.PositionModify(ticket,atr_stop_loss,atr_take_profit);
              }
           }

         //If the position is a sell
         else
            if(type == POSITION_TYPE_SELL)
              {
               //The new stop loss value is just the ask price minus the ATR stop we calculated above
               double atr_stop_loss = NormalizeDouble(bid + ((min_distance * sl_width)/2),_Digits);
               //The new take profit is just the ask price plus the ATR stop we calculated above
               double atr_take_profit = NormalizeDouble(bid - (min_distance * sl_width),_Digits);

               //If our current stop loss is greater than our calculated ATR stop loss
               //Or if our current stop loss is 0 then we will modify the stop loss and take profit
               if((current_stop_loss > atr_stop_loss) || (current_stop_loss == 0))
                 {
                  Trade.PositionModify(ticket,atr_stop_loss,atr_take_profit);
                 }
              }
        }
     }
  }

然后,我们需要一个函数,每当我们想计算新的止损和止盈值时,就可以调用它。

//+------------------------------------------------------------------+
//|This function is responsible for updating our SL&TP values        |
//+------------------------------------------------------------------+
void ManageTrade()
  {
   CheckAtrStop();
  }

既然我们已经定义了辅助函数,我们就可以开始在事件处理程序中调用它们了。当我们的程序首次加载时,我们希望初始化训练过程。因此,我们将在OnInit事件处理程序中调用负责训练我们智能交易系统的辅助函数。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   //Define important global variables
   min_volume = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
   min_distance = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL);
   //Train the model
   get_training_data();
   if(train_model())
     {
      interpret_forecast();
     }
   return(INIT_SUCCEEDED);
  }

训练完模型后,我们就能开始用它进行真正的交易了。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//Get updates bid and ask prices
   bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
   ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);

   if(PositionsTotal() == 0)
     {
      current_forecast();
     }

   if(PositionsTotal() > 0)
     {
      ManageTrade();
     }
  }

模型输出

图12:这是我们智能交易系统输出结果的一个示例

运行EA

图13:我们的智能交易系统在运行中


结论

在本文中,我们证明了我们的模型预测移动平均线交叉比直接预测价格变化在计算上更容易。

与我所有的文章一样,我更喜欢先展示原理,然后再提供技术解释。这个观察结果有几个可能的原因。一个潜在的原因是,根据所选的周期,移动平均线可能不会像价格方向那样频繁地无规则改变而发生交叉。换句话说,在过去两个小时内,价格可能先上涨,然后下跌,或者改变了两次方向。然而,在同一时间段内,移动平均线可能根本没有发生交叉。因此,移动平均线交叉可能更容易预测,因为它们的方向不会像价格本身那样迅速改变。这只是一种可能的解释。请随意独立思考,得出自己的结论,并在下面的评论中分享。

接下来,我们采用了向后选择法进行特征消除,这是一种线性模型训练方法,其中每一步都会根据特征对模型准确性的影响移除一个特征,从而迭代训练。这种方法有助于识别和保留最具信息量的特征,尽管它容易受到噪声影响而消除可能看似无信息量但实际上重要的特征。

在验证了两个移动平均线之间存在显著关系后,我们探索了整合其他技术指标:MACD、Awesome Oscillator、Aroon、Chaikins Commodity Index和Percent Return。这些指标旨在提高我们准确预测移动平均线交叉的能力。然而,由于这些指标对模型性能的影响具有不可预测性,因此选择这些指标仍然具有一定的艺术性。

总体而言,我们的方法结合了经验验证和策略特征选择,从数量上证明了移动平均线交叉确实可以被预测,并且进一步提高这一交易策略的努力绝对不会是浪费时间。

本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/15160

在MQL5中创建交互式图形用户界面(第1部分):制作面板 在MQL5中创建交互式图形用户界面(第1部分):制作面板
本文探讨了使用MetaQuotes Language 5(MQL5)设计和实施图形用户界面(GUI)面板的基本步骤。自定义实用面板通过简化常见任务并可视化重要的交易信息,增强了交易中的用户交互。通过创建自定义面板,交易者可以优化其工作流程,并在交易操作中节省时间。
开发多币种 EA 交易 (第 11 部分):自动化优化(第一步) 开发多币种 EA 交易 (第 11 部分):自动化优化(第一步)
为了获得一个好的 EA,我们需要为它选择多组好的交易策略实例参数。这可以通过对不同的交易品种运行优化然后选择最佳结果来手动完成。但最好将这项工作委托给程序,并从事更有成效的活动。
《数据科学与机器学习(第25部分):使用循环神经网络(RNN)进行外汇时间序列预测》 《数据科学与机器学习(第25部分):使用循环神经网络(RNN)进行外汇时间序列预测》
循环神经网络(RNN)非常擅长利用过去的信息来预测未来的事件。它们卓越的预测能力已经在各个领域得到了广泛应用,并取得了巨大成功。在本文中,我们将部署RNN模型来预测外汇市场的趋势,展示它们在提高外汇交易预测准确性方面的潜力。
神经网络实践:直线函数 神经网络实践:直线函数
在本文中,我们将快速了解一些方法,以获得可以在数据库中表示数据的函数。我不会详细介绍如何使用统计和概率研究来解释结果。让我们把它留给那些真正想深入研究数学方面的人。探索这些问题对于理解研究神经网络所涉及的内容至关重要。在这里,我们将非常冷静地探讨这个问题。