English Русский Español Deutsch 日本語
preview
关于因果网络分析(Causality Network Analysis,CNA)和向量自回归(Vector Autoregression,VAR)模型在市场事件预测中的应用实例

关于因果网络分析(Causality Network Analysis,CNA)和向量自回归(Vector Autoregression,VAR)模型在市场事件预测中的应用实例

MetaTrader 5交易系统 | 17 四月 2025, 07:47
439 0
Javier Santiago Gaston De Iriarte Cabrera
Javier Santiago Gaston De Iriarte Cabrera

概述

因果网络分析(CNA)是一种用于理解和建模系统中变量之间复杂因果关系的方法。当应用于金融市场时,它可以帮助识别不同的市场事件和因素如何相互影响,从而可能带来更准确的预测。

因果发现:因果发现是从观察数据中推断因果关系的过程。在金融市场的背景下,这意味着识别哪些变量(如经济指标、市场价格或外部事件)对其他变量有因果影响。因果发现有多种算法,但其中最流行的是PC(Peter-Clark)算法。

这个交易机器人并没有明确实现一个被称为“市场事件预测的因果网络分析”的系统。然而,该机器人确实融入了因果分析和基于网络的方法,这些方法在概念上是相似的。我们来将其分解:

因果分析:机器人使用因果发现算法,具体而言是快速因果推断(Fast Causal Inference,FCI)算法(而不是PC算法)。其在FCIAlgorithm()函数中实现。

网络分析:机器人使用网络结构来表示不同金融工具或指标之间的关系。这可以从Node结构和SetupNetwork()函数中看出。

事件预测:虽然没有明确称为“事件预测”,但机器人使用向量自回归(VAR)模型对未来市场状态进行预测。这在TrainVARModel()和PredictVARValue()函数中实现。


因果网络分析:市场事件预测的新领域

在算法交易的世界中,一种新的方法正在量化交易者和普通交易者中流行起来:用于市场事件预测的因果网络分析。这种复杂的方法结合了因果推断、网络理论和预测分析的优势,以前所未有的准确性预测重要的市场事件。

想象一下金融市场是一个庞大且相互连接的网络。每一条线代表不同市场变量之间的关系——股票价格、经济指标、当地政治事件等。传统分析通常关注相关性,但正如经验丰富的交易者所知,相关性并不总是意味着因果关系。

这就是因果网络分析发挥作用的地方。它旨在揭示这个复杂网络中真正的因果关系。通过这样做,为交易者提供了对市场动态的更深入理解,使他们能够预测可能对传统分析不可见的事件。

因果网络分析的核心涉及三个关键步骤:

1. 构建网络:首先,我们构建一个网络,其中每个节点代表一个市场变量。这些可以是资产价格、交易量、经济指标和情绪评分等任何内容。

2. 发现因果联系:接下来,我们使用高级算法来确定这些节点之间的因果关系。这就是体现魔法之处——我们不只关注哪些变量一起变动,而更要重点关注哪些变量实际上推动了其他变量的变化。

3. 预测事件:最后,我们使用这个因果网络来预测重要的市场事件。通过理解市场变动的真正驱动因素,我们可以更好地预测重大变化、崩盘或反弹。


对于交易者的优势

对于MQL5交易者来说,实施因果网络分析可能会改变游戏规则。原因如下:

  • 改进的风险管理:通过识别市场波动的根源,您可以更好地为潜在的下跌做好准备。
  • 更准确的预测:理解因果关系比仅基于相关性的预测更可靠。
  • 发现隐藏的机会:因果网络可能会揭示一开始并不明显的关系,从而带来少见的交易机会。
  • 适应性策略:随着市场的演变,因果网络也会演变,促使您的策略能够与时俱进。


在MQL5中实现

虽然实现一个完整的因果网络分析系统是复杂的,但MQL5为这种高级分析提供了一个强大的平台。您可以从以下方面入手:

  1. 使用iCustom()函数为网络中的每个节点创建指标。
  2. 实现因果发现算法,如PC算法或格兰杰因果检验(在本例中我们使用了FCI算法)。
  3. 利用MQL5的神经网络功能,基于您的因果网络构建预测模型。


为什么我们使用FCI而不是PC?

FCI(快速因果推断)和PC(Peter-Clark)都是因果推断领域中使用的因果发现算法。它们旨在从观察数据中推断因果关系。以下是选择FCI而非PC的原因:

  1. 潜在混杂因素:FCI相对于PC的主要优势在于其处理潜在混杂因素的能力。FCI可以推断隐藏的共同原因的存在,而PC假设因果充分性(没有潜在混杂因素)。
  2. 选择偏差:FCI还可以考虑数据中的选择偏差,而PC则不能。
  3. 更一般的模型:FCI产生了一种被称为部分祖先图(Partial Ancestral Graph,PAG)的更一般的图形模型,它可以表示比PC产生的有向无环图(Directed Acyclic Graphs,DAGs)更广泛的因果结构类别。
  4. 正确性和完备性:FCI对于因果不充分系统类别是正确且完备的,这意味着在样本量无限的情况下,它可以正确识别所有可能的因果关系。
  5. 鲁棒性:由于其处理潜在混杂因素和选择偏差的能力,FCI在处理现实世界数据时通常更具鲁棒性,因为在现实世界数据中,隐藏变量是常见的。

然而,需要注意的是,与PC相比,FCI也有一些缺点:

  1. 计算复杂性:FCI通常比PC计算成本更高,尤其是对于大型数据集。
  2. 输出结果不够明确:FCI产生的PAG通常比PC产生的DAG包含更多的未确定的方向,这反映了潜在混杂因素带来的额外不确定性。
  3. 解释性:对于非专业人士而言,PAG比DAG更难以解释。

在实践中,选择FCI还是PC通常取决于您对于因果推断任务的具体需求、数据的性质以及您对因果系统的假设。如果您确信没有隐藏的混杂因素且没有选择偏差,那么PC可能就足够且更高效了。如果您怀疑存在潜在变量或选择偏差,那么FCI将是更合适的选择。


向量自回归(VAR)模型

VAR是一种多变量预测算法,用于两个或多个时间序列相互影响的情况。在该交易系统中,它可能被用来建模不同金融工具或经济指标之间的关系。

VAR的关键特征:

  1. 它捕捉多个时间序列之间的线性相互依赖关系。
  2. 每个变量都是自身过去滞后值和其他变量过去滞后值的线性函数。
  3. 它允许在多时间序列系统中存在丰富的动态变化。

在该交易系统的上下文中:

  • 使用TrainVARModel函数训练VAR模型。
  • PredictVARValue函数使用训练好的模型进行预测。
  • 该系统通过OptimizeVARModel函数选择最优滞后和重要变量来优化VAR模型。


数学表示如下:

对于一个滞后为1的两变量VAR模型:

y1,t = c1 + φ11y1,t-1 + φ12y2,t-1 + ε1,t

y2,t = c2 + φ21y1,t-1 + φ22y2,t-1 + ε2,t

其中:

  • y1,t和y2,t表示在t时刻的两个变量值
  • c1和c2表示常数
  • φij表示系数
  • ε1,t和ε2,t表示误差项

系统通过估计这些系数来进行预测。

想象一下,您不仅预测单一资产的未来价格,而是同时预测多个相互关联的金融变量。这就是VAR模型的亮点所在。它就像一个水晶球,不仅能向您展示一个未来场景,而是同时展示多个相互关联的未来场景。

VAR的核心是一种多变量预测算法,用于两个或多个时间序列相互影响的情况。简言之,它是一种捕捉多个时间序列之间线性依赖关系的方法。


VAR是如何发挥其作用的?

我们来将其分解:

  1. 数据收集:您首先收集想要包含在模型中的所有变量的历史数据。这可能是多个货币对、商品甚至经济指标的价格数据。
  2. 模型设定:由您决定包含多久的滞后(过去的时期)。这里就需要用到您的交易经验了!
  3. 估计:模型估计每个变量如何受到自身过去值和其他变量过去值的影响。
  4. 预测:一旦估计完成,VAR模型可以同时为所有包含的变量生成预测。


在MQL5中实现VAR

尽管MQL5没有内置的VAR函数,但您可以自己来实现它。以下是一个简化的例子,展示您可以如何构建代码:

// Define your VAR model
struct VARModel {
    int lag;
    int variables;
    double[][] coefficients;
};

// Estimate VAR model
VARModel EstimateVAR(double[][] data, int lag) {
    // Implement estimation logic here
    // You might use matrix operations for efficiency
}

// Make predictions
double[] Forecast(VARModel model, double[][] history) {
    // Implement forecasting logic here
}

// In your EA or indicator
void OnTick() {
    // Collect your multivariate data
    double[][] data = CollectData();
    
    // Estimate model
    VARModel model = EstimateVAR(data, 5); // Using 5 lags
    
    // Make forecast
    double[] forecast = Forecast(model, data);
    
    // Use forecast in your trading logic
    // ...
}


VAR在交易中的优势

想象一下您在交易欧元/美元(EUR/USD)。传统的方法可能只会查看过去的欧元/美元价格。但使用VAR,您可以包括:

  • 欧元/美元(EUR/USD)价格
  • 美元/日元(USD/JPY)价格(以捕捉美元整体的强弱)
  • 石油价格(对许多货币来说是一个重要因素)
  • 标普500(S&P 500)指数(以衡量整体市场情绪)
  • 美元欧元(US-EU )利率差异

现在,您的模型捕捉到了一个更丰富的外汇市场画面,这可能会带来更明智的交易决策。


挑战和考虑因素

像任何强大的工具一样,VAR也有其挑战:

  1. 数据需求:VAR模型可能对数据需求很高。确保您有足够的历史数据来进行可靠的估计。
  2. 计算强度:随着变量和滞后的增加,计算需求也会增加。优化您的代码以提高效率。
  3. 平稳性:VAR假设时间序列是平稳的。您可能需要对数据进行预处理(例如,差分)以满足这一假设。
  4. 解释性:在多个变量和滞后的情况下,解释VAR结果可能会很复杂。不要忘记将统计分析与您的交易知识结合起来。


CNA交易系统中的网络分析

在此EA中实现的网络分析是其市场分析和预测策略的基本组成部分。它旨在表示和分析不同金融工具或市场变量之间的复杂关系。

总结一下这个EA中使用的网络分析的关键点:

  1. 结构:网络由节点组成,每个节点代表一种金融工具(通常是货币对)。
  2. 目的:它旨在建模市场中不同金融工具之间的关系和相互依赖性。
  3. 因果发现:EA使用快速因果推断(FCI)算法来发现这些工具之间的潜在因果关系。
  4. 关系表示:这些关系在邻接矩阵中表示,显示哪些工具在因果影响方面直接相连。
  5. 分析:EA对这个网络进行各种分析,包括识别v结构(因果图中的特定模式)并应用方向规则,以进一步完善对因果关系的理解。
  6. 与预测的整合:网络结构和通过这种分析发现的关系作为输入,用于向量自回归(VAR)模型进行预测。
  7. 自适应性:网络分析不是静态的。它可以随着时间的推移而更新,使EA能够适应不断变化的市场条件和工具之间的关系。

这种方法背后的关键思想是,金融工具并不是孤立移动的。通过建模和分析不同工具之间的关系网络,EA旨在获得对市场动态的更全面理解。理论上而言,这应该能够带来更准确的预测和更明智的交易决策。

然而,重要的是要注意,尽管这种方法比较先进,但金融市场极其复杂,受许多因素的影响。这种网络分析的有效性将取决于它捕捉真实市场动态的能力以及它与其他交易策略组成部分的整合程度。


网络的结构

网络由以下结构组成:

struct Node
{
    string name;
    double value;
};

Node g_network[];
int g_node_count = 0;

网络中的每个节点代表一个特定的金融工具或市场变量。名称字段用于标识该工具(例如,“EURUSD”,“GBPUSD”),而值字段可以存储该节点的相关数据。


设置网络

用SetupNetwork()函数初始化网络:

void SetupNetwork()
{
    string symbols[] = {"EURUSD", "GBPUSD", "USDCAD", "USDCHF", "USDJPY", "AUDUSD"};
    for(int i = 0; i < ArraySize(symbols); i++)
    {
        AddNode(symbols[i]);
    }
}

void AddNode(string name)
{
    ArrayResize(g_network, g_node_count + 1);
    g_network[g_node_count].name = name;
    g_network[g_node_count].value = 0; // Initialize with default value
    g_node_count++;
}

该设置创建了一个网络,其中每个节点代表不同的货币对。


网络的目的

在该EA中,网络有几个关键目的:

  1. 市场结构的表示:它模拟了不同货币对之间的相互联系,使EA能够考虑一个货币对的变动可能如何影响其他货币对。
  2. 因果分析的基础:网络结构被用作快速因果推断(FCI)算法的基础,该算法试图发现节点之间的因果关系。
  3. 预测建模的输入:通过因果分析所发现的网络结构及关系,可作为(用于预测的)向量自回归(Vector Autoregression,简称VAR)模型的输入。


网络分析的实际应用

EA对这个网络进行几种类型的分析:

  1. 因果发现:FCIAlgorithm()函数应用快速因果推断算法来发现节点之间的潜在因果关系。
  2. 邻接矩阵:因果关系在邻接矩阵中表示,其中每个条目指示两个节点之间是否存在直接的因果联系。
  3. V结构定向:OrientVStructures()函数识别并定向网络中的V结构,这是因果图中的重要模式。
  4. 图分析:最终的图结构被分析以指导交易决策,假设因果关联的工具可能为彼此提供预测信息。


对交易的影响

这种基于网络的方法使EA能够:

  1. 考虑复杂的市场动态,这些动态在单独查看工具时可能不明显。
  2. 在分析的工具中潜在地识别领先指标。
  3. 通过考虑更广泛的市场背景,做出更明智的预测。
  4. 跟随定期重新评估的因果结构,适应不断变化的市场条件。

通过利用这种网络分析,EA旨在获得对市场动态进行更深入地理解,从而可能实现更准确的预测和更明智的交易决策。


代码示例

该代码遵循以下两个流程图:

流程图

功能流程图



MQL5交易程序中关键函数的详细解释

此交易EA相当复杂,结合了几种高级方法:

  1. 使用向量自回归(VAR)模型进行预测。
  2. 实现了运用因果发现算法(FCI - 快速因果推断)来理解不同市场变量之间的关系。
  3. 采用网络结构来同时表示和分析多个金融工具。
  4. 结合了各种技术过滤器,如波动率、相对强弱指数(RSI)和趋势强度。
  5. 使用自适应的风险管理和仓位调整。
  6. 实现了滑动窗口策略,以持续更新和重新训练模型。
  7. 包括交叉验证和模型优化技术。

此EA的结构允许进行复杂分析和决策,同时保持灵活性,以便未来进行改进或适应不同的市场条件。


交易EA中关键函数的详细解释

1. OnInit()

int OnInit()
  {
// Step 1: Set up your network structure
   SetupNetwork();

// Step 2: Run the causal discovery algorithm (e.g., PC or FCI)
   FCIAlgorithm();

// Step 3: Train the optimized causal model
   TrainOptimizedVARModel();

   ArrayResize(g_previous_predictions, g_node_count);
   ArrayInitialize(g_previous_predictions, 0);  // Initialize with zeros

   return(INIT_SUCCEEDED);
  }

这是EA首次在图表上加载或重新加载时运行的初始化函数。

关键功能:

  • 通过调用SetupNetwork()设置网络结构。
  • 使用FCIAlgorithm()运行因果发现算法(FCI)。
  • 使用TrainOptimizedVARModel()训练优化后的因果模型。
  • 初始化之前的预测数组。

此函数为EA将要进行的所有分析和预测奠定了基础。

2. OnTick()

void OnTick()
  {
   ENUM_TIMEFRAMES tf = ConvertTimeframe(InputTimeframe);

   static datetime lastBarTime = 0;
   datetime currentBarTime = iTime(Symbol(), tf, 0);

   if(currentBarTime == lastBarTime)
      return;

   lastBarTime = currentBarTime;

   Print("--- New bar on timeframe ", EnumToString(tf), " ---");

   UpdateModelSlidingWindow();

   for(int i = 0; i < g_node_count; i++)
     {
      string symbol = g_network[i].name;
      Print("Processing symbol: ", symbol);

      double prediction = PredictVARValue(i);
      Print("Prediction for ", symbol, ": ", prediction);

      int signal = GenerateSignal(symbol, prediction);
      Print("Signal for ", symbol, ": ", signal);

      // Imprimir más detalles sobre las condiciones
      Print("RSI: ", CustomRSI(symbol, ConvertTimeframe(InputTimeframe), 14, PRICE_CLOSE, 0));
      Print("Trend: ", DetermineTrend(symbol, ConvertTimeframe(InputTimeframe)));
      Print("Trend Strong: ", IsTrendStrong(symbol, ConvertTimeframe(InputTimeframe)));
      Print("Volatility OK: ", VolatilityFilter(symbol, ConvertTimeframe(InputTimeframe)));
      Print("Fast MA > Slow MA: ", (iMA(symbol, PERIOD_CURRENT, 14, 0, MODE_EMA, PRICE_CLOSE) > iMA(symbol, PERIOD_CURRENT, 24, 0, MODE_EMA, PRICE_CLOSE)));

      if(signal != 0)
        {
         Print("Attempting to execute trade for ", symbol);
         ExecuteTrade(symbol, signal);
        }
      else
        {
         g_debug_no_signal_count++;
         Print("No trade signal generated for ", symbol, ". Total no-signal count: ", g_debug_no_signal_count);
        }
      ManageOpenPositions();
      ManageExistingOrders(symbol);


      Print("Current open positions for ", symbol, ": ", PositionsTotal());
      Print("Current pending orders for ", symbol, ": ", OrdersTotal());
     }

   Print("--- Bar processing complete ---");
   Print("Total no-signal count: ", g_debug_no_signal_count);
   Print("Total failed trade count: ", g_debug_failed_trade_count);
  }

此函数在每个市场tick时被调用。

主要任务:

  • 转换输入时间框架
  • 通过使用UpdateModelSlidingWindow()滑动窗口更新模型。
  • 对于网络中的每个交易品种:
    • 使用PredictVARValue()预测其值
    • 使用GenerateSignal()生成交易信号
    • 如果已生成信号,则执行交易。
    • 管理开仓
    • 管理现有的订单

这是EA的操作核心,交易决策在这里执行。

3. OnCalculate()

虽然在提供的代码中没有明确显示,但这个函数通常会用于计算自定义指标的值。在此EA中,其中一些功能似乎被集成到了OnTick()中。

4. CheckMarketConditions()

int GenerateSignal(string symbol, double prediction)
  {
   int node_index = FindNodeIndex(symbol);
   if(node_index == -1)
      return 0;

   static bool first_prediction = true;
   if(first_prediction)
     {
      first_prediction = false;
      return 0; // No generar señal en la primera predicción
     }

   double current_price = SymbolInfoDouble(symbol, SYMBOL_BID);

// Calculate predicted change as a percentage
   double predicted_change = (prediction - current_price) / current_price * 100;

   bool volatility_ok = VolatilityFilter(symbol, ConvertTimeframe(InputTimeframe));
   double rsi = CustomRSI(symbol, ConvertTimeframe(InputTimeframe), 14, PRICE_CLOSE, 0);
   bool trend_strong = IsTrendStrong(symbol, ConvertTimeframe(InputTimeframe));
   int trend = DetermineTrend(symbol, ConvertTimeframe(InputTimeframe));

   double fastMA = iMA(symbol, PERIOD_CURRENT, 8, 0, MODE_EMA, PRICE_CLOSE);
   double slowMA = iMA(symbol, PERIOD_CURRENT, 24, 0, MODE_EMA, PRICE_CLOSE);

   Print("Debugging GenerateSignal for ", symbol);
   Print("Current price: ", current_price);
   Print("Prediction diff: ", prediction);
   Print("predicted_change: ", predicted_change);
   Print("RSI: ", rsi);
   Print("Trend: ", trend);
   Print("Trend Strong: ", trend_strong);
   Print("Volatility OK: ", volatility_ok);
   Print("Fast MA: ", fastMA, ", Slow MA: ", slowMA);

   bool buy_condition =  prediction >  0.00001 && rsi < 30 && trend_strong  && volatility_ok && fastMA > slowMA;
   bool sell_condition = prediction < -0.00001 && rsi > 70 && trend_strong && volatility_ok && fastMA < slowMA;

   Print("Buy condition met: ", buy_condition);
   Print("Sell condition met: ", sell_condition);

// Buy conditions
   if(buy_condition)
     {
      Print("Buy signal generated for ", symbol);
      int signal = 1;
      return signal;  // Buy signal
     }
// Sell conditions
   else
      if(sell_condition)
        {
         Print("Sell signal generated for ", symbol);
         int signal = -1;
         return signal; // Sell signal
        }
      else
        {
         Print("No signal generated for ", symbol);
         int signal = 0;
         return signal; // No signal
        }


  }

此函数在代码中没有明确定义,但其功能分布在几个部分中,主要在GenerateSignal()中:

  • 通过VolatilityFilter()检查波动率
  • 检查相对强弱指数(RSI)
  • 通过IsTrendStrong()验证趋势强度
  • 通过DetermineTrend()确定趋势方向
  • 比较快速和慢速移动平均线

这些检查决定了市场条件是否有利于交易。

5. ExecuteTrade()

void ExecuteTrade(string symbol, int signal)
  {
   if(!IsMarketOpen(symbol) || !IsTradingAllowed())
     {
      Print("Market is closed or trading is not allowed for ", symbol);
      return;
     }

   double price = (signal == 1) ? SymbolInfoDouble(symbol, SYMBOL_ASK) : SymbolInfoDouble(symbol, SYMBOL_BID);

   double stopLoss, takeProfit;
   CalculateAdaptiveLevels(symbol, signal, stopLoss, takeProfit);

   double lotSize = CalculateDynamicLotSize(symbol, stopLoss);

   if(lotSize <= 0)
     {
      Print("Invalid lot size for symbol: ", symbol);
      return;
     }

   trade.SetExpertMagicNumber(123456);
   trade.SetTypeFilling(ORDER_FILLING_FOK);

   bool result = false;
   int attempts = 3;

   for(int i = 0; i < attempts; i++)
     {
      if(signal == 1)
        {
         result = trade.Buy(lotSize, symbol, price, stopLoss, takeProfit, "CNA Buy");
         Print("Attempting Buy order: Symbol=", symbol, ", Lot Size=", lotSize, ", Price=", price, ", SL=", stopLoss, ", TP=", takeProfit);
        }
      else
         if(signal == -1)
           {
            result = trade.Sell(lotSize, symbol, price, stopLoss, takeProfit, "CNA Sell");
            Print("Attempting Sell order: Symbol=", symbol, ", Lot Size=", lotSize, ", Price=", price, ", SL=", stopLoss, ", TP=", takeProfit);
           }

      if(result)
        {
         Print("Order executed successfully for ", symbol, ", Type: ", (signal == 1 ? "Buy" : "Sell"), ", Lot size: ", lotSize);
         break;
        }
      else
        {
         int lastError = GetLastError();
         Print("Attempt ", i+1, " failed for ", symbol, ". Error: ", lastError, " - ", GetErrorDescription(lastError));

         if(lastError == TRADE_RETCODE_REQUOTE || lastError == TRADE_RETCODE_PRICE_CHANGED || lastError == TRADE_RETCODE_INVALID_PRICE)
           {
            price = (signal == 1) ? SymbolInfoDouble(symbol, SYMBOL_ASK) : SymbolInfoDouble(symbol, SYMBOL_BID);
            CalculateAdaptiveLevels(symbol, signal, stopLoss, takeProfit);
           }
         else
            break;
        }
     }

   if(!result)
     {
      Print("All attempts failed for ", symbol, ". Last error: ", GetLastError(), " - ", GetErrorDescription(GetLastError()));
     }
  }

此函数负责实际执行交易。

关键方面:

  • 验证市场是否开放以及是否允许交易
  • 计算自适应的止损和获利水平
  • 根据风险管理确定手数大小
  • 尝试执行订单,失败时重试
  • 处理错误并提供详细的反馈

6. OnDeinit()

void OnDeinit(const int reason)
  {
   if(atr_handle != INVALID_HANDLE)
      IndicatorRelease(atr_handle);
   if(rsi_handle != INVALID_HANDLE)
      IndicatorRelease(rsi_handle);
   if(momentum_handle != INVALID_HANDLE)
      IndicatorRelease(momentum_handle);
   GenerateFinalSummary();
  }

当EA从图表中移除或终端关闭时,会调用此函数。

主要功能:

  • 释放指标句柄
  • 通过调用GenerateFinalSummary()生成对于EA性能的最终总结


其他重要函数

UpdateModelSlidingWindow()

void UpdateModelSlidingWindow()
  {
   static int bars_since_update = 0;
   ENUM_TIMEFRAMES tf = ConvertTimeframe(InputTimeframe);

// Check if it's time to update
   if(bars_since_update >= g_update_frequency)
     {
      // Collect new data
      double training_data[];
      CollectTrainingData(training_data, g_window_size, tf);

      // Retrain model
      TrainModel(training_data);

      // Validate model
      double validation_score = ValidateModel();
      Print("Model updated. Validation score: ", validation_score);

      bars_since_update = 0;
     }
   else
     {
      bars_since_update++;
     }
  }

使用数据的滑动窗口更新VAR模型,使模型能够适应不断变化的市场条件。


TrainOptimizedVARModel()

void TrainOptimizedVARModel()
  {
   OptimizationResult opt_result = OptimizeVARModel();

   Print("Lag óptimo encontrado: ", opt_result.optimal_lag);
   Print("AIC: ", opt_result.aic);
   Print("Variables seleccionadas: ", ArraySize(opt_result.selected_variables));

// Usar opt_result.optimal_lag y opt_result.selected_variables para entrenar el modelo final
   TrainVARModel(opt_result.optimal_lag, opt_result.selected_variables);
  }
优化并训练VAR模型,选择最优的滞后数量和重要变量。


PredictVARValue()

double PredictVARValue(int node_index)
{
   if(node_index < 0 || node_index >= g_node_count)
   {
      Print("Error: Invalid node index: ", node_index);
      return 0;
   }

   double prediction = 0;
   int lag = g_var_params[node_index].lag;

   Print("Predicting for node: ", g_network[node_index].name, ", Lag: ", lag);

   // Retrieve the previous prediction
   double previous_prediction = g_previous_predictions[node_index];

   // Verify if there are enough coefficients
   int expected_coefficients = g_node_count * lag + 1; // +1 for the intercept
   if(ArraySize(g_var_params[node_index].coefficients) < expected_coefficients)
   {
      Print("Error: Not enough coefficients for node ", node_index, ". Expected: ", expected_coefficients, ", Actual: ", ArraySize(g_var_params[node_index].coefficients));
      return 0;
   }

   prediction = g_var_params[node_index].coefficients[0]; // Intercept
   Print("Intercept: ", prediction);

   double sum_predictions = 0;
   double sum_weights = 0;
   double current_price = iClose(g_network[node_index].name, PERIOD_CURRENT, 0);

   for(int l = 1; l <= lag; l++)
   {
      double time_weight = 1.0 - (double)(l-1) / lag; // Time-based weighting
      sum_weights += time_weight;

      double lag_prediction = 0;
      for(int j = 0; j < g_node_count; j++)
      {
         int coeff_index = (l - 1) * g_node_count + j + 1;
         if(coeff_index >= ArraySize(g_var_params[node_index].coefficients))
         {
            Print("Warning: Coefficient index out of range. Skipping. Index: ", coeff_index, ", Array size: ", ArraySize(g_var_params[node_index].coefficients));
            continue;
         }

         double coeff = g_var_params[node_index].coefficients[coeff_index];
         double raw_value = iClose(g_network[j].name, PERIOD_CURRENT, l);

         if(raw_value == 0 || !MathIsValidNumber(raw_value))
         {
            Print("Warning: Invalid raw value for ", g_network[j].name, " at lag ", l);
            continue;
         }

         // Normalize the value as a percentage change
         double normalized_value = (raw_value - current_price) / current_price;

         double partial_prediction = coeff * normalized_value;
         lag_prediction += partial_prediction;

         Print("Lag ", l, ", Node ", j, ": Coeff = ", coeff, ", Raw Value = ", raw_value,
               ", Normalized Value = ", normalized_value, ", Partial prediction: ", partial_prediction);
      }

      sum_predictions += lag_prediction * time_weight;
      Print("Lag ", l, " prediction: ", lag_prediction, ", Weighted: ", lag_prediction * time_weight);
   }

   Print("Sum of weights: ", sum_weights);

   // Calculate the final prediction
   if(sum_weights > 1e-10)
   {
      prediction = sum_predictions / sum_weights;
   }
   else
   {
      Print("Warning: sum_weights is too small (", sum_weights, "). Using raw sum of predictions.");
      prediction = sum_predictions;
   }

   // Calculate the difference between the current prediction and the previous prediction
   double prediction_change = prediction - previous_prediction;

   Print("Previous prediction: ", previous_prediction);
   Print("Current prediction: ", prediction);
   Print("Prediction change: ", prediction_change);

   // Convert the prediction to a percentage change
   double predicted_change_percent = (prediction - current_price) / current_price * 100;

   Print("Final prediction (as percentage change): ", predicted_change_percent, "%");

   // Update the current prediction for the next iteration
   g_previous_predictions[node_index] = prediction;

   // Return the difference in basis points
   return prediction_change * 10000; // Multiply by 10000 to convert to basis points
}

使用训练好的VAR模型进行预测,考虑历史值和模型系数。


ManageOpenPositions() and ManageExistingOrders()

void ManageOpenPositions()
  {
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket))
        {
         string symbol = PositionGetString(POSITION_SYMBOL);
         double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
         double currentPrice = PositionGetDouble(POSITION_PRICE_CURRENT);
         double stopLoss = PositionGetDouble(POSITION_SL);
         ENUM_POSITION_TYPE positionType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);

         // Implement trailing stop
         if(positionType == POSITION_TYPE_BUY && currentPrice - openPrice > 2 * CustomATR(symbol, PERIOD_CURRENT, 14, 0))
           {
            double newStopLoss = NormalizeDouble(currentPrice - CustomATR(symbol, PERIOD_CURRENT, 14, 0), SymbolInfoInteger(symbol, SYMBOL_DIGITS));
            if(newStopLoss > stopLoss)
              {
               trade.PositionModify(ticket, newStopLoss, 0);
              }
           }
         else
            if(positionType == POSITION_TYPE_SELL && openPrice - currentPrice > 2 * CustomATR(symbol, PERIOD_CURRENT, 14, 0))
              {
               double newStopLoss = NormalizeDouble(currentPrice + CustomATR(symbol, PERIOD_CURRENT, 14, 0), SymbolInfoInteger(symbol, SYMBOL_DIGITS));
               if(newStopLoss < stopLoss || stopLoss == 0)
                 {
                  trade.PositionModify(ticket, newStopLoss, 0);
                 }
              }
        }
     }
  }


void ManageExistingOrders(string symbol)
  {
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket) && PositionGetString(POSITION_SYMBOL) == symbol)
        {
         double stopLoss = PositionGetDouble(POSITION_SL);
         double takeProfit = PositionGetDouble(POSITION_TP);
         double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
         double currentPrice = PositionGetDouble(POSITION_PRICE_CURRENT);
         ENUM_POSITION_TYPE positionType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);

         double atr = CustomATR(symbol, PERIOD_CURRENT, 14, 0);
         double newStopLoss, newTakeProfit;

         if(positionType == POSITION_TYPE_BUY)
           {
            newStopLoss = NormalizeDouble(currentPrice - 2 * atr, SymbolInfoInteger(symbol, SYMBOL_DIGITS));
            newTakeProfit = NormalizeDouble(currentPrice + 3 * atr, SymbolInfoInteger(symbol, SYMBOL_DIGITS));
            if(newStopLoss > stopLoss && currentPrice - openPrice > atr)
              {
               trade.PositionModify(ticket, newStopLoss, newTakeProfit);
              }
           }
         else
            if(positionType == POSITION_TYPE_SELL)
              {
               newStopLoss = NormalizeDouble(currentPrice + 2 * atr, SymbolInfoInteger(symbol, SYMBOL_DIGITS));
               newTakeProfit = NormalizeDouble(currentPrice - 3 * atr, SymbolInfoInteger(symbol, SYMBOL_DIGITS));
               if(newStopLoss < stopLoss && openPrice - currentPrice > atr)
                 {
                  trade.PositionModify(ticket, newStopLoss, newTakeProfit);
                 }
              }

         // Implementar cierre parcial
         double profit = PositionGetDouble(POSITION_PROFIT);
         double volume = PositionGetDouble(POSITION_VOLUME);
         if(profit > 0 && MathAbs(currentPrice - openPrice) > 2 * atr)
           {
            double closeVolume = volume * 0.5;
            if(IsValidVolume(symbol, closeVolume))
              {
               ClosePosition(ticket, closeVolume);
              }
           }
        }
     }
  }

管理开仓和现有的订单,实施诸如跟踪止损和部分平仓等策略。

此EA将传统的技术分析与先进的统计建模(VAR)和机器学习技术(因果发现)相结合,以做出交易决策。模块化结构允许轻松修改和改进各个组件。


结果

以下是一些交易品种的结果,对于所有研究的交易品种,设置和输入将保持不变:

设置

输入


EURUSD

EURUSD


GBPUSD

GBPUSD


AUDUSD

AUDUSD

USDJPY

USDJPY


USDCAD

USDCAD


USDCHF

USDCHF


如何获得最佳的结果?

如果在交易品种的节点中添加更多交易品种,并使用具有协整关系以及具有相关性的交易品种,可以获得更好的结果。要了解是哪些交易品种,您可以使用我在本文中提供的Python脚本:交易中纳什博弈论与HMM滤波的应用 - MQL5文章

此外,如果在策略中加入深度学习,也可以取得更好的结果。在类似这样的文章中有一些例子:情绪分析与深度学习在交易中的应用以及使用EA和Python进行回测 - MQL5文章。 

另外,过滤器尚未进行优化,您也可以尝试增加更多的过滤。

添加更多的交易品种到网络中时,图表的变化示例

void SetupNetwork()
  {
   string symbols[] = {"EURUSD", "GBPUSD", "USDCAD", "USDCHF", "USDJPY", "AUDUSD", "XAUUSD", "SP500m", "ND100m"};
   for(int i = 0; i < ArraySize(symbols); i++)
     {
      AddNode(symbols[i]);
     }
  }


EURUSD修改版

EURUSD修改版

仅仅通过在网络中添加更多交易品种,我们就成功地在以下方面取得了改进:

  1. 交易活跃度(总交易次数增加)
  2. 做空交易胜率
  3. 获利交易的百分比
  4. 最大获利交易的规模
  5. 每笔获利交易的平均利润
  6. 亏损交易的较低百分比
  7. 每笔亏损交易的较小平均亏损

这些改进表明整体交易管理和风险控制有所改善,尽管一些关键绩效指标(如盈利因子和恢复因子)较低。


结论

本文讨论了一个先进的交易系统,该系统整合了因果网络分析(Causality Network Analysis,CNA)和向量自回归(Vector Auto-Regression,VAR)来预测市场事件并做出交易决策。该系统使用复杂的统计和机器学习技术来建模金融工具之间的关系。尽管前景光明,但它强调了将这种方法与稳健的风险管理和对市场的深刻理解相结合的重要性。

对于寻求更好结果的交易者,文章建议扩展网络以包含更多相关交易品种,引入深度学习,并优化过滤方法。持续学习和适应对于交易的长期成功至关重要。

祝交易愉快,愿您的算法始终领先市场一步!


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

附加的文件 |
CNA_Final_v4.mq5 (179.13 KB)
掌握 MQL5:从入门到精通(第四部分):关于数组、函数和全局终端变量 掌握 MQL5:从入门到精通(第四部分):关于数组、函数和全局终端变量
本文是初学者系列文章的延续。它详细介绍了数据数组、数据和函数的交互,以及允许不同 MQL5 程序之间交换数据的全局终端变量。
交易中的神经网络:TEMPO 方法的实施结果 交易中的神经网络:TEMPO 方法的实施结果
我们继续领略 TEMPO 方法。在本文中,我们将评估所提议方法在真实历史数据上的真实有效性。
您应当知道的 MQL5 向导技术(第 30 部分):聚焦机器学习中的批量归一化 您应当知道的 MQL5 向导技术(第 30 部分):聚焦机器学习中的批量归一化
批量归一化是把数据投喂给机器学习算法(如神经网络)之前对数据进行预处理。始终要留意算法所用的激活类型,完成该操作。因此,我们探索在向导组装的智能系统帮助下,能够采取的不同方式,并从中受益。
重构经典策略(第七部分):基于USDJPY的外汇市场与主权债务分析 重构经典策略(第七部分):基于USDJPY的外汇市场与主权债务分析
在今天的文章中,我们将分析汇率走势与政府债券之间的关系。债券是固定收益证券中最受欢迎的形式之一,将成为我们讨论的重点。加入我们,一起探索是否可以利用人工智能技术改进一种经典策略。