重构经典策略(第七部分):基于USDJPY的外汇市场与主权债务分析·综合运用
「给 ONNX 模型喂对输入输出维度」
在 MT5 里加载训练好的 USDJPY 推理模型,第一道坎不是预测,而是把输入输出张量形状钉死。下面这段先校验输入:若 OnnxSetInputShape 返回 false,说明你本地模型的输入节点数量和代码里写的 {1,15} 对不上,直接弹窗报出 OnnxGetInputCount 的真实值,并 return(false) 中断初始化。 输出侧同样不能马虎,output_shape 写死成 {1,1} 表示单次推理只吐一个标量(例如下一根 K 线的回归值)。若 OnnxSetOutputShape 失败,同样报警并退出,避免后续拿空指针做矩阵运算把终端搞崩。
| 模型维度没问题后,真正喂数据前还得把标准化参数读进来。代码里硬编码了文件名 "usdjpy scaling factors.csv",用 FileOpen 以 FILE_READ | FILE_CSV | FILE_ANSI 打开;若句柄不等于 INVALID_HANDLE 才算打开成功。 |
|---|
读文件时有个隐蔽点:counter 超过 100 就强制 break,这是防 csv 格式异常时死循环。真正落库的逻辑在 counter∈[17,31] 时填 mean_values[0..14],counter∈[33,47] 时填 std_values[0..14]——也就是说你的标准化因子必须严格排在 csv 的第 18 到 32 行(均值)和 34 到 48 行(标准差),否则数组下标会错位。外汇与贵金属杠杆高,模型推理仅是辅助,实盘前务必在策略测试器跑通这段 IO 逻辑。
Alert("Incorrect input shape, model has input shape ", OnnxGetInputCount(onnx_model)); class="kw">return(false); } class=class="str">"cmt">//--- Set the output shape class="type">ulong output_shape [] = {class="num">1,class="num">1}; class=class="str">"cmt">//--- Check if the output shape is valid if(!OnnxSetOutputShape(onnx_model,class="num">0,output_shape)) { Alert("Incorrect output shape, model has output shape ", OnnxGetOutputCount(onnx_model)); class="kw">return(false); } class=class="str">"cmt">//--- Everything went fine class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Load our scaling factors | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void load_scaling_factors(class="type">void) { class=class="str">"cmt">//--- Read in the file class="type">class="kw">string file_name = "usdjpy scaling factors.csv"; class=class="str">"cmt">//--- Try open the file class="type">int result = FileOpen(file_name,FILE_READ|FILE_CSV|FILE_ANSI,","); class=class="str">"cmt">//Strings of ANSI type(one byte symbols). class=class="str">"cmt">//--- Check the result if(result != INVALID_HANDLE) { Print("Opened the file"); class=class="str">"cmt">//--- Store the values of the file class="type">int counter = class="num">0; class="type">class="kw">string value = ""; while(!FileIsEnding(result) && !IsStopped()) class=class="str">"cmt">//read the entire csv file to the end { if (counter > class="num">100) class=class="str">"cmt">//if you aim to read class="num">10 values set a break point after class="num">10 elements have been read break; class=class="str">"cmt">//stop the reading progress value = FileReadString(result); Print("Trying to read class="type">class="kw">string: ",value," count value: ",counter); class=class="str">"cmt">//--- Check where we are if((counter >= class="num">17) && (counter < class="num">32)) { mean_values[counter - class="num">17] = (class="type">class="kw">float) value; } class=class="str">"cmt">//--- Check where we are if((counter >= class="num">33) && (counter < class="num">48)) { std_values[counter - class="num">33] = (class="type">class="kw">float) value; } class=class="str">"cmt">//--- Reading a new row if(FileIsLineEnding(result)) { Print("row++"); }
◍ 把多品种行情塞进ONNX模型前先过标准化
模型推理不是直接把报价丢进去就能跑。上面这段把美债10年、日债10年和美元日元三个品种的 OHLCV 共 15 个字段拼成 vectorf,再逐列减去均值除以标准差,否则不同量纲会把网络激活值冲垮。
标准化循环里 model_inputs[i] = ((model_inputs[i] - mean_values[i])/std_values[i]) 写死长度 15,意味着 mean_values 和 std_values 必须来自训练时同一套统计,否则预测倾向失真。开 MT5 后先确认这两个数组已从前一节的文件里读满 15 项再调 predict。
跑完 OnnxRun 后只用 model_output[0] 做判断:若它大于 USDJPY 当前收盘价,后面才进多分支。外汇与贵金属杠杆高,模型输出只是概率倾向,实盘仍需自设止损。
class="type">void model_predict(class="type">void) { class=class="str">"cmt">//--- Fetch input values class="type">class="kw">string symbols[class="num">3] = {"UST10Y_U4","JGB10Y_U4","USDJPY"}; vectorf model_inputs = {iOpen(symbols[class="num">0],PERIOD_CURRENT,class="num">0),iHigh(symbols[class="num">0],PERIOD_CURRENT,class="num">0),iLow(symbols[class="num">0],PERIOD_CURRENT,class="num">0),iClose(symbols[class="num">0],PERIOD_CURRENT,class="num">0),iTickVolume(symbols[class="num">0],PERIOD_CURRENT,class="num">0), iOpen(symbols[class="num">1],PERIOD_CURRENT,class="num">0),iHigh(symbols[class="num">1],PERIOD_CURRENT,class="num">0),iLow(symbols[class="num">1],PERIOD_CURRENT,class="num">0),iClose(symbols[class="num">1],PERIOD_CURRENT,class="num">0),iTickVolume(symbols[class="num">1],PERIOD_CURRENT,class="num">0), iOpen(symbols[class="num">2],PERIOD_CURRENT,class="num">0),iHigh(symbols[class="num">2],PERIOD_CURRENT,class="num">0),iLow(symbols[class="num">2],PERIOD_CURRENT,class="num">0),iClose(symbols[class="num">2],PERIOD_CURRENT,class="num">0),iTickVolume(symbols[class="num">2],PERIOD_CURRENT,class="num">0) }; class=class="str">"cmt">//--- Normalize and scale our inputs for(class="type">int i=class="num">0;i < class="num">15;i++) { model_inputs[i] = ((model_inputs[i] - mean_values[i])/std_values[i]); } class=class="str">"cmt">//--- Show the inputs Print("Model inputs: ",model_inputs); class=class="str">"cmt">//--- Fetch a forecast from our model OnnxRun(onnx_model,ONNX_DEFAULT,model_inputs,model_output); class=class="str">"cmt">//--- Give the user feedback Comment("Model forecast: ",model_output[class="num">0]); class=class="str">"cmt">//--- Store the prediction if(model_output[class="num">0] > iClose("USDJPY",PERIOD_CURRENT,class="num">0)) {
把 ONNX 模型接进 EA 的生命周期
EA 的初始化函数 OnInit 里先调 load_onnx_file(),加载失败直接返回 INIT_FAILED,MT5 会拒绝启动策略。随后 load_scaling_factors() 读入归一化参数,再跑一次 model_predict() 做冒烟测试,确认模型能正常推理才返回 INIT_SUCCEEDED。 OnDeinit 负责释放资源:OnnxRelease(onnx_model) 清掉模型占用,ExpertRemove() 把 EA 从图表卸掉。不释放的话,反复加载不同 ONNX 文件可能撑爆内存句柄。 OnTick 是实盘心跳。每次报价先 model_predict() 拿最新输出;无持仓时把 state 归零,若模型输出大于 USDJPY 当前收盘价,就用 Trade.Buy 下 0.3 手,止损止盈各挂 2 点(按 SymbolInfoDouble 的 ASK 加减 2),备注写 USDJPY Bonds AI,state 置 1。外汇与贵金属杠杆高,0.3 手在 USDJPY 上若遇跳空可能触发远超 2 点损失的滑点,参数需按账户风控重设。
class="type">int OnInit() { class=class="str">"cmt">//--- Load the ONNX file if(!load_onnx_file()) { class=class="str">"cmt">//--- We failed to load our onnx model class="kw">return(INIT_FAILED); } class=class="str">"cmt">//--- Load scaling factors load_scaling_factors(); class=class="str">"cmt">//--- Test if our ONNX model works model_predict(); class=class="str">"cmt">//--- Everything worked out class="kw">return(INIT_SUCCEEDED); } class="type">void OnDeinit(const class="type">int reason) { class=class="str">"cmt">//--- Release the resources we used for our onnx model OnnxRelease(onnx_model); class=class="str">"cmt">//--- Release the expert advisor ExpertRemove(); } class="type">void OnTick() { class=class="str">"cmt">//--- Obtain a forecast from our model model_predict(); class=class="str">"cmt">//--- Check if we have any positions if(PositionsTotal() == class="num">0) { class=class="str">"cmt">//--- Reset the state of our system state = class="num">0; class=class="str">"cmt">//--- Check for an entry if(model_output[class="num">0] > iClose("USDJPY",PERIOD_CURRENT,class="num">0)) { Trade.Buy(class="num">0.3,"USDJPY",SymbolInfoDouble("USDJPY",SYMBOL_ASK),SymbolInfoDouble("USDJPY",SYMBOL_ASK)-class="num">2,SymbolInfoDouble("USDJPY",SYMBOL_ASK)+class="num">2,"USDJPY Bonds AI"); state = class="num">1; } } }
「用模型输出压现价触发美日空单」
这段逻辑把 AI 模型的实时输出和 USDJPY 当前收盘价做了硬比较:只要 model_output[0] 低于 iClose("USDJPY",PERIOD_CURRENT,0),就视为偏空信号,直接以 0.3 手卖出。 卖单参数里,开仓价取 SYMBOL_BID,止损放在 ASK+2 点、止盈放在 ASK-2 点,注释写的品种是 USDJPY Bonds AI,实际下单品种仍是 USDJPY。外汇与贵金属杠杆高,这类 2 点窄止损在美日这种平均日波幅常超 50 点的品种上,可能被瞬时噪音扫掉,概率不低。 反转检查另起一段:当 state 与 prediction 不一致,系统弹 Alert 并平掉 USDJPY 仓位。state 被设成 2 代表已进入空头状态,下次预测值若翻转就可能触发被动平仓。
if(model_output[class="num">0] < iClose("USDJPY",PERIOD_CURRENT,class="num">0)) { Trade.Sell(class="num">0.3,"USDJPY",SymbolInfoDouble("USDJPY",SYMBOL_BID),SymbolInfoDouble("USDJPY",SYMBOL_ASK)+class="num">2,SymbolInfoDouble("USDJPY",SYMBOL_ASK)-class="num">2,"USDJPY Bonds AI"); state = class="num">2; } } class=class="str">"cmt">//--- Check for reversals if(state != prediction) { Alert("Reversal detected by the AI system!"); Trade.PositionClose("USDJPY"); } } class=class="str">"cmt">//+------------------------------------------------------------------+
if(model_output[class="num">0] < iClose("USDJPY",PERIOD_CURRENT,class="num">0)) { Trade.Sell(class="num">0.3,"USDJPY",SymbolInfoDouble("USDJPY",SYMBOL_BID),SymbolInfoDouble("USDJPY",SYMBOL_ASK)+class="num">2,SymbolInfoDouble("USDJPY",SYMBOL_ASK)-class="num">2,"USDJPY Bonds AI"); state = class="num">2; } } class=class="str">"cmt">//--- Check for reversals if(state != prediction) { Alert("Reversal detected by the AI system!"); Trade.PositionClose("USDJPY"); } } class=class="str">"cmt">//+------------------------------------------------------------------+
◍ 记住这一条就够了
把神经网络塞进经典策略未必划算。前面那套 USDJPY_M1_FLOAT.onnx 权重文件只有 0.33 KB,推理快但准确率上限受限于特征质量,作者也承认更简单的市价模型可能就够用。 外汇与贵金属属高风险品类,模型准确率波动会直接放大实盘回撤。若你真要上 AI 策略,先拿 MT5 跑通 USDJPY_Bonds.mq5 看样本外表现,再决定要不要堆特征。 特征没打磨清楚之前,普通报价驱动的轻量策略倾向更稳。少写复杂网络,多调一个参数,可能比训模型更值钱。