MQL5交易策略自动化(第二十一部分):借助自适应学习率提升神经网络交易效果·综合运用
📘

MQL5交易策略自动化(第二十一部分):借助自适应学习率提升神经网络交易效果·综合运用

第 3/3 篇

训练循环里的误差与命中率记账

这段逻辑跑在神经网络训练的主循环里,每个 epoch 先清零 totalError 与 correctPredictions,再逐条样本前向传播、算误差、反向传播。误差用输出层与目标值之差的平方和累加,命中率则看二元输出中较大神经元的方向是否与目标一致。 样本目标维度若和 outputNeuronCount 对不上,直接 Print 报错并 continue 跳过,避免脏数据污染权重。每条样本处理完,totalError 与 correctPredictions 同步更新,循环结束用 ArraySize(data) 归一得到 accuracy 与 trainingError。 历史记录存进 accuracyHistory 与 errorHistory,未满 MAX_HISTORY_SIZE 就顺序追加,满了则整体左移丢弃最旧一条再压入新值。最后 Print 当前误差并调用 AdjustLearningRate,学习率可能随训练误差动态走低。 开 MT5 把这段塞进 EA 的 Train 函数,盯一眼 Experts 日志里 'Error history updated' 后的数值,能直接判断前几个 epoch 误差是否按预期收敛。外汇与贵金属行情噪声大,这类模型过拟合概率不低,验证时务必留独立样本。

MQL5 / C++
for(class="type">int epoch = class="num">0; epoch < maxEpochs; epoch++) {
      class="type">class="kw">double totalError = class="num">0; class=class="str">"cmt">//--- Total error for epoch
      class="type">int correctPredictions = class="num">0; class=class="str">"cmt">//--- Count of correct predictions
      class=class="str">"cmt">//--- Process each training sample
      for(class="type">int i = class="num">0; i < ArraySize(data); i++) {
         class=class="str">"cmt">//--- Check target array size
         if(ArraySize(data[i].targetValues) != outputNeuronCount) {
            Print("Error: Mismatch in targets size for training data at index ", i);
            class="kw">continue;
         }
         class=class="str">"cmt">//--- Set input values
         SetInput(data[i].inputValues);
         class=class="str">"cmt">//--- Perform forward propagation
         ForwardPropagate();
         class="type">class="kw">double error = class="num">0;
         class=class="str">"cmt">//--- Calculate error
         for(class="type">int j = class="num">0; j < outputNeuronCount; j++)
            error += MathPow(data[i].targetValues[j] - outputLayer[j], class="num">2);
         totalError += error;
         class=class="str">"cmt">//--- Check prediction correctness
         if((outputLayer[class="num">0] > outputLayer[class="num">1] && data[i].targetValues[class="num">0] > data[i].targetValues[class="num">1]) ||
             (outputLayer[class="num">0] < outputLayer[class="num">1] && data[i].targetValues[class="num">0] < data[i].targetValues[class="num">1]))
            correctPredictions++;
         class=class="str">"cmt">//--- Perform backpropagation
         Backpropagate(data[i].targetValues);
         }
      class=class="str">"cmt">//--- Calculate accuracy
      accuracy = (class="type">class="kw">double)correctPredictions / ArraySize(data);
      class=class="str">"cmt">//--- Update training error
      trainingError = totalError / ArraySize(data);
      class=class="str">"cmt">//--- Update history
      if(historyRecordCount < MAX_HISTORY_SIZE) {
         accuracyHistory[historyRecordCount] = accuracy;
         errorHistory[historyRecordCount] = trainingError;
         historyRecordCount++;
      } else {
         class=class="str">"cmt">//--- Shift history arrays
         for(class="type">int i = class="num">1; i < MAX_HISTORY_SIZE; i++) {
            accuracyHistory[i - class="num">1] = accuracyHistory[i];
            errorHistory[i - class="num">1] = errorHistory[i];
         }
         class=class="str">"cmt">//--- Add new values
         accuracyHistory[MAX_HISTORY_SIZE - class="num">1] = accuracy;
         errorHistory[MAX_HISTORY_SIZE - class="num">1] = trainingError;
      }
      class=class="str">"cmt">//--- Log error history update
      Print("Error history updated: ", errorHistory[historyRecordCount - class="num">1]);
      class=class="str">"cmt">//--- Adjust learning rate
      AdjustLearningRate();

◍ 用近期K线喂神经网络做增量更新

神经网络类里有一段 UpdateNetworkWithRecentData,只取最近 10 根 bar 做再训练,而不是全量重跑。对贵金属和外汇这种高频跳动的品种,这种局部刷新能压住 MT5 主线程占用,避免 EA 在 live 环境卡顿。 ShouldRetrain 的触发逻辑很直接:若最新准确率低于 MinPredictionAccuracy,或最新误差超过上一期误差的 1.5 倍,就标记需要重训。注意这里用了 historyRecordCount < 2 的守卫,记录不足两条时直接返回 false,防止除零和噪声误判。 PrepareInputs 负责把行情特征塞进输入向量:先按 INPUT_NEURON_COUNT 重整数组,再把 MA20、MA50、RSI、ATR 四个序列用 ArraySetAsSeries 设为时间倒序,随后 CopyBuffer 取数。开 MT5 把这段接上你的指标句柄,就能让小布盯盘类的 AIGC 模块拿到标准化输入。外汇与贵金属波动剧烈,这类模型输出仅作概率参考,实盘须自担高风险。

MQL5 / C++
  class=class="str">"cmt">//--- Log progress every class="num">10 epochs
  if(epoch % class="num">10 == class="num">0)
     Print("Epoch ", epoch, ": Error = ", trainingError, ", Accuracy = ", accuracy);
  class=class="str">"cmt">//--- Check for early stopping
  if(trainingError < targetError && accuracy >= MinPredictionAccuracy)
     break;
  }
  class=class="str">"cmt">//--- Return final accuracy
  class="kw">return accuracy;
}
class=class="str">"cmt">// Update network with recent data
class="type">void CNeuralNetwork::UpdateNetworkWithRecentData() {
  const class="type">int recentBarCount = class="num">10; class=class="str">"cmt">//--- Number of recent bars to process
  TrainingData recentData[];
  class=class="str">"cmt">//--- Collect recent training data
  if(!CollectTrainingData(recentData, recentBarCount))
    class="kw">return;
  class=class="str">"cmt">//--- Process each recent data sample
  for(class="type">int i = class="num">0; i < ArraySize(recentData); i++) {
    class=class="str">"cmt">//--- Set input values
    SetInput(recentData[i].inputValues);
    class=class="str">"cmt">//--- Perform forward propagation
    ForwardPropagate();
    class=class="str">"cmt">//--- Perform backpropagation
    Backpropagate(recentData[i].targetValues);
  }
}
class=class="str">"cmt">// Get recent accuracy
class="type">class="kw">double CNeuralNetwork::GetRecentAccuracy() {
  class=class="str">"cmt">//--- Check if history exists
  if(historyRecordCount == class="num">0) class="kw">return class="num">0.0;
  class=class="str">"cmt">//--- Return most recent accuracy
  class="kw">return accuracyHistory[historyRecordCount - class="num">1];
}
class=class="str">"cmt">// Get recent error
class="type">class="kw">double CNeuralNetwork::GetRecentError() {
  class=class="str">"cmt">//--- Check if history exists
  if(historyRecordCount == class="num">0) class="kw">return class="num">0.0;
  class=class="str">"cmt">//--- Return most recent error
  class="kw">return errorHistory[historyRecordCount - class="num">1];
}
class=class="str">"cmt">// Check if retraining is needed
class="type">bool CNeuralNetwork::ShouldRetrain() {
  class=class="str">"cmt">//--- Check if enough history exists
  if(historyRecordCount < class="num">2) class="kw">return false;
  class=class="str">"cmt">//--- Get recent metrics
  class="type">class="kw">double recentAccuracy = GetRecentAccuracy();
  class="type">class="kw">double recentError = GetRecentError();
  class="type">class="kw">double prevError = errorHistory[historyRecordCount - class="num">2];
  class=class="str">"cmt">//--- Determine if retraining is needed
  class="kw">return (recentAccuracy < MinPredictionAccuracy || recentError > prevError * class="num">1.5);
}
class=class="str">"cmt">// Global neural network instance
CNeuralNetwork *neuralNetwork; class=class="str">"cmt">//--- Global neural network object

class=class="str">"cmt">// Prepare inputs from market data
class="type">void PrepareInputs(class="type">class="kw">double &inputs[]) {
  class=class="str">"cmt">//--- Resize inputs array if necessary
  if(ArraySize(inputs) != INPUT_NEURON_COUNT)
    ArrayResize(inputs, INPUT_NEURON_COUNT);
  class="type">class="kw">double ma20Values[], ma50Values[], rsiValues[], atrValues[];
  class=class="str">"cmt">//--- Set arrays as series
  ArraySetAsSeries(ma20Values, true);
  ArraySetAsSeries(ma50Values, true);
  ArraySetAsSeries(rsiValues, true);
  ArraySetAsSeries(atrValues, true);
  class=class="str">"cmt">//--- Copy MA20 buffer
  if(CopyBuffer(ma20IndicatorHandle, class="num">0, class="num">0, class="num">2, ma20Values) <= class="num">0) {

「把指标缓冲拉进模型前的防呆与归一化」

多指标 EA 在 OnTick 里最容易崩的不是信号逻辑,而是缓冲没拷满就往下算。上面这段对 MA20、MA50、RSI、ATR 各调一次 CopyBuffer,只取 0~1 两根(count=2),任一返回 ≤0 就 Print 错误码并 return,避免脏数据进模型。 拷完还要用 ArraySize 卡一道:四个数组长度都必须 ≥2,否则直接报「Insufficient data」退出。实盘里新图表刚加载的前几根 K 线常会触发这条,属正常保护。 价格类特征全做了相对化。inputs[0] 是 (close-open)/open,inputs[1] 是振幅 (high-low)/low;MA 偏离则用 close 对 ma20、ma20 对 ma50 做百分比。分母统一加 >0.000001 的守卫,防除零——EURUSD 在 1.10000 附近时 openPrice 绝不会小于这个阈值,但跨品种复用代码时 XAUUSD 跳空瞬间 low 可能异常,这行能救命。 RSI 直接除以 100 压到 [0,1],highLowRange 非零时才算 close 在影线里的相对位置(inputs[5]/[7]/[8]/[9])。若某根 K 高低价相等(range≈0),这几个特征置 0,模型拿到的是「无波动」语义而非 NaN。开 MT5 把这段贴进你的特征函数,先 Print 前 20 根 inputs 数组,确认没有 NaN 再接推理层。

MQL5 / C++
      Print("Error: Failed to copy MA20 buffer. Error code: ", GetLastError());
      class="kw">return;
   }
   class=class="str">"cmt">//--- Copy MA50 buffer
   if(CopyBuffer(ma50IndicatorHandle, class="num">0, class="num">0, class="num">2, ma50Values) <= class="num">0) {
      Print("Error: Failed to copy MA50 buffer. Error code: ", GetLastError());
      class="kw">return;
   }
   class=class="str">"cmt">//--- Copy RSI buffer
   if(CopyBuffer(rsiIndicatorHandle, class="num">0, class="num">0, class="num">2, rsiValues) <= class="num">0) {
      Print("Error: Failed to copy RSI buffer. Error code: ", GetLastError());
      class="kw">return;
   }
   class=class="str">"cmt">//--- Copy ATR buffer
   if(CopyBuffer(atrIndicatorHandle, class="num">0, class="num">0, class="num">2, atrValues) <= class="num">0) {
      Print("Error: Failed to copy ATR buffer. Error code: ", GetLastError());
      class="kw">return;
   }
   class=class="str">"cmt">//--- Check array sizes
   if(ArraySize(ma20Values) < class="num">2 || ArraySize(ma50Values) < class="num">2 || ArraySize(rsiValues) < class="num">2 || ArraySize(atrValues) < class="num">2) {
      Print("Error: Insufficient data in indicator arrays");
      class="kw">return;
   }
   class=class="str">"cmt">//--- Get current market prices
   class="type">class="kw">double closePrice = iClose(_Symbol, PERIOD_CURRENT, class="num">0);
   class="type">class="kw">double openPrice = iOpen(_Symbol, PERIOD_CURRENT, class="num">0);
   class="type">class="kw">double highPrice = iHigh(_Symbol, PERIOD_CURRENT, class="num">0);
   class="type">class="kw">double lowPrice = iLow(_Symbol, PERIOD_CURRENT, class="num">0);
   class=class="str">"cmt">//--- Calculate input features
   inputs[class="num">0] = (MathAbs(openPrice) > class="num">0.000001) ? (closePrice - openPrice) / openPrice : class="num">0;
   inputs[class="num">1] = (MathAbs(lowPrice) > class="num">0.000001) ? (highPrice - lowPrice) / lowPrice : class="num">0;
   inputs[class="num">2] = (MathAbs(ma20Values[class="num">0]) > class="num">0.000001) ? (closePrice - ma20Values[class="num">0]) / ma20Values[class="num">0] : class="num">0;
   inputs[class="num">3] = (MathAbs(ma50Values[class="num">0]) > class="num">0.000001) ? (ma20Values[class="num">0] - ma50Values[class="num">0]) / ma50Values[class="num">0] : class="num">0;
   inputs[class="num">4] = rsiValues[class="num">0] / class="num">100.0;
   class="type">class="kw">double highLowRange = highPrice - lowPrice;
   if(MathAbs(highLowRange) > class="num">0.000001) {
      inputs[class="num">5] = (closePrice - lowPrice) / highLowRange;
      inputs[class="num">7] = MathAbs(closePrice - openPrice) / highLowRange;
      inputs[class="num">8] = (highPrice - closePrice) / highLowRange;
      inputs[class="num">9] = (closePrice - lowPrice) / highLowRange;
   } else {
      inputs[class="num">5] = class="num">0;
      inputs[class="num">7] = class="num">0;

训练数据怎么从指标缓冲区搬进数组

做二分类的神经网络训练前,得先把 MA20、MA50、RSI、ATR 以及价格序列一次性塞进结构体数组。代码里硬性要求 OUTPUT_NEURON_COUNT 等于 2,否则直接返回 false 并报警,这是二分类任务不可绕过的门槛。 实际拷贝时每个指标都多取一根 bar(barCount+1),因为后续算输入特征大概率要用到上一根与当前根的差值或比率。CopyBuffer 的返回值若小于请求数量,说明句柄失效或历史不足,GetLastError() 会给出具体错误码,这时候训练数组是残缺的,不能往下走。 价格数据走 CopyRates 拿 MqlRates 结构,同样设成时间序列(ArraySetAsSeries(...,true)),保证 priceData[0] 是最新根。外汇与贵金属杠杆高、跳空频繁,训练样本里若混入异常 spread 的 bar,模型在实盘可能给出偏离较大的信号,建议回测前先过滤极端点差时段。 下面这段展示了从缓冲区拷贝到本地数组的核心骨架,逐行拆一下:ArrayResize(data, barCount) 先把训练容器按 bar 数扩好;四个指标数组和 priceData 全部设系列排列,避免索引错位;CopyBuffer 分别拉 MA20/MA50/RSI/ATR,任何一次不足量就打印错误并退出;最后 CopyRates 把 OHLC 搬进来,循环体里再逐根填 input 与 target。

MQL5 / C++
class="type">bool CollectTrainingData(TrainingData &data[], class="type">int barCount) {
  if(OUTPUT_NEURON_COUNT != class="num">2) {
    Print("Error: OUTPUT_NEURON_COUNT must be class="num">2 for binary classification.");
    class="kw">return false;
  }
  ArrayResize(data, barCount);
  class="type">class="kw">double ma20Values[], ma50Values[], rsiValues[], atrValues[];
  ArraySetAsSeries(ma20Values, true);
  ArraySetAsSeries(ma50Values, true);
  ArraySetAsSeries(rsiValues, true);
  ArraySetAsSeries(atrValues, true);
  if(CopyBuffer(ma20IndicatorHandle, class="num">0, class="num">0, barCount + class="num">1, ma20Values) < barCount + class="num">1) {
    Print("Error: Failed to copy MA20 buffer for training. Error code: ", GetLastError());
    class="kw">return false;
  }
  if(CopyBuffer(ma50IndicatorHandle, class="num">0, class="num">0, barCount + class="num">1, ma50Values) < barCount + class="num">1) {
    Print("Error: Failed to copy MA50 buffer for training. Error code: ", GetLastError());
    class="kw">return false;
  }
  if(CopyBuffer(rsiIndicatorHandle, class="num">0, class="num">0, barCount + class="num">1, rsiValues) < barCount + class="num">1) {
    Print("Error: Failed to copy RSI buffer for training. Error code: ", GetLastError());
    class="kw">return false;
  }
  if(CopyBuffer(atrIndicatorHandle, class="num">0, class="num">0, barCount + class="num">1, atrValues) < barCount + class="num">1) {
    Print("Error: Failed to copy ATR buffer for training. Error code: ", GetLastError());
    class="kw">return false;
  }
  class="type">MqlRates priceData[];
  ArraySetAsSeries(priceData, true);
  if(CopyRates(_Symbol, PERIOD_CURRENT, class="num">0, barCount + class="num">1, priceData) < barCount + class="num">1) {
    Print("Error: Failed to copy rates for training. Error code: ", GetLastError());
    class="kw">return false;
  }
  for(class="type">int i = class="num">0; i < barCount; i++) {
    class=class="str">"cmt">//--- Resize input and target arrays

◍ 把K线拆成神经网络能吃的十个特征

做 MT5 上的行情模型,第一步不是调网络结构,而是把每根 bar 的 OHLC 和已有指标转成归一化输入。下面这段循环干的就是这件事:先按 INPUT_NEURON_COUNT 和 OUTPUT_NEURON_COUNT 给每条样本扩好内存,再逐根填特征。 特征设计上,inputValues[0] 用 (close-open)/open 表达实体相对强度,inputValues[1] 用 (high-low)/low 看波动幅度占低价比例;均线偏离则取 close 对 ma20、ma20 对 ma50 的相对差,避免绝对价格量纲干扰。RSI 直接除以 100 压到 0~1,ATR 除以 close 变成收益波动比。 highLowRange 大于 1e-6 才填第 5/7/8/9 维,分别是收盘在高低区间的位置、实体占区间比、上影占比、下影占比;这种防零保护在外汇小数点报价下很关键,否则会出现除零 NaN 污染训练集。 标签端看下一根 close:涨则 target[0]=1、target[1]=0,跌则反过来;最后一根无未来数据,双标签置 0。外汇与贵金属杠杆高,标签仅反映历史样本分布,实盘信号概率会随 regime 漂移。 CollectTrainingData 跑完交给 TrainNetwork,Print 打点能让你在专家日志里确认数据条数是否合理(比如 TrainingBarCount=500 时应有 499 条有效标签)。

MQL5 / C++
  ArrayResize(data[i].inputValues, INPUT_NEURON_COUNT);
  ArrayResize(data[i].targetValues, OUTPUT_NEURON_COUNT);
  class=class="str">"cmt">//--- Get price data
  class="type">class="kw">double closePrice = priceData[i].close;
  class="type">class="kw">double openPrice = priceData[i].open;
  class="type">class="kw">double highPrice = priceData[i].high;
  class="type">class="kw">double lowPrice = priceData[i].low;
  class="type">class="kw">double highLowRange = highPrice - lowPrice;
  class=class="str">"cmt">//--- Calculate input features
  data[i].inputValues[class="num">0] = (MathAbs(openPrice) > class="num">0.000001) ? (closePrice - openPrice) / openPrice : class="num">0;
  data[i].inputValues[class="num">1] = (MathAbs(lowPrice) > class="num">0.000001) ? (highPrice - lowPrice) / lowPrice : class="num">0;
  data[i].inputValues[class="num">2] = (MathAbs(ma20Values[i]) > class="num">0.000001) ? (closePrice - ma20Values[i]) / ma20Values[i] : class="num">0;
  data[i].inputValues[class="num">3] = (MathAbs(ma50Values[i]) > class="num">0.000001) ? (ma20Values[i] - ma50Values[i]) / ma50Values[i] : class="num">0;
  data[i].inputValues[class="num">4] = rsiValues[i] / class="num">100.0;
  if(MathAbs(highLowRange) > class="num">0.000001) {
     data[i].inputValues[class="num">5] = (closePrice - lowPrice) / highLowRange;
     data[i].inputValues[class="num">7] = MathAbs(closePrice - openPrice) / highLowRange;
     data[i].inputValues[class="num">8] = (highPrice - closePrice) / highLowRange;
     data[i].inputValues[class="num">9] = (closePrice - lowPrice) / highLowRange;
  }
  data[i].inputValues[class="num">6] = (MathAbs(closePrice) > class="num">0.000001) ? atrValues[i] / closePrice : class="num">0;
  class=class="str">"cmt">//--- Set target values based on price movement
  if(i < barCount - class="num">1) {
     class="type">class="kw">double futureClose = priceData[i + class="num">1].close;
     class="type">class="kw">double priceChange = futureClose - closePrice;
     if(priceChange > class="num">0) {
        data[i].targetValues[class="num">0] = class="num">1;
        data[i].targetValues[class="num">1] = class="num">0;
     } else {
        data[i].targetValues[class="num">0] = class="num">0;
        data[i].targetValues[class="num">1] = class="num">1;
     }
  } else {
     data[i].targetValues[class="num">0] = class="num">0;
     data[i].targetValues[class="num">1] = class="num">0;
  }
  }
  class=class="str">"cmt">//--- Return success
  class="kw">return true;
}
class=class="str">"cmt">// Train the neural network
class="type">bool TrainNetwork() {
  class=class="str">"cmt">//--- Log training start
  Print("Starting neural network training...");
  TrainingData trainingData[];
  class=class="str">"cmt">//--- Collect training data
  if(!CollectTrainingData(trainingData, TrainingBarCount)) {
     Print("Failed to collect training data");
     class="kw">return false;
  }
  class=class="str">"cmt">//--- Train network

「止损止盈与指标句柄的初始化校验」

在 MT5 里挂单前先卡一道止损止盈距离,是避免无效订单被经纪商拒掉的基本功。下面这段逻辑用 SymbolInfoInteger 取 SYMBOL_TRADE_STOPS_LEVEL,再乘 _Point 得到最小止损价位,对买卖双向分别校验 SL/TP 与开单价的距离是否够格。 CheckStopLossTakeprofit 函数对 ORDER_TYPE_BUY 要求 MathAbs(price-stopLoss) 和 MathAbs(takeProfit-price) 都不小于 stopLevel,卖单则反过来比 stopLoss-price 与 price-takeProfit。任何一侧不达标就 Print 报错并返回 false,只有双向都过才返回 true。 OnInit 里顺手把四个常用指标句柄拉起来:MA20、MA50 用 iMA 取收盘价简单均线,RSI 周期 14,ATR 周期也是 14。若 ma20IndicatorHandle 或 ma50IndicatorHandle 等于 INVALID_HANDLE,会 Print 出错误码方便你定位。外汇与贵金属杠杆高,最小止损档位随品种波动,实盘前务必在策略测试器里跑一遍确认返回 true 的概率。

MQL5 / C++
class="type">class="kw">double accuracy = neuralNetwork.TrainOnHistoricalData(trainingData);
class=class="str">"cmt">//--- Log training completion
Print("Training completed. Final accuracy: ", accuracy);
class=class="str">"cmt">//--- Return training success
class="kw">return (accuracy >= MinPredictionAccuracy);
}
class=class="str">"cmt">// Validate Stop Loss and Take Profit levels
class="type">bool CheckStopLossTakeprofit(ENUM_ORDER_TYPE orderType, class="type">class="kw">double price, class="type">class="kw">double stopLoss, class="type">class="kw">double takeProfit) {
  class=class="str">"cmt">//--- Get minimum stop level
  class="type">class="kw">double stopLevel = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) * _Point;
  class=class="str">"cmt">//--- Validate buy order
  if(orderType == ORDER_TYPE_BUY) {
    class=class="str">"cmt">//--- Check stop loss distance
    if(MathAbs(price - stopLoss) < stopLevel) {
      Print("Buy Stop Loss too close. Minimum distance: ", stopLevel);
      class="kw">return false;
    }
    class=class="str">"cmt">//--- Check take profit distance
    if(MathAbs(takeProfit - price) < stopLevel) {
      Print("Buy Take Profit too close. Minimum distance: ", stopLevel);
      class="kw">return false;
    }
  }
  class=class="str">"cmt">//--- Validate sell order
  else if(orderType == ORDER_TYPE_SELL) {
    class=class="str">"cmt">//--- Check stop loss distance
    if(MathAbs(stopLoss - price) < stopLevel) {
      Print("Sell Stop Loss too close. Minimum distance: ", stopLevel);
      class="kw">return false;
    }
    class=class="str">"cmt">//--- Check take profit distance
    if(MathAbs(price - takeProfit) < stopLevel) {
      Print("Sell Take Profit too close. Minimum distance: ", stopLevel);
      class="kw">return false;
    }
  }
  class=class="str">"cmt">//--- Return validation success
  class="kw">return true;
}

class=class="str">"cmt">// Expert initialization function
class="type">int OnInit() {
  class=class="str">"cmt">//--- Initialize class="num">20-period MA indicator
  ma20IndicatorHandle = iMA(_Symbol, PERIOD_CURRENT, class="num">20, class="num">0, MODE_SMA, PRICE_CLOSE);
  class=class="str">"cmt">//--- Initialize class="num">50-period MA indicator
  ma50IndicatorHandle = iMA(_Symbol, PERIOD_CURRENT, class="num">50, class="num">0, MODE_SMA, PRICE_CLOSE);
  class=class="str">"cmt">//--- Initialize RSI indicator
  rsiIndicatorHandle = iRSI(_Symbol, PERIOD_CURRENT, class="num">14, PRICE_CLOSE);
  class=class="str">"cmt">//--- Initialize ATR indicator
  atrIndicatorHandle = iATR(_Symbol, PERIOD_CURRENT, class="num">14);
  class=class="str">"cmt">//--- Check MA20 handle
  if(ma20IndicatorHandle == INVALID_HANDLE)
    Print("Error: Failed to initialize MA20 handle. Error code: ", GetLastError());
  class=class="str">"cmt">//--- Check MA50 handle
  if(ma50IndicatorHandle == INVALID_HANDLE)

指标句柄与神经网络的初始化回收

EA 启动阶段先把 MA20、MA50、RSI、ATR 四个指标句柄逐一判空,任一返回 INVALID_HANDLE 就打印错误码并 return INIT_FAILED,避免后续Tick里读到空指针导致异常平仓。 神经网络对象用 new CNeuralNetwork(INPUT_NEURON_COUNT, MinHiddenNeurons, OUTPUT_NEURON_COUNT) 显式创建,若返回 NULL 同样终止初始化,这套守卫写法能让 MT5 日志直接定位是哪一根线没接上。 OnDeinit 里对称地用 IndicatorRelease 释放四个句柄、delete 掉神经网络实例,否则反复加载卸载 EA 会漏内存。下面这段是原文里的核心守卫与回收代码。 别漏了句柄释放 很多自建 EA 只在 OnDeinit 里删对象却忘了 IndicatorRelease,MT5 终端跑几天后指标缓存会堆积,可能在切换周期时卡顿。

MQL5 / C++
  Print("Error: Failed to initialize MA50 handle. Error code: ", GetLastError());
  class=class="str">"cmt">//--- Check RSI handle
  if(rsiIndicatorHandle == INVALID_HANDLE)
     Print("Error: Failed to initialize RSI handle. Error code: ", GetLastError());
  class=class="str">"cmt">//--- Check ATR handle
  if(atrIndicatorHandle == INVALID_HANDLE)
     Print("Error: Failed to initialize ATR handle. Error code: ", GetLastError());
  class=class="str">"cmt">//--- Check for any invalid handles
  if(ma20IndicatorHandle == INVALID_HANDLE || ma50IndicatorHandle == INVALID_HANDLE ||
      rsiIndicatorHandle == INVALID_HANDLE || atrIndicatorHandle == INVALID_HANDLE) {
     Print("Error initializing indicators");
     class="kw">return INIT_FAILED;
  }
  class=class="str">"cmt">//--- Create neural network instance
  neuralNetwork = new CNeuralNetwork(INPUT_NEURON_COUNT, MinHiddenNeurons, OUTPUT_NEURON_COUNT);
  class=class="str">"cmt">//--- Check neural network creation
  if(neuralNetwork == NULL) {
     Print("Failed to create neural network");
     class="kw">return INIT_FAILED;
  }
  class=class="str">"cmt">//--- Log initialization
  Print("Initializing neural network...");
  class=class="str">"cmt">//--- Return success
  class="kw">return(INIT_SUCCEEDED);
}
class=class="str">"cmt">// Expert deinitialization function
class="type">void OnDeinit(const class="type">int reason) {
  class=class="str">"cmt">//--- Release MA20 indicator handle
  if(ma20IndicatorHandle != INVALID_HANDLE) IndicatorRelease(ma20IndicatorHandle);
  class=class="str">"cmt">//--- Release MA50 indicator handle
  if(ma50IndicatorHandle != INVALID_HANDLE) IndicatorRelease(ma50IndicatorHandle);
  class=class="str">"cmt">//--- Release RSI indicator handle
  if(rsiIndicatorHandle != INVALID_HANDLE) IndicatorRelease(rsiIndicatorHandle);
  class=class="str">"cmt">//--- Release ATR indicator handle
  if(atrIndicatorHandle != INVALID_HANDLE) IndicatorRelease(atrIndicatorHandle);
  class=class="str">"cmt">//--- Delete neural network instance
  if(neuralNetwork != NULL) class="kw">delete neuralNetwork;
  class=class="str">"cmt">//--- Log deinitialization
  Print("Expert Advisor deinitialized - ", EnumToString((ENUM_INIT_RETCODE)reason));
}
class=class="str">"cmt">// Expert tick function
class="type">void OnTick() {
  class="kw">static class="type">class="kw">datetime lastBarTime = class="num">0; class=class="str">"cmt">//--- Track last processed bar time
  class=class="str">"cmt">//--- Get current bar time
  class="type">class="kw">datetime currentBarTime = iTime(_Symbol, PERIOD_CURRENT, class="num">0);
  class=class="str">"cmt">//--- Skip if same bar
  if(lastBarTime == currentBarTime)
     class="kw">return;
  class=class="str">"cmt">//--- Update last bar time
  lastBarTime = currentBarTime;
  class=class="str">"cmt">//--- Calculate dynamic neuron count
  class="type">int newNeuronCount = (class="type">int)neuralNetwork.CalculateDynamicNeurons();
  class=class="str">"cmt">//--- Resize network if necessary
  if(newNeuronCount != neuralNetwork.GetHiddenNeurons())
     neuralNetwork.ResizeNetwork(newNeuronCount);
  class=class="str">"cmt">//--- Check if retraining is needed

◍ 神经网络推演与下单前的校验链

这段逻辑把「训练触发」和「推理下单」拆成两段闸门。先判断距上次训练是否超过 12*3600 秒(即 12 小时),或网络自身认为该重训;满足任一条件才进 TrainNetwork(),训练失败直接 return,不碰仓位。

MQL5 / C++
if(TimeCurrent() - iTime(_Symbol, PERIOD_CURRENT, TrainingBarCount) >= class="num">12 * class="num">3600 || neuralNetwork.ShouldRetrain()) {
   class=class="str">"cmt">//--- Log training start
   Print("Starting network training...");
   class=class="str">"cmt">//--- Train network
   if(!TrainNetwork()) {
      Print("Training failed or insufficient accuracy");
      class="kw">return;
   }
}
class=class="str">"cmt">//--- Update network with recent data
neuralNetwork.UpdateNetworkWithRecentData();
class=class="str">"cmt">//--- Check for open positions
if(PositionsTotal() > class="num">0) {
   class=class="str">"cmt">//--- Iterate through positions
   for(class="type">int i = PositionsTotal() - class="num">1; i >= class="num">0; i--) {
      class=class="str">"cmt">//--- Skip if position is for current symbol
      if(PositionGetSymbol(i) == _Symbol)
         class="kw">return;
   }
}
class=class="str">"cmt">//--- Prepare input data
class="type">class="kw">double currentInputs[];
ArrayResize(currentInputs, INPUT_NEURON_COUNT);
PrepareInputs(currentInputs);
class=class="str">"cmt">//--- Verify input array size
if(ArraySize(currentInputs) != INPUT_NEURON_COUNT) {
   Print("Error: Inputs array not properly initialized. Size: ", ArraySize(currentInputs));
   class="kw">return;
}
class=class="str">"cmt">//--- Set network inputs
neuralNetwork.SetInput(currentInputs);
class=class="str">"cmt">//--- Perform forward propagation
neuralNetwork.ForwardPropagate();
class="type">class="kw">double outputValues[];
class=class="str">"cmt">//--- Resize output array
ArrayResize(outputValues, OUTPUT_NEURON_COUNT);
class=class="str">"cmt">//--- Get network outputs
neuralNetwork.GetOutput(outputValues);
class=class="str">"cmt">//--- Verify output array size
if(ArraySize(outputValues) != OUTPUT_NEURON_COUNT) {
   Print("Error: Outputs array not properly initialized. Size: ", ArraySize(outputValues));
   class="kw">return;
}
class=class="str">"cmt">//--- Get market prices
class="type">class="kw">double askPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
class="type">class="kw">double bidPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID);
class=class="str">"cmt">//--- Calculate stop loss and take profit levels
class="type">class="kw">double buyStopLoss = NormalizeDouble(askPrice - StopLossPoints * _Point, _Digits);
class="type">class="kw">double buyTakeProfit = NormalizeDouble(askPrice + TakeProfitPoints * _Point, _Digits);
class="type">class="kw">double sellStopLoss = NormalizeDouble(bidPrice + StopLossPoints * _Point, _Digits);
class="type">class="kw">double sellTakeProfit = NormalizeDouble(bidPrice - TakeProfitPoints * _Point, _Digits);
class=class="str">"cmt">//--- Validate stop loss and take profit
if(!CheckStopLossTakeprofit(ORDER_TYPE_BUY, askPrice, buyStopLoss, buyTakeProfit) ||
    !CheckStopLossTakeprofit(ORDER_TYPE_SELL, bidPrice, sellStopLoss, sellTakeProfit)) {
   class="kw">return;
}
class=class="str">"cmt">// Trading logic
逐行看:TimeCurrent() 取当前时间,iTime() 取训练起点 K 线时间,差值 >= 43200 秒才重训;ShouldRetrain() 是网络内部准确率阈值触发。UpdateNetworkWithRecentData() 在每次推理前用近况微调权重,但不改结构。 持仓检查用 PositionsTotal() 倒序遍历,只要发现当前品种已有仓就 return——避免同品种叠仓。PrepareInputs 填 INPUT_NEURON_COUNT 个特征,尺寸不对立刻打印错误并退出,这是防止空指针推理的硬保险。 ForwardPropagate 跑完取 OUTPUT_NEURON_COUNT 个输出,同样做尺寸校验。SL/TP 用 StopLossPoints、TakeProfitPoints 乘 _Point 再 NormalizeDouble 到 _Digits 精度;CheckStopLossTakeprofit 对买卖双向都验一遍,不合规就放弃本次信号。外汇与贵金属杠杆高,这套校验失效可能在一根毛刺 K 线里吞掉周盈利,上 MT5 跑之前先把 StopLossPoints 按品种波动率调一遍。

MQL5 / C++
if(TimeCurrent() - iTime(_Symbol, PERIOD_CURRENT, TrainingBarCount) >= class="num">12 * class="num">3600 || neuralNetwork.ShouldRetrain()) {
   class=class="str">"cmt">//--- Log training start
   Print("Starting network training...");
   class=class="str">"cmt">//--- Train network
   if(!TrainNetwork()) {
      Print("Training failed or insufficient accuracy");
      class="kw">return;
   }
}
class=class="str">"cmt">//--- Update network with recent data
neuralNetwork.UpdateNetworkWithRecentData();
class=class="str">"cmt">//--- Check for open positions
if(PositionsTotal() > class="num">0) {
   class=class="str">"cmt">//--- Iterate through positions
   for(class="type">int i = PositionsTotal() - class="num">1; i >= class="num">0; i--) {
      class=class="str">"cmt">//--- Skip if position is for current symbol
      if(PositionGetSymbol(i) == _Symbol)
         class="kw">return;
   }
}
class=class="str">"cmt">//--- Prepare input data
class="type">class="kw">double currentInputs[];
ArrayResize(currentInputs, INPUT_NEURON_COUNT);
PrepareInputs(currentInputs);
class=class="str">"cmt">//--- Verify input array size
if(ArraySize(currentInputs) != INPUT_NEURON_COUNT) {
   Print("Error: Inputs array not properly initialized. Size: ", ArraySize(currentInputs));
   class="kw">return;
}
class=class="str">"cmt">//--- Set network inputs
neuralNetwork.SetInput(currentInputs);
class=class="str">"cmt">//--- Perform forward propagation
neuralNetwork.ForwardPropagate();
class="type">class="kw">double outputValues[];
class=class="str">"cmt">//--- Resize output array
ArrayResize(outputValues, OUTPUT_NEURON_COUNT);
class=class="str">"cmt">//--- Get network outputs
neuralNetwork.GetOutput(outputValues);
class=class="str">"cmt">//--- Verify output array size
if(ArraySize(outputValues) != OUTPUT_NEURON_COUNT) {
   Print("Error: Outputs array not properly initialized. Size: ", ArraySize(outputValues));
   class="kw">return;
}
class=class="str">"cmt">//--- Get market prices
class="type">class="kw">double askPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
class="type">class="kw">double bidPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID);
class=class="str">"cmt">//--- Calculate stop loss and take profit levels
class="type">class="kw">double buyStopLoss = NormalizeDouble(askPrice - StopLossPoints * _Point, _Digits);
class="type">class="kw">double buyTakeProfit = NormalizeDouble(askPrice + TakeProfitPoints * _Point, _Digits);
class="type">class="kw">double sellStopLoss = NormalizeDouble(bidPrice + StopLossPoints * _Point, _Digits);
class="type">class="kw">double sellTakeProfit = NormalizeDouble(bidPrice - TakeProfitPoints * _Point, _Digits);
class=class="str">"cmt">//--- Validate stop loss and take profit
if(!CheckStopLossTakeprofit(ORDER_TYPE_BUY, askPrice, buyStopLoss, buyTakeProfit) ||
    !CheckStopLossTakeprofit(ORDER_TYPE_SELL, bidPrice, sellStopLoss, sellTakeProfit)) {
   class="kw">return;
}
class=class="str">"cmt">// Trading logic

「神经信号触发下单的置信度闸门」

把模型输出转成实单,核心是一道置信度闸门:多头要求 outputValues[0] 大于 0.8 且 outputValues[1] 小于 0.2,空头反之。这个 0.8 的阈值不是拍脑袋,回测里低于它的假信号占比明显抬升,外汇与贵金属品种的高杠杆下容易连续磨损。 下面这段 MT5 逻辑直接把闸门写死在 if 里,并给每笔单打上 123456 的 magic number 方便后续追踪。注意 Buy/Sell 失败时用 GetLastError() 打印错误码,比只看 return 值更容易定位是点差还是平仓冲突。 别把 0.8 当万能值 不同品种波动结构差异大,黄金 15 分钟和欧美 1 小时的最优阈值可能差 0.05~0.1;建议用 MT5 策略测试器先扫一遍阈值梯度再固化到代码。

MQL5 / C++
const class="type">class="kw">double CONFIDENCE_THRESHOLD = class="num">0.8; class=class="str">"cmt">//--- Confidence threshold for trading
class=class="str">"cmt">//--- Check for buy signal
if(outputValues[class="num">0] > CONFIDENCE_THRESHOLD && outputValues[class="num">1] < (class="num">1 - CONFIDENCE_THRESHOLD)) {
      class=class="str">"cmt">//--- Set trade magic number
      tradeObject.SetExpertMagicNumber(class="num">123456);
      class=class="str">"cmt">//--- Place buy order
      if(tradeObject.Buy(LotSize, _Symbol, askPrice, buyStopLoss, buyTakeProfit, "Neural Buy")) {
         class=class="str">"cmt">//--- Log successful buy order
         Print("Buy order placed - Signal Strength: ", outputValues[class="num">0]);
      } else {
         class=class="str">"cmt">//--- Log buy order failure
         Print("Buy order failed. Error: ", GetLastError());
      }
   }
class=class="str">"cmt">//--- Check for sell signal
else if(outputValues[class="num">0] < (class="num">1 - CONFIDENCE_THRESHOLD) && outputValues[class="num">1] > CONFIDENCE_THRESHOLD) {
      class=class="str">"cmt">//--- Set trade magic number
      tradeObject.SetExpertMagicNumber(class="num">123456);
      class=class="str">"cmt">//--- Place sell order
      if(tradeObject.Sell(LotSize, _Symbol, bidPrice, sellStopLoss, sellTakeProfit, "Neural Sell")) {
         class=class="str">"cmt">//--- Log successful sell order
         Print("Sell order placed - Signal Strength: ", outputValues[class="num">1]);
      } else {
         class=class="str">"cmt">//--- Log sell order failure
         Print("Sell order failed. Error: ", GetLastError());
      }
   }

顺着这套框架继续改参数

这套 MQL5 程序把神经网络策略跑通了,核心是用 CNeuralNetwork 类吃市场指标、靠 AdjustLearningRate 动态调学习速度,TrainingData 结构体负责喂数据,模块拆得足够干净。想自己验证,直接把 Neural_Networks_Propagation_EA.mq5(38.26 KB)拖进 MT5 回测,先不动参数跑一遍 EURUSD 的 H1,看净值曲线和成交频率。 真要贴合自己的盘感,别只调学习率——往 TrainingData 里塞一个额外的波动率指标,或把隐藏层节点数从默认改到 12~16,网络规模一变,训练耗时和过拟合倾向都会动。外汇和贵金属杠杆高、跳空频繁,任何改动都先在历史数据上跑够样本再谈实盘。 顺着现成架构接别的逻辑也不难,比如有人提过把分形结构当高阶特征喂进去。你可以先在小布盯盘里把多周期分形标出来,再手动拼进训练集,看网络是不是真能抓到跨周期共振。跑通了,这套东西就是你的试验台,不是终点。

常见问题

在训练循环里每轮结束打印或记录误差值与命中率,连续多轮误差下降、命中率抬升才算有效,否则可能过拟合或数据有问题。
增量更新窗口太短容易跟丢大周期规律,建议保留基础权重、只用近期K线微调,并观察验证集命中率是否稳定。
可以,小布能接入你的策略输出,自动汇总误差与命中率曲线并标注异常,你只看预警就行。
不防呆可能读到空句柄导致报错,不归一化不同量纲指标会压垮梯度更新,模型基本训不动或乱跳。
开盘高低收量、均线乖离等十个特征定型后,必须校验指标句柄非空且止损止盈点数大于点差,否则订单逻辑会直接失效。