
在 MQL5 中重新构想经典策略(第二部分):富时 100 指数(FTSE100)与英国国债(UK Gilts)
现代投资者将人工智能融入到交易策略中的方式可能有无穷多种。任何个人投资者都不太可能有足够的时间仔细分析每种策略,然后再决定将自己的资金投于哪种策略。在本文系列中,我们将探索交易领域中人工智能可能的广阔应用前景。我们的目标是帮助您找到适合您投资偏好的策略。
交易策略概述
金融时报股票交易所100指数(FTSE100)是一个全球公认的指数,跟踪在伦敦证券交易所(LSE)上市的100家最大公司的表现。该指数于1984年创立,初始值为1000点,目前交易价格约为8000点。指数中的公司按其市值成比例加权,这意味着大公司对市场的影响比小公司更大。
所有发达经济体的政府都会发行以本国货币计价的债务,英国政府也不例外。英国国债是一种英国政府债务工具,它也在伦敦证券交易所上市。国债是固定收益证券,分为两种类型。第一种是传统国债,它构成了所销售国债的大部分。这种传统国债在到期前向国债持有者支付固定的票息,到期时将最后一笔票息和本金偿还给投资者。
第二种国债是通胀挂钩国债。这种国债没有固定的票息率,而是允许投资者从债券中获得浮动的回报,以补偿其在持有国债期间累积的通货膨胀总额。由于其现金流不可预测,通胀挂钩国债不如固定国债受欢迎。
债券的收益率与债券的需求之间存在反向关系。当某种债券在投资者中需求旺盛时,与该债券相关的收益率会下降;相反,如果债券表现不佳,其收益率会上升,以试图激发投资者的兴趣。
通常,当股市表现不佳时,投资者倾向于从风险较高的股市撤出资金,转而投资于更安全的证券,如政府债券。相反,当投资者对股市恢复信心时,他们倾向于从安全的政府债券中撤出资金,转而投资于股市。这种行为的根本原因在于预期回报率的差异。总体而言,股市的平均回报率高于债市,使其更具吸引力。然而,股市本身具有更大的内在风险,使其投资者对任何疲软迹象都极为敏感。
方法论概述
我们构建了一个由人工智能驱动的EA,以学习其自身关于这两个市场的交易规则。通常作为投资者,我们更偏向于那些吸引我们的策略,然而通过采用算法学习方法,可以确信我们的程序是基于收集的数据做出所有决策的,而不是偏见。
金融时间序列数据以噪声多著称。在处理噪声数据集时,众所周知,简单模型的表现可能与神经网络一样好。因此,我们使用矩阵和向量API从零开始,在MQL5中实现了一个线性回归模型。我们在拟合模型之前对输入数据进行了标准化和缩放,因为我们的输入数据处于不同的量纲,国债和英国100指数处于不同的量纲。我们采用了伪逆解来计算模型参数。
此外,我们还通过技术分析引导人工智能模型,因此我们的系统只有在技术指标也得出相同结论时才会进入交易。最后,我们还使用人工智能系统帮助我们检测潜在的反转,并决定何时平仓。在我们之前的文章中,当我们从零开始开发线性模型时,有用户评论说模型的输出超出了可接受范围。在本文中,我们尝试通过为模型添加截距列以及标准化和缩放数据来解决该问题。
在MQL5中的实现
我们首先在 MQL5 中构建了一个脚本,以便了解我们的EA的各个部分将如何组合在一起。首先,我们需要定义应用程序的输入参数。我们需要定义要获取多少数据,以及应该预测多远的未来。请记住,获取的数据越多,应用程序最终的计算复杂度就会越高。因此,我们尝试在这里找到一个平衡点,允许用户根据自身资源能力获取相应数据量。此外,我们还应该记住,市场环境是会变化的。因此,获取所有可用的数据可能不会带来性能上的提升。
最近,市场报价是通过算法提供的,而不是之前的公开喊价系统。基于我们不太可能再现的市场行为的报价来做研究,可能没有任何价值。因此,我们将专注于最近的数据。
//+------------------------------------------------------------------+ //| UK100 Gilts.mq5 | //| Gamuchirai Zororo Ndawana | //| https://www.mql5.com/en/gamuchiraindawa | //+------------------------------------------------------------------+ #property copyright "Gamuchirai Zororo Ndawana" #property link "https://www.mql5.com/en/gamuchiraindawa" #property version "1.00" #property script_show_inputs //+------------------------------------------------------------------+ //| Inputs | //+------------------------------------------------------------------+ input int fetch = 20; //How much data should we fetch? input int look_ahead = 20; //How far into the future should we forecast?
现在我们可以开始定义脚本的核心部分,首先定义在整个脚本中需要使用的变量。
//+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- Global variables we will need inside our expert advisor matrix coefficients = matrix::Zeros(1,9); vector mean_values = vector::Zeros(8); vector std_values = vector::Zeros(8); vector intercept = vector::Ones(fetch); matrix input_matrix = matrix::Zeros(9,fetch); matrix gilts_data,uk100_data,target;
现在我们来获取所需的数据,注意输入数据被向前滞后了look_ahead步,而目标数据则比输入数据提前获取。
//--- First we will fetch the market data gilts_data.CopyRates("UKGB_Z4",PERIOD_CURRENT,COPY_RATES_OHLC,1+look_ahead,fetch); uk100_data.CopyRates("UK100",PERIOD_CURRENT,COPY_RATES_OHLC,1+look_ahead,fetch); target.CopyRates("UK100",PERIOD_CURRENT,COPY_RATES_CLOSE,1,fetch);
我们现在需要创建一个输入矩阵。输入矩阵的第一列将是一个数字“1”,重复的次数与输入数据的获取次数相同。这是因为这一列的“1”将用于乘以线性模型中偏置项的计算结果。接下来,我们将填入与英国国债和富时100指数相关的数据。
//--- Fill in the input matrix input_matrix.Row(intercept,0); input_matrix.Row(gilts_data.Row(0),1); input_matrix.Row(gilts_data.Row(1),2); input_matrix.Row(gilts_data.Row(2),3); input_matrix.Row(gilts_data.Row(3),4); input_matrix.Row(uk100_data.Row(0),5); input_matrix.Row(uk100_data.Row(1),6); input_matrix.Row(uk100_data.Row(2),7); input_matrix.Row(uk100_data.Row(3),8);
让我们检查一下我们获取的数据,以确保数据的大小正确,并且我们拥有的矩阵适合进行我们即将执行的操作。请注意,如果我们的矩阵大小不适当,我们的计算将失败,并在执行时返回错误。
//--- Display the data fetched Print("Input matrix: "); Print("Rows: ",input_matrix.Rows()," Columns: ",input_matrix.Cols()); Print(input_matrix); Print("Target: "); Print("Rows: ",target.Rows()," Columns: ",target.Cols()); Print(target); Print("UK100: "); Print("Rows: ",uk100_data.Rows()," Columns: ",uk100_data.Cols()); Print(uk100_data); Print("GILTS: "); Print("Rows: ",gilts_data.Rows()," Columns: ",gilts_data.Cols()); Print(gilts_data);
为了对输入数据进行缩放和归一化,我们需要计算所处理的每一列数据的均值和标准差。当我刚开始学习机器学习时,我不确定何时需要对数据进行缩放和标准化。然而,随着时间的推移,我学到了一个经验法则,即当输入数据处于不同的量纲时,缩放是必要的。例如,我们的国债市场数据范围在100左右,而我们的富时100指数市场数据范围在8000左右,因此在这里进行缩放是必要的。请注意,目标值永远不需要进行缩放。
//--- Calculate the scaling values mean_values = input_matrix.Mean(1); std_values = input_matrix.Std(1); Print("Mean values: "); Print(mean_values); Print("Std values: "); Print(std_values);
现在,为了归一化我们的输入数据,我们首先减去列均值,然后除以标准差。这个预处理步骤有助于我们的模型有效学习,当模型输入处于不同量纲时,执行这一步骤是至关重要的,就像我们的情况一样。
//--- Normalizing and scaling our input data for(int i = 0; i < 8; i++) { //--- Extract the vector vector temp = input_matrix.Row(i + 1); //--- Scale the data temp = ((temp - mean_values[i+1]) / std_values[i+1]); //--- Write the data back input_matrix.Row(temp,i+1); } //--- Finished normalizing the data Print("Finished normalizing the data."); Print(input_matrix);
现在,让我们计算模型参数,即线性模型的系数。我们可以通过执行目标数据与输入数据的伪逆的矩阵乘法来实现这一点,这将返回一个1xn的矩阵,其中每个列都有一个系数值。每个系数值的符号告诉我们斜率,或者目标变量是随着输入变量的增加而增加还是减少。
//--- Now we can calculate our coefficient values coefficients = target.MatMul(input_matrix.PInv()); Print("Coefficient values"); Print(coefficients);
为了从我们的模型中获得预测,我们首先必须获取当前的输入数据值,对它们进行缩放,然后应用线性回归公式来获得模型的预测。
//--- Now we can obtain a forecast from our model gilts_data.CopyRates("UKGB_Z4",PERIOD_CURRENT,COPY_RATES_OHLC,0,1); uk100_data.CopyRates("UK100",PERIOD_CURRENT,COPY_RATES_OHLC,0,1); //--- Scale our inputs gilts_data[0,0] = ((gilts_data[0,0] - mean_values[1]) / std_values[1]); gilts_data[1,0] = ((gilts_data[1,0] - mean_values[2]) / std_values[2]); gilts_data[2,0] = ((gilts_data[2,0] - mean_values[3]) / std_values[3]); gilts_data[3,0] = ((gilts_data[3,0] - mean_values[4]) / std_values[4]); uk100_data[0,0] = ((uk100_data[0,0] - mean_values[5]) / std_values[5]); uk100_data[1,0] = ((uk100_data[1,0] - mean_values[6]) / std_values[6]); uk100_data[2,0] = ((uk100_data[2,0] - mean_values[7]) / std_values[7]); uk100_data[3,0] = ((uk100_data[3,0] - mean_values[8]) / std_values[8]); Print("Normalized inputs: "); Print(gilts_data); Print(uk100_data); double forecast = ( (1 * coefficients[0,0]) + (gilts_data[0,0] * coefficients[0,1]) + (gilts_data[1,0] * coefficients[0,2]) + (gilts_data[2,0] * coefficients[0,3]) + (gilts_data[3,0] * coefficients[0,4]) + (uk100_data[0,0] * coefficients[0,5]) + (gilts_data[1,0] * coefficients[0,6]) + (gilts_data[2,0] * coefficients[0,7]) + (gilts_data[3,0] * coefficients[0,8]) ); //--- Give our predictions Comment("Model forecast: ",forecast);
图1:我们的脚本
图2:脚本输入参数
图3:我们模型的预测
在MQL5中创建EA
既然我们已经进展到这一步,就可以开始构建我们的EA了。要开始构建我们的应用程序,首先需要导入交易库,以便我们可以管理的头寸。
//+------------------------------------------------------------------+ //| UK100 Gilts.mq5 | //| Gamuchirai Zororo Ndawana | //| https://www.mql5.com/en/gamuchiraindawa | //+------------------------------------------------------------------+ #property copyright "Gamuchirai Zororo Ndawana" #property link "https://www.mql5.com/en/gamuchiraindawa" #property version "1.00" //+------------------------------------------------------------------+ //|Libraries we need | //+------------------------------------------------------------------+ #include <Trade/Trade.mqh> //Trade class CTrade Trade; //Initialize the class
然后,我们将设置应用程序的输入参数,以便最终用户可以决定获取多少数据以及预测多远的未来。
//+------------------------------------------------------------------+ //| Inputs | //+------------------------------------------------------------------+ input int fetch = 20; //How much data should we fetch? input int look_ahead = 20; //How far into the future should we forecast?
我们还需要整个程序中都将使用到的全局变量。
//+------------------------------------------------------------------+ //| Global vairables | //+------------------------------------------------------------------+ matrix coefficients = matrix::Zeros(1,9); vector mean_values = vector::Zeros(8); vector std_values = vector::Zeros(8); vector intercept = vector::Ones(fetch); matrix input_matrix = matrix::Zeros(9,fetch); matrix gilts_data,uk100_data,target; double willr_buffer[],rsi_buffer[]; int willr_handler,rsi_handler; double forecast,bid,ask; int model_forecast = 0; int state = 0;
让我们定义一个负责获取训练数据以及获取我们需要的缩放值以标准化和缩放数据的函数。请注意,我们使用矩阵而不是数组,这是因为我们打算利用 MQL5 API 中包含的专用矩阵和向量函数。
//+------------------------------------------------------------------+ //| Let us fetch our training data | //+------------------------------------------------------------------+ void fetch_training_data(void) { //--- First we will fetch the market data gilts_data.CopyRates("UKGB_Z4",PERIOD_CURRENT,COPY_RATES_OHLC,1+look_ahead,fetch); uk100_data.CopyRates("UK100",PERIOD_CURRENT,COPY_RATES_OHLC,1+look_ahead,fetch); target.CopyRates("UK100",PERIOD_CURRENT,COPY_RATES_CLOSE,1,fetch); //--- Fill in the input matrix input_matrix.Row(intercept,0); input_matrix.Row(gilts_data.Row(0),1); input_matrix.Row(gilts_data.Row(1),2); input_matrix.Row(gilts_data.Row(2),3); input_matrix.Row(gilts_data.Row(3),4); input_matrix.Row(uk100_data.Row(0),5); input_matrix.Row(uk100_data.Row(1),6); input_matrix.Row(uk100_data.Row(2),7); input_matrix.Row(uk100_data.Row(3),8); //--- Display the data fetched Print("Input matrix: "); Print("Rows: ",input_matrix.Rows()," Columns: ",input_matrix.Cols()); Print(input_matrix); Print("Target: "); Print("Rows: ",target.Rows()," Columns: ",target.Cols()); Print(target); Print("UK100: "); Print("Rows: ",uk100_data.Rows()," Columns: ",uk100_data.Cols()); Print(uk100_data); Print("GILTS: "); Print("Rows: ",gilts_data.Rows()," Columns: ",gilts_data.Cols()); Print(gilts_data); //--- Calculate the scaling values mean_values = input_matrix.Mean(1); std_values = input_matrix.Std(1); Print("Mean values: "); Print(mean_values); Print("Std values: "); Print(std_values); } //+------------------------------------------------------------------+
既然我们已经得到了缩放值,让我们定义一个函数来实际对输入数据进行缩放和标准化。
//+------------------------------------------------------------------+ //| Let us scale and standardize the training data | //+------------------------------------------------------------------+ void scale_training_data(void) { //--- Normalizing and scaling our input data for(int i = 0; i < 8; i++) { //--- Extract the vector vector temp = input_matrix.Row(i + 1); //--- Scale the data temp = ((temp - mean_values[i+1]) / std_values[i+1]); //--- Write the data back input_matrix.Row(temp,i+1); } //--- Finished normalizing the data Print("Finished normalizing the data."); Print(input_matrix); }
在我们的训练数据经过缩放和标准化之后,我们就可以开始计算模型参数了。
//+------------------------------------------------------------------+ //| Calculate coefficient values | //+------------------------------------------------------------------+ void calculate_coefficient_values(void) { //--- Now we can calculate our coefficient values coefficients = target.MatMul(input_matrix.PInv()); Print("Coefficient values"); Print(coefficients); }
我们还需要一个负责从模型中获取预测值的函数。流程很简单:首先获取市场数据,对数据进行缩放,然后最终应用线性回归公式以获得预测值。
//+------------------------------------------------------------------+ //| Fetch a forecast from our model | //+------------------------------------------------------------------+ void fetch_forecast(void) { //--- Now we can obtain a forecast from our model gilts_data.CopyRates("UKGB_Z4",PERIOD_CURRENT,COPY_RATES_OHLC,0,1); uk100_data.CopyRates("UK100",PERIOD_CURRENT,COPY_RATES_OHLC,0,1); //--- Scale our inputs gilts_data[0,0] = ((gilts_data[0,0] - mean_values[1]) / std_values[1]); gilts_data[1,0] = ((gilts_data[1,0] - mean_values[2]) / std_values[2]); gilts_data[2,0] = ((gilts_data[2,0] - mean_values[3]) / std_values[3]); gilts_data[3,0] = ((gilts_data[3,0] - mean_values[4]) / std_values[4]); uk100_data[0,0] = ((uk100_data[0,0] - mean_values[5]) / std_values[5]); uk100_data[1,0] = ((uk100_data[1,0] - mean_values[6]) / std_values[6]); uk100_data[2,0] = ((uk100_data[2,0] - mean_values[7]) / std_values[7]); uk100_data[3,0] = ((uk100_data[3,0] - mean_values[8]) / std_values[8]); Print("Normalized inputs: "); Print(gilts_data); Print(uk100_data); //--- Calculate the model's prediction forecast = ( (1 * coefficients[0,0]) + (gilts_data[0,0] * coefficients[0,1]) + (gilts_data[1,0] * coefficients[0,2]) + (gilts_data[2,0] * coefficients[0,3]) + (gilts_data[3,0] * coefficients[0,4]) + (uk100_data[0,0] * coefficients[0,5]) + (gilts_data[1,0] * coefficients[0,6]) + (gilts_data[2,0] * coefficients[0,7]) + (gilts_data[3,0] * coefficients[0,8]) ); //--- Store the model's prediction if(forecast < iClose("UK100",PERIOD_CURRENT,0)) { model_forecast = -1; } if(forecast > iClose("UK100",PERIOD_CURRENT,0)) { model_forecast = 1; } //--- Give the user feedback Comment("Model forecast: ",forecast); }
最后,我们还需要一个函数来获取当前市场数据和技术指标数据。
//+------------------------------------------------------------------+ //| This function will fetch current market data | //+------------------------------------------------------------------+ void update_market_data(void) { //--- Market prices bid = SymbolInfoDouble("UK100",SYMBOL_BID); ask = SymbolInfoDouble("UK100",SYMBOL_ASK); //--- Technical indicators CopyBuffer(rsi_handler,0,0,1,rsi_buffer); CopyBuffer(willr_handler,0,0,1,willr_buffer); }
此外,我们还需要两个函数来验证我们AI模型的预测是否与技术指标一致。我们希望仅在技术分析和量化分析都指向同一方向时,进行高成功率的交易。
//+------------------------------------------------------------------+ //| This function will check if we have oppurtunities to buy | //+------------------------------------------------------------------+ void check_bullish_sentiment(void) { if((willr_buffer[0] > -20) && (rsi_buffer[0] > 70)) { Trade.Buy(0.2,"UK100",ask,ask-5,ask+5,"UK100 Gilts AI"); state = 1; } } //+------------------------------------------------------------------+ //| This function will check if we have oppurtunities to sell | //+------------------------------------------------------------------+ void check_bearish_sentiment(void) { if((willr_buffer[0] < -80) && (rsi_buffer[0] <370)) { Trade.Sell(0.2,"UK100",ask,ask-5,ask+5,"UK100 Gilts AI"); state = -1; } }
不仅如此,我们现在可以开始定义EA的初始化过程了。首先,我们需要获取训练数据,然后对训练数据进行缩放和标准化,之后,我们将计算系数值,最后,我们将设置技术指标并验证这些技术指标是否有效。
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Fetch the training data fetch_training_data(); //--- Scale the training data scale_training_data(); //--- Calculate the coefficients calculate_coefficient_values(); //--- Setup the indicators rsi_handler = iRSI("UK100",PERIOD_CURRENT,20,PRICE_CLOSE); willr_handler = iWPR("UK100",PERIOD_CURRENT,30); //--- Validate the technical indicators if((rsi_handler == INVALID_HANDLE) || (willr_handler == INVALID_HANDLE)) { Comment("Failed to load indicators. ",GetLastError()); return(INIT_FAILED); } //--- Everything went well return(INIT_SUCCEEDED); }
每当EA被从图表上移除时,我们将释放不再使用的资源。我们将释放在技术分析中使用过的指标,并且还会将EA从图表中移除。
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Free up the resoruces we don't need IndicatorRelease(willr_handler); IndicatorRelease(rsi_handler); ExpertRemove(); }
最后,每当我们收到更新的价格时,首先会更新市场数据,然后从我们的 AI 模型中获取预测。如果我们没有任何未平仓头寸,我们会检查技术指标是否与 AI 模型一致,然后再开仓。否则,如果已经有未平仓头寸,我们会检查 AI 模型的预测是否与未平仓头寸走势相反。如果我们的模型预计会出现反转,我们将关闭未平仓头寸。
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Fetch updated market data update_market_data(); //--- Fetch a forecast from our model fetch_forecast(); //--- Check for a position we can open if(PositionsTotal() == 0) { if(model_forecast == 1) { check_bullish_sentiment(); } else if(model_forecast == -1) { check_bearish_sentiment(); } } //--- Check for a reversal else if(PositionsTotal() > 0) { if(model_forecast != state) { Alert("Reversal detected by our AI system! Closing all positions now."); Trade.PositionClose("UK100"); } } } //+------------------------------------------------------------------+
图4:运行我们的AI系统
结论
在今天的文章中,我们展示了如何在 MQL5 中构建一个由人工智能引导的EA。我们方法的最大优势在于,可以在不需要重新校准应用程序的情况下更改时间框架。相比之下,如果使用ONNX来实现我们的解决方案,那么对于每个希望交易的时间框架,我们至少需要一个ONNX模型。我们目前的解决方案是自我优化的,并且能够在许多不同的条件下工作。
本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/15771


