因果网络分析(CNA)、随机模型最优控制(SMOC)和纳什博弈论结合深度学习的示例·综合运用
🧩

因果网络分析(CNA)、随机模型最优控制(SMOC)和纳什博弈论结合深度学习的示例·综合运用

(3/3)·把因果网络、随机最优控制与博弈均衡塞进同一个EA,看深度学习如何改写信号质量

案例拆解新手友好 第 3/3 篇
很多人把 CNA、SMOC 和纳什博弈当成互不相关的三个模块分别跑,结果信号互相打架。本篇演示怎么把它们和深度学习拼成一套协同系统,而不是各算各的。

◍ 用 ONNX 模型给收盘价打方向标签

这段逻辑把下一根 K 线的时间锚点和当前收盘价的极值跟踪先算好,再丢给 PredictPrice() 跑推理。ExtNextBar 用 TimeCurrent() 减去自身对 PeriodSeconds() 取模的余数,再加一个周期秒数,等于把时间戳对齐到整根 bar 的边界,避免半截 K 线触发预测。 PredictPrice() 里先建两个静态 vectorf:output_data(1) 接模型输出,x_norm(SAMPLE_SIZE) 装归一化收盘价。若 ExtMin>=ExtMax 说明样本里没有波动,直接把 ExtPredictedClass 置 -1 并返回,不浪费一次推理。 归一化做得直白:x_norm 减去 ExtMin 再除以 (ExtMax-ExtMin),把价格压到 0~1 区间。OnnxRun 跑完把 output_data[0] 反归一化回真实价 predicted,再用 last_close-predicted 的 delta 判方向——fabs(delta)<=0.00001 算 PRICE_SAME,delta<0 判 PRICE_UP,否则 PRICE_DOWN。 GenerateSignal 的买卖条件很硬:多单要 prediction>0.00001 且 rsi<30 且趋势强且波动合格且快线在慢线上,并叠加 dlsgnl==PRICE_UP;空单镜像对称。外汇与贵金属杠杆高,信号只是概率倾向,实盘前请在 MT5 用策略测试器按 SAMPLE_SIZE 跑一遍确认延迟。

MQL5 / C++
  ExtNextBar=TimeCurrent();
  ExtNextBar-=ExtNextBar%PeriodSeconds();
  ExtNextBar+=PeriodSeconds();
class=class="str">"cmt">//--- check min and max
  class="type">class="kw">float close=(class="type">class="kw">float)iClose(_Symbol,_Period,class="num">0);
  if(ExtMin>close)
      ExtMin=close;
  if(ExtMax<close)
      ExtMax=close;

  PredictPrice();
class="type">int signal = GenerateSignal(symbol, prediction);
class="type">int signal = GenerateSignal(symbol, prediction, ExtPredictedClass);
class="type">int GenerateSignal(class="type">class="kw">string symbol, class="type">class="kw">double prediction, class="type">int dlsgnl)
  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;
  class="type">bool buy_condition =  prediction >  class="num">0.00001 && rsi < class="num">30 && trend_strong  && volatility_ok && fastMA > slowMA && dlsgnl==PRICE_UP;
  class="type">bool sell_condition = prediction < -class="num">0.00001 && rsi > class="num">70 && trend_strong && volatility_ok && fastMA < slowMA && dlsgnl==PRICE_DOWN;
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void PredictPrice(class="type">void)
  {
   class="kw">static vectorf output_data(class="num">1);                    class=class="str">"cmt">// vector to get result
   class="kw">static vectorf x_norm(SAMPLE_SIZE);               class=class="str">"cmt">// vector for prices normalize
class=class="str">"cmt">//--- check for normalization possibility
   if(ExtMin>=ExtMax)
     {
      Print("ExtMin>=ExtMax");
      ExtPredictedClass=-class="num">1;
      class="kw">return;
     }
class=class="str">"cmt">//--- request last bars
   if(!x_norm.CopyRates(_Symbol,_Period,COPY_RATES_CLOSE,class="num">1,SAMPLE_SIZE))
     {
      Print("CopyRates ",x_norm.Size());
      ExtPredictedClass=-class="num">1;
      class="kw">return;
     }
   class="type">class="kw">float last_close=x_norm[SAMPLE_SIZE-class="num">1];
class=class="str">"cmt">//--- normalize prices
   x_norm-=ExtMin;
   x_norm/=(ExtMax-ExtMin);
class=class="str">"cmt">//--- run the inference
   if(!OnnxRun(ExtHandle,ONNX_NO_CONVERSION,x_norm,output_data))
     {
      Print("OnnxRun");
      ExtPredictedClass=-class="num">1;
      class="kw">return;
     }
class=class="str">"cmt">//--- denormalize the price from the output value
   class="type">class="kw">float predicted=output_data[class="num">0]*(ExtMax-ExtMin)+ExtMin;
class=class="str">"cmt">//--- classify predicted price movement
   class="type">class="kw">float delta=last_close-predicted;
   if(fabs(delta)<=class="num">0.00001)
      ExtPredictedClass=PRICE_SAME;
   else
     {
      if(delta<class="num">0)
         ExtPredictedClass=PRICE_UP;
      else
         ExtPredictedClass=PRICE_DOWN;
     }
  }




  class="type">void GetMinMax(class="type">void)
  {
   vectorf close;

日线收盘采样里的极值抓取

在 MT5 里做日线级别统计,先要把收盘价序列拉进数组再谈别的。上面这段把当前品种 D1 周期的收盘价按 SAMPLE_SIZE 条样本拷进 close 数组,紧跟着用 Min() 和 Max() 直接拿到这段样本的最低收和最高收。 这里有个容易被忽略的点:COPY_RATES_CLOSE 只取收盘价,不连带高低时间,所以 ExtMin、ExtMax 是收盘价的边界,不是日 K 实体的边界。若你想框定真实波幅,得换 COPY_RATES_OHLC 重新采。 实盘里把 SAMPLE_SIZE 设成 60,就能快速看近三个月日线收盘的上下沿;外汇和贵金属杠杆高、跳空频繁,这套边界只反映历史样本,下破或上破都只是概率事件,别当支撑阻力铁律。

MQL5 / C++
  close.CopyRates(_Symbol,PERIOD_D1,COPY_RATES_CLOSE,class="num">0,SAMPLE_SIZE);
  ExtMin=close.Min();
  ExtMax=close.Max();
}

「SMOC 改写时砍掉的两处初始化」

把上一版的 SMOC 逻辑搬过来时,输入参数里不用再给 dlsignal 赋初值,也别把 CTrade 对象塞进全局声明。这两样在纳什均衡脚本里由 OnTick 内部的状态变量接手,硬加只会增加编译警告。 OnTick 里先按日线边界刷一次 ExtMin / ExtMax,再用当前收盘价动态更新极值。SAMPLE_SIZE 固定为 120,意味着模型输入窗口是 120 根 K 线的归一化序列,跑 EURUSD_D1.onnx 时少一根都会让 PredictPrice() 吐出越界结果。 ManageOpenOrder() 现在靠 barsOpen 计数平仓:用 iBars 减 iBarShift 拿到持仓经历的根数,超过 maxBarsOpen 就触发退出。ExecuteTrade() 的入场闸门改成 decision!=0 且双向趋势过滤(IsTrendFavorable + IsLongTermTrendFavorable)都过,且 ExtPredictedClass 必须是 PRICE_UP 或 PRICE_DOWN——同价信号直接被拦在门外。 外汇与贵金属杠杆高,这套过滤只是降低噪音,不等于方向锁死,实盘前请在 MT5 策略测试器用 2020–2023 的 D1 数据跑一遍再上。

MQL5 / C++
class="macro">#resource "/Files/EURUSD_D1.onnx" as class="type">uchar ExtModel[]
class="macro">#define SAMPLE_SIZE class="num">120
class="type">long    ExtHandle=INVALID_HANDLE;
class="type">int     ExtPredictedClass=-class="num">1;
class="type">class="kw">datetime ExtNextBar=class="num">0;
class="type">class="kw">datetime ExtNextDay=class="num">0;
class="type">class="kw">float   ExtMin=class="num">0.0;
class="type">class="kw">float   ExtMax=class="num">0.0;
class=class="str">"cmt">//--- price movement prediction
class="macro">#define PRICE_UP   class="num">0
class="macro">#define PRICE_SAME class="num">1
class="macro">#define PRICE_DOWN class="num">2
class="type">void OnTick()
  {
class=class="str">"cmt">//--- check new day
   if(TimeCurrent()>=ExtNextDay)
     {
      GetMinMax();
      class=class="str">"cmt">//--- set next day time
      ExtNextDay=TimeCurrent();
      ExtNextDay-=ExtNextDay%PeriodSeconds(PERIOD_D1);
      ExtNextDay+=PeriodSeconds(PERIOD_D1);
     }
class=class="str">"cmt">//--- check new bar
   if(TimeCurrent()<ExtNextBar)
      class="kw">return;
class=class="str">"cmt">//--- set next bar time
   ExtNextBar=TimeCurrent();
   ExtNextBar-=ExtNextBar%PeriodSeconds();
   ExtNextBar+=PeriodSeconds();
class=class="str">"cmt">//--- check min and max
   class="type">class="kw">float close=(class="type">class="kw">float)iClose(_Symbol,_Period,class="num">0);
   if(ExtMin>close)
      ExtMin=close;
   if(ExtMax<close)
      ExtMax=close;
   PredictPrice();
   class="kw">static class="type">class="kw">datetime lastBarTime = class="num">0;
   class="type">class="kw">datetime currentBarTime = iTime(Symbol(), PERIOD_CURRENT, class="num">0);
class=class="str">"cmt">// Only process on new bar
   if(currentBarTime == lastBarTime)
      class="kw">return;
   lastBarTime = currentBarTime;
   class="type">class="kw">double currentPrice = SymbolInfoDouble(Symbol(), SYMBOL_LAST);
   class="type">int decision = OptimalControl(currentPrice);
   class="type">class="kw">string logMessage = StringFormat("New bar: Time=%s, Price=%f, Decision=%d",
                                                    TimeToString(currentBarTime), currentPrice, decision);
   LogMessage(logMessage);
class=class="str">"cmt">// Manage open order if exists
   if(orderTicket != class="num">0)
     {
      ManageOpenOrder(decision, ExtPredictedClass);
     }
   if(orderTicket == class="num">0 && decision != class="num">0 && IsTrendFavorable(decision) && IsLongTermTrendFavorable(decision) && (ExtPredictedClass==PRICE_DOWN || ExtPredictedClass==PRICE_UP))
     {
      ExecuteTrade(decision, ExtPredictedClass);
     }
  }
class="type">void ManageOpenOrder(class="type">int decision, class="type">int dlsignal)
  {
   class="type">int barsOpen = iBars(Symbol(), PERIOD_CURRENT) - iBarShift(Symbol(), PERIOD_CURRENT, orderOpenTime);
   LogMessage(StringFormat("Bars open: %d for order ticket: %d", barsOpen, orderTicket));
   if(barsOpen >= maxBarsOpen ||

◍ 平仓与开仓的执行分支

这段逻辑把持仓管理和新单触发拆成两条路:当已有多单且决策看空、日线信号也向下,或已有空单且决策看多、日线信号向上时,直接调用 CloseOrder 清掉当前票,并把 orderTicket 与 orderOpenTime 归零,避免后续误判持仓状态。 若不满足反向平仓条件,则走 UpdateSLTP 按最新 decision 移动止损止盈,等于把已有仓位的风控线动态跟着信号走。外汇与贵金属杠杆高,SL/TP 被拉太宽可能瞬间吞掉本金,调参时先在小周期回测。 ExecuteTrade 里先取 SYMBOL_ASK 或 SYMBOL_BID 作为市价,decision 与 dlsignal 同向才进场:1 配 PRICE_UP 做多,-1 配 PRICE_DOWN 做空。SL/TP 由 CalculateDynamicSL / CalculateDynamicTP 按价格与方向算出,volume 走 AdjustLotSizeForDrawdown 做回撤自适应,不是写死 lotSize。 request.deviation 设 10 点、magic=123456 便于在 MT5 订单池里筛出本 EA 的痕迹;OrderSend 成功后用 iTime 抓当前 K 线时间写入 orderOpenTime,失败则 Print 错误码。把这段代码贴进 MT5 的 EA 模板,改 magic 与 deviation 就能跑通基础闭环。

MQL5 / C++
   (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY && decision == -class="num">1 && dlsignal == PRICE_DOWN) ||
   (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && decision == class="num">1 && dlsignal == PRICE_UP))
   {
      CloseOrder(orderTicket);
      orderTicket = class="num">0;
      orderOpenTime = class="num">0;
   }
  else
   {
      UpdateSLTP(orderTicket, decision);
   }
}
class="type">void ExecuteTrade(class="type">int decision, class="type">int dlsignal)
  {
  class="type">MqlTradeRequest request = {};
  class="type">MqlTradeResult result = {};
  class="type">class="kw">double price;
  class="type">int decision1;


  if(decision == class="num">1 && dlsignal == PRICE_UP)
    {
      price = SymbolInfoDouble(Symbol(), SYMBOL_ASK);
      request.type = ORDER_TYPE_BUY;
      decision1 = class="num">1;
    }
  else
    if(decision == -class="num">1 && dlsignal == PRICE_DOWN)
      {
       price = SymbolInfoDouble(Symbol(), SYMBOL_BID);
       request.type = ORDER_TYPE_SELL;
       decision1 = -class="num">1;
      }
  class="type">class="kw">double sl = CalculateDynamicSL(price, decision1);
  class="type">class="kw">double tp = CalculateDynamicTP(price, decision1);
  class="type">class="kw">double adjustedLotSize = AdjustLotSizeForDrawdown();
  request.volume = adjustedLotSize;
  request.action = TRADE_ACTION_DEAL;
  request.symbol = Symbol();
class=class="str">"cmt">//request.volume = lotSize;
class=class="str">"cmt">//request.type = (decision == class="num">1 && dlsignal == PRICE_UP) ? ORDER_TYPE_BUY : ORDER_TYPE_SELL;
  request.price = price;
  request.sl = sl;
  request.tp = tp;
  request.deviation = class="num">10;
  request.magic = class="num">123456; class=class="str">"cmt">// Magic number to identify orders from this EA
  request.comment = (decision == class="num">1) ? "Buy Order" : "Sell Order";
  if(OrderSend(request, result))
    {
      Print((decision1 == class="num">1 ? "Buy" : "Sell"), " Order Executed Successfully. Ticket: ", result.order);
      orderOpenTime = iTime(Symbol(), PERIOD_CURRENT, class="num">0);
      orderTicket = result.order;
    }
  else
    {
      Print("Error executing Order: ", GetLastError());
    }
  }

把纳什均衡塞进 OnTick 的改法

这一版只动 OnTick() 一个函数,其余逻辑和先前保持一致。核心变化是:每根新_bar 上先做市场状态识别(HMM 与对数似然两套 regime),再让 CalculateStrategySignals 算出各策略信号,最后单独拎出 strategies[3] 这个纳什均衡策略做条件开仓。 代码前半段仍是日界与_bar界的时间戳刷新:TimeCurrent() 超过 ExtNextDay 就重抓高低点并推进到次日 0 点;小于 ExtNextBar 直接 return,避免重复计算。注意 ExtNextBar 用 PeriodSeconds() 无参调用,依赖当前图表周期。 中段把当前收盘价转 float 后持续刷新 ExtMin / ExtMax,随后调 PredictPrice() 得到 ExtPredictedClass(PRICE_UP / PRICE_DOWN)。若 IsNewBar() 不成立直接退出,保证信号只在_bar开盘处理一次。 真正的新增在尾部:DetectMarketRegime 填充 hmmRegime 与 logLikelihoodRegime,传入信号计算函数。当 strategies[3].enabled 且 signal 非零,并且预测方向与 signal 同向(买看 PRICE_UP、卖看 PRICE_DOWN)才触发 OpenBuyOrder / OpenSellOrder。最后若 UseTrailingStop 为真则更新跟踪止损。 外汇与贵金属杠杆高、滑点跳空频繁,这套条件单在实盘可能因点差扩大而错失触发,建议在 MT5 用 EURUSD 15 分钟图先跑复盘,把 strategies[3].enabled 从 false 改 true 看信号密度。

MQL5 / C++
class="type">void OnTick()
  {
class=class="str">"cmt">//--- check new day
   if(TimeCurrent()>=ExtNextDay)
     {
       GetMinMax();
       class=class="str">"cmt">//--- set next day time
       ExtNextDay=TimeCurrent();
       ExtNextDay-=ExtNextDay%PeriodSeconds(PERIOD_D1);
       ExtNextDay+=PeriodSeconds(PERIOD_D1);
     }
class=class="str">"cmt">//--- check new bar
   if(TimeCurrent()<ExtNextBar)
        class="kw">return;
class=class="str">"cmt">//--- set next bar time
   ExtNextBar=TimeCurrent();
   ExtNextBar-=ExtNextBar%PeriodSeconds();
   ExtNextBar+=PeriodSeconds();
class=class="str">"cmt">//--- check min and max
   class="type">class="kw">float close=(class="type">class="kw">float)iClose(_Symbol,_Period,class="num">0);
   if(ExtMin>close)
      ExtMin=close;
   if(ExtMax<close)
      ExtMax=close;
   PredictPrice();
   if(!IsNewBar())
      class="kw">return; class=class="str">"cmt">// Solo procesar en nueva barra
   MarketRegime hmmRegime = NOT_PRESENT;
   MarketRegime logLikelihoodRegime = NOT_PRESENT;
   DetectMarketRegime(hmmRegime, logLikelihoodRegime);
class=class="str">"cmt">// Calcular señales para cada estrategia
   CalculateStrategySignals(_Symbol, TimeCurrent(), hmmRegime, logLikelihoodRegime);
class=class="str">"cmt">// Verificar si la estrategia de Nash Equilibrium ha generado una señal
   if(strategies[class="num">3].enabled && strategies[class="num">3].signal != class="num">0)
     {
       if(strategies[class="num">3].signal > class="num">0 && ExtPredictedClass==PRICE_UP)
         {
          OpenBuyOrder(strategies[class="num">3].name);
         }
       else
         if(strategies[class="num">3].signal < class="num">0 && ExtPredictedClass==PRICE_DOWN)
           {
            OpenSellOrder(strategies[class="num">3].name);
           }
     }
class=class="str">"cmt">// Actualizar stops de seguimiento si es necesario
   if(UseTrailingStop)
     {
       UpdateTrailingStops();
     }
  }

「保守模型与裸因果网络的实盘反差」

同一套因果网络分析(CNA)框架,接不接深度学习层,跑出来的脾气完全两样。裸 CNA 的 EA 在回测里做了 1292 笔交易,带深度学习的只做了 58 笔——频率差出二十多倍,前者明显靠刷量吃行情,后者基本在等高置信信号。 裸模型的盈利能力数字更好看,但回撤也更大,属于典型的高换手换收益、敞口不收手。带深度学习的版本回撤压得更低,连续盈利或亏损的 streaks 更短,看起来更像在刻意控制暴露,而不是赌单边。 胜率上两者都没过 50%:深度学习版略高一点,裸版更低,说明 CNA 本身不是靠胜率吃饭,而是靠盈亏比和频率错位。裸版明显偏做空,深度学习版多空更平衡,这对贵金属和外汇直盘的反转窗口意义不一样。 外汇与贵金属杠杆高、跳空频繁,这种回撤差异不是纸面指标。开 MT5 把同一周期数据分别跑两版,重点看 58 笔那版是不是在你常做的时段根本不出手——若信号稀缺,实盘滑点会先把低频优势磨掉。

◍ SMOC_DL 与 SMOC 的回测硬指标对照

把同一品种上跑出的两份 SMOC 报告摊开看,差异直接落在数字里。策略 2(SMOC_DL)净利润 4.32,策略 1(SMOC)是 -7.33,一个转正一个吃亏。夏普比率更刺眼:策略 2 为 1.69,策略 1 是 -3.77,风险调整后回报谁优谁劣不用多说。 胜率上策略 2 干到 59.38%,比策略 1 的 54% 高出五点多个百分点;交易次数也更多,64 对 50,说明带深度学习的版本可能挖到了更密集的入场窗口。盈利因子一道分水岭:策略 2 是 1.13(大于 1 即净赚),策略 1 仅 0.74,亏钱概率占优。 两边回撤都压在账户 0.01%,风控动作算克制。方向偏好上二者都爱做空,但策略 2 多空分布更匀,不像策略 1 一边倒。单看这组数,SMOC_DL 是更稳的选择,换时间框架喂给模型也值得一试。 不挂深度学习的 NASH 版本另算一笔账:总盈利和风险调整回报反而更好,代价是最大回撤偏高,交易更频繁、多头处理更利索;DL 版则保守,回撤小但整体收益薄。这套对照只来自一个回测周期,外汇与贵金属属高风险品种,换时段和多行情重跑前,别把结论当必然。

收束

把深度学习模型塞进 CNA、SMOC 和纳什博弈这三套 EA 里,结果并不整齐:CNA 的 DL 版收益降了但回撤同步缩窄,交易一致性反而更好;SMOC 的 DL 版胜率和风险调整回报都压过原版;纳什博弈原版总盈利更高,DL 版只是更保守却没赚到更多。 这种分化说明 DL 不是万能插件,它能不能提效高度依赖底层策略和当时市场。回测只跑过特定周期与品种,换时间框架或行情就得重测,否则容易把局部结论当普适规律。 外汇与贵金属属高风险品种,任何策略回测优绩都不代表实盘必然复现,上 MT5 前先用作者给的 ONNX 与 mq5 文件做跨周期验证再谈参数微调。

交给小布盯盘看多模型信号
这些多模型协同诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到 CNA 与 SMOC 的实时分歧度,你只管判断要不要跟。

常见问题

相关系数只描述同步变动,快速因果推断试图在短时窗口内判定变量间的方向性影响,速度换深度,适合盘口级事件推演。
单边市中对手策略趋同,均衡点可能退化;此时模型倾向给出低置信信号,概率上更该降仓而非硬做。
不一定。DL 可能捕捉非线性扰动使控制轨迹更贴合,但也可能引入过拟合,需用 walk-forward 验证。
目前小布提供信号分歧与风险读数,不托管EA执行;把重复劳动交给小布,你专注决策即可。
VAR 对线性滞后敏感,CNA 能标出跳空前的因果链断裂,贵金属高波动下后者预警更早但误报略多。