利用 MQL5 向导和 Hlaiman EA 生成器创建神经网络 EA·进阶篇
HNN 信号类的骨架与可调参数
在 MT5 里做基于历史形态神经网络的信号模块,第一步是把类结构立起来。下面这段声明定义了隐藏层数量、每层神经元数、训练轮次、最近一次信号值,以及承载形态数值的 pattern 数组,还有是否对形态做归一化的开关。 hnn_layers、hnn_neurons、hnn_epoch 三个 int 变量直接决定网络容量与训练成本;pattern[] 的长度由 PatternBarsCount 动态设定,调用时会在原值上加 1,例如传入 10 则数组实际尺寸为 11。外汇与贵金属波动非线性强,这类网络仅提供概率倾向,实盘前务必在策略测试器跑过。 公开方法里,LayersCount / NeuronsCount / EpochCount / Normalize 都是一行赋值,真正干活的是 FillPattern(按时间准备形态)、TeachHNN(训练)、SaveFileHNN(落盘网络文件)。开 MT5 新建 EA 时,先把这几个 setter 接上外部输入参数,就能在属性框里实时调层数和神经元数。
class="type">int hnn_layers; class=class="str">"cmt">// layers count class="type">int hnn_neurons; class=class="str">"cmt">// neurons count class="type">int hnn_epoch; class=class="str">"cmt">// learn epoch class="type">class="kw">double hnn_signal; class=class="str">"cmt">// value of last signal class="type">class="kw">double pattern[]; class=class="str">"cmt">// values of the pattern class="type">bool hnn_norm; class=class="str">"cmt">// normalize pattern class="kw">public: CSignalHNN(class="type">void); class=class="str">"cmt">// class constructor ~CSignalHNN(class="type">void); class=class="str">"cmt">// class destructor class=class="str">"cmt">//--- methods of setting adjustable parameters class="type">void PatternBarsCount(class="type">int value) { hnn_in = value; ArrayResize(pattern, value + class="num">1); } class="type">void LayersCount(class="type">int value) { hnn_layers = value; } class="type">void NeuronsCount(class="type">int value) { hnn_neurons = value; } class="type">void EpochCount(class="type">int value) { hnn_epoch = value; } class="type">void Normalize(class="type">bool value) { hnn_norm = value; } class=class="str">"cmt">//--- method of verification of settings class="kw">virtual class="type">bool ValidationSettings(class="type">void); class=class="str">"cmt">//--- method of creating the indicator and timeseries class="kw">virtual class="type">bool InitIndicators(CIndicators *indicators); class=class="str">"cmt">//--- methods of checking conditions of entering the market class="kw">virtual class="type">class="kw">double Direction(class="type">void); class="type">bool FillPattern(class="type">class="kw">datetime tim = class="num">0); class=class="str">"cmt">// prepare pattern class="type">bool AddPattern(class="type">class="kw">string name, class="type">int ptype); class=class="str">"cmt">// add new pattern class="type">bool TeachHNN(class="type">void); class=class="str">"cmt">// learn neural net class="type">bool SaveFileHNN(class="type">void); class=class="str">"cmt">// neural net file
◍ HNN 信号类的初始化与句柄回收
在 MT5 里把外部神经网信号接进 EA,第一步是给 CSignalHNN 类挂好句柄。类内声明了 CalculateHNN() 负责算信号、InitHNN(bool openn) 做初始化、FreeHNN() 做反初始化,其中 FreeHNN 直接判断 m_hnn 非零且非 INVALID_HANDLE 才 FileClose,避免重复关句柄导致 4019 错误。 InitHNN 的实作里,先取品种名与周期:若类内 m_symbol 非空就用它,否则退回 _Symbol 与 _Period;周期分钟数由 PeriodSeconds(hnn_period)/60 算出,拼成文件名 hnn_fil 供后续定位神经网模型。
| 管道用 FileOpen(HLAIMAN_PIPE, FILE_READ | FILE_WRITE | FILE_BIN) 打开,返回句柄写回 m_hnn。若 openn 为真且 hnn_fil 不存在,会依次在公共目录 TERMINAL_COMMONDATA_PATH 与本地 TERMINAL_DATA_PATH+MQL5+FILES 下补全路径——外汇与贵金属行情高波动,模型文件放错目录会让信号返回空值,开 MT5 后先确认文件存在再跑。 |
|---|
下面这段是类内反初始化与初始化核心,逐行拆一下 FreeHNN: if(m_hnn!=0 && m_hnn!=INVALID_HANDLE) // 句柄有效才动手 FileClose(m_hnn); // 关管道释放系统资源 m_hnn=0; // 置零防止野指针 初始化入口 bool CSignalHNN::InitHNN(bool openn) 中,m_hnn=FileOpen(...) 拿到跨进程管道,后面用 CON_OPENN+CON_TRUE 通知外部应用开启会话。
class="type">class="kw">double CalculateHNN(class="type">void); class=class="str">"cmt">// calc neural signal class=class="str">"cmt">//class="kw">protected: class=class="str">"cmt">//--- method of initialization of the Hlaiman Application class="type">bool InitHNN(class="type">bool openn); class=class="str">"cmt">// Hlaiman App Init class="type">void FreeHNN(class="type">void) { class=class="str">"cmt">// Hlaiman App Deinit if(m_hnn!=class="num">0 && m_hnn!=INVALID_HANDLE) { FileClose(m_hnn); m_hnn=class="num">0; } }; }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Initialize HNN | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CSignalHNN::InitHNN(class="type">bool openn) { class=class="str">"cmt">//--- initialize Hlaiman Application class="type">int num=class="num">0; class="type">ulong res=class="num">0; if(m_symbol!=NULL) { hnn_sym=m_symbol.Name(); hnn_period=m_period; } else { hnn_sym=_Symbol; hnn_period=_Period; } hnn_per = class="type">class="kw">string(PeriodSeconds(hnn_period) / class="num">60); hnn_fil = hnn_nam + NAME_DELIM + hnn_sym + hnn_per + NAME_DELIM + class="type">class="kw">string(hnn_index) + TYPE_NEURO; if(m_hnn== class="num">0|| m_hnn == INVALID_HANDLE) m_hnn=FileOpen(HLAIMAN_PIPE,FILE_READ|FILE_WRITE|FILE_BIN); if(m_hnn!=class="num">0 && m_hnn!=INVALID_HANDLE) { class="type">class="kw">string source,result=""; if(openn==true) { result=CON_OPENN+CON_TRUE; if(!FileIsExist(hnn_fil,FILE_READ)) { if(FileIsExist(hnn_fil,FILE_READ|FILE_COMMON)) hnn_fil=TerminalInfoString(TERMINAL_COMMONDATA_PATH)+PATH_FILES+hnn_fil; else { class=class="str">"cmt">// hnn_fil = hnn_path + PATH_MQL5 + PATH_FILES + hnn_fil; hnn_fil=TerminalInfoString(TERMINAL_DATA_PATH)+PATH_MQL5+PATH_FILES+hnn_fil; } }
「用 Pascal 脚本在 MT5 里热加载神经网络模型」
这段脚本负责在 EA 初始化阶段,把训练好的神经网络参数文件挂到对应品种和周期对象上。路径拼接依赖 TerminalInfoString(TERMINAL_DATA_PATH) 拿到终端数据目录,再叠加 MQL5\Files 子目录,文件找不到时 result 会带上 CON_OPENN+CON_FALSE 标记。 核心逻辑写在一段 Pascal 源码字符串里:先 Open('mt45.dll') 调外部库,再按 hnn_path 打开终端对象,用 ObjectOfName 找专家对象(不存在就 AddObject('TMT45Expert'))。品种对象按 '符号+周期' 命名,例如 'EURUSD15' 这种拼接值,避免多周期混淆。 如果 openn=true,走 sym.Open(hnn_fil) 直接读盘上模型文件,成功则返回 sym.TeachInput 作为层数输入;否则走分支手动写 TeachInput / TeachOutput / TeachLayer / TeachNeurons 四个整数参数。开 MT5 后把 hnn_fil 指向你自己的 .txt 权重文件,就能验证加载是否返回非零 TeachInput。 注意外汇与贵金属杠杆高、滑点随机,神经网络历史拟合不代表实盘概率优势,参数误配可能直接返回 0 导致 EA 静默不训。
else hnn_fil=TerminalInfoString(TERMINAL_DATA_PATH)+PATH_MQL5+PATH_FILES+hnn_fil; } else { result=CON_OPENN+CON_FALSE; hnn_fil=TerminalInfoString(TERMINAL_DATA_PATH)+PATH_MQL5+PATH_FILES+hnn_fil; } InitHNN; Interface "+result+" var libr, term, exp, sym: TObject;" " Implementation function main: integer;\n\r" class=class="str">"cmt">// Line #class="num">1 " begin" " Result := class="num">0;" " libr := Open(&class="macro">#x27;mt45.dll&class="macro">#x27;);\n\r" class=class="str">"cmt">// Line #class="num">2 " if (libr <> nil) then" " begin" " term := Open(&class="macro">#x27;"+hnn_path+"&class="macro">#x27;);\n\r" class=class="str">"cmt">// Line #class="num">3 " if (term <> nil) then" " begin" " exp := term.ObjectOfName(&class="macro">#x27;"+hnn_nam+"&class="macro">#x27;);" " if (exp = nil) then exp := term.AddObject(&class="macro">#x27;TMT45Expert&class="macro">#x27;);\n\r" class=class="str">"cmt">// Line #class="num">5 " if (exp <> nil) then" " begin" " if (exp.Name <> &class="macro">#x27;"+hnn_nam+"&class="macro">#x27;) then exp.Name := &class="macro">#x27;"+hnn_nam+"&class="macro">#x27;;\n\r" class=class="str">"cmt">// Line #class="num">6 " sym := exp.ObjectOfName(&class="macro">#x27;"+hnn_sym+hnn_per+"&class="macro">#x27;);" " if (sym = nil) then sym := exp.AddObject(&class="macro">#x27;TMT45Symbol&class="macro">#x27;);" " if (sym <> nil) then" " begin" " sym.Log.Add(&class="macro">#x27;"+hnn_sym+hnn_per+"&class="macro">#x27;);\n\r" " if (sym.Name <> &class="macro">#x27;"+hnn_sym+hnn_per+"&class="macro">#x27;) then sym.Name := &class="macro">#x27;"+hnn_sym+hnn_per+"&class="macro">#x27;;" " if (sym.Period <> "+hnn_per+") then sym.Period := "+hnn_per+";" " if (openn = true) then" " begin" class=class="str">"cmt">// " sym.Log.Add(&class="macro">#x27;" + hnn_fil + "&class="macro">#x27;);" " if (sym.Open(&class="macro">#x27;"+hnn_fil+"&class="macro">#x27;)) then Result := sym.TeachInput;\n\r" class=class="str">"cmt">// ret input Line #class="num">8 " end else" " begin" " sym.TeachInput := "+IntegerToString(hnn_in)+";" " sym.TeachOutput := "+IntegerToString(hnn_out)+";" " sym.TeachLayer := "+IntegerToString(hnn_layers)+";" " sym.TeachNeurons := "+IntegerToString(hnn_neurons)+";"
HNN 管道通信与信号计算的代码骨架
这段 MQL5 片段展示了层级神经网络(HNN)指标如何通过命名管道与外部 HLAIMAN 应用交换数据并完成初始化。写入源码字符串后调用 FileWriteString 与 FileFlush,把 TeachEpoch、FileName 等参数推给服务端,随后进入轮询等待返回文件尺寸大于 0。 轮询上限由 WAIT_TIMES 与 SLEEP_TIM 控制;若在回测环境(MQL5InfoInteger(MQL5_TESTER) 为真)则不受 WAIT_TIMES 约束,实盘下最多空转 WAIT_TIMES 次。读到内容后取前半长度字符串转整数,若返回值大于 RES_OK 才视为「初始化成功」,并据此 ArrayResize 出 pattern 数组。 CalculateHNN 开头先判管道句柄 m_hnn 是否为 0 或 INVALID_HANDLE,异常直接回 0.0;通过 FillPattern(0) 拉到数据才继续拼 CON_START 指令。外汇与贵金属波动剧烈,这类外部依赖型信号在断管或文件损坏时可能直接失效,开 MT5 跑前务必确认 HLAIMAN 应用已起。
" sym.TeachEpoch := "+IntegerToString(hnn_epoch)+";" " sym.FileName := &class="macro">#x27;"+hnn_fil+"&class="macro">#x27;;" " Result := sym.TeachInput;\n\r" class=class="str">"cmt">// ret input Line #class="num">9 " end;" " end;" " end;" " end;" " end;" " end; end."; FileWriteString(m_hnn,source,StringLen); FileFlush(m_hnn); while(res<=class="num">0 && (MQL5InfoInteger(MQL5_TESTER) || num<WAIT_TIMES)) { Sleep(SLEEP_TIM); res=FileSize(m_hnn); num++; } if(res>class="num">0) { result=FileReadString(m_hnn,class="type">int(res/class="num">2)); res=StringToInteger(result); if(res<=RES_OK) printf(__FUNCTION__+": Error! Initialization data(possible reason: FILE NOT EXIST OR CORRUPTED "+hnn_fil); else { printf(__FUNCTION__+": Initialization successful! NEURAL PATTERN "+class="type">class="kw">string(res)); ArrayResize(pattern,class="type">int(res+class="num">1)); class="kw">return(true); } } else printf(__FUNCTION__+": Error! pipe server not responding(possible elimination: RESTART HLAIMAN APPLICATION)"); } else printf(__FUNCTION__+": Error! initializing pipe server(possible reason: HLAIMAN APPLICATION IS NOT RUNNING!)"); class=class="str">"cmt">//--- ok class="kw">return(false); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Calculate HNN signal | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double CSignalHNN::CalculateHNN(class="type">void) { if(m_hnn==class="num">0 || m_hnn==INVALID_HANDLE) class="kw">return(class="num">0.0); class="type">int num = class="num">0; class="type">ulong siz = class="num">0; class="type">class="kw">double res=class="num">0.0; class="type">class="kw">string source,result=""; if(FillPattern(class="num">0)==true) { result=CON_START;
◍ 把形态数组拼进神经网推理脚本
这段逻辑干一件事:把前面提取出的价格形态 pattern 数组,拼成一段可调用的神经网络计算单元源码字符串。循环从下标 1 跑到数组倒数第二位,用 DoubleToString 把每个数值转成文本,中间以 CON_ADD 连接;末位单独追加 CON_END,避免尾部多出一个分隔符。 拼完的 result 被塞进一段 Pascal 风格的计算单元骨架里:先声明接口与变量,main 函数里加载 mt45.dll,再打开指定路径的神经网络术语文件,逐层取到对应品种周期的 exp 对象。若 lst 行数不少于网络输入数,就把每行字符串写进 NetInputs 的 NetValue,触发 Computed 后返回 NetOutputs 第一个节点的 NetValue。 在 MT5 里验证时,重点看 Line #6 附近:lst.Count 必须 ≥ sym.NetInputs.Count,否则整段推理被跳过、Result 恒为 0.0。外汇与贵金属市场波动剧烈、杠杆风险高,这套推理输出仅作概率参考,不能直接当作下单依据。
for(class="type">int i=class="num">1; i<(ArraySize(pattern)-class="num">1); i++) result= result+DoubleToString(pattern[i])+CON_ADD; result = result + DoubleToString(pattern[ArraySize(pattern) - class="num">1]) + CON_END; source = "unit CalcHNN; Interface " + result + " var i: integer; libr, term, exp, sym, lst: TObject;" " Implementation function main: class="type">class="kw">double;\n\r" class=class="str">"cmt">// Line #class="num">1 " begin" " Result := class="num">0.0;" " libr := Open(&class="macro">#x27;mt45.dll&class="macro">#x27;);\n\r" class=class="str">"cmt">// Line #class="num">2 " if (libr <> nil) then" " begin" " term := Open(&class="macro">#x27;"+hnn_path+"&class="macro">#x27;);\n\r" class=class="str">"cmt">// Line #class="num">3 " if (term <> nil) then" " begin" " exp := term.ObjectOfName(&class="macro">#x27;"+hnn_nam+"&class="macro">#x27;);\n\r" class=class="str">"cmt">// Line #class="num">4 " if (exp <> nil) then" " begin" " sym := exp.ObjectOfName(&class="macro">#x27;"+hnn_sym+hnn_per+"&class="macro">#x27;);\n\r" class=class="str">"cmt">// Line #class="num">5 " if (sym <> nil) then" " begin" " lst := TStringList.Create;" " if (lst <> nil) then" " begin" " lst.Text := cons;" " if (lst.Count >= sym.NetInputs.Count) then" " begin" " for i := class="num">0 to sym.NetInputs.Count - class="num">1 do" " begin" " sym.NetInputs.Objects[i].NetValue := StrToFloat(lst[i]);\n\r" class=class="str">"cmt">// Line #class="num">6 class=class="str">"cmt">// " sym.Log.Add(&class="macro">#x27;Input &class="macro">#x27; + IntToStr(i) + &class="macro">#x27; = &class="macro">#x27; + lst[i]);" " end;" " sym.Computed := true;" " Result := sym.NetOutputs.Objects[class="num">0].NetValue;\n\r" class=class="str">"cmt">// ret input Line #class="num">7 " end;" " lst.Free;" " end;" " end;" " end;" " end;"
「把形态写进神经网推理脚本」
上一节解决了从外部模块取数的阻塞等待,这一节看 AddPattern 如何把 K 线形态拼装成可执行的推理脚本。它先以 ptype 填入 pattern[0],再遍历数组用 CON_ADD 连接各分量,尾部用 CON_END 封口,最终拼出一段 Pascal 风格字符串。 这段字符串不是给人读的,是喂给 mt45.dll 加载的神经网络终端用的。源码里硬编码了 'unit AddPatternHNN' 与 'libr := Open(\'mt45.dll\')',说明实盘或回测时本机必须存在该 DLL,否则 libr 为 nil,整条推理链直接断掉。 注意第 3 行 term := Open('"+hnn_path+"') 与第 4 行 exp := term.ObjectOfName('"+hnn_nam+"'),路径和实例名都是外部变量。你在 MT5 里复用这段代码前,得先确认 hnn_path 指向的终端文件、hnn_nam 对应的网络对象真实存在,否则 exp 恒为 nil,返回结果永远为 0。外汇与贵金属品种波动剧烈,这类外部依赖一旦失效,信号可能瞬间消失,属高风险操作。
class="type">bool CSignalHNN::AddPattern(class="type">class="kw">string name,class="type">int ptype) { class="type">int num=class="num">0; class="type">long res=class="num">0; class="type">ulong siz=class="num">0; class="type">class="kw">string result,source,nam=name; if(m_hnn!=class="num">0 || m_hnn!=INVALID_HANDLE) { pattern[class="num">0]=ptype; result=CON_START; for(class="type">int i=class="num">0; i<(ArraySize(pattern)-class="num">1); i++) result= result+DoubleToString(pattern[i])+CON_ADD; result = result + DoubleToString(pattern[ArraySize(pattern) - class="num">1]) + CON_END; source = "unit AddPatternHNN; Interface " + result + " Implementation function main: integer;" " var i: integer; out: class="type">class="kw">double; onam: class="type">class="kw">string;" " libr, term, exp, sym, ord, tck, lst: TObject;\n\r" class=class="str">"cmt">// Line #class="num">1 " begin" " Result := class="num">0;" " libr := Open(&class="macro">#x27;mt45.dll&class="macro">#x27;);\n\r" class=class="str">"cmt">// Line #class="num">2 " if (libr <> nil) then" " begin" " term := Open(&class="macro">#x27;"+hnn_path+"&class="macro">#x27;);\n\r" class=class="str">"cmt">// Line #class="num">3 " if (term <> nil) then" " begin" " exp := term.ObjectOfName(&class="macro">#x27;"+hnn_nam+"&class="macro">#x27;);\n\r" class=class="str">"cmt">// Line #class="num">4 " if (exp <> nil) then" " begin"