在 ONNX 模型中使用 float16 和 float8 格式·综合运用
- Float16 转 Float 的底层字节验证
- 把 ONNX 模型塞进 MT5 的底层三板斧
- 用 BFloat16 跑通 ONNX 张量回环
- 在 MT5 里把 BFloat16 权重cast回 float 的实测路径
- float16 与 ONNX 张量的来回转换实测
- BFloat16 转 float 的字节级验证
- 8位浮点如何在MT5里跑通类型转换
- 把 ONNX 模型塞进 MT5 的修补与张量定型
- 用 ONNX 给 FP8 转 Float 做交叉验证
- 在 MT5 里跑通 FP8 转浮点的模型验证
- float8 与 ONNX 张量的映射实测
- FP8 转 float 的零误差实测
- float8 与 ONNX 张量往返零误差验证
- FP8 转 float 在 EURUSD H1 上的零误差表现
- Float8 量化往返零误差的实测证据
- FP8转float在EURUSD-H1上的零误差验证
- Float8 量化在 EURUSD H1 上的往返验证
- FP8 转 float 在 EURUSD H1 上零误差
- 用ONNX在MT5里跑ESRGAN超分
- float16 量化让模型体积砍半
- 把图表像素拆成 ONNX 能吃的 RGB 张量
- 把分离通道塞进画布并喂给推理
- 超分推理跑通后的张量落地细节
- 把 ESRGAN 压成 float16 再塞进 MT5
- 把图像张量塞回 RGB 缓冲区的双向搬运
- 把图丢进神经网络前的浮点压缩
- 用 ONNX 跑超分辨率放大时要注意的句柄与形状
- 把缩放画布落盘并挂起脚本
- FP16/FP8 模型落地的代码改动量
- 半精度数字的实战边界
◍ Float16 转 Float 的底层字节验证
在 EURUSD 的 H1 周期下跑了一遍 RunCastFloat16ToFloat,从输入 5.0 到 12.0 共 8 组样本,输出 float 值与预期完全一致,difference 均为 0.000000,最终 sum_error 也是 0.000000。 具体看字节层:5.0 的 float16 布局是 [00,00,a0,40],6.0 是 [00,00,c0,40],7.0 是 [00,00,e0,40];到 8.0 时高位字节翻成 41,即 [00,00,00,41],之后 9.0~12.0 依次步进 10、20、30、40 十六进制偏移。这说明半精度到单精度的映射在整数段是无损的。 代码里用了一个泛型 union 做位级转换,把 uchar 数组和模板类型 T 共用同一段内存,避免浮点赋值带来的舍入。外汇与贵金属杠杆高、点差跳变频繁,这类精度验证虽小,但模型推理前若字节错位,信号可能整体偏移。
class="macro">#resource "models\\test_cast_BFLOAT16_to_FLOAT.onnx" as class="kw">const class="type">uchar ExtModel1[]; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| union for data conversion | class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">template<class="kw">typename T> union U { class="type">uchar uc[class="kw">sizeof(T)]; T value; };
「把 ONNX 模型塞进 MT5 的底层三板斧」
想在 MT5 里跑 AIGC 推理,第一步不是调网络结构,而是把模型缓冲和张量形状先安顿好。下面这组函数覆盖了从字节修补到句柄创建的完整链路,复制进 EA 就能直接编译验证。 ArrayToString 只是个辅助:把任意类型数组按 16 长度截断,格式化成 [ab,cd,ef] 这种十六进制串,方便在日志里肉眼核对模型头尾字节有没有改对。 PatchONNXModel 干的是硬核活——把 original_model 整份拷到 patched_model,然后把第 2 个字节(下标 1)写成 0x09,末尾字节写成 0x14。这对应 ONNX IR 版本 9、Opset 20 的强制标记;若你手里的模型头尾不符,OnnxCreateFromBuffer 会直接返回无效句柄。 CreateModel 用 OnnxCreateFromBuffer 从 uchar 数组建句柄,flags 默认 ONNX_DEFAULT,注释里留了 ONNX_DEBUG_LOGS 可开调试日志。PrepareShapes 则把输入 0 和输出 0 都锁成 {3,4} 的二维形状,任一步失败就打印错误码并释放句柄——实盘前务必确认这两处维度跟你训练时的 placeholder 一致,否则推理结果可能全错。
class="type">class="kw">string ArrayToString(class="kw">const T &data[],class="type">uint length=class="num">16) { class="type">class="kw">string res; for(class="type">uint n=class="num">0;n<MathMin(length,data.Size()); n++) res+="," + StringFormat("%.2x",data[n]); StringSetCharacter(res,class="num">0,&class="macro">#x27;[&class="macro">#x27;); class="kw">return res+"]"; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| PatchONNXModel | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void PatchONNXModel(class="kw">const class="type">uchar &original_model[],class="type">uchar &patched_model[]) { ArrayCopy(patched_model,original_model,class="num">0,class="num">0,WHOLE_ARRAY); class=class="str">"cmt">//--- special ONNX model patch(IR=class="num">9,Opset=class="num">20) patched_model[class="num">1]=0x09; patched_model[ArraySize(patched_model)-class="num">1]=0x14; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| CreateModel | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CreateModel(class="type">long &model_handle,class="kw">const class="type">uchar &model[]) { model_handle=INVALID_HANDLE; class="type">class="kw">ulong flags=ONNX_DEFAULT; class=class="str">"cmt">//class="type">class="kw">ulong flags=ONNX_DEBUG_LOGS; class=class="str">"cmt">//--- model_handle=OnnxCreateFromBuffer(model,flags); if(model_handle==INVALID_HANDLE) class="kw">return(class="kw">false); class=class="str">"cmt">//--- class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| PrepareShapes | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool PrepareShapes(class="type">long model_handle) { class="type">class="kw">ulong input_shape1[]= {class="num">3,class="num">4}; if(!OnnxSetInputShape(model_handle,class="num">0,input_shape1)) { PrintFormat("error in OnnxSetInputShape for input1. error code=%d",GetLastError()); class=class="str">"cmt">//-- OnnxRelease(model_handle); class="kw">return(class="kw">false); } class=class="str">"cmt">//--- class="type">class="kw">ulong output_shape[]= {class="num">3,class="num">4}; if(!OnnxSetOutputShape(model_handle,class="num">0,output_shape)) { PrintFormat("error in OnnxSetOutputShape for output. error code=%d",GetLastError()); class=class="str">"cmt">//-- OnnxRelease(model_handle); class="kw">return(class="kw">false); } class=class="str">"cmt">//--- class="kw">return(true); }
用 BFloat16 跑通 ONNX 张量回环
在 MT5 里调用 ONNX 模型做推理时,若模型权重以 BFloat16 存储,需要先把普通 double 数组压成 16 位浮点再送进推理接口。下面这段函数演示了从 1~12 的 12 个 double 值出发,经 ArrayToFP16 转码、OnnxRun 推理、再反向校验误差的完整链路。 核心转换靠 ArrayToFP16(test_data, FLOAT_BFP16) 与 ArrayFromFP16 成对使用;转出的 ushort 数组 data_uint16 长度与原数组一致,都是 12。打印可见 Hex float16 字符串与 ushort 数值一一对应,方便在专家日志里核对位宽。 推理阶段用 U<ushort> 与 U<float> 联合体承载输入输出,OnnxRun 传 ONNX_NO_CONVERSION 告知框架「别再擅自转精度」。回环结束后逐元素算 delta = 原值 - 模型输出,并累加 sum_error。若 sum_error 偏大,说明该模型在 BFloat16 下对整数序列的保真度不足,可能需要在预处理里改用 FLOAT32。 外汇与贵金属行情的高波动会让推理输入偏离训练分布,用此类回环自测能提前暴露数值截断风险,但模型输出仅作概率参考,实盘仍属高风险。
class="type">bool RunCastBFloat16ToFloat(class="type">long model_handle) { PrintFormat("test=%s",__FUNCTION__); class="type">class="kw">double test_data[class="num">12]= {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="num">11,class="num">12}; class="type">class="kw">ushort data_uint16[class="num">12]; if(!ArrayToFP16(data_uint16,test_data,FLOAT_BFP16)) { Print("error in ArrayToFP16. error code=",GetLastError()); class="kw">return(class="kw">false); } Print("test array:"); ArrayPrint(test_data); Print("ArrayToFP16:"); ArrayPrint(data_uint16); U<class="type">class="kw">ushort> input_float16_values[class="num">3*class="num">4]; U<class="type">class="kw">float> output_float_values[class="num">3*class="num">4]; class="type">class="kw">float test_data_float[]; if(!ArrayFromFP16(test_data_float,data_uint16,FLOAT_BFP16)) { Print("error in ArrayFromFP16. error code=",GetLastError()); class="kw">return(class="kw">false); } for(class="type">int i=class="num">0; i<class="num">12; i++) { input_float16_values[i].value=data_uint16[i]; PrintFormat("%d class="kw">input value =%f Hex float16 = %s class="type">class="kw">ushort value=%d",i,test_data_float[i],ArrayToString(input_float16_values[i].uc),input_float16_values[i].value); } Print("ONNX class="kw">input array:"); ArrayPrint(input_float16_values); class="type">bool res=OnnxRun(model_handle,ONNX_NO_CONVERSION,input_float16_values,output_float_values); if(!res) { PrintFormat("error in OnnxRun. error code=%d",GetLastError()); class="kw">return(class="kw">false); } Print("ONNX output array:"); ArrayPrint(output_float_values); class=class="str">"cmt">//--- class="type">class="kw">double sum_error=class="num">0.0; for(class="type">int i=class="num">0; i<class="num">12; i++) { class="type">class="kw">double delta=test_data[i]-(class="type">class="kw">double)output_float_values[i].value; sum_error+=MathAbs(delta); PrintFormat("%d output class="type">class="kw">float %f = %s difference=%f",i,output_float_values[i].value,ArrayToString(output_float_values[i].uc),delta); } class=class="str">"cmt">//--- PrintFormat("test=%s sum_error=%f",__FUNCTION__,sum_error); class=class="str">"cmt">//--- class="kw">return(true); }
◍ 在 MT5 里把 BFloat16 权重cast回 float 的实测路径
想在 MT5 跑 ONNX 模型,第一道坎经常是权重格式。很多导出模型带的是 BFloat16,而终端的 ONNX 推理接口更认标准 float,直接喂可能算不出东西,得先做一次类型转换。 下面这段脚本就是最小可跑的入口:先打补丁拿到模型字节,建句柄,定输入输出维度,再跑转换函数,最后释放。EURUSD H1 上跑通后,终端会打印转换前后的数组。
class="type">int OnStart(class="type">void) { class="type">uchar model[]; PatchONNXModel(ExtModel1,model); class=class="str">"cmt">//--- get model handle class="type">long model_handle=INVALID_HANDLE; class=class="str">"cmt">//--- get model handle if(!CreateModel(model_handle,model)) class="kw">return class="num">1; class=class="str">"cmt">//--- prepare class="kw">input and output shapes if(!PrepareShapes(model_handle)) class="kw">return class="num">1; class=class="str">"cmt">//--- run ONNX model if(!RunCastBFloat16ToFloat(model_handle)) class="kw">return class="num">1; class=class="str">"cmt">//--- release model handle OnnxRelease(model_handle); class=class="str">"cmt">//--- class="kw">return class="num">0; } class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnStart(class="type">void) { class="type">uchar model[]; PatchONNXModel(ExtModel1,model); class=class="str">"cmt">//--- get model handle class="type">long model_handle=INVALID_HANDLE; class=class="str">"cmt">//--- get model handle if(!CreateModel(model_handle,model)) class="kw">return class="num">1; class=class="str">"cmt">//--- prepare class="kw">input and output shapes if(!PrepareShapes(model_handle)) class="kw">return class="num">1; class=class="str">"cmt">//--- run ONNX model if(!RunCastBFloat16ToFloat(model_handle)) class="kw">return class="num">1; class=class="str">"cmt">//--- release model handle OnnxRelease(model_handle); class=class="str">"cmt">//--- class="kw">return class="num">0; } class=class="str">"cmt">//+------------------------------------------------------------------+
「float16 与 ONNX 张量的来回转换实测」
在 EURUSD H1 图表上跑 TestCastBFloat16,输入 10.0 被压成 float16 后字节序列是 [20,41],对应 ushort 值 16672;11.0 则是 [30,41] 与 16688,每递增 1.0,ushort 固定加 16,这是半精度尾数偏移的直接体现。 喂给 ONNX 的 input array 从索引 0 的 16256 一路排到索引 11 的 16704,恰好覆盖 1.0~12.0 的半精度编码;模型回吐的 output array 又把同样顺序还原成 1.00000 到 12.00000 的 32 位浮点。 逐元素比对:索引 0 输出 float 1.0 的四字节是 [00,00,80,3f],索引 2 输出 3.0 是 [00,00,40,40],所有 difference 字段均为 0.000000,说明该转换链路在样本区间内无精度损失。 外汇与贵金属行情的高波动可能让极端值超出半精度安全范围,实际接模型前建议在 MT5 用 Print 复刻这段日志,确认自己的输入域不掉精度。
BFloat16 转 float 的字节级验证
在 EURUSD 的 H1 周期下跑 TestCastBFloat16,连续 8 个样本(4.0 到 11.0)从 bfloat16 还原成 float 后,difference 全部为 0.000000,说明这路转换在整数和小数位边界上没有精度塌缩。 具体看字节:4.0 存成 [00,00,80,40],5.0 是 [00,00,a0,40],每加 1.0 高位字节按 0x20 步进;到 8.0 时变成 [00,00,00,41],指数段翻了一位。这种排布和 IEEE754 单精度的内存布局是对得上的。 最后 RunCastBFloat16ToFloat 汇总 sum_error=0.000000,即在测试集内 bfloat16→float 是可逆无损的。外汇与贵金属波动剧烈、杠杆高风险大,实盘里别默认所有数值类型转换都这么干净,建议自己挂 EA 把这段打印出来核对。
TestCastBFloat16(EURUSD,H1) class="num">3 output class="type">class="kw">float class="num">4.000000 = [class="num">00,class="num">00,class="num">80,class="num">40] difference=class="num">0.000000 TestCastBFloat16(EURUSD,H1) class="num">4 output class="type">class="kw">float class="num">5.000000 = [class="num">00,class="num">00,a0,class="num">40] difference=class="num">0.000000 TestCastBFloat16(EURUSD,H1) class="num">5 output class="type">class="kw">float class="num">6.000000 = [class="num">00,class="num">00,c0,class="num">40] difference=class="num">0.000000 TestCastBFloat16(EURUSD,H1) class="num">6 output class="type">class="kw">float class="num">7.000000 = [class="num">00,class="num">00,e0,class="num">40] difference=class="num">0.000000 TestCastBFloat16(EURUSD,H1) class="num">7 output class="type">class="kw">float class="num">8.000000 = [class="num">00,class="num">00,class="num">00,class="num">41] difference=class="num">0.000000 TestCastBFloat16(EURUSD,H1) class="num">8 output class="type">class="kw">float class="num">9.000000 = [class="num">00,class="num">00,class="num">10,class="num">41] difference=class="num">0.000000 TestCastBFloat16(EURUSD,H1) class="num">9 output class="type">class="kw">float class="num">10.000000 = [class="num">00,class="num">00,class="num">20,class="num">41] difference=class="num">0.000000 TestCastBFloat16(EURUSD,H1) class="num">10 output class="type">class="kw">float class="num">11.000000 = [class="num">00,class="num">00,class="num">30,class="num">41] difference=class="num">0.000000 TestCastBFloat16(EURUSD,H1) class="num">11 output class="type">class="kw">float class="num">12.000000 = [class="num">00,class="num">00,class="num">40,class="num">41] difference=class="num">0.000000 TestCastBFloat16(EURUSD,H1) test=RunCastBFloat16ToFloat sum_error=class="num">0.000000
◍ 8位浮点如何在MT5里跑通类型转换
把训练好的大模型塞进终端,最先撞墙的就是内存。FP16权重占2字节,FP8只占1字节,显存和带宽压力直接砍半,推理延迟也跟着降。代价是尾数只有2~3位,科学计算或报价精算里用FP8可能丢精度,但做特征张量和梯度传递够用。 2022年三家公司联合发文定了两套8位布局:E4M3(1符号/4指数/3尾数)偏向权重和激活,E5M2(1符号/5指数/2尾数)留给梯度。ONNX从1.15.0起落地了四种变体,区别只在有没有负零和无穷值,例如E4M3FN无无穷、E4M3FNUZ连负零也去掉。 下面这段MQ5把四个官方Cast模型以资源方式编进程序,在MetaEditor里能直接看输入输出的张量形状,用来验证FLOAT8到float的映射是否和文档一致。
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| TestCastBFloat8.mq5 |</span> <span class="comment">class=class="str">"cmt">//| Copyright class="num">2024, MetaQuotes Ltd. |</span> <span class="comment">class=class="str">"cmt">//| [MQL5官方文档] |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="preprocessor">class="macro">#class="kw">property </span><span class="macro">copyright</span> <span class="class="type">class="kw">string">"Copyright class="num">2024, MetaQuotes Ltd."</span> <span class="preprocessor">class="macro">#class="kw">property </span><span class="macro">link</span> <span class="class="type">class="kw">string">"[MQL5官方文档] <span class="preprocessor">class="macro">#class="kw">property </span><span class="macro">version</span> <span class="class="type">class="kw">string">"class="num">1.00"</span> <span class="preprocessor">class="macro">#resource </span><span class="class="type">class="kw">string">"models\\test_cast_FLOAT8E4M3FN_to_FLOAT.onnx"</span> <span class="keyword">as</span> <span class="keyword">class="kw">const</span> <span class="keyword">class="type">uchar</span> ExtModel_FLOAT8E4M3FN_to_FLOAT[]; <span class="preprocessor">class="macro">#resource </span><span class="class="type">class="kw">string">"models\\test_cast_FLOAT8E4M3FNUZ_to_FLOAT.onnx"</span> <span class="keyword">as</span> <span class="keyword">class="kw">const</span> <span class="keyword">class="type">uchar</span> ExtModel_FLOAT8E4M3FNUZ_to_FLOAT[]; <span class="preprocessor">class="macro">#resource </span><span class="class="type">class="kw">string">"models\\test_cast_FLOAT8E5M2_to_FLOAT.onnx"</span> <span class="keyword">as</span> <span class="keyword">class="kw">const</span> <span class="keyword">class="type">uchar</span> ExtModel_FLOAT8E5M2_to_FLOAT[]; <span class="preprocessor">class="macro">#resource </span><span class="class="type">class="kw">string">"models\\test_cast_FLOAT8E5M2FNUZ_to_FLOAT.onnx"</span> <span class="keyword">as</span> <span class="keyword">class="kw">const</span> <span class="keyword">class="type">uchar</span> ExtModel_FLOAT8E5M2FNUZ_to_FLOAT[]; <span class="preprocessor">class="macro">#define </span>TEST_PASSED <span class="number">class="num">0</span> <span class="preprocessor">class="macro">#define </span>TEST_FAILED <span class="number">class="num">1</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| union for data conversion |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">class="kw">template</span><<span class="keyword">class="kw">typename</span> T> <span class="keyword">union</span> U { <span class="keyword">class="type">uchar</span> uc[<span class="keyword">class="kw">sizeof</span>(T)]; T value; }; <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| ArrayToHexString |</span>
「把 ONNX 模型塞进 MT5 的修补与张量定型」
在 MT5 里直接跑 ONNX 推理,第一步往往是模型版本号和张量维度的对齐。下面这组函数覆盖了十六进制数组打印、包装类型转字符串、模型补丁和输入输出形状设定,复制进 EA 的 include 段就能用。 template<typename T> string ArrayToHexString(const T &data[],uint length=16) { string res; for(uint n=0;n<MathMin(length,data.Size()); n++) res+=',' + StringFormat("%.2x",data[n]); StringSetCharacter(res,0,'['); return(res+"]"); } 上面这段是泛型十六进制打印:默认取前 16 个元素,用 MathMin 防止越界;循环里 %.2x 把每个字节格式化成两位十六进制,首字符被 StringSetCharacter 替换成左括号,末尾补右括号,输出形如 [ab,cd,ef]。 template<typename T> string ArrayToString(const U<T> &data[],uint length=16) { string res; for(uint n=0;n<MathMin(length,data.Size()); n++) res+=',' + (string)data[n].value; StringSetCharacter(res,0,'['); return(res+"]"); } 这是包装结构体 U<T> 的转字符串版本,直接取 .value 成员拼接,调试张量数值时比看内存方便。 long CreatePatchedModel(const uchar &original_model[]) { uchar patched_model[]; ArrayCopy(patched_model,original_model); patched_model[1]=0x09; patched_model[ArraySize(patched_model)-1]=0x14; return(OnnxCreateFromBuffer(patched_model,ONNX_DEFAULT)); } 模型补丁函数把 IR 版本写死在偏移 1 处为 0x09(即 9),末尾字节置为 0x14(即 20,对应 Opset 20)。若你的原始模型头尾不符,OnnxCreateFromBuffer 可能返回空句柄,这时该改的是导出的 ONNX 版本而非硬补。 bool PrepareShapes(long model_handle) { ulong input_shape[]= {3,5}; if(!OnnxSetInputShape(model_handle,0,input_shape)) { PrintFormat("error in OnnxSetInputShape for input1. error code=%d",GetLastError()); OnnxRelease(model_handle); return(false); } ulong output_shape[]= {3,5}; if(!OnnxSetOutputShape(model_handle,0,output_shape)) { PrintFormat("error in OnnxSetOutputShape for output. error code=%d",GetLastError()); OnnxRelease(model_handle); return(false); } 输入和输出都设成 {3,5} 的二维张量,任一步失败就打印错误码并释放句柄。外汇与贵金属行情高波动、高杠杆,模型推理仅作辅助,实盘信号失效概率不低,验证时先用历史 tick 回测再上模拟盘。
class="kw">template<class="kw">typename T> class="type">class="kw">string ArrayToHexString(class="kw">const T &data[],class="type">uint length=class="num">16) { class="type">class="kw">string res; for(class="type">uint n=class="num">0;n<MathMin(length,data.Size()); n++) res+=&class="macro">#x27;,&class="macro">#x27; + StringFormat("%.2x",data[n]); StringSetCharacter(res,class="num">0,&class="macro">#x27;[&class="macro">#x27;); class="kw">return(res+"]"); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| ArrayToString | class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">template<class="kw">typename T> class="type">class="kw">string ArrayToString(class="kw">const U<T> &data[],class="type">uint length=class="num">16) { class="type">class="kw">string res; for(class="type">uint n=class="num">0;n<MathMin(length,data.Size()); n++) res+=&class="macro">#x27;,&class="macro">#x27; + (class="type">class="kw">string)data[n].value; StringSetCharacter(res,class="num">0,&class="macro">#x27;[&class="macro">#x27;); class="kw">return(res+"]"); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| PatchONNXModel | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">long CreatePatchedModel(class="kw">const class="type">uchar &original_model[]) { class="type">uchar patched_model[]; ArrayCopy(patched_model,original_model); class=class="str">"cmt">//--- special ONNX model patch(IR=class="num">9,Opset=class="num">20) patched_model[class="num">1]=0x09; patched_model[ArraySize(patched_model)-class="num">1]=0x14; class="kw">return(OnnxCreateFromBuffer(patched_model,ONNX_DEFAULT)); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| PrepareShapes | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool PrepareShapes(class="type">long model_handle) { class=class="str">"cmt">//--- configure class="kw">input shape class="type">class="kw">ulong input_shape[]= {class="num">3,class="num">5}; if(!OnnxSetInputShape(model_handle,class="num">0,input_shape)) { PrintFormat("error in OnnxSetInputShape for input1. error code=%d",GetLastError()); OnnxRelease(model_handle); class="kw">return(class="kw">false); } class=class="str">"cmt">//--- configure output shape class="type">class="kw">ulong output_shape[]= {class="num">3,class="num">5}; if(!OnnxSetOutputShape(model_handle,class="num">0,output_shape)) { PrintFormat("error in OnnxSetOutputShape for output. error code=%d",GetLastError()); OnnxRelease(model_handle); class="kw">return(class="kw">false); }
用 ONNX 给 FP8 转 Float 做交叉验证
这段函数把 1 到 15 的 15 个 float 原数压成 FP8 再解回 float,顺带丢进 ONNX 模型跑一遍,看两套路径算出来的误差会不会对不上。外汇和贵金属模型量化到 FP8 时,这种偏差可能直接放大信号噪声,属于高风险操作,动手前先在回测里摸清分布。 核心校验逻辑在末尾:用 ArrayFromFP8 的解码结果减 ONNX 输出,逐元素取绝对值累加到 sum_error。如果 sum_error 明显不为零,说明本地解码与推理框架的 FP8 语义有分歧,不能直接替换用。 跑的时候 PrintFormat 会把每个元素的 Hex float8、ushort 值、ONNX 输出差打印出来,你开 MT5 专家日志就能逐行比对。若同一种 ENUM_FLOAT8_FORMAT 下 sum_error 始终为 0.0,那这套转换链在数值上才算闭环。
class="type">bool RunCastFloat8ToFloat(class="type">long model_handle,class="kw">const ENUM_FLOAT8_FORMAT fmt) { PrintFormat("TEST: %s(%s)",__FUNCTION__,EnumToString(fmt)); class=class="str">"cmt">//--- class="type">class="kw">float test_data[class="num">15] = {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="num">11,class="num">12,class="num">13,class="num">14,class="num">15}; class="type">uchar data_float8[class="num">15] = {}; if(!ArrayToFP8(data_float8,test_data,fmt)) { Print("error in ArrayToFP8. error code=",GetLastError()); OnnxRelease(model_handle); class="kw">return(class="kw">false); } U<class="type">uchar> input_float8_values[class="num">3*class="num">5]; U<class="type">class="kw">float> output_float_values[class="num">3*class="num">5]; class="type">class="kw">float test_data_float[]; class=class="str">"cmt">//--- convert float8 to class="type">class="kw">float if(!ArrayFromFP8(test_data_float,data_float8,fmt)) { Print("error in ArrayFromFP8. error code=",GetLastError()); OnnxRelease(model_handle); class="kw">return(class="kw">false); } for(class="type">uint i=class="num">0; i<data_float8.Size(); i++) { input_float8_values[i].value=data_float8[i]; PrintFormat("%d class="kw">input value =%f Hex float8 = %s class="type">class="kw">ushort value=%d",i,test_data_float[i],ArrayToHexString(input_float8_values[i].uc),input_float8_values[i].value); } Print("ONNX class="kw">input array: ",ArrayToString(input_float8_values)); class=class="str">"cmt">//--- execute model(convert float8 to class="type">class="kw">float using ONNX) if(!OnnxRun(model_handle,ONNX_NO_CONVERSION,input_float8_values,output_float_values)) { PrintFormat("error in OnnxRun. error code=%d",GetLastError()); OnnxRelease(model_handle); class="kw">return(class="kw">false); } Print("ONNX output array: ",ArrayToString(output_float_values)); class=class="str">"cmt">//--- calculate error(compare ONNX and ArrayFromFP8 results) class="type">class="kw">double sum_error=class="num">0.0; for(class="type">uint i=class="num">0; i<test_data.Size(); i++) { class="type">class="kw">double delta=test_data_float[i]-(class="type">class="kw">double)output_float_values[i].value; sum_error+=MathAbs(delta); PrintFormat("%d output class="type">class="kw">float %f = %s difference=%f",i,output_float_values[i].value,ArrayToHexString(output_float_values[i].uc),delta); } class=class="str">"cmt">//--- PrintFormat("%s(%s): sum_error=%f\n",__FUNCTION__,EnumToString(fmt),sum_error); class="kw">return(true); }
◍ 在 MT5 里跑通 FP8 转浮点的模型验证
把 ONNX 的 Float8 权重塞进 MT5 做推理前,得先确认四种 FP8 格式都能正确反量化成 float。下面这段脚本就是干这件事的:先打补丁加载模型,再准备输入输出张量形状,最后跑转换并释放句柄。 TestModel 函数接收模型字节流和格式枚举,内部依次调用 CreatePatchedModel、PrepareShapes、RunCastFloat8ToFloat,任何一步返回 false 就直接退出,避免脏句柄残留。OnStart 里把 E4M3FN、E4M3FNUZ、E5M2FN、E5M2FNUZ 四种格式各测一遍,全过才返回 TEST_PASSED。 实盘日志(EURUSD H1 测试)给出了可核对的数字:输入 1.0 对应 hex float8 为 0x38、ushort 值 56;输入 5.0 对应 0x4a、ushort 值 74。如果你在终端看到同样的映射,说明本机 ONNX 后端对该格式的字节序和偏移处理是对的。 外汇与贵金属行情受杠杆与跳空影响,这类模型验证仅保证数值通路正确,不预示任何方向;拿去接信号前请在模拟盘先跑一轮。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| TestModel | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool TestModel(class="kw">const class="type">uchar &model[],class="kw">const ENUM_FLOAT8_FORMAT fmt) { class=class="str">"cmt">//--- create patched model class="type">long model_handle=CreatePatchedModel(model); if(model_handle==INVALID_HANDLE) class="kw">return(class="kw">false); class=class="str">"cmt">//--- prepare class="kw">input and output shapes if(!PrepareShapes(model_handle)) class="kw">return(class="kw">false); class=class="str">"cmt">//--- run ONNX model if(!RunCastFloat8ToFloat(model_handle,fmt)) class="kw">return(class="kw">false); class=class="str">"cmt">//--- release model handle OnnxRelease(model_handle); class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Script program start function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnStart(class="type">void) { class=class="str">"cmt">//--- run ONNX model if(!TestModel(ExtModel_FLOAT8E4M3FN_to_FLOAT,FLOAT_FP8_E4M3FN)) class="kw">return(TEST_FAILED); class=class="str">"cmt">//--- run ONNX model if(!TestModel(ExtModel_FLOAT8E4M3FNUZ_to_FLOAT,FLOAT_FP8_E4M3FNUZ)) class="kw">return(TEST_FAILED); class=class="str">"cmt">//--- run ONNX model if(!TestModel(ExtModel_FLOAT8E5M2_to_FLOAT,FLOAT_FP8_E5M2FN)) class="kw">return(TEST_FAILED); class=class="str">"cmt">//--- run ONNX model if(!TestModel(ExtModel_FLOAT8E5M2FNUZ_to_FLOAT,FLOAT_FP8_E5M2FNUZ)) class="kw">return(TEST_FAILED); class="kw">return(TEST_PASSED); } class=class="str">"cmt">//+------------------------------------------------------------------+
「float8 与 ONNX 张量的映射实测」
在 EURUSD H1 上跑一组 float8 转换探针,输入 7.0 到 15.0 这 9 个值,对应 Hex float8 从 0x4e 线性走到 0x57,ushort 承载值从 78 递增到 87,说明该 8 位浮点编码对整数区间的映射是连续且无重叠的。 把上述 15 个采样(含前置的 56、64、68 等)塞进 ONNX 输入数组,模型原样回吐 1.0 到 15.0,输出数组与输入序号严格对齐,证明在推理侧 float8 张量未被重排或归一化。 回读阶段把输出 float 拆成 4 字节小端十六进制,例如 3.0 落为 [00,00,40,40]、4.0 为 [00,00,80,40],difference 全为 0.000000,即 MT5 侧反序列化与 ONNX 侧数值一致,外汇品种上做这类轻量推理前可先跑通该断言再上实盘信号。
FP8 转 float 的零误差实测
在 EURUSD 的 H1 周期下跑了 RunCastFloat8ToFloat(FLOAT_FP8_E4M3FN) 的批量转换,输入 5.0 到 15.0 共 10 个整数浮点,输出 6.0 到 15.0 对应的 4 字节十六进制分别是 [00,00,c0,40] 到 [00,00,70,41],逐行 difference 全部为 0.000000,最终 sum_error=0.000000。 换用 FLOAT_FP8_E4M3FNUZ 格式后,正向打印了 1.0 到 6.0 的编码:1.0 对应 float8 字节 [40]、ushort 值 64;2.0 为 [48]、72;3.0 为 [4c]、76;4.0 为 [50]、80;5.0 为 [52]、82;6.0 为 [54]、84。两种格式在 EURUSD H1 的小数列里都没出现量化偏差。 外汇与贵金属杠杆高、滑点随机,这类底层数值精度验证只是确认 MT5 的 FP8 转换内核可信,不代表任何策略收益。开 MT5 把这段日志用 Expert Advisor 跑一遍,能直接核对你的终端是否同版输出。
◍ float8 与 ONNX 张量往返零误差验证
在 EURUSD H1 周期下跑 TestCastFloat8,输入 7.0 到 15.0 这 9 个值,对应的 float8 十六进制分别是 [56] 到 [5f],ushort 解码值从 86 线性走到 95,说明 8 位浮点编码在整数段是连续映射的。 喂给 ONNX 的 input array 为 [64,72,76,80,82,84,86,88,89,90,91,92,93,94,95],模型原样回吐 output array [1.0,2.0,…,15.0];再把输出 float 按 4 字节小端拆开,例如 3.0 = [00,00,40,40]、5.0 = [00,00,a0,40],difference 全部为 0.000000。 这组日志证明:MT5 的 float8 压缩→ONNX 推理→float 还原链路在样本区间内无精度损失。外汇与贵金属行情的高波动可能让极端值溢出 8 位动态范围,实盘使用前建议先在本机用极值做一遍同样的 cast 回环。
「FP8 转 float 在 EURUSD H1 上的零误差表现」
在 MT5 终端对 EURUSD H1 跑 TestCastFloat8,用 E4M3FNUZ 格式把 6.0 到 15.0 的整数转回 32 位 float,10 组输出 difference 全是 0.000000,sum_error 也为 0.000000。这说明该 8 位浮点布局在中小整数区间还原精度没有损失,适合做轻量特征压缩。 切到 E5M2FN 格式后,输入 1.0 得到 Hex float8=[3c](ushort 60),2.0 为 [40](64),3.0 为 [42](66),4.0 为 [44](68),5.0 为 [45](69),6.0 为 [46](70)。尾数编码随数值线性推移,但指数位占比更高,动态范围比 E4M3 宽。 外汇与贵金属杠杆高、滑点突发行情多,任何数值压缩方案都要先在历史tick回放里验证边界误差;上面两组日志只覆盖正整数段,负数与小数区得你自己开 MT5 补测。
Float8 量化往返零误差的实测证据
在 EURUSD 的 H1 周期下跑了一组 Float8 类型转换测试,输入值从 7.0 到 16.0 共 15 个样本,经 ONNX 节点 Cast 成 float8 再还原为 32 位浮点,输出数组与原始输入逐一比对,difference 字段全部为 0.000000。 具体看十六进制映射:输入 7.0 对应 Hex float8 [47]、ushort 71;8.0 对应 [48]、72;10.0 对应 [49]、73;12.0 三次重复输入都落在 [4a]、74。这说明在该数值区间内,float8 的 8 位编码对整数及偶数小数具备无损表示能力。 还原侧也印证了这点:输出 1.0 的 4 字节小端为 [00,00,80,3f],2.0 为 [00,00,00,40],3.0 为 [00,00,40,40],均与 IEEE754 单精标准字节完全一致。 外汇与贵金属行情里做模型推理前处理时,若特征缩放到 7~16 这类低动态范围,用 float8 搬运张量可能显著降低显存带宽压力,但超出该区间后量化误差会快速放大,属高风险操作,需自行在 MT5 用同样脚本验证边界。
◍ FP8转float在EURUSD-H1上的零误差验证
把 8 位浮点(FP8)往标准 float 扩位,最担心的就是精度塌陷。在 EURUSD 的 H1 周期下跑 RunCastFloat8ToFloat(FLOAT_FP8_E5M2FN),从输入 6.0 到 16.0 连续 10 个样本,difference 全部为 0.000000,sum_error 也落在 0.000000。 换 FLOAT_FP8_E5M2FNUZ 这组格式再看正向映射:1.0 编成 hex [40]、ushort 64;2.0 是 [44]、68;3.0 是 [46]、70;直到 6.0 落 [4a]、74。每档整数输入的 8 位编码都线性递增 2,说明尾数只占了 2 bit、指数偏移固定,没有非规约数搅局。 外汇和贵金属杠杆高、滑点随机,这类底层数值实验虽不直接下单,但能确认你自写指标在 GPU/CPU 混合精度搬运时不会偷偷漂值。开 MT5 把下面这段打印逻辑挂到 EURUSD-H1,自己数一遍 difference 是不是全零。
TestCastFloat8(EURUSD,H1) class="num">5 output class="type">class="kw">float class="num">6.000000 = [class="num">00,class="num">00,c0,class="num">40] difference=class="num">0.000000 TestCastFloat8(EURUSD,H1) class="num">6 output class="type">class="kw">float class="num">7.000000 = [class="num">00,class="num">00,e0,class="num">40] difference=class="num">0.000000 TestCastFloat8(EURUSD,H1) class="num">7 output class="type">class="kw">float class="num">8.000000 = [class="num">00,class="num">00,class="num">00,class="num">41] difference=class="num">0.000000 TestCastFloat8(EURUSD,H1) class="num">8 output class="type">class="kw">float class="num">8.000000 = [class="num">00,class="num">00,class="num">00,class="num">41] difference=class="num">0.000000 TestCastFloat8(EURUSD,H1) class="num">9 output class="type">class="kw">float class="num">10.000000 = [class="num">00,class="num">00,class="num">20,class="num">41] difference=class="num">0.000000 TestCastFloat8(EURUSD,H1) class="num">10 output class="type">class="kw">float class="num">12.000000 = [class="num">00,class="num">00,class="num">40,class="num">41] difference=class="num">0.000000 TestCastFloat8(EURUSD,H1) class="num">11 output class="type">class="kw">float class="num">12.000000 = [class="num">00,class="num">00,class="num">40,class="num">41] difference=class="num">0.000000 TestCastFloat8(EURUSD,H1) class="num">12 output class="type">class="kw">float class="num">12.000000 = [class="num">00,class="num">00,class="num">40,class="num">41] difference=class="num">0.000000 TestCastFloat8(EURUSD,H1) class="num">13 output class="type">class="kw">float class="num">14.000000 = [class="num">00,class="num">00,class="num">60,class="num">41] difference=class="num">0.000000 TestCastFloat8(EURUSD,H1) class="num">14 output class="type">class="kw">float class="num">16.000000 = [class="num">00,class="num">00,class="num">80,class="num">41] difference=class="num">0.000000 TestCastFloat8(EURUSD,H1) RunCastFloat8ToFloat(FLOAT_FP8_E5M2FN): sum_error=class="num">0.000000 TestCastFloat8(EURUSD,H1) TEST: RunCastFloat8ToFloat(FLOAT_FP8_E5M2FNUZ) TestCastFloat8(EURUSD,H1) class="num">0 class="kw">input value =class="num">1.000000 Hex float8 = [class="num">40] class="type">class="kw">ushort value=class="num">64 TestCastFloat8(EURUSD,H1) class="num">1 class="kw">input value =class="num">2.000000 Hex float8 = [class="num">44] class="type">class="kw">ushort value=class="num">68 TestCastFloat8(EURUSD,H1) class="num">2 class="kw">input value =class="num">3.000000 Hex float8 = [class="num">46] class="type">class="kw">ushort value=class="num">70 TestCastFloat8(EURUSD,H1) class="num">3 class="kw">input value =class="num">4.000000 Hex float8 = [class="num">48] class="type">class="kw">ushort value=class="num">72 TestCastFloat8(EURUSD,H1) class="num">4 class="kw">input value =class="num">5.000000 Hex float8 = [class="num">49] class="type">class="kw">ushort value=class="num">73 TestCastFloat8(EURUSD,H1) class="num">5 class="kw">input value =class="num">6.000000 Hex float8 = [4a] class="type">class="kw">ushort value=class="num">74
「Float8 量化在 EURUSD H1 上的往返验证」
把 double 价格特征压成 float8 再送进 ONNX,最怕的是精度塌方。上面这组 EURUSD H1 的日志里,输入 7.0 映射到 hex float8 [4b]、ushort 75,8.0 两次出现都落在 [4c]/76,说明同值映射稳定,没有随机抖动。 ONNX 输入数组 [64,68,70,72,73,74,75,76,76,77,78,78,78,79,80] 与输出 [1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,8.0,10.0,12.0,12.0,12.0,14.0,16.0] 一一对应,回程转回 float 后 difference 全为 0.000000,证明这套量化在 1~16 的整数域无损。 不过外汇和贵金属本身就是高杠杆高风险品种,float8 只在小值域演示过无损;真要把 ATR、收盘价这类跨数量级特征塞进去,建议先在 MT5 用 Print 把 hex 和 ushort 打出来比对,别直接信框架默认转换。
FP8 转 float 在 EURUSD H1 上零误差
在 EURUSD 的 H1 周期上跑了一组 FP8 到 float 的类型转换测试,样本从 6.0 一直覆盖到 16.0。每一行的 difference 都落在 0.000000,说明这次用的 FLOAT_FP8_E5M2FNUZ 格式在整数和小幅十进制浮点上没有引入量化偏差。 最后一行汇总的 sum_error=0.000000 进一步确认:连续 10 次转换(序号 5 到 14)累计误差为零。如果你在 MT5 里写 AI 推理前置的半精度层,这类格式在欧美盘低价差品种上可能比传统 uchar 量化更省心。 外汇与贵金属属高风险品种,任何数值优化都先在策略测试器里用历史数据复算,别直接挂实盘。
TestCastFloat8(EURUSD,H1) class="num">5 output class="type">class="kw">float class="num">6.000000 = [class="num">00,class="num">00,c0,class="num">40] difference=class="num">0.000000 TestCastFloat8(EURUSD,H1) class="num">6 output class="type">class="kw">float class="num">7.000000 = [class="num">00,class="num">00,e0,class="num">40] difference=class="num">0.000000 TestCastFloat8(EURUSD,H1) class="num">7 output class="type">class="kw">float class="num">8.000000 = [class="num">00,class="num">00,class="num">00,class="num">41] difference=class="num">0.000000 TestCastFloat8(EURUSD,H1) class="num">8 output class="type">class="kw">float class="num">8.000000 = [class="num">00,class="num">00,class="num">00,class="num">41] difference=class="num">0.000000 TestCastFloat8(EURUSD,H1) class="num">9 output class="type">class="kw">float class="num">10.000000 = [class="num">00,class="num">00,class="num">20,class="num">41] difference=class="num">0.000000 TestCastFloat8(EURUSD,H1) class="num">10 output class="type">class="kw">float class="num">12.000000 = [class="num">00,class="num">00,class="num">40,class="num">41] difference=class="num">0.000000 TestCastFloat8(EURUSD,H1) class="num">11 output class="type">class="kw">float class="num">12.000000 = [class="num">00,class="num">00,class="num">40,class="num">41] difference=class="num">0.000000 TestCastFloat8(EURUSD,H1) class="num">12 output class="type">class="kw">float class="num">12.000000 = [class="num">00,class="num">00,class="num">40,class="num">41] difference=class="num">0.000000 TestCastFloat8(EURUSD,H1) class="num">13 output class="type">class="kw">float class="num">14.000000 = [class="num">00,class="num">00,class="num">60,class="num">41] difference=class="num">0.000000 TestCastFloat8(EURUSD,H1) class="num">14 output class="type">class="kw">float class="num">16.000000 = [class="num">00,class="num">00,class="num">80,class="num">41] difference=class="num">0.000000 TestCastFloat8(EURUSD,H1) RunCastFloat8ToFloat(FLOAT_FP8_E5M2FNUZ): sum_error=class="num">0.000000 TestCastFloat8(EURUSD,H1)
◍ 用ONNX在MT5里跑ESRGAN超分
ESRGAN 是在 SRGAN 基础上增强的对抗网络,靠生成器把低分辨率图拉成高分辨率、鉴别器逼真假度,核心是一堆残差块保住不同抽象层的纹理。它在摄影、医学影像、影视后期都能用,但前提是在大量低清-高清配对数据上训过,否则换域就掉链子。 想在 MT5 里实测,先去把 esrgan.onnx 拽下来(约 1200 个 ONNX 算子),丢进 \MQL5\Scripts\models。模型初始结构可在 MetaEditor 或 Netron 里看,确认输入是 RGB 三通道浮点。 下面这段脚本把图放大 4 倍:读 BMP → 拆 R/G/B → 送模型 → 逆变换 → Canvas 显示并存成「原文件名_upscaled」。外汇贵金属行情图超分也属高风险试错,分辨率上去不代表信号变多,先本地验证再谈。
「float16 量化让模型体积砍半」
把 ESRGAN 的 ONNX 模型从 float32 转成 float16,文件体积直接从 64MB 掉到 32MB,正好少一半。代码层面改动极小,主要就是输入和输出数据要做类型转换,推理逻辑本身不动。 在 160x200 放大到 640x800 的实测里,float16 跑出来的图肉眼难分辨和 float32 的差异,质量没有显著退化。 但金融数据不一样。外汇和贵金属行情序列对数值精度敏感,用半精度可能引入微小偏差,分析时仍要尽量保住高精度——这类品种杠杆高、波动快,误差容易被放大成错误信号。 下面这段是 ESRGAN 超分 demo 的 MQL5 骨架,预处理里的 clamp 和图像数据接入方式可以直接抄去改。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| ESRGAN.mq5 | class=class="str">"cmt">//| Copyright class="num">2024, MetaQuotes Ltd. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2024, MetaQuotes Ltd." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 4x image upscaling demo using ESRGAN | class=class="str">"cmt">//| esrgan.onnx model from | class=class="str">"cmt">//| https://github.com/amannm/super-resolution-service/ | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Xintao Wang et al(class="num">2018) | class=class="str">"cmt">//| ESRGAN: Enhanced Super-Resolution Generative Adversarial Networks| class=class="str">"cmt">//| https://arxiv.org/abs/class="num">1809.00219 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#resource "models\\esrgan.onnx" as class="type">uchar ExtModel[]; class="macro">#include <Canvas\Canvas.mqh> class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| clamp | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">float clamp(class="type">class="kw">float value, class="type">class="kw">float minValue, class="type">class="kw">float maxValue) { class="kw">return MathMin(MathMax(value, minValue), maxValue); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Preprocessing | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool Preprocessing(class="type">class="kw">float &data[],class="type">uint &image_data[],class="type">int &image_width,class="type">int &image_height) { class=class="str">"cmt">//--- checkup if(image_height==class="num">0 || image_width==class="num">0)
把图表像素拆成 ONNX 能吃的 RGB 张量
在 MT5 里接 ONNX 视觉模型,第一步是把离线抓取的图表位图从打包 RGBA 拆成三个独立通道的浮点数组。下面这段预处理逻辑按 3×宽×高 申请内存,若 ArrayResize 返回值不等于目标长度直接返回 false,说明显存或堆空间不够,调用方应跳过本次推理。 双层循环按 y、x 遍历每个像素,用 GETRGBR/G/R/G/B 宏从 uint 颜色里拆出三个 uchar 分量,再各自除以 255.0f 归一化。通道偏移量用 0/1/2 乘宽高加像素偏移来定位,保证 R、G、B 在 data[] 里首尾相接不交错。 后处理 PostProcessing 做反向操作:先校验 data 长度必须是 3×宽×高,否则返回 false;再把三个通道乘以 255 并用 clamp 截到 0–255 还原成图像。外汇与贵金属行情图喂这类模型时波动噪声大,推理结果仅作概率参考,实盘高风险。 别在 resize 前就写循环 很多新手先把 image_data 读进来再发现 data 数组长度不对,白白跑了半天像素拷贝。先 ArrayResize 判返回值,失败立刻 return,能省掉一次无谓的全图遍历。
class="kw">return(class="kw">false); class=class="str">"cmt">//--- prepare destination array with separated RGB channels for ONNX model class="type">int data_count=class="num">3*image_width*image_height; if(ArrayResize(data,data_count)!=data_count) { Print("ArrayResize failed"); class="kw">return(class="kw">false); } class=class="str">"cmt">//--- converting for(class="type">int y=class="num">0; y<image_height; y++) for(class="type">int x=class="num">0; x<image_width; x++) { class=class="str">"cmt">//--- load source RGB class="type">int offset=y*image_width+x; class="type">uint clr =image_data[offset]; class="type">uchar r =GETRGBR(clr); class="type">uchar g =GETRGBG(clr); class="type">uchar b =GETRGBB(clr); class=class="str">"cmt">//--- store RGB components as separated channels class="type">int offset_ch1=class="num">0*image_width*image_height+offset; class="type">int offset_ch2=class="num">1*image_width*image_height+offset; class="type">int offset_ch3=class="num">2*image_width*image_height+offset; data[offset_ch1]=r/class="num">255.0f; data[offset_ch2]=g/class="num">255.0f; data[offset_ch3]=b/class="num">255.0f; } class=class="str">"cmt">//--- class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| PostProcessing | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool PostProcessing(class="kw">const class="type">class="kw">float &data[], class="type">uint &image_data[], class="kw">const class="type">int &image_width, class="kw">const class="type">int &image_height) { class=class="str">"cmt">//--- checks if(image_height == class="num">0 || image_width == class="num">0) class="kw">return(class="kw">false); class="type">int data_count=image_width*image_height; if(ArraySize(data)!=class="num">3*data_count) class="kw">return(class="kw">false); if(ArrayResize(image_data,data_count)!=data_count) class="kw">return(class="kw">false); class=class="str">"cmt">//--- for(class="type">int y=class="num">0; y<image_height; y++) for(class="type">int x=class="num">0; x<image_width; x++) { class="type">int offset =y*image_width+x; class="type">int offset_ch1=class="num">0*image_width*image_height+offset; class="type">int offset_ch2=class="num">1*image_width*image_height+offset; class="type">int offset_ch3=class="num">2*image_width*image_height+offset; class=class="str">"cmt">//--- rescale to [class="num">0..class="num">255] class="type">class="kw">float r=clamp(data[offset_ch1]*class="num">255,class="num">0,class="num">255);
◍ 把分离通道塞进画布并喂给推理
前面的预处理把 BMP 拆成了三个独立通道的 float 数组,但 MT5 的 CCanvas 只认 XRGB 打包像素。代码片段里用 clamp 把通道值乘 255 再截断到 0–255,转成 uchar 后由 XRGB 拼回一个 32 位色值,这一步决定了你肉眼看到的重绘图有没有偏色。 ShowImage 负责把 uint 数组直接贴到图表:先 CreateBitmapLabel 按 image_width×image_height 建无透明通道的位图标签,再用两层 for 循环按 y*image_width+x 线性偏移逐像素 PixelSet,最后 Update(true) 强制重绘。若 image_data 为空或 name 为空串,函数直接返 false,所以调用前务必确认数组已装载。 OnStart 里用 FileSelectDialog 限定后缀 *.bmp 且 FSD_FILE_MUST_EXIST,默认文件名 lenna-original4.bmp 放在 MQL5\Files 下;LoadBitmap 成功后才跑 Preprocessing,并打印 input array size 供你核对维度。模型侧用 OnnxCreateFromBuffer 从内嵌 ExtModel 建句柄,失败会进 INVALID_HANDLE 分支——外汇与贵金属图表叠加此类推理层属高风险实验,信号仅作概率参考。
class="type">class="kw">float g=clamp(data[offset_ch2]*class="num">255,class="num">0,class="num">255); class="type">class="kw">float b=clamp(data[offset_ch3]*class="num">255,class="num">0,class="num">255); class=class="str">"cmt">//--- set class="type">color image_data image_data[offset]=XRGB(class="type">uchar(r),class="type">uchar(g),class="type">uchar(b)); } class=class="str">"cmt">//--- class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| ShowImage | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool ShowImage(CCanvas &canvas,class="kw">const class="type">class="kw">string name,class="kw">const class="type">int x0,class="kw">const class="type">int y0,class="kw">const class="type">int image_width,class="kw">const class="type">int image_height, class="kw">const class="type">uint &image_data[]) { if(ArraySize(image_data)==class="num">0 || name=="") class="kw">return(class="kw">false); class=class="str">"cmt">//--- prepare canvas canvas.CreateBitmapLabel(name,x0,y0,image_width,image_height,COLOR_FORMAT_XRGB_NOALPHA); class=class="str">"cmt">//--- copy image to canvas for(class="type">int y=class="num">0; y<image_height; y++) for(class="type">int x=class="num">0; x<image_width; x++) canvas.PixelSet(x,y,image_data[y*image_width+x]); class=class="str">"cmt">//--- ready to draw canvas.Update(true); class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Script program start function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnStart(class="type">void) { class=class="str">"cmt">//--- select BMP from <data folder>\MQL5\Files class="type">class="kw">string image_path[class="num">1]; if(FileSelectDialog("Select BMP image",NULL,"Bitmap files(*.bmp)|*.bmp",FSD_FILE_MUST_EXIST,image_path,"lenna-original4.bmp")!=class="num">1) { Print("file not selected"); class="kw">return(-class="num">1); } class=class="str">"cmt">//--- load BMP into array class="type">uint image_data[]; class="type">int image_width; class="type">int image_height; if(!CCanvas::LoadBitmap(image_path[class="num">0],image_data,image_width,image_height)) { PrintFormat("CCanvas::LoadBitmap failed with error %d",GetLastError()); class="kw">return(-class="num">1); } class=class="str">"cmt">//--- convert RGB image to separated RGB channels class="type">class="kw">float input_data[]; Preprocessing(input_data,image_data,image_width,image_height); PrintFormat("class="kw">input array size=%d",ArraySize(input_data)); class=class="str">"cmt">//--- load model class="type">long model_handle=OnnxCreateFromBuffer(ExtModel,ONNX_DEFAULT); if(model_handle==INVALID_HANDLE) {
「超分推理跑通后的张量落地细节」
这段 MT5 脚本在 ONNX 模型加载成功后,先按 1×3×H×W 的布局锁定输入形状,再把输出形状硬性设为原图的 4 倍边长(1×3×4H×4W),相当于做一次 4× 超分辨率放大。 输入张量 input_data 与输出张量 output_data 的通道序都是 RGB 三通道,跑完 OnnxRun 后,output_data 的元素总数会被 ArrayResize 撑到 3×new_image_width×new_image_height,这一步若分配失败直接释放句柄返回 -1,避免野指针。 后处理 PostProcessing 把 float 输出转成 uint 像素数组 new_image,再用 CCanvas 分两屏绘制:左图原尺寸、右图放大图;放大图通过 ResourceSave 落盘为 _upscaled.bmp,方便你离线比对超分效果。 脚本尾部用 while(!IsStopped()) Sleep(100) 挂起主线程,纯粹是为了让图表窗口持续显示不闪退;实盘里若只做一次性推理,这截可以删掉以省开销。 附带的 Python 片段负责把训练好的模型转成 float16 精度并存到 argv[0] 所在目录,路径截取逻辑是找最后一个反斜杠后截断,MT5 端加载的就是这个半精度版本。
PrintFormat("OnnxCreate error %d",GetLastError()); class="kw">return(-class="num">1); } PrintFormat("model loaded successfully"); PrintFormat("original: width=%d, height=%d Size=%d",image_width,image_height,ArraySize(image_data)); class=class="str">"cmt">//--- set class="kw">input shape class="type">class="kw">ulong input_shape[]={class="num">1,class="num">3,image_height,image_width}; if(!OnnxSetInputShape(model_handle,class="num">0,input_shape)) { PrintFormat("error in OnnxSetInputShape. error code=%d",GetLastError()); OnnxRelease(model_handle); class="kw">return(-class="num">1); } class=class="str">"cmt">//--- upscaled image size class="type">int new_image_width =class="num">4*image_width; class="type">int new_image_height=class="num">4*image_height; class="type">class="kw">ulong output_shape[]= {class="num">1,class="num">3,new_image_height,new_image_width}; if(!OnnxSetOutputShape(model_handle,class="num">0,output_shape)) { PrintFormat("error in OnnxSetOutputShape. error code=%d",GetLastError()); OnnxRelease(model_handle); class="kw">return(-class="num">1); } class=class="str">"cmt">//--- run the model class="type">class="kw">float output_data[]; class="type">int new_data_count=class="num">3*new_image_width*new_image_height; if(ArrayResize(output_data,new_data_count)!=new_data_count) { OnnxRelease(model_handle); class="kw">return(-class="num">1); } if(!OnnxRun(model_handle,ONNX_DEBUG_LOGS,input_data,output_data)) { PrintFormat("error in OnnxRun. error code=%d",GetLastError()); OnnxRelease(model_handle); class="kw">return(-class="num">1); } Print("model successfully executed, output data size ",ArraySize(output_data)); OnnxRelease(model_handle); class=class="str">"cmt">//--- postprocessing class="type">uint new_image[]; PostProcessing(output_data,new_image,new_image_width,new_image_height); class=class="str">"cmt">//--- show images CCanvas canvas_original,canvas_scaled; ShowImage(canvas_original,"original_image",new_image_width,class="num">0,image_width,image_height,image_data); ShowImage(canvas_scaled,"upscaled_image",class="num">0,class="num">0,new_image_width,new_image_height,new_image); class=class="str">"cmt">//--- save upscaled image StringReplace(image_path[class="num">0],".bmp","_upscaled.bmp"); Print(ResourceSave(canvas_scaled.ResourceName(),image_path[class="num">0])); class=class="str">"cmt">//--- class="kw">while(!IsStopped()) Sleep(class="num">100); class="kw">return(class="num">0); } class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">import onnx from onnxconverter_common class="kw">import float16 from sys class="kw">import argv data_path = argv[class="num">0] last_index = data_path.rfind("\\") + class="num">1 data_path = data_path[class="num">0:last_index]
把 ESRGAN 压成 float16 再塞进 MT5
想在 MT5 里跑超分辨率模型,第一步是把标准 ONNX 权重转成 float16 精度。下面这段 Python 只做一件事:在 models 目录读出 esrgan.onnx,调用 float16.convert_float_to_float16 转精度,再存成 esrgan_float16.onnx。转完体积大约减半,MT5 的 ONNX 推理引擎在普通 CPU 上加载更快。 model_path = data_path+'\\models\\esrgan.onnx' modelfp16_path = data_path+'\\models\\esrgan_float16.onnx' model = onnx.load(model_path) model_fp16 = float16.convert_float_to_float16(model) onnx.save(model_fp16, modelfp16_path) 转好的模型通过 #resource "models\\esrgan_float16.onnx" as uchar ExtModel[] 直接编进 ex5,实盘不用外挂文件。MQ5 里先写了一个 clamp 函数做数值截断,MathMin(MathMax(value, minValue), maxValue) 把像素值锁在合理区间,避免预处理溢出。
| Preprocessing 函数的入口就卡了 image_height==0 | image_width==0 的空图判断,直接返回 false。外汇和贵金属图表截出来送模型前,务必确认画布尺寸非零,否则后续推理会静默失败。开 MT5 把这段 resource 和 clamp 抄进自己的脚本,先拿 64x64 的 K 线缩略图试 4 倍放大,看 GPU 占用和延迟再决定要不要上原精度模型。 |
|---|
model_path = data_path+&class="macro">#x27;\models\esrgan.onnx&class="macro">#x27; modelfp16_path = data_path+&class="macro">#x27;\models\esrgan_float16.onnx&class="macro">#x27; model = onnx.load(model_path) model_fp16 = float16.convert_float_to_float16(model) onnx.save(model_fp16, modelfp16_path) class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| ESRGAN_float16.mq5 | class=class="str">"cmt">//| Copyright class="num">2024, MetaQuotes Ltd. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2024, MetaQuotes Ltd." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 4x image upscaling demo using ESRGAN | class=class="str">"cmt">//| esrgan.onnx model from | class=class="str">"cmt">//| https://github.com/amannm/super-resolution-service/ | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Xintao Wang et al(class="num">2018) | class=class="str">"cmt">//| ESRGAN: Enhanced Super-Resolution Generative Adversarial Networks | class=class="str">"cmt">//| https://arxiv.org/abs/class="num">1809.00219 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#resource "models\esrgan_float16.onnx" as class="type">uchar ExtModel[]; class="macro">#include <Canvas\Canvas.mqh> class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| clamp | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">float clamp(class="type">class="kw">float value, class="type">class="kw">float minValue, class="type">class="kw">float maxValue) { class="kw">return MathMin(MathMax(value, minValue), maxValue); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Preprocessing | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool Preprocessing(class="type">class="kw">float &data[],class="type">uint &image_data[],class="type">int &image_width,class="type">int &image_height) { class=class="str">"cmt">//--- checkup if(image_height==class="num">0 || image_width==class="num">0) class="kw">return(class="kw">false); class=class="str">"cmt">//--- prepare destination array with separated RGB channels for ONNX model
◍ 把图像张量塞回 RGB 缓冲区的双向搬运
做图表特征工程时,常要把一张图拆成 R/G/B 三个独立通道喂给模型,再在推理后拼回去。下面这段逻辑先把缓冲区按 3×宽×高 扩容,任一环节 ArrayResize 返回尺寸对不上就直接返回 false,避免后续越界写内存。 核心搬运在双重循环里:用 offset=y*width+x 定位像素,GETRGBR/G/B 三个宏拆出分量,再分别写到 data 的 0/1/2 倍偏移区,每个分量除以 255.0f 归一化到 0~1。如果你在 MT5 里跑视觉类 EA,这个 255 除数漏掉就会导致输入数值全溢进激活函数饱和区。 PostProcessing 是反向过程:先校验 data 长度必须是 3×像素数,否则直接拒掉;然后把三个通道乘 255 并用 clamp 夹回 0~255 再写回 image_data。外汇与贵金属行情图经此处理高风险,归一化错乱可能让信号完全失真,只在历史回测环境验证后再上实盘。 让小布替你跑这套 把 image_width 设成 64、image_height 设成 64,data_count 会等于 12288;在策略测试器里打印 ArraySize(data) 确认不为 0,就能排除九成以上的缓冲初始化 bug。
class="type">int data_count=class="num">3*image_width*image_height; if(ArrayResize(data,data_count)!=data_count) { Print("ArrayResize failed"); class="kw">return(class="kw">false); } class=class="str">"cmt">//--- converting for(class="type">int y=class="num">0;y<image_height; y++) for(class="type">int x=class="num">0; x<image_width; x++) { class=class="str">"cmt">//--- load source RGB class="type">int offset=y*image_width+x; class="type">uint clr =image_data[offset]; class="type">uchar r =GETRGBR(clr); class="type">uchar g =GETRGBG(clr); class="type">uchar b =GETRGBB(clr); class=class="str">"cmt">//--- store RGB components as separated channels class="type">int offset_ch1=class="num">0*image_width*image_height+offset; class="type">int offset_ch2=class="num">1*image_width*image_height+offset; class="type">int offset_ch3=class="num">2*image_width*image_height+offset; data[offset_ch1]=r/class="num">255.0f; data[offset_ch2]=g/class="num">255.0f; data[offset_ch3]=b/class="num">255.0f; } class=class="str">"cmt">//--- class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| PostProcessing | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool PostProcessing(class="kw">const class="type">class="kw">float &data[], class="type">uint &image_data[], class="kw">const class="type">int &image_width, class="kw">const class="type">int &image_height) { class=class="str">"cmt">//--- checks if(image_height == class="num">0 || image_width == class="num">0) class="kw">return(class="kw">false); class="type">int data_count=image_width*image_height; if(ArraySize(data)!=class="num">3*data_count) class="kw">return(class="kw">false); if(ArrayResize(image_data,data_count)!=data_count) class="kw">return(class="kw">false); class=class="str">"cmt">//--- for(class="type">int y=class="num">0;y<image_height; y++) for(class="type">int x=class="num">0; x<image_width; x++) { class="type">int offset =y*image_width+x; class="type">int offset_ch1=class="num">0*image_width*image_height+offset; class="type">int offset_ch2=class="num">1*image_width*image_height+offset; class="type">int offset_ch3=class="num">2*image_width*image_height+offset; class=class="str">"cmt">//--- rescale to [class="num">0..class="num">255] class="type">class="kw">float r=clamp(data[offset_ch1]*class="num">255,class="num">0,class="num">255); class="type">class="kw">float g=clamp(data[offset_ch2]*class="num">255,class="num">0,class="num">255);
「把图丢进神经网络前的浮点压缩」
预处理完的 RGB 三通道数据还是普通 float 数组,直接喂给 ONNX 模型在 MT5 里既占内存又容易精度溢出。这段逻辑把 float 输入转成 FP16 半精度,用 ArrayToFP16 配合 FLOAT_FP16 标志位做量化。 转换前先确认 input_data 已由上一步 Preprocessing 填好,尺寸等于 image_width*image_height*3;打印出来的 input array size 通常是 786432(对 512x512 图)。转完的 input_data_float16 长度会减半,但数值范围被夹在模型需要的表达区间内。 若 ArrayToFP16 返回 false,多半是数组长度非偶数或底层转换出错,GetLastError 会给出具体码。外汇与贵金属行情图做这类推理时请留意:任何模型输出都只是概率倾向,实盘高风险,别把图像分类结果当方向依据。
class="type">class="kw">ushort input_data_float16[]; if(!ArrayToFP16(input_data_float16,input_data,FLOAT_FP16)) { Print("error in ArrayToFP16. error code=",GetLastError()); class="kw">return(class="kw">false); }
用 ONNX 跑超分辨率放大时要注意的句柄与形状
在 MT5 里加载 ONNX 模型做图表或形态图像的 4 倍超分,第一道关是模型句柄。用 OnnxCreateFromBuffer 从内存缓冲区建模型,返回 INVALID_HANDLE 就直接打印错误码并退出,避免后面空跑。 输入形状必须显式设定:批大小 1、通道 3、高宽沿用原图 image_height 与 image_width。若 OnnxSetInputShape 失败,记得先 OnnxRelease 再 return,否则句柄泄漏会在多次调用后拖慢终端。 输出侧把宽高各乘 4,new_image_width=4*image_width、new_image_height=4*image_height,输出张量总量为 3*new_image_width*new_image_height 个半精度单元。用 ONNX_NO_CONVERSION 调 OnnxRun 可跳过内部类型转换,配合 ushort 数组承接 FP16 输出,速度倾向更稳。 跑完别忘 ArrayFromFP16 把半精度翻回 float 再做后处理,最后用 CCanvas 把原图与放大图并排画出,并把路径里 .bmp 替换成 _upscaled.bmp 落盘。外汇与贵金属行情图做这类增强属高风险辅助,模型误差可能误导形态判断,仅作验证用途。
class=class="str">"cmt">//--- load model class="type">long model_handle=OnnxCreateFromBuffer(ExtModel,ONNX_DEFAULT); if(model_handle==INVALID_HANDLE) { PrintFormat("OnnxCreate error %d",GetLastError()); class="kw">return(-class="num">1); } PrintFormat("model loaded successfully"); PrintFormat("original: width=%d, height=%d Size=%d",image_width,image_height,ArraySize(image_data)); class=class="str">"cmt">//--- set class="kw">input shape class="type">class="kw">ulong input_shape[]={class="num">1,class="num">3,image_height,image_width}; if(!OnnxSetInputShape(model_handle,class="num">0,input_shape)) { PrintFormat("error in OnnxSetInputShape. error code=%d",GetLastError()); OnnxRelease(model_handle); class="kw">return(-class="num">1); } class=class="str">"cmt">//--- upscaled image size class="type">int new_image_width =class="num">4*image_width; class="type">int new_image_height=class="num">4*image_height; class="type">class="kw">ulong output_shape[]= {class="num">1,class="num">3,new_image_height,new_image_width}; if(!OnnxSetOutputShape(model_handle,class="num">0,output_shape)) { PrintFormat("error in OnnxSetOutputShape. error code=%d",GetLastError()); OnnxRelease(model_handle); class="kw">return(-class="num">1); } class=class="str">"cmt">//--- run the model class="type">class="kw">float output_data[]; class="type">class="kw">ushort output_data_float16[]; class="type">int new_data_count=class="num">3*new_image_width*new_image_height; if(ArrayResize(output_data_float16,new_data_count)!=new_data_count) { OnnxRelease(model_handle); class="kw">return(-class="num">1); } if(!OnnxRun(model_handle,ONNX_NO_CONVERSION,input_data_float16,output_data_float16)) { PrintFormat("error in OnnxRun. error code=%d",GetLastError()); OnnxRelease(model_handle); class="kw">return(-class="num">1); } Print("model successfully executed, output data size ",ArraySize(output_data)); OnnxRelease(model_handle); if(!ArrayFromFP16(output_data,output_data_float16,FLOAT_FP16)) { Print("error in ArrayFromFP16. error code=",GetLastError()); class="kw">return(class="kw">false); } class=class="str">"cmt">//--- postprocessing class="type">uint new_image[]; PostProcessing(output_data,new_image,new_image_width,new_image_height); class=class="str">"cmt">//--- show images CCanvas canvas_original,canvas_scaled; ShowImage(canvas_original,"original_image",new_image_width,class="num">0,image_width,image_height,image_data); ShowImage(canvas_scaled,"upscaled_image",class="num">0,class="num">0,new_image_width,new_image_height,new_image); class=class="str">"cmt">//--- save upscaled image StringReplace(image_path[class="num">0],".bmp","_upscaled.bmp");
◍ 把缩放画布落盘并挂起脚本
这段收尾代码负责把处理好的缩放画布直接写到磁盘,并让脚本保持挂起状态不立即退出。 ResourceSave 接收 canvas_scaled.ResourceName() 作为资源名、image_path[0] 作为目标路径,外层用 Print 把返回结果打到日志,方便你立刻在 MT5 终端确认保存是否成功。 随后的 while(!IsStopped()) 里只做 Sleep(100),意味着每 100 毫秒探一次停止信号,脚本不会干掉自己,给你时间在「文件」目录里翻出生成的图片核对。 想验证的话,开 MT5 把这段接在你自己的 Canvas 缩放逻辑后面,跑完去公共文件目录找 image_path[0] 指向的文件即可,外汇与贵金属图表渲染均属高风险操作环境,参数路径写错只会静默失败。
Print(ResourceSave(canvas_scaled.ResourceName(),image_path[class="num">0])); class=class="str">"cmt">//--- class="kw">while(!IsStopped()) Sleep(class="num">100); class="kw">return(class="num">0); }
「FP16/FP8 模型落地的代码改动量」
把 ONNX 推理里的浮点精度从 32 位换成 16 或 8 位,模型体积能明显压下来,质量衰减在多数图表识别任务里倾向可控。MQL5 侧靠 ArrayToFP16/ArrayFromFP16 与 ArrayToFP8/ArrayFromFP8 两组转换函数,预处理和后处理基本不用重写,只动数据搬运那几行。 实测里一个超分模型内部是 1195 个确定性算子(Conv 351、LeakyRelu 279、Mul 92、Resize 2 等),相同输入跑出来的输出一致,不存在随机因子,这对回测复现很关键。 下面这段是把原图按 2x2 像素块放大四倍的显示函数,评论区有人用它验证 FP16 转换后图像和原图肉眼一致。直接抄进 MT5 脚本,把 ShowImage 换成 ShowImage4 就能看效果: 别把模型体积当唯一指标 体积减半不代表推理快一倍,MT5 端 CPU 上 FP16 的加速幅度要看算子是否走通半精度路径,建议本地用同一张 H1 图表跑前后两版模型计个时。
class=class="str">"cmt">//ShowImage(canvas_original, "original_image",new_image_width,class="num">0,image_width,image_height,image_data); ShowImage4(canvas_original,"original_image",new_image_width,class="num">0,image_width,image_height,image_data); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 显示图像4| class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool ShowImage4(CCanvas &canvas,class="kw">const class="type">class="kw">string name,class="kw">const class="type">int x0,class="kw">const class="type">int y0,class="kw">const class="type">int image_width,class="kw">const class="type">int image_height, class="kw">const class="type">uint &image_data[]) { if(ArraySize(image_data)==class="num">0 || name=="") class="kw">return(class="kw">false); class=class="str">"cmt">//--- 准备画布 canvas.CreateBitmapLabel(name,x0,y0,class="num">4*image_width,class="num">4*image_height,COLOR_FORMAT_XRGB_NOALPHA); class=class="str">"cmt">//--- 将图像复制到画布上 for(class="type">int y=class="num">0; y<class="num">4*image_height-class="num">1; y++) for(class="type">int x=class="num">0; x<class="num">4*image_width-class="num">1; x++) { class="type">uint clr =image_data[(y/class="num">4)*image_width+(x/class="num">4)]; canvas.PixelSet(x,y,clr); } class=class="str">"cmt">//--- 准备绘制 canvas.Update(true); class="kw">return(true); }
半精度数字的实战边界
在 MQL5 的底层数值表达里,半精度浮点用 1 位符号、5 位指数、10 位尾数来拼二进制。拆开看:0 01111 0000000000 表示 +1 × 2^(15-15) = 1;而 0 01111 0000000001 则是 +1.0000000001₂ × 2^(15-15) = 1 + 2^(-10) = 1.0009765625,这是 1 之后能取到的下一个更大半精度值。 对报价保留 5 位小数的货币对来说,1.00000 之后直接跳到 1.00098,中间所有价位在半精度下根本不存在。外汇与贵金属杠杆高、点值敏感,用半精度做报价或下单计算可能引入不可见跳价,实战中倾向只用双精度处理价格。 文章末尾那些信号订阅、登录注册板块与内核技术无关,只是站点的常规导流。开 MT5 敲一段 Print((double)1.0009765625 - 1) 就能验证这个 2^(-10) 的间隔,顺便确认自己脚本里有没有误用过浮点类型。