Python中的虚假回归(伪回归)·综合运用
🔌

Python中的虚假回归(伪回归)·综合运用

(3/3)· 把前两步的检验与建模收口到交易端,看伪回归模型如何被真实行情拆穿

新手友好 第 3/3 篇
很多人在 Python 里跑出低误差就直接上线 EA,却从没验证过输入和输出是否真有关联。伪回归模型靠错误原因也能给出漂亮回测,实盘一跑就露馅。先问一句:你假设的关系,真的检验过吗?

把欧元美元模型落地成 ONNX 双精度文件

ONNX 是一套开放模型交换格式,核心作用是让训练好的机器学习模型脱离原框架,在支持该格式的平台间直接迁移。对 MT5 交易者来说,这意味着用 Python 训好的线性回归模型,可以转成文件后交给其他推理端调用,不必把整套训练环境搬进终端。 下面这段脚本干的事很具体:先把输入特征列数从 DataFrame 里取出来,定义成双精度张量;再把 sklearn 的线性回归模型 lm 转成 ONNX,运算符集锁在 12 版;最后存成 EURUSD_ONNX_Double.onnx。特征维度用 csv.loc[:,predictors].shape[1] 动态决定,第一维 None 表示样本数不固定,适合按批推理。 双精度(DoubleTensorType)在这里不是凑字样。外汇价格本身小数位多,若用单精度导出,EURUSD 这类品种在 1.0800 附近的微小波动可能在推理端被截断,回测与实盘信号容易漂移。 实盘接这套文件前,先在 Python 侧用 onnxruntime 跑一遍相同样本,比对输出与 sklearn 原生 predict 的误差;外汇与贵金属杠杆高、滑点随机,模型互换格式只是工程环节,不预示任何方向胜率。

MQL5 / C++
initial_type_double = [(&class="macro">#x27;double_input&class="macro">#x27;, DoubleTensorType([None, csv.loc[:,predictors].shape[class="num">1]]))]
onnx_model_double = convert_sklearn(lm, initial_types=initial_type_double, target_opset=class="num">12)
onnx_model_filename = "EURUSD_ONNX"
onnx_filename=onnx_model_filename+"_Double.onnx"
onnx.save_model(onnx_model_double, onnx_filename)

「用 MetaEditor 把 ONNX 模型塞进 EA 骨架」

在 MT5 里做自动化交易,EA 本质是一段 MQL5 脚本,难点不在下单逻辑,而在把训练好的机器学习模型接进来。MetaEditor 自带编译和调试环境,能直接把 ONNX 文件作为资源嵌入,省去运行时外部读取的麻烦。 代码开头先 #include <Trade\Trade.mqh>,这是 MT5 处理开平仓的标准库。引入 CTrade 对象后,后续下单、改止损都走预定义方法,不用自己拼交易事件结构。 #resource 指令把 EURUSD_ONNX_Double.onnx 以 uchar 数组形式编进 EA,运行时用 ONNXHandle 持有模型句柄,初始置 INVALID_HANDLE,等加载成功才被改写。PredictedMove 这类预测变量先给 -1,代表模型还没吐结果,后面跑完推理再覆盖。 指标端声明了 ma_handle / ma_reading[] 和 ao_handle / ao_reading[],分别接移动平均线和 Awesome 振荡器。句柄拿数据、数组存历史值,EA 就能在每根 K 线拿它们和模型输出交叉验证。 注意输入参数里 sl_width=150 是点数止损宽度,positions=1 控制同时开仓数,lot_multiple=1 表示按最小手数倍数下量。外汇和贵金属杠杆高,模型预测仅提供概率倾向,实盘前务必在策略测试器用历史数据跑通。

MQL5 / C++
class=class="str">"cmt">//Our trade class helps us open trades
class="macro">#include <Trade\Trade.mqh>
CTrade Trade;
class=class="str">"cmt">//Loading our ONNX model
class="macro">#resource "ONNX\\EURUSD_ONNX_Double.onnx" as class="type">uchar EURUSD_ONNX_MODEL[]
class="type">long    ONNXHandle=INVALID_HANDLE;
class=class="str">"cmt">//Handles for our technical indicators and dynamic arrays to store their readings
class="type">int ma_handle;
class="type">class="kw">double ma_reading[];
class="type">int ao_handle;
class="type">class="kw">double ao_reading[];
class=class="str">"cmt">//Inputs
class="type">int class="kw">input sl_width = class="num">150; class=class="str">"cmt">//How wide should the stoploss be?
class="type">int class="kw">input  positions = class="num">1; class=class="str">"cmt">//How many positions should the we open?
class="type">int class="kw">input lot_multiple = class="num">1; class=class="str">"cmt">//How many times greater than minimum lot should each position be?
class=class="str">"cmt">//Symbol variables
class="type">class="kw">double min_volume;
class="type">class="kw">double bid,ask;
class=class="str">"cmt">//We&class="macro">#x27;ll use this time stamp to keep track of the number of candles passing
class="kw">static class="type">class="kw">datetime time_stamp;
class=class="str">"cmt">//Our model&class="macro">#x27;s forecast will be stored here
vector model_forecast(class="num">1);

◍ EA 挂载时 OnInit 做了什么

把 EA 拖到 MT5 图表上的一瞬间,OnInit() 就会跑一遍,它决定这个机器人能不能活下来。我们这段逻辑强制只认 EURUSD 的 M1 周期:不是这个品种或周期,直接 Print 提示并 return(INIT_FAILED),初始化当场中断。 接着从静态缓冲区 ONNXModel 里用 OnnxCreateFromBuffer 建模型,拿到 ONNXHandle。若等于 INVALID_HANDLE,把 GetLastError() 打出来再失败返回——这一步没过,后面机器学习推理全免谈。 输入张量形状写死为 {1,5},即一行五列;输出为 {1,1},单个标量。两者分别用 OnnxSetInputShape / OnnxSetOutputShape 校验,任一失败同样 INIT_FAILED。这里 5 个输入通常对应 MA、AO 等特征拼接,输出 1 维多为方向概率。 最后挂两个 M1 指标:20 周期收盘价 EMA(iMA 句柄给 ma_handle)和 Awesome 振荡器(iAO 句柄给 ao_handle),再取经纪商最小合约 min_volume。全部就绪才 return(INIT_SUCCEEDED)。 别把品种周期写死当万能 真要换品种测,至少把 "EURUSD" 和 PERIOD_M1 两处判断抽成外部输入,否则换图表必 INIT_FAILED,排查半天还以为模型坏了。

MQL5 / C++
class="type">int OnInit(){
   class=class="str">"cmt">//Validating trading conditions
   if(_Symbol!="EURUSD" || _Period!=PERIOD_M1)
     {
       Print("Model must work with EURUSD on the M1 timeframe");
       class="kw">return(INIT_FAILED);
     }
ONNXHandle=OnnxCreateFromBuffer(ONNXModel,ONNX_DEFAULT);
if(ONNXHandle==INVALID_HANDLE)
{
   Print("OnnxCreateFromBuffer error ",GetLastError());
   class="kw">return(INIT_FAILED);
}
class=class="str">"cmt">//Defining the model&class="macro">#x27;s class="kw">input shape
const class="type">long input_shape[] = {class="num">1,class="num">5};
if(!OnnxSetInputShape(ONNXHandle,ONNX_DEFAULT,input_shape)) 
{
    Print("OnnxSetInputShape error ",GetLastError());
    class="kw">return(INIT_FAILED); 
}
class=class="str">"cmt">//Defining the model&class="macro">#x27;s output shape
const class="type">long output_shape[] = {class="num">1,class="num">1};
if(!OnnxSetOutputShape(ONNXHandle,class="num">0,output_shape))
{
    Print("OnnxSetOutputShape error ",GetLastError());
    class="kw">return(INIT_FAILED);
}
class=class="str">"cmt">//Setting up our technical indicators
ma_handle = iMA(_Symbol,PERIOD_M1,class="num">20,class="num">0,MODE_EMA,PRICE_CLOSE);
ao_handle = iAO(_Symbol,PERIOD_M1);
min_volume = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
class="kw">return(INIT_SUCCEEDED);
}

EA卸载时怎么干净释放ONNX资源

MT5 的 EA 在被手动移除、切换周期或终端关闭时,会触发 OnDeinit。这一步若偷懒不清理,加载过的 ONNX 模型句柄会一直占着内存,多次重载策略后终端变卡的概率明显上升。 看代码逻辑:先判断 ONNXHandle 是否不等于 INVALID_HANDLE。若模型此前已初始化,就调 OnnxRelease 把模型资源交还系统,并立刻把句柄置回 INVALID_HANDLE,避免后续误用悬空指针。 最后一行 ExpertRemove 让 EA 自身退出。整套动作不复杂,但能直接压住内存泄漏,属于写 AI 策略 EA 时不能省的底子工程。外汇与贵金属杠杆高,模型崩在资源没清干净的环境里,复盘都难定位。

MQL5 / C++
class="type">void OnDeinit(const class="type">int reason)
  {
    class=class="str">"cmt">//OnDeinit we should freeup resources we don&class="macro">#x27;t require
    class=class="str">"cmt">//We&class="macro">#x27;ll begin by releasing the ONNX models then removing the Expert Advisor
    if(ONNXHandle!=INVALID_HANDLE)
     {
      OnnxRelease(ONNXHandle);
      ONNXHandle=INVALID_HANDLE;
     }
   class=class="str">"cmt">//Lastly remove the Expert Advisor
   ExpertRemove();
  }

「行情Tick里喂给模型的最新指标快照」

EA 里 OnTick 是每次收到新报价就触发的入口。想在外汇或贵金属这种高波动品种上做实时机器学习预测,就得在这里把指标数据刷成最新,否则模型看到的永远是旧盘面。 具体做法是用 CopyBuffer 从 MA 和 AO 两个指标句柄各取最近 10 个值进数组,再用 ArraySetAsSeries 把数组倒序——索引 0 是当前值,越往后越老。这样后面写逻辑不用自己算偏移,直接 ma_reading[0] 就是眼前这根。 时间锚点用 iTime(_Symbol, PERIOD_M1, 0) 拿当前 M1 柱时间。只要它和上次存的 time_stamp 不同,就说明来了新柱,此时若无持仓调 ModelForecast 让模型出信号,有持仓则走 ManageTrade 管风控。注意外汇/贵金属杠杆高,模型误判可能瞬间放大亏损,这段代码只解决「何时喂数」,不保证信号对。 下面这段是 MT5 里可直接贴进 EA 的 OnTick 骨架,逐行拆完你能马上改参数跑: void OnTick() 定义每次报价执行的函数体 // 前两行注释:把技术指标数组更新到当前值,并设为序列方便取最新 CopyBuffer(ma_handle,0,0,10,ma_reading); 从 MA 句柄取 0~9 共 10 个缓冲写进 ma_reading ArraySetAsSeries(ma_reading,true); 把 ma_reading 设为时间序列,索引 0 为最新 CopyBuffer(ao_handle,0,0,10,ao_reading); 同上取 AO 振荡器最近 10 值 ArraySetAsSeries(ao_reading,true); AO 数组也倒序 ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); 取当前品种卖一价 bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); 取买一价 // Update time marker 注释:更新时间标记 datetime current_time = iTime(_Symbol, PERIOD_M1, 0); 取本品种 M1 当前柱开盘时间 if(time_stamp != current_time){ 若时间和上次不同,即新 K 线 // No open positions 注释分支 if(PositionsTotal() == 0){ 若无任何持仓 ModelForecast(); 调用模型预测函数 } // We have open positions to manage 注释分支 else if(PositionsTotal() > 0){ 若有持仓 ManageTrade(); 调用持仓管理函数 } } 把 10 改成 20 就能让模型看更长窗口,但 Tick 内复制开销随数组变长线性增加,实盘前建议在 MT5 策略测试器数一下单 Tick 耗时。

MQL5 / C++
class="type">void OnTick()
{
   class=class="str">"cmt">//Update the arrays storing the technical indicator values to their current values
   class=class="str">"cmt">//Then set the arrays as series so that the current reading is at the top and the oldest reading is last
   CopyBuffer(ma_handle,class="num">0,class="num">0,class="num">10,ma_reading);
   ArraySetAsSeries(ma_reading,true);
   CopyBuffer(ao_handle,class="num">0,class="num">0,class="num">10,ao_reading);
   ArraySetAsSeries(ao_reading,true);
   ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
   bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
class=class="str">"cmt">//Update time marker
   class="type">class="kw">datetime current_time = iTime(_Symbol,PERIOD_M1,class="num">0);
   class=class="str">"cmt">//Periodically make forecasts if we have no open positions
   if(time_stamp != current_time){
      class=class="str">"cmt">//No open positions
      if(PositionsTotal() == class="num">0){
         ModelForecast();
      }
      class=class="str">"cmt">//We have open positions to manage
      else if(PositionsTotal() > class="num">0){
         ManageTrade();
      }
   }
}

◍ EA 里跑 ONNX 推理的那几行

把训练好的 ONNX 模型接进 MT5 专家顾问,核心就是 ModelForecast 这一个 void 函数。它不接收参数,只在内部拼好输入向量、调 OnnxRun、把结果丢给下一步。 输入特征固定为 5 个:当前 M1 K 线的高、低、收,以及 ma_reading[0](20 周期 EMA 收盘价)和 ao_reading[0](Awesome 振荡器)。注意周期写死在 PERIOD_M1,换周期得改代码而非传参。 OnnxRun 用了 ONNX_NO_CONVERSION 标志,意味着推理时不自动转数据类型,输入向量必须是模型期望的精度,否则会进 if 分支打错误码。推理失败只 Print 不中断,成功才顺带调用 NextMove() 推进交易逻辑。 别把正态当圣经:这段没有任何风控或阈值判断,model_forecast 打印出来后怎么用完全在 NextMove 里。外汇与贵金属杠杆高、模型误判概率不低,实盘前务必在策略测试器用历史 Tick 验证输出分布。

MQL5 / C++
class=class="str">"cmt">//This function provides these utilities:
class=class="str">"cmt">//			class="num">1) Inferencing using our ONNX model 
class=class="str">"cmt">//			class="num">2) Calling the next function responsible for intepreting model forecasts and other subroutines
class="type">void ModelForecast(class="type">void){
		class=class="str">"cmt">//These are the inputs for our ONNX model:
		class=class="str">"cmt">//		class="num">1)High 
		class=class="str">"cmt">//		class="num">2)Low
		class=class="str">"cmt">//		class="num">3)Close
		class=class="str">"cmt">//		class="num">4)class="num">20 PERIOD MA MODE: EMA	APPLIED_PRICE:PRICE CLOSE 
		class=class="str">"cmt">//		class="num">5)Awesome Oscilator
		vector model_input{iHigh(_Symbol,PERIOD_M1,class="num">0),iLow(_Symbol,PERIOD_M1,class="num">0),iClose(_Symbol,PERIOD_M1,class="num">0),ma_reading[class="num">0],ao_reading[class="num">0]};
		class=class="str">"cmt">//Inferencing with our model
		if(!OnnxRun(ONNXHandle,ONNX_NO_CONVERSION,model_input,model_forecast)){
				Print("Error performing inference: ",GetLastError());
		}
		class=class="str">"cmt">//Pring model forecast to the terminal
		else{
					 Print(model_forecast);
					NextMove();
		}
}

预测输出怎么被翻译成下单动作

模型跑完之后不会自己下单,得有人把那一串 forecast 数值读成「涨过现价」或「跌过现价」的判定。NextMove 干的就是这个翻译兼调度:它先后用方向参数 1 和 -1 去问 InterpretForecast,拿到 true 才去调 CheckOrder,方向错了就换另一边试。 InterpretForecast 的逻辑很硬:只认 model_forecast[0] 这一个元素,direction=1 时要求它大于 1,direction=-1 时要求它小于 1,其他任何入参直接返回 false。也就是说模型输出若不在「明显大于/小于 1」的区间里,这套解析就当没看见。 外汇与贵金属波动剧烈、滑点随机,这种非黑即白的阈值判断可能漏掉大量模糊信号;实盘前建议在 MT5 里把 model_forecast[0] 的分布打印出来,确认大于 1 和小于 1 的样本占比,避免函数长期返回 false 而从不触发。 下面这段是原始实现,逐行看更清楚调度关系:NextMove 先查多头解释,命中就 CheckOrder(1) 并 return;不命中再查空头解释,命中才 CheckOrder(-1)。InterpretForecast 里两个 if 分别管正负方向,末尾 false 是兜底。

MQL5 / C++
class=class="str">"cmt">//This function provides these utilities:
class=class="str">"cmt">//			class="num">1) Getting the model output intepreted
class=class="str">"cmt">//			class="num">2) Acting on the intepretations
class="type">void NextMove(){
		if(InterpretForecast(class="num">1)){
			CheckOrder(class="num">1);
			class="kw">return;
		}		 
		else if(InterpretForecast(-class="num">1)){
			CheckOrder(-class="num">1);
		}
}
class=class="str">"cmt">//This function provides these utilities:
class=class="str">"cmt">//			class="num">1) Check whether the model forecasted a reading greater than or less than class="num">1.
class="type">bool InterpretForecast(class="type">int direction){
		class=class="str">"cmt">//class="num">1 means check if the model is forecasting growth greater than the current price
		if(direction == class="num">1){
			class="kw">return(model_forecast[class="num">0] > class="num">1);
		}
		class=class="str">"cmt">//-class="num">1 means check if the model is forecasting growth less than the current price
		if(direction == -class="num">1){
			class="kw">return(model_forecast[class="num">0] < class="num">1);
		}
		class=class="str">"cmt">//Otherwise class="kw">return class="kw">false.
		class="kw">return class="kw">false;
}

「无持仓时的定向开仓与动态止损止盈」

EA 把开仓逻辑收敛进 CheckOrder 函数:只有当账户里 PositionsTotal() 返回 0,也就是当前没有任何未平头寸时,才允许按 order_direction 参数发单。order_direction 传 1 走买入分支,传 -1 走卖出分支,两个分支都再用一次 PositionsTotal()==0 做冗余校验,避免同根信号重复触发。 开仓不是一次只下一单。代码里用 for 循环跑 positions 次,每次调用 Trade.PositionOpen,手数固定为 min_volume * lot_multiple,买用 ask 价、卖用 bid 价,注释标了魔法号 "Volatitlity Doctor AI"。你在 MT5 策略测试器里把 positions 设成 3,就可能看到同一信号下连续铺 3 个同方向头寸。 持仓后的风控交给 ManageTrade,它只做一件事——调 CheckStop。CheckStop 倒序遍历所有持仓,用 PositionGetSymbol 取每个仓的符号,先卡一道 _Symbol == symbol,不是当前图表品种的头寸一律跳过。 通过品种校验后,函数按 sl_width 和点值 _Point 算新止损止盈:买仓基于卖价重算、卖仓基于买价重算,且只在“新水平比原有更优”时才改。NormalizeDouble 按 _Digits 修精度,确有必要才 Trade.PositionModify。外汇与贵金属杠杆高,这类自动挪损盈在跳空时可能失效,实盘前先用历史数据回测确认。

MQL5 / C++
class=class="str">"cmt">//This function is responsible for opening positions
class="type">void CheckOrder(class="type">int order_direction){
    class=class="str">"cmt">//Only open new positions if we have no open positions
    if(PositionsTotal() == class="num">0){
        class=class="str">"cmt">//Buy
        if(order_direction == class="num">1){
            class=class="str">"cmt">//Iterate over the desired number of positions
            for(class="type">int i = class="num">0; i < positions; i++){
                Trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,min_volume * lot_multiple,ask,class="num">0,class="num">0,"Volatitlity Doctor AI");
            }
        }
        class=class="str">"cmt">//Sell
        else if(order_direction == -class="num">1 && PositionsTotal() == class="num">0){
            class=class="str">"cmt">//Iterate over the desired number of positions
            for(class="type">int i = class="num">0; i < positions; i++){
                Trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,min_volume  * lot_multiple ,bid,class="num">0,class="num">0,"Volatitlity Doctor AI");
            }
        }
     }
}
class=class="str">"cmt">//This function handles our trade management
class="type">void ManageTrade(){
   CheckStop();
}
class=class="str">"cmt">//This funciton will update our S/L & T/P 
class="type">void CheckStop(){
    class=class="str">"cmt">//First we iterate over the total number of open positions                
    for(class="type">int i = PositionsTotal() -class="num">1; i >= class="num">0; i--){
        class=class="str">"cmt">//Then we fetch the name of the symbol of the open position
        class="type">class="kw">string symbol = PositionGetSymbol(i);
        class=class="str">"cmt">//Before going any furhter we need to ensure that the symbol of the position matches the symbol we&class="macro">#x27;re trading
        if(_Symbol == symbol){
            class=class="str">"cmt">//Now we get information about the position

◍ 多单尾仓的止损挪移逻辑

在 MT5 的持仓遍历循环里,先通过 PositionGetInteger 拿到 ticket 与持仓类型,再用 PositionGetDouble 取开仓价、当前 SL。这套取值是后续所有修改动作的前提,缺一个都会让 PositionModify 直接报错。 针对多单分支,新止损放在 ask 减去一半 sl_width 点差的位置,新止盈则是 ask 加上完整 sl_width 点差,统一用 NormalizeDouble 按 _Digits 截断。例如 sl_width=200、_Point=0.00001 的黄金品种,新 SL 距 ask 约 1 点、新 TP 距 ask 约 2 点。 修改触发条件很硬:仅当原 SL 小于算出值,或原 SL 为 0(市价单未设损)才执行。这样能避免把已有更宽止损往不利方向收,外汇与贵金属杠杆高,乱移损可能瞬间放大回撤风险。 别让旧损被覆盖 写 EA 时很多人忽略 current_stop_loss == 0 的判定,结果把手动设好的宽损给洗掉。建议先在策略测试器用欧美 1 分钟数据跑一遍,确认日志里 Modify 次数符合预期再上模拟盘。

MQL5 / C++
class="type">ulong ticket = PositionGetInteger(POSITION_TICKET); class=class="str">"cmt">//Position Ticket
class="type">class="kw">double position_price = PositionGetDouble(POSITION_PRICE_OPEN); class=class="str">"cmt">//Position Open Price
class="type">long type = PositionGetInteger(POSITION_TYPE); class=class="str">"cmt">//Position Type
class="type">class="kw">double current_stop_loss = PositionGetDouble(POSITION_SL); class=class="str">"cmt">//Current Stop loss value
class=class="str">"cmt">//If the position is a buy
if(type == POSITION_TYPE_BUY){
   class=class="str">"cmt">//The new stop loss value is just the ask price minus the stop we calculated above
   class="type">class="kw">double new_stop_loss = NormalizeDouble(ask - ((sl_width * _Point) / class="num">2) ,_Digits);
   class=class="str">"cmt">//The new take profit is just the ask price plus the stop we calculated above
   class="type">class="kw">double new_take_profit = NormalizeDouble(ask + (sl_width * _Point),_Digits);
   class=class="str">"cmt">//If our current stop loss is less than our calculated stop loss 
   class=class="str">"cmt">//Or if our current stop loss is class="num">0 then we will modify the stop loss and take profit
   if((current_stop_loss < new_stop_loss) || (current_stop_loss == class="num">0)){
       Trade.PositionModify(ticket,new_stop_loss,new_take_profit);
   }   
   }
   class=class="str">"cmt">//If the position is a sell
   else if(type == POSITION_TYPE_SELL){
   class=class="str">"cmt">//The new stop loss value is just the ask price minus the stop we calculated above

挂单改止损的对称算法

这段逻辑处理的是持仓止损与止盈的对称重设:以 bid 为基准,止损放在 bid 上方 sl_width 一半的点距处,止盈则落在 bid 下方一个完整 sl_width 点距处。用 NormalizeDouble 按 _Digits 精度截断,避免跨品种点值误差。 判定条件很直接——当当前止损大于新算出的止损,或当前止损为 0(即未设防),就调用 PositionModify 把两个价位一起改掉。外汇与贵金属杠杆高,这类自动改单逻辑在跳空时可能失效,实盘前务必在 MT5 策略测试器用历史 tick 跑一遍。 代码逐行拆解: new_stop_loss = NormalizeDouble(bid + ((sl_width * _Point)/2), _Digits); // 止损 = 买价 + 半宽点距,按小数位规范化 double new_take_profit = NormalizeDouble(bid - (sl_width * _Point), _Digits); // 止盈 = 买价 - 全宽点距,规范化

if((current_stop_loss > new_stop_loss)(current_stop_loss == 0)){ // 原止损过宽或未设

Trade.PositionModify(ticket,new_stop_loss,new_take_profit); // 改单 }

MQL5 / C++
class="type">class="kw">double new_stop_loss = NormalizeDouble(bid + ((sl_width * _Point)/class="num">2),_Digits);
class=class="str">"cmt">//The new take profit is just the ask price plus the stop we calculated above
class="type">class="kw">double new_take_profit = NormalizeDouble(bid - (sl_width * _Point),_Digits);
class=class="str">"cmt">//If our current stop loss is greater than our calculated stop loss 
class=class="str">"cmt">//Or if our current stop loss is class="num">0 then we will modify the stop loss and take profit 
if((current_stop_loss > new_stop_loss) || (current_stop_loss == class="num">0)){
      Trade.PositionModify(ticket,new_stop_loss,new_take_profit);
      }
     }  
   }  
  }
}

「最后一句大实话」

做时间序列回归最坑的就是伪回归,两个本来各走各的非平稳序列,凑一起算出的高 R² 往往是假象,EURUSD 日线用 FetchData.mq5 拉 330KB 数据回测就常出现这种虚高拟合。 真要落地,先跑单位根检验看平稳性,不平稳就上协整或误差修正模型;文末给的 Spurious_Regression_ONNX_EA.mq5(10.16KB)能直接挂 MT5 验证 ONNX 双精度模型在样本内外的差异。 外汇和贵金属杠杆高、序列非平稳是常态,任何统计套利结论都只是概率倾向,开实盘前务必用未见过的数据重测残差。

交给小布盯盘看信号源
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到相关统计特征,帮你快速判断策略信号是否踩中伪回归陷阱,把重复劳动交给小布,你专注决策。

常见问题

ONNX 是开放神经网络交换标准,MetaEditor 对 ONNX 有原生推理接口,避免在 MQL5 侧重写模型结构,降低移植误差与维护成本。
推理耗时取决于模型规模与特征维度,轻量 ONNX 在常规品种上通常亚毫秒级;若模型偏重,可改为定时推理缓存信号,减少每 tick 的计算压力。
小布盯盘提供品种页的统计特征与相关性诊断,可辅助你观察信号与价格的实际耦合度,但是否伪回归仍需结合本文的单位根与残差检验自行确认。
先确认 Python 端导出时输入输出节点名与维度,再用 OnnxRuntime 的 GetOutputShape 打印核对;常见问题是批量维未压缩为 1 或归一化参数未同步。
未检验的模型可能带着伪回归上线,回测好看实盘漂移,关于完整检验流程见《Python中的虚假回归(伪回归)·基础篇》。