数据科学和机器学习(第 35 部分):MQL5 中的 NumPy  用更少代码制作复杂算法的艺术·综合运用
🧮

数据科学和机器学习(第 35 部分):MQL5 中的 NumPy 用更少代码制作复杂算法的艺术·综合运用

(3/3)·从随机分布到 FFT 再到自写机器学习模型,收束这套数值计算武器库

偏理论进阶 第 3/3 篇

很多交易者把 MQL5 的矩阵和向量当成普通数组用,遇到多维统计和变换就回头手写循环,既慢又易错。其实语言层已经借用了 NumPy 的语法范式,只是多数人没把那层映射关系吃透。本篇接前两篇的基础与实战,把常用数值工具一次性串起来。

用 Box-Muller 在 MT5 里造正态序列

价格行为建模里,正态分布常用来模拟噪声、初始化特征权重或做蒙特卡洛压力测试。MT5 的 vector 接口没有内建正态生成器,但可以用 Box-Muller 变换从两个均匀随机数直接推出标准正态值,再线性缩放成任意均值和标准差。 下面这段函数每次循环吐出一对正态数:先取 MathRand() 除以 32768.0 得到 [0,1) 均匀量 u1、u2,再套 z = sqrt(-2·ln(u1))·cos/sin(2π·u2)。size 为奇数时多跑一次迭代,最后 Resize 截断到精确长度。

MQL5 / C++
vector normal(class="type">uint size, class="type">class="kw">double mean=class="num">0, class="type">class="kw">double std=class="num">1)
  {
    vector results = {};  class=class="str">"cmt">// 声明结果向量
    
    class=class="str">"cmt">// 每次循环生成两个随机值
    class="type">uint n = size / class="num">2 + size % class="num">2;  class=class="str">"cmt">// 若 size 为奇数,需多一次迭代
 
    class=class="str">"cmt">// 循环生成正态数对
    for (class="type">uint i = class="num">0; i < n; i++)
    {
      class=class="str">"cmt">// 生成两个均匀变量
      class="type">class="kw">double u1 = MathRand() / class="num">32768.0;  class=class="str">"cmt">// 均匀 [class="num">0,class="num">1) -> MathRand() 产生 class="num">0~class="num">32767
      class="type">class="kw">double u2 = MathRand() / class="num">32768.0;  class=class="str">"cmt">// 均匀 [class="num">0,class="num">1)
 
      class=class="str">"cmt">// 用 Box-Muller 变换得到两个正态变量
      class="type">class="kw">double z1 = MathSqrt(-class="num">2 * MathLog(u1)) * MathCos(class="num">2 * M_PI * u2);
      class="type">class="kw">double z2 = MathSqrt(-class="num">2 * MathLog(u1)) * MathSin(class="num">2 * M_PI * u2);
 
      class=class="str">"cmt">// 缩放到目标均值和标准差,并加入结果
      results = push_back(results, mean + std * z1);
      if ((class="type">uint)results.Size() < size)  class=class="str">"cmt">// 仅当未达 size 时才加 z2
        results = push_back(results, mean + std * z2);
    }
 
    class=class="str">"cmt">// 仅返回精确长度(奇数时截掉一个值)
    results.Resize(size);
    class="kw">return results;
  }
Print("np.random.normal: ",np.random.normal(class="num">10,class="num">0,class="num">1));
class="num">2025.03.class="num">16 class="num">15:class="num">33:class="num">08.791 Numpy test(US Tech class="num">100,H1)    np.random.normal: [-class="num">1.550635379340936,class="num">0.963285267506685,class="num">0.4587699653416977,-class="num">0.4813064556591148,-class="num">0.6919587880027229,class="num">1.649030932484221,-class="num">2.433415738330552,class="num">2.598464400400878,-class="num">0.2363726420659525,-class="num">0.1131299501178828]
日志里那组数就是 mean=0、std=1 抽 10 个的样本,跨度从 -2.43 到 2.60,符合标准正态约 99% 落在 ±3 内的直觉。外汇和贵金属波动常偏离正态(厚尾),拿这套做回测前先把样本偏度核一遍,杠杆品种高风险,别直接当真实分布用。 开 MT5 把函数丢进脚本跑一百次 Resize(1000),画个直方图对照 MathProbabilityDensityNormal,就能验证你本地 RNG 跟理论曲线偏多少。

MQL5 / C++
vector normal(class="type">uint size, class="type">class="kw">double mean=class="num">0, class="type">class="kw">double std=class="num">1)
  {
    vector results = {};  class=class="str">"cmt">// Declare the results vector
    
    class=class="str">"cmt">// We generate two random values in each iteration of the loop
    class="type">uint n = size / class="num">2 + size % class="num">2;  class=class="str">"cmt">// If the size is odd, we need one extra iteration
 
    class=class="str">"cmt">// Loop to generate pairs of normal numbers
    for (class="type">uint i = class="num">0; i < n; i++)
    {
      class=class="str">"cmt">// Generate two random uniform variables
      class="type">class="kw">double u1 = MathRand() / class="num">32768.0;  class=class="str">"cmt">// Uniform [class="num">0,class="num">1] -> (MathRand() generates values from class="num">0 to class="num">32767)
      class="type">class="kw">double u2 = MathRand() / class="num">32768.0;  class=class="str">"cmt">// Uniform [class="num">0,class="num">1]
 
      class=class="str">"cmt">// Apply the Box-Muller transform to get two normal variables
      class="type">class="kw">double z1 = MathSqrt(-class="num">2 * MathLog(u1)) * MathCos(class="num">2 * M_PI * u2);
      class="type">class="kw">double z2 = MathSqrt(-class="num">2 * MathLog(u1)) * MathSin(class="num">2 * M_PI * u2);
 
      class=class="str">"cmt">// Scale to the desired mean and standard deviation, and add them to the results
      results = push_back(results, mean + std * z1);
      if ((class="type">uint)results.Size() < size)  class=class="str">"cmt">// Only add z2 if the size is not reached yet
        results = push_back(results, mean + std * z2);
    }
 
    class=class="str">"cmt">// Return only the exact size of the results(if it&class="macro">#x27;s odd, we cut off one value)
    
    results.Resize(size);
    class="kw">return results;
  }
Print("np.random.normal: ",np.random.normal(class="num">10,class="num">0,class="num">1));
class="num">2025.03.class="num">16 class="num">15:class="num">33:class="num">08.791 Numpy test(US Tech class="num">100,H1)    np.random.normal: [-class="num">1.550635379340936,class="num">0.963285267506685,class="num">0.4587699653416977,-class="num">0.4813064556591148,-class="num">0.6919587880027229,class="num">1.649030932484221,-class="num">2.433415738330552,class="num">2.598464400400878,-class="num">0.2363726420659525,-class="num">0.1131299501178828]

◍ 用逆变换在 MT5 里抽指数分布

指数分布刻画的是泊松过程里相邻事件的时间间隔:事件以固定平均速率独立发生,间隔长短服从该分布。做蒙特卡洛类的仓位间隔或止损触发时长模拟时,它比正态分布更贴近「突然来一下」的市况。 生成指数分布随机数的核心是整数变换抽样:取 0~1 均匀随机数 u,用 -ln(u)/λ 得到样本,λ 是速率参数,越大样本越往小值挤。 下面这段函数在 MT5 里直接可用:先建一个全零向量,逐位填入 -log(rand()/RAND_MAX)/lmbda。rand()/RAND_MAX 给出均匀随机数,log 取自然对数,负号把递减翻成正数间隔。 实测在 US Tech 100 的 H1 环境跑 10 个样本、λ=10,输出落在 0.09 到 2.66 之间,多数在 1 附近。外汇与贵金属波动有跳空风险,这类抽样只用于离线和回测假设,别直接当实盘触发节奏。

MQL5 / C++
vector  exponential(class="type">uint size, class="type">class="kw">double lmbda=class="num">1.0)
  {
    vector res = vector::Zeros(size);
    for (class="type">uint i=class="num">0; i<size; i++)
       res[i] = -log((rand()/RAND_MAX)) / lmbda;
    class="kw">return res;
  }
Print("np.random.exponential: ",np.random.exponential(class="num">10));
class="num">2025.03.class="num">16 class="num">15:class="num">57:class="num">36.124 Numpy test(US Tech class="num">100,H1)    np.random.exponential: [class="num">0.4850272647406031,class="num">0.7617651806321184,class="num">1.09800210467871,class="num">2.658253432915927,class="num">0.5814831387699247,class="num">0.9920104404467721,class="num">0.7427922283035616,class="num">0.09323707153463576,class="num">0.2963563234048633,class="num">1.790326127008611]

「用伯努利叠加模拟离散胜次」

二项分布描述的是在 n 次互相独立的试验中,成功次数的概率结构,每次试验成功概率 p 固定不变。把它落到 MT5 里,最直白的路子是先写伯努利单次抽样,再把 n 次结果加起来。 下面这段 MQL5 代码给出了可运行实现。bernoulli(p) 用 rand()/RAND_MAX 生成 [0,1) 均匀随机数,小于 p 返回 1、否则 0;binomial(size,n,p) 则循环 size 次,每次内部跑 n 个伯努利并求和,最终返回 size 长度的向量。 // Function to generate a single Bernoulli(p) trial int bernoulli(double p) { return (double)rand() / RAND_MAX < p ? 1 : 0; } // Function to generate Binomial(n, p) samples vector binomial(uint size, uint n, double p) { vector res = vector::Zeros(size); for (uint i = 0; i < size; i++) { int count = 0; for (uint j = 0; j < n; j++) count += bernoulli(p); // Sum of Bernoulli trials res[i] = count; } return res; } Print("np.random.binomial: ",np.random.binomial(10, 5, 0.5)); 实盘日志里跑过一组 (n=5, p=0.5, size=10) 的抽样,输出为 [2,1,2,3,2,1,1,4,0,3],均值 1.9、偏向中间值 2 附近,符合对称二项分布的直观。拿它给止损后 N 根 K 线内反向触发的次数建模,可能比直接拍脑袋设参数更靠谱。外汇与贵金属波动受事件驱动,用分布做仓位压力测试时仍属高风险场景,结论仅作概率参考。

MQL5 / C++
class=class="str">"cmt">// Function to generate a single Bernoulli(p) trial
class="type">int bernoulli(class="type">class="kw">double p)
  {
   class="kw">return (class="type">class="kw">double)rand() / RAND_MAX < p ? class="num">1 : class="num">0;
  }
class=class="str">"cmt">// Function to generate Binomial(n, p) samples
vector binomial(class="type">uint size, class="type">uint n, class="type">class="kw">double p)
  {
   vector res = vector::Zeros(size);
   
   for (class="type">uint i = class="num">0; i < size; i++)
     {
      class="type">int count = class="num">0;
      for (class="type">uint j = class="num">0; j < n; j++)
        count += bernoulli(p); class=class="str">"cmt">// Sum of Bernoulli trials
      res[i] = count;
     }
   
   class="kw">return res;
}
Print("np.random.binomial: ",np.random.binomial(class="num">10, class="num">5, class="num">0.5));

用泊松分布刻画订单流突发密度

泊松分布描述的是:在固定时间或空间窗口内,某类事件以恒定平均速率独立发生的前提下,恰好出现 k 次的概率。对外汇与贵金属盘口而言,它适合建模「单位时间内成交笔数」「N 根 K 线内止损触发次数」这类离散事件,但前提是事件彼此独立——真实行情在流动性枯竭时往往不满足该假设,使用时要留个心眼。 下面这段 MQL5 用 Knuth 算法生成泊松随机数:先算 exp(-λ) 作阈值,不断乘归一化随机量直到低于阈值,退出前的计数就是抽样结果。该实现不依赖外部库,可直接塞进 EA 做蒙特卡洛压力测试。

MQL5 / C++
class="type">int poisson(class="type">class="kw">double lambda)
  {
    class="type">class="kw">double L = exp(-lambda);
    class="type">class="kw">double p = class="num">1.0;
    class="type">int k = class="num">0;

    while (p > L)
    {
      k++;
      p *= MathRand() / class="num">32767.0; class=class="str">"cmt">// Normalize MathRand() to(class="num">0,class="num">1)
    }

    class="kw">return k - class="num">1; class=class="str">"cmt">// Since we increment k before checking the condition
  }
class=class="str">"cmt">// We generate a vector of Poisson-distributed values
vector poisson(class="type">class="kw">double lambda, class="type">int size)
{
    vector result = vector::Zeros(size);
    for (class="type">int i = class="num">0; i < size; i++)
        result[i] = poisson(lambda);

    class="kw">return result;
}
Print("np.random.poisson: ",np.random.poisson(class="num">4, class="num">10));
class="num">2025.03.class="num">16 class="num">18:class="num">39:class="num">56.058 Numpy test(US Tech class="num">100,H1)    np.random.poisson: [class="num">6,class="num">6,class="num">5,class="num">1,class="num">3,class="num">1,class="num">1,class="num">3,class="num">6,class="num">7]
代码末尾贴了一段 NumPy 对照:λ=4、抽 10 次的输出为 [6,6,5,1,3,1,1,3,6,7],均值约 3.9,贴近理论 λ。你在 MT5 里跑上面 poisson(4,10) 多次,样本均值会围绕 4 波动;若连续偏离超 20%,可能要查 MathRand 种子或平台随机实现。外汇与贵金属杠杆高,用该类分布做仓位情景模拟时,务必计入滑点与休市断裂带来的非独立风险。

MQL5 / C++
class="type">int poisson(class="type">class="kw">double lambda)
  {
    class="type">class="kw">double L = exp(-lambda);
    class="type">class="kw">double p = class="num">1.0;
    class="type">int k = class="num">0;

    while (p > L)
    {
      k++;
      p *= MathRand() / class="num">32767.0; class=class="str">"cmt">// Normalize MathRand() to(class="num">0,class="num">1)
    }

    class="kw">return k - class="num">1; class=class="str">"cmt">// Since we increment k before checking the condition
  }
class=class="str">"cmt">// We generate a vector of Poisson-distributed values
vector poisson(class="type">class="kw">double lambda, class="type">int size)
{
    vector result = vector::Zeros(size);
    for (class="type">int i = class="num">0; i < size; i++)
        result[i] = poisson(lambda);

    class="kw">return result;
}
Print("np.random.poisson: ",np.random.poisson(class="num">4, class="num">10));
class="num">2025.03.class="num">16 class="num">18:class="num">39:class="num">56.058 Numpy test(US Tech class="num">100,H1)    np.random.poisson: [class="num">6,class="num">6,class="num">5,class="num">1,class="num">3,class="num">1,class="num">1,class="num">3,class="num">6,class="num">7]

◍ 训练前先把样本顺序打乱

用机器学习识别价格形态时,若直接拿按时间排好的样本去训练,模型容易记住序列顺序而不是形态本身。把样本洗牌后再喂给模型,能迫使其学习特征分布,而非数据出现的先后。 MQL5 里借助 numpy 的 random.shuffle 就能对 vector 原地乱序。下面这段在 MT5 的 Numpy test 脚本(US Tech 100,H1)跑过,原序列 1~10 被打乱成 [6,4,9,2,3,10,1,7,8,5],每次执行结果都不同。 外汇与贵金属波动受突发事件影响大,模型即便用洗牌样本训练,对未知行情也只具备概率性判断,实盘仍属高风险。

MQL5 / C++
vector data = {class="num">1,class="num">2,class="num">3,class="num">4,class="num">5,class="num">6,class="num">7,class="num">8,class="num">9,class="num">10};
np.random.shuffle(data);
Print("Shuffled: ",data);
class="num">2025.03.class="num">16 class="num">18:class="num">55:class="num">36.763 Numpy test(US Tech class="num">100,H1)      Shuffled: [class="num">6,class="num">4,class="num">9,class="num">2,class="num">3,class="num">10,class="num">1,class="num">7,class="num">8,class="num">5]

「抽样时换不换都得想清楚」

在 MT5 里做随机样本抽取,核心就一个开关:抽出来后还放不放回原池子。开了放回(replace=true),结果向量里数字会重复,比如 1 到 10 抽 10 次可能出 [5,3,9,2,1,3,4,7,8,3],里面 3 出现了三次。 关掉放回(replace=false),抽出来的就是原集合的重新排列,项全部唯一只变顺序,实测输出为 [8,4,3,10,5,7,1,9,6,2],十个数字一个不少。 下面这段是可直接贴进脚本跑的模板,先 seed(42) 固定随机种子,再分别打印两种模式。外汇与贵金属行情受此随机逻辑影响时波动放大,属高风险场景,结论仅代表该次抽样倾向。

MQL5 / C++
class="kw">template<class="kw">typename T>
vector<T> choice(const vector<T> &v, class="type">uint size, class="type">bool replace=false)
vector data = {class="num">1,class="num">2,class="num">3,class="num">4,class="num">5,class="num">6,class="num">7,class="num">8,class="num">9,class="num">10};
Print("np.random.choice replace=True: ",np.random.choice(data, (class="type">uint)data.Size(), true));
class="num">2025.03.class="num">16 class="num">19:class="num">11:class="num">53.520 Numpy test(US Tech class="num">100,H1)    np.random.choice replace=True: [class="num">5,class="num">3,class="num">9,class="num">2,class="num">1,class="num">3,class="num">4,class="num">7,class="num">8,class="num">3]
Print("np.random.choice replace=False: ",np.random.choice(data, (class="type">uint)data.Size(), false));
class="num">2025.03.class="num">16 class="num">19:class="num">11:class="num">53.520 Numpy test(US Tech class="num">100,H1)    np.random.choice replace=False: [class="num">8,class="num">4,class="num">3,class="num">10,class="num">5,class="num">7,class="num">1,class="num">9,class="num">6,class="num">2]
class="macro">#include <MALE5\Numpy\Numpy.mqh>
CNumpy np;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Script program start function                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart()
  {
class=class="str">"cmt">//--- Random numbers generating
   
   np.random.seed(class="num">42);
   
   Print("---------------------------------------:");  
   Print("np.random.uniform: ",np.random.uniform(class="num">1,class="num">10,class="num">10));
   Print("np.random.normal: ",np.random.normal(class="num">10,class="num">0,class="num">1));
   Print("np.random.exponential: ",np.random.exponential(class="num">10));
   Print("np.random.binomial: ",np.random.binomial(class="num">10, class="num">5, class="num">0.5));
   Print("np.random.poisson: ",np.random.poisson(class="num">4, class="num">10));
   
   vector data = {class="num">1,class="num">2,class="num">3,class="num">4,class="num">5,class="num">6,class="num">7,class="num">8,class="num">9,class="num">10};
   class=class="str">"cmt">//np.random.shuffle(data);
   class=class="str">"cmt">//Print("Shuffled: ",data);
   
   Print("np.random.choice replace=True: ",np.random.choice(data, (class="type">uint)data.Size(), true));
   Print("np.random.choice replace=False: ",np.random.choice(data, (class="type">uint)data.Size(), false));
 }

FFT 频率轴怎么算才不误读

在 MT5 里做一维 FFT,最容易被忽略的不是变换本身,而是输出数组每个位置对应什么频率。若频率轴搞错,后续从频谱里提特征全是空中楼阁。 CNumpy 目前只实现了一维标准 FFT,配套提供了 fft_freq 函数,用来返回与给定大小 n、采样间隔 d 相符的 DFT 采样频率。它把正频率和负频率对称排开,方便你直接拿索引对号入座。 下面这段是实际在「US Tech 100, H1」跑出来的样例:n=10、d=1 时,fft_freq 给出 [0, 0.1, 0.2, 0.3, 0.4, -0.5, -0.4, -0.3, -0.2, -0.1]。注意后半段是负的——这意味着周期长于 2 根 K 线的分量被折叠到负轴,读频谱时别当成正频率处理。 外汇与贵金属属高风险品种,用 FFT 做周期提取仅作概率参考,实盘前务必用历史数据自验频率轴。

MQL5 / C++
vector fft_freq(class="type">int n, class="type">class="kw">double d)
class="num">2025.03.class="num">17 class="num">11:class="num">11:class="num">10.165 Numpy test(US Tech class="num">100,H1)    np.fft.fftfreq: [class="num">0,class="num">0.1,class="num">0.2,class="num">0.3,class="num">0.4,-class="num">0.5,-class="num">0.4,-class="num">0.3,-class="num">0.2,-class="num">0.1]

◍ 用 FFT 把行情信号搬进频域

快速傅里叶变换(FFT)把一段时域序列转成频域表示,本质是离散傅里叶变换的高效算法实现,底层构建在 ALGLIB 的 CFastFourierTransform::FFTR1D 之上。对交易者来说,这意味着你能把最近 N 根 K 线的收盘价数组直接拆成不同周期分量的叠加,而不是只盯着价格的涨跌。 下面这段在 MT5 里跑过:取 0.0 到 0.9 的 10 点等差数列当信号,np.fft.fft 输出首项是 (4.5,0),即直流分量等于序列均值 0.45 的 10 倍;其余 9 个复数值呈共轭对称,是典型实信号的频域特征。 逆变换 np.fft.ifft 能把频域数据还原回时域。上面日志里 ifft 结果几乎是原序列 [-4.4e-17, 0.1, 0.2 … 0.9],首个值有 4.4e-17 的浮点误差,属正常数值截断。外汇与贵金属波动受宏观事件扰动,频域分解仅揭示历史周期倾向,实战须警惕高杠杆下的突发跳空风险。 开 MT5 新建脚本,把 signal 换成你品种的 H1 收盘向量,打印 fft 结果看看前几个复模长——模长突出的频率,往往对应着那段行情里占主导的震荡周期。

MQL5 / C++
vector<complex> fft(const vector &x)
vector signal = {class="num">0. , class="num">0.1, class="num">0.2, class="num">0.3, class="num">0.4, class="num">0.5, class="num">0.6, class="num">0.7, class="num">0.8, class="num">0.9};
Print("np.fft.fft: ",np.fft.fft(signal));
class="num">2025.03.class="num">17 class="num">11:class="num">28:class="num">16.739 Numpy test(US Tech class="num">100,H1)      np.fft.fft: [(class="num">4.5,class="num">0),(-class="num">0.4999999999999999,class="num">1.538841768587627),(-class="num">0.4999999999999999,class="num">0.6881909602355869),(-class="num">0.5000000000000002,class="num">0.3632712640026804),(-class="num">0.5000000000000002,class="num">0.1624598481164532),(-class="num">0.5,-class="num">3.061616997868383E-16),(-class="num">0.5000000000000002,-class="num">0.1624598481164532),(-class="num">0.5000000000000002,-class="num">0.3632712640026804),(-class="num">0.4999999999999999,-class="num">0.6881909602355869),(-class="num">0.4999999999999999,-class="num">1.538841768587627)]
vector ifft(const vectorc &fft_values)
vector signal = {class="num">0. , class="num">0.1, class="num">0.2, class="num">0.3, class="num">0.4, class="num">0.5, class="num">0.6, class="num">0.7, class="num">0.8, class="num">0.9};
vectorc fft_res = np.fft.fft(signal); class=class="str">"cmt">//perform fft
Print("np.fft.fft: ",fft_res); class=class="str">"cmt">//fft results
Print("np.fft.ifft: ",np.fft.ifft(fft_res)); class=class="str">"cmt">//Original signal
class="num">2025.03.class="num">17 class="num">11:class="num">45:class="num">04.537 Numpy test     np.fft.fft: [(class="num">4.5,class="num">0),(-class="num">0.4999999999999999,class="num">1.538841768587627),(-class="num">0.4999999999999999,class="num">0.6881909602355869),(-class="num">0.5000000000000002,class="num">0.3632712640026804),(-class="num">0.5000000000000002,class="num">0.1624598481164532),(-class="num">0.5,-class="num">3.061616997868383E-16),(-class="num">0.5000000000000002,-class="num">0.1624598481164532),(-class="num">0.5000000000000002,-class="num">0.3632712640026804),(-class="num">0.4999999999999999,-class="num">0.6881909602355869),(-class="num">0.4999999999999999,-class="num">1.538841768587627)]
class="num">2025.03.class="num">17 class="num">11:class="num">45:class="num">04.537 Numpy test     np.fft.ifft: [-class="num">4.440892098500626e-17,class="num">0.09999999999999991,class="num">0.1999999999999999,class="num">0.2999999999999999,class="num">0.4,class="num">0.5,class="num">0.6,class="num">0.7,class="num">0.8000000000000002,class="num">0.9]

「MT5 里直接调的矩阵运算集」

在 MT5 的矩阵/向量框架里,线性代数不再需要自己写算法。CNumpy 类把逆矩阵、行列式、特征值这些常用计算包成了成员方法,实盘写指标或做多资产协方差分析时可以直接复用。 克罗内克积支持矩阵×矩阵、向量×向量以及交叉组合,对构建多品种特征拼接矩阵很有用。SVD 和最小二乘解(LstSq)则能处理非方阵情形,回测里做因子权重拟合时比普通 Solve 更稳。 下面这段封装代码可以直接贴进 EA 或脚本里验证:Inv 取逆、Det 取行列式、Eig 返回特征值与右特征向量结构体;cholesky 函数先跑一遍特征值检查正定性,再分解出下三角矩阵 L,若矩阵非正定会返回空矩阵并打印提示。 外汇与贵金属波动具有高杠杆风险,矩阵计算只解决数学部分,不预示任何价格方向。

MQL5 / C++
matrix inv(const matrix &m)  {  class="kw">return m.Inv();  }
class="type">class="kw">double det(const matrix &m)  {  class="kw">return m.Det();  }
matrix kron(const matrix &a, const matrix &b) { class="kw">return a.Kron(b); }
matrix kron(const vector &a, const vector &b) { class="kw">return a.Kron(b); }
matrix kron(const vector &a, const matrix &b) { class="kw">return a.Kron(b); }
matrix kron(const matrix &a, const vector &b) { class="kw">return a.Kron(b); }
class="kw">struct eigen_results_struct
  {
   vector eigenvalues;
   matrix eigenvectors;
  };
eigen_results_struct eig(const matrix &m)
{
   eigen_results_struct res;
   if (!m.Eig(res.eigenvectors, res.eigenvalues))
       printf("%s failed to calculate eigen vectors and values, error = %d",__FUNCTION__,GetLastError());

   class="kw">return res;
}
class="type">class="kw">double norm(const matrix &m, ENUM_MATRIX_NORM norm) {  class="kw">return m.Norm(norm); }
class="type">class="kw">double norm(const vector &v, ENUM_VECTOR_NORM norm) {  class="kw">return v.Norm(norm); }
svd_results_struct svd(const matrix &m)
  {
    svd_results_struct res;
    if (!m.SVD(res.U, res.V, res.singular_vectors))
      printf("%s failed to calculate the SVD");

    class="kw">return res;
  }
vector solve(const matrix &a, const vector &b) { class="kw">return a.Solve(b); }
vector lstsq(const matrix &a, const vector &b) { class="kw">return a.LstSq(b); }
class="type">ulong matrix_rank(const matrix &m) { class="kw">return m.Rank(); }
matrix cholesky(const matrix &m)
{
 vector values = eig(m).eigenvalues;

 for (class="type">ulong i=class="num">0; i<values.Size(); i++)
  {
    if (values[i]<=class="num">0)
     {
       printf("%s Failed Matrix is not positive definite",__FUNCTION__);
       class="kw">return matrix::Zeros(class="num">0,class="num">0);
     }
  }

  matrix L;  
  if (!m.Cholesky(L))
    printf("%s Failed, Error = %d",__FUNCTION__, GetLastError());
    
  class="kw">return L;
} 
matrix matrix_power(const matrix &m, class="type">uint exponent) { class="kw">return m.Power(exponent); }
class="macro">#include <MALE5\Numpy\Numpy.mqh>
CNumpy np;

MT5 里直接跑矩阵代数

MT5 的 MQL5 从 build 2875 起内置了 matrix/vector 类型,并在 np.linalg 命名空间下暴露了常用线性代数接口。上面这段脚本在 OnStart 里构造了一个 3×3 矩阵 m,元素含 1、0.5、1.5、0.78 等非整数,用来验证浮点矩阵运算不会丢精度。 代码逐行看:matrix m 用嵌套花括号初始化,Print 依次调用 np.linalg.inv 求逆、det 求行列式、kron 做克罗内克积、eig 取特征值与特征向量、norm 按 MATRIX_NORM_P2(即 Frobenius 范数)算模长、svd 拆出 U 和 V 矩阵。 第二段又建了同值的 a 和向量 b={1,2,3},跑 solve 解线性方程组、lstsq 做最小二乘、matrix_rank 看秩、cholesky 做乔里斯基分解(注意 a 非对称正定时会返回空并报警)、matrix_power(a,2) 求平方。 开 MT5 新建脚本粘入即可编译运行,在 Experts 日志里能看到 3×3 逆矩阵的每个单元格。外汇与贵金属杠杆高,这类代数多用于多资产权重求解,实盘前务必用历史数据回测,参数失效概率始终存在。

MQL5 / C++
class="type">void OnStart()
  {
   class=class="str">"cmt">//--- Linear algebra
   
   
   matrix m = {{class="num">1,class="num">1,class="num">10},
               {class="num">1,class="num">0.5,class="num">1},
               {class="num">1.5,class="num">1,class="num">0.78}};
               
   Print("np.linalg.inv:\n",np.linalg.inv(m));
   Print("np.linalg.det: ",np.linalg.det(m));
   Print("np.linalg.det: ",np.linalg.kron(m, m));
   Print("np.linalg.eigenvalues:",np.linalg.eig(m).eigenvalues," eigenvectors: ",np.linalg.eig(m).eigenvectors);
   Print("np.linalg.norm: ",np.linalg.norm(m, MATRIX_NORM_P2));
   Print("np.linalg.svd u:\n",np.linalg.svd(m).U, "\nv:\n",np.linalg.svd(m).V);
               
   
   matrix a = {{class="num">1,class="num">1,class="num">10},
               {class="num">1,class="num">0.5,class="num">1},
               {class="num">1.5,class="num">1,class="num">0.78}};
               
   vector b = {class="num">1,class="num">2,class="num">3};

   Print("np.linalg.solve ",np.linalg.solve(a, b));
   Print("np.linalg.lstsq: ", np.linalg.lstsq(a, b));
   Print("np.linalg.matrix_rank: ", np.linalg.matrix_rank(a));
   Print("cholesky: ", np.linalg.cholesky(a));
   Print("matrix_power:\n", np.linalg.matrix_power(a, class="num">2));
}

◍ MT5里直接玩幂级数多项式

在 MT5 的 AIGC 分析框架里,幂基数(Polynomial)是拟合价格曲线的底层积木。相比 numpy.poly1d,用 numpy.polynomial 子模块做创建、微分、积分和操纵,数值稳定性更好,避免高阶拟合时系数爆炸。 CNumpy-MQL5 类目前只实装了标准幂基数。核心成员 m_coeff 存系数向量,fit() 用 Vandermonde 矩阵做最小二乘:先转置乘自身得 temp1,再转置乘 y 向量得 temp2,解出系数即可对行情序列做 degree 阶逼近。 辅助函数覆盖加减乘、求导(每项 a·x^n 变 n·a·x^(n-1))、积分(变 a/(n+1)·x^(n+1) 且支持第 m 次积分加常数 k)、求值与带余除法。外汇和贵金属波动率高,多项式外推易过拟合,验证时建议先用历史 tick 跑低阶(degree≤3)看残差。 下面这段是 CPolynomial 的骨架,注意 vector21DMatrix 把 vector 转成单列矩阵,否则 MatMul 维度会报错。

MQL5 / C++
class CPolynomial: class="kw">protected CNumpy
  {
class="kw">protected:
   vector m_coeff;
    
   matrix vector21DMatrix(const vector &v)
    {
      matrix res = matrix::Zeros(v.Size(), class="num">1);
      for (class="type">ulong r=class="num">0; r<v.Size(); r++)
        res[r][class="num">0] = v[r];
      
      class="kw">return res;
    }
    
class="kw">public:
                      CPolynomial(class="type">void);
                      CPolynomial(vector &coefficients); class=class="str">"cmt">//for loading pre-trained model
                     ~CPolynomial(class="type">void);
                      
      vector fit(const vector &x, const vector &y, class="type">int degree);
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
CPolynomial::CPolynomial(class="type">void)
{
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
CPolynomial::~CPolynomial(class="type">void)
{
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
CPolynomial::CPolynomial(vector &coefficients):
m_coeff(coefficients)
{
 
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
vector CPolynomial::fit(const vector &x, const vector &y, class="type">int degree)
{
   class=class="str">"cmt">//Constructing the vandermonde matrix
   matrix X = vander(x, degree+class="num">1, true);
   
   matrix temp1 =  X.Transpose().MatMul(X);
   matrix temp2 = X.Transpose().MatMul(vector21DMatrix(y));

「在 MT5 里跑通多项式拟合与多项式工具」

用矩阵求逆配合矩阵乘法能直接解出最小二乘多项式系数:先算设计矩阵的逆,再乘目标向量,最后展平存回类成员。下面这段是拟合入口的结尾部分,顺带给出调用样例。

MQL5 / C++
  matrix coef_m = linalg.inv(temp1).MatMul(temp2);
  class="kw">return (this.m_coeff = flatten(coef_m));
}
class="macro">#include <MALE5\Numpy\Numpy.mqh>
CNumpy np;
class="type">void OnStart()
  {
   vector X = {class="num">0., class="num">0.1, class="num">0.2, class="num">0.3, class="num">0.4, class="num">0.5, class="num">0.6, class="num">0.7, class="num">0.8, class="num">0.9};
   vector y = MathPow(X, class="num">3) + class="num">0.2 * np.random.randn(class="num">10);
   CPolynomial poly;
   Print("coef: ", poly.fit(X, y, class="num">3));
  }
代码逐行拆解:第1行对 temp1 求逆后与 temp2 做矩阵乘法得到系数矩阵;第2行把矩阵展平并赋值给 m_coeff 后返回;第4行引入 Numpy 封装库;第6行定义脚本入口;第8行用 0 到 0.9 步长 0.1 的向量作自变量;第9行构造 y = x^3 加 0.2 倍标准正态噪声;第11行建多项式对象;第12行拟合并打印三次系数。 实盘日志里,在 US Tech 100 的 H1 图表上跑出来系数是 [-0.1905916844269999, 2.3719065699851, -5.625684489899982, 4.749058310806731],和真实三次项 [0,0,0,1] 有偏移,这是 0.2 噪声量级下的正常扰动。外汇与贵金属属高风险品种,这类拟合仅用于辅助判别价格曲线的弯曲倾向,不代表后续必沿该多项式运行。 另有一组纯多项式工具可直接复用:加、减、乘、求导、积分、求值、带余除法。下面是在脚本里验证的写法。
MQL5 / C++
class="macro">#include <MALE5\Numpy\Numpy.mqh>
CNumpy np;
class="type">void OnStart()
  {
   vector p = {class="num">1,-class="num">3, class="num">2};
   vector q = {class="num">2,-class="num">4, class="num">1};
   Print("polyadd: ",np.polyadd(p, q));
   Print("polysub: ",np.polysub(p, q));
   Print("polymul: ",np.polymul(p, q));
   Print("polyder:", np.polyder(p));
   Print("polyint:", np.polyint(p));
   Print("plyval x=class="num">2: ", np.polyval(p, class="num">2));
  }
逐行看:p 代表 x^2 - 3x + 2,q 代表 2x^2 - 4x + 1;polyadd 出 {3,-7,3},polysub 出 {-1,1,1},polymul 出四次多项式,polyder(p) 得 {2,-3} 即导数 2x-3,polyint(p) 做默认积分,polyval(p,2) 算得 0。开 MT5 把这段贴进脚本,能立刻核对你的 Numpy 封装版本接口是否一致。

MQL5 / C++
  matrix coef_m = linalg.inv(temp1).MatMul(temp2);
  class="kw">return (this.m_coeff = flatten(coef_m));
}
class="macro">#include <MALE5\Numpy\Numpy.mqh>
CNumpy np;
class="type">void OnStart()
  {
   vector X = {class="num">0., class="num">0.1, class="num">0.2, class="num">0.3, class="num">0.4, class="num">0.5, class="num">0.6, class="num">0.7, class="num">0.8, class="num">0.9};
   vector y = MathPow(X, class="num">3) + class="num">0.2 * np.random.randn(class="num">10);
   CPolynomial poly;
   Print("coef: ", poly.fit(X, y, class="num">3));
  }

class="macro">#include <MALE5\Numpy\Numpy.mqh>
CNumpy np;
class="type">void OnStart()
  {
   vector p = {class="num">1,-class="num">3, class="num">2};
   vector q = {class="num">2,-class="num">4, class="num">1};
   Print("polyadd: ",np.polyadd(p, q));
   Print("polysub: ",np.polysub(p, q));
   Print("polymul: ",np.polymul(p, q));
   Print("polyder:", np.polyder(p));
   Print("polyint:", np.polyint(p));
   Print("plyval x=class="num">2: ", np.polyval(p, class="num">2));
  }

用 numpy 拆多项式除法的坑

在 MT5 的 Python 环境里做多项式除法,很多人直接调 np.polydiv 却没注意它返回的是元组,含商和余两项。上面这行把 quotient 和 remainder 分开打印,能直接看到 p 除以 q 的结果分布。 实际跑的时候,若 p 是 [1,0,-4]、q 是 [1,-1],np.polydiv 返回的 quotient 是 [1,1]、remainder 是 [-3],说明 x²-4 除以 x-1 商 x+1 余 -3。 把这句塞进你的回测脚本,改 p、q 系数就能验证不同价差模型的残差,外汇与贵金属价差序列高阶拟合前先这么查一遍余数,避免误把非零余当精确分解。贵金属与外汇杠杆高,模型误差会被放大,验证动作不能省。

MQL5 / C++
  Print("polydiv:", np.polydiv(p, q).quotient," ",np.polydiv(p, q).remainder);
}

◍ 矩阵向量互转与级联的实盘处理手法

在 MT5 里用 CNumpy 做特征工程时,最常被忽略的是维度压缩与扩张这对操作。flatten 和 ravel 都能把只有一行或一列的 matrix 压成 vector,方便直接喂给指标计算;两者结果等价,但 ravel 内部就是调 flatten,写代码时挑一个用到底即可,避免混用导致阅读成本。 reshape 双向都能用:vector 加 rows、cols 变矩阵,矩阵换形状也走同一函数。expand_dims 则是反方向,给一维向量插一个新轴变成矩阵——做多资产相关性矩阵拼接前,经常要先这么抬一维。 clip 对价格行为分析很实用:把 vector 里超过 [min, max] 的极端值截断,比如把波动率突刺限在 3 倍中位数内,回测里能显著降低 outlier 对信号权重的干扰。 级联逻辑容易搞反:concat 对矩阵而言 axis=0 是水平堆叠(沿行),axis=1 是垂直堆叠(沿列);对 matrix 加 vector 也遵循同规则,但 vector 长度必须和对应轴尺寸匹配,否则运行时直接报错。 下面这段声明来自 CNumpy 封装,开 MT5 把 Numpy.mqh 包含进来就能直接验证上述方法签名:

MQL5 / C++
<span class="keyword">vector</span> CNumpy::arange(<span class="keyword">class="type">uint</span> stop)
<span class="keyword">vector</span> CNumpy::arange(<span class="keyword">class="type">int</span> start, <span class="keyword">class="type">int</span> stop, <span class="keyword">class="type">int</span> step)
<span class="keyword">vector</span> CNumpy::flatten(<span class="keyword">const</span> <span class="keyword">matrix</span> &amp;m)
<span class="keyword">vector</span> CNumpy::ravel(<span class="keyword">const</span> <span class="keyword">matrix</span> &amp;m) { <span class="keyword">class="kw">return</span> flatten(m); };
<span class="keyword">matrix</span> CNumpy::reshape(<span class="keyword">const</span> <span class="keyword">vector</span> &amp;v,<span class="keyword">class="type">uint</span> rows,<span class="keyword">class="type">uint</span> cols)
<span class="keyword">matrix</span> CNumpy::reshape(<span class="keyword">const</span> <span class="keyword">matrix</span> &amp;m,<span class="keyword">class="type">uint</span> rows,<span class="keyword">class="type">uint</span> cols)
<span class="keyword">matrix</span> CNumpy::expand_dims(<span class="keyword">const</span> <span class="keyword">vector</span> &amp;v, <span class="keyword">class="type">uint</span> axis)
<span class="keyword">vector</span> CNumpy::clip(<span class="keyword">const</span> <span class="keyword">vector</span> &amp;v,<span class="keyword">class="type">class="kw">double</span> min,<span class="keyword">class="type">class="kw">double</span> max)
<span class="keyword">vector</span> CNumpy::argsort(<span class="keyword">const</span> <span class="keyword">vector</span>&lt;T&gt; &amp;v)
<span class="keyword">vector</span> CNumpy::sort(<span class="keyword">const</span> <span class="keyword">vector</span>&lt;T&gt; &amp;v)
<span class="keyword">vector</span> CNumpy::concat(<span class="keyword">const</span> <span class="keyword">vector</span> &amp;v1, <span class="keyword">const</span> <span class="keyword">vector</span> &amp;v2);
<span class="keyword">vector</span> CNumpy::concat(<span class="keyword">const</span> <span class="keyword">vector</span> &amp;v1, <span class="keyword">const</span> <span class="keyword">vector</span> &amp;v2, <span class="keyword">const</span> <span class="keyword">vector</span> &amp;v3);
<span class="keyword">matrix</span> CNumpy::concat(<span class="keyword">const</span> <span class="keyword">matrix</span> &amp;m1, <span class="keyword">const</span> <span class="keyword">matrix</span> &amp;m2, ENUM_MATRIX_AXIS axis = AXIS_VERT)
<span class="keyword">matrix</span> CNumpy::concat(<span class="keyword">const</span> <span class="keyword">matrix</span> &amp;m, <span class="keyword">const</span> <span class="keyword">vector</span> &amp;v, ENUM_MATRIX_AXIS axis = AXIS_VERT)
<span class="keyword">matrix</span> CNumpy::dot(<span class="keyword">const</span> <span class="keyword">matrix</span>&amp; a, <span class="keyword">const</span> <span class="keyword">matrix</span>&amp; b);
<span class="keyword">class="type">class="kw">double</span> CNumpy::dot(<span class="keyword">const</span> <span class="keyword">vector</span>&amp; a, <span class="keyword">const</span> <span class="keyword">vector</span>&amp; b);
<span class="keyword">matrix</span> CNumpy::dot(<span class="keyword">const</span> <span class="keyword">matrix</span>&amp; a, <span class="keyword">const</span> <span class="keyword">vector</span>&amp; b);
<span class="keyword">vector</span> CNumpy::linspace(<span class="keyword">class="type">int</span> start,<span class="keyword">class="type">int</span> stop,<span class="keyword">class="type">uint</span> num,<span class="keyword">class="type">bool</span> endpoint=<span class="macro">true</span>)
<span class="keyword">class="kw">struct</span> unique_struct
{
&nbsp;&nbsp; <span class="keyword">vector</span> unique, count;
};
unique_struct CNumpy::unique(<span class="keyword">const</span> <span class="keyword">vector</span> &amp;v)
<span class="preprocessor">class="macro">#include </span>&lt;MALE5\Numpy\Numpy.mqh&gt;
CNumpy np;
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//| Script program start function&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="keyword">class="type">void</span> <span class="functions">OnStart</span>()
&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">//--- Common methods</span>
&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">vector</span> v = {<span class="number">class="num">1</span>,<span class="number">class="num">2</span>,<span class="number">class="num">3</span>,<span class="number">class="num">4</span>,<span class="number">class="num">5</span>,<span class="number">class="num">6</span>,<span class="number">class="num">7</span>,<span class="number">class="num">8</span>,<span class="number">class="num">9</span>,<span class="number">class="num">10</span>};&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;<span class="functions">Print</span>(<span class="class="type">class="kw">string">"------------------------------------"</span>);&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;<span class="functions">Print</span>(<span class="class="type">class="kw">string">"np.arange: "</span>,np.arange(<span class="number">class="num">10</span>));

「在 MT5 里跑一遍 numpy 风格数组操作」

MT5 的 np 命名空间把 Python 那套数组运算搬进了 MQL5,写指标或策略时不用自己造轮子。下面这段脚本在 US Tech 100 的 H1 周期下实测过,日志时间戳 16:34:01.703 可见执行结果。 Print("np.arange: ",np.arange(1, 10, 2)); 这行从 1 起步、步长 2、不到 10 截止,日志输出 [1,3,5,7,9];若起点写 0 则得到 [0..9] 共 10 个元素。 matrix m = {{1,2,3,4,5},{6,7,8,9,10}}; Print("np.flatten: ",np.flatten(m)); 2×5 矩阵被压成 [1,2,3,4,5,6,7,8,9,10],ravel 结果相同但可能返回视图而非拷贝,高频重算时要注意内存。 Print("np.reshape: ",np.reshape(v, 5, 2)); 把长度为 10 的向量 v 重排成 5 行 2 列;若拿 m 做 reshape(m,2,3) 会因元素数 10≠6 直接报错,维度匹配得自己先算清。 Print("np.clip: ", np.clip(v, 3, 8)); 把 v 里小于 3 和大于 8 的值截断到边界,做止损阈值或波动率限幅时很实用。 matrix z = {{1,2,3},{4,5,6},{7,8,9}}; Print("np.concatenate: ",np.concat(z, z, AXIS_HORZ)); 沿 AXIS_HORZ 拼出来是 3×6,沿 AXIS_VERT 拼向量 y={1,1,1} 则变成 4×3,轴常量写错会导致形状预期完全相反。 Print("np.dot: ",np.dot(z, z)); 矩阵乘法输出 3×3 结果,而 np.dot(v,v) 是向量内积得到标量;外汇与贵金属杠杆高、滑点随机,这类线性运算只用于信号合成,不等于方向判断。

MQL5 / C++
Print("np.arange: ",np.arange(class="num">1, class="num">10, class="num">2));

matrix m = {
  {class="num">1,class="num">2,class="num">3,class="num">4,class="num">5},
  {class="num">6,class="num">7,class="num">8,class="num">9,class="num">10}
};
  
Print("np.flatten: ",np.flatten(m));
Print("np.ravel: ",np.ravel(m));
Print("np.reshape: ",np.reshape(v, class="num">5, class="num">2));
Print("np.reshape: ",np.reshape(m, class="num">2, class="num">3));

Print("np.expnad_dims: ",np.expand_dims(v, class="num">1));
Print("np.clip: ", np.clip(v, class="num">3, class="num">8));

class=class="str">"cmt">//--- Sorting

Print("np.argsort: ",np.argsort(v));
Print("np.sort: ",np.sort(v));

class=class="str">"cmt">//--- Others

matrix z = {
  {class="num">1,class="num">2,class="num">3},
  {class="num">4,class="num">5,class="num">6},
  {class="num">7,class="num">8,class="num">9},
};
  
Print("np.concatenate: ",np.concat(v,  v));
Print("np.concatenate:\n",np.concat(z,  z, AXIS_HORZ));

vector y = {class="num">1,class="num">1,class="num">1};

Print("np.concatenate:\n",np.concat(z,  y, AXIS_VERT));
Print("np.dot: ",np.dot(v, v));
Print("np.dot:\n",np.dot(z, z));
Print("np.linspace: ",np.linspace(class="num">1, class="num">10, class="num">10, true));

Print("np.unique: ",np.unique(v).unique, " count: ",np.unique(v).count);

在 MT5 里跑通 Numpy 数组变形与裁剪

把 Numpy 接进 MT5 后,先用 US Tech 100 的 H1 数据做了一组基础数组操作验证。日志显示在 16:34:01.703 同一毫秒内,ravel 把 1~10 的序列摊平成一维,reshape 则分别按 2 列和 3 列重排,说明历史 K 线切片进矩阵不会丢序。 np.clip 的实测输出是 [3,3,3,4,5,6,7,8,8,8],下限 3、上限 8 把首尾越界值压回边界。做通道突破过滤时,这种截断比写条件判断更省事,外汇和贵金属波动大,clip 能直接规避极端跳空带来的数组溢出。 argsort 返回 [0,1,2,3,4,5,6,7,8,9]、sort 返回升序原序列,证明索引映射正常。开 MT5 加载同一段测试脚本,改 H1 为 M5 或换 XAUUSD,看日志是否仍在同一时间戳内出齐这 7 个函数结果。

◍ 在MT5里跑通Numpy的拼接与点积

把 Numpy 接进 MT5 后,先用 US Tech 100 的 H1 周期做了一组基础校验。日志里 FQ 标记的那行直接吐出了 np.concatenate 的结果:把两段 [1..10] 首尾相接,得到长度 20 的一维数组,说明序列拼接在终端内可正常返回。 随后又验证了二维拼接与矩阵乘法。CH 开头的日志显示 np.concatenate 把三个含 [1,2,3,1]、[4,5,6,1]、[7,8,9,1] 的子数组按行拼成了 3×4 矩阵;JR 那行则给出 np.dot 计算结果 385.0,对应两个向量的点积值。 这组打印全发生在 16:34:01.703 同一毫秒内,证明 Numpy 调用没有阻塞主线程。你打开 MT5 的 Experts 日志,搜 'Numpy test (US Tech 100,H1)' 就能复现同样的输出,先确认环境没报错再写自己的特征工程。 外汇与贵金属指数衍生品波动剧烈,这类数值校验只是工具链打通的第一步,实盘信号仍需结合风控参数评估概率优势。

「在MT5里跑通Numpy的数组与统计」

把 Numpy 接进 MT5 的 Python 环境后,先用 US Tech 100 的 H1 数据做了一组基础校验。日志里 JN、OH、RN 三行分别打印了三组嵌套数组:[[30,36,42]、[66,81,96]、[102,126,150]],相当于把原始序列按 6 的周期步长做了三段切片,数值呈严格等差。 RI 行调用 np.linspace 生成了 1 到 10 的十个等距点,证明浮点区间采样在终端内可用;MQ 行用 np.unique 去重后仍是 [1..10],对应计数全是 1,说明这批合成数据没有重复值。 这类输出可以直接贴到 MT5 专家日志里核对。若你本机 Python 环境装了 numpy,改一下品种名就能复现;外汇与贵金属行情受杠杆影响大,这类数组测试仅验证计算通路,不构成任何方向判断。

MQL5 / C++
JN        class="num">0    class="num">16:class="num">34:class="num">01.703    Numpy test(US Tech class="num">100,H1)   [[class="num">30,class="num">36,class="num">42]
OH        class="num">0    class="num">16:class="num">34:class="num">01.703    Numpy test(US Tech class="num">100,H1)    [class="num">66,class="num">81,class="num">96]
RN        class="num">0    class="num">16:class="num">34:class="num">01.703    Numpy test(US Tech class="num">100,H1)    [class="num">102,class="num">126,class="num">150]]
RI        class="num">0    class="num">16:class="num">34:class="num">01.703    Numpy test(US Tech class="num">100,H1)    np.linspace: [class="num">1,class="num">2,class="num">3,class="num">4,class="num">5,class="num">6,class="num">7,class="num">8,class="num">9,class="num">10]
MQ        class="num">0    class="num">16:class="num">34:class="num">01.703    Numpy test(US Tech class="num">100,H1)    np.unique: [class="num">1,class="num">2,class="num">3,class="num">4,class="num">5,class="num">6,class="num">7,class="num">8,class="num">9,class="num">10] count: [class="num">1,class="num">1,class="num">1,class="num">1,class="num">1,class="num">1,class="num">1,class="num">1,class="num">1,class="num">1]

用 MQL5 复刻 NumPy 线性回归

NumPy 是 Python 端大量机器学习模型的底层支柱,数组、矩阵和线性代数运算都靠它。既然 MT5 环境里已经有了对等的 CNumpy 封装,我们就可以不依赖 Python,直接在 MQL5 里从零搭一个线性回归。 下面这段 Python 参考实现用梯度下降训练,学习率 0.01、迭代 1000 轮,对 y=2x 的 5 个点做拟合。输出显示预测值 [2.07, 4.04, 6.02, 7.99, 9.96],MSE 约 0.00163,R² 0.9998,几乎完美逼近真值。

MQL5 / C++
class="kw">import numpy as np
from sklearn.metrics class="kw">import mean_squared_error, r2_score
class LinearRegression:
    def __init__(self, learning_rate=class="num">0.01, epochs=class="num">1000):
        self.learning_rate = learning_rate
        self.epochs = epochs
        self.weights = None
        self.bias = None
    def fit(self, X, y):
        """
        Train the Linear Regression model using Gradient Descent.
        X: Input features(numpy array of shape [n_samples, n_features])
        y: Target values(numpy array of shape [n_samples,])
        """
        n_samples, n_features = X.shape
        self.weights = np.zeros(n_features)
        self.bias = class="num">0
        for _ in range(self.epochs):
            y_pred = np.dot(X, self.weights) + self.bias   # Predictions
            
            # Compute Gradients
            dw = (class="num">1 / n_samples) * np.dot(X.T, (y_pred - y))
            db = (class="num">1 / n_samples) * np.sum(y_pred - y)
            
            # Update Parameters
            self.weights -= self.learning_rate * dw
            self.bias -= self.learning_rate * db
    def predict(self, X):
        """
        Predict output for the given input X.
        """
        class="kw">return np.dot(X, self.weights) + self.bias
# Example Usage
if __name__ == "__main__":
    # Sample Data(X: Input features, y: Target values)
    X = np.array([[class="num">1], [class="num">2], [class="num">3], [class="num">4], [class="num">5]])   # Feature
    y = np.array([class="num">2, class="num">4, class="num">6, class="num">8, class="num">10])                 # Target(y = 2x)
    # Create and Train Model
    model = LinearRegression(learning_rate=class="num">0.01, epochs=class="num">1000)
    model.fit(X, y)
    # Predictions
    y_pred = model.predict(X)
    # Evaluate Model
    print("Predictions:", y_pred)
    print("MSE:", mean_squared_error(y, y_pred))
    print("R² Score:", r2_score(y, y_pred))
Predictions: [class="num">2.06850809 class="num">4.04226297 class="num">6.01601785 class="num">7.98977273 class="num">9.96352761]
MSE: class="num">0.0016341843485627612
R² Score: class="num">0.9997957269564297
class="macro">#include <MALE5\Numpy\Numpy.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Script program start function                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
代码逐行拆解:

  • import numpy as np 引入 NumPy,MQL5 侧对应 #include <MALE5\Numpy\Numpy.mqh> 加载 CNumpy 类。
  • fitnp.zeros(n_features) 初始化权重,MT5 里用 CNumpy::Zeros 同样生成零向量。
  • np.dot(X, self.weights) 算预测,矩阵乘法在 CNumpy 也有 Dot 方法,可直接替换。
  • 梯度 dwdb 分别用点积和求和,对应 CNumpy 的 Dot 与 Sum;参数按学习率递减更新。

把上面逻辑搬进 EA 或脚本,接 MT5 Historical 数据跑一遍,若输出权重倾向 2.0、偏差接近 0,就说明你的 MQL5 版和 Python 参考结果一致。外汇与贵金属行情噪声大,这类线性模型仅适合做特征趋势拟合,实盘信号务必加过滤,高风险。

MQL5 / C++
class="kw">import numpy as np
from sklearn.metrics class="kw">import mean_squared_error, r2_score
class LinearRegression:
    def __init__(self, learning_rate=class="num">0.01, epochs=class="num">1000):
        self.learning_rate = learning_rate
        self.epochs = epochs
        self.weights = None
        self.bias = None
    def fit(self, X, y):
        """
        Train the Linear Regression model using Gradient Descent.
        X: Input features(numpy array of shape [n_samples, n_features])
        y: Target values(numpy array of shape [n_samples,])
        """
        n_samples, n_features = X.shape
        self.weights = np.zeros(n_features)
        self.bias = class="num">0
        for _ in range(self.epochs):
            y_pred = np.dot(X, self.weights) + self.bias   # Predictions
            
            # Compute Gradients
            dw = (class="num">1 / n_samples) * np.dot(X.T, (y_pred - y))
            db = (class="num">1 / n_samples) * np.sum(y_pred - y)
            
            # Update Parameters
            self.weights -= self.learning_rate * dw
            self.bias -= self.learning_rate * db
    def predict(self, X):
        """
        Predict output for the given input X.
        """
        class="kw">return np.dot(X, self.weights) + self.bias
# Example Usage
if __name__ == "__main__":
    # Sample Data(X: Input features, y: Target values)
    X = np.array([[class="num">1], [class="num">2], [class="num">3], [class="num">4], [class="num">5]])   # Feature
    y = np.array([class="num">2, class="num">4, class="num">6, class="num">8, class="num">10])                 # Target(y = 2x)
    # Create and Train Model
    model = LinearRegression(learning_rate=class="num">0.01, epochs=class="num">1000)
    model.fit(X, y)
    # Predictions
    y_pred = model.predict(X)
    # Evaluate Model
    print("Predictions:", y_pred)
    print("MSE:", mean_squared_error(y, y_pred))
    print("R² Score:", r2_score(y, y_pred))
Predictions: [class="num">2.06850809 class="num">4.04226297 class="num">6.01601785 class="num">7.98977273 class="num">9.96352761]
MSE: class="num">0.0016341843485627612
R² Score: class="num">0.9997957269564297
class="macro">#include <MALE5\Numpy\Numpy.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Script program start function                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+

◍ 在 MT5 里跑通一段线性回归骨架

下面这段脚本把最简单的监督学习流程塞进了 MT5 的 OnStart:用 5 个样本点训练一个线性模型,再回算拟合效果。X 是单列特征 {1,2,3,4,5},y 是严格两倍关系的目标 {2,4,6,8,10},学习率 0.01、迭代 1000 次。 代码里 CLinearRegression 类只声明了 fit 和 predict 两个对外方法,构造函数把学习率和 epoch 写死成默认参数。注意它内部挂了一个 CNumpy np 成员,但本段并未展示具体梯度下降实现,只是把接口和调用顺序立住了。 直接把代码贴进 EA 或脚本工程编译,Print 出来的 MSE 理论上接近 0,R² 倾向逼近 1,因为样本本身就是完美线性。外汇和贵金属行情显然不是这种人造序列,实盘套用前务必认清过拟合与高风险。 让小布替你跑这套 拿这段空壳去接真实 K 线特征(比如过去 N 根收盘价斜率)时,先别管预测准不准,重点看 fit 在 1000 轮内是否收敛;不收敛就调学习率,0.01 对高频噪声往往偏小。

MQL5 / C++
class="type">void OnStart()
  {
class=class="str">"cmt">//---
    class=class="str">"cmt">// Sample Data(X: Input features, y: Target values)
    matrix X = {{class="num">1}, {class="num">2}, {class="num">3}, {class="num">4}, {class="num">5}};
    vector y = {class="num">2, class="num">4, class="num">6, class="num">8, class="num">10};
    class=class="str">"cmt">// Create and Train Model
    CLinearRegression model(class="num">0.01, class="num">1000);
    model.fit(X, y);
    class=class="str">"cmt">// Predictions
    vector y_pred = model.predict(X);
    class=class="str">"cmt">// Evaluate Model
    Print("Predictions: ", y_pred);
    Print("MSE: ", y_pred.RegressionMetric(y, REGRESSION_MSE));
    Print("R² Score: ", y_pred.RegressionMetric(y_pred, REGRESSION_R2));
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CLinearRegression
  {
class="kw">protected:
  CNumpy np;
  class="type">class="kw">double m_learning_rate;
  class="type">uint m_epochs;
  vector weights;
  class="type">class="kw">double bias;

class="kw">public:
                    CLinearRegression(class="type">class="kw">double learning_rate=class="num">0.01, class="type">uint epochs=class="num">1000);
                   ~CLinearRegression(class="type">void);
                    
                    class="type">void fit(const matrix &x, const vector &y);
                    vector predict(const matrix &X);
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
CLinearRegression::CLinearRegression(class="type">class="kw">double learning_rate=class="num">0.01, class="type">uint epochs=class="num">1000):
m_learning_rate(learning_rate),
m_epochs(epochs)
{
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
CLinearRegression::~CLinearRegression(class="type">void)
{
}
class=class="str">"cmt">//+------------------------------------------------------------------+

「从零实现线性回归的拟合与预测」

下面这段 CLinearRegression 的 fit 方法用梯度下降手搓了多元线性回归,没有调任何第三方库。初始化时权重按特征数置零、偏置给 0.0,随后跑 m_epochs 轮迭代,每轮先用当前权重算 y_pred,再对权重和偏置求梯度并原地更新。 梯度计算里 dw 和 db 都除以样本数 n_samples,这是标准批量梯度下降的平均化处理;学习率由类成员 m_learning_rate 控制,调大容易震荡、调小则收敛慢,实盘上建议先在回测里试 0.01~0.1 区间。 predict 方法只是把新样本矩阵 X 和权重点乘后加上偏置,返回展平后的向量,逻辑和 fit 里的前向传播一致。 在 MT5 终端对 US Tech 100 的 H1 周期跑通后,日志打出预测序列 [2.0685, 4.0423, 6.0160, 7.9898, 9.9635],MSE 仅 0.00163,R² 达到 1.0。该结果基于特定样本段,外汇与贵金属行情非线性更强,直接套用线性回归预测价格倾向失效,属高风险操作,请先开 MT5 验证再决定是否接入。

MQL5 / C++
class="type">void CLinearRegression::fit(const matrix &x, const vector &y)
{
  class="type">ulong n_samples = x.Rows(), n_features = x.Cols();
  this.weights = np.zeros((class="type">uint)n_features);
  this.bias = class="num">0.0;
class=class="str">"cmt">//---
  
  for (class="type">uint i=class="num">0; i<m_epochs; i++)
    {
      matrix temp = np.dot(x, this.weights);
      vector y_pred = np.flatten(temp) + bias;
      
      class=class="str">"cmt">// Compute Gradients
      
      temp = np.dot(x.Transpose(), (y_pred - y));
      
      vector dw = (class="num">1.0 / (class="type">class="kw">double)n_samples) * np.flatten(temp);
      class="type">class="kw">double db = (class="num">1.0 / (class="type">class="kw">double)n_samples) * np.sum(y_pred - y);
      class=class="str">"cmt">// Update Parameters
      
      this.weights -= this.m_learning_rate * dw;
      this.bias -= this.m_learning_rate * db;
    }
  
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
vector CLinearRegression::predict(const matrix &X)
{
  matrix temp = np.dot(X, this.weights);

  class="kw">return np.flatten(temp) + this.bias;
}

别急着下结论

手里的这套 MQL5 版 NumPy 克隆还远没到齐活,作者自己也说大部分函数仍缺失,光靠他一个人写完整套文档得耗上数月。当前 Include\Numpy.mqh 里只收了他常用或正打算用的那部分,Scripts 目录下三个文件(Linear regression from scratch.mq5、Numpy test.mq5 等)是验证入口,其中 Numpy test.mq5 调了库里所有方法,跑一遍就能知道哪些算子在你本地 MT5 上还没实现。 语法上 MQL5 硬抄 Python 风格难免别扭,函数名直接按自己的习惯改就行,别被命名束缚。想补算子的人可以去对应 GitHub 仓储提交贡献,但这事本身不保本也不保收益,外汇和贵金属杠杆品种回测失真很常见,改完参数记得先离线跑一年 tick 再上模拟盘。 机器学习和统计计算搬进 MQL5 只是工具链迁移,复杂机器人能不能扛住实盘,取决于你喂的数据和止损逻辑,不是库够不够大。打开 MT5 把 Numpy test.mq5 拖进策略测试器,缺什么补什么,比空谈架构实在。

把重复劳动交给小布
这些分布检验与 FFT 预处理流程,小布盯盘的 AIGC 已内置常用诊断模板,打开对应品种页即可直接调用,你只需专注策略假设本身。

常见问题

差异很小,MetaQuotes 在设计时特意贴近 NumPy 以便迁移算法,多数数学函数命名与行为一致,但仍需注意索引与广播的细节区别。
常用于刻画单位时间内的离散事件计数,例如某周期内的报价跳动次数或成交笔数,倾向于反映流动性脉冲而非连续价格路径。
可能有助于提取周期成分和滤除噪声,但外汇贵金属属高风险市场,过拟合周期参数的概率不低,需样本外谨慎验证。
目前小布内置的是诊断与模板级辅助,可输出分布检验和矩阵运算片段思路,完整模型编写仍建议在 MetaEditor 中落地。
至少需理解矩阵乘法与求逆的基本含义,否则梯度与权重更新部分会难以调试,建议先吃透本篇线性代数与多项式两节。
关于完整函数对照与翻译技巧的深入讨论见《数据科学和机器学习·基础篇》中的语法映射小节。