基于通用 MLP 逼近器的EA·进阶篇
📘

基于通用 MLP 逼近器的EA·进阶篇

第 2/3 篇

多层感知机结构在MT5里的落地骨架

想在 MT5 里自己搭一个前馈神经网络,先得把网络结构用结构体钉死。下面这段定义把每一层神经元值塞进动态数组,再用一个总数组管理所有层,权重总数、层数、遍历游标都单独记——这是后续前向计算不越界的基础。 初始化函数 C_MLP::Init 干的第一件事是防御性校验:层数少于 2(没有输入和输出层)直接返回 0 并在日志抛 "Layers less than 2";任意一层神经元数 ≤0 也会返回 0 并报出具体第几层出错。 通过校验后,代码用 ArrayCopy 把配置存进 layerConf,按 layersCNT 扩出层数组,再逐层给 neurons 分配内存。权重总数 weightsCNT 初始为 0,随后对相邻层循环累加,这部分循环体虽被截断,但逻辑就是 (前层神经元数 × 后层神经元数 + 后层偏置数) 的连加。 外汇与贵金属行情用这类模型做信号推断时,过拟合和样本外失效风险极高,任何权重初始化后的网络都只是未训练空壳,实盘前必须自行喂数据回测。

MQL5 / C++
class="kw">struct S_Layer
{
  class="type">class="kw">double l [];    class=class="str">"cmt">// Neuron values
};
S_Layer layers [];    class=class="str">"cmt">// Array of all network layers
class="type">int     weightsCNT;   class=class="str">"cmt">// Total number of weights in the network(including biases)
class="type">int     layersCNT;    class=class="str">"cmt">// Total number of layers(including input and output ones)
class="type">int     cnt_W;        class=class="str">"cmt">// Current index in the weights array when traversing the network
class="type">class="kw">double  temp;         class=class="str">"cmt">// Temporary variable to store the sum of the weighted inputs
class=class="str">"cmt">// Calculate values of one layer of the network
class="type">void LayerCalc(class="type">class="kw">double  &inLayer  [], class=class="str">"cmt">// values of neurons of the previous layer
                class="type">class="kw">double  &weights  [], class=class="str">"cmt">// array of weights and biases of the entire network
                class="type">class="kw">double  &outLayer [], class=class="str">"cmt">// array for writing values of the current layer
                class="kw">const class="type">int inSize,     class=class="str">"cmt">// number of neurons in the input layer
                class="kw">const class="type">int outSize);   class=class="str">"cmt">// outSize  - number of neurons in the output layer
};
class=class="str">"cmt">//+----------------------------------------------------------------------------+
class=class="str">"cmt">//| Initialize the network                                                      |
class=class="str">"cmt">//| layerConfig - array with the number of neurons in each layer                |
class=class="str">"cmt">//| Returns the total number of weights needed, or class="num">0 in case of an error        |
class=class="str">"cmt">//+----------------------------------------------------------------------------+
class="type">int C_MLP::Init(class="type">int &layerConfig [])
{
  class=class="str">"cmt">// Check that the network has at least class="num">2 layers(input and output)
  layersCNT = ArraySize(layerConfig);
  if (layersCNT < class="num">2)
  {
    Print("Error Net config! Layers less than class="num">2!");
    class="kw">return class="num">0;
  }
  class=class="str">"cmt">// Check that each layer has at least class="num">1 neuron
  for (class="type">int i = class="num">0; i < layersCNT; i++)
  {
    if (layerConfig [i] <= class="num">0)
    {
      Print("Error Net config! Layer No." + class="type">class="kw">string (i + class="num">1) + " contains class="num">0 neurons!");
      class="kw">return class="num">0;
    }
  }
  class=class="str">"cmt">// Save network configuration
  ArrayCopy(layerConf, layerConfig, class="num">0, class="num">0, WHOLE_ARRAY);
  class=class="str">"cmt">// Create an array of layers
  ArrayResize(layers, layersCNT);
  class=class="str">"cmt">// Allocate memory for neurons of each layer
  for (class="type">int i = class="num">0; i < layersCNT; i++)
  {
    ArrayResize(layers [i].l, layerConfig [i]);
  }
  class=class="str">"cmt">// Calculate the total number of weights in the network
  weightsCNT = class="num">0;
  for (class="type">int i = class="num">0; i < layersCNT - class="num">1; i++)
  {

◍ 单层前向传播与权重布局的实算

构建一个多层感知机时,权重总数不是简单相乘。代码里用 weightsCNT += layerConf[i] * layerConf[i+1] + layerConf[i+1] 累加:每一层向下一层全连接,需要「本层神经元数 × 下层神经元数」条连接权重,再加下层每个神经元一个偏置。比如三层配置 [10, 5, 1],权重总量 = 10*5+5 + 5*1+1 = 61。 单层计算在 LayerCalc 里完成,核心就是 y = tanh(bias + Σ w*u)。循环先取当前神经元的偏置 weights[cnt_W],再遍历输入层每个神经元做加权累加,最后套双曲正切。 激活函数写成 2.0/(1.0+exp(-temp))-1.0,输出被压进 [-1, 1] 区间。对外汇或贵金属信号做归一化时,这个有界输出比无界线性层更不容易在极端波动下炸数值,但模型本身不预测方向,只是拟合历史样本,实盘仍属高风险。 ANN 方法负责串起整网:cnt_W 归零后,ArrayCopy 把行情特征塞进第 0 层,再逐层调 LayerCalc 直到输出层。开 MT5 把这段直接挂进 EA,改 layerConf 数组就能验证不同网络宽度对回测拟合的影响。

MQL5 / C++
class=class="str">"cmt">// For each neuron of the next layer we need:
class=class="str">"cmt">// - one bias value
class=class="str">"cmt">// - weights for connections with all neurons of the current layer
weightsCNT += layerConf [i] * layerConf [i + class="num">1] + layerConf [i + class="num">1];
}
class="kw">return weightsCNT;
}
class=class="str">"cmt">//+----------------------------------------------------------------------------+
class=class="str">"cmt">//| Calculate values of one layer of the network                                |
class=class="str">"cmt">//| Implement the equation: y = tanh(bias + w1*x1 + w2*x2 + ... + wn*xn)        |
class=class="str">"cmt">//+----------------------------------------------------------------------------+
class="type">void C_MLP::LayerCalc(class="type">class="kw">double&    inLayer  [],
                       class="type">class="kw">double&    weights  [],
                       class="type">class="kw">double&    outLayer [],
                       class="kw">const class="type">int  inSize,
                       class="kw">const class="type">int  outSize)
{
  class=class="str">"cmt">// Calculate the value for each neuron in the output layer
  for (class="type">int i = class="num">0; i < outSize; i++)
  {
    class=class="str">"cmt">// Start with the bias value for the current neuron
    temp = weights [cnt_W];
    cnt_W++;
    class=class="str">"cmt">// Add weighted inputs from each neuron in the previous layer
    for (class="type">int u = class="num">0; u < inSize; u++)
    {
      temp += inLayer [u] * weights [cnt_W];
      cnt_W++;
    }
    class=class="str">"cmt">// Apply the "hyperbolic tangent" activation function
    class=class="str">"cmt">// f(x) = class="num">2/(class="num">1 + e^(-x)) - class="num">1
    class=class="str">"cmt">// Range of values f(x): [-class="num">1, class="num">1]
    outLayer [i] = class="num">2.0 / (class="num">1.0 + exp(-temp)) - class="num">1.0;
  }
}
class=class="str">"cmt">//+----------------------------------------------------------------------------+
class=class="str">"cmt">//| Calculate the values of all layers sequentially from input to output        |
class=class="str">"cmt">//+----------------------------------------------------------------------------+
class="type">void C_MLP::ANN(class="type">class="kw">double &inLayer  [],   class=class="str">"cmt">// input values
                 class="type">class="kw">double &weights  [],   class=class="str">"cmt">// network weights(including biases)
                 class="type">class="kw">double &outLayer [])   class=class="str">"cmt">// output layer values
{
  class=class="str">"cmt">// Reset the weight counter before starting the pass
  cnt_W = class="num">0;
  class=class="str">"cmt">// Copy the input data to the first layer of the network
  ArrayCopy(layers [class="num">0].l, inLayer, class="num">0, class="num">0, WHOLE_ARRAY);
  class=class="str">"cmt">// Calculate the values of each layer sequentially
  for (class="type">int i = class="num">0; i < layersCNT - class="num">1; i++)
  {
    LayerCalc(layers    [i].l,        class=class="str">"cmt">// output of the previous layer
               weights,                class=class="str">"cmt">// network weights(including bias)
               layers    [i + class="num">1].l,    class=class="str">"cmt">// next layer
               layerConf [i],          class=class="str">"cmt">// size of current layer

「EA 输入参数与指标句柄的初始化落点」

这段 MT5 代码把多层感知机(MLP)交易 EA 的外部参数和内部句柄一次性铺开,重点在 input 分组与 OnInit 里的指标挂载。交易参数块里 Lot_P 默认 0.01 手,交易时段被锁在 StartTradeH_P=3 到 EndTradeH_P=12(服务器小时),避开了流动性最稀薄的亚盘尾段。 训练侧给的是典型离线配置:NumbTestFuncRuns_P=5000 次函数评估、MLPstructure_P="1|1" 表示单隐藏层结构、DepthHistoryBars_P=10000 根历史棒做训练样本、RetrainingPeriod_P=12 小时强制重训一次,SigThr_P=0.5 是网络输出越过即触发的信号阈值。外汇与贵金属杠杆高,这类默认仓位和阈值直接实盘可能放大回撤,建议先开 MT5 策略测试器用 EURUSD 15M 跑一遍再调。 OnInit 中只挂了两个输入特征源:iStochastic(_Symbol,PERIOD_CURRENT,5,3,3,MODE_EMA,STO_LOWHIGH) 与 iRSI(_Symbol,PERIOD_CURRENT,14,PRICE_TYPICAL)。随机指标用 5 周期快线、3 平滑,RSI 取典型价的 14 周期——这两个句柄后续会喂给 BarsAnalysis_P=3 根棒拼成的输入向量。

别把默认结构当最优解。MLPstructure_P 写成 "11" 只是最小验证用例,真要做贵金属波段,hidden 层加到 "462" 才可能吃到非线性边界,但 5000 次评估在 10000 根历史上大概率欠拟合。
MQL5 / C++
class="macro">#include "class="macro">#Symbol.mqh"
class="macro">#include <Math\AOs\Utilities.mqh>
class="macro">#include <Math\AOs\NeuroNets\MLP.mqh>
class="macro">#include <Math\AOs\PopulationAO\class="macro">#C_AO_enum.mqh>
class=class="str">"cmt">//------------------------------------------------------------------------------
input group      "---Trade parameters-------------------";
input class="type">class="kw">double    Lot_P              = class="num">0.01;   class=class="str">"cmt">// Position volume
input class="type">int       StartTradeH_P      = class="num">3;      class=class="str">"cmt">// Trading start time
input class="type">int       EndTradeH_P        = class="num">12;     class=class="str">"cmt">// Trading end time
input group      "---Training parameters----------------";
input E_AO      OptimizerSelect_P  = AO_CLA; class=class="str">"cmt">// Select optimizer
input class="type">int       NumbTestFuncRuns_P = class="num">5000;   class=class="str">"cmt">// Total number of function runs
input class="type">class="kw">string    MLPstructure_P     = "class="num">1|class="num">1";  class=class="str">"cmt">// Hidden layers, <class="num">4|class="num">6|class="num">2> - three hidden layers
input class="type">int       BarsAnalysis_P     = class="num">3;      class=class="str">"cmt">// Number of bars to analyze
input class="type">int       DepthHistoryBars_P = class="num">10000;  class=class="str">"cmt">// History depth for training in bars
input class="type">int       RetrainingPeriod_P = class="num">12;     class=class="str">"cmt">// Duration in hours of the model&class="macro">#x27;s relevance
input class="type">class="kw">double    SigThr_P           = class="num">0.5;    class=class="str">"cmt">// Signal threshold
class=class="str">"cmt">//------------------------------------------------------------------------------
C_AO_Utilities U;
C_MLP          NN;
class="type">int            InpSigNumber;
class="type">int            WeightsNumber;
class="type">class="kw">double         Inputs  [];
class="type">class="kw">double         Weights [];
class="type">class="kw">double         Outs    [class="num">1];
class="type">class="kw">datetime       LastTrainingTime = class="num">0;
C_Symbol       S;
C_NewBar       B;
class="type">int            HandleS;
class="type">int            HandleR;
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">int OnInit()
{
  class=class="str">"cmt">//----------------------------------------------------------------------------
  class=class="str">"cmt">// Initializing indicators: Stochastic and RSI
  HandleS = iStochastic(_Symbol, PERIOD_CURRENT, class="num">5, class="num">3, class="num">3, MODE_EMA, STO_LOWHIGH);
  HandleR = iRSI(_Symbol, PERIOD_CURRENT, class="num">14, PRICE_TYPICAL);

MLP 结构初始化与权重随机 seeding

把分析窗口换算成网络输入维度时,代码用 BarsAnalysis_P * 2 + BarsAnalysis_P * 4 直接算出 InpSigNumber。若 BarsAnalysis_P 取 30,输入节点就是 180,Print 会吐出 "Number of network logins : 180",开 MT5 跑这段能立刻核对自己的窗口参数有没有算错。 MLP 隐层靠 '|' 分隔的字符串解析,StringSplit 拿到 layersNumb 后若小于 1 直接 INIT_FAILED,说明配置里至少得写一层隐层。代码随后把数组扩成 layersNumb+2,下标 0 塞输入数、末位塞 1(单输出),中间循环把字符串转成每隐层神经元数,任一层 ≤0 同样报错退初始化。 NN.Init 返回权重总数,权重数组按 WeightsNumber 扩容,并用 rand()/32767.0 做 [-1,1] 均匀随机初始化——这只是调试用的 naive seeding,真实训练前会被覆盖。最后 Print 的 "Number of network parameters" 就是后续要优化的自由参数量,例如 180 输入+隐层 50+输出 1 的全连接结构,权重数会明显大于 230,外汇与贵金属品种用此类模型属高风险,过拟合概率偏高。 交易与 K 线对象以 _Symbol 和 PERIOD_CURRENT 绑定后返回 INIT_SUCCEEDED,EA 才算真正挂上当前图表。改 BarsAnalysis_P 或 MLPstructure_P 任意一个,都要重看 Print 出来的层数与参数总量是否匹配预期。

MQL5 / C++
  class=class="str">"cmt">// Calculate the number of inputs to the neural network based on the number of bars to analyze
  InpSigNumber = BarsAnalysis_P * class="num">2 + BarsAnalysis_P * class="num">4;
  class=class="str">"cmt">// Display information about the number of inputs
  Print("Number of network logins  : ", InpSigNumber);
  class=class="str">"cmt">//----------------------------------------------------------------------------
  class=class="str">"cmt">// Initialize the structure of the multilayer MLP
  class="type">class="kw">string sepResult [];
  class="type">int layersNumb = StringSplit(MLPstructure_P, StringGetCharacter("|", class="num">0), sepResult);
  class=class="str">"cmt">// Check if the number of hidden layers is greater than class="num">0
  if (layersNumb < class="num">1)
  {
    Print("Network configuration error, hidden layers < class="num">1...");
    class="kw">return INIT_FAILED; class=class="str">"cmt">// Return initialization error
  }
  class=class="str">"cmt">// Increase the number of layers by class="num">2 (input and output)
  layersNumb += class="num">2;
  class=class="str">"cmt">// Initialize array for neural network configuration
  class="type">int nnConf [];
  ArrayResize(nnConf, layersNumb);
  class=class="str">"cmt">// Set the number of inputs and outputs in the network configuration
  nnConf [class="num">0] = InpSigNumber;  class=class="str">"cmt">// Input layer
  nnConf [layersNumb - class="num">1] = class="num">1; class=class="str">"cmt">// Output layer
  class=class="str">"cmt">// Filling the hidden layers configuration
  for (class="type">int i = class="num">1; i < layersNumb - class="num">1; i++)
  {
    nnConf [i] = (class="type">int)StringToInteger(sepResult [i - class="num">1]); class=class="str">"cmt">// Convert a class="type">class="kw">string value to an integer
    class=class="str">"cmt">// Check that the number of neurons in a layer is greater than class="num">0
    if (nnConf [i] < class="num">1)
    {
      Print("Network configuration error, in layer ", i, " <= class="num">0 neurons...");
      class="kw">return INIT_FAILED; class=class="str">"cmt">// Return initialization error
    }
  }
  class=class="str">"cmt">// Initialize the neural network and get the number of weights
  WeightsNumber = NN.Init(nnConf);
  if (WeightsNumber <= class="num">0)
  {
    Print("Error initializing MLP network...");
    class="kw">return INIT_FAILED; class=class="str">"cmt">// Return initialization error
  }
  class=class="str">"cmt">// Resize the input array and weights
  ArrayResize(Inputs,  InpSigNumber);
  ArrayResize(Weights, WeightsNumber);
  class=class="str">"cmt">// Initialize weights with random values in the range [-class="num">1, class="num">1] (for debugging)
  for (class="type">int i = class="num">0; i < WeightsNumber; i++)
      Weights [i] = class="num">2 * (rand() / class="num">32767.0) - class="num">1;
  class=class="str">"cmt">// Output network configuration information
  Print("Number of all layers     : ", layersNumb);
  Print("Number of network parameters: ", WeightsNumber);
  class=class="str">"cmt">//----------------------------------------------------------------------------
  class=class="str">"cmt">// Initialize the trade and bar classes
  S.Init(_Symbol);
  B.Init(_Symbol, PERIOD_CURRENT);
  class="kw">return (INIT_SUCCEEDED); class=class="str">"cmt">// Return successful initialization result
}

◍ 定时重训与每根新K线的特征归一化

EA 用 RetrainingPeriod_P 控制重训节奏:当当前时间距离上次训练已超过该参数乘以 3600 秒(即小时数转秒),就触发 Training() 并重写 LastTrainingTime;训练失败仅打印错误并直接 return,不往下走。这一步若设得太短,CPU 占用会明显抬升,外汇与贵金属的高波动时段跑模型要留意算力成本。 非重训分支里,先靠 B.IsNewBar() 拦截,只在每根 K 线开盘后处理一次,避免 tick 级重复计算。随后用 CopyRates 取 BarsAnalysis_P 根历史 K 线,再用 CopyBuffer 分别抓 Stochastic 主线与 RSI 各 BarsAnalysis_P 个值,任一带回数量不符就退出。 归一化是这套 NN 输入的关键:先扫一遍 high/low 找全样本 max/min,再把每根 K 线的 open/high/low/close 以及 sto、rsi 都通过 U.Scale 压到 [-1,1] 区间,其中 sto 与 rsi 的固定上下界是 0 和 100。若你改了指标周期,这一步的边界常量要同步核对。 喂完 Inputs 后调 NN.ANN 得到 Outs,当 Outs[0] 大于 SigThr_P 时 signal 置 1,作为做多倾向的触发条件。SigThr_P 越靠近 1,信号越保守,实盘前建议在 MT5 策略测试器里用不同阈值跑一遍命中率。

MQL5 / C++
  if (TimeCurrent() - LastTrainingTime >= RetrainingPeriod_P * class="num">3600)
  {
    class=class="str">"cmt">// Start the neural network training
    if (Training()) LastTrainingTime = TimeCurrent(); class=class="str">"cmt">// Update last training time
    else              Print("Training error...");      class=class="str">"cmt">// Display an error message
    class="kw">return; class=class="str">"cmt">// Complete function execution
  }
  class=class="str">"cmt">//----------------------------------------------------------------------------
  class=class="str">"cmt">// Check if the current tick is the start of a new bar
  if (!B.IsNewBar()) class="kw">return;
  class=class="str">"cmt">//----------------------------------------------------------------------------
  class=class="str">"cmt">// Declare arrays to store price and indicator data
  class="type">MqlRates rates [];
  class="type">class="kw">double   rsi   [];
  class="type">class="kw">double   sto   [];
  class=class="str">"cmt">// Get price data
  if (CopyRates(_Symbol, PERIOD_CURRENT, class="num">1, BarsAnalysis_P, rates) != BarsAnalysis_P) class="kw">return;
  class=class="str">"cmt">// Get Stochastic values
  if (CopyBuffer(HandleS, class="num">0, class="num">1, BarsAnalysis_P, sto) != BarsAnalysis_P) class="kw">return;
  class=class="str">"cmt">// Get RSI values
  if (CopyBuffer(HandleR, class="num">0, class="num">1, BarsAnalysis_P, rsi) != BarsAnalysis_P) class="kw">return;
  class=class="str">"cmt">// Initialize variables to normalize data
  class="type">int wCNT   = class="num">0;
  class="type">class="kw">double max = -DBL_MAX; class=class="str">"cmt">// Initial value for maximum
  class="type">class="kw">double min =  DBL_MAX; class=class="str">"cmt">// Initial value for minimum
  class=class="str">"cmt">// Find the maximum and minimum among high and low
  for (class="type">int b = class="num">0; b < BarsAnalysis_P; b++)
  {
    if (rates [b].high > max) max = rates [b].high; class=class="str">"cmt">// Update the maximum
    if (rates [b].low  < min) min = rates [b].low;  class=class="str">"cmt">// Update the minimum
  }
  class=class="str">"cmt">// Normalization of input data for neural network
  for (class="type">int b = class="num">0; b < BarsAnalysis_P; b++)
  {
    Inputs [wCNT] = U.Scale(rates [b].high,  min, max, -class="num">1, class="num">1); wCNT++; class=class="str">"cmt">// Normalizing high
    Inputs [wCNT] = U.Scale(rates [b].low,   min, max, -class="num">1, class="num">1); wCNT++; class=class="str">"cmt">// Normalizing low
    Inputs [wCNT] = U.Scale(rates [b].open,  min, max, -class="num">1, class="num">1); wCNT++; class=class="str">"cmt">// Normalizing open
    Inputs [wCNT] = U.Scale(rates [b].close, min, max, -class="num">1, class="num">1); wCNT++; class=class="str">"cmt">// Normalizing close
    Inputs [wCNT] = U.Scale(sto   [b],      class="num">0,   class="num">100, -class="num">1, class="num">1); wCNT++; class=class="str">"cmt">// Normalizing Stochastic
    Inputs [wCNT] = U.Scale(rsi   [b],      class="num">0,   class="num">100, -class="num">1, class="num">1); wCNT++; class=class="str">"cmt">// Normalizing RSI
  }
  class=class="str">"cmt">// Convert data from Inputs to Outs
  NN.ANN(Inputs, Weights, Outs);
  class=class="str">"cmt">//----------------------------------------------------------------------------
  class=class="str">"cmt">// Generate a trading signal based on the output of a neural network
  class="type">int signal = class="num">0;
  if (Outs [class="num">0] >  SigThr_P) signal =  class="num">1; class=class="str">"cmt">// Buy signal

「用历史K线喂给优化器找权重」

信号判定之后,真正决定策略泛化能力的是参数权重。Training() 函数先把当前品种、当前周期的历史数据拉进来:CopyRates 从偏移 1 根开始取 DepthHistoryBars_P 根,假设设成 5000,终端日志会打印 'Training on history of 5000 bars',这就是可验证的数据点。 RSI 与随机指标各自用 CopyBuffer 取主缓冲区,若返回根数不等于 bars 直接 return false,说明历史深度对齐失败,常见于品种休市缺口。 时间过滤不是只在实盘做,回测同样按 StartTradeH_P 到 EndTradeH_P 逐根打标签写进 truTradeTime[],非交易时段不参与适应度计算。 优化骨架用固定种群:popSize = 50,epochCount = NumbTestFuncRuns_P / 50。若总运行次数设 20000,则迭代 400 代。WeightsNumber 个权重各自分配 min/max/step 边界数组,循环里逐个初始化,下一步就是丢进进化算法搜最优组合。外汇与贵金属杠杆高,历史权重优异不代表实盘能复现,开 MT5 把 DepthHistoryBars_P 调小先跑通再放大。

MQL5 / C++
class="type">bool Training()
{
   class="type">MqlRates rates [];
   class="type">class="kw">double   rsi   [];
   class="type">class="kw">double   sto   [];
   class="type">int bars = CopyRates(_Symbol, PERIOD_CURRENT, class="num">1, DepthHistoryBars_P, rates);
   Print("Training on history of ", bars, " bars");
   if (CopyBuffer(HandleS, class="num">0, class="num">1, DepthHistoryBars_P, sto) != bars) class="kw">return class="kw">false;
   if (CopyBuffer(HandleR, class="num">0, class="num">1, DepthHistoryBars_P, rsi) != bars) class="kw">return class="kw">false;
   class="type">MqlDateTime time;
   class="type">bool truTradeTime []; ArrayResize(truTradeTime, bars); ArrayInitialize(truTradeTime, class="kw">false);
   for (class="type">int i = class="num">0; i < bars; i++)
   {
      TimeToStruct(rates [i].time, time);
      if (time.hour >= StartTradeH_P && time.hour < EndTradeH_P) truTradeTime [i] = true;
   }
   class="type">int popSize            = class="num">50;                     class=class="str">"cmt">// Population size for optimization algorithm
   class="type">int epochCount         = NumbTestFuncRuns_P / popSize; class=class="str">"cmt">// Total number of epochs(iterations) for optimization
   class="type">class="kw">double rangeMin [], rangeMax [], rangeStep [];     class=class="str">"cmt">// Arrays for storing the parameters&class="macro">#x27; boundaries and steps
   ArrayResize(rangeMin,  WeightsNumber);           class=class="str">"cmt">// Resize &class="macro">#x27;min&class="macro">#x27; borders array
   ArrayResize(rangeMax,  WeightsNumber);           class=class="str">"cmt">// Resize &class="macro">#x27;max&class="macro">#x27; borders array
   ArrayResize(rangeStep, WeightsNumber);           class=class="str">"cmt">// Resize the steps array
   for (class="type">int i = class="num">0; i < WeightsNumber; i++)
   {

常见问题

先用数组存每层神经元与权重矩阵,明确输入层、隐藏层、输出层维度,再把前向传播和权重初始化拆成独立函数方便后续调用。
权重按[上层索引][下层索引]二维数组布局,偏置单列,计算时逐神经元做加权求和再加偏置并通过激活函数,注意数组越界。
小布可替你跑特征归一化与定时重训的监控提醒,并把历史K线喂优化器的流程做成可复用模板,你只管调结构和看信号。
放在OnInit里创建指标句柄并读取输入参数,失败直接返回INIT_FAILED,避免后续Tick里重复申请资源。
行情尺度漂移会让旧归一化失效,每根新K线重算均值方差可避免输入突变导致MLP输出失真,提升泛化概率。