使用经典机器学习方法预测汇率:逻辑回归(logit)模型和概率回归(probit)模型(基础篇)
◍ 用 logit 与 probit 给汇率做概率预判
在 MT5 里做汇率方向预测,逻辑回归(logit)和概率回归(probit)是两条最朴素的经典机器学习路径。两者都把行情特征压缩成一个 0 到 1 之间的概率值:接近 1 代表下一根 K 线上涨倾向较高,接近 0 则下跌概率偏大。 logit 用 logistic 函数把线性组合映射到概率,probit 则换成正态累积分布函数。对外汇和贵金属这类高波动品种,输出的是条件概率而非确定信号,实盘仍需配合仓位与止损。 一篇 2025 年 7 月 21 日发布于 MT5 社区的 EA 文章给出参考实现,原文显示基础版在回测中获 509 次访问与 9 条互动,说明该类模型在零售交易者中确有可验证的受众需求。 开 MT5 新建 EA,把收盘价斜率、波动率分位等特征喂进这两类模型,先跑 EURUSD 的 M15 看概率输出是否和反转点吻合,比直接信信号更靠谱。
「从二选一开始的行情预测切入」
做外汇或贵金属预测,最容易陷进模型堆里出不来。把问题压到最薄——「明天收盘价比今天高还是低」——就自然落到二元分类上。逻辑回归(logit)和概率回归(probit)结构简单、资料多,是有监督学习里最该先跑通的两类。 有监督学习本质是学一个映射:输入 {x} 是特征,输出 {y} 是标签。这里我们只判两种状态,涨标 1、跌标 0,即 y ∊ {1,0}。特征用标准化且带指定滞后的价格增量,也就是价格模式,凑成 {x,y} 训练集去估参数。 基于训好的分类器,预测模型落地成一个叫 LogitExpert 的 EA。MT5 里加载它,先用历史数据跑一遍,看二分类命中率是否明显高过 0.5 的随机线,再谈调滞后阶数。外汇贵金属杠杆高,模型仅给概率倾向,不构成方向保证。
线性概率崩在边界,logit与probit怎么兜底
线性概率模型把成功概率写成解释变量的线性组合:P = w0*1 + w1x1 + … + wkxk。它最致命的问题是预测值会跑出 [0,1] 区间,比如样本外推时算出一宗贵金属突破概率 1.3,根本没法当概率用。 probit 用标准正态累积分布 F(·) 把线性输出 x_n w 压回 [0,1],logit 则用逻辑函数 L(z)=exp(z)/(1+exp(z))。两者激活曲线在 [-1.2, 1.2] 内几乎重合,所以只要前激活值不极端,两个模型给的方向概率差不了多少。 实际盯盘里,把特征向量(如动量、波动率分位)代进去算 μ_n,就能得到下一根 K 线上涨的倾向概率。外汇与贵金属杠杆高、跳空频繁,μ_n 接近 0 或 1 时两个模型分歧会拉大,这时候别只看一个点,最好用 MT5 把两种激活都跑一遍比对。
◍ 训练集怎么从K线里抠出来
建模前得先把特征做出来并标准化,再喂给寻优函数。GetDataset 就干这个活:InpCount_ 是训练样本数,lag_ 是用作特征的滞后价格增量个数,X 算特征、y 算标签,start 是从第几根 K 线起采。 拿滞后收益当特征最直观。lag_ 设 4,特征就是 {return-4, return-3, return-2, return-1},实际能用的样本数是 (InpCount_ - lag_),少掉的那几根被滞后窗口吃掉了。 代码里先 CopyRates 取 OHLC,y 从 start+1 取 InpCount_ 根,X 从 start+2 取 InpCount_-1 根,转置后按收盘价>开盘价标 1、否则 0。标签后移 lag_ 步,特征矩阵用每根 K 线的收盘减开盘填,最后按最后一列均值和标准差做 z-score 标准化。 外汇和贵金属波动受杠杆与消息面驱动,标准化只是数学处理,不预示方向。跑通这段代码你能在 MT5 里直接拿到 Input_X 和 Target_y,接下一步参数估计。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|Get data for analysis: features and corresponding labels | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool GetDataset(class="type">int InpCount_,class="type">int lag_,class="type">int start,matrix &Input_X,vector & Target_y,class="type">class="kw">string X,class="type">class="kw">string y) { matrix rates; matrix target; target.CopyRates(y, PERIOD_CURRENT, COPY_RATES_OHLC, start+class="num">1, InpCount_); rates.CopyRates(X, PERIOD_CURRENT, COPY_RATES_OHLC, start+class="num">2, InpCount_-class="num">1); rates = rates.Transpose(); target = target.Transpose(); class="type">int Class_ []; ArrayResize(Class_,InpCount_); for(class="type">int i=class="num">0; i<InpCount_; i++) { if(target[i,class="num">3] > target[i,class="num">0]) Class_[i] = class="num">1; else Class_[i] = class="num">0; } vector label=vector::Zeros(InpCount_-lag_); for(class="type">int i=class="num">0; i<InpCount_-lag_; i++) { label[i] = Class_[i+lag_]; class=class="str">"cmt">// class label } matrix returns=matrix::Zeros(InpCount_-lag_, lag_); for(class="type">int j=class="num">0; j<lag_; j++) { for(class="type">int i=class="num">0; i<InpCount_-lag_; i++) { returns[i,j] =rates[i+j,class="num">3] - rates[i+j,class="num">0] ; class=class="str">"cmt">// Input Data } } vector cols_mean=returns.Mean(class="num">0); vector cols_std=returns.Std(class="num">0); mean_ = cols_mean[lag_-class="num">1]; std_ = cols_std[lag_-class="num">1]; for(class="type">int j=class="num">0; j<lag_; j++) { for(class="type">int i=class="num">0; i<InpCount_-lag_; i++) { returns[i,j] = (returns[i,j] - cols_mean[lag_-class="num">1])/cols_std[lag_-class="num">1]; } } Input_X = returns; Target_y = label; class="kw">return true; }
「在 MT5 里亲手估参数才看得见显著性」
最大似然是二元分类里最顺手的参数估计路线。logit 与 probit 都假定标签 y 服从伯努利分布,把对数似然取负就得到负对数似然(NLL),正好对接 L-BFGS 这类默认「最小化」的拟牛顿优化器,Alglib 里已直接可用。 光拿到参数还不够,标准误才是判断特征是否真有统计显著性的钥匙。scikit-learn 的 logit 接口不吐这东西,而我在 MQL 里自写模型后能顺手算出协方差矩阵、再导出标准差,哪些特征在拽预测一目了然。另一个硬需求是动态性:模型得能在每根 K 线或任意周期上重估参数,而不是靠 ONNX 把离线训练的静态包搬进来。 过拟合在这类线性分类器上常表现为参数被推得异常大。加一道 L2 正则化即可压住:损失函数后补一项 λ·‖w‖²,λ=1/C 且 C∈(0,1],C 越小惩罚越狠。FitLogitRegression 里 L2 默认关、C=1.0、probit 默认 false,另收一个卡方显著性水平 alpha,函数会自动补一列恒为 1 的哑变量来估截距 w₀。 下面这段是目标函数类,把 NLL 在 MT5 里落了地。注意 probit_ 开关切正态累积分布与 sigmoid,并对 0/1 激活值夹到 0.001/0.999 防对数算崩。外汇与贵金属波动大、样本非平稳,这类模型过拟合风险高,参数显著性得结合样本外验证再看。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Derived class from CNDimensional_Func | class=class="str">"cmt">//+------------------------------------------------------------------+ class CNDimensional_Logit : class="kw">public CNDimensional_Func { class="kw">public: CNDimensional_Logit(class="type">void) {} ~CNDimensional_Logit(class="type">void) {} class="kw">virtual class="type">void Func(CRowDouble &w,class="type">class="kw">double &func,CObject &obj); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Objective Function: Logit Negative loglikelihood | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CNDimensional_Logit::Func(CRowDouble &w,class="type">class="kw">double &func,CObject &obj) { class="type">class="kw">double LLF[],probit[],probitact[]; vector logitact; ArrayResize(LLF,Rows_); ArrayResize(probit,Rows_); vector params=vector::Zeros(Cols_); for(class="type">int i = class="num">0; i<Cols_; i++) { params[i] = w[i]; class=class="str">"cmt">// vector of parameters } vector logit=vector::Zeros(Rows_); logit = Input_X_gl.MatMul(params); for(class="type">int i=class="num">0; i <Rows_; i++) { probit[i] = logit[i]; } if(probit_) MathCumulativeDistributionNormal(probit,class="num">0,class="num">1,probitact); class=class="str">"cmt">// Probit activation else logit.Activation(logitact,AF_SIGMOID); class=class="str">"cmt">// Logit activation class=class="str">"cmt">//--------------------to avoid NAN error when calculating logarithm ------------------------------------ if(probit_) { for(class="type">int i = class="num">0; i<Rows_; i++) { if(probitact[i]==class="num">1) probitact[i]= class="num">0.999; if(probitact[i]==class="num">0) probitact[i]= class="num">0.001; } } else { for(class="type">int i = class="num">0; i<Rows_; i++) { if(logitact[i]==class="num">1) logitact[i]= class="num">0.999; if(logitact[i]==class="num">0)