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

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

(3/3)·从FCI算法到VAR预测函数,手把手把因果网络分析接进你的交易机器人

实战向 第 3/3 篇
很多交易者把相关性曲线当因果信号,一遇到政策突袭就满盘皆输。把PC或FCI跑出来的边当成确定性驱动,是实盘里最常见的自欺。本篇用具体函数拆解,告诉你网络里的“因”该怎么进VAR、又该怎么止步于概率。

◍ 把货币对织成因果网而非孤立看盘

这套EA把网络分析当作市场理解的主干:网络由节点构成,每个节点对应一个交易品种,通常是货币对。它不把品种当孤立K线,而是建模彼此间的依赖与因果链路。 底层用快速因果推断(FCI)算法去发现品种间的潜在因果关系,结果写进邻接矩阵——矩阵里直接标出谁在因果上连着谁。EA还会在图上找v结构、套方向规则,把因果箭头修得更准。 网络不是一次算死。它会随行情推移重算,让品种间关系跟着市场变。网络结构连同发现的因果关系,会作为向量自回归(VAR)模型的输入去出预测。 逻辑前提很直接:外汇与贵金属品种很少独立波动,关系网建得越贴近真实,预测与决策可能越靠谱。但这类方法依赖捕捉真动态的能力,且金属/外汇属高风险市场,网络分析失效时VAR输入也会偏。

「节点怎么装下品种和数值」

在 MT5 里把跨品种关系抽象成网络,第一步是定义最小单元:节点。每个节点只干两件事——用 name 记住自己是哪一个工具,用 value 挂上这个工具当下的关键数据,比如报价、波动率或者自定义因子。 下面的结构体就是最直接的落地方式。Node 里 string name 存标识("EURUSD"、"XAUUSD" 都行),double value 存浮点数值;g_network[] 是运行期动态增长的节点数组,g_node_count 记录已写入几个节点,初始化为 0。 外汇与贵金属杠杆高、跳空频繁,value 里若直接塞实时 Bid 要做越界与除零保护,否则复盘时数组下标乱掉很难查。

MQL5 / C++
class="kw">struct Node
{
    class="type">class="kw">string name;
    class="type">class="kw">double value;
};
Node g_network[];
class="type">int g_node_count = class="num">0;

把六个直盘塞进一张关联网

多币种联动分析的第一步,是在 MT5 里先把节点建起来。下面这套 SetupNetwork() 不干别的,就是把六个常见美元直盘变成网络里的六个顶点,方便后续算跨品种传导。 代码里写死了 symbols 数组:EURUSD、GBPUSD、USDCAD、USDCHF、USDJPY、AUDUSD,一共 6 个。跑一遍循环,每个品种名交给 AddNode() 挂到全局数组 g_network 上,节点计数 g_node_count 同步加一。 AddNode() 内部先 ArrayResize 把数组撑大一个位置,再把 name 存进去、value 初始化成 0。这个 0 只是占位,后面接行情刷新时才会被真实报价或派生指标覆盖。 在「小布盯盘」里你可以直接把这段抄进 EA 的初始化段,编译后从全局数组长度就能验证:g_node_count 应该等于 6,否则就是符号名拼写或 resize 逻辑出了问题。外汇与贵金属联动分析属高风险操作,节点权重设定不当会放大误判。

MQL5 / C++
class="type">void SetupNetwork()
{
   class="type">class="kw">string symbols[] = {"EURUSD", "GBPUSD", "USDCAD", "USDCHF", "USDJPY", "AUDUSD"};
   for(class="type">int i = class="num">0; i < ArraySize(symbols); i++)
   {
      AddNode(symbols[i]);
   }
}
class="type">void AddNode(class="type">class="kw">string name)
{
   ArrayResize(g_network, g_node_count + class="num">1);
   g_network[g_node_count].name = name;
   g_network[g_node_count].value = class="num">0; class=class="str">"cmt">// Initialize with class="kw">default value
   g_node_count++;
}

◍ 这套EA里网络到底在干什么

在涉及的EA架构中,网络并非装饰层,而是把多货币联动关系显式建模的核心结构。它先把不同货币对抽象成节点,边代表统计意义上的相互牵扯,让EA在评估某一个品种波动时,能顺带把关联品种的传导效应算进去。 网络结构的第二个落点是因果推断。EA把它当作快速因果推断(FCI)算法的骨架,试图从观测数据里区分「A导致B」和「A与B同受C驱动」这类混杂,而不是只停留在相关系数。 第三个用途是直接喂给预测模型。FCI跑出来的因果边和结构,会作为向量自回归(VAR)模型的输入,用滞后项的线性组合去推下一步的多品种联合走势。外汇与贵金属杠杆高、跳空频繁,这类联动模型只代表历史结构倾向,实盘须以小仓位验证。

「EA 如何把因果网络变成交易信号」

EA 对构建出的网络做四类分析,每一类都对应一段具体函数调用,而不是泛泛的图论描述。 因果发现由 FCIAlgorithm() 完成,它跑快速因果推断,从价格序列里挖出节点间潜在的因果方向;结果落进邻接矩阵,矩阵里某个条目非零就代表两节点间存在直接因果边。 OrientVStructures() 专门识别并定向 V 结构——这类「A→C←B」形态在因果图里很关键,能帮助区分真假相关。 最后图分析阶段,EA 假设有因果关联的工具会互相泄露预测信息,据此调整下单逻辑;外汇与贵金属杠杆高,这类关联只在特定波动机制下成立,实战须用小样本回测验证。

网络分析给EA带来的实际改变

把多品种塞进同一张关系网后,EA不再只盯单一图表。那些单独看某根货币对或贵金属都显不出来的复杂联动,在网里会浮出来,可能成为提前半步的领先信号。 EA按网络里定期重算的因果结构走,市场换节奏它跟着换,不会拿三个月前的权重硬套现在。 broader market context 进来后,预测从「这个品种怎么走」变成「这片品种在怎么推它」,决策依据更厚一层。 外汇与贵金属杠杆高、跳空频繁,网络分析只降低盲点,不消除爆仓风险,参数和重算周期都得在MT5里实跑校验。

◍ 这套EA把VAR和因果发现揉进了一个框架

这个EA不是单纯挂个指标就下单,它把向量自回归(VAR)拿来做价格预测,又接了快速因果推断(FCI)去厘清不同市场变量谁影响谁,而不是只看相关系数。 网络结构被用来同时装下多个金融工具,变量之间的关系以图的方式被表示和分析,这比逐对计算要更贴近真实市场的传导链。 技术过滤器叠了波动率、RSI和趋势强度三层,用来在模型信号出来之后再做一道筛。风险管理走的是自适应路线,仓位随条件变动而不是固定手数。 它用滑动窗口持续重新训练,配合交叉验证和模型优化,意味着你每跑一段时间,内部权重可能已悄悄换过一轮。外汇和贵金属杠杆高,这类复杂模型过拟合后实盘回撤可能放大,开MT5加载后建议先观察信号频率再决定跟单。

「EA生命周期里的六个关键钩子」

做MT5自动化交易,先把EA在终端里的运行节奏拆清楚。加载时跑一次初始化,每个tick触发决策,卸载时做清理——这三段是骨架,其余函数嵌在里头干活。 OnInit()只在图表加载或重载时执行一次。它先搭网络结构,跑FCI因果发现,再训练VAR模型,最后把预测数组清零。这一步决定了后面所有分析的底层假设,初始化失败后面全废。 OnTick()是真正的操作核心。代码里用iTime比对lastBarTime,只在新K线出现时才往下走,避免同根bar重复计算。随后滑动窗口更新模型,遍历网络里每个品种做预测和发信号,有信号就调ExecuteTrade。 CheckMarketConditions()虽没单独写成函数,但逻辑散在GenerateSignal()内:RSI(14)过滤、快慢EMA(14/24)比较、VolatilityFilter()和IsTrendStrong()把关。这些条件全过才可能出现非零信号。 ExecuteTrade()下单前会校验市场开放与权限,按风险管理算手数并设自适应止损止盈,失败重试并打日志。OnDeinit()在移除EA或关终端时释放指标句柄并出总结。外汇与贵金属杠杆高,EA任何钩子异常都可能放大亏损,建议先在策略测试器跑历史数据验证。

MQL5 / C++
class="type">int OnInit()
  {
class=class="str">"cmt">// Step class="num">1: Set up your network structure
  SetupNetwork();
class=class="str">"cmt">// Step class="num">2: Run the causal discovery algorithm(e.g., PC or FCI)
  FCIAlgorithm();
class=class="str">"cmt">// Step class="num">3: Train the optimized causal model
  TrainOptimizedVARModel();
  ArrayResize(g_previous_predictions, g_node_count);
  ArrayInitialize(g_previous_predictions, class="num">0);  class=class="str">"cmt">// Initialize with zeros
  class="kw">return(INIT_SUCCEEDED);
  }
class="type">void OnTick()
  {
  ENUM_TIMEFRAMES tf = ConvertTimeframe(InputTimeframe);
  class="kw">static class="type">class="kw">datetime lastBarTime = class="num">0;
  class="type">class="kw">datetime currentBarTime = iTime(Symbol(), tf, class="num">0);
  if(currentBarTime == lastBarTime)
    class="kw">return;
  lastBarTime = currentBarTime;
  Print("--- New bar on timeframe ", EnumToString(tf), " ---");
  UpdateModelSlidingWindow();
  for(class="type">int i = class="num">0; i < g_node_count; i++)
    {
      class="type">class="kw">string symbol = g_network[i].name;
      Print("Processing symbol: ", symbol);
      class="type">class="kw">double prediction = PredictVARValue(i);
      Print("Prediction for ", symbol, ": ", prediction);
      class="type">int signal = GenerateSignal(symbol, prediction);
      Print("Signal for ", symbol, ": ", signal);
      class=class="str">"cmt">// Imprimir más detalles sobre las condiciones
      Print("RSI: ", CustomRSI(symbol, ConvertTimeframe(InputTimeframe), class="num">14, PRICE_CLOSE, class="num">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, class="num">14, class="num">0, MODE_EMA, PRICE_CLOSE) > iMA(symbol, PERIOD_CURRENT, class="num">24, class="num">0, MODE_EMA, PRICE_CLOSE)));
      if(signal != class="num">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();

信号生成的判定链与首根K线跳过

EA 在每根 bar 收线后会先调用 ManageExistingOrders 处理持仓,再打印当前 symbol 的持仓数与挂单数,最后输出本轮无信号计数与失败开仓计数,方便在 MT5 Experts 日志里直接核对。 GenerateSignal 是核心判定函数:先用 FindNodeIndex 找品种节点,找不到直接返回 0;并设了 static bool first_prediction,首根预测强制返回 0,避免模型冷启动那一下乱发单。 函数内部把模型输出的 prediction 和实时 SYMBOL_BID 做差,换算成百分比 predicted_change,再拉 volatility_ok、RSI(14)、趋势强度、快慢 EMA(8/24)等过滤项。买条件要求 prediction>0.00001 且 rsi<30 且趋势强且波动过关且快线在慢线上;卖条件对称,prediction<-0.00001 且 rsi>70 且快线在慢线下。外汇与贵金属杠杆高,这些条件只是概率倾斜,不代表方向必现。 日志里把每个变量都 Print 出来,实盘前你该把这段 EA 挂模拟盘跑一晚,看 predicted_change 与 rsi 是否如预期共振,再决定要不要放开真仓。

MQL5 / C++
class="type">int GenerateSignal(class="type">class="kw">string symbol, class="type">class="kw">double prediction)
  {
   class="type">int node_index = FindNodeIndex(symbol);
   if(node_index == -class="num">1)
     class="kw">return class="num">0;
   class="kw">static class="type">bool first_prediction = true;
   if(first_prediction)
     {
      first_prediction = false;
      class="kw">return class="num">0; class=class="str">"cmt">// No generar señal en la primera predicción
     }
   class="type">class="kw">double current_price = SymbolInfoDouble(symbol, SYMBOL_BID);
class=class="str">"cmt">// Calculate predicted change as a percentage
   class="type">class="kw">double predicted_change = (prediction - current_price) / current_price * class="num">100;
   class="type">bool volatility_ok = VolatilityFilter(symbol, ConvertTimeframe(InputTimeframe));
   class="type">class="kw">double rsi = CustomRSI(symbol, ConvertTimeframe(InputTimeframe), class="num">14, PRICE_CLOSE, class="num">0);
   class="type">bool trend_strong = IsTrendStrong(symbol, ConvertTimeframe(InputTimeframe));
   class="type">int trend = DetermineTrend(symbol, ConvertTimeframe(InputTimeframe));
   class="type">class="kw">double fastMA = iMA(symbol, PERIOD_CURRENT, class="num">8, class="num">0, MODE_EMA, PRICE_CLOSE);
   class="type">class="kw">double slowMA = iMA(symbol, PERIOD_CURRENT, class="num">24, class="num">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);
   class="type">bool buy_condition =  prediction >  class="num">0.00001 && rsi < class="num">30 && trend_strong  && volatility_ok && fastMA > slowMA;
   class="type">bool sell_condition = prediction < -class="num">0.00001 && rsi > class="num">70 && trend_strong && volatility_ok && fastMA < slowMA;
   Print("Buy condition met: ", buy_condition);
   Print("Sell condition met: ", sell_condition);
class=class="str">"cmt">// Buy conditions
   if(buy_condition)
     {
      Print("Buy signal generated for ", symbol);
      class="type">int signal = class="num">1;
      class="kw">return signal;  class=class="str">"cmt">// Buy signal
     }
class=class="str">"cmt">// Sell conditions
   else
     if(sell_condition)
       {

◍ 信号落地前的下单与重试逻辑

信号函数返回 -1 代表做空、0 代表无信号、1 代表做多,真正把信号变成仓位的动作在 ExecuteTrade 里完成。它先卡两道闸门:市场是否开放、账户是否允许交易,任一不满足直接 Print 后 return,不碰下单接口。 价格取数跟着信号走——买用 SYMBOL_ASK、卖用 SYMBOL_BID,止损止盈和仓位都由前面算好的自适应函数给出,若动态仓位算出来 ≤0 也直接退出,避免脏单。 trade 对象设了魔术码 123456 与 ORDER_FILLING_FOK 填充方式,随后进入最多 3 次的发单循环。遇到 REQUOTE、PRICE_CHANGED、INVALID_PRICE 三类错误会刷新当前买卖价再试,其余错误打印后继续计数,成功就 break 并留执行日志。 外汇与贵金属杠杆高、滑点突变频繁,这种带重试的机制只能降低瞬时报价失效的概率,不保证成交,实盘前务必在 MT5 策略测试器用历史 tick 跑一遍确认错误分支覆盖。

MQL5 / C++
class="type">void ExecuteTrade(class="type">class="kw">string symbol, class="type">int signal)
  {
  if(!IsMarketOpen(symbol) || !IsTradingAllowed())
    {
      Print("Market is closed or trading is not allowed for ", symbol);
      class="kw">return;
    }
  class="type">class="kw">double price = (signal == class="num">1) ? SymbolInfoDouble(symbol, SYMBOL_ASK) : SymbolInfoDouble(symbol, SYMBOL_BID);
  class="type">class="kw">double stopLoss, takeProfit;
  CalculateAdaptiveLevels(symbol, signal, stopLoss, takeProfit);
  class="type">class="kw">double lotSize = CalculateDynamicLotSize(symbol, stopLoss);
  if(lotSize <= class="num">0)
    {
      Print("Invalid lot size for symbol: ", symbol);
      class="kw">return;
    }
  trade.SetExpertMagicNumber(class="num">123456);
  trade.SetTypeFilling(ORDER_FILLING_FOK);
  class="type">bool result = false;
  class="type">int attempts = class="num">3;
  for(class="type">int i = class="num">0; i < attempts; i++)
    {
      if(signal == class="num">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 == -class="num">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 == class="num">1 ? "Buy" : "Sell"), ", Lot size: ", lotSize);
          class="kw">break;
        }
      else
        {
          class="type">int lastError = GetLastError();
          Print("Attempt ", i+class="num">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 == class="num">1) ? SymbolInfoDouble(symbol, SYMBOL_ASK) : SymbolInfoDouble(symbol, SYMBOL_BID);

「退出时释放指标句柄与收尾汇总」

EA 在 OnDeinit 里第一件事是释放三个指标句柄:atr_handle、rsi_handle、momentum_handle。每个都先判断不等于 INVALID_HANDLE,再调用 IndicatorRelease,避免 MT5 终端退出或重载时残留句柄占用内存。 最后一行 GenerateFinalSummary() 负责在 EA 卸载前输出整段运行期的统计摘要,方便你回看信号触发次数与失败原因。外汇与贵金属品种波动剧烈,这类自动化统计仅作复盘参考,实盘仍属高风险。 把这段逻辑直接拷进你的 EA 尾部,能在策略测试器反复加载/移除时减少『旧指标未释放』类的报错概率,具体表现可在 MT5 Experts 日志里验证。

MQL5 / C++
class="type">void OnDeinit(const class="type">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里的模型刷新与订单托管函数

这套 EA 把传统技术面和 VAR 统计建模、因果发现揉在一起出信号,核心靠几个模块函数保持模型活着、仓位可控。UpdateModelSlidingWindow() 用滑动窗口重训 VAR,让模型跟得上行情切换;TrainOptimizedVARModel() 负责挑最优滞后阶数和变量;PredictVARValue() 按训练好的系数出预测值;ManageOpenPositions() 与 ManageExistingOrders() 管跟踪止损和部分平仓。 滑动窗口那段逻辑很直白:用静态计数器 bars_since_update 累加,达到 g_update_frequency 才采新数据、重训、算验证分并打印。想看模型衰没衰,直接盯日志里的 Validation score,外汇和贵金属波动大,分数掉得快时说明旧样本已失真,高风险品种更该调小 g_update_frequency。 TrainOptimizedVARModel() 会打出最优滞后、AIC 和入选变量数,这三行是调参锚点。AIC 越低模型拟合越好,但别盲目追低,样本外可能过拟合。PredictVARValue() 里对系数数量做了防御:expected_coefficients = g_node_count * lag + 1,少一个就报错返回 0,复制代码时若改了节点数忘了同步系数数组,预测会直接废。

MQL5 / C++
class="type">void UpdateModelSlidingWindow()
  {
  class="kw">static class="type">int bars_since_update = class="num">0;
  ENUM_TIMEFRAMES tf = ConvertTimeframe(InputTimeframe);
class=class="str">"cmt">// Check if it&class="macro">#x27;s time to update
  if(bars_since_update >= g_update_frequency)
    {
      class=class="str">"cmt">// Collect new data
      class="type">class="kw">double training_data[];
      CollectTrainingData(training_data, g_window_size, tf);
      class=class="str">"cmt">// Retrain model
      TrainModel(training_data);
      class=class="str">"cmt">// Validate model
      class="type">class="kw">double validation_score = ValidateModel();
      Print("Model updated. Validation score: ", validation_score);
      bars_since_update = class="num">0;
    }
  else
    {
      bars_since_update++;
    }
  }
class="type">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));
class=class="str">"cmt">// Usar opt_result.optimal_lag y opt_result.selected_variables para entrenar el modelo final
  TrainVARModel(opt_result.optimal_lag, opt_result.selected_variables);
  }
class="type">class="kw">double PredictVARValue(class="type">int node_index)
{
  if(node_index < class="num">0 || node_index >= g_node_count)
  {
    Print("Error: Invalid node index: ", node_index);
    class="kw">return class="num">0;
  }
  class="type">class="kw">double prediction = class="num">0;
  class="type">int lag = g_var_params[node_index].lag;
  Print("Predicting for node: ", g_network[node_index].name, ", Lag: ", lag);
  class=class="str">"cmt">// Retrieve the previous prediction
  class="type">class="kw">double previous_prediction = g_previous_predictions[node_index];
  class=class="str">"cmt">// Verify if there are enough coefficients
  class="type">int expected_coefficients = g_node_count * lag + class="num">1; class=class="str">"cmt">// +class="num">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));
    class="kw">return class="num">0;
  }
  prediction = g_var_params[node_index].coefficients[class="num">0]; class=class="str">"cmt">// Intercept
  Print("Intercept: ", prediction);
  class="type">class="kw">double sum_predictions = class="num">0;
  class="type">class="kw">double sum_weights = class="num">0;
  class="type">class="kw">double current_price = iClose(g_network[node_index].name, PERIOD_CURRENT, class="num">0);
  for(class="type">int l = class="num">1; l <= lag; l++)
  {
    class="type">class="kw">double time_weight = class="num">1.0 - (class="type">class="kw">double)(l-class="num">1) / lag; class=class="str">"cmt">// Time-based weighting
    sum_weights += time_weight;
    class="type">class="kw">double lag_prediction = class="num">0;
    for(class="type">int j = class="num">0; j < g_node_count; j++)
    {

◍ 网络预测落地的边界与基点换算

这段逻辑把多层网络的滞后项累加预测收口,并直接转成基点差返回。先算 coeff_index = (l-1)*g_node_count + j + 1,一旦超出 coefficients 数组尺寸就 Print 警告并 continue,避免越界读脏数据。 原始价用 iClose(g_network[j].name, PERIOD_CURRENT, l) 取,若为 0 或 MathIsValidNumber 不通过同样跳过;随后 normalized_value = (raw_value - current_price) / current_price,即把价格转成相对当前价的百分比偏移,再乘系数得 partial_prediction 累进 lag_prediction。 全部滞后项加权求和后,若 sum_weights > 1e-10 则 prediction = sum_predictions / sum_weights,否则退化为原始累加并打警告。prediction_change = prediction - previous_prediction 算出预测差分,最终 predicted_change_percent 乘 100 输出百分比,函数返回 prediction_change * 10000 即基点差。 ManageOpenPositions 从 PositionsTotal()-1 倒序遍历持仓,用 PositionGetTicket(i) 拿ticket——倒序是因为平仓后索引会前移,正序遍历容易漏单。外汇与贵金属杠杆高,这类预测差分只作辅助信号,实盘前请在 MT5 策略测试器用真实点差重跑验证。

MQL5 / C++
class="type">int coeff_index = (l - class="num">1) * g_node_count + j + class="num">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));
   class="kw">continue;
}
class="type">class="kw">double coeff = g_var_params[node_index].coefficients[coeff_index];
class="type">class="kw">double raw_value = iClose(g_network[j].name, PERIOD_CURRENT, l);
if(raw_value == class="num">0 || !MathIsValidNumber(raw_value))
{
   Print("Warning: Invalid raw value for ", g_network[j].name, " at lag ", l);
   class="kw">continue;
}
class=class="str">"cmt">// Normalize the value as a percentage change
class="type">class="kw">double normalized_value = (raw_value - current_price) / current_price;
class="type">class="kw">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);
class=class="str">"cmt">// Calculate the final prediction
if(sum_weights > class="num">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;
}
class=class="str">"cmt">// Calculate the difference between the current prediction and the previous prediction
class="type">class="kw">double prediction_change = prediction - previous_prediction;
Print("Previous prediction: ", previous_prediction);
Print("Current prediction: ", prediction);
Print("Prediction change: ", prediction_change);
class=class="str">"cmt">// Convert the prediction to a percentage change
class="type">class="kw">double predicted_change_percent = (prediction - current_price) / current_price * class="num">100;
Print("Final prediction(as percentage change): ", predicted_change_percent, "%");
class=class="str">"cmt">// Update the current prediction for the next iteration
g_previous_predictions[node_index] = prediction;
class=class="str">"cmt">// Return the difference in basis points
class="kw">return prediction_change * class="num">10000; class=class="str">"cmt">// Multiply by class="num">10000 to convert to basis points
}
class="type">void ManageOpenPositions()
{
   for(class="type">int i = PositionsTotal() - class="num">1; i >= class="num">0; i--)
   {
      class="type">ulong ticket = PositionGetTicket(i);

「用 ATR 倍数触发移动止损的单子管理」

这段逻辑只处理已开仓位的 trailing stop,不碰挂单。先用 PositionSelectByTicket 按单号锁定持仓,再读出品种、开仓价、当前价、SL 和仓位方向,后面才决定要不要推止损。 触发门槛写死为盈利超过 2 倍 CustomATR(symbol, PERIOD_CURRENT, 14, 0)。也就是说,14 周期 ATR 是当前图表周期计算的,盈利没到 2 个 ATR 之前,止损纹丝不动,避免了震荡市里被毛刺扫掉。 多头那边,新止损 = 当前价 - 1 倍 ATR,并经 SymbolInfoInteger 取小数位 NormalizeDouble;仅当算出来比原 SL 大才调用 trade.PositionModify 改写,保证止损只上移不下移。空头对称处理,新止损 = 当前价 + 1 倍 ATR,条件改成 newStopLoss < stopLoss 或原 SL 为 0 才改,防止初始没设 SL 的仓位永远裸奔。 ManageExistingOrders 则是外层循环,从 PositionsTotal()-1 倒序遍历,用 PositionGetTicket 取单号并筛选同品种。外汇和贵金属杠杆高、跳空频繁,这种 ATR 移动止损能压回撤,但参数 2 和 14 都只是参考值,实盘前请在 MT5 策略测试器里按品种波动重调。

MQL5 / C++
if(PositionSelectByTicket(ticket))
  {
   class="type">class="kw">string symbol = PositionGetString(POSITION_SYMBOL);
   class="type">class="kw">double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
   class="type">class="kw">double currentPrice = PositionGetDouble(POSITION_PRICE_CURRENT);
   class="type">class="kw">double stopLoss = PositionGetDouble(POSITION_SL);
   class="type">ENUM_POSITION_TYPE positionType = (class="type">ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
   class=class="str">"cmt">// Implement trailing stop
   if(positionType == POSITION_TYPE_BUY && currentPrice - openPrice > class="num">2 * CustomATR(symbol, PERIOD_CURRENT, class="num">14, class="num">0))
     {
      class="type">class="kw">double newStopLoss = NormalizeDouble(currentPrice - CustomATR(symbol, PERIOD_CURRENT, class="num">14, class="num">0), SymbolInfoInteger(symbol, SYMBOL_DIGITS));
      if(newStopLoss > stopLoss)
        {
         trade.PositionModify(ticket, newStopLoss, class="num">0);
        }
     }
   else
     if(positionType == POSITION_TYPE_SELL && openPrice - currentPrice > class="num">2 * CustomATR(symbol, PERIOD_CURRENT, class="num">14, class="num">0))
       {
        class="type">class="kw">double newStopLoss = NormalizeDouble(currentPrice + CustomATR(symbol, PERIOD_CURRENT, class="num">14, class="num">0), SymbolInfoInteger(symbol, SYMBOL_DIGITS));
        if(newStopLoss < stopLoss || stopLoss == class="num">0)
          {
           trade.PositionModify(ticket, newStopLoss, class="num">0);
          }
       }
  }
}
class="type">void ManageExistingOrders(class="type">class="kw">string symbol)
  {
   for(class="type">int i = PositionsTotal() - class="num">1; i >= class="num">0; i--)
     {
      class="type">ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket) && PositionGetString(POSITION_SYMBOL) == symbol)
        {
         class="type">class="kw">double stopLoss = PositionGetDouble(POSITION_SL);
         class="type">class="kw">double takeProfit = PositionGetDouble(POSITION_TP);
         class="type">class="kw">double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
         class="type">class="kw">double currentPrice = PositionGetDouble(POSITION_PRICE_CURRENT);

用 ATR 拖止损并做半仓止盈

下面这段逻辑把 ATR 动态止损和价格脱离成本区后的半仓平仓揉进同一个持仓管理循环,适合在 MT5 EA 的 OnTradeTransaction 或定时扫描里直接抄。 先取持仓方向与当前 ATR(14):买仓把止损压到现价减 2 倍 ATR、止盈放到现价加 3 倍 ATR;卖仓反向对称。但修改触发有门槛——买仓需新止损高于原止损且(现价-开仓价)>1 倍 ATR,卖仓同理,避免逆势硬拉止损。

半仓逻辑另算一笔:当浮利为正且现价-开仓价> 2 倍 ATR,平掉 50% 仓位,且必须经过 IsValidVolume 校验最小交易量。外汇与贵金属杠杆高,ATR 倍数止损不代表回撤可控,实盘前请用策略测试器按品种跑一遍。

让小布替你跑这套 把上面代码塞进 EA 后,建议先对 XAUUSD 用默认 14 周期 ATR 回测 2023 全年,观察 2ATR 止损被触发的概率是否高于你当前固定点数止损,再决定搬去 live。

MQL5 / C++
   class="type">ENUM_POSITION_TYPE positionType = (class="type">ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
   class="type">class="kw">double atr = CustomATR(symbol, PERIOD_CURRENT, class="num">14, class="num">0);
   class="type">class="kw">double newStopLoss, newTakeProfit;
   if(positionType == POSITION_TYPE_BUY)
     {
      newStopLoss = NormalizeDouble(currentPrice - class="num">2 * atr, SymbolInfoInteger(symbol, SYMBOL_DIGITS));
      newTakeProfit = NormalizeDouble(currentPrice + class="num">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 + class="num">2 * atr, SymbolInfoInteger(symbol, SYMBOL_DIGITS));
        newTakeProfit = NormalizeDouble(currentPrice - class="num">3 * atr, SymbolInfoInteger(symbol, SYMBOL_DIGITS));
        if(newStopLoss < stopLoss && openPrice - currentPrice > atr)
          {
           trade.PositionModify(ticket, newStopLoss, newTakeProfit);
          }
       }
   class=class="str">"cmt">// Implementar cierre parcial
   class="type">class="kw">double profit = PositionGetDouble(POSITION_PROFIT);
   class="type">class="kw">double volume = PositionGetDouble(POSITION_VOLUME);
   if(profit > class="num">0 && MathAbs(currentPrice - openPrice) > class="num">2 * atr)
     {
      class="type">class="kw">double closeVolume = volume * class="num">0.5;
      if(IsValidVolume(symbol, closeVolume))
        {
         ClosePosition(ticket, closeVolume);
        }
     }
   }
  }
 }

◍ 往网络里多塞几个品种会怎样

把具有协整关系或相关性的更多交易品种接入节点网络,系统的表现往往会出现结构性变化。原文实测中,仅做这一项改动,就观察到总交易次数上升、做空胜率提高、获利交易占比扩大、最大单笔获利变大、平均盈利抬升,同时亏损交易占比与平均亏损均下降。外汇与贵金属属高杠杆高波动市场,这类改善不代表未来收益,只是概率层面的管理优化。 值得注意的是,盈利因子和恢复因子反而低于此前,说明网络扩张改善了风险分布,却未直接抬高综合效率。若你想在 MT5 里复现,可先用手头 EA 的建网函数做扩展,别急着动过滤参数。 过滤器目前没有经过优化,留了空间。自己加一层波动率或时段过滤,可能进一步压低亏损比例,但要用历史数据跑一遍确认。原文给出的建网片段可以直接抄去改: 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 到 ND100m 共 9 个,循环调用 AddNode 把每个品种挂进网络。你换掉数组里的字符串,就能验证不同组合下的交易活跃度差异。

MQL5 / C++
class="type">void SetupNetwork()
  {
   class="type">class="kw">string symbols[] = {"EURUSD", "GBPUSD", "USDCAD", "USDCHF", "USDJPY", "AUDUSD", "XAUUSD", "SP500m", "ND100m"};
   for(class="type">int i = class="num">0; i < ArraySize(symbols); i++)
     {
      AddNode(symbols[i]);
     }
  }

「把工具请下神坛」

前面那套把因果网络分析和向量自回归揉在一起的系统,本质是用统计与机器学习给品种间关系建模,用来预判事件并下注。它附带的 CNA_Final_v4.mq5 有 179.13 KB,能直接丢进 MT5 跑,但别指望它替你扛风险。 想再榨点性能,原作者提过三条路:把网络扩到更多关联品种、接深度学习、调过滤条件。外汇和贵金属杠杆高、跳空狠,光靠模型不盯盘,爆仓只是时间问题。 长期活下来的交易者,都是把这类算法当探针而非神谕——持续改参数、持续看输出偏离。开 MT5 加载那个 mq5,先跑 EURUSD 和 XAUUSD 的关联,比读十篇软文有用。

让小布替你扫因果边
这些FCI边筛选与VAR滚动训练的小布盯盘AIGC已内置,打开对应品种页即可看到节点强度和预测置信带,你只管判冲突。

常见问题

FCI能处理隐变量与选择偏差,对跨品种延迟传导更鲁棒;PC假设无隐变量,在真实盘口噪声下易多连伪边,实盘倾向用FCI先减边再进VAR。
权重应来自滚动窗口的偏相关而非全样本相关,避免旧 regime 的边压制新结构;初始化为零边、靠FCIAlgorithm动态加边更稳。
阶数过大会吃掉脉冲响应里的因果实体,预测倾向回归噪声均值;可用BIC在滚动窗上掐阶,贵金属等高波动品种宜偏低阶。
可以,品种页的AIGC模块支持按本文函数逻辑生成近20根K的因果边与VAR预测区间,外汇贵金属属高风险,仅作概率参考。
情绪评分常是滞后显变量,单独成节点可隔离其作为被驱动端,防止把舆情波动误判为价格因;但需先测其与宏观节点的混淆。