神经网络实验(第 5 部分):常规化传输到神经网络的输入参数·进阶篇
📘

神经网络实验(第 5 部分):常规化传输到神经网络的输入参数·进阶篇

第 2/3 篇

◍ 均值方差与差分在序列预处理里的落地

把一段价格序列送进统计函数之前,先算清楚均值和标准差,才能做后续的 Z 值标准化。下面这段 MQL5 把标准差拆成先求均值、再累加偏差平方、最后开方三步,逻辑直接对应样本总体标准差公式。 [CODE] double ArrayStdDev(double &array[], int start_pos=0, int count=-1) { double mean = ArrayAverage(array, start_pos, count); double sum = 0.0; int size = ArraySize(array); int end_pos = count < 0 ? size - 1 : start_pos + count - 1; end_pos = end_pos >= size ? size - 1 : end_pos; for(int i = start_pos; i <= end_pos; i++) { sum += MathPow(array[i] - mean, 2); } double std_dev = MathSqrt(sum / (end_pos - start_pos + 1)); return (std_dev); } [/CODE] 逐行看:ArrayAverage 先拿到区间均值;end_pos 处理 count 为负时默认取到末尾,并做了越界保护;循环里 MathPow(x,2) 就是偏差平方;分母用 end_pos-start_pos+1 即样本个数,不是 ArraySize 全局长度,这点在外汇 H1 这类局部窗口计算时容易写错。 实盘日志里 EURUSD,H1 上跑过一组 {1.2,2.3,3.4,4.5} 的测试,Z 标准化结果分别是 -1.34164、-0.44721、0.44721、1.34164,对称分布说明均值恰在中点。外汇和贵金属波动有跳空,这种标准化只是描述历史离散度,对后市方向只提供概率参考,杠杆品种高风险不变。 另一路预处理是差分,公式 y'(t)=y(t)-y(t-1),用来抹掉趋势、提取边际变化。OnInit 里那段把 {1.2,2.3,3.4,4.5,5.6} 差分后每项恒为 1.1,Print 出来就能在 MT5 专家日志直接核对。开 MT5 新建脚本粘进去,改 data_array 为你拉的收盘价缓冲,立刻能看到自己品种的微分形态。

MQL5 / C++
class="type">class="kw">double ArrayStdDev(class="type">class="kw">double &array[], class="type">int start_pos=class="num">0, class="type">int count=-class="num">1)
{
  class="type">class="kw">double mean = ArrayAverage(array, start_pos, count);
  class="type">class="kw">double sum = class="num">0.0;
  class="type">int size = ArraySize(array);
  class="type">int end_pos = count < class="num">0 ? size - class="num">1 : start_pos + count - class="num">1;
  end_pos = end_pos >= size ? size - class="num">1 : end_pos;
  for(class="type">int i = start_pos; i <= end_pos; i++) {
    sum += MathPow(array[i] - mean, class="num">2);
  }
  class="type">class="kw">double std_dev = MathSqrt(sum / (end_pos - start_pos + class="num">1));
  class="kw">return (std_dev);
}

class="type">int OnInit()
  {
  class="type">class="kw">double data_array[] = {class="num">1.2, class="num">2.3, class="num">3.4, class="num">4.5, class="num">5.6};
  class="type">class="kw">double diff_array[ArraySize(data_array)];
  for(class="type">int i = class="num">0; i < ArraySize(data_array)-class="num">1; i++) {
    diff_array[i] = data_array[i+class="num">1] - data_array[i];
  }
  for(class="type">int i = class="num">0; i < ArraySize(data_array)-class="num">1; i++) {
    Print("Source array: ", data_array[i]);
    Print("Differentiation result: ", diff_array[i]);
  }
  class="kw">return(INIT_SUCCEEDED);
  }

对数变换压平价格序列的曲率

把 EURUSD 的 H1 收盘价序列直接做差分,得到的不是等距台阶:源数组 1.2→2.3→3.4→4.5,差分结果在 1.0999999999999999 到 1.0999999999999996 之间抖动,浮点误差暴露了线性处理的毛刺。 换用常用对数变换后,同样的 1.2 映射为 0.07918124604762482,2.3 映射为 0.36172783601759284,序列被压缩进更窄的动态区间,后续做波动率比较时尺度更统一。 下面这段 MT5 脚本在 OnInit 里跑通了两种处理,你复制进 EA 模板就能在 Experts 日志里对照看。外汇与贵金属杠杆高,对数变换只改表征、不改方向概率,实盘前先在策略测试器回放。

MQL5 / C++
class="type">int OnInit()
  {
class=class="str">"cmt">// declare and initialize the array
   class="type">class="kw">double data_array[] = {class="num">1.2, class="num">2.3, class="num">3.4, class="num">4.5, class="num">5.6};
class=class="str">"cmt">// create an array to store the normalization result
   class="type">class="kw">double norm_array[ArraySize(data_array)];
class=class="str">"cmt">// normalize the array
   LogTransform(data_array, norm_array);
class=class="str">"cmt">// display the result
   for(class="type">int i = class="num">0; i < ArraySize(data_array)-class="num">1; i++) {
      Print("Source array: ", data_array[i]);
      Print("Logarithmic transformation result: ", norm_array[i]);
   }
   class="kw">return(INIT_SUCCEEDED);
}
class="type">void LogTransform(class="type">class="kw">double& array1[], class="type">class="kw">double& array2[])
{
   class="type">int size = ArraySize(array1);
   for(class="type">int i = class="num">0; i < size; i++) {
      array2[i] = MathLog10(array1[i]);
   }
}

「日志里跑出来的对数变换实测」

在 EURUSD 的 H1 周期下,用 Expert Advisor 打印源数组与对数变换结果,能直接看到浮点计算的中间状态。上面这组 2023.04.07 14:21:22.374 的日志,是同一帧里连续两次变换的输出。 第一次源值为 4.5,对数变换结果落在 0.5314789170422551;紧接着第二次源值未显式打印但结果变为 0.6532125137753437,说明数组里第二项明显大于 4.5(按自然对数反推约为 1.92 的倍数关系)。 这种打印方式适合在 MT5 策略测试器里抓隐性数值漂移。外汇与贵金属杠杆高、点差跳变频繁,此类微差在极端行情中可能放大执行偏差,建议只作诊断用途、勿直接挂钩下单逻辑。

MQL5 / C++
class="num">2023.04.class="num">07 class="num">14:class="num">21:class="num">22.374 class="num">11111111111111 (EURUSD,H1)        Logarithmic transformation result: class="num">0.5314789170422551
class="num">2023.04.class="num">07 class="num">14:class="num">21:class="num">22.374 class="num">11111111111111 (EURUSD,H1)        Source array: class="num">4.5
class="num">2023.04.class="num">07 class="num">14:class="num">21:class="num">22.374 class="num">11111111111111 (EURUSD,H1)        Logarithmic transformation result: class="num">0.6532125137753437

◍ 清洗失真数据的四类实战手段

真实行情序列里混进尖刺、噪声或断点,会直接拉歪后续预测。MT5 自带的函数族能就地修这些毛病,关键看你怎么组合。 尖刺指偏离主体极远的点,多来自报价异常或录入错误。RemoveOutliers() 用均值±2倍标准差做边界,超界就填回均值;iqr() 按四分位距判定也常用,MODE_OUTLIERS 则基于决策层检测,可叠用。 平滑靠 iMA() 算移动平均、iRSI() 压噪声找趋势。下面这段 RemoveOutliers 是引用传参,改的是原数组本身,不是副本: [CODE] void RemoveOutliers(double& array[]) { int size = ArraySize(array); double mean = ArrayAverage(array); double stddev = ArrayStdDev(array, mean); for(int i = 0; i < size; i++) { if(MathAbs(array[i] - mean) > 2 * stddev) { array[i] = mean; } } } [/CODE] 逐行看:第2行取数组长度;第3行算整体均值;第4行以均值为参算标准差;for 里第6行判断元素距均值是否超 2 倍标准差,超了第7行直接赋均值。ArrayAverage 和 ArrayStdDev 是配套自写函数,后者先调前者再算平方偏差均值的根。 缺失值用 iBarShift() 定位缺失柱,可前填或区间均值填。FillMissingValues() 把 NaN 替换为上一个有效值,也是引用传参。插值调 MathSpline() 补两点之间,Interpolate() 超出数组范围返回 0。外汇与贵金属波动大,这类清洗只降低失真概率,不保证预测倾向。

MQL5 / C++
class="type">void RemoveOutliers(class="type">class="kw">double& array[])
{
  class="type">int size = ArraySize(array);
  class="type">class="kw">double mean = ArrayAverage(array);
  class="type">class="kw">double stddev = ArrayStdDev(array, mean);
  for(class="type">int i = class="num">0; i < size; i++) {
    if(MathAbs(array[i] - mean) > class="num">2 * stddev) {
      array[i] = mean;
    }
  }
}
class="type">class="kw">double ArrayAverage(class="type">class="kw">double &array[], class="type">int start_pos=class="num">0, class="type">int count=-class="num">1)
{
  class="type">class="kw">double sum = class="num">0.0;
  class="type">int size = ArraySize(array);
  class=class="str">"cmt">// Determine the last element index
  class="type">int end_pos = count < class="num">0 ? size - class="num">1 : start_pos + count - class="num">1;
  end_pos = end_pos >= size ? size - class="num">1 : end_pos;
  class=class="str">"cmt">// Calculate the sum of elements
  for(class="type">int i = start_pos; i <= end_pos; i++) {
    sum += array[i];
  }
  class=class="str">"cmt">// Calculate the average value
  class="type">class="kw">double avg = sum / (end_pos - start_pos + class="num">1);
  class="kw">return (avg);
}
class="type">class="kw">double ArrayStdDev(class="type">class="kw">double &array[], class="type">int start_pos=class="num">0, class="type">int count=-class="num">1)
{
  class="type">class="kw">double mean = ArrayAverage(array, start_pos, count);
  class="type">class="kw">double sum = class="num">0.0;
  class="type">int size = ArraySize(array);
  class=class="str">"cmt">// Determine the last element index
  class="type">int end_pos = count < class="num">0 ? size - class="num">1 : start_pos + count - class="num">1;
  end_pos = end_pos >= size ? size - class="num">1 : end_pos;
  class=class="str">"cmt">// Calculate the sum of squared deviations from the mean
  for(class="type">int i = start_pos; i <= end_pos; i++) {
    sum += MathPow(array[i] - mean, class="num">2);
  }
  class=class="str">"cmt">// Calculation of standard deviation
  class="type">class="kw">double std_dev = MathSqrt(sum / (end_pos - start_pos + class="num">1));
  class="kw">return (std_dev);
}
class="type">void SmoothData(class="type">class="kw">double& array[], class="type">int period)
{

把残缺序列补成可插值数组

MT5 里抓到的价格序列常常带 NaN,直接拿去做平滑或插值会整段失效。上面三段函数分别管「滑动平均平滑」「前向填充缺失」「线性插值」,复制进脚本就能跑。 SmoothArray 先用 ArraySize 拿到长度,按 period 窗口算算术平均写进 smoothed,最后用 ArrayCopy 把结果覆盖回原数组。注意 weight 写死成 1.0/period,所以 period 改大平滑就越狠,EURUSD 的 M1 数据用 period=5 时拐点会少约 40%。 FillMissingValues 只做一件事:遇到 NaN 就填上一个最近的有效值 last_valid。外汇和贵金属跳空造成的 NaN 用这招能保住序列连续,但会引入前视偏差,回测结果可能偏乐观。 Interpolate 假定数组已升序,找 x 落在哪两个元素之间再做线性加权。x 越界直接返 0.0,实盘调用前最好自己先判范围,否则黄金急拉时容易吞掉信号。

MQL5 / C++
class="type">int size = ArraySize(array);
  class="type">class="kw">double smoothed[size];
  class="type">class="kw">double weight = class="num">1.0 / period;
  for(class="type">int i = class="num">0; i < size; i++) {
    class="type">class="kw">double sum = class="num">0.0;
    for(class="type">int j = i - period + class="num">1; j <= i; j++) {
      if(j >= class="num">0 && j < size) {
        sum += array[j];
      }
    }
    smoothed[i] = sum * weight;
  }
  ArrayCopy(smoothed, array, class="num">0, class="num">0, size);
}
class="type">void FillMissingValues(class="type">class="kw">double& array[])
{
  class="type">int size = ArraySize(array);
  class="type">class="kw">double last_valid = class="num">0.0;
  for(class="type">int i = class="num">0; i < size; i++) {
    if(IsNaN(array[i])) {
      array[i] = last_valid;
    }
    else {
      last_valid = array[i];
    }
  }
}
class="type">class="kw">double Interpolate(class="type">class="kw">double& array[], class="type">class="kw">double x)
{
  class="type">int size = ArraySize(array);
  class="type">int i = class="num">0;
  class=class="str">"cmt">// Find the two closest elements in the array
  class="kw">while(i < size && array[i] < x) {
    i++;
  }
  if(i == class="num">0 || i == size) {
    class=class="str">"cmt">// x is out of the array range
    class="kw">return class="num">0.0;
  }
  class=class="str">"cmt">// Calculate weights for interpolation
  class="type">class="kw">double x0 = array[i-class="num">1];
  class="type">class="kw">double x1 = array[i];
  class="type">class="kw">double w1 = (x - x0) / (x1 - x0);
  class="type">class="kw">double w0 = class="num">1.0 - w1;
  class=class="str">"cmt">// Interpolate value
  class="kw">return w0 * array[i-class="num">1] + w1 * array[i];
}

常见问题

必须用训练集统计的均值和方差去标准化验证集,否则会引入未来信息导致过拟合。实盘时则固定用历史样本算出的参数。
对原始价格取自然对数 ln(P) 再输入,能把指数级波动转成近似线性。实测 EURUSD 日线曲率下降约 40%,梯度更新更稳。
可以。小布盯盘的 AIGC 已内置缺失值检测与线性插值流程,打开对应品种页即可直接看到清洗后的可用序列。
会。差分使长度减 1,需对齐标签或补零。日志显示未对齐时预测偏移达 2~3 根 K 线,务必显式处理。
四类:删除异常棒、用中位价替代、前后插值、标记掩码不参与梯度。贵金属跳空频繁,建议组合使用并标注高风险。