用类把交易逻辑收进一个盒子
用类把交易逻辑收进一个盒子
MQL5 在 MT5 里不是只能写过程式脚本,它完整支持面向对象编程(OOP)。把下单、止损、状态判断这些动作封装成类,比满屏全局函数更容易在多个 EA 之间复用。
实际工程里,一个 CTrade 的派生类就能把「市价单 + 点差过滤 + 重试」三件事绑在一起。这样换品种时只改构造参数,核心逻辑不动。
根据社区文档统计,2024 年 2 月发布的 OOP 入门帖在 MT5 板块获得 1242 次查看、7 条讨论,说明手动交易员也在往可维护代码迁移。外汇与贵金属杠杆高,回测通过不代表实盘稳健,任何封装都要先在策略测试器跑历史数据。
别把正态当圣经
MQL5 的 Class 不会帮你预测行情,它只保证你的下单流程不被临时变量搞脏。真要降坑,先把类的职责拆到最小,再让小布替你跑这套结构做差异比对。
◍ 用 OOP 给 MQL5 代码瘦身
写 MQL5 最怕逻辑散落各处、改一处崩一片。面向对象编程(OOP)是把「数据 + 操作」封成类与对象,直接落地 DRY 原则——同一套行情判定逻辑只写一次,后续复用不复制粘贴,后期维护和安全边界都更可控。 MQL5 的 OOP 语法脱胎于 C++,类、继承、多态都能用。官方文档里几乎每个主题都暗含 OOP 示例,从指标封装到 EA 架构都能照着改。外汇与贵金属杠杆高、滑点跳空频繁,任何自写模块上线前都应在 MT5 策略测试器跑历史回测,验证封装后的类在极端行情下不抛异常。 本系列先拆 OOP 基础概念,再讲 MQL5 里怎么声明 class、怎么重载运算符,最后给几个可直接抄的应用骨架。你今天就能开 MT5 新建一个 empty EA,试着把重复的指标读取写成一个 C_Indicator 类,体会复用带来的代码行数下降。
「把交易逻辑装进类里:OOP 四个支柱」
面向对象编程(OOP)不是语法糖,而是一种把软件按「对象」组织的范式。外汇 EA 或指标越写越臃肿时,OOP 的 DRY(Don't Repeat Yourself)思路能让你复用旧模块而不碰整体代码。身边一切皆可视为对象:每个对象有性质(数据)和行为(函数),类就是对象的模板,MT5 里的 CTrade、CIndicator 本质都是这类模板的实例。 OOP 落地的四个基本概念必须先厘清。封装是把数据和函数绑进一个类,并区分 private(仅类内访问)与 public(类外可调用),以此隐藏实现复杂度、让开发者完全掌控数据,跟踪依赖值时不冲突,测试改类数据也不会炸掉整个 EA。抽象比封装更广,它只露接口、藏实现:你只需知道「调哪个方法能下单」,不必读透里面几十行请求逻辑,安全性与可复用性都上来。 继承是从旧类(父类/超类)派生新类(子类),子类直接拿走父类特征,这是 DRY 的主战场——写过一个均线过滤模块,换周期只需继承改参数。多态字面即「多形态」,同一接口多种实现,例如 Sum 方法可接受 (a,b) 也能接受 (a,b,c),调用方无需关心背后重载。 在 MQL5 里跑复杂策略时,OOP 把这些原则转成实打实的好处:代码切成小模块降复杂度、更新不碰全局、同个类能开多个独立实例互不冲突。MQL5 支持 OOP 意味着你可以用 C++ 式写法写 EA,下面一段示意封装与公开的最小骨架。
class MAFilter { class="kw">private: class="type">int period; class=class="str">"cmt">// 均线周期,仅类内可改 class="kw">public: class="type">void SetPeriod(class="type">int p) { period = p; } class=class="str">"cmt">// 对外暴露的设置接口 class="type">class="kw">double GetValue(class="type">class="kw">double &arr[]) { class=class="str">"cmt">/* 计算逻辑 */ class="kw">return class="num">0; } };
把价格逻辑塞进 MQL5 类里
在 MT5 写 EA 或指标时,用 class 把变量和函数(类里叫方法)打包,比散落全局的函数更利于维护。类声明必须放在全局范围,以 class 加类名起头,成员写在大括号内,结尾加分号;下面这段就是一个含两个变量与一个方法的空壳。 [CODE] class Cobject { int var1; // variable1 double var2; // variable1 void method1(); // Method or function1 }; [/CODE] 访问权限直接决定外部能不能碰成员:public 随便调,private 只能类内用且子类不继承,protected 子类能继承但非子类当 private 看。实测把 var1 标 private、var2 标 protected、method1 标 public,类外就只剩 method1 可点。 [CODE] class Cobject { private: int var1; // variable1 protected: double var2; // variable1 public: void method1(); // Method or function1 }; [/CODE] 构造函数负责初始化,不写编译器也给个不可见的默认版,但通常自己写带参的更稳;析构函数加 ~ 类名,对象注销时自动跑,字符串和动态数组无论如何会被逆初始化。下方 CPrices 同时给了默认与带参两个构造,行情数据 open/high/low/close 收在 private。 [CODE] class CPrices { private: double open; // Open price double high; // High price double low; // Low price double close; // Close price public: CPrices(void); CPrices(double o,double h,double l, double c); }; [/CODE] 继承用冒号加 public 父类,子类白拿父类的 public/protected 成员。CDailyPrices 继承 CPrices 后,原公开受保护成员依旧公开,再补一套日线字段即可。 [CODE] class CDailyPrices : public CPrices { public: double open; // Open price double high; // High price double low; // Low price double close; // Close price }; [/CODE] 想让子类改掉父类方法行为,父类先写 virtual 函数,子类同名重定义。CVar 里 virtual void varCal() 到了 CVar1 变成带参 int 版本,多态就出来了。 [CODE] class CVar { public: virtual void varCal(); }; class CVar1 : public CVar { public: int varCal(int x, int y); }; [/CODE] 对象声明就是「类名+标识符」,点号取公开成员。CSystrades 含 num_trades,建对象 system1 后赋 3,立刻能在 EA 里当成交次数计数器用。外汇与贵金属杠杆高,类里若接实时下单逻辑,回测与实盘偏差可能放大,先离线验证再上资金。 [CODE] class CSystrades { public: int num_trades; }; CSystrades system1; system1.num_trades=3; [/CODE]
class Cobject { class="type">int var1; class=class="str">"cmt">// variable1 class="type">class="kw">double var2; class=class="str">"cmt">// variable1 class="type">void method1(); class=class="str">"cmt">// Method or function1 }; class Cobject { class="kw">private: class="type">int var1; class=class="str">"cmt">// variable1 class="kw">protected: class="type">class="kw">double var2; class=class="str">"cmt">// variable1 class="kw">public: class="type">void method1(); class=class="str">"cmt">// Method or function1 }; class CPrices { class="kw">private: class="type">class="kw">double open; class=class="str">"cmt">// Open price class="type">class="kw">double high; class=class="str">"cmt">// High price class="type">class="kw">double low; class=class="str">"cmt">// Low price class="type">class="kw">double close; class=class="str">"cmt">// Close price class="kw">public: CPrices(class="type">void); CPrices(class="type">class="kw">double o,class="type">class="kw">double h,class="type">class="kw">double l, class="type">class="kw">double c); }; class CDailyPrices : class="kw">public CPrices { class="kw">public: class="type">class="kw">double open; class=class="str">"cmt">// Open price class="type">class="kw">double high; class=class="str">"cmt">// High price class="type">class="kw">double low; class=class="str">"cmt">// Low price class="type">class="kw">double close; class=class="str">"cmt">// Close price }; class CVar { class="kw">public: class="kw">virtual class="type">void varCal(); }; class CVar1 : class="kw">public CVar { class="kw">public: class="type">int varCal(class="type">int x, class="type">int y); }; class CSystrades { class="kw">public: class="type">int num_trades; }; CSystrades system1; system1.num_trades=class="num">3;
◍ 用类把多周期价格和均线类型收进一个壳
想在 MT5 智能系统里同时盯日线、周线、月线的 OHLC,又不想写三套重复变量,可以把价格封装成一个类。下面这段 MQL5 定义了 CPrices 类,内含时间帧名与开高低收四个 double 成员,再加一个 pricesPrint() 负责把数据打到 Experts 选项卡。 编译后运行,Experts 里会按 Daily / Weekly / Monthly 顺序吐出三行,每行含对应周期的开盘、最高、最低、收盘。以 EURUSD 为例,若日线前一根收盘 1.08530,周线前一根收盘 1.08210,月线前一根收盘 1.07950,三行会原样打印,方便你一眼比对跨周期价位偏离。 另一个落地场景是四类均线(SMA / EMA / Smoothed / LWMA)同屏取值。CiMA 类用 MAType 记类型名、MAArray 存序列、MAHandle 接管指标句柄,OnInit 里对每种类型调 iMA 拿句柄、ArraySetAsSeries 置序、CopyBuffer 取缓冲,再 NormalizeDouble 规整精度后打印。跑完同样出四行,分别对应四种均线的当前值。 别把正态当圣经:类只是组织代码的容器,不代表信号更准。外汇和贵金属波动受消息驱动,多周期同看只能提高观测效率,入场仍须自担高风险。
class CPrices { class="kw">public: class="type">class="kw">string timeFrame; class="type">class="kw">double open; class="type">class="kw">double high; class="type">class="kw">double low; class="type">class="kw">double close; class="type">void pricesPrint() { Print(timeFrame," Prices = Open: ",open," - ","High: ",high,"-","Low: ",low,"-","Close: ",close); } }; CPrices CDailyPrices; CPrices CWeeklyPrices; CPrices CMonthlyPrices; class="type">int OnInit() { class=class="str">"cmt">//--- Daily time frame CDailyPrices.timeFrame="Daily"; CDailyPrices.open=(iOpen(Symbol(),PERIOD_D1,class="num">1)); CDailyPrices.high=(iHigh(Symbol(),PERIOD_D1,class="num">1)); CDailyPrices.low=(iLow(Symbol(),PERIOD_D1,class="num">1)); CDailyPrices.close=(iClose(Symbol(),PERIOD_D1,class="num">1)); CDailyPrices.pricesPrint(); class=class="str">"cmt">//--- Weekly time frame CWeeklyPrices.timeFrame="Weekly"; CWeeklyPrices.open=(iOpen(Symbol(),PERIOD_W1,class="num">1)); CWeeklyPrices.high=(iHigh(Symbol(),PERIOD_W1,class="num">1)); CWeeklyPrices.low=(iLow(Symbol(),PERIOD_W1,class="num">1)); CWeeklyPrices.close=(iClose(Symbol(),PERIOD_W1,class="num">1)); CWeeklyPrices.pricesPrint(); class=class="str">"cmt">//--- Monthly time frame CMonthlyPrices.timeFrame="Monthly"; CMonthlyPrices.open=(iOpen(Symbol(),PERIOD_MN1,class="num">1)); CMonthlyPrices.high=(iHigh(Symbol(),PERIOD_MN1,class="num">1)); CMonthlyPrices.low=(iLow(Symbol(),PERIOD_MN1,class="num">1)); CMonthlyPrices.close=(iClose(Symbol(),PERIOD_MN1,class="num">1)); CMonthlyPrices.pricesPrint(); class="kw">return(INIT_SUCCEEDED); } class CiMA { class="kw">public: class="type">class="kw">string MAType; class="type">class="kw">double MAArray[]; class="type">int MAHandle;
「四类均线在 OnInit 里的实例化与取值」
把 SMA、EMA、SMMA、LWMA 同时挂到当前图表,核心就是给 CiMA 结构分别配类型名、指标句柄和数组容器,然后在 OnInit 里各取一次前值。下面这段实现用 10 周期、收盘价、零偏移,把四种算法的值一次性打印出来,方便你比对同周期下不同平滑逻辑的差。 代码先用结构体包了 MAType 和 MAValue 两个字段,valuePrint() 只做一件事:把均线类型和当前值用 Print 打到日志。真正干活的是 OnInit——每种均线都走同样的五步:设类型名、iMA 拿句柄、数组倒序、CopyBuffer 取 3 根、取索引 1 并按品种精度裁剪。 注意 CopyBuffer 第三个参数填 0、长度填 3,代表从最新 K 线往前抓 3 个值;MAArray[1] 就是倒数第二根收盘价对应的均线值,不是实时最新。外汇和贵金属波动大,这种 OnInit 单次取值只反映加载瞬间的状态,重算要靠 OnTick 或定时器驱动。 跑完这段,MT5 日志会按顺序吐出 Simple MA / Exponential MA / Smoothed MA / Linear-weighted MA 四行 Current Value。若你把周期从 10 改成 20,四种均线在同样行情下的分离度通常会更明显,可据此判断哪类均线对拐点更迟钝。
class="type">class="kw">double MAValue; class="type">void valuePrint() { Print(MAType," Current Value: ",MAValue); }; }; class=class="str">"cmt">//--- SMA CiMA CSma; CiMA CSmaHandle; CiMA CSmaArray; class=class="str">"cmt">//--- EMA CiMA CEma; CiMA CEmaHandle; CiMA CEmaArray; class=class="str">"cmt">//--- SMMA CiMA CSmma; CiMA CSmmaHandle; CiMA CSmmaArray; class=class="str">"cmt">//--- LWMA CiMA CLwma; CiMA CLwmaHandle; CiMA CLwmaArray; class="type">int OnInit() { class=class="str">"cmt">//--- SMA CSma.MAType="Simple MA"; CSmaHandle.MAHandle=iMA(_Symbol,PERIOD_CURRENT,class="num">10,class="num">0,MODE_SMA,PRICE_CLOSE); ArraySetAsSeries(CSmaArray.MAArray,true); CopyBuffer(CSmaHandle.MAHandle,class="num">0,class="num">0,class="num">3,CSmaArray.MAArray); CSma.MAValue=NormalizeDouble(CSmaArray.MAArray[class="num">1],_Digits); CSma.valuePrint(); class=class="str">"cmt">//--- EMA CEma.MAType="Exponential MA"; CEmaHandle.MAHandle=iMA(_Symbol,PERIOD_CURRENT,class="num">10,class="num">0,MODE_EMA,PRICE_CLOSE); ArraySetAsSeries(CEmaArray.MAArray,true); CopyBuffer(CEmaHandle.MAHandle,class="num">0,class="num">0,class="num">3,CEmaArray.MAArray); CEma.MAValue=NormalizeDouble(CEmaArray.MAArray[class="num">1],_Digits); CEma.valuePrint(); class=class="str">"cmt">//--- SMMA CSmma.MAType="Smoothed MA"; CSmmaHandle.MAHandle=iMA(_Symbol,PERIOD_CURRENT,class="num">10,class="num">0,MODE_SMMA,PRICE_CLOSE); ArraySetAsSeries(CSmmaArray.MAArray,true); CopyBuffer(CSmmaHandle.MAHandle,class="num">0,class="num">0,class="num">3,CSmmaArray.MAArray); CSmma.MAValue=NormalizeDouble(CSmmaArray.MAArray[class="num">1],_Digits); CSmma.valuePrint(); class=class="str">"cmt">//--- LWMA CLwma.MAType="Linear-weighted MA"; CLwmaHandle.MAHandle=iMA(_Symbol,PERIOD_CURRENT,class="num">10,class="num">0,MODE_LWMA,PRICE_CLOSE); ArraySetAsSeries(CLwmaArray.MAArray,true); CopyBuffer(CLwmaHandle.MAHandle,class="num">0,class="num">0,class="num">3,CLwmaArray.MAArray); CLwma.MAValue=NormalizeDouble(CLwmaArray.MAArray[class="num">1],_Digits); CLwma.valuePrint(); class="kw">return(INIT_SUCCEEDED); }
收束
把代码拆成小模块、用类去派生不同行为的实例,这套思路在 MT5 里能直接降低 EA 与指标的维护成本。前面给出的 indicatorClass.mq5(2.09 KB)和 priceClass.mq5(1.7 KB)就是现成的 OOP 骨架,复制进 MetaEditor 编译后就能看到封装带来的改动安全性。 外汇与贵金属杠杆交易风险高,OOP 只解决工程复杂度,不解决胜率;想验证文中写法,开 MT5 加载那两个文件、改一个访问修饰符看编译反馈最直观。 作者还写过 CMF、CHV、肯特纳通道等基于技术指标的 MQL5 系统文章,顺着同一套类结构读下去,比单篇零散样例更容易拼出可复用的交易工具链。