更好的程序员(第 06 部分):9 个导致有效编码的习惯(基础篇)
「九个拖慢你 EA 开发进度的编码陋习」
在 MT5 里写 EA 或指标,真正卡住进度的往往不是算法难,而是几个反复出现的编码习惯。根据 2022 年 1 月社区一篇阅读量 1427 的实操梳理,有 9 类习惯最容易导致有效编码被打断:硬编码魔法数字、忽视 OnTick 与 OnCalculate 的调用频率差异、把打印调试当常规日志、不做输入参数校验、用全局变量代替类成员、循环里重复调用 iMA 等序列函数、不处理历史数据不足返回空值、注释与逻辑脱节、以及不区分测试环境与实盘环境。 这些习惯单独看都不致命,但叠加之后会让回测和实盘行为不一致。比如 iMA 在每根 tick 里重复取句柄,不仅拖慢执行,还可能在点差跳变时返回意料外的缓冲偏移。 打开你最近一个 EA 的 OnTick,搜一下是否在循环内调用了指标取数函数,如果有,先把它提到循环外缓存句柄,这是当天就能验证的一个改法。外汇与贵金属杠杆交易高风险,改动后请在策略测试器用 2021 年 XAUUSD 数据跑一遍确认。
◍ 动键盘前先把纪律钉死
很多交易者写 EA 时都有过这种状态:手指在敲,脑子已飘,回头一看三小时只写出一段逻辑还全是坑。低效的根源通常不是不会写,而是缺计划、无节奏、被消息弹窗切碎注意力。 动手前先列颗粒化清单:把「读取均线斜率」「判断影线占比」「发通知」拆成单用途小函数,每个函数写完立刻用 Print 自测,而不是堆到终盘才编译。给半年后的自己留注释,不是写「这是循环」,而是写「这里用 iBarShift 是为了避开离线图重绘错位」。 盲打和版本控制是隐性杠杆。盲打省下的视线往返时间按一次 EA 迭代 2000 次击键算,可能省出 15% 工时;Git 提交记录让你在参数改崩时能回退到「上周还能跑」的版本。MT5 自带的 MetaEditor 配合外部版本工具即可,不挑贵贱,顺手就行。 外汇与贵金属杠杆高、滑点随机,任何自写代码未经实盘前都只是概率玩具。上面这些习惯不保证盈利,但能让你在下次重看代码时少骂两句自己。
动手写EA前先把逻辑落到纸面
随便问 100 个程序员,在敲键盘前真把系统规划清楚的,恐怕不到 10 个。做 MQL5 开发也是一样,没想明白就写代码,后期改结构的成本远高于前期列清楚。 你可以把它当成上车前得知道去哪——跳上公交乱坐不可能到目的地。动手前先记下来:要写的程序类型、要用到的工具、打算调用的函数库。自由职业平台里的「工作规范」本质就是这份规划,不特殊,但必须有。 手边没纸笔就用 Word 或 WPS 画流程图,把开仓条件、资金规则、调用的标准类库先描一遍。查尔斯·凯特林那句「问题说清楚就解决了一半」放在 EA 开发里很贴切:模糊的念头直接写代码,基本会卡在半路。 举个最小例子,只想做一个带 Labouchere 资金管理的网格 EA,初始值先这样定(均可后续优化):Lotsize=0.01、xgrid=100 点、LotIncrement=0.01。规则写明白后,代码只是翻译:无持仓时双向各开一单;买条件为无买仓或现 Bid 低于末笔买仓开仓价减 xgrid,手数=0.01+0.01×买仓数;卖条件为无卖仓或 Ask 高于末笔卖仓开仓价加 xgrid,手数同理累加;默认 SL=0、TP=100。配标准仓位/品种/交易类库。 这样一份规划摆着,写代码时不会漏分支,也方便回头查自己哪块 MQL5 类库还不熟。外汇与贵金属杠杆高、网格策略回撤风险大,规划归规划,真跑之前务必在 MT5 策略测试器用历史数据验过。
「把网格逻辑收进一个 mqh 模块」
写网格类 EA 时,按持仓类型统计笔数、取最后一笔同类型持仓的开仓价,这两个动作几乎每个网格程序都要用。与其在每一个 .mq5 里重写一遍,不如抽成一个 gridmodule.mqh 包含文件,主程序用 #include 挂进来就能复用。 这么做最直接的回报是调试面缩小:函数逻辑只在一处改,主文件代码量明显下降。以一个典型网格 EA 为例,抽出这两函数后,主文件里至少能少写 30~50 行重复代码。 下面这段是 gridmodule.mqh 的骨架,已经把持仓信息类和品种类挂进来了,并声明了 CGrid 类。注意 MagicNumber 放在 protected,外部只能通过 InitializeModule(int magic) 注入,避免乱改标识。 别把模块写成万能库 只放真正跨 EA 通用的两块逻辑,塞太多无关函数反而让编译依赖变重,MT5 里追调用栈也更费劲。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| gridmodule.mqh | class=class="str">"cmt">//| Copyright class="num">2021, Omega Joctan | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2021, Omega Joctan" class="macro">#class="kw">property link "[MQL5官方文档] class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Libraries | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include <Trade\PositionInfo.mqh> class="macro">#include <Trade\SymbolInfo.mqh> CSymbolInfo m_symbol; CPositionInfo m_position; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| DLL imports | class=class="str">"cmt">//+------------------------------------------------------------------+ class CGrid { class="kw">protected: class="type">int MagicNumber; class="kw">public: CGrid(class="type">void); ~CGrid(class="type">void); class="type">void InitializeModule(class="type">int magic) { MagicNumber = magic; } class="type">class="kw">double LastPositionOpenPrice(class="type">ENUM_POSITION_TYPE type); class="type">int CountPositions(class="type">ENUM_POSITION_TYPE type); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Constructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CGrid::CGrid(class="type">void) { } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Destructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CGrid :: ~CGrid(class="type">void) { } class=class="str">"cmt">//+------------------------------------------------------------------+