6 步创建您自己的交易机器人!·进阶篇
双均线类的默认参数写在构造里
把快速和慢速均线的周期、平滑方式直接塞进构造函数初始化列表,是 MQL5 里封装指标信号类最常见的起步做法。下面这段初始化把快线默认设成 13 周期 SMA,慢线默认设成 21 周期 SMA,类一实例化就带好基础参数,不用每次在 OnInit 里手动赋值。 注意原注释里有一处笔误:快线周期写的是 13,但注释说「默认快速 MA 周期是 3」,实际代码以 m_period_fast(13) 为准,3 只是错写。开 MT5 新建 EA 把这段贴进类定义,编译后从左侧导航看成员变量初始值就能验证。 别把注释当真值 代码注释和初始化列表不一致时,以分号前的赋值表达式为唯一依据;注释只作人读参考,回测前先打印 m_period_fast 确认。
MA_Cross::MA_Cross(class="type">void) : m_period_fast(class="num">13), m_method_fast(MODE_SMA), m_period_slow(class="num">21), m_method_slow(MODE_SMA) { }
◍ 给均线交叉信号类加一道参数闸门
写 EA 信号模块时,参数填错不会让编译器立刻报错,但会让策略在实盘里静默失效。MA_Cross 继承自 CExpertSignal,父类强制要求重写 ValidationSettings(),无参且必须返回 bool——这是 MT5 框架初始化交易信号模块的硬性入口。 很多人漏掉的第一行是调用基类校验:if(!CExpertSignal::ValidationSettings()) return(false);。若省掉这行,EA 加载时可能直接初始化失败,信号模块根本不会挂载。外汇与贵金属杠杆高,参数错误引发的错单风险会被放大,上线前必须本地先跑通。 下面这段是类内声明与函数体的最小可用骨架,逐行拆给你看:
| class MA_Cross : public CExpertSignal 定义派生关系;bool ValidationSettings(); 是公共区的方法声明。函数体开头先调基类,再用 m_period_fast<1 | m_period_slow<1 拦掉周期小于 1 的非法值;m_period_fast>m_period_slow 保证快线周期小于慢线;最后用四个 MODE_* 枚举比对,卡死平滑类型只能是 SMA/EMA/SMMA/LWMA 之一。任一不满足就 PrintFormat 报错并返回 false。 |
|---|
验证动作很直接:把 fast 周期故意设成 50、slow 设成 10,编译挂 EA 到 EURUSD 图表,专家日志会打印 SlowPeriod=10 必须大于 FastPeriod=50! 并拒绝启动。改回 fast=10、slow=50 才过校验。
class MA_Cross : class="kw">public CExpertSignal { ... class=class="str">"cmt">//--- 类构造函数 MA_Cross(class="type">void); class=class="str">"cmt">//--- 类析构函数 ~MA_Cross(class="type">void); class=class="str">"cmt">//--- 检查输入数据正确与否 class="type">bool ValidationSettings(); ... }; class=class="str">"cmt">//+------------------------------------------------------------------ class=class="str">"cmt">//| 检查输入参数和返回true是否一切OK | class=class="str">"cmt">//+------------------------------------------------------------------ class="type">bool MA_Cross:: ValidationSettings() { class=class="str">"cmt">//--- 调用基类函数 if(!CExpertSignal::ValidationSettings()) class="kw">return(false); class=class="str">"cmt">//--- 检查周期,柱线数量计算MA >=class="num">1 if(m_period_fast<class="num">1 || m_period_slow<class="num">1) { PrintFormat("不正确的周期值!FastPeriod=%d, SlowPeriod=%d", m_period_fast,m_period_slow); class="kw">return false; } class=class="str">"cmt">//--- 慢速 MA 周期必须大于快速MA周期 if(m_period_fast>m_period_slow) { PrintFormat("SlowPeriod=%d 必须大于 FastPeriod=%d!", m_period_slow,m_period_fast); class="kw">return false; } class=class="str">"cmt">//--- 快速 MA 平滑类型必须是四个枚举类型值之一 if(m_method_fast!=MODE_SMA && m_method_fast!=MODE_EMA && m_method_fast!=MODE_SMMA && m_method_fast!=MODE_LWMA) { PrintFormat("无效平滑类型: 快速 MA!"); class="kw">return false; } class=class="str">"cmt">//--- 慢速 MA 平滑类型必须是四个枚举类型值之一 if(m_method_slow!=MODE_SMA && m_method_slow!=MODE_EMA && m_method_slow!=MODE_SMMA && m_method_slow!=MODE_LWMA) { PrintFormat("无效平滑类型: 慢速 MA!"); class="kw">return false; } class=class="str">"cmt">//--- 所有检查完成, 全部通过 class="kw">return true; } class="type">bool MA_Cross:: ValidationSettings() class=class="str">"cmt">//--- 调用基类函数 if(!CExpertSignal::ValidationSettings()) class="kw">return(false); class=class="str">"cmt">//--- 我们的代码检查参数
「把自定义均线接进信号模块」
信号模块跑起来时,InitIndicators() 会被 EA 自动调用,这里就是挂指标的地方。原文把快速、慢速两条均线拆成 CreateFastMA() 和 CreateSlowMA() 两个独立函数,成功返回 true,失败直接断链,逻辑干净也好排错。 MQL5 里 CIndicators 是专门收留技术指标实例的容器,只认 CIndicator 及其子类。想塞自定义指标,就得用 CiCustom——它本身就是 CIndicator 的派生类,能直接进这个集中被统一管理(同步、计算、释放内存全交给框架)。 以快速 MA 为例,先判一下 indicators 指针是否为空,再把 m_fast_ma 的指针加进集里;随后填 MqlParam 结构。标准库自带的 Custom Moving Average.mq5 实际在 data_folder/MQL5/Indicators/Examples/ 下,所以路径必须带 Examples/,否则测试仪找不到。参数对依次是:指标名字符串、时段 int、位移 int、平均法 int,最后用 Create() 绑定品种、周期、ENUM_INDICATOR 类型和缓冲区数。 慢速 MA 同理。关键一步:文件头加 #property tester_indicator 指向编译后的 .ex5,不然回测时自定义指标加载不了——用几个不同指标就写几行,缺一行测出来就是空值。 取数不用自己写缓冲读取,直接调父类 SIndicator 的 GetData() 按索引拿值就行。下面这段是 InitIndicators 的骨架,注意 MA_Cross:: 作用域和指针判空是必写项。
class="kw">public: class=class="str">"cmt">//--- 类构造函数 MA_Cross(class="type">void); class=class="str">"cmt">//--- 类析构函数 ~MA_Cross(class="type">void); class=class="str">"cmt">//--- 设置方法 class="type">void FastPeriod(class="type">int value) { m_period_fast=value; } class="type">void FastMethod(ENUM_MA_METHOD value) { m_method_fast=value; } class="type">void SlowPeriod(class="type">int value) { m_period_slow=value; } class="type">void SlowMethod(ENUM_MA_METHOD value) { m_method_slow=value; } class=class="str">"cmt">//--- 检查输入数据正确与否 class="type">bool ValidationSettings(); class=class="str">"cmt">//--- 创建信号模型的指标和时间帧 class="type">bool InitIndicators(CIndicators *indicators); }; ... class=class="str">"cmt">//+------------------------------------------------------------------ class=class="str">"cmt">//| 创建指标 | class=class="str">"cmt">//| 输入: 一组指标指针 | class=class="str">"cmt">//| 输出:若成功true,否则false | class=class="str">"cmt">//+------------------------------------------------------------------ class="type">bool MA_Cross::InitIndicators(CIndicators* indicators) { class=class="str">"cmt">//--- 标准检查一组 NULL指标 if(indicators==NULL) class="kw">return(false); class=class="str">"cmt">//--- 初始化指标和时间帧及附加过滤器 if(!CExpertSignal::InitIndicators(indicators)) class="kw">return(false); class=class="str">"cmt">//--- 创建我们的 MA 指标 ... 一些代码 class=class="str">"cmt">//--- 抵达此处, 函数成功, 返回 true class="kw">return(true);
双均线信号的指标初始化链路
在 MT5 的 EA 信号类里,均线的生命周期从 InitIndicators 开始。基类 CExpertSignal 先接管 indicators 指针做空值校验,再交给标准初始化,之后才轮到你自己声明的快/慢 MA 对象挂上去。 下面这段是初始化入口的骨架,两个 Create 函数任一返回 false 都会直接中断,EA 不会进入后续逻辑: protected: bool CreateFastMA(CIndicators *indicators); bool CreateSlowMA(CIndicators *indicators); }; bool MA_Cross::InitIndicators(CIndicators *indicators) { if(indicators==NULL) return(false); if(!CExpertSignal::InitIndicators(indicators)) return(false); if(!CreateFastMA(indicators)) return(false); if(!CreateSlowMA(indicators)) return(false); return(true); } 类内部用 CiCustom 承载两个均线实例,并各自保留周期与平滑类型字段。m_period_fast / m_method_fast 与 m_period_slow / m_method_slow 解耦了参数和对象,实盘里改周期不必动创建逻辑。 成员声明对应如下: private: CiCustom m_fast_ma; // 指标作为一个对象 CiCustom m_slow_ma; // 指标作为一个对象 int m_period_fast; // 快速 MA 周期 ENUM_MA_METHOD m_method_fast; // 快速 MA 平滑类型 int m_period_slow; // 慢速 MA 周期 ENUM_MA_METHOD m_method_slow; // 慢速 MA 平滑类型 外汇与贵金属杠杆高,这类信号类只解决「指标就位」,金叉死叉的触发概率仍受滑点与时框切换影响,开 MT5 把 m_period_fast 设成 5、m_period_slow 设成 20 跑回测能看到交叉频次明显多于 10/50 组合。
class="kw">protected: class="type">bool CreateFastMA(CIndicators *indicators); class="type">bool CreateSlowMA(CIndicators *indicators); }; class="type">bool MA_Cross::InitIndicators(CIndicators *indicators) { if(indicators==NULL) class="kw">return(false); if(!CExpertSignal::InitIndicators(indicators)) class="kw">return(false); if(!CreateFastMA(indicators)) class="kw">return(false); if(!CreateSlowMA(indicators)) class="kw">return(false); class="kw">return(true); } class="type">bool MA_Cross::CreateFastMA(CIndicators *indicators) { ... 一些代码 class="kw">return(true); } class="type">bool MA_Cross::CreateSlowMA(CIndicators *indicators) { ... 一些代码 class="kw">return(true); } class MA_Cross : class="kw">public CExpertSignal { class="kw">private: CiCustom m_fast_ma; class=class="str">"cmt">// 指标作为一个对象 CiCustom m_slow_ma; class=class="str">"cmt">// 指标作为一个对象 class="type">int m_period_fast; class=class="str">"cmt">// 快速 MA 周期 ENUM_MA_METHOD m_method_fast; class=class="str">"cmt">// 快速 MA 平滑类型 class="type">int m_period_slow; class=class="str">"cmt">// 慢速 MA 周期 ENUM_MA_METHOD m_method_slow; class=class="str">"cmt">// 慢速 MA 平滑类型
◍ 把自定义均线塞进信号类里
在 MT5 里做双均线信号,快速 MA 不能靠系统自带的 iMA 硬凑,得用自定义指标 Examples\Custom Moving Average.ex5 挂进 CIndicators 集合。下面这段 CreateFastMA 就是干这事的核心:先判空指针,再把 m_fast_ma 的指针 Add 进去,任何一步失败直接返回 false 并打印函数名方便排查。
参数数组固定开 4 个 MqlParam:0 号放自定义 ex5 路径字符串,1 号放周期(默认类内成员 m_period_fast),2 号平移写 0,3 号放平均方法 m_method_fast。随后 m_fast_ma.Create 用 IND_CUSTOM 类型带 4 个参数初始化,NumBuffers(1) 锁死单缓冲——这意味着你只能取 buffer 0 的数值,别去碰不存在的 buffer 1。
取数接口被缩写成两行内联:FastMA(index) 和 SlowMA(index) 都直接调 GetData(0,index)。回测前记得在 mqh 同目录外的 main 文件加 #property tester_indicator "Examples\\Custom Moving Average.ex5",否则策略测试器加载不了这个自定义指标,EA 在外汇或贵金属品种上跑历史会直接报错。这类品种杠杆高、滑点跳空频繁,自定义均线的缓存失效可能导致信号延迟,实盘前务必用 EURUSD 或 XAUUSD 的 M1 数据先验证一次。
class="type">bool MA_Cross::CreateFastMA(CIndicators *indicators) { class=class="str">"cmt">//--- 检查指针 if(indicators==NULL) class="kw">return(false); class=class="str">"cmt">//--- 添加对象至集合 if(!indicators.Add(GetPointer(m_fast_ma))) { printf(__FUNCTION__+": 添加快速 MA 对象错误"); class="kw">return(false); } class=class="str">"cmt">//--- 设置快速 MA 参数 class="type">MqlParam parameters[class="num">4]; class=class="str">"cmt">//--- parameters[class="num">0].type=TYPE_STRING; parameters[class="num">0].string_value="Examples\\Custom Moving Average.ex5"; parameters[class="num">1].type=TYPE_INT; parameters[class="num">1].integer_value=m_period_fast; class=class="str">"cmt">// 周期 parameters[class="num">2].type=TYPE_INT; parameters[class="num">2].integer_value=class="num">0; class=class="str">"cmt">// 平移 parameters[class="num">3].type=TYPE_INT; parameters[class="num">3].integer_value=m_method_fast; class=class="str">"cmt">// 平均方法 class=class="str">"cmt">//--- 对象初始化 if(!m_fast_ma.Create(m_symbol.Name(),m_period,IND_CUSTOM,class="num">4,parameters)) { printf(__FUNCTION__+": 初始化快速 MA 对象错误"); class="kw">return(false); } class=class="str">"cmt">//--- 缓存区数量 if(!m_fast_ma.NumBuffers(class="num">1)) class="kw">return(false); class=class="str">"cmt">//--- 抵达此处, 函数成功, 返回 true class="kw">return(true); } parameters[class="num">0].string_value="Examples\\Custom Moving Average.ex5"; class=class="str">"cmt">//--- 输入参数 input class="type">int InpMAPeriod=class="num">13; class=class="str">"cmt">// 周期 input class="type">int InpMAShift=class="num">0; class=class="str">"cmt">// 平移 input ENUM_MA_METHOD InpMAMethod=MODE_SMMA; class=class="str">"cmt">// 方法 class="macro">#include "..\ExpertSignal.mqh" class=class="str">"cmt">// CExpertSignal 类包含于文件 ExpertSignal class="macro">#class="kw">property tester_indicator "Examples\\Custom Moving Average.ex5" class=class="str">"cmt">//--- 检查输入数据正确与否 class="type">bool ValidationSettings(class="type">void); class=class="str">"cmt">//--- 创建信号模型的指标和时间帧 class="type">bool InitIndicators(CIndicators *indicators); class=class="str">"cmt">//--- 存取指标数据 class="type">class="kw">double FastMA(const class="type">int index) const { class="kw">return(m_fast_ma.GetData(class="num">0,index)); } class="type">class="kw">double SlowMA(const class="type">int index) const { class="kw">return(m_slow_ma.GetData(class="num">0,index)); }
「把交叉判据塞进 LongCondition 与 ShortCondition」
信号模块真正吐出交易指令,靠的是 CExpertSignal 子类必须覆写的两个虚函数:LongCondition() 与 ShortCondition()。前者回测做多条件并给出 0~100 的强度值,后者对应做空;返回 0 即无信号,非零值越大代表该侧信号越强,方便后续多模块加权组合。
简单模块里我们直接约定:金叉给买入强度 100,死叉给卖出强度 100,不做梯度细分。代码里先用 StartIndex() 拿分析起点 idx——向导默认返回 1,意味着 EA 只在新柱开盘后检查,跳过当前未成形柱的噪音;若改回 0 则每 tick 都判,但易吞假信号。
下面这段是 LongCondition 的核心:取 idx 与 idx+1 两根已成形柱的快速/慢速 MA,若快线由下穿上即为满格买信号。外汇与贵金属杠杆高,这类交叉在震荡市会连续打脸,实盘前务必在 MT5 策略测试器用历史数据跑一遍确认触发频率。
[CODE] 中的关键行拆解:
int idx=StartIndex(); 拿分析基准偏移,默认 1。
double last_fast_value=FastMA(idx); 读基准柱快线值。
double prev_fast_value=FastMA(idx+1); 读前一根快线值。
if((last_fast_value>last_slow_value) && (prev_fast_value<prev_slow_value)) 快线从下方穿越慢线。
signal=100; 命中则给最强买入强度。
... class="type">bool InitIndicators(CIndicators *indicators); class=class="str">"cmt">//--- 访问指标数据 class="type">class="kw">double FastMA(const class="type">int index) const { class="kw">return(m_fast_ma.GetData(class="num">0,index)); } class="type">class="kw">double SlowMA(const class="type">int index) const { class="kw">return(m_slow_ma.GetData(class="num">0,index)); } class=class="str">"cmt">//--- 检查买、卖条件 class="kw">virtual class="type">int LongCondition(); class="kw">virtual class="type">int ShortCondition(); class=class="str">"cmt">//+------------------------------------------------------------------ class=class="str">"cmt">//| 返回买入信号强度 | class=class="str">"cmt">//+------------------------------------------------------------------ class="type">int MA_Cross::LongCondition() { class="type">int signal=class="num">0; class=class="str">"cmt">//--- 操作条件 idx=class="num">0, 及已经建立的 idx=class="num">1 class="type">int idx=StartIndex(); class=class="str">"cmt">//--- 最后已建立柱线的 MAs 值 class="type">class="kw">double last_fast_value=FastMA(idx); class="type">class="kw">double last_slow_value=SlowMA(idx); class=class="str">"cmt">//--- 最后已建立柱线的前一根柱线 MAs 值 class="type">class="kw">double prev_fast_value=FastMA(idx+class="num">1); class="type">class="kw">double prev_slow_value=SlowMA(idx+class="num">1); class=class="str">"cmt">//---如果两根靠拢的柱线的快速 MA 从下往上与慢速 MA 交叉 if((last_fast_value>last_slow_value) && (prev_fast_value<prev_slow_value)) { signal=class="num">100; class=class="str">"cmt">// 这是买信号 } class=class="str">"cmt">//--- 返回信号值 class="kw">return(signal); }