为 MetaTrader 打造的高级 EA 构造器 - botbrains·进阶篇
「用可视化逻辑拼出双均线交叉机器人」
在 BotBrains 编辑器里搭一套均线交叉策略,不需要手写一行 MQL5。先建 5 个常量:slow_ma_period=60、fast_ma_period=30、symbol(如 MGCV21)、admin_id(Telegram 用户 ID)、lot 手数。再建 4 个变量存快慢均线的当前与前一根柱值:ma_slow、ma_fast、ma_slow_prev、ma_fast_prev,变量不赋初值,新报价到来时由模块刷新。 检测交叉靠 New Tick 事件拉起 4 个 Set variable complex value,里面嵌 Moving Average 模块。该模块 6 个参数里,shift=0 取最后一根柱、shift=1 取前一根柱;用 period 区分 30/60,就能把当前和前一根的快慢均线值分别写进 4 个变量。 为确认取值没错,拖一个 Timer(默认 1 秒触发)连 4 个 Print debug info,每个里用 Value input(文本)+“+”+Variable select(数值)拼接输出。注意 BotBrains 所有值按数字存,必须给 Value input 套 Regular string 或给变量套 Fraction(小数位设 2)转字符串,否则编译报“数字到字符串隐式转换”警告。 多头开仓条件三选:ma_fast > ma_slow、ma_fast_prev < ma_slow_prev、当前持仓方向非买入。用 Position info 取持仓方向,接 Not + Equals + Position direction 判断。市价单模块填 symbol 常量、做多、volume 用 lot 常量。开仓后顺手接终端日志、警报、日志文件、延迟 1 秒播 Buy Signal、Telegram 发消息和截屏——后两项仅 Pro 可用,需 BotFather 拿 token 并填编辑器设置。 实测一分钟帧、随机周期跑 80 分钟开了 4 笔,通知和截屏都正常。但除首笔外每笔都成交两次(先平后开),因为默认最大成交频率是 10 秒 2 笔,机器人触频后直接清仓停摆。修法是建 full_lot 变量:启动时=full_lot 设 lot,首单后用 full_lot=lot*2,之后一次交易同时平旧开新。历史里可见第一笔 1 手、后续 2 手。外汇/贵金属波动剧烈,此类的可视化机器人务必先在演示账户跑稳再谈实盘。
◍ 把界面元素绑进均线交叉逻辑
在可视化编辑器里按住 CTRL 双击目标模块,先复制第一条虚线框的 ID,再打开名为 Modify interface element 的模块,把修改类型选成 Text,新文本字段填变量 ma_fast_prev。若写入的是数字,才需要顺手设小数位,否则留空即可。 其余三个 Modify interface element 模块照同样流程绑定 ma_fast、ma_slow、ma_slow_prev。新报价到来时,快慢均线值先落进这四个变量,再被刷到面板文本,虚线位置实时显示数值。 按钮动作靠两个 Market order 加一个 Close position 模块承接。复制首个 Market Order 的 ID,填进 Buy 按钮的 Linked block ID 参数,按下即调对应模块;另外两个按钮同理绑定。生成代码丢进终端,编辑器里的界面会完整迁移,按钮可触发、文本随行情变。 BotBrains 里搭这套机器人约几分钟,代码生成不到一秒。外汇与贵金属波动剧烈,实盘前务必先在模拟环境验证按钮与文本联动是否如预期。
<span class="comment">class=class="str">"cmt">/********** <ROBOT CONSTANTS> **********/</span> <span class="keyword">class="kw">const</span> <span class="keyword">class="type">class="kw">double</span> __slow_ma_period = user_value(<span class="class="type">class="kw">string">"class="num">60"</span>); <span class="keyword">class="kw">const</span> <span class="keyword">class="type">class="kw">double</span> __fast_ma_period = user_value(<span class="class="type">class="kw">string">"class="num">30"</span>); <span class="keyword">class="kw">const</span> <span class="keyword">class="type">class="kw">double</span> __symbol = user_value(<span class="class="type">class="kw">string">"MGCG22"</span>); <span class="keyword">class="kw">const</span> <span class="keyword">class="type">class="kw">double</span> __admin_id = user_value(<span class="class="type">class="kw">string">"class="num">744875082"</span>); <span class="keyword">class="kw">const</span> <span class="keyword">class="type">class="kw">double</span> __lot = user_value(<span class="class="type">class="kw">string">"class="num">1"</span>); <span class="comment">class=class="str">"cmt">/********** </ROBOT CONSTANTS> **********/</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| moving_average_cross_en.mq5 |</span> <span class="comment">class=class="str">"cmt">//| botbrains.app |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="preprocessor">class="macro">#include </span><botbrains_constants.mqh> <span class="preprocessor">class="macro">#include </span><botbrains_lib.mqh> <span class="comment">class=class="str">"cmt">/********** <ROBOT CONSTANTS> **********/</span> <span class="keyword">class="kw">const</span> <span class="keyword">class="type">class="kw">double</span> __slow_ma_period = user_value(<span class="class="type">class="kw">string">"class="num">60"</span>); <span class="keyword">class="kw">const</span> <span class="keyword">class="type">class="kw">double</span> __fast_ma_period = user_value(<span class="class="type">class="kw">string">"class="num">30"</span>); <span class="keyword">class="kw">const</span> <span class="keyword">class="type">class="kw">double</span> __symbol = user_value(<span class="class="type">class="kw">string">"MGCG22"</span>); <span class="keyword">class="kw">const</span> <span class="keyword">class="type">class="kw">double</span> __admin_id = user_value(<span class="class="type">class="kw">string">"class="num">744875082"</span>); <span class="keyword">class="kw">const</span> <span class="keyword">class="type">class="kw">double</span> __lot = user_value(<span class="class="type">class="kw">string">"class="num">1"</span>); <span class="comment">class=class="str">"cmt">/********** </ROBOT CONSTANTS> **********/</span> <span class="comment">class=class="str">"cmt">/********** <ROBOT VARIABLES> **********/</span> <span class="keyword">class="type">class="kw">double</span> __ma_slow = user_value(<span class="class="type">class="kw">string">""</span>); <span class="keyword">class="type">class="kw">double</span> __ma_fast = user_value(<span class="class="type">class="kw">string">""</span>); <span class="keyword">class="type">class="kw">double</span> __ma_slow_prev = user_value(<span class="class="type">class="kw">string">""</span>); <span class="keyword">class="type">class="kw">double</span> __ma_fast_prev = user_value(<span class="class="type">class="kw">string">""</span>); <span class="keyword">class="type">class="kw">double</span> __full_lot = user_value(<span class="class="type">class="kw">string">""</span>); <span class="comment">class=class="str">"cmt">/********** </ROBOT VARIABLES> **********/</span> <span class="keyword">class="type">int</span> <span class="functions">OnInit</span>(){ <span class="comment">class=class="str">"cmt">//Is autotrading allowed:</span> <span class="keyword">if</span>(!<span class="functions">TerminalInfoInteger</span>(<span class="macro">TERMINAL_TRADE_ALLOWED</span>)){ <span class="functions">MessageBox</span>(<span class="class="type">class="kw">string">"Autotrading is not allowed, expert will be removed"</span>); <span class="functions">ExpertRemove</span>(); <span class="keyword">class="kw">return</span>(-<span class="number">class="num">1</span>); } <span class="comment">class=class="str">"cmt">//Is trading on live account allowed:</span> <span class="keyword">if</span>(<span class="functions">AccountInfoInteger</span>(<span class="macro">ACCOUNT_TRADE_MODE</span>) == <span class="macro">ACCOUNT_TRADE_MODE_REAL</span>){ <span class="functions">MessageBox</span>(<span class="class="type">class="kw">string">"Expert is not allowd to trade on live account!"</span>); <span class="functions">ExpertRemove</span>(); <span class="keyword">class="kw">return</span>(-<span class="number">class="num">1</span>); } <span class="comment">class=class="str">"cmt">//Set robot name:</span> set_robot_name(<span class="class="type">class="kw">string">"moving_average_cross_en"</span>); <span class="comment">class=class="str">"cmt">//Set license key:</span> set_license_key(<span class="class="type">class="kw">string">"2L5J7K-K986ND-KMPT94-1Q"</span>); <span class="comment">class=class="str">"cmt">//Set language:</span>
初始化阶段如何铺好面板与风控阈值
EA 在 OnStart 里第一件事是把语言锁成英文,并调用 generate_magic() 生成独立魔术码,避免和多开的其它机器人订单混淆。紧接着 set_init_account_balance() 记下初始权益,作为后续资金曲线比对的基准线。 set_suspicous_deals_frequency(60,3,false,true) 与 set_max_deals_frequency(10,2,false,true) 这两行把“可疑成交频率”和“单周期最大成交频率”钉死:窗口 60 秒内超过 3 次判可疑,10 秒内超过 2 次直接限流。外汇与贵金属杠杆高,这类频率闸门能降低错单雪崩的概率。 EventSetTimer(1) 把定时器切成 1 秒心跳,之后 block_bYi6ikfde()、block_bYUS6GLT0() 跑自定义启动块。下面那段 create_* 函数群才是真活:用 15/15 坐标画了 390x195 的深色矩形底框,左上角标题写着 "MA Cross" trading robot,并在 30/165、195/165 分别生成绿 Buy、红 Sell 按钮。 想验证就开 MT5 新建 EA,把这段原样贴进 OnStart,编译后拖到图表,能看到左上角面板即说明坐标与 CORNER_LEFT_UPPER 锚点生效;按钮颜色 C'51,255,0' / C'255,51,0' 对应 RGB,改数值即可换肤。
set_lang("en"); class=class="str">"cmt">//Generate robot magic number: generate_magic(); class=class="str">"cmt">//Set initial trading account balance: set_init_account_balance(); class=class="str">"cmt">//Set suspicious deals frequency params: set_suspicous_deals_frequency(class="num">60, class="num">3, class="kw">false, true); class=class="str">"cmt">//Set maximal deals frequency params: set_max_deals_frequency(class="num">10, class="num">2, class="kw">false, true); class=class="str">"cmt">//Set the timer with an interval of class="num">1 second: EventSetTimer(class="num">1); class=class="str">"cmt">//Blocks executed when the robot is launched: block_bYi6ikfde(); block_bYUS6GLT0(); class=class="str">"cmt">//Create interface elements: create_rectangle("b1ELCu5iq", class="num">0, class="num">0, CORNER_LEFT_UPPER, class="num">15, class="num">15, class="num">390, class="num">195, C&class="macro">#x27;class="num">20,class="num">20,class="num">20&class="macro">#x27;, BORDER_FLAT, STYLE_SOLID, C&class="macro">#x27;class="num">10,class="num">191,class="num">254&class="macro">#x27;, class="num">2, class="num">0, class="kw">false, class="kw">false, class="kw">false); create_button("b4rh5uKlb", class="num">0, class="num">0, CORNER_LEFT_UPPER, class="num">195, class="num">165, class="num">150, class="num">30, "Sell", "Ubuntu Mono", class="num">8, C&class="macro">#x27;class="num">255,class="num">255,class="num">255&class="macro">#x27;, C&class="macro">#x27;class="num">20,class="num">20,class="num">20&class="macro">#x27;, C&class="macro">#x27;class="num">255,class="num">51,class="num">0&class="macro">#x27;, class="kw">false, class="num">0, class="kw">false, class="kw">false, class="kw">false); create_text("b5BGSldua", class="num">0, class="num">0, CORNER_LEFT_UPPER, class="num">120, class="num">135, "-", "Ubuntu Mono", class="num">9, C&class="macro">#x27;class="num">255,class="num">255,class="num">255&class="macro">#x27;, class="num">0, ANCHOR_LEFT_UPPER, class="num">0, class="kw">false, class="kw">false, class="kw">false); create_text("b9EjNpibO", class="num">0, class="num">0, CORNER_LEFT_UPPER, class="num">30, class="num">30, "\"MA Cross\" trading robot", "Ubuntu Mono", class="num">14, C&class="macro">#x27;class="num">255,class="num">255,class="num">255&class="macro">#x27;, class="num">0, ANCHOR_LEFT_UPPER, class="num">0, class="kw">false, class="kw">false, class="kw">false); create_button("bBTrBQlkS", class="num">0, class="num">0, CORNER_LEFT_UPPER, class="num">30, class="num">165, class="num">150, class="num">30, "Buy", "Ubuntu Mono", class="num">8, C&class="macro">#x27;class="num">255,class="num">255,class="num">255&class="macro">#x27;, C&class="macro">#x27;class="num">20,class="num">20,class="num">20&class="macro">#x27;, C&class="macro">#x27;class="num">51,class="num">255,class="num">0&class="macro">#x27;, class="kw">false, class="num">0, class="kw">false, class="kw">false, class="kw">false); create_text("bEncRhDIR", class="num">0, class="num">0, CORNER_LEFT_UPPER, class="num">285, class="num">75, "Current bar:", "Ubuntu Mono", class="num">9, C&class="macro">#x27;class="num">255,class="num">255,class="num">255&class="macro">#x27;, class="num">0, ANCHOR_LEFT_UPPER, class="num">0, class="kw">false, class="kw">false, class="kw">false); create_text("bI0vadsS2", class="num">0, class="num">0, CORNER_LEFT_UPPER, -class="num">195, class="num">270, "Text", "Ubuntu Mono", class="num">9, C&class="macro">#x27;class="num">255,class="num">255,class="num">255&class="macro">#x27;, class="num">0, ANCHOR_LEFT_UPPER, class="num">0, class="kw">false, class="kw">false, class="kw">false);
「面板文字与按钮的初始化落点」
这段初始化代码在 MT5 图表左上角批量创建了只读标签和一颗关闭按钮,坐标以像素为单位,锚点统一用 ANCHOR_LEFT_UPPER。注意 MA slow 标签放在 (30,135),MA fast 在 (30,105),两者纵向差 30 像素,正好对应慢快两条均线的视觉分行。 create_text 的字体写死 Ubuntu Mono、字号 9、颜色 C'255,255,255'(纯白),最后一个 false 三元组分别代表不被鼠标选中、不随滚动、不高亮。X 按钮由 create_button 生成于 (360,165),尺寸 30x30,悬停色 C'255,51,0'(橙红),用来手动清场。 初始化末尾调 ChartRedraw() 强制重绘,并返回 INIT_SUCCEEDED;OnDeinit 里先用 Comment("") 清状态栏,再 remove_all_objects(0) 把图表对象全删,避免残影。OnTimer 以 1 秒节拍轮询四个 block 函数,且用取模判断:每 10 秒重置最大交易频率计数器,每 60 秒重置可疑交易频率计数器——外汇与贵金属波动剧烈,这类频率闸口只是概率层面的风控,不保证避开滑点。 想验证就直接把这段贴进 EA 的 OnInit / OnDeinit / OnTimer,编译后开一个 XAUUSD 的 M1 图,看左上角是否按上述坐标出现标签;若位置偏了,改 CORNER_LEFT_UPPER 下的 x/y 参数即可。
create_text("bK1cW1i6s", class="num">0, class="num">0, CORNER_LEFT_UPPER, class="num">30, class="num">135, "MA slow:", "Ubuntu Mono", class="num">9, C&class="macro">#x27;class="num">255,class="num">255,class="num">255&class="macro">#x27;, class="num">0, ANCHOR_LEFT_UPPER, class="num">0, class="kw">false, class="kw">false, class="kw">false); create_text("bLcIIkYqO", class="num">0, class="num">0, CORNER_LEFT_UPPER, class="num">285, class="num">135, "-", "Ubuntu Mono", class="num">9, C&class="macro">#x27;class="num">255,class="num">255,class="num">255&class="macro">#x27;, class="num">0, ANCHOR_LEFT_UPPER, class="num">0, class="kw">false, class="kw">false, class="kw">false); create_text("bSDWBsxbk", class="num">0, class="num">0, CORNER_LEFT_UPPER, class="num">285, class="num">105, "-", "Ubuntu Mono", class="num">9, C&class="macro">#x27;class="num">255,class="num">255,class="num">255&class="macro">#x27;, class="num">0, ANCHOR_LEFT_UPPER, class="num">0, class="kw">false, class="kw">false, class="kw">false); create_text("bTK8r1zb1", class="num">0, class="num">0, CORNER_LEFT_UPPER, class="num">30, class="num">105, "MA fast:", "Ubuntu Mono", class="num">9, C&class="macro">#x27;class="num">255,class="num">255,class="num">255&class="macro">#x27;, class="num">0, ANCHOR_LEFT_UPPER, class="num">0, class="kw">false, class="kw">false, class="kw">false); create_text("bhKcJ2pwx", class="num">0, class="num">0, CORNER_LEFT_UPPER, class="num">120, class="num">75, "Previous bar:", "Ubuntu Mono", class="num">9, C&class="macro">#x27;class="num">255,class="num">255,class="num">255&class="macro">#x27;, class="num">0, ANCHOR_LEFT_UPPER, class="num">0, class="kw">false, class="kw">false, class="kw">false); create_text("bj6MtjhZF", class="num">0, class="num">0, CORNER_LEFT_UPPER, class="num">120, class="num">105, "-", "Ubuntu Mono", class="num">9, C&class="macro">#x27;class="num">255,class="num">255,class="num">255&class="macro">#x27;, class="num">0, ANCHOR_LEFT_UPPER, class="num">0, class="kw">false, class="kw">false, class="kw">false); create_button("bympSPhAp", class="num">0, class="num">0, CORNER_LEFT_UPPER, class="num">360, class="num">165, class="num">30, class="num">30, "X", "Ubuntu Mono", class="num">8, C&class="macro">#x27;class="num">255,class="num">255,class="num">255&class="macro">#x27;, C&class="macro">#x27;class="num">20,class="num">20,class="num">20&class="macro">#x27;, C&class="macro">#x27;class="num">255,class="num">51,class="num">0&class="macro">#x27;, class="kw">false, class="num">0, class="kw">false, class="kw">false, class="kw">false); ChartRedraw(); class=class="str">"cmt">//Robot initialization was successful: class="kw">return(INIT_SUCCEEDED); } class="type">void OnDeinit(class="kw">const class="type">int reason){ Comment(""); PlaySound(NULL); class=class="str">"cmt">//Remove all graphical objects from the chart on which the robot was launched: remove_all_objects(class="num">0); } class="type">void OnTimer(){ class=class="str">"cmt">//Timer with class="num">1 sec. interval(bgP4YbxaQ): block_bTonyumSN(); block_bO7LNEs4m(); block_bS1JODjZj(); block_bM4s77Wvc(); class=class="str">"cmt">//Every class="num">10 sec. reset the counter of the performed deals number(max deals frequency): if(get_timer_tick_index() % class="num">10 == class="num">0){ deals_max_frequency_counter_reset(); } class=class="str">"cmt">//Every class="num">60 sec. reset the counter of the performed deals number(suspicious deals frequency): if(get_timer_tick_index() % class="num">60 == class="num">0){
◍ 把均线和按钮事件接进主循环
这段截取的 MQL5 逻辑展示了 EA 如何把快慢均线计算、图表按钮点击与订单触发缝合到标准事件函数里。OnTick 里顺序调用三个 block 函数,意味着每来一个新 tick 就重算指标并跑条件判断;OnTrade 则专门查成交频率,避免短时间内刷单触发券商限制。 OnChartEvent 监听 OBJ_BUTTON 点击,对象名 b4rh5uKlb、bBTrBQlkS、bympSPhAp 分别对应不同 block:例如 bBTrBQlkS 直接走 block_bCa4uSC92 市价买入。点击后 Sleep(100) 再把按钮状态复位并 ChartRedraw,防止重复触发。 block_b0Wkfq6OD 用 moving_average 取当前周期收盘价 SMA,快线周期存 __ma_fast、慢线前一根值存 __ma_slow_prev(shift=1)。block_bHxbWWtwW 的入场条件是快线大于慢线且前一根快线低于前一根慢线,同时当前无 BUY 持仓——典型均线金叉过滤。 开 MT5 把这段接进自己的 EA 时,重点核对 __fast_ma_period 与 __slow_ma_period 实参;外汇和贵金属杠杆高,这类金叉逻辑在震荡市可能连续假突破,建议先用策略测试器跑 3 个月 tick 数据看成交分布。
deals_suspicious_frequency_counter_reset(); } timer_tick_index_increment(); } class="type">void OnTick(class="type">void){ class=class="str">"cmt">//Blocks executed when a new tick is received on the symbol on the chart of which the robot was launched: block_bHxbWWtwW(); block_bvIvs7SMe(); block_boHVNlqnO(); } class="type">void OnTrade(){ class=class="str">"cmt">//Check deals frequency: check_deals_frequency(); } class="type">void OnChartEvent( class="kw">const class="type">int id, class=class="str">"cmt">// event identificator class="kw">const class="type">long& lparam, class=class="str">"cmt">// event parameter of type class="type">long class="kw">const class="type">class="kw">double& dparam, class=class="str">"cmt">// event parameter of type class="type">class="kw">double class="kw">const class="type">class="kw">string& sparam class=class="str">"cmt">// event parameter of type class="type">class="kw">string ){ if(id == CHARTEVENT_OBJECT_CLICK){ class="type">class="kw">string object_name = sparam; if(ObjectGetInteger(class="num">0, object_name, OBJPROP_TYPE) == OBJ_BUTTON){ if(object_name == "b4rh5uKlb"){ block_bYGEhpZDM(); } if(object_name == "bBTrBQlkS"){ block_bCa4uSC92(); } if(object_name == "bympSPhAp"){ block_bXRrICVna(); } Sleep(class="num">100); ObjectSetInteger(class="num">0, object_name, OBJPROP_STATE, class="kw">false); ChartRedraw(); } } } class=class="str">"cmt">//Function of the block b0Wkfq6OD(set_complex_variable_value): class="type">void block_b0Wkfq6OD(){ vset(__ma_fast, ( moving_average(to_string(__symbol), PERIOD_CURRENT, (class="type">int)__fast_ma_period, MODE_SMA, PRICE_CLOSE, class="num">0) )); block_b6LYVQGej(); } class=class="str">"cmt">//Function of the block b1VYatoxs(interface_element_modify): class="type">void block_b1VYatoxs(){ modify_text("b5BGSldua", DoubleToString(__ma_slow_prev, class="num">2) ); } class=class="str">"cmt">//Function of the block b3UJfY74N(interface_element_modify): class="type">void block_b3UJfY74N(){ modify_text("bSDWBsxbk", DoubleToString(__ma_fast, class="num">2) ); } class=class="str">"cmt">//Function of the block b3crj4ayK(log): class="type">void block_b3crj4ayK(){ log_to_file("Open class="type">class="kw">short!"); block_bzVyOb4tW(); } class=class="str">"cmt">//Function of the block b6LYVQGej(set_complex_variable_value): class="type">void block_b6LYVQGej(){ vset(__ma_slow_prev, ( moving_average(to_string(__symbol), PERIOD_CURRENT, (class="type">int)__slow_ma_period, MODE_SMA, PRICE_CLOSE, class="num">1) )); block_bQMre45Bd(); } class=class="str">"cmt">//Function of the block bCa4uSC92(place_market_order): class="type">void block_bCa4uSC92(){ place_market_order(to_string(__symbol), "BUY", __lot); } class=class="str">"cmt">//Function of the block bHxbWWtwW(condition): class="type">void block_bHxbWWtwW(){ if (( __ma_fast > __ma_slow ) && ( __ma_fast_prev < __ma_slow_prev ) && ( get_position_info(to_string(__symbol), "POSITION_DIRECTION") != BUY_DIRECTION )){ block_bUySCvh6M(); } } class=class="str">"cmt">//Function of the block bM4s77Wvc(print_debug_info): class="type">void block_bM4s77Wvc(){ print_debug_info(( to_string(to_double("ma_fast_prev = ")) + DoubleToString(__ma_fast_prev, class="num">2) )); } class=class="str">"cmt">//Function of the block bMgaVnT74(pause): class="type">void block_bMgaVnT74(){ pause(class="num">1000); block_bT8xv0qGj(); } class=class="str">"cmt">//Function of the block bO7LNEs4m(print_debug_info):