数据科学和机器学习(第 33 部分):MQL5 中的 Pandas 数据帧,为机器学习收集数据更加容易·综合运用
(3/3)· 从二维矩阵到 ONNX 部署,用 MQL5 复刻 Python 数据游乐场的最后一块拼图
- 用 DataFrame 把 EURUSD 日线读进 MT5
- 用 Iloc 切片验证 EURUSD H1 数据读取
- 用类 pandas 接口在 MT5 里抽 CSV 行值
- 按列名剔除字段的 DataFrame 落法
- MT5 日志里那行 pandas test 是什么
- 在 MT5 里复刻 Pandas 的帧检查三板斧
- 用 DataFrame 把 EURUSD 行情跑出统计截面
- 在 MT5 里用 pandas 接口拉 EURUSD 小时线
- 特征工程里的时间位移与滚动窗口
- 用 Shift 造一列未来收盘价
- 用 MT5 给收盘价算环比涨跌幅
- 用 Diff 给 EURUSD 日线算开盘价差
- 用 Rolling 在 MT5 里复刻 pandas 的 5 周期均线
- 用 DataFrame 给日线 EURUSD 加一条 5 周期均线
- 给AI喂日线特征前的变量拼装
- 从日志行看 pandas 回测的字段对齐
- EURUSD H1 数据框的实时打印与结构核对
- EURUSD_H1 特征矩阵的缺失分布
- EURUSD H1 的 pandas 特征快照怎么读
- EURUSD_H1 多模型回测日志怎么读
- 回归模型在 EURUSD 日线上的过拟合现场
- 线性回归系数暴露的因子权重断层
- 把训练好的回归模型塞进EA里跑
- 把日线行情压成特征矩阵
- 把日线裸K喂给特征工程
- 扔掉噪声列再取最新一行
- 线性回归预测在 EURUSD H1 上的日志与调用写法
- 让 CDataframe 替你扛住二维数据
用 DataFrame 把 EURUSD 日线读进 MT5
想在 MT5 里像 pandas 那样直接切分外汇历史数据,可以自己封装一个 CDataFrame 类,把 CSV 读入矩阵再做行列定位。下面这段演示了边界检查与按索引取列的核心逻辑,以及从日线文件拉数据的入口。
class=class="str">"cmt">//--- Check bounds if(index < class="num">0 || index >= (class="type">int)m_values.Cols()) { printf("%s Error: Column index out of bounds. Given index: %d", __FUNCTION__, index); class="kw">return column; } class="kw">return m_values.Col(index); } else printf("%s Failed, Unknown axis ",__FUNCTION__); class="kw">return vector::Zeros(class="num">0); } class="type">void OnStart() { class=class="str">"cmt">//--- CDataFrame df; df.ReadCSV("EURUSD.PERIOD_D1.csv"); df.Head(); Print("First row",df.Loc(class="num">0)); class=class="str">"cmt">//--- Print("Last class="num">5 items in df\n",df.Tail()); Print("Last row: ",df.Loc(-class="num">1)); Print("Last Column: ",df.Loc(-class="num">1, class="num">1)); }
m_values.Col(index) 取该列向量。若轴类型不对,打印未知轴错误并返回零长向量。
OnStart 里先 ReadCSV 读入 EURUSD.PERIOD_D1.csv,Head 看表头,Loc(0) 取首行;Tail 看末尾 5 行,Loc(-1) 取最后一行、Loc(-1,1) 取末行第 2 列(如 Close)。实跑日志显示 EURUSD H1 下矩阵为 1000x4,首行 Open 1.09381、High 1.09548、Low 1.09003、Close 1.09373。
外汇与贵金属属高风险品种,历史 CSV 回放仅用于验证数据结构,不预示后续行情方向。开 MT5 把对应品种 CSV 丢进 MQL5/Files,改文件名即可复跑这套读取。
class=class="str">"cmt">//--- Check bounds if(index < class="num">0 || index >= (class="type">int)m_values.Cols()) { printf("%s Error: Column index out of bounds. Given index: %d", __FUNCTION__, index); class="kw">return column; } class="kw">return m_values.Col(index); } else printf("%s Failed, Unknown axis ",__FUNCTION__); class="kw">return vector::Zeros(class="num">0); } class="type">void OnStart() { class=class="str">"cmt">//--- CDataFrame df; df.ReadCSV("EURUSD.PERIOD_D1.csv"); df.Head(); Print("First row",df.Loc(class="num">0)); class=class="str">"cmt">//--- Print("Last class="num">5 items in df\n",df.Tail()); Print("Last row: ",df.Loc(-class="num">1)); Print("Last Column: ",df.Loc(-class="num">1, class="num">1)); }
◍ 用 Iloc 切片验证 EURUSD H1 数据读取
在 MT5 里跑 pandas 风格的数据框测试,日志会直接吐出 EURUSD H1 的 OHLC 样本。首行打印为 [1.09381, 1.09548, 1.09003, 1.09373],末行则是 [1.21444, 1.21774, 1.21101, 1.21203],说明数据框按时间升序装载了至少百根以上小时线。 最后列(收盘列)抽样显示价格从 1.09373 一路走到 1.11932 附近,中间出现一个异常值 1.00063——这大概率是某根跳空或脏数据,实盘前必须做缺失值清洗。外汇与贵金属杠杆高,这类异常不处理会直接带偏信号。 用 Iloc 做区域切片很直接:CDataFrame Iloc(ulong start_row, ulong end_row, ulong start_col, ulong end_col) 按行列区间截取。下面这行取前 100 行、前 3 列(实际索引 0~2),跑完 Head() 就能在日志里核对表头是不是 Open/High/Low。
CDataFrame Iloc(class="type">class="kw">ulong start_row, class="type">class="kw">ulong end_row, class="type">class="kw">ulong start_col, class="type">class="kw">ulong end_col); df = df.Iloc(class="num">0,class="num">100,class="num">0,class="num">3); class=class="str">"cmt">//Slice from the first row to the 99th from the first column to the 2nd df.Head();
「用类 pandas 接口在 MT5 里抽 CSV 行值」
把日线 EURUSD 的 CSV 读进自定义 CDataFrame 后,取数方式和 python 的 pandas 几乎同构。At(0,"Close") 按列名取首行收盘价,日志里落出 1.09373;Iat(0,0) 按纯坐标取首行首列,返回 1.09381——两者在 H1 测试环境下同根不同入口。
class="type">class="kw">double CDataFrame::At(class="type">class="kw">ulong row, class="type">class="kw">string col_name) { class="type">class="kw">ulong col_number = (class="type">class="kw">ulong)ColNameToIndex(col_name, m_columns); class="kw">return m_values[row][col_number]; } class="type">void OnStart() { class=class="str">"cmt">//--- CDataFrame df; df.ReadCSV("EURUSD.PERIOD_D1.csv"); Print(df.At(class="num">0,"Close")); class=class="str">"cmt">//Returns the first value within the Close column }
class="type">class="kw">double CDataFrame::At(class="type">class="kw">ulong row, class="type">class="kw">string col_name) { class="type">class="kw">ulong col_number = (class="type">class="kw">ulong)ColNameToIndex(col_name, m_columns); class="kw">return m_values[row][col_number]; } class="type">void OnStart() { class=class="str">"cmt">//--- CDataFrame df; df.ReadCSV("EURUSD.PERIOD_D1.csv"); Print(df.At(class="num">0,"Close")); class=class="str">"cmt">//Returns the first value within the Close column } class="type">class="kw">double CDataFrame::Iat(class="type">class="kw">ulong row,class="type">class="kw">ulong col) { class="kw">return m_values[row][col]; } Print(df.Iat(class="num">0,class="num">0)); class=class="str">"cmt">//Returns the value at first row and first colum CDataFrame CDataFrame::Drop(class="kw">const class="type">class="kw">string cols) { CDataFrame df; class="type">class="kw">string column_names[]; class="type">class="kw">ushort sep = StringGetCharacter(",",class="num">0); if(StringSplit(cols, sep, column_names) < class="num">0) { printf("%s Failed to get the columns, ensure they are separated by a comma. Error = %d", __FUNCTION__, GetLastError()); class="kw">return df; } class="type">int columns_index[]; class="type">uint size = column_names.Size(); ArrayResize(columns_index, size); if(size > m_values.Cols()) { printf("%s failed, The number of columns > columns present in the dataframe", __FUNCTION__); class="kw">return df; } class=class="str">"cmt">// Fill columns_index with column indices to drop
按列名剔除字段的 DataFrame 落法
在 MT5 里给自定义 CDataFrame 写 Drop 方法,核心是先拿到待删列的下标,再重建一个少列的 matrix。下面这段就是按列名数组逐个查索引、命中就跳过、未命中就拷进新矩阵的实现。
for(class="type">uint i = class="num">0; i < size; i++) { columns_index[i] = ColNameToIndex(column_names[i], m_columns); if(columns_index[i] == -class="num">1) { printf("%s Column &class="macro">#x27;%s&class="macro">#x27; not found in this DataFrame", __FUNCTION__, column_names[i]); class=class="str">"cmt">//ArrayRemove(column_names, i, class="num">1); class="kw">continue; } } matrix new_data(m_values.Rows(), m_values.Cols() - size); class="type">class="kw">string new_columns[]; ArrayResize(new_columns, (class="type">int)m_values.Cols() - size); class=class="str">"cmt">// Populate new_data with columns not in columns_index for(class="type">uint i = class="num">0, count = class="num">0; i < m_values.Cols(); i++) { class="type">bool to_drop = class="kw">false; for(class="type">uint j = class="num">0; j < size; j++) { if(i == columns_index[j]) { to_drop = true; class="kw">break; } } if(!to_drop) { new_data.Col(m_values.Col(i), count); new_columns[count] = m_columns[i]; count++; } } class=class="str">"cmt">// Replace original data with the updated matrix and columns df.m_values = new_data; ArrayResize(df.m_columns, new_columns.Size()); ArrayCopy(df.m_columns, new_columns); class="kw">return df; } class="type">void OnStart() { class=class="str">"cmt">//--- CDataFrame df; df.ReadCSV("EURUSD.PERIOD_D1.csv"); CDataFrame new_df = df.Drop("Open,Close"); class=class="str">"cmt">//drop the columns and assign the dataframe to a new object new_df.Head(); }
for(class="type">uint i = class="num">0; i < size; i++) { columns_index[i] = ColNameToIndex(column_names[i], m_columns); if(columns_index[i] == -class="num">1) { printf("%s Column &class="macro">#x27;%s&class="macro">#x27; not found in this DataFrame", __FUNCTION__, column_names[i]); class=class="str">"cmt">//ArrayRemove(column_names, i, class="num">1); class="kw">continue; } } matrix new_data(m_values.Rows(), m_values.Cols() - size); class="type">class="kw">string new_columns[]; ArrayResize(new_columns, (class="type">int)m_values.Cols() - size); class=class="str">"cmt">// Populate new_data with columns not in columns_index for(class="type">uint i = class="num">0, count = class="num">0; i < m_values.Cols(); i++) { class="type">bool to_drop = class="kw">false; for(class="type">uint j = class="num">0; j < size; j++) { if(i == columns_index[j]) { to_drop = true; class="kw">break; } } if(!to_drop) { new_data.Col(m_values.Col(i), count); new_columns[count] = m_columns[i]; count++; } } class=class="str">"cmt">// Replace original data with the updated matrix and columns df.m_values = new_data; ArrayResize(df.m_columns, new_columns.Size()); ArrayCopy(df.m_columns, new_columns); class="kw">return df; } class="type">void OnStart() { class=class="str">"cmt">//--- CDataFrame df; df.ReadCSV("EURUSD.PERIOD_D1.csv"); CDataFrame new_df = df.Drop("Open,Close"); class=class="str">"cmt">//drop the columns and assign the dataframe to a new object new_df.Head(); }
◍ MT5 日志里那行 pandas test 是什么
在 MT5 的 Experts 日志中,偶尔会刷出类似 DH 0 19:18:22.998 pandas test (EURUSD,H1) (1000x2) 的记录。DH 是调试句柄标识,后面的 0 通常指线程或实例序号,时间戳精确到毫秒级,说明这是策略在 H1 周期 EURUSD 上跑的一次诊断输出。
括号里的 (1000x2) 最值得留意:它大概率表示样本量为 1000 根 K 线、做了 2 次迭代或双通道校验。如果你在自家 EA 里也看到这种写法,可以直接去 OnTick 或独立测试函数里搜 'pandas test' 字符串,确认是不是别人遗留的基准测试桩。
外汇与贵金属属高风险品种,这类日志仅反映历史回测片段,不代表实盘表现,开 MT5 按 Ctrl+T 切到 Experts 标签就能复现验证。
DH class="num">0 class="num">19:class="num">18:class="num">22.998 pandas test(EURUSD,H1) (1000x2)
「在 MT5 里复刻 Pandas 的帧检查三板斧」
把 Pandas 里常用的数据帧探查方法搬到 MQL5,核心思路是用一个封装类把 CSV 读进全局矩阵 m_values,再写几个成员函数做切片和统计。这样不用切到 Python 环境,直接在 EA 或脚本里就能看数据尾部、结构和分布。 Tail 方法默认取最后 5 行,若请求行数大于等于总行数会打印警告并返回空矩阵。下面这段是 MQL5 实现与一次实跑输出,读取的是 EURUSD 的 D1 导出的 H1 帧,日志里能看到尾行 OHLC 近似落在 1.20742~1.21774 区间。 Info 方法用来摸清帧的结构:行数、列数、每列非空计数和类型。实测输出显示该帧有 1000 行(索引 0 到 999)、4 列,若某列有缺失值这里会直接暴露出来,比肉眼翻 CSV 靠谱。 Describe 则对数值列给描述统计——均值、标准差、最小最大、以及 25/50/75 分位。配合 shape 和 columns 的访问(直接读 m_values.Rows()/Cols()),基本覆盖盘前数据体检的需求。外汇与贵金属数据波动剧烈,这类统计只反映历史样本,实盘信号仍需结合价格行为判断。
matrix CDataFrame::Tail(class="type">uint count=class="num">5) { class="type">class="kw">ulong rows = m_values.Rows(); if(count>=rows) { printf("%s count[%d] >= number of rows in the df[%d]",__FUNCTION__,count,rows); class="kw">return matrix::Zeros(class="num">0,class="num">0); } class="type">class="kw">ulong start = rows-count; matrix res = matrix::Zeros(count, m_values.Cols()); for(class="type">class="kw">ulong i=start, row_count=class="num">0; i<rows; i++, row_count++) res.Row(m_values.Row(i), row_count); class="kw">return res; } class="type">void OnStart() { class=class="str">"cmt">//--- CDataFrame df; df.ReadCSV("EURUSD.PERIOD_D1.csv"); Print(df.Tail()); } GR class="num">0 class="num">17:class="num">06:class="num">42.044 pandas test(EURUSD,H1) [[class="num">1.20796,class="num">1.21591,class="num">1.20742,class="num">1.21416] MG class="num">0 class="num">17:class="num">06:class="num">42.044 pandas test(EURUSD,H1) [class="num">1.21023,class="num">1.21474,class="num">1.20588,class="num">1.20814] KQ class="num">0 class="num">17:class="num">06:class="num">42.044 pandas test(EURUSD,H1) [class="num">1.21089,class="num">1.21342,class="num">1.20953,class="num">1.21046] DK class="num">0 class="num">17:class="num">06:class="num">42.044 pandas test(EURUSD,H1) [class="num">1.21281,class="num">1.21664,class="num">1.20785,class="num">1.2109] MO class="num">0 class="num">17:class="num">06:class="num">42.044 pandas test(EURUSD,H1) [class="num">1.21444,class="num">1.21774,class="num">1.21101,class="num">1.21203]] class="type">void CDataFrame::Info(class="type">void) ES class="num">0 class="num">17:class="num">34:class="num">04.968 pandas test(EURUSD,H1) <class &class="macro">#x27;CDataFrame&class="macro">#x27;> IH class="num">0 class="num">17:class="num">34:class="num">04.968 pandas test(EURUSD,H1) RangeIndex: class="num">1000 entries, class="num">0 to class="num">999 LR class="num">0 class="num">17:class="num">34:class="num">04.968 pandas test(EURUSD,H1) Data columns(total class="num">4 columns): PD class="num">0 class="num">17:class="num">34:class="num">04.968 pandas test(EURUSD,H1) # Column Non-Null Count Dtype OQ class="num">0 class="num">17:class="num">34:class="num">04.968 pandas test(EURUSD,H1) --- ------ -------------- -----
用 DataFrame 把 EURUSD 行情跑出统计截面
在 MT5 里用 CDataFrame 读 CSV 后,直接调 Describe() 就能把 OHLC 四列的基础统计量打印出来。上面这段日志是 EURUSD H1 最近 1000 根 K 线的实测:四列均为 1000 non-null,内存占用 31.2 KB,说明千行级数据在终端内处理毫无压力。 从输出看,Open 均值 1.104156、Close 均值 1.104306,标准差都在 0.06 附近;最低 Close 到过 0.959320,中位数 Close 为 1.090385。外汇和贵金属杠杆高、滑点大,这些数字只描述历史分布,后续信号能否重复靠概率,不保证。 下面这段代码就是触发上面日志的最小骨架,复制进 EA 的 OnStart 即可在终端看到同款输出。
class="type">void CDataFrame::Describe(class="type">void) class="type">void OnStart() { class=class="str">"cmt">//--- CDataFrame df; df.ReadCSV("EURUSD.PERIOD_D1.csv"); class=class="str">"cmt">//Print(df.Tail()); df.Describe(); }
◍ 在 MT5 里用 pandas 接口拉 EURUSD 小时线
把 pandas 风格的数据接口接进 MT5 后,可以直接在专家日志里看到 EURUSD 的 H1 行情快照。上面这段日志里,FN 标记的那行给出某一时刻的报价:开盘 1.142937、最高 1.145505、最低 1.139295、收盘 1.142365,旁边还标了 75% 的某种统计比例,具体含义取决于你脚本里的计算逻辑。 CG 行则是另一组极值样本,最大区间的开高低收分别为 1.232510 / 1.234950 / 1.226560 / 1.232620,说明同一品种在不同窗口下的波动边界可能差出近 900 点。 真正落地只需一行 shape 打印:df shape = (1000x4) 表示拉到了 1000 根 K 线、每根 4 个价格字段(开高低收)。你在 MT5 里跑这段,把 1000 改成 5000 就能验证数据框是否真按历史深度扩容。 外汇和贵金属杠杆高、滑点大,这类数据接口只解决‘看得快’,不等于信号靠谱,实盘前务必在策略测试器里跑通再说。
FN class="num">0 class="num">18:class="num">10:class="num">42.460 pandas test(EURUSD,H1) class="num">75% class="num">1.142937 class="num">1.145505 class="num">1.139295 class="num">1.142365 CG class="num">0 class="num">18:class="num">10:class="num">42.460 pandas test(EURUSD,H1) max class="num">1.232510 class="num">1.234950 class="num">1.226560 class="num">1.232620 printf("df shape = (%dx%d)",df.m_values.Rows(),df.m_values.Cols()); class="num">2025.01.class="num">27 class="num">18:class="num">24:class="num">14.436 pandas test(EURUSD,H1) df shape = (1000x4)
「特征工程里的时间位移与滚动窗口」
做外汇或贵金属特征工程时,最常碰到的需求是把一行数据和前一根 K 线、后一根 K 线摆在一起比。MQL5 里没有现成的 Pandas,但用向量移位就能复刻 shift() 的逻辑:正索引把元素往前推,生成滞后变量;负索引往后推,生成未来变量,后者在构造标签列时很实用。 pct_change() 算相邻元素百分比变化,用来估回报;diff() 算差值,看逐根波动;rolling() 则是滚动窗口,求某段区间的移动均值或统计量。和其它方法不同,滚动需要先把数据切成按行排的二维矩阵,再喂给标准差、方差、偏态、峰度、中位数这类函数。 下面这段 MQL5 是 CDataFrame 的 Shift 实现,以及用日线 EURUSD csv 造一个「Close lag 1」列的实例。日志里能看到:第一行 Close lag 1 为 nan,第二行补上了前一根的 1.09373000,说明滞后列已正确对齐。 把滚动矩阵接上 Std()、Var()、Skew() 之后,你能直接拿去喂机器学习。常规流水线是在 MQL5 采集数据导出 CSV,Python 训练成 ONNX 再回灌 MT5;外汇和贵金属杠杆高、滑点跳空频繁,回测吻合不代表实盘概率同分布,参数要重验。
vector CDataFrame::Shift(class="kw">const vector &v, class="kw">const class="type">int shift) { class=class="str">"cmt">// Initialize a result vector filled with NaN vector result(v.Size()); result.Fill(NaN); if(shift > class="num">0) { class=class="str">"cmt">// Positive shift: Move elements forward for(class="type">class="kw">ulong i = class="num">0; i < v.Size() - shift; i++) result[i + shift] = v[i]; } else if(shift < class="num">0) { class=class="str">"cmt">// Negative shift: Move elements backward for(class="type">class="kw">ulong i = -shift; i < v.Size(); i++) result[i + shift] = v[i]; } else { class=class="str">"cmt">// Zero shift: Return the vector unchanged result = v; } class="kw">return result; } vector CDataFrame::Shift(class="kw">const class="type">class="kw">string index, class="kw">const class="type">int shift) { vector v = this.GetColumn(index); class=class="str">"cmt">// Initialize a result vector filled with NaN class="kw">return Shift(v, shift); } class="type">void OnStart() { class=class="str">"cmt">//--- CDataFrame df; df.ReadCSV("EURUSD.PERIOD_D1.csv"); vector close_lag_1 = df.Shift("Close", class="num">1); class=class="str">"cmt">//Create a previous class="num">1 lag on the close price df.Insert("Close lag class="num">1",close_lag_1); class=class="str">"cmt">//Insert this new column into a dataframe df.Head(); }
用 Shift 造一列未来收盘价
在 MT5 的 pandas 环境里做 EURUSD H1 回测,最常踩的坑是把「下一根收盘价」当特征用。上面这段日志显示,程序在 19:40:14 先打印了 ES/PS/PP 三组原始 OHLC 样本,紧接着用 Shift("Close", -1) 把 Close 列向上挪一行,生成 future_close_1。 vector future_close_1 = df.Shift("Close", -1); 这一行是核心:负号代表取「之后」的数据,也就是当前 K 线收盘后下一根 H1 的收盘价。Insert 进 dataframe 后,19:43:08 的 Head 输出里就能看到新列「Future 1 close」,首行因为是数据最末端,值为 nan,第二行填进了 1.09399000。 拿具体行看:第三行 Open 1.09701000、Close 1.09805000,它的 Future 1 close 是 1.09742000,即下一根 H1 真实收在 1.09742000,比本根低了 63 点。外汇与贵金属杠杆高,这类未来列只能用于离线标签,实盘直接引用就是偷窥答案,会导致过拟合与重大回撤可能。 开 MT5 跑同样脚本,把 Shift 的 -1 改成 -2,你能立刻得到「下两根」收盘价列,用以构造更宽松的持仓目标阈值。
vector future_close_1 = df.Shift("Close", -class="num">1); class=class="str">"cmt">//Create a future class="num">1 variable df.Insert("Future class="num">1 close",future_close_1); class=class="str">"cmt">//Insert this new column into a dataframe df.Head();
◍ 用 MT5 给收盘价算环比涨跌幅
想把日线收盘做成 pandas 里的 pct_change,直接在 MQL5 里封装两个重载就能跑。下面这段代码在 EURUSD 的 D1 数据上实测:首行 Close pct_change 为 nan,第二行由 1.09373000 变到 1.09399000,算出 0.02377186%,说明除首值外每根 K 线都拿到了前一收的变动百分比。 CDataFrame::Pct_change 先按列名取向量,再转发到向量版本;向量版用循环从 i=1 开始,避免除零,prev_value 为 0 时直接填 0.0,否则按 (curr-prev)/prev*100 写回 results。 在 OnStart 里 ReadCSV 读入 EURUSD.PERIOD_D1.csv,调用 df.Pct_change("Close") 把新列插回数据框并打印 Head,你开 MT5 挂这段到脚本里,换 "Open" 或调成 *1000 看基点波动,都能立刻验证。外汇与贵金属杠杆高,回测结果仅反映历史概率,实盘可能明显偏离。
vector CDataFrame::Pct_change(class="kw">const class="type">class="kw">string index) { vector col = GetColumn(index); class="kw">return Pct_change(col); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ vector CDataFrame::Pct_change(class="kw">const vector &v) { vector col = v; class="type">class="kw">ulong size = col.Size(); vector results(size); results.Fill(NaN); for(class="type">class="kw">ulong i=class="num">1; i<size; i++) { class="type">class="kw">double prev_value = col[i - class="num">1]; class="type">class="kw">double curr_value = col[i]; class=class="str">"cmt">// Calculate percentage change and handle division by zero if(prev_value != class="num">0.0) { results[i] = ((curr_value - prev_value) / prev_value) * class="num">100.0; } else { results[i] = class="num">0.0; class=class="str">"cmt">// Handle division by zero case } } class="kw">return results; } class="type">void OnStart() { class=class="str">"cmt">//--- CDataFrame df; df.ReadCSV("EURUSD.PERIOD_D1.csv"); vector close_pct_change = df.Pct_change("Close"); df.Insert("Close pct_change", close_pct_change); df.Head(); }
「用 Diff 给 EURUSD 日线算开盘价差」
上面那串日志是 EA 在 EURUSD H1 测试时打出的四组样本:DD、QE、NF 三行各带 OHLC 与一列浮值,NJ 行标注了 (1000x5) 的向量规模,说明底层在跑批量差分而非逐根 K 线。 Diff 方法有两个重载:一个吃 vector 和 period,另一个吃列名字符串。核心逻辑就一句——从 period 位置开始,用当前值减前 period 根的值,之前的位置填 NaN。
vector CDataFrame::Diff(class="kw">const vector &v, class="type">int period=class="num">1) { vector res(v.Size()); res.Fill(NaN); for(class="type">class="kw">ulong i=period; i<v.Size(); i++) res[i] = v[i] - v[i-period]; class=class="str">"cmt">//当前值减前 period 根 class="kw">return res; }
vector CDataFrame::Diff(class="kw">const vector &v, class="type">int period=class="num">1) { vector res(v.Size()); res.Fill(NaN); for(class="type">class="kw">ulong i=period; i<v.Size(); i++) res[i] = v[i] - v[i-period]; class=class="str">"cmt">//Calculate the difference between the current value and the previous one class="kw">return res; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ vector CDataFrame::Diff(class="kw">const class="type">class="kw">string index, class="type">int period=class="num">1) { vector v = this.GetColumn(index); class=class="str">"cmt">// Initialize a result vector filled with NaN class="kw">return Diff(v, period); } class="type">void OnStart() { class=class="str">"cmt">//--- CDataFrame df; df.ReadCSV("EURUSD.PERIOD_D1.csv"); vector diff_open = df.Diff("Open"); df.Insert("Open diff", diff_open); df.Head(); }
用 Rolling 在 MT5 里复刻 pandas 的 5 周期均线
上面这组日志是 EURUSD H1 上跑 pandas 测试时打印的 5 行样本:OQ 行收盘 1.09399、盈利 0.00297,LF 行最高摸到 1.10396、浮盈 0.00663,而 FF 行收盘 1.09742 相对开仓是 -0.00062。外汇与贵金属波动剧烈,这类回测数字仅代表历史样本,实盘可能完全走反。 python 侧只写了一行 df["Close sma_5"] = df["Close"].rolling(window=5).mean(),意思是取收盘价的 5 窗滚动平均。要在 MQL5 里不依赖 python 插件自己算,就得把滚动窗口摊成一个矩阵。 下面这段 struct 加 Rolling 函数就是干这个的:先建一个行数等于样本量、列数等于窗口的矩阵,再按偏移把每根 K 线的取值填进去,缺前面的位置填 NaN;Mean() 方法对每一行求平均,就得到和 pandas 一模一样的序列。你可以直接把代码贴进 EA 的 CDataFrame 类里,传 window=5 验证和 python 输出是否对齐。
class="kw">struct rolling_struct { class="kw">public: matrix matrix__; vector Mean() { vector res(matrix__.Rows()); res.Fill(NaN); for(class="type">class="kw">ulong i=class="num">0; i<res.Size(); i++) res[i] = matrix__.Row(i).Mean(); class="kw">return res; } }; rolling_struct CDataFrame::Rolling(class="kw">const vector &v, class="kw">const class="type">uint window) { rolling_struct roll_res; roll_res.matrix__.Resize(v.Size(), window); roll_res.matrix__.Fill(NaN); for(class="type">class="kw">ulong i = class="num">0; i < v.Size(); i++) { for(class="type">class="kw">ulong j = class="num">0; j < window; j++) { class=class="str">"cmt">// Calculate the index in the vector for the Rolling window class="type">class="kw">ulong index = i - (window - class="num">1) + j; if(index >= class="num">0 && index < v.Size()) roll_res.matrix__[i][j] = v[index]; } } class="kw">return roll_res; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ rolling_struct CDataFrame::Rolling(class="kw">const class="type">class="kw">string index, class="kw">const class="type">uint window) { vector v = GetColumn(index);
◍ 用 DataFrame 给日线 EURUSD 加一条 5 周期均线
MT5 的 CDataFrame 可以直接读 CSV 再做滚动统计,不用自己写循环算 SMA。上面这段把 EURUSD 日线 CSV 读进来,对 Close 列取 5 周期滚动均值,生成新列 Close sma_5,再打印前 10 行验证。 实盘前先在 MT5 终端的 Files 目录放好 EURUSD.PERIOD_D1.csv(用历史中心导出或脚本生成),否则 ReadCSV 会返回空表。Head(10) 只控制台输出,不会写回文件,适合快速看前几根 K 线的均值是否合理。 外汇与贵金属属高风险品种,均线仅描述已发生价格的平均位置,对后续拐头或延续只具概率性参考,不能单独作为入场依据。
class="kw">return Rolling(v, window); } class="type">void OnStart() { class=class="str">"cmt">//--- CDataFrame df; df.ReadCSV("EURUSD.PERIOD_D1.csv"); vector close_sma_5 = df.Rolling("Close", class="num">5).Mean(); df.Insert("Close sma_5", close_sma_5); df.Head(class="num">10); }
「给AI喂日线特征前的变量拼装」
做外汇或贵金属的机器学习模型,第一步不是调参,是把日线数据框搭对。基础OHLC四个字段必须先进Dataframe,它们是后续所有衍生特征的母体,市场上绝大多数形态都从这四个数里长出来。 在日线周期上,把前5天的滞后收盘价逐一加进数据框,模型才有可能随时间捕捉周级别形态。再补上收盘价的每日百分比变化、滚动5天方差,前者侦测单日波动幅度,后者抓5日窗口内的离散程度。 差分特征(如open-close、high-low)用来暴露OHLC之间的内部张力,平均价则给模型一条更平滑的中枢参考线。最后塞进布林带、ATR(14)、MACD(12,26,9)三类指标,总变量数会到21个。实跑下来整份数据约占1.6MB内存,且带不少nan值,训练前得先丢弃。 下面这段MQL5是把上述拼装落地的核心:先拉10000根D1柱的OHLC,再循环生成5列滞后收盘,接着算百分比变化、5日方差、差分与均价,最后挂上指标。 CDataFrame df; int size = 10000; //We collect this amount of bars for training purposes vector open, high, low, close; open.CopyRates(Symbol(), PERIOD_D1, COPY_RATES_OPEN,1, size); high.CopyRates(Symbol(), PERIOD_D1, COPY_RATES_HIGH,1, size); low.CopyRates(Symbol(), PERIOD_D1, COPY_RATES_LOW,1, size); close.CopyRates(Symbol(), PERIOD_D1, COPY_RATES_CLOSE,1, size); df.Insert("open",open); df.Insert("high",high); df.Insert("low",low); df.Insert("close",close); int lags = 5; for (int i=1; i<=lags; i++) { vector lag = df.Shift("close", i); df.Insert("close lag_"+string(i), lag); } vector pct_change = df.Pct_change("close"); df.Insert("close pct_change", pct_change); vector var_5 = df.Rolling("close", 5).Var(); df.Insert("var close 5 days", var_5); df.Insert("open_close",open-close); df.Insert("high_low",high-low); df.Insert("Avg price",(open+high+low+close)/4); BB_res_struct bb = CTrendIndicators::BollingerBands(close,20,0,2.000000); //Calculating the bollinger band indicator df.Insert("bb_lower",bb.lower_band); //Inserting lower band values df.Insert("bb_middle",bb.middle_band); //Inserting the middle band values df.Insert("bb_upper",bb.upper_band); //Inserting the upper band values vector atr = COscillatorIndicators::ATR(high,low,close,14); //Calculating the ATR Indicator df.Insert("ATR 14",atr); //Inserting the ATR indicator values MACD_res_struct macd = COscillatorIndicators::MACD(close,12,26,9); //MACD indicator applied to the closing price df.Insert("macd histogram", macd.histogram); //Inserting the MAC historgram values df.Insert("macd main", macd.main); //Inserting the macd main line values df.Insert("macd signal", macd.signal); //Inserting the macd signal line values df.Head(); 逐行看:size=10000定义训练取样柱数;四个CopyRates分别把D1的O/H/L/C拉进向量;循环里df.Shift把close往后移i根生成滞后列;Pct_change算日收益率;Rolling(5).Var()得5日方差;open-close与high-low是裸差分;均价用四价算术平均;布林带周期20、偏差2.0,ATR周期14,MACD快12慢26信号9——插完这些,df.Head()即可在日志里瞄一眼前几行。 外汇与贵金属杠杆高、跳空频繁,这类数据集在重大数据周可能产生异常nan,实盘前建议在MT5里先跑df.Head()确认字段顺序与缺失情况再存CSV。
CDataFrame df; <span class="keyword">class="type">int</span> size = <span class="number">class="num">10000</span>; <span class="comment">class=class="str">"cmt">//We collect this amount of bars for training purposes</span> <span class="keyword">vector</span> open, high, low, close; open.<span class="functions">CopyRates</span>(<span class="functions">Symbol</span>(), <span class="macro">PERIOD_D1</span>, <span class="macro">COPY_RATES_OPEN</span>,<span class="number">class="num">1</span>, size); high.<span class="functions">CopyRates</span>(<span class="functions">Symbol</span>(), <span class="macro">PERIOD_D1</span>, <span class="macro">COPY_RATES_HIGH</span>,<span class="number">class="num">1</span>, size); low.<span class="functions">CopyRates</span>(<span class="functions">Symbol</span>(), <span class="macro">PERIOD_D1</span>, <span class="macro">COPY_RATES_LOW</span>,<span class="number">class="num">1</span>, size); close.<span class="functions">CopyRates</span>(<span class="functions">Symbol</span>(), <span class="macro">PERIOD_D1</span>, <span class="macro">COPY_RATES_CLOSE</span>,<span class="number">class="num">1</span>, size); df.Insert(<span class="class="type">class="kw">string">"open"</span>,open); df.Insert(<span class="class="type">class="kw">string">"high"</span>,high); df.Insert(<span class="class="type">class="kw">string">"low"</span>,low); df.Insert(<span class="class="type">class="kw">string">"close"</span>,close); <span class="keyword">class="type">int</span> lags = <span class="number">class="num">5</span>; <span class="keyword">for</span> (<span class="keyword">class="type">int</span> i=<span class="number">class="num">1</span>; i<=lags; i++) { <span class="keyword">vector</span> lag = df.Shift(<span class="class="type">class="kw">string">"close"</span>, i); df.Insert(<span class="class="type">class="kw">string">"close lag_"</span>+<span class="keyword">class="type">class="kw">string</span>(i), lag); } <span class="keyword">vector</span> pct_change = df.Pct_change(<span class="class="type">class="kw">string">"close"</span>); df.Insert(<span class="class="type">class="kw">string">"close pct_change"</span>, pct_change); <span class="keyword">vector</span> var_5 = df.Rolling(<span class="class="type">class="kw">string">"close"</span>, <span class="number">class="num">5</span>).Var(); df.Insert(<span class="class="type">class="kw">string">"var close class="num">5 days"</span>, var_5); df.Insert(<span class="class="type">class="kw">string">"open_close"</span>,open-close); df.Insert(<span class="class="type">class="kw">string">"high_low"</span>,high-low); df.Insert(<span class="class="type">class="kw">string">"Avg price"</span>,(open+high+low+close)/<span class="number">class="num">4</span>); BB_res_struct bb = CTrendIndicators::BollingerBands(close,<span class="number">class="num">20</span>,<span class="number">class="num">0</span>,<span class="number">class="num">2.000000</span>); <span class="comment">class=class="str">"cmt">//Calculating the bollinger band indicator</span> df.Insert(<span class="class="type">class="kw">string">"bb_lower"</span>,bb.lower_band); <span class="comment">class=class="str">"cmt">//Inserting lower band values</span> df.Insert(<span class="class="type">class="kw">string">"bb_middle"</span>,bb.middle_band); <span class="comment">class=class="str">"cmt">//Inserting the middle band values</span> df.Insert(<span class="class="type">class="kw">string">"bb_upper"</span>,bb.upper_band); <span class="comment">class=class="str">"cmt">//Inserting the upper band values</span> <span class="keyword">vector</span> atr = COscillatorIndicators::ATR(high,low,close,<span class="number">class="num">14</span>); <span class="comment">class=class="str">"cmt">//Calculating the ATR Indicator</span> df.Insert(<span class="class="type">class="kw">string">"ATR class="num">14"</span>,atr); <span class="comment">class=class="str">"cmt">//Inserting the ATR indicator values</span> MACD_res_struct macd = COscillatorIndicators::MACD(close,<span class="number">class="num">12</span>,<span class="number">class="num">26</span>,<span class="number">class="num">9</span>); <span class="comment">class=class="str">"cmt">//MACD indicator applied to the closing price</span> df.Insert(<span class="class="type">class="kw">string">"macd histogram"</span>, macd.histogram); <span class="comment">class=class="str">"cmt">//Inserting the MAC historgram values</span> df.Insert(<span class="class="type">class="kw">string">"macd main"</span>, macd.main); <span class="comment">class=class="str">"cmt">//Inserting the macd main line values </span> df.Insert(<span class="class="type">class="kw">string">"macd signal"</span>, macd.signal); <span class="comment">class=class="str">"cmt">//Inserting the macd signal line values </span> df.Head();
从日志行看 pandas 回测的字段对齐
上面这段 MT5 专家日志,是同一时刻 11:32:21.371 在 EURUSD H1 上跑 pandas test 时打印的三行样本。前两行标记 DD、JN 的委托还处在未成交或刚挂出状态,很多字段是 nan,只有高低收和区间幅度被填了。 DD 行开价 1.1562、高 1.1566、低 1.1503、收 1.1508,区间幅度 0.0054,另一列 0.0063,中间价 1.153475;JN 行开 1.1510、高 1.1513、低 1.1422、收 1.1428,幅度 0.0082,中间价 1.146825,且第 10 列出现 -0.69516858 的浮值——这大概率是某收益率或 z-score 类的计算占位。 ID 行信息最全:开 1.1430、高 1.1536、低 1.1423、收 1.1511,前两笔成交价 1.1428 与 1.1508 都已落库,第 10 列 0.72628631、第 12 列 -0.0081,幅度 0.0113,中间价 1.1475。可见同一时间戳下,不同订单 ID 的 nan 填充进度不一致,是典型的多单并行回测输出。 开 MT5 把这段贴进 Experts 日志对照,重点看自己 EA 里第 10、12 列对应变量名;若你那边全 nan,说明 pandas 向量计算还没接进 OnTick 的写库路径。外汇与贵金属杠杆高,这类回测数值仅作方法验证,实盘信号概率仍受滑点干扰。
◍ EURUSD H1 数据框的实时打印与结构核对
在 MT5 终端跑 pandas test 脚本时,专家日志会按行吐出 EURUSD H1 的单根 K 线快照。上面 ES 行显示 11:32:21.371 时刻,open=1.1507、high=1.1549、low=1.1489、close=1.1505,而某自定义列值为 -0.05212406,说明当时该指标因子处于轻微负偏。 LJ 行同时间戳下 open=1.1482、high=1.1490、low=1.1356、close=1.1387,自定义列读到 -1.02564103,波动挤压后出现了极端负值,EURUSD 这类高杠杆品种此时反向扫损的概率倾向放大,需警惕滑点。 HG 行暴露了数据规模:脚本构造了 10000×22 的矩阵,并在 11:32:21.371 完成填充。随后 FN 到 LK 的日志是 df.Info() 的输出——RangeIndex 覆盖 0 到 9999 共 10000 条,21 个列全为 double 且 non-null 计数满格。 开 MT5 加载同款脚本,把品种切到 XAUUSD 重跑一次,对比 non-null 列数是否仍为 21,就能验证你的环境里 pandas 封装有没有丢字段。
「EURUSD_H1 特征矩阵的缺失分布」
把 EURUSD 的 H1 行情拉成 10000 行特征矩阵后,原生列里 close 满血 10000 非空,但一旦做滞后处理,lag_1 就掉到 9999、lag_5 只剩 9995,说明每多一阶滞后就稳定丢一行,这是 shift 操作的固有代价。 布林带三列(bb_lower / bb_middle / bb_upper)同步落在 9981 非空,缺口 19 行;ATR14 为 9986、macd 主线与柱状 9975,signal 略好到 9992。这些缺口来自指标初始化窗口,不是数据损坏。 直接 Dropnan 后,引擎日志显示从 10000 行剔掉 25 行,留存 9975 行。你可以自己在 MT5 里跑同样脚本,比对 Dropnan 前后的行数差,验证指标 warm-up 到底吃掉了哪些样本。 外汇与贵金属属高杠杆品种,这类特征清洗会改变样本分布,回测结果仅代表历史概率,实盘可能显著偏离。
EURUSD H1 的 pandas 特征快照怎么读
把 MT5 的 EURUSD H1 行情塞进 pandas DataFrame 后,第一行输出就暴露了特征工程的骨架:除 OHLC 外,还叠了 close 的 1~5 期滞后、百分比变动、5 日方差,以及布林带三轨、ATR(14)、MACD 三线。 看具体数值,首根 K 线开盘 1.2306、最高 1.2390、最低 1.2037、收盘 1.2147,close_pct_change 为 -1.324%,说明相对前收出现了逾 1.3% 的回落;var_close_5days 仅 0.00005234,波动聚集度极低。 第二根开盘 1.2154、收盘 1.2113,close_pct_change 收窄到 -0.280%,bb_lower 与 bb_upper 分别落在 1.1724 和 1.2366,价格已贴近上轨外侧。ATR14 从 0.01279 降到 0.01265,MACD histogram 维持在 -1.19 附近的深负区,短线动能仍偏空。 这类快照直接打印到专家日志,你能在 MT5 里复刻:用 CopyRates 取 H1 数据,丢进 Python 的 pandas 算同样字段,比对数值是否一致。外汇与贵金属属高杠杆品种,上述特征仅描述历史分布,后续方向仍可能反转。
◍ EURUSD_H1 多模型回测日志怎么读
上面这段 IO / QP / DJ 三行输出,是同一时刻 12:18:01.766 在 EURUSD H1 上跑 pandas test 留下的快照。三套代号对应不同信号逻辑,但喂的是同一根 K 线上下文,直接横向比就能看出偏差在哪。 IO 行开 1.2104、收 1.2093,DJ 行开 1.2100、收 1.2133,两者收盘差 40 点;而 QP 行收 1.2034,比 IO 低了 59 点。同一时间戳三种收盘推断,说明信号层对枢轴支撑的取法差异被放大到了 0.005 量级。 尾部那行 MS 标注 (9975x21),意思是主矩阵跑了 9975 根样本、21 列特征,落地用 ToCSV 写了 Symbol()+".dailytf.data.csv",精度 8 位。你在本机重跑,只要改 csv_name 后缀就能分周期落盘,不用动前面的特征工程。 外汇与贵金属属高杠杆品种,这类回测偏差只代表历史样本内的分布,实盘可能明显偏离。
class="type">class="kw">string csv_name = Symbol()+".dailytf.data.csv"; new_df.ToCSV(csv_name, class="kw">false, class="num">8);
「回归模型在 EURUSD 日线上的过拟合现场」
用 Pandas 读入 EURUSD 日线 CSV 后,先把 close 向下平移一根得到 future_close 作为回归目标,再丢弃平移产生的空值。特征矩阵剔掉目标列,按时间序列不洗牌拆成训练集与测试集,裹一层 RobustScaler 加 LinearRegression 的流水线直接 fit。 首版模型在训练集和测试集都跑出约 0.99 的 r2,典型过拟合,不是模型健康信号。审查系数重要性时,macd 主线贡献最大,macd 直方图与 macd 信号信息量最低,把负重要性的特征砍掉重训,精度几乎没变但过拟合仍在。 外汇与贵金属杠杆高、单边行情会让这类线性外推快速失效,过拟合模型实盘胜率倾向走低。下面这段 Notebook 代码可直接丢进 Jupyter 复现拆分、训练与真假值对比图,重点看 shuffle=False 那行——保住时间顺序是能信任回测的前提。
class="kw">import pandas as pd class="kw">import matplotlib.pyplot as plt class="kw">import seaborn as sns from sklearn.linear_model class="kw">import LinearRegression from sklearn.pipeline class="kw">import Pipeline from sklearn.preprocessing class="kw">import RobustScaler from sklearn.model_selection class="kw">import train_test_split class="kw">import skl2onnx from sklearn.metrics class="kw">import r2_score sns.set_style("darkgrid") df = pd.read_csv("EURUSD.dailytf.data.csv") df["future_close"] = df["close"].shift(-class="num">1) # Shift the close price by one to get df = df.dropna() # drop nan values caused by the shift operation X = df.drop(columns=[ "future_close" # drop the target veriable from the independent variables matrix ]) y = df["future_close"] # Train test split X_train, X_test, y_train, y_test = train_test_split(X, y, shuffle=False) pipe_model = Pipeline([ ("scaler", RobustScaler()), ("LR", LinearRegression()) ]) pipe_model.fit(X_train, y_train) # Training a Linear regression model # Preparing the data for plotting train_pred = pipe_model.predict(X_train) test_pred = pipe_model.predict(X_test) train_data = pd.DataFrame({ &class="macro">#x27;Index&class="macro">#x27;: range(len(y_train)), &class="macro">#x27;True Values&class="macro">#x27;: y_train, &class="macro">#x27;Predicted Values&class="macro">#x27;: train_pred, &class="macro">#x27;Set&class="macro">#x27;: &class="macro">#x27;Train&class="macro">#x27; }) test_data = pd.DataFrame({ &class="macro">#x27;Index&class="macro">#x27;: range(len(y_test)), &class="macro">#x27;True Values&class="macro">#x27;: y_test, &class="macro">#x27;Predicted Values&class="macro">#x27;: test_pred, &class="macro">#x27;Set&class="macro">#x27;: &class="macro">#x27;Test&class="macro">#x27; }) # figure size 750x1000 pixels fig, axes = plt.subplots(class="num">2, class="num">1, figsize=(class="num">7.5, class="num">10), sharex=False) # Plot Train Data sns.lineplot(ax=axes[class="num">0], data=train_data, x=&class="macro">#x27;Index&class="macro">#x27;, y=&class="macro">#x27;True Values&class="macro">#x27;, label=&class="macro">#x27;True Values&class="macro">#x27;, class="type">class="kw">color=&class="macro">#x27;blue&class="macro">#x27;) sns.lineplot(ax=axes[class="num">0], data=train_data, x=&class="macro">#x27;Index&class="macro">#x27;, y=&class="macro">#x27;Predicted Values&class="macro">#x27;, label=&class="macro">#x27;Predicted Values&class="macro">#x27;, class="type">class="kw">color=&class="macro">#x27;orange&class="macro">#x27;) axes[class="num">0].set_title(f&class="macro">#x27;Train Set: True vs Predicted Values | Acc = {r2_score(y_train, train_pred)}&class="macro">#x27;, fontsize=class="num">14) axes[class="num">0].set_ylabel(&class="macro">#x27;Values&class="macro">#x27;, fontsize=class="num">12) axes[class="num">0].legend() # Plot Test Data sns.lineplot(ax=axes[class="num">1], data=test_data, x=&class="macro">#x27;Index&class="macro">#x27;, y=&class="macro">#x27;True Values&class="macro">#x27;, label=&class="macro">#x27;True Values&class="macro">#x27;, class="type">class="kw">color=&class="macro">#x27;blue&class="macro">#x27;) sns.lineplot(ax=axes[class="num">1], data=test_data, x=&class="macro">#x27;Index&class="macro">#x27;, y=&class="macro">#x27;Predicted Values&class="macro">#x27;, label=&class="macro">#x27;Predicted Values&class="macro">#x27;, class="type">class="kw">color=&class="macro">#x27;orange&class="macro">#x27;) axes[class="num">1].set_title(f&class="macro">#x27;Test Set: True vs Predicted Values | Acc = {r2_score(y_test, test_pred)}&class="macro">#x27;, fontsize=class="num">14) axes[class="num">1].set_xlabel(&class="macro">#x27;Index&class="macro">#x27;, fontsize=class="num">12) axes[class="num">1].set_ylabel(&class="macro">#x27;Values&class="macro">#x27;, fontsize=class="num">12) axes[class="num">1].legend() # Final adjustments plt.tight_layout() plt.show() # Extract the linear regression model from the pipeline lr_model = pipe_model.named_steps[&class="macro">#x27;LR&class="macro">#x27;] # Get feature importance(coefficients)
线性回归系数暴露的因子权重断层
用 pandas 把逻辑回归(此处为线性回归)系数转成 Series 并按绝对值排序,能直接看出哪些特征在拽价格方向。上面这组输出里,macd main 的系数 266.7 几乎独占鳌头,而 macd signal、macd histogram 分别是 -5518 和 -5504,符号相反且量级惊人,说明 MACD 家族内部在模型里互相撕扯,直接全量喂进去会严重共线性。 close、open、Avg price 这类裸价特征系数都在 0.04~0.09 之间,lag 类只有千分之几,布林带三轨更是万级。也就是说,若只做线性映射,裸价和 MACD 主线主导了拟合,其余因子近乎噪声。外汇和贵金属波动受宏观事件驱动,这种线性权重在高杠杆下误判风险极高。 落地时别偷懒全列进 X。下面这段代码把 12 个系数极小或符号混乱的列手动 drop 掉,再用 MinMaxScaler 接 LinearRegression 跑管道,既压住量纲又避开共线性陷阱。你在 MT5 导出的 csv 上复刻这套 drop 清单,R² 可能比全量模型更稳。
feature_importance = pd.Series(lr_model.coef_, index=X_train.columns) # Sort feature importance feature_importance = feature_importance.sort_values(ascending=False) print(feature_importance) macd main class="num">266.706747 close class="num">0.093652 open class="num">0.093435 Avg price class="num">0.042505 close lag_1 class="num">0.006972 close lag_3 class="num">0.003645 bb_upper class="num">0.001423 close lag_5 class="num">0.001415 bb_middle class="num">0.000766 high_low class="num">0.000201 bb_lower class="num">0.000087 var close class="num">5 days -class="num">0.000179 ATR class="num">14 -class="num">0.000185 close pct_change -class="num">0.001046 close lag_4 -class="num">0.002636 close lag_2 -class="num">0.003881 open_close -class="num">0.004705 high -class="num">0.008575 low -class="num">0.008663 macd histogram -class="num">5504.010453 macd signal -class="num">5518.035201 dtype: float64 X = df.drop(columns=[ "future_close", # drop the target veriable from the independent variables matrix "var close class="num">5 days", "ATR class="num">14", "close pct_change", "close lag_4", "close lag_2", "open_close", "high", "low", "macd histogram", "macd signal" ]) pipe_model = Pipeline([ ("scaler", MinMaxScaler()), ("LR", LinearRegression()) ]) pipe_model.fit(X_train, y_train)
◍ 把训练好的回归模型塞进EA里跑
在 MT5 里跑机器学习模型,第一步是把 .onnx 模型文件作为编译资源嵌进 EA,这样分发时不用额外带文件。OnInit 里只做一件事:用 lr.Init(lr_onnx) 把模型加载进内存,失败就直接 INIT_FAILED,避免后续空跑。 数据收集别照抄训练脚本的全量逻辑。原文训练用了 10000 根日线柱,但实盘推理只需约 30 根——MACD 周期 26、布林带 20、ATR 14,留 30 根刚好有余量。OnTick 里流动性爆发时逐笔报价刷新变量太浪费,应该把收集逻辑打包成独立函数,且只在新柱开立时算一次。 特征工程能复用 Pandas 风格的数据帧库,但被模型舍弃的列,最好连生成代码一起删掉。特征多的时候,无谓计算会明显拖慢 EA。Head() 查看后确认最终喂给模型的是 11 个特征,和 Python 端训练出的特征数一致。 预测信号可以很简单:模型输出收盘价高于当前 bid 倾向买,低于 ask 倾向卖。2024-11-01 至 2025-01-25 的回测跑过这套逻辑,外汇品种波动剧烈、杠杆风险高,信号仅作概率参考,实盘前务必在 MT5 策略测试器用自己的品种复验。
class="macro">#resource "\Files\EURUSD.dailytf.model.onnx" as class="type">uchar lr_onnx[] class="macro">#include <Linear Regression.mqh> class="macro">#include <MALE5\pandas.mqh> class="macro">#include <ta-lib.mqh> CLinearRegression lr; class="type">int OnInit() { class=class="str">"cmt">//--- if (!lr.Init(lr_onnx)) class="kw">return INIT_FAILED; class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); } class="type">void OnTick() { class=class="str">"cmt">//--- CDataFrame df; class="type">int size = class="num">10000; class=class="str">"cmt">//We collect this amount of bars for training purposes vector open, high, low, close; open.CopyRates(Symbol(), PERIOD_D1, COPY_RATES_OPEN,class="num">1, size); high.CopyRates(Symbol(), PERIOD_D1, COPY_RATES_HIGH,class="num">1, size); low.CopyRates(Symbol(), PERIOD_D1, COPY_RATES_LOW,class="num">1, size); close.CopyRates(Symbol(), PERIOD_D1, COPY_RATES_CLOSE,class="num">1, size); df.Insert("open",open); df.Insert("high",high); df.Insert("low",low); df.Insert("close",close); class="type">int lags = class="num">5; for (class="type">int i=class="num">1; i<=lags; i++) { vector lag = df.Shift("close", i); df.Insert("close lag_"+class="type">class="kw">string(i), lag); } vector pct_change = df.Pct_change("close"); df.Insert("close pct_change", pct_change); vector var_5 = df.Rolling("close", class="num">5).Var(); df.Insert("var close class="num">5 days", var_5); df.Insert("open_close",open-close); df.Insert("high_low",high-low); df.Insert("Avg price",(open+high+low+close)/class="num">4); class=class="str">"cmt">//--- BB_res_struct bb = CTrendIndicators::BollingerBands(close,class="num">20,class="num">0,class="num">2.000000); class=class="str">"cmt">//Calculating the bollinger band indicator df.Insert("bb_lower",bb.lower_band); class=class="str">"cmt">//Inserting lower band values df.Insert("bb_middle",bb.middle_band); class=class="str">"cmt">//Inserting the middle band values df.Insert("bb_upper",bb.upper_band); class=class="str">"cmt">//Inserting the upper band values
「把日线行情压成特征矩阵」
做外汇或贵金属的日线模型,第一步不是跑算法,而是先把 OHLC 和常用振荡指标塞进同一张数据表。下面这段逻辑在 MT5 里直接取当前品种 PERIOD_D1 的 30 根 K 线,把开高低收分别拷进向量,再统一插入 DataFrame,方便后续做滞后与统计特征。 除了原始价格,还顺手造了 5 阶 close 滞后列、收盘百分比变化、5 日滚动方差,以及 open-close、high-low、均价(四价除以 4)这几类裸价格差。外汇和贵金属波动受消息面驱动明显,这类特征对捕捉跳空和实体放大有概率上的辅助意义,但高杠杆下误判成本也高。 布林带用 20 周期、偏移 0、倍数 2.0,ATR 取 14 周期,都直接写进同一张表。这样你导出的 CSV 就同时带了趋势通道与真实波幅,开 MT5 跑一遍 GetData() 就能在终端看到各列是否对齐。
vector atr = COscillatorIndicators::ATR(high,low,close,class="num">14); class=class="str">"cmt">//计算 ATR 指标,周期14 df.Insert("ATR class="num">14",atr); class=class="str">"cmt">//将 ATR 序列插入数据表 MACD_res_struct macd = COscillatorIndicators::MACD(close,class="num">12,class="num">26,class="num">9); class=class="str">"cmt">//对收盘价算 MACD,快12慢26信号9 df.Insert("macd histogram", macd.histogram); class=class="str">"cmt">//插入 MACD 柱 ndf.Insert("macd main", macd.main); class=class="str">"cmt">//插入 MACD 主线 ndf.Insert("macd signal", macd.signal); class=class="str">"cmt">//插入 MACD 信号线 df.Info(); CDataFrame new_df = df.Dropnan(); new_df.Head(); class="type">class="kw">string csv_name = Symbol()+".dailytf.data.csv"; new_df.ToCSV(csv_name, class="kw">false, class="num">8); } vector GetData(class="type">int start_bar=class="num">1, class="type">int size=class="num">30) { open_.CopyRates(Symbol(), PERIOD_D1, COPY_RATES_OPEN,start_bar, size); class=class="str">"cmt">//取日线开盘 high_.CopyRates(Symbol(), PERIOD_D1, COPY_RATES_HIGH,start_bar, size); class=class="str">"cmt">//取日线最高 low_.CopyRates(Symbol(), PERIOD_D1, COPY_RATES_LOW,start_bar, size); class=class="str">"cmt">//取日线最低 close_.CopyRates(Symbol(), PERIOD_D1, COPY_RATES_CLOSE,start_bar, size); class=class="str">"cmt">//取日线收盘 df_.Insert("open",open_); df_.Insert("high",high_); df_.Insert("low",low_); df_.Insert("close",close_); class="type">int lags = class="num">5; vector lag = {}; for (class="type">int i=class="num">1; i<=lags; i++) class=class="str">"cmt">//生成1~5根收盘滞后 { lag = df_.Shift("close", i); df_.Insert("close lag_"+class="type">class="kw">string(i), lag); } pct_change = df_.Pct_change("close"); class=class="str">"cmt">//收盘变化率 df_.Insert("close pct_change", pct_change); var_5 = df_.Rolling("close", class="num">5).Var(); class=class="str">"cmt">//5日滚动方差 df_.Insert("var close class="num">5 days", var_5); df_.Insert("open_close",open_-close_); df_.Insert("high_low",high_-low_); df_.Insert("Avg price",(open_+high_+low_+close_)/class="num">4); class=class="str">"cmt">//--- BB_res_struct bb = CTrendIndicators::BollingerBands(close_,class="num">20,class="num">0,class="num">2.000000); class=class="str">"cmt">//布林带20/class="num">2.0 df_.Insert("bb_lower",bb.lower_band); df_.Insert("bb_middle",bb.middle_band); df_.Insert("bb_upper",bb.upper_band); atr = COscillatorIndicators::ATR(high_,low_,close_,class="num">14); class=class="str">"cmt">//ATR class="num">14 df_.Insert("ATR class="num">14",atr);
把日线裸K喂给特征工程
这段逻辑干的事很直接:从日线周期拉取最近 30 根 K 线的开高低收,塞进一个 DataFrame,再批量造出滞后、波动率、布林带和 MACD 等特征。外汇与贵金属日线波动受消息面扰动大,这类特征仅描述历史分布,不代表后续方向。 CopyRates 用 PERIOD_D1 取数据,start_bar=1 表示从倒数第二根开始(跳过当前未收盘根),size=30 控制样本量。之后对 close 做 5 期滞后(lag_1 到 lag_5)、百分比变化、以及 5 日滚动方差,等于把‘价格记忆’摊开给模型看。 指标参数都是经典配置:布林带周期 20、偏离 2.0,ATR 周期 14,MACD 快 12 / 慢 26 / 信号 9。你在 MT5 里改 BollingerBands 的 2.000000 为 1.5,上下轨会明显收窄,样本被判定为‘突破’的频率会上升,可直观比对特征分布差异。
MACD_res_struct macd = COscillatorIndicators::MACD(close_,class="num">12,class="num">26,class="num">9); class=class="str">"cmt">//MACD indicator applied to the closing price df_.Insert("macd histogram", macd.histogram); class=class="str">"cmt">//Inserting the MAC historgram values df_.Insert("macd main", macd.main); class=class="str">"cmt">//Inserting the macd main line values df_.Insert("macd signal", macd.signal); class=class="str">"cmt">//Inserting the macd signal line values CDataFrame new_df = df_.Dropnan(); class=class="str">"cmt">//Drop NaN values class="kw">return new_df.Loc(-class="num">1); //class="kw">return the latest row } vector GetData(class="type">int start_bar=class="num">1, class="type">int size=class="num">30) { open_.CopyRates(Symbol(), PERIOD_D1, COPY_RATES_OPEN,start_bar, size); high_.CopyRates(Symbol(), PERIOD_D1, COPY_RATES_HIGH,start_bar, size); low_.CopyRates(Symbol(), PERIOD_D1, COPY_RATES_LOW,start_bar, size); close_.CopyRates(Symbol(), PERIOD_D1, COPY_RATES_CLOSE,start_bar, size); df_.Insert("open",open_); df_.Insert("high",high_); df_.Insert("low",low_); df_.Insert("close",close_); class="type">int lags = class="num">5; vector lag = {}; for (class="type">int i=class="num">1; i<=lags; i++) { lag = df_.Shift("close", i); df_.Insert("close lag_"+class="type">class="kw">string(i), lag); } pct_change = df_.Pct_change("close"); df_.Insert("close pct_change", pct_change); var_5 = df_.Rolling("close", class="num">5).Var(); df_.Insert("var close class="num">5 days", var_5); df_.Insert("open_close",open_-close_); df_.Insert("high_low",high_-low_); df_.Insert("Avg price",(open_+high_+low_+close_)/class="num">4); class=class="str">"cmt">//--- BB_res_struct bb = CTrendIndicators::BollingerBands(close_,class="num">20,class="num">0,class="num">2.000000); class=class="str">"cmt">//Calculating the bollinger band indicator df_.Insert("bb_lower",bb.lower_band); class=class="str">"cmt">//Inserting lower band values df_.Insert("bb_middle",bb.middle_band); class=class="str">"cmt">//Inserting the middle band values df_.Insert("bb_upper",bb.upper_band); class=class="str">"cmt">//Inserting the upper band values atr = COscillatorIndicators::ATR(high_,low_,close_,class="num">14); class=class="str">"cmt">//Calculating the ATR Indicator df_.Insert("ATR class="num">14",atr); class=class="str">"cmt">//Inserting the ATR indicator values MACD_res_struct macd = COscillatorIndicators::MACD(close_,class="num">12,class="num">26,class="num">9); class=class="str">"cmt">//MACD indicator applied to the closing price df_.Insert("macd histogram", macd.histogram); class=class="str">"cmt">//Inserting the MAC historgram values df_.Insert("macd main", macd.main); class=class="str">"cmt">//Inserting the macd main line values df_.Insert("macd signal", macd.signal); class=class="str">"cmt">//Inserting the macd signal line values
◍ 扔掉噪声列再取最新一行
线性回归预测前要先给特征矩阵瘦身。下面这段把 5 日收盘方差、ATR(14)、收盘变化率、多个滞后收盘价、开盘收盘差、高低价、MACD 柱和信号线等列直接 Drop 掉,只留与滞后结构和布林带相关的字段进模型。 CDataFrame new_df = df_.Dropnan(); 之后,日志显示 EURUSD H1 上 30 行里丢掉了 25 行,仅剩 5 行干净样本——缺失值这么高,说明你喂的窗口长度或指标周期没对齐,实盘前得先把数据完整性跑通。 最后 return new_df.Loc(-1) 取的是最新一根 K 线的特征行。从打印看,最新行 open 1.04158、close 1.04956、high_low 0.01099、bb_upper 1.04640、macd main 0.00192371,价格已顶到布林上轨外侧,短线回落的概率倾向大于续涨,但外汇高杠杆下仍可能反向刺穿。 把 Drop 列表里的列名和你的 MT5 自定义指标输出对一遍,缺一列 Loc(-1) 就会返回空行,模型直接哑火。
df_ = df_.Drop( class=class="str">"cmt">//"future_close", "var close class="num">5 days,"+ "ATR class="num">14,"+ "close pct_change,"+ "close lag_4,"+ "close lag_2,"+ "open_close,"+ "high,"+ "low,"+ "macd histogram,"+ "macd signal" ); CDataFrame new_df = df_.Dropnan(); class="kw">return new_df.Loc(-class="num">1); class=class="str">"cmt">//class="kw">return the latest row
「线性回归预测在 EURUSD H1 上的日志与调用写法」
上面这段回测日志来自 EURUSD H1 周期下跑的 LR model Test,同一时间戳 15:45:36.543 输出了 JR、CP、CH 三组记录。JR 行里预测收盘 1.04743000,而实际区间高 1.04956000、低 1.04079000,残差绝对值约 0.00204;CP 行预测 1.04664500,对应真实低 1.04057000,残差拉到 0.01049,说明不同样本点上线性模型偏差波动不小。 CH 行标注 (5x11),意指用 5 个特征乘以 11 根 K 线的窗口构造输入向量。外汇与贵金属杠杆高,这类回归预测仅作概率参考,实盘须防样本外失效。 最朴素的 OnTick 里直接取向量预测并 Comment,每跳都算一次,浪费资源。 改进写法用 isNewBar() 包一层,只在新柱触发时跑 lr.predict(x),MT5 里复制下面代码即可验证频率差异。
class="type">void OnTick() { vector x = GetData(); Comment("Predicted close: ", lr.predict(x)); } class="type">void OnTick() { if (isNewBar()) { vector x = GetData(); Comment("Predicted close: ", lr.predict(x)); } }
让 CDataframe 替你扛住二维数据
把训练好的 AI 模型塞进 MT5 跑推理已经没门槛,真正的麻烦在模型结构和训练数据对齐——稍有不慎,ONNX 加载进来也是 garbage in garbage out。作者在 MQL5 里手搓了 CDataframe 类,思路直接搬 Python 的 Pandas:二维表操作、列切片、训练集装配,全用熟悉的语法在 MT5 侧完成。 附件里给了一套可跑的闭环:Experts\LR model Test.mq5 部署线性回归模型,Include\pandas.mqh 装了 Dataframe 处理方法,Python\main.ipynb 是 Jupyter 里的训练代码,Scripts\pandas test.mq5 负责采数。ZIP 体积 743.15 KB,解压就能在 MT5 里复现。 外汇与贵金属杠杆高、滑点凶,这类 ML 管线只降低工程成本,不替你兜底胜率。开 MT5 把 pandas test.mq5 挂上跑一轮,看采出来的 CSV 和 notebook 里的特征维度是否对得上,比看任何说明都实在。