MQL5交易策略自动化(第二十一部分):借助自适应学习率提升神经网络交易效果·综合运用
训练循环里的误差与命中率记账
这段逻辑跑在神经网络训练的主循环里,每个 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 误差是否按预期收敛。外汇与贵金属行情噪声大,这类模型过拟合概率不低,验证时务必留独立样本。
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 模块拿到标准化输入。外汇与贵金属波动剧烈,这类模型输出仅作概率参考,实盘须自担高风险。
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 再接推理层。
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。
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 条有效标签)。
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 的概率。
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 终端跑几天后指标缓存会堆积,可能在切换周期时卡顿。
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,不碰仓位。
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
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 策略测试器先扫一遍阈值梯度再固化到代码。
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,网络规模一变,训练耗时和过拟合倾向都会动。外汇和贵金属杠杆高、跳空频繁,任何改动都先在历史数据上跑够样本再谈实盘。 顺着现成架构接别的逻辑也不难,比如有人提过把分形结构当高阶特征喂进去。你可以先在小布盯盘里把多周期分形标出来,再手动拼进训练集,看网络是不是真能抓到跨周期共振。跑通了,这套东西就是你的试验台,不是终点。