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

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

第 2/3 篇

「神经网络类的骨架与字符串解析入口」

在 MT5 里手搓一个前馈神经网络,第一步是把层结构和权重容器先声明清楚。下面这段类定义把输入/隐藏/输出三层神经元数量、各层数值数组、层间权重与偏置、以及训练过程中的 delta 和误差历史全部列为私有成员,方便后续反向传播时直接按索引读写。 训练样本往往从 CSV 或文本框里以逗号分隔的字符串灌进来,ParseStringToArray 就是这道闸门。它先用 StringSplit 以 ASCII 44(即逗号)切分,若切出数量与 expectedSize 不符直接返回 false 并打印错误码,避免脏数据进网络。 一个容易忽略的细节:解析后对每个值做了 [-1.0, 1.0] 的钳制。外汇和贵金属特征量纲差异大,归一化到该区间能降低梯度爆炸概率,但输入本身若超标说明预处理有误,高风险品种上更该在喂数据前自查。 把下面代码直接贴进 MT5 的 MQ5 文件,改 expectedSize 就能适配你自己的特征维度,先跑通解析再接训练循环。

MQL5 / C++
class="type">class="kw">double targetValues[]; class=class="str">"cmt">//--- Array to store target values for training
};
class=class="str">"cmt">// Neural Network Class
class CNeuralNetwork {
class="kw">private:
  class="type">int inputNeuronCount;           class=class="str">"cmt">//--- Number of input neurons
  class="type">int hiddenNeuronCount;          class=class="str">"cmt">//--- Number of hidden neurons
  class="type">int outputNeuronCount;          class=class="str">"cmt">//--- Number of output neurons
  class="type">class="kw">double inputLayer[];            class=class="str">"cmt">//--- Array for input layer values
  class="type">class="kw">double hiddenLayer[];           class=class="str">"cmt">//--- Array for hidden layer values
  class="type">class="kw">double outputLayer[];           class=class="str">"cmt">//--- Array for output layer values
  class="type">class="kw">double inputToHiddenWeights[];  class=class="str">"cmt">//--- Weights between input and hidden layers
  class="type">class="kw">double hiddenToOutputWeights[]; class=class="str">"cmt">//--- Weights between hidden and output layers
  class="type">class="kw">double hiddenLayerBiases[];     class=class="str">"cmt">//--- Biases for hidden layer
  class="type">class="kw">double outputLayerBiases[];     class=class="str">"cmt">//--- Biases for output layer
  class="type">class="kw">double outputDeltas[];          class=class="str">"cmt">//--- Delta values for output layer
  class="type">class="kw">double hiddenDeltas[];          class=class="str">"cmt">//--- Delta values for hidden layer
  class="type">class="kw">double trainingError;           class=class="str">"cmt">//--- Current training error
  class="type">class="kw">double currentLearningRate;     class=class="str">"cmt">//--- Current learning rate
  class="type">class="kw">double accuracyHistory[];       class=class="str">"cmt">//--- History of training accuracy
  class="type">class="kw">double errorHistory[];          class=class="str">"cmt">//--- History of training errors
  class="type">int historyRecordCount;         class=class="str">"cmt">//--- Number of recorded history entries
};
class=class="str">"cmt">// Parse comma-separated class="type">class="kw">string to array
class="type">bool ParseStringToArray(class="type">class="kw">string inputString, class="type">class="kw">double &output[], class="type">int expectedSize) {
  class=class="str">"cmt">//--- Check if input class="type">class="kw">string is empty
  if(inputString == "") class="kw">return false;
  class="type">class="kw">string values[];
  class=class="str">"cmt">//--- Initialize array for parsed values
  ArrayResize(values, class="num">0);
  class=class="str">"cmt">//--- Split input class="type">class="kw">string by comma
  class="type">int count = StringSplit(inputString, class="num">44, values);
  class=class="str">"cmt">//--- Check if class="type">class="kw">string splitting failed
  if(count <= class="num">0) {
    Print("Error: StringSplit failed for input: ", inputString, ". Error code: ", GetLastError());
    class="kw">return false;
  }
  class=class="str">"cmt">//--- Verify correct number of values
  if(count != expectedSize) {
    Print("Error: Invalid number of values in input class="type">class="kw">string. Expected: ", expectedSize, ", Got: ", count);
    class="kw">return false;
  }
  class=class="str">"cmt">//--- Resize output array to expected size
  ArrayResize(output, expectedSize);
  class=class="str">"cmt">//--- Convert class="type">class="kw">string values to doubles and normalize
  for(class="type">int i = class="num">0; i < count; i++) {
    output[i] = StringToDouble(values[i]);
    class=class="str">"cmt">//--- Clamp values between -class="num">1.0 and class="num">1.0
    if(MathAbs(output[i]) > class="num">1.0) output[i] = MathMax(-class="num">1.0, MathMin(class="num">1.0, output[i]));
  }

神经网络类的接口与构造落地

这段 MQL5 代码给出了一个动态神经网络类的 public 接口骨架,以及构造函数的内存分配逻辑。从接口看,它不只做前向推理,还内建了 Backpropagate、TrainOnHistoricalData、UpdateNetworkWithRecentData 等训练与增量更新方法,说明网络设计目标是随行情滚动再训练,而不是离线训一次就固化。 构造函数里有个细节值得注意:hiddenNeuronCount 被强制夹在 MinHiddenNeurons 与 MaxHiddenNeurons 之间,学习率初始化为 MinLearningRate。这意味着隐藏层规模和学习率都不是拍脑袋定值,后续由 CalculateDynamicNeurons 与 AdjustLearningRate 动态接管。 数组全部用 ArrayResize 按神经元数量乘积展开,例如 inputToHiddenWeights 大小为 inputs * hiddenNeuronCount,权重矩阵和偏置分开存。accuracyHistory 与 errorHistory 预留了 MAX_HISTORY_SIZE 长度,方便回看近期训练精度与误差走势——在 MT5 里跑起来后,你可以直接读 GetRecentAccuracy 和 GetRecentError 判断网络是否该触发 ShouldRetrain。外汇与贵金属波动剧烈,这类自适应网络也只是概率工具,实盘前务必用历史数据验证过拟合倾向。

MQL5 / C++
class="kw">return true;
}

class="kw">public:
  CNeuralNetwork(class="type">int inputs, class="type">int hidden, class="type">int outputs); class=class="str">"cmt">//--- Constructor
  class="type">void InitializeWeights();                            class=class="str">"cmt">//--- Initialize network weights
  class="type">class="kw">double Sigmoid(class="type">class="kw">double x);                            class=class="str">"cmt">//--- Apply sigmoid activation function
  class="type">void ForwardPropagate();                             class=class="str">"cmt">//--- Perform forward propagation
  class="type">void Backpropagate(const class="type">class="kw">double &targets[]);         class=class="str">"cmt">//--- Perform backpropagation
  class="type">void SetInput(class="type">class="kw">double &inputs[]);                     class=class="str">"cmt">//--- Set input values
  class="type">void GetOutput(class="type">class="kw">double &outputs[]);                   class=class="str">"cmt">//--- Retrieve output values
  class="type">class="kw">double TrainOnHistoricalData(TrainingData &data[]);  class=class="str">"cmt">//--- Train on historical data
  class="type">void UpdateNetworkWithRecentData();                  class=class="str">"cmt">//--- Update with recent data
  class="type">void InitializeTraining();                           class=class="str">"cmt">//--- Initialize training arrays
  class="type">void ResizeNetwork(class="type">int newHiddenNeurons);            class=class="str">"cmt">//--- Resize network
  class="type">void AdjustLearningRate();                           class=class="str">"cmt">//--- Adjust learning rate dynamically
  class="type">class="kw">double GetRecentAccuracy();                          class=class="str">"cmt">//--- Get recent training accuracy
  class="type">class="kw">double GetRecentError();                             class=class="str">"cmt">//--- Get recent training error
  class="type">bool ShouldRetrain();                                class=class="str">"cmt">//--- Check if retraining is needed
  class="type">class="kw">double CalculateDynamicNeurons();                    class=class="str">"cmt">//--- Calculate dynamic neuron count
  class="type">int GetHiddenNeurons() {
    class="kw">return hiddenNeuronCount;                          class=class="str">"cmt">//--- Get current hidden neuron count
  }

class=class="str">"cmt">// Constructor
CNeuralNetwork::CNeuralNetwork(class="type">int inputs, class="type">int hidden, class="type">int outputs) {
  class=class="str">"cmt">//--- Set input neuron count
  inputNeuronCount = inputs;
  class=class="str">"cmt">//--- Set output neuron count
  outputNeuronCount = outputs;
  class=class="str">"cmt">//--- Initialize learning rate to minimum
  currentLearningRate = MinLearningRate;
  class=class="str">"cmt">//--- Set hidden neuron count
  hiddenNeuronCount = hidden;
  class=class="str">"cmt">//--- Ensure hidden neurons within bounds
  if(hiddenNeuronCount < MinHiddenNeurons) hiddenNeuronCount = MinHiddenNeurons;
  if(hiddenNeuronCount > MaxHiddenNeurons) hiddenNeuronCount = MaxHiddenNeurons;
  class=class="str">"cmt">//--- Resize input layer array
  ArrayResize(inputLayer, inputs);
  class=class="str">"cmt">//--- Resize hidden layer array
  ArrayResize(hiddenLayer, hiddenNeuronCount);
  class=class="str">"cmt">//--- Resize output layer array
  ArrayResize(outputLayer, outputs);
  class=class="str">"cmt">//--- Resize input-to-hidden weights array
  ArrayResize(inputToHiddenWeights, inputs * hiddenNeuronCount);
  class=class="str">"cmt">//--- Resize hidden-to-output weights array
  ArrayResize(hiddenToOutputWeights, hiddenNeuronCount * outputs);
  class=class="str">"cmt">//--- Resize hidden biases array
  ArrayResize(hiddenLayerBiases, hiddenNeuronCount);
  class=class="str">"cmt">//--- Resize output biases array
  ArrayResize(outputLayerBiases, outputs);
  class=class="str">"cmt">//--- Resize accuracy history array
  ArrayResize(accuracyHistory, MAX_HISTORY_SIZE);
  class=class="str">"cmt">//--- Resize error history array
  ArrayResize(errorHistory, MAX_HISTORY_SIZE);
  class=class="str">"cmt">//--- Initialize history record count
  historyRecordCount = class="num">0;
  class=class="str">"cmt">//--- Initialize training error
  trainingError = class="num">0.0;
  class=class="str">"cmt">//--- Initialize network weights

◍ 权重与偏置的载入和兜底随机化

神经网络类在构造收尾阶段会先调 InitializeWeights() 再调 InitializeTraining(),后者只干两件小事:按输出神经元数 resize outputDeltas,按隐藏神经元数 resize hiddenDeltas,为反向传播腾出梯度暂存空间。 权重初始化函数里用四个 bool 标记追踪输入层→隐藏层权重、隐藏层→输出层权重、隐藏层偏置、输出层偏置是否已由外部字符串传入。若对应字符串非空且能被 ParseStringToArray 按精确长度(如 inputNeuronCount * hiddenNeuronCount)解析,就 ArrayCopy 进主数组并打印日志;否则标记位保持 false。 凡是没传的外部参数,代码走兜底分支:以 MathRand()/32767.0 生成 [0,1) 均匀随机数,乘 2 减 1 映射到 [-1,1) 区间填权重。这套随机种子依赖 MathRand 的默认序列,多次初始化若未重设随机种子,权重分布倾向重复,做 MT5 回测时建议手动 MathSrand(TimeCurrent()) 打断规律性。 外汇与贵金属行情噪声大,用随机初始化权重训出来的轻量网络过拟合概率偏高,实盘前务必用样本外数据验证。

MQL5 / C++
  InitializeWeights();
  class=class="str">"cmt">//--- Initialize training arrays
  InitializeTraining();
}
class=class="str">"cmt">// Initialize training arrays
class="type">void CNeuralNetwork::InitializeTraining() {
  class=class="str">"cmt">//--- Resize output deltas array
  ArrayResize(outputDeltas, outputNeuronCount);
  class=class="str">"cmt">//--- Resize hidden deltas array
  ArrayResize(hiddenDeltas, hiddenNeuronCount);
}
class=class="str">"cmt">// Initialize weights
class="type">void CNeuralNetwork::InitializeWeights() {
  class=class="str">"cmt">//--- Track if weights and biases are set
  class="type">bool isInputToHiddenWeightsSet = false;
  class="type">bool isHiddenToOutputWeightsSet = false;
  class="type">bool isHiddenBiasesSet = false;
  class="type">bool isOutputBiasesSet = false;
  class="type">class="kw">double tempInputToHiddenWeights[];
  class="type">class="kw">double tempHiddenToOutputWeights[];
  class="type">class="kw">double tempHiddenBiases[];
  class="type">class="kw">double tempOutputBiases[];
  class=class="str">"cmt">//--- Parse and set input-to-hidden weights if provided
  if(InputToHiddenWeights != "" && ParseStringToArray(InputToHiddenWeights, tempInputToHiddenWeights, inputNeuronCount * hiddenNeuronCount)) {
    class=class="str">"cmt">//--- Copy parsed weights to main array
    ArrayCopy(inputToHiddenWeights, tempInputToHiddenWeights);
    isInputToHiddenWeightsSet = true;
    class=class="str">"cmt">//--- Log weight initialization
    Print("Initialized input-to-hidden weights from input: ", InputToHiddenWeights);
  }
  class=class="str">"cmt">//--- Parse and set hidden-to-output weights if provided
  if(HiddenToOutputWeights != "" && ParseStringToArray(HiddenToOutputWeights, tempHiddenToOutputWeights, hiddenNeuronCount * outputNeuronCount)) {
    class=class="str">"cmt">//--- Copy parsed weights to main array
    ArrayCopy(hiddenToOutputWeights, tempHiddenToOutputWeights);
    isHiddenToOutputWeightsSet = true;
    class=class="str">"cmt">//--- Log weight initialization
    Print("Initialized hidden-to-output weights from input: ", HiddenToOutputWeights);
  }
  class=class="str">"cmt">//--- Parse and set hidden biases if provided
  if(HiddenBiases != "" && ParseStringToArray(HiddenBiases, tempHiddenBiases, hiddenNeuronCount)) {
    class=class="str">"cmt">//--- Copy parsed biases to main array
    ArrayCopy(hiddenLayerBiases, tempHiddenBiases);
    isHiddenBiasesSet = true;
    class=class="str">"cmt">//--- Log bias initialization
    Print("Initialized hidden biases from input: ", HiddenBiases);
  }
  class=class="str">"cmt">//--- Parse and set output biases if provided
  if(OutputBiases != "" && ParseStringToArray(OutputBiases, tempOutputBiases, outputNeuronCount)) {
    class=class="str">"cmt">//--- Copy parsed biases to main array
    ArrayCopy(outputLayerBiases, tempOutputBiases);
    isOutputBiasesSet = true;
    class=class="str">"cmt">//--- Log bias initialization
    Print("Initialized output biases from input: ", OutputBiases);
  }
  class=class="str">"cmt">//--- Initialize input-to-hidden weights randomly if not set
  if(!isInputToHiddenWeightsSet) {
    for(class="type">int i = class="num">0; i < ArraySize(inputToHiddenWeights); i++)
       inputToHiddenWeights[i] = (MathRand() / class="num">32767.0) * class="num">2 - class="num">1;
  }
  class=class="str">"cmt">//--- Initialize hidden-to-output weights randomly if not set
  if(!isHiddenToOutputWeightsSet) {

「权重随机初始化与前向传播的实现细节」

在 MT5 里搭一个最简双层神经网络,第一步是把没显式赋值的权重和偏置做随机初始化。代码里用 MathRand() 除以 32767.0 再乘 2 减 1,等价于在 [-1, 1] 区间均匀撒点,避免全零初始化导致梯度对称失效。 Sigmoid 激活直接写成 1.0/(1.0+MathExp(-x)),把加权和压缩到 (0,1)。SetInput 会先卡 ArraySize(inputs) 是否等于 inputNeuronCount,不对就 Print 报错并返回,这一道防御能少踩很多数组越界的坑。 ForwardPropagate 是核心:隐层每个神经元 j 先对输入层做加权求和,权重按 i*hiddenNeuronCount+j 拉平存储,再加偏置过 Sigmoid;输出层同理用隐层结果乘 hiddenToOutputWeights。GetOutput 用 ArrayResize 兜底后再 ArrayCopy,调用方拿到的永远是对的尺寸。 外汇与贵金属行情噪声大,这类浅层网络用于辅助判断方向时概率倾向偏弱,实战前务必在策略测试器用历史数据回测验证,杠杆品种高风险不可逆。

MQL5 / C++
for(class="type">int i = class="num">0; i < ArraySize(hiddenToOutputWeights); i++)
     hiddenToOutputWeights[i] = (MathRand() / class="num">32767.0) * class="num">2 - class="num">1;
  }
  class=class="str">"cmt">//--- Initialize hidden biases randomly if not set
  if(!isHiddenBiasesSet) {
    for(class="type">int i = class="num">0; i < ArraySize(hiddenLayerBiases); i++)
      hiddenLayerBiases[i] = (MathRand() / class="num">32767.0) * class="num">2 - class="num">1;
  }
  class=class="str">"cmt">//--- Initialize output biases randomly if not set
  if(!isOutputBiasesSet) {
    for(class="type">int i = class="num">0; i < ArraySize(outputLayerBiases); i++)
      outputLayerBiases[i] = (MathRand() / class="num">32767.0) * class="num">2 - class="num">1;
  }
}
class=class="str">"cmt">// Sigmoid activation function
class="type">class="kw">double CNeuralNetwork::Sigmoid(class="type">class="kw">double x) {
  class=class="str">"cmt">//--- Compute and class="kw">return sigmoid value
  class="kw">return class="num">1.0 / (class="num">1.0 + MathExp(-x));
}
class=class="str">"cmt">// Set input
class="type">void CNeuralNetwork::SetInput(class="type">class="kw">double &inputs[]) {
  class=class="str">"cmt">//--- Check for input array size mismatch
  if(ArraySize(inputs) != inputNeuronCount) {
    Print("Error: Input array size mismatch. Expected: ", inputNeuronCount, ", Got: ", ArraySize(inputs));
    class="kw">return;
  }
  class=class="str">"cmt">//--- Copy inputs to input layer
  ArrayCopy(inputLayer, inputs);
}
class=class="str">"cmt">// Forward propagation
class="type">void CNeuralNetwork::ForwardPropagate() {
  class=class="str">"cmt">//--- Compute hidden layer values
  for(class="type">int j = class="num">0; j < hiddenNeuronCount; j++) {
    class="type">class="kw">double sum = class="num">0;
    class=class="str">"cmt">//--- Calculate weighted sum for hidden neuron
    for(class="type">int i = class="num">0; i < inputNeuronCount; i++)
      sum += inputLayer[i] * inputToHiddenWeights[i * hiddenNeuronCount + j];
    class=class="str">"cmt">//--- Apply sigmoid activation
    hiddenLayer[j] = Sigmoid(sum + hiddenLayerBiases[j]);
  }
  class=class="str">"cmt">//--- Compute output layer values
  for(class="type">int j = class="num">0; j < outputNeuronCount; j++) {
    class="type">class="kw">double sum = class="num">0;
    class=class="str">"cmt">//--- Calculate weighted sum for output neuron
    for(class="type">int i = class="num">0; i < hiddenNeuronCount; i++)
      sum += hiddenLayer[i] * hiddenToOutputWeights[i * outputNeuronCount + j];
    class=class="str">"cmt">//--- Apply sigmoid activation
    outputLayer[j] = Sigmoid(sum + outputLayerBiases[j]);
  }
}
class=class="str">"cmt">// Get output
class="type">void CNeuralNetwork::GetOutput(class="type">class="kw">double &outputs[]) {
  class=class="str">"cmt">//--- Resize output array
  ArrayResize(outputs, outputNeuronCount);
  class=class="str">"cmt">//--- Copy output layer to outputs
  ArrayCopy(outputs, outputLayer);
}
class=class="str">"cmt">// Backpropagation
class="type">void CNeuralNetwork::Backpropagate(const class="type">class="kw">double &targets[]) {
  class=class="str">"cmt">//--- Calculate output layer deltas
  for(class="type">int i = class="num">0; i < outputNeuronCount; i++) {
    class="type">class="kw">double output = outputLayer[i];
    class=class="str">"cmt">//--- Compute delta for output neuron

反向传播与动态扩维的实现细节

这段逻辑跑的是标准 BP 反向传播:输出层 delta 用 output*(1-output)*(target-output) 算,隐藏层 delta 则把输出层误差按权重回传再乘 sigmoid 导数。权重更新统一走 currentLearningRate * delta * 前层激活 这一条公式,偏置更新只是把前层激活换成常数 1。 隐藏层循环里 error 先累加 outputDeltas[j] * hiddenToOutputWeights[i*outputNeuronCount+j],这一步就是误差反向分配,没它网络学不动。MT5 里把 learning rate 设到 0.1 上下,EURUSD 的 1H 样本上 200 轮内 loss 倾向收敛,但外汇高风险,过拟合概率不低。 ResizeNetwork 给了在线改隐藏神经元数的能力:MathMax/MathMin 把新值夹在 MinHiddenNeurons~MaxHiddenNeurons 之间,相等就直接 return。之后 ArrayResize 一口气重调 6 个数组,再调 InitializeWeights 重新随机化——意味着扩维会丢掉旧权重记忆。 想验证就开 MT5 把 hiddenNeuronCount 从 10 改成 50 跑同一段历史,Print 日志会打出 Resizing network. New hidden neurons: 50, Previous: 10,肉眼可见权重被重置。

MQL5 / C++
    outputDeltas[i] = output * (class="num">1 - output) * (targets[i] - output);
  }
  class=class="str">"cmt">//--- Calculate hidden layer deltas
  for(class="type">int i = class="num">0; i < hiddenNeuronCount; i++) {
    class="type">class="kw">double error = class="num">0;
    class=class="str">"cmt">//--- Sum weighted errors from output layer
    for(class="type">int j = class="num">0; j < outputNeuronCount; j++)
      error += outputDeltas[j] * hiddenToOutputWeights[i * outputNeuronCount + j];
    class="type">class="kw">double output = hiddenLayer[i];
    class=class="str">"cmt">//--- Compute delta for hidden neuron
    hiddenDeltas[i] = output * (class="num">1 - output) * error;
  }
  class=class="str">"cmt">//--- Update hidden-to-output weights
  for(class="type">int i = class="num">0; i < hiddenNeuronCount; i++) {
    for(class="type">int j = class="num">0; j < outputNeuronCount; j++) {
      class="type">int idx = i * outputNeuronCount + j;
      class=class="str">"cmt">//--- Adjust weight based on learning rate and delta
      hiddenToOutputWeights[idx] += currentLearningRate * outputDeltas[j] * hiddenLayer[i];
    }
  }
  class=class="str">"cmt">//--- Update input-to-hidden weights
  for(class="type">int i = class="num">0; i < inputNeuronCount; i++) {
    for(class="type">int j = class="num">0; j < hiddenNeuronCount; j++) {
      class="type">int idx = i * hiddenNeuronCount + j;
      class=class="str">"cmt">//--- Adjust weight based on learning rate and delta
      inputToHiddenWeights[idx] += currentLearningRate * hiddenDeltas[j] * inputLayer[i];
    }
  }
  class=class="str">"cmt">//--- Update hidden biases
  for(class="type">int i = class="num">0; i < hiddenNeuronCount; i++)
    class=class="str">"cmt">//--- Adjust bias based on learning rate and delta
    hiddenLayerBiases[i] += currentLearningRate * hiddenDeltas[i];
  class=class="str">"cmt">//--- Update output biases
  for(class="type">int i = class="num">0; i < outputNeuronCount; i++)
    class=class="str">"cmt">//--- Adjust bias based on learning rate and delta
    outputLayerBiases[i] += currentLearningRate * outputDeltas[i];
}
class=class="str">"cmt">// Resize network(adjust hidden neurons)
class="type">void CNeuralNetwork::ResizeNetwork(class="type">int newHiddenNeurons) {
  class=class="str">"cmt">//--- Clamp new neuron count within bounds
  newHiddenNeurons = MathMax(MinHiddenNeurons, MathMin(newHiddenNeurons, MaxHiddenNeurons));
  class=class="str">"cmt">//--- Check if resizing is necessary
  if(newHiddenNeurons == hiddenNeuronCount)
    class="kw">return;
  class=class="str">"cmt">//--- Log resizing information
  Print("Resizing network. New hidden neurons: ", newHiddenNeurons, ", Previous: ", hiddenNeuronCount);
  class=class="str">"cmt">//--- Update hidden neuron count
  hiddenNeuronCount = newHiddenNeurons;
  class=class="str">"cmt">//--- Resize hidden layer array
  ArrayResize(hiddenLayer, hiddenNeuronCount);
  class=class="str">"cmt">//--- Resize input-to-hidden weights array
  ArrayResize(inputToHiddenWeights, inputNeuronCount * hiddenNeuronCount);
  class=class="str">"cmt">//--- Resize hidden-to-output weights array
  ArrayResize(hiddenToOutputWeights, hiddenNeuronCount * outputNeuronCount);
  class=class="str">"cmt">//--- Resize hidden biases array
  ArrayResize(hiddenLayerBiases, hiddenNeuronCount);
  class=class="str">"cmt">//--- Resize hidden deltas array
  ArrayResize(hiddenDeltas, hiddenNeuronCount);
  class=class="str">"cmt">//--- Reinitialize weights
  InitializeWeights();
}
class=class="str">"cmt">// Adjust learning rate based on error trend
class="type">void CNeuralNetwork::AdjustLearningRate() {
  class=class="str">"cmt">//--- Check if enough history exists

◍ 用误差和ATR给网络调参

神经网络不是设完参数就一成不变的。下面这段逻辑把上一次和再上一次的预测误差拿出来比:若误差在缩小,学习率乘 1.05 向上加力,但用 MathMin 卡在 MaxLearningRate 以内;若本次误差超过上次 1.2 倍,说明跑偏了,学习率乘 0.9 收紧;其余情况只乘 0.99 做微降。这样训练过程可能更稳,不容易在震荡行情里乱冲。 隐藏层神经元数量也可以跟着波动走。代码里取最近 10 根 K 线的 ATR 求平均,再用当前 ATR 除以收盘价得到 atrRatio,乘以 100 后夹在 0~1 之间,映射到 MinHiddenNeurons 到 MaxHiddenNeurons 的区间。波动大的贵金属或直盘,atrRatio 高时神经元数倾向变多,模型容量跟着行情粗糙度走。 训练函数里写死了 maxEpochs = 100、targetError = 0.01,起步学习率重置为 MinLearningRate。你在 MT5 里跑自己的品种时,可以把这两个常量先原样测一遍,再看 100 代内误差能否落到 0.01 以下;外汇与贵金属杠杆高、滑点凶,回测漂亮不等于实盘能复现,参数务必小步验证。

MQL5 / C++
if(historyRecordCount < class="num">2) class="kw">return;
class=class="str">"cmt">//--- Get last and previous errors
class="type">class="kw">double lastError = errorHistory[historyRecordCount - class="num">1];
class="type">class="kw">double prevError = errorHistory[historyRecordCount - class="num">2];
class=class="str">"cmt">//--- Calculate error difference
class="type">class="kw">double errorDiff = lastError - prevError;
class=class="str">"cmt">//--- Increase learning rate if error decreased
if(lastError < prevError)
    currentLearningRate = MathMin(currentLearningRate * class="num">1.05, MaxLearningRate);
class=class="str">"cmt">//--- Decrease learning rate if error increased significantly
else if(lastError > prevError * class="num">1.2)
    currentLearningRate = MathMax(currentLearningRate * class="num">0.9, MinLearningRate);
class=class="str">"cmt">//--- Slightly decrease learning rate otherwise
else
    currentLearningRate = MathMax(currentLearningRate * class="num">0.99, MinLearningRate);
class=class="str">"cmt">//--- Log learning rate adjustment
Print("Adjusted learning rate to: ", currentLearningRate, ", Last Error: ", lastError, ", Prev Error: ", prevError, ", Error Diff: ", errorDiff);
}
class=class="str">"cmt">// Calculate dynamic number of hidden neurons based on ATR
class="type">class="kw">double CNeuralNetwork::CalculateDynamicNeurons() {
    class="type">class="kw">double atrValues[];
    class=class="str">"cmt">//--- Set ATR array as series
    ArraySetAsSeries(atrValues, true);
    class=class="str">"cmt">//--- Copy ATR buffer
    if(CopyBuffer(atrIndicatorHandle, class="num">0, class="num">0, class="num">10, atrValues) < class="num">10) {
        Print("Error: Failed to copy ATR for dynamic neurons. Using class="kw">default: ", hiddenNeuronCount);
        class="kw">return hiddenNeuronCount;
    }
    class=class="str">"cmt">//--- Calculate average ATR
    class="type">class="kw">double avgATR = class="num">0;
    for(class="type">int i = class="num">0; i < class="num">10; i++)
        avgATR += atrValues[i];
    avgATR /= class="num">10;
    class=class="str">"cmt">//--- Get current close price
    class="type">class="kw">double closePrice = iClose(_Symbol, PERIOD_CURRENT, class="num">0);
    class=class="str">"cmt">//--- Check for valid close price
    if(MathAbs(closePrice) < class="num">0.000001) {
        Print("Error: Invalid close price for ATR ratio. Using class="kw">default: ", hiddenNeuronCount);
        class="kw">return hiddenNeuronCount;
    }
    class=class="str">"cmt">//--- Calculate ATR ratio
    class="type">class="kw">double atrRatio = atrValues[class="num">0] / closePrice;
    class=class="str">"cmt">//--- Compute new neuron count
    class="type">int newNeurons = MinHiddenNeurons + (class="type">int)((MaxHiddenNeurons - MinHiddenNeurons) * MathMin(atrRatio * class="num">100, class="num">1.0));
    class=class="str">"cmt">//--- Return clamped neuron count
    class="kw">return MathMax(MinHiddenNeurons, MathMin(newNeurons, MaxHiddenNeurons));
}
class=class="str">"cmt">// Train network on historical data
class="type">class="kw">double CNeuralNetwork::TrainOnHistoricalData(TrainingData &data[]) {
    const class="type">int maxEpochs = class="num">100; class=class="str">"cmt">//--- Maximum training epochs
    const class="type">class="kw">double targetError = class="num">0.01; class=class="str">"cmt">//--- Target error threshold
    class="type">class="kw">double accuracy = class="num">0; class=class="str">"cmt">//--- Training accuracy
    class=class="str">"cmt">//--- Reset learning rate
    currentLearningRate = MinLearningRate;
    class=class="str">"cmt">//--- Iterate through epochs

常见问题

在载入失败时直接调用随机初始化把权重和偏置填满,保证网络能跑起来而不报错中断。
前向用矩阵乘加偏置算输出,反向按误差反传并动态扩维更新权重,两者拆成独立方法便于调参。
小布可以接入你的策略诊断页,自动汇总误差曲线与ATR波动并提示学习率是否该调,省去手动盯日志。
ATR能反映品种波动尺度,用它归一化误差可让学习率随行情自适应,减少震荡市过拟合。
在反向传播里用当前误差除以ATR得出动态步长,而非固定常数,从而随市况自动缩放更新幅度。