在 MQL5 中构建自优化EA(第六部分):自适应交易规则(二)·综合运用
(3/3)·固定周期和70/30老规则早不够用,这篇把自动挑周期、量化水平值的闭环一次讲透
把多周期RSI差值落盘成CSV
做外汇或贵金属量化复盘时,单周期RSI往往不够看。这段代码把周期 5 到 70(步长 5)共 14 条 RSI 曲线,以及它们相对 HORIZON 根 K 线前的差分结果,一次性写进 CSV 文件,方便后续用小布盯盘做横向扫描。 先给每个 RSI 数组灌入指标值:SetIndicatorValues 存原始读数,SetDifferencedIndicatorValues 按 HORIZON 偏移算差分,两者都带 true 强制刷新。循环里从 size 倒序到 1,第一行写表头,之后每行写时间、真实收盘价,以及开高低收相对 HORIZON 前的价差。 表头里能看到 RSI 5 到 RSI 70 与对应的 Diff RSI 系列,说明数据维度覆盖短中长三类动量。贵金属和外汇杠杆高,差分RSI只是概率参考,实盘前务必在MT5策略测试器跑一遍样本外数据。
class=class="str">"cmt">//--- Set the RSI buffers my_rsi_array[i].SetIndicatorValues(fetch,true); my_rsi_array[i].SetDifferencedIndicatorValues(fetch,HORIZON,true); } class=class="str">"cmt">//---Write to file class="type">int file_handle=FileOpen(file_name,FILE_WRITE|FILE_ANSI|FILE_CSV,","); for(class="type">int i=size;i>=class="num">1;i--) { if(i == size) { FileWrite(file_handle,"Time","True Close","Open","High","Low","Close","RSI class="num">5","RSI class="num">10","RSI class="num">15","RSI class="num">20","RSI class="num">25","RSI class="num">30","RSI class="num">35","RSI class="num">40","RSI class="num">45","RSI class="num">50","RSI class="num">55","RSI class="num">60","RSI class="num">65","RSI class="num">70","Diff RSI class="num">5","Diff RSI class="num">10","Diff RSI class="num">15","Diff RSI class="num">20","Diff RSI class="num">25","Diff RSI class="num">30","Diff RSI class="num">35","Diff RSI class="num">40","Diff RSI class="num">45","Diff RSI class="num">50","Diff RSI class="num">55","Diff RSI class="num">60","Diff RSI class="num">65","Diff RSI class="num">70"); } else { FileWrite(file_handle, iTime(_Symbol,PERIOD_CURRENT,i), iClose(_Symbol,PERIOD_CURRENT,i), iOpen(_Symbol,PERIOD_CURRENT,i) - iOpen(Symbol(),PERIOD_CURRENT,i + HORIZON), iHigh(_Symbol,PERIOD_CURRENT,i) - iHigh(Symbol(),PERIOD_CURRENT,i + HORIZON), iLow(_Symbol,PERIOD_CURRENT,i) - iLow(Symbol(),PERIOD_CURRENT,i + HORIZON), iClose(_Symbol,PERIOD_CURRENT,i) - iClose(Symbol(),PERIOD_CURRENT,i + HORIZON), my_rsi_array[class="num">0].GetReadingAt(i), my_rsi_array[class="num">1].GetReadingAt(i), my_rsi_array[class="num">2].GetReadingAt(i), my_rsi_array[class="num">3].GetReadingAt(i), my_rsi_array[class="num">4].GetReadingAt(i), my_rsi_array[class="num">5].GetReadingAt(i), my_rsi_array[class="num">6].GetReadingAt(i), my_rsi_array[class="num">7].GetReadingAt(i),
◍ 把 14 条 RSI 序列落盘后再释放
这段收尾逻辑紧接前面的循环:先把索引 8 到 13 的原始 RSI 读数、以及 0 到 13 的差分读数依次写入同一行,随后关闭文件句柄。 文件写出用的是 FileClose(file_handle),句柄一旦关闭,本次 CSV 便定型,后面再想追加得重新打开。 RSI 对象指针放在一个 0 到 13 的 for 循环里统一 delete,共 14 个实例,漏删任何一个都会在 EA 反复加载时堆出内存碎片。 开 MT5 把这段接在你自己的 HORIZON 宏后面跑一遍,重点看文件行数是否等于数据条数、以及日志里有没有 delete 报错——外汇与贵金属波动剧烈,这类资源泄漏在长周期回测里可能拖垮终端。
my_rsi_array[class="num">8].GetReadingAt(i), my_rsi_array[class="num">9].GetReadingAt(i), my_rsi_array[class="num">10].GetReadingAt(i), my_rsi_array[class="num">11].GetReadingAt(i), my_rsi_array[class="num">12].GetReadingAt(i), my_rsi_array[class="num">13].GetReadingAt(i), my_rsi_array[class="num">0].GetDifferencedReadingAt(i), my_rsi_array[class="num">1].GetDifferencedReadingAt(i), my_rsi_array[class="num">2].GetDifferencedReadingAt(i), my_rsi_array[class="num">3].GetDifferencedReadingAt(i), my_rsi_array[class="num">4].GetDifferencedReadingAt(i), my_rsi_array[class="num">5].GetDifferencedReadingAt(i), my_rsi_array[class="num">6].GetDifferencedReadingAt(i), my_rsi_array[class="num">7].GetDifferencedReadingAt(i), my_rsi_array[class="num">8].GetDifferencedReadingAt(i), my_rsi_array[class="num">9].GetDifferencedReadingAt(i), my_rsi_array[class="num">10].GetDifferencedReadingAt(i), my_rsi_array[class="num">11].GetDifferencedReadingAt(i), my_rsi_array[class="num">12].GetDifferencedReadingAt(i), my_rsi_array[class="num">13].GetDifferencedReadingAt(i) ); } } class=class="str">"cmt">//--- Close the file FileClose(file_handle); class=class="str">"cmt">//--- Delete our RSI object pointers for(class="type">int i = class="num">0; i <= class="num">13; i++) { class="kw">delete my_rsi_array[i]; } } class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#undef HORIZON
「用岭回归给 14 个 RSI 周期称重」
把 MT5 导出的 EURUSD 日线丢进 Python 后,先按 10 天前收盘价打上 Bull 标记,再算出 10 日真实回报做目标列。回测期之外的数据必须切掉——代码里用 iloc 把最后约 3 年减去 95 天的窗口完全剔除,避免用未来信息骗自己。 手跑 14 次回测挑 RSI 周期太笨。直接把 14 个周期的 RSI 差值全喂给 Ridge 模型,用网格搜 Alpha 和 Tolerance,让系数自己收缩到接近 0。在 sparse_cg 求解器下,Alpha 设为 0 那组误差最低,仅 0.053509;Alpha 调到第 9 档时误差升到 0.085171。 表现最好的模型把最大绝对值系数给了 55 周期 RSI 的 10 周期变化。进一步以 10 为步长搜临界水平:以 0 为界误差 0.0269,用 30/70 反而涨到 0.0313,精细搜后真实最优落在 9 附近——但死抠历史最优是过拟合,取最优值两侧标准差当置信带更稳。 置信区间算出来是 [7.18, 10.82]。Ridge 解释向量 [1.97e-04, -1.64e-04, -7.55e-05] 表明:55 周期 RSI 变化超 9 时模型学成正系数倾向做多,其余条件倾向做空。外汇高杠杆下这仅是概率倾向,开 MT5 接 EA 前先拿样本外年份验一遍。
class="kw">import pandas as pd class="kw">import numpy as np class="kw">import seaborn as sns class="kw">import matplotlib.pyplot as plt class="kw">import plotly class="macro">#Let&class="macro">#x27;s read in our market data data = pd.read_csv("EURUSD RSI Algorithmic Input Selection.csv") data[&class="macro">#x27;Bull&class="macro">#x27;] = np.NaN data.loc[data[&class="macro">#x27;True Close&class="macro">#x27;] > data[&class="macro">#x27;True Close&class="macro">#x27;].shift(class="num">10),&class="macro">#x27;Bull&class="macro">#x27;] = class="num">1 data.loc[data[&class="macro">#x27;True Close&class="macro">#x27;] < data[&class="macro">#x27;True Close&class="macro">#x27;].shift(class="num">10),&class="macro">#x27;Bull&class="macro">#x27;] = class="num">0 data.dropna(inplace=True) data.reset_index(inplace=True,drop=True) class="macro">#Estimate the market returns class="macro">#Define our forecast horizon HORIZON = class="num">10 data[&class="macro">#x27;Target&class="macro">#x27;] = class="num">0 data[&class="macro">#x27;Return&class="macro">#x27;] = data[&class="macro">#x27;True Close&class="macro">#x27;].shift(-HORIZON) - data[&class="macro">#x27;True Close&class="macro">#x27;] data.loc[data[&class="macro">#x27;Return&class="macro">#x27;] > class="num">0,&class="macro">#x27;Target&class="macro">#x27;] = class="num">1 class="macro">#Drop missing values data.dropna(inplace=True) class="macro">#No cheating boys. _ = data.iloc[((-class="num">365 * class="num">3) + class="num">95):,:] data = data.iloc[:((-class="num">365 * class="num">3) + class="num">95),:] data plt.title(&class="macro">#x27;Distribution of EURUSD class="num">10 Day Returns&class="macro">#x27;) plt.grid() sns.histplot(data[&class="macro">#x27;Return&class="macro">#x27;],class="type">color=&class="macro">#x27;black&class="macro">#x27;) plt.title(&class="macro">#x27;Comapring The Distribution of RSI Changes Across Different RSI Periods&class="macro">#x27;) sns.histplot(data[&class="macro">#x27;RSI class="num">5&class="macro">#x27;],class="type">color=&class="macro">#x27;black&class="macro">#x27;) sns.histplot(data[&class="macro">#x27;RSI class="num">70&class="macro">#x27;],class="type">color=&class="macro">#x27;white&class="macro">#x27;) plt.xlabel(&class="macro">#x27;RSI Level&class="macro">#x27;) plt.legend([&class="macro">#x27;RSI class="num">5&class="macro">#x27;,&class="macro">#x27;RSI class="num">70&class="macro">#x27;]) plt.axvline(class="num">30,class="type">color=&class="macro">#x27;red&class="macro">#x27;,linestyle=&class="macro">#x27;--&class="macro">#x27;) plt.axvline(class="num">70,class="type">color=&class="macro">#x27;red&class="macro">#x27;,linestyle=&class="macro">#x27;--&class="macro">#x27;) plt.grid()
用岭回归网格搜 EURUSD 的 RSI 差分预测误差
把 5 到 70 周期 RSI 的 10 日差分作为特征,去回归 EURUSD 的 10 日收益率,这一步先用散点图看 50 周期与 5 周期 RSI 差分和后续回报的分布,红色虚线标在 ±10 处,肉眼能看出极端 RSI 变动后价格倾向有反向或延续动作,但相关性并不强。 真正定量的是网格搜索:设 ALPHA_LEVELS 与 TOL_LEVELS 各为 10,对 Ridge 回归的 alpha 与 tol 取 10^(-j) 与 10^(-i) 组合,用时间序列交叉验证算平均绝对误差。特征矩阵覆盖 'Diff RSI 5' 到 'Diff RSI 70' 共 14 列,cv 用 TimeSeriesSplit,误差写进 10×10 的 DataFrame。 用 plotly 画 contour,z 轴是误差矩阵,蓝红配色下颜色越红代表误差越低。实盘前可在 MT5 导出自家 EURUSD 的 RSI 差分序列,跑同样网格看你的样本里最低误差落在 alpha、tol 的哪一格。 最后用 alpha=0、tol=0 的 Ridge 拟合全量数据,后续可提取 coef_ 看哪个 RSI 周期差分权重最大——外汇与贵金属杠杆高,这类线性模型只揭示统计倾向,不能直接当作入场信号。
plt.title(&class="macro">#x27;Scatter Plot of class="num">10 Day Change in class="num">50 Period RSI & EURUSD class="num">10 Day Return&class="macro">#x27;) sns.scatterplot(data,y=&class="macro">#x27;Diff RSI class="num">60&class="macro">#x27;,x=&class="macro">#x27;Diff RSI class="num">60&class="macro">#x27;,hue=&class="macro">#x27;Target&class="macro">#x27;) plt.xlabel(&class="macro">#x27;class="num">50 Period RSI&class="macro">#x27;) plt.ylabel(&class="macro">#x27;class="num">50 Period RSI&class="macro">#x27;) plt.grid() plt.axvline(-class="num">10,class="type">color=&class="macro">#x27;red&class="macro">#x27;,linestyle=&class="macro">#x27;--&class="macro">#x27;) plt.axvline(class="num">10,class="type">color=&class="macro">#x27;red&class="macro">#x27;,linestyle=&class="macro">#x27;--&class="macro">#x27;) plt.title(&class="macro">#x27;Scatter Plot of class="num">10 Day Change in class="num">5 Period RSI & EURUSD class="num">10 Day Return&class="macro">#x27;) sns.scatterplot(data,y=&class="macro">#x27;Diff RSI class="num">60&class="macro">#x27;,x=&class="macro">#x27;Diff RSI class="num">5&class="macro">#x27;,hue=&class="macro">#x27;Target&class="macro">#x27;) plt.xlabel(&class="macro">#x27;class="num">5 Period RSI&class="macro">#x27;) plt.ylabel(&class="macro">#x27;class="num">5 Period RSI&class="macro">#x27;) plt.grid() class="macro">#Set the max levels we wish to check ALPHA_LEVELS = class="num">10 TOL_LEVELS = class="num">10 class="macro">#DataFrame labels r_c = &class="macro">#x27;TOL_LEVEL_&class="macro">#x27; r_r = &class="macro">#x27;ALHPA_LEVEL_&class="macro">#x27; results_columns = [] results_rows = [] for c in range(TOL_LEVELS): n_c = r_c + str(c) n_r = r_r + str(c) results_columns.append(n_c) results_rows.append(n_r) class="macro">#Create a DataFrame to store our results results = pd.DataFrame(columns=results_columns,index=results_rows) class="macro">#Cross validate our model for i in range(TOL_LEVELS): tol = class="num">10 ** (-i) error = [] for j in range(ALPHA_LEVELS): class="macro">#Set alpha alpha = class="num">10 ** (-j) class="macro">#Its good practice to generally check the class="num">0 case if(i == class="num">0 & j == class="num">0): model = Ridge(alpha=j,tol=i,solver=&class="macro">#x27;sparse_cg&class="macro">#x27;) class="macro">#Otherwise use a class="type">float model = Ridge(alpha=alpha,tol=tol,solver=&class="macro">#x27;sparse_cg&class="macro">#x27;) class="macro">#Store the error levels error.append(np.mean(np.abs(cross_val_score(model,data.loc[:,[ &class="macro">#x27;Diff RSI class="num">5&class="macro">#x27;, &class="macro">#x27;Diff RSI class="num">10&class="macro">#x27;, &class="macro">#x27;Diff RSI class="num">15&class="macro">#x27;, &class="macro">#x27;Diff RSI class="num">20&class="macro">#x27;, &class="macro">#x27;Diff RSI class="num">25&class="macro">#x27;, &class="macro">#x27;Diff RSI class="num">30&class="macro">#x27;, &class="macro">#x27;Diff RSI class="num">35&class="macro">#x27;, &class="macro">#x27;Diff RSI class="num">40&class="macro">#x27;, &class="macro">#x27;Diff RSI class="num">45&class="macro">#x27;, &class="macro">#x27;Diff RSI class="num">50&class="macro">#x27;, &class="macro">#x27;Diff RSI class="num">55&class="macro">#x27;, &class="macro">#x27;Diff RSI class="num">60&class="macro">#x27;, &class="macro">#x27;Diff RSI class="num">65&class="macro">#x27;, &class="macro">#x27;Diff RSI class="num">70&class="macro">#x27;,]],data[&class="macro">#x27;Return&class="macro">#x27;],cv=tscv)))) class="macro">#Record the error levels results.iloc[:,i] = error results class="kw">import plotly.graph_objects as go fig = go.Figure(data = go.Contour( z=results, colorscale=&class="macro">#x27;bluered&class="macro">#x27; )) fig.update_layout( width = class="num">600, height = class="num">400, title=&class="macro">#x27;Contour Plot Of Our Error Forecasting EURUSD Using Grid Search &class="macro">#x27; ) fig.show() class="macro">#Let&class="macro">#x27;s visualize the importance of each column model = Ridge(alpha=class="num">0,tol=class="num">0,solver=&class="macro">#x27;sparse_cg&class="macro">#x27;) model.fit(data.loc[:,[ &class="macro">#x27;Diff RSI class="num">5&class="macro">#x27;, &class="macro">#x27;Diff RSI class="num">10&class="macro">#x27;, &class="macro">#x27;Diff RSI class="num">15&class="macro">#x27;, &class="macro">#x27;Diff RSI class="num">20&class="macro">#x27;, &class="macro">#x27;Diff RSI class="num">25&class="macro">#x27;, &class="macro">#x27;Diff RSI class="num">30&class="macro">#x27;, &class="macro">#x27;Diff RSI class="num">35&class="macro">#x27;, &class="macro">#x27;Diff RSI class="num">40&class="macro">#x27;, &class="macro">#x27;Diff RSI class="num">45&class="macro">#x27;,
◍ 用交叉验证给 RSI 阈值找最优解
训练好的 Ridge 模型在特征重要性图上明显偏向 25 周期 RSI,但落到 EURUSD 实盘输入时,55 周期差分 RSI(Diff RSI 55)才是阈值扫描的主战场。 下面这段 Python 把 Diff RSI 55 的高低穿越规则写成目标函数,用时间序列交叉验证的均值绝对误差当评分:阈值越合理,error 越低。 def objective(x): data = pd.read_csv("EURUSD RSI Algorithmic Input Selection.csv") data['0'] = 0 data['1'] = 0 data['2'] = 0 HORIZON = 10 data['Return'] = data['True Close'].shift(-HORIZON) - data['True Close'] data.dropna(subset=['Return'],inplace=True) data.iloc[data['Diff RSI 55'] > x[0],12] = 1 data.iloc[data['Diff RSI 55'] < x[1],13] = 1 data.iloc[(data['Diff RSI 55'] < x[0]) & (data['RSI 55'] > x[1]),14] = 1 model = Ridge(alpha=0,tol=0,solver='sparse_cg') error = np.mean(np.abs(cross_val_score(model,data.iloc[:,12:15],data['Return'],cv=tscv))) return(error) 先跑 objective([0,0]) 和 objective([70,-30]) 会发现,传统「超买 70 / 超卖 30」的硬切法误差偏高,属于低效规则。 把阈值按 10 的倍数从 0 扫到 9,记录每次 error 并标出最低点;再细化到 20 档逐整数扫描,最低误差对应的索引就是最优阈值中心。以 0.5 倍 Diff RSI 55 标准差画红虚线,圈出稳健区间——外汇与贵金属波动剧烈,这套扫描仅降低模型误差,不预示方向,实盘仍属高风险。
def objective(x): data = pd.read_csv("EURUSD RSI Algorithmic Input Selection.csv") data[&class="macro">#x27;class="num">0&class="macro">#x27;] = class="num">0 data[&class="macro">#x27;class="num">1&class="macro">#x27;] = class="num">0 data[&class="macro">#x27;class="num">2&class="macro">#x27;] = class="num">0 HORIZON = class="num">10 data[&class="macro">#x27;Return&class="macro">#x27;] = data[&class="macro">#x27;True Close&class="macro">#x27;].shift(-HORIZON) - data[&class="macro">#x27;True Close&class="macro">#x27;] data.dropna(subset=[&class="macro">#x27;Return&class="macro">#x27;],inplace=True) data.iloc[data[&class="macro">#x27;Diff RSI class="num">55&class="macro">#x27;] > x[class="num">0],class="num">12] = class="num">1 data.iloc[data[&class="macro">#x27;Diff RSI class="num">55&class="macro">#x27;] < x[class="num">1],class="num">13] = class="num">1 data.iloc[(data[&class="macro">#x27;Diff RSI class="num">55&class="macro">#x27;] < x[class="num">0]) & (data[&class="macro">#x27;RSI class="num">55&class="macro">#x27;] > x[class="num">1]),class="num">14] = class="num">1 model = Ridge(alpha=class="num">0,tol=class="num">0,solver=&class="macro">#x27;sparse_cg&class="macro">#x27;) error = np.mean(np.abs(cross_val_score(model,data.iloc[:,class="num">12:class="num">15],data[&class="macro">#x27;Return&class="macro">#x27;],cv=tscv))) class="kw">return(error)
「用 RSI 差值分布找最优阈值」
这段 Python 片段把 EURUSD 的 'Diff RSI 55' 序列当成分布来处理,用最小误差点加减一个标准差系数来标定上下边界。plt.axvline 画出的红线位置是 results.index(min(results)) 减去 coef 乘上 Diff RSI 55 的标准差,直观看就是分布里最优点向左偏移的那条临界线。 def explanation(x) 里先读入 'EURUSD RSI Algorithmic Input Selection.csv',把 '0''1''2' 三列置零,再算 True Close 向后平移 10 根(HORIZON=10)的 Return 作为标签,dropna 去掉空值。 接着按 x[0]、x[1] 给三列打 1:Diff RSI 55 大于 x[0] 标 12 列,小于 x[1] 标 13 列,同时 Diff RSI 55 小于 x[0] 且 RSI 55 大于 x[1] 标 14 列。Ridge(alpha=0) 拿这三列去拟合 Return,返回系数。 opt=9 时 print(explanation([9,-9])) 跑出来的就是这组阈值下的模型系数,你可以直接换 opt 值看系数怎么变,外汇EURUSD这类品种波动大、杠杆高,回测结论只代表历史样本,实盘可能失效。
plt.axvline(-(results.index(min(results)) + (coef * np.std(data[&class="macro">#x27;Diff RSI class="num">55&class="macro">#x27;]))),class="type">color=&class="macro">#x27;red&class="macro">#x27;) plt.title("Visualizing our Optimal Point in The Distribution") results.index(min(results)) + ( coef * np.std(data[&class="macro">#x27;Diff RSI class="num">55&class="macro">#x27;])) results.index(min(results)) - (coef * np.std(data[&class="macro">#x27;Diff RSI class="num">55&class="macro">#x27;])) def explanation(x): data = pd.read_csv("EURUSD RSI Algorithmic Input Selection.csv") data[&class="macro">#x27;class="num">0&class="macro">#x27;] = class="num">0 data[&class="macro">#x27;class="num">1&class="macro">#x27;] = class="num">0 data[&class="macro">#x27;class="num">2&class="macro">#x27;] = class="num">0 HORIZON = class="num">10 data[&class="macro">#x27;Return&class="macro">#x27;] = data[&class="macro">#x27;True Close&class="macro">#x27;].shift(-HORIZON) - data[&class="macro">#x27;True Close&class="macro">#x27;] data.dropna(subset=[&class="macro">#x27;Return&class="macro">#x27;],inplace=True) data.iloc[data[&class="macro">#x27;Diff RSI class="num">55&class="macro">#x27;] > x[class="num">0],class="num">12] = class="num">1 data.iloc[data[&class="macro">#x27;Diff RSI class="num">55&class="macro">#x27;] < x[class="num">1],class="num">13] = class="num">1 data.iloc[(data[&class="macro">#x27;Diff RSI class="num">55&class="macro">#x27;] < x[class="num">0]) & (data[&class="macro">#x27;RSI class="num">55&class="macro">#x27;] > x[class="num">1]),class="num">14] = class="num">1 class="macro">#Calculate or RMSE When using those levels model = Ridge(alpha=class="num">0,tol=class="num">0,solver=&class="macro">#x27;sparse_cg&class="macro">#x27;) model.fit(data.iloc[:,class="num">12:class="num">15],data[&class="macro">#x27;Return&class="macro">#x27;]) class="kw">return(model.coef_.copy()) opt = class="num">9 print(explanation([opt,-opt]))
把RSI模型落进EA骨架
顺着前几节对 EURUSD 日线 RSI 关系的梳理,下一步是把这套认知写成可跑的 MT5 程序。先钉死系统常量:RSI 周期 55、时间帧 PERIOD_D1、价格源 PRICE_CLOSE、缓冲 20 根,交易量直接取品种最小手数,避免手动填仓引发风控告警。 全局层只留必要句柄与变量——CTrade 实例、rsi_55 对象、last_value、count 以及两条均线数组 ma_o/ma_c,再加一个 trade_sl 专门扛动态止损。事件入口 OnInit 只做一件事:调 setup() 把指标句柄和计数器归零,后面更新函数再周期性刷系统变量并判断开仓或挪止损。 回测用 2022-01-01 到 2025-03 的日线,删掉与训练重叠的部分以近似实盘。测试模式选「基于真实 Tick 的每个 Tick」,夏普 0.92、预期回报 2.49、净利 151.87 美元、胜率 57.38%,但 61 笔里仅 4 笔多单——重度偏空是这版 EA 的明显畸变,后续要回图表查偏差源。外汇与贵金属杠杆高,回测甜美不代表实盘能复现,须以小资金验证。 下面这段是初始化骨架,逐行看:前几行宏定义常量;#include 拉入自写 RSI 库与系统交易类;全局区声明交易对象与指标实例;OnInit 仅调用 setup 并返回成功。复制进 MQ5 编译器即可接着补 update / trade rules。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Algorithmic Input Selection.mq5 | class=class="str">"cmt">//| Gamuchirai Ndawana | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Gamuchirai Ndawana" 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">//| System constants | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#define RSI_PERIOD class="num">55 class="macro">#define RSI_TIME_FRAME PERIOD_D1 class="macro">#define SYSTEM_TIME_FRAME PERIOD_D1 class="macro">#define RSI_PRICE PRICE_CLOSE class="macro">#define RSI_BUFFER_SIZE class="num">20 class="macro">#define TRADING_VOLUME SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN) class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Load our RSI library | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include <VolatilityDoctor\Indicators\RSI.mqh> class="macro">#include <Trade\Trade.mqh> class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Global variables | class=class="str">"cmt">//+------------------------------------------------------------------+ CTrade Trade; RSI rsi_55(Symbol(),RSI_TIME_FRAME,RSI_PERIOD,RSI_PRICE); class="type">class="kw">double last_value; class="type">int count; class="type">int ma_o_handler,ma_c_handler; class="type">class="kw">double ma_o[],ma_c[]; class="type">class="kw">double trade_sl; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- setup(); class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+
◍ 从 OnDeinit 到仓位管理的事件链
EA 卸载时先把两条均线指标句柄交还给系统,避免 MT5 终端残留句柄导致下次加载报 'indicator already loaded' 类错误。OnDeinit 里只做 IndicatorRelease(ma_c_handler) 与 IndicatorRelease(ma_o_handler),不在这里平仓,平仓逻辑留给策略自身控制。 行情驱动全压在 OnTick 调用 update(),update() 用静态变量 time_stamp 对比 iTime(Symbol(),SYSTEM_TIME_FRAME,0) 判断是否进入新 K 线。只有时间戳变化才重取 CopyBuffer 的 1 根均线值,并把无持仓时的 RSI 初值通过 rsi_55.GetReadingAt(RSI_BUFFER_SIZE-1) 写入 last_value,count 置 1 后不再重复初始化。 持仓后就走 manage_setup():先抓 bid/ask,再用 PositionSelect 读出现仓的 SL/TP。新止损按 '原 TP 大于原 SL 则为买仓,新 SL=bid-trade_sl;否则为卖仓,新 SL=ask+trade_sl' 推算,新 TP 对称反向。外汇与贵金属杠杆高,SL/TP 重算若间距过小可能被 broker 拒单,建议先在策略测试器用 2023 年 XAUUSD 的 M15 跑一遍看成交率。
class=class="str">"cmt">//| Expert deinitialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(const class="type">int reason) { class=class="str">"cmt">//--- IndicatorRelease(ma_c_handler); IndicatorRelease(ma_o_handler); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert tick function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class=class="str">"cmt">//--- update(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Update our system variables | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void update(class="type">void) { class="kw">static class="type">class="kw">datetime time_stamp; class="type">class="kw">datetime current_time = iTime(Symbol(),SYSTEM_TIME_FRAME,class="num">0); if(time_stamp != current_time) { time_stamp = current_time; CopyBuffer(ma_c_handler,class="num">0,class="num">0,class="num">1,ma_c); CopyBuffer(ma_o_handler,class="num">0,class="num">0,class="num">1,ma_o); if((count == class="num">0) && (PositionsTotal() == class="num">0)) { rsi_55.SetIndicatorValues(RSI_BUFFER_SIZE,true); last_value = rsi_55.GetReadingAt(RSI_BUFFER_SIZE - class="num">1); count = class="num">1; } if(PositionsTotal() == class="num">0) check_signal(); if(PositionsTotal() > class="num">0) manage_setup(); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Manage our open trades | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void manage_setup(class="type">void) { class="type">class="kw">double bid = SymbolInfoDouble(Symbol(),SYMBOL_BID); class="type">class="kw">double ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK); if(PositionSelect(Symbol())) { class="type">class="kw">double current_sl = PositionGetDouble(POSITION_SL); class="type">class="kw">double current_tp = PositionGetDouble(POSITION_TP); class="type">class="kw">double new_sl = (current_tp > current_sl) ? (bid-trade_sl) : (ask+trade_sl); class="type">class="kw">double new_tp = (current_tp < current_sl) ? (bid+trade_sl) : (ask-trade_sl); class=class="str">"cmt">//--- Buy setup if((current_tp > current_sl) && (new_sl < current_sl))
「信号判定与全局变量的落地写法」
这段逻辑把 RSI 读数的跳变和两条 2 周期 EMA(收盘、开盘)的上下关系绑在一起,作为开仓触发条件。setup 里把 trade_sl 写死成 1.5e-2(即 150 点),外汇与贵金属杠杆高,这种固定止损距离在黄金上可能直接扫掉正常波动,实盘前建议在 MT5 里按品种重算。
check_signal 中 cp_lb=7.17、cp_ub=10.82 是 RSI(55) 前后读差的下上限阈值。注意原文第二个 if 的运算符 < ((last_value - current_reading) < cp_ub) 是语法错误,应改为 &&,否则编译会报类型不匹配,复制代码后必须先修这一行。
开仓只在 PositionsTotal()==0 时执行,避免加仓堆叠;卖单挂于 bid,止损 ask+trade_sl、止盈 ask-trade_sl,买单反之。想验证信号质量,把 Comment 输出的 Last Reading 和 Difference 调到图表左上角盯几天,看差值落进阈值带的频率再决定跟不跟。
Trade.PositionModify(Symbol(),new_sl,new_tp); class=class="str">"cmt">//--- Sell setup if((current_tp < current_sl) && (new_sl > current_sl)) Trade.PositionModify(Symbol(),new_sl,new_tp); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Setup our global variables | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void setup(class="type">void) { ma_c_handler = iMA(Symbol(),SYSTEM_TIME_FRAME,class="num">2,class="num">0,MODE_EMA,PRICE_CLOSE); ma_o_handler = iMA(Symbol(),SYSTEM_TIME_FRAME,class="num">2,class="num">0,MODE_EMA,PRICE_OPEN); count = class="num">0; last_value = class="num">0; trade_sl = class="num">1.5e-2; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Check if we have a trading setup | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void check_signal(class="type">void) { class="type">class="kw">double current_reading = rsi_55.GetCurrentReading(); Comment("Last Reading: ",last_value,"\nDifference in Reading: ",(last_value - current_reading)); class="type">class="kw">double bid = SymbolInfoDouble(Symbol(),SYMBOL_BID); class="type">class="kw">double ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK); class="type">class="kw">double cp_lb = class="num">7.17; class="type">class="kw">double cp_ub = class="num">10.82; if((((last_value - current_reading) <= -(cp_lb)) && ((last_value - current_reading) > (cp_ub)))|| ((((last_value - current_reading) > -(cp_lb))) && ((last_value - current_reading) < (cp_lb)))) { if(ma_o[class="num">0] > ma_c[class="num">0]) { if(PositionsTotal() == class="num">0) { Trade.Sell(TRADING_VOLUME,Symbol(),bid,(ask+trade_sl),(ask-trade_sl)); count = class="num">0; } } } if(((last_value - current_reading) >= (cp_lb)) < ((last_value - current_reading) < cp_ub)) { if(ma_c[class="num">0] < ma_o[class="num">0]) { if(PositionsTotal() == class="num">0) { Trade.Buy(TRADING_VOLUME,Symbol(),ask,(bid-trade_sl),(bid+trade_sl)); count = class="num">0; } } } } class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#undef RSI_BUFFER_SIZE class="macro">#undef RSI_PERIOD class="macro">#undef RSI_PRICE class="macro">#undef RSI_TIME_FRAME class="macro">#undef SYSTEM_TIME_FRAME class="macro">#undef TRADING_VOLUME class=class="str">"cmt">//+------------------------------------------------------------------+
用更灵活的模型给EA补多头信号
图 18 的 EURUSD 月线截图里,两条红线框住 2009 年初到 2021 年末,绿线标出训练数据起点。2008 年开启的漫长熊市直接灌进 Ridge 模型,让它天然偏向空头仓位——这不是 bug,是数据分布使然。 与其拼命拉长历史样本去‘纠正’偏见,不如换一个更自由的 learner。把 Ridge 换成随机森林回归器,让 EA 去估未来 10 天汇率走高的概率,模型弹性上来后,多头触发点自然变多。 具体落地:随机森林输出看涨概率,越过 0.5 就开多;低于这条线,退回原 Ridge 策略执行。外汇与贵金属属高风险品种,概率阈值只是倾向性参考,实盘前请在 MT5 用 2009–2021 区间外推样本跑一遍确认信号分布。
◍ 用随机森林给欧元兑美元概率建模
把价格行为转成可训练样本后,下一步是在 Python 里用随机森林回归器拟合「Diff RSI 55」与未来价格动作之间的映射关系。这里自变量只取日线级别的 RSI(55) 差值,因变量是预先打标的 Target,模型不复杂,但足以输出未来 10 天出现看涨价格行为的概率倾向。 代码里先载入 sklearn 的 RandomForestRegressor 与 accuracy_score,用 data[['Diff RSI 55']] 作 X、data['Target'] 作 y,空参初始化后直接 fit。外汇与贵金属属高风险品种,这类概率仅作辅助参考,不代表方向必然成立。 模型训完要落地到 MT5,得转成 ONNX。用 skl2onnx 把模型转成原型,输入形状写死为 [1,1] 的浮点张量,target_opset=12,存盘文件名带品种周期标记:「EURUSD Diff RSI 55 D1 1 1.onnx」。 导出的 ONNX 建议用 netron 起本地服务看一眼图结构,确认输入输出节点形状无误再喂给 EA。图 19、图 20 展示的随机森林原型与预期属性一致,EA 端就能按这个接口去预测 10 日内的看涨概率。
from sklearn.metrics class="kw">import accuracy_score from sklearn.ensemble class="kw">import RandomForestRegressor class="macro">#Independent variable X = data[[&class="macro">#x27;Diff RSI class="num">55&class="macro">#x27;]] class="macro">#Dependent variable y = data[&class="macro">#x27;Target&class="macro">#x27;] model = RandomForestRegressor() model.fit(X,y) class="kw">import onnx from skl2onnx class="kw">import convert_sklearn from skl2onnx.common.data_types class="kw">import FloatTensorType inital_params = [("float_input",FloatTensorType([class="num">1,class="num">1]))] onnx_proto = convert_sklearn(model=model,initial_types=inital_params,target_opset=class="num">12) onnx.save(onnx_proto,"EURUSD Diff RSI class="num">55 D1 class="num">1 class="num">1.onnx") class="kw">import netron netron.start("EURUSD Diff RSI class="num">55 D1 class="num">1 class="num">1.onnx")
「把概率模型塞进EA后的真实账本」
把 EURUSD 的差分 RSI 概率模型导进 EA,核心动作就是加一组 ONNX 宏和全局句柄,让策略在多个代码分支里都能直接调模型,不必重复加载。下面这段只留了初始化与信号读取的增量部分,其余未改动的逻辑按原库沿用。 回测对比很说明问题:第二版在同样图 14、15 的条件下跑,资金曲线肉眼看和初版一致,但细分统计露了底。交易笔数从 61 笔升到 85 笔,增幅 39%;多头持仓从 4 笔跳到 42 笔,涨幅 950%。准确率由 57.38% 微降到 56.47%,约掉 1.59%,夏普与预期回报也略降。 额外风险显著抬高,而盈利统计只是小幅走弱,这种交换让策略倾向仍具备正向预期,但外汇品种杠杆高、价差跳变频繁,实盘前务必在 MT5 用历史数据重跑确认。 代码里 OnnxCreateFromBuffer 从 resource 载入二进制,OnnxSetInputShape / OutputShape 把张量锁成 [1,1],信号函数里直接把最新差分读数喂给 onnx_input[0],整个链路没有多余拷贝。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Algorithmic Input Selection.mq5 | class=class="str">"cmt">//| Gamuchirai Ndawana | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Gamuchirai Ndawana" 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">//| System resources | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#resource "\Files\EURUSD Diff RSI class="num">55 D1 class="num">1 class="num">1.onnx" as class="type">uchar onnx_model_buffer[]; class="macro">#define ONNX_INPUTS class="num">1 class="macro">#define ONNX_OUTPUTS class="num">1 class="type">long onnx_model; vectorf onnx_output(class="num">1); vectorf onnx_input(class="num">1); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Setup our global variables | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool setup(class="type">void) { class=class="str">"cmt">//--- Setup our technical indicators ... class=class="str">"cmt">//--- Create our ONNX model onnx_model = OnnxCreateFromBuffer(onnx_model_buffer,ONNX_DATA_TYPE_FLOAT); class=class="str">"cmt">//--- Validate the ONNX model if(onnx_model == INVALID_HANDLE) { class="kw">return(false); } class=class="str">"cmt">//--- Define the I/O signature class="type">ulong onnx_param[] = {class="num">1,class="num">1}; if(!OnnxSetInputShape(onnx_model,class="num">0,onnx_param)) class="kw">return(false); if(!OnnxSetOutputShape(onnx_model,class="num">0,onnx_param)) class="kw">return(false); class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Check if we have a trading setup | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void check_signal(class="type">void) { rsi_55.SetDifferencedIndicatorValues(RSI_BUFFER_SIZE,HORIZON,true); onnx_input[class="num">0] = (class="type">float) rsi_55.GetDifferencedReadingAt(RSI_BUFFER_SIZE - class="num">1);
把 ONNX 模型信号接进下单逻辑
这段 MT5 代码演示了随机森林模型如何直接驱动实盘下单。核心判断是:当 ONNX 输出的多头概率大于 0.5,且开盘均线低于收盘均线时,才触发 Buy 指令,属于典型的「模型信号 + 价格结构」双过滤。
外汇与贵金属杠杆高,这类自动下单逻辑在欧元美元日线周期上可能出现连续假信号,实盘前务必用策略测试器跑历史数据验证。
代码里加载的模型文件写死为 \Files\EURUSD Diff RSI 55 D1 1 1.onnx,说明特征工程基于周期 55 的 RSI 差值,输入维度只有 1 个、输出 1 个概率值。想换品种就得重新训练并替换 resource,不能指望同一模型跨货币对泛化。
常数里 RSI_BUFFER_SIZE 20 与 HORIZON 10 暗示系统用最近 20 根日线的 RSI 差分作为上下文,预测未来 10 根日线方向。开 MT5 把 #resource 路径指向你自己的 onnx 文件,即可让小布这类 AIGC 工具替你跑通这套推断。
class=class="str">"cmt">//--- Our Random forest model if(!OnnxRun(onnx_model,ONNX_DATA_TYPE_FLOAT,onnx_input,onnx_output)) Comment("Failed to obtain a forecast from our model!"); else { if(onnx_output[class="num">0] > class="num">0.5) if(ma_o[class="num">0] < ma_c[class="num">0]) Trade.Buy(TRADING_VOLUME,Symbol(),ask,(bid-trade_sl),(bid+trade_sl)); Print("Model Bullish Probabilty: ",onnx_output); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Algorithmic Input Selection.mq5 | class=class="str">"cmt">//| Gamuchirai Ndawana | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Gamuchirai Ndawana" 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">//| System resources | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#resource "\\Files\\EURUSD Diff RSI class="num">55 D1 class="num">1 class="num">1.onnx" as class="type">uchar onnx_model_buffer[]; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| System constants | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#define RSI_PERIOD class="num">55 class="macro">#define RSI_TIME_FRAME PERIOD_D1 class="macro">#define SYSTEM_TIME_FRAME PERIOD_D1 class="macro">#define RSI_PRICE PRICE_CLOSE class="macro">#define RSI_BUFFER_SIZE class="num">20 class="macro">#define TRADING_VOLUME SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN) class="macro">#define ONNX_INPUTS class="num">1 class="macro">#define ONNX_OUTPUTS class="num">1 class="macro">#define HORIZON class="num">10 class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Load our RSI library | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include <VolatilityDoctor\Indicators\RSI.mqh> class="macro">#include <Trade\Trade.mqh> class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Global variables | class=class="str">"cmt">//+------------------------------------------------------------------+ CTrade Trade;
◍ EA 骨架与每根 K 线的状态刷新
这段 MT5 专家顾问的骨架把 RSI、两条均线句柄和 ONNX 模型句柄全部塞进全局变量,用 OnInit 调 setup() 完成指标绑定,OnDeinit 里释放均线句柄避免内存泄漏。 OnTick 只做一件事:每次报价进来就调 update()。真正有节流意义的是 update() 里的时间戳比对——用 iTime(Symbol(),SYSTEM_TIME_FRAME,0) 取当前周期时间,只有 time_stamp 变化(即新 K 线成型)才重算,避免每跳都跑全套逻辑。 新周期触发后,CopyBuffer 分别把收盘均线和开盘均线最新值拷进 ma_c[]、ma_o[]。首次运行且空仓时(count==0 && PositionsTotal()==0),RSI 对象一次性灌入 RSI_BUFFER_SIZE 长度的值并做差分处理,再把末尾读数存进 last_value、count 置 1。 之后每个新 K 线:空仓就 check_signal() 找入口,有仓就 manage_setup() 管止损仓位。外汇和贵金属波动大、滑点不可控,这套机制只是工程节流,信号本身仍可能连续亏损,实盘前请在策略测试器跑至少 3 个月 tick 数据。
RSI rsi_55(Symbol(),RSI_TIME_FRAME,RSI_PERIOD,RSI_PRICE); class="type">class="kw">double last_value; class="type">int count; class="type">int ma_o_handler,ma_c_handler; class="type">class="kw">double ma_o[],ma_c[]; class="type">class="kw">double trade_sl; class="type">long onnx_model; vectorf onnx_output(class="num">1); vectorf onnx_input(class="num">1); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- setup(); class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert deinitialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(const class="type">int reason) { class=class="str">"cmt">//--- IndicatorRelease(ma_c_handler); IndicatorRelease(ma_o_handler); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert tick function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class=class="str">"cmt">//--- update(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Update our system variables | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void update(class="type">void) { class="kw">static class="type">class="kw">datetime time_stamp; class="type">class="kw">datetime current_time = iTime(Symbol(),SYSTEM_TIME_FRAME,class="num">0); if(time_stamp != current_time) { time_stamp = current_time; CopyBuffer(ma_c_handler,class="num">0,class="num">0,class="num">1,ma_c); CopyBuffer(ma_o_handler,class="num">0,class="num">0,class="num">1,ma_o); if((count == class="num">0) && (PositionsTotal() == class="num">0)) { rsi_55.SetIndicatorValues(RSI_BUFFER_SIZE,true); rsi_55.SetDifferencedIndicatorValues(RSI_BUFFER_SIZE,HORIZON,true); last_value = rsi_55.GetReadingAt(RSI_BUFFER_SIZE - class="num">1); count = class="num">1; } if(PositionsTotal() == class="num">0) check_signal(); if(PositionsTotal() > class="num">0) manage_setup(); } } class=class="str">"cmt">//+------------------------------------------------------------------+
「持仓跟踪与模型初始化的实盘接法」
把挂着的单子管起来,核心不是频繁改止损,而是只在止损距离变得更优时才动手。manage_setup 里先抓 bid/ask,再用 PositionSelect 确认当前品种有无持仓;若有,就读出原 SL/TP,按多空方向算出新止损 new_sl,只有当新止损比原止损更靠安全侧才调用 PositionModify。 初始化阶段别漏掉 ONNX 句柄校验。setup 函数里用 OnnxCreateFromBuffer 从内存缓冲区建模型,若返回 INVALID_HANDLE 直接 false 退出;随后把输入和输出 shape 都设成 {1,1},这一步不通后面推理全废。 信号读取靠差分 RSI。check_signal 中 rsi_55 先填 55 长度缓冲并做差分,取末尾值 last_value,再拿当前读数 current_reading 做比对——外汇与贵金属波动剧烈,这套逻辑在实盘里可能只在特定时段出信号,属高风险用法,建议先开 MT5 用策略测试器跑通 shape 设置。
class=class="str">"cmt">//| Manage our open trades | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void manage_setup(class="type">void) { class="type">class="kw">double bid = SymbolInfoDouble(Symbol(),SYMBOL_BID); class="type">class="kw">double ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK); if(PositionSelect(Symbol())) { class="type">class="kw">double current_sl = PositionGetDouble(POSITION_SL); class="type">class="kw">double current_tp = PositionGetDouble(POSITION_TP); class="type">class="kw">double new_sl = (current_tp > current_sl) ? (bid-trade_sl) : (ask+trade_sl); class="type">class="kw">double new_tp = (current_tp < current_sl) ? (bid+trade_sl) : (ask-trade_sl); class=class="str">"cmt">//--- Buy setup if((current_tp > current_sl) && (new_sl < current_sl)) Trade.PositionModify(Symbol(),new_sl,new_tp); class=class="str">"cmt">//--- Sell setup if((current_tp < current_sl) && (new_sl > current_sl)) Trade.PositionModify(Symbol(),new_sl,new_tp); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Setup our global variables | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool setup(class="type">void) { class=class="str">"cmt">//--- Setup our technical indicators ma_c_handler = iMA(Symbol(),SYSTEM_TIME_FRAME,class="num">2,class="num">0,MODE_EMA,PRICE_CLOSE); ma_o_handler = iMA(Symbol(),SYSTEM_TIME_FRAME,class="num">2,class="num">0,MODE_EMA,PRICE_OPEN); count = class="num">0; last_value = class="num">0; trade_sl = class="num">1.5e-2; class=class="str">"cmt">//--- Create our ONNX model onnx_model = OnnxCreateFromBuffer(onnx_model_buffer,ONNX_DATA_TYPE_FLOAT); class=class="str">"cmt">//--- Validate the ONNX model if(onnx_model == INVALID_HANDLE) { class="kw">return(false); } class=class="str">"cmt">//--- Define the I/O signature class="type">ulong onnx_param[] = {class="num">1,class="num">1}; if(!OnnxSetInputShape(onnx_model,class="num">0,onnx_param)) class="kw">return(false); if(!OnnxSetOutputShape(onnx_model,class="num">0,onnx_param)) class="kw">return(false); class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Check if we have a trading setup | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void check_signal(class="type">void) { rsi_55.SetDifferencedIndicatorValues(RSI_BUFFER_SIZE,HORIZON,true); last_value = rsi_55.GetReadingAt(RSI_BUFFER_SIZE - class="num">1); class="type">class="kw">double current_reading = rsi_55.GetCurrentReading();
把模型输出接进下单逻辑
这段代码片段展示了如何把随机森林与岭回归两类模型的判断,转成 MT5 实际的下单动作。外汇与贵金属杠杆高,任何模型信号都只是概率倾向,实盘前必须在策略测试器里跑过历史数据。
随机森林部分先取买价卖价,再把 RSI(55) 差分读数塞进 onnx_input[0],阈值卡在 0.5:输出大于该值且开盘均线下穿收盘均线时,才用 Trade.Buy 以卖价做多,止损止盈对称挂在 bid±trade_sl。
岭回归的触发区间写死了两个常数:cp_lb=7.17、cp_ub=10.82。读数差落进 [-7.17,7.17] 或 ≤-7.17 且 >10.82 的异常带时,若开盘均线高于收盘均线且空仓,就反向卖单;否则读数差 ≥7.17 且 <10.82 时,收盘均线低于开盘均线才买。
末尾一堆 #undef 只是清理宏定义,避免与其他文件撞名。复制时留意原文 Ridge Buy 那行逻辑运算符 < 与 && 混用疑似笔误,开 MT5 编译会报错,需按意图改成 && 再验证。
Comment("Last Reading: ",last_value,"\nDifference in Reading: ",(last_value - current_reading)); class="type">class="kw">double bid = SymbolInfoDouble(Symbol(),SYMBOL_BID); class="type">class="kw">double ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK); class="type">class="kw">double cp_lb = class="num">7.17; class="type">class="kw">double cp_ub = class="num">10.82; class="type">class="kw">double onnx_input[class="num">0] = (class="type">float) rsi_55.GetDifferencedReadingAt(RSI_BUFFER_SIZE - class="num">1); class=class="str">"cmt">//--- Our Random forest model if(!OnnxRun(onnx_model,ONNX_DATA_TYPE_FLOAT,onnx_input,onnx_output)) Comment("Failed to obtain a forecast from our model!"); else { if(onnx_output[class="num">0] > class="num">0.5) if(ma_o[class="num">0] < ma_c[class="num">0]) Trade.Buy(TRADING_VOLUME,Symbol(),ask,(bid-trade_sl),(bid+trade_sl)); Print("Model Bullish Probabilty: ",onnx_output); } class=class="str">"cmt">//--- The trading rules we learned from our Ridge Regression Model class=class="str">"cmt">//--- Ridge Regression Sell if((((last_value - current_reading) <= -(cp_lb)) && ((last_value - current_reading) > (cp_ub)))|| ((((last_value - current_reading) > -(cp_lb))) && ((last_value - current_reading) < (cp_lb)))) { if(ma_o[class="num">0] > ma_c[class="num">0]) { if(PositionsTotal() == class="num">0) { Trade.Sell(TRADING_VOLUME,Symbol(),bid,(ask+trade_sl),(ask-trade_sl)); count = class="num">0; } } } class=class="str">"cmt">//--- Ridge Regression Buy else if(((last_value - current_reading) >= (cp_lb)) < ((last_value - current_reading) < cp_ub)) { if(ma_c[class="num">0] < ma_o[class="num">0]) { if(PositionsTotal() == class="num">0) { Trade.Buy(TRADING_VOLUME,Symbol(),ask,(bid-trade_sl),(bid+trade_sl)); count = class="num">0; } } } } class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#undef RSI_BUFFER_SIZE class="macro">#undef RSI_PERIOD class="macro">#undef RSI_PRICE class="macro">#undef RSI_TIME_FRAME class="macro">#undef SYSTEM_TIME_FRAME class="macro">#undef TRADING_VOLUME class="macro">#undef ONNX_INPUTS class="macro">#undef ONNX_OUTPUTS class=class="str">"cmt">//+------------------------------------------------------------------+
◍ 画得少,看得清
网格搜索配上统计模型,本质是把挑指标周期这件事从人工试错变成一次性算完。你不用再逐个回测 14、20、30 这些 RSI 周期,脚本跑一遍就把较优解列出来,EURUSD 上那几个 mq5 文件(Fetch_Data 5.25 KB、Testing_RSI_Class 10.19 KB)直接丢进 MT5 就能复现取数到验证的链路。 用新 RSI 阈值对比传统 70/30 的那套办法,价值不在『战胜市场』,而在于你清楚自己偏离惯例多少、样本内偏差有多大。Algorithmic_Input_Selection_2.mq5(8.19 KB)修掉的正是初版学来的偏误,打开看 diff 比看结论更有用。 外汇和贵金属杠杆高、滑点凶,任何『最优周期』都只是历史样本里的倾向,换一段行情就可能失效。把文件下下来跑一遍,比记住结论更实在。