MQL5交易策略自动化(第二十一部分):借助自适应学习率提升神经网络交易效果·进阶篇
「神经网络类的骨架与字符串解析入口」
在 MT5 里手搓一个前馈神经网络,第一步是把层结构和权重容器先声明清楚。下面这段类定义把输入/隐藏/输出三层神经元数量、各层数值数组、层间权重与偏置、以及训练过程中的 delta 和误差历史全部列为私有成员,方便后续反向传播时直接按索引读写。 训练样本往往从 CSV 或文本框里以逗号分隔的字符串灌进来,ParseStringToArray 就是这道闸门。它先用 StringSplit 以 ASCII 44(即逗号)切分,若切出数量与 expectedSize 不符直接返回 false 并打印错误码,避免脏数据进网络。 一个容易忽略的细节:解析后对每个值做了 [-1.0, 1.0] 的钳制。外汇和贵金属特征量纲差异大,归一化到该区间能降低梯度爆炸概率,但输入本身若超标说明预处理有误,高风险品种上更该在喂数据前自查。 把下面代码直接贴进 MT5 的 MQ5 文件,改 expectedSize 就能适配你自己的特征维度,先跑通解析再接训练循环。
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。外汇与贵金属波动剧烈,这类自适应网络也只是概率工具,实盘前务必用历史数据验证过拟合倾向。
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()) 打断规律性。 外汇与贵金属行情噪声大,用随机初始化权重训出来的轻量网络过拟合概率偏高,实盘前务必用样本外数据验证。
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,调用方拿到的永远是对的尺寸。 外汇与贵金属行情噪声大,这类浅层网络用于辅助判断方向时概率倾向偏弱,实战前务必在策略测试器用历史数据回测验证,杠杆品种高风险不可逆。
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,肉眼可见权重被重置。
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 以下;外汇与贵金属杠杆高、滑点凶,回测漂亮不等于实盘能复现,参数务必小步验证。
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