手工制图表与交易工具箱:从智能交易系统到指标的进化与全新绘图工具(基础篇)
(1/3)· 当 40 个图表同时跑快捷键逻辑,终端卡顿只是时间问题,本篇拆解函数库重构与交叉线工具
MT5 里手动画工具也能被代码接管
MetaTrader 5 自带的手动图表工具(趋势线、矩形、斐波那契)靠人拖拽,重复劳动多且容易漏。第三部分的思路是:把这些手工动作封装成脚本和指标,让优化后的工具自动落位。
- 年 1 月 24 日那篇示例文章在站内有 2932 次浏览、5 条评论,说明手动工具自动化这个方向实操者不少。它本质是用 MQL5 把「人眼识别 + 鼠标绘制」两步压缩成「条件触发 + 对象创建」。
外汇和贵金属杠杆高、滑点随机,任何自动绘图只解决「画得准」,不解决「看得对」。先在 MT5 策略测试器外开图表手动比对,确认触发逻辑再谈批量使用。
「快捷键函数库的五个模块分工」
这套快捷键函数库目前由五个 .mqh 文件构成,不是单文件黑盒,而是按职责拆开维护。主文件 Shortcuts.mqh 是唯一需要被 EA 或指标直接 include 的入口,它内部再逐级包含其余文件并完成初始化,击键逻辑也写在这里。 GlobalVariables.mqh 集中放全局变量,主要管线条颜色、线宽、延伸系数这类显示参数;Mouse.mqh 定义鼠标移动处理类,记录光标当前的像素坐标和“价格-时间”坐标,以及正处于哪条主线。 Utilites.mqh 放辅助函数,算柱线极值、指标线交点等不一定直接绘图但能判断图形状态的量;Graphics.mqh 则依据前面文件的数据真正画图,它的主函数会被 Shortcuts.mqh 调用。作者坦言分组并不严格,部分计算就夹在绘图函数里,当前结构够用,以后可能重排。 一个可验证的落点是:在 MT5 里新建指标,只需 #include <Shortcuts.mqh> 这一个文件,其余四个由它自动拉入,你能直接看到五个文件协同跑通。
◍ 只处理活动图表就够用了
最初函数库用智能交易系统跑快捷键,是因为每个 EA 在独立线程里运行,多图表挂载时终端不易卡。但 EA 本意是交易,这个程序不下一单,反而挡着用户在图表上跑别的 EA,于是改成了指标。 改成指标后马上遇到性能疑点:如果用户开了 40 个甚至更多图表选项卡,所有图表同时响应击键,键盘检查会拖垮终端。后来想通了——没必要全图都处理,击键只在当前活动窗口发生,非活动图表直接跳过即可。 上面代码块里高亮的那段就是落地办法:函数一进来先调 ChartGetInteger(0, CHART_BRING_TO_TOP),若返回 false 说明图表不在最前,直接 return。我没测过精确的百分比提速,但用过大量选项卡的真实用户反馈,转成指标后响应反而更跟手。 这个库本身只干偶发活儿,不在每次 tick 里被调用,资源占用时间很短。真正吃资源的是极值搜索函数,目前没找到更快写法,不过它只在画直线等少数造型时触发,频率低到可以忽略。其余函数调用更少、速度够快,不在优化讨论范围内。
class=class="str">"cmt">/* Shortcuts.mqh */ class="type">void CShortcuts::OnChartEvent( class="kw">const class="type">int id, class="kw">const class="type">long &lparam, class="kw">const class="type">class="kw">double &dparam, class="kw">const class="type">class="kw">string &sparam ) { class=class="str">"cmt">//... if(ChartGetInteger(class="num">0,CHART_BRING_TO_TOP)==class="kw">false) { class="kw">return; } class=class="str">"cmt">//...
把绘图类从外部文件里解耦出来
早先那版工具库图省事,全局设定直接写死,鼠标类也被一堆实用函数绑死。真要拿到不碰鼠标、不画图形的新项目里复用,还得把全局文件和鼠标类一起拽进去,纯属累赘。 这次重构的思路很直接:全局变量留着不动,但在主类里加一份私有字段副本,再配一对公开的 get/set 函数读写。上面那段 MQL5 片段就是样板——m_TrendLengthCoefficient 作为 static 私有字段,外部通过同名的 TrendLengthCoefficient() 重载来取或改。 代价是写起来啰嗦,光把现有设定都包一遍就挺磨人。但换来四个实在好处:类不再依赖外部文件,用哪个变量设哪个;运行时能改参,比如画扇形线时每条线长度是前一条两倍、角度岔开,每次绘制前重设 TrendLengthCoefficient 即可;内部数据可随意分组,对外接口不变;数据不一定驻内存,可以扔终端全局变量、文件甚至库里,按别的参数动态算。 重构完之后,鼠标类和实用类各自独立,绘图类只耦合这两者加一个派发器。外汇与贵金属自动化工具高风险,但这套解耦让你在 MT5 里把实用文件原样拖进别的项目验证复用,省掉不少接线活。
<span class="keyword">class="kw">private</span>: <span class="comment">class=class="str">"cmt">/* Fields */</span> <span class="comment">class=class="str">"cmt">//---</span> <span class="keyword">class="kw">static</span> <span class="keyword">class="type">int</span> m_TrendLengthCoefficient; <span class="keyword">class="kw">public</span>: <span class="comment">class=class="str">"cmt">/* Methods */</span> <span class="comment">class=class="str">"cmt">//---</span> <span class="keyword">class="kw">static</span> <span class="keyword">class="type">int</span> TrendLengthCoefficient(<span class="keyword">class="type">void</span>) {<span class="keyword">class="kw">return</span> m_TrendLengthCoefficient;} <span class="comment">class=class="str">"cmt">//---</span> <span class="keyword">class="kw">static</span> <span class="keyword">class="type">void</span> TrendLengthCoefficient(<span class="keyword">class="type">int</span> _coefficient) {m_TrendLengthCoefficient=_coefficient;}
「用 X 键在任意位置打交叉线」
老版本函数库画趋势线时,末端会带一条由垂直线和水平线拼出的交叉线,用来读时间和价位。想把它放到图表任意点,得先按 H 再按 I,两步操作在盯盘时有点碍事。 新版本直接给了独立的交叉线工具:光标移到目标位,按 X 键就生成。底层没啥魔法,就是调了之前文章里写过的指示线绘制函数,坐标逻辑也简单——传了参数就用传的值,没传就抓鼠标指针的当前位置。 代码里 DrawCross 的默认参数是 _time=-1、_price=-1,命中缺省就走 CMouse::Time() 和 CMouse::Price() 取实时光标位;否则价格还会过一道 NormalizeDouble(_price,Digits()) 对齐品种小数位。最后两行 DrawSimple 分别画 OBJ_HLINE 和 OBJ_VLINE,交叉线就出来了。 触发靠 Shortcuts.mqh 里的按键事件:全局变量 Cross_Key 默认赋 "X",ChartEvent 收到 KEYDOWN 且键值匹配时调 m_graphics.DrawCross()。想换键位的,改 input string Cross_Key 即可,外汇和贵金属波动剧烈,这类辅助线只帮定位、不预示方向。
class=class="str">"cmt">/* Graphics.mqh */ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Draws a crosshair at specified coordinates. If the coordinates | class=class="str">"cmt">//| are not set, the mouse pointer coordinates are used. | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Parameters: | class=class="str">"cmt">//| class="type">class="kw">datetime _time - crosshair time | class=class="str">"cmt">//| class="type">class="kw">double _price - price level | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CGraphics::DrawCross(class="type">class="kw">datetime _time=-class="num">1,class="type">class="kw">double _price=-class="num">1) { class="type">class="kw">datetime time; class="type">class="kw">double price; class=class="str">"cmt">//--- if(_time==-class="num">1) { time=CMouse::Time(); } else { time=_time; } if(_price==-class="num">1) { price=CMouse::Price(); } else { price=NormalizeDouble(_price,Digits()); } DrawSimple(OBJ_HLINE,time,price); DrawSimple(OBJ_VLINE,time,price); } class=class="str">"cmt">/* GlobalVariables.mqh */ class=class="str">"cmt">// ... class="kw">input class="type">class="kw">string Cross_Key="X"; class=class="str">"cmt">// Crosshair where the mouse was clicked class=class="str">"cmt">// ... class=class="str">"cmt">/* Shortcuts.mqh */ class="type">void CShortcuts::OnChartEvent( class=class="str">"cmt">/* ... */ ) class="kw">switch(id) { case CHARTEVENT_KEYDOWN: class=class="str">"cmt">// ... class=class="str">"cmt">//--- Draw a crosshair if(CUtilites::GetCurrentOperationChar(Cross_Key) == lparam) { m_graphics.DrawCross(); } }
◍ 用 Q 键在 MT5 上拉任意极值趋势线
在 MT5 图表里,除了按固定左右柱数取极值画线,还能用 Q 键(对应外部变量 Free_Line_Key)直接以任意极值拉一条指示线。按一次 Q 进入绘制模式并标出第一个点;若点的位置不对,再按 Q 可取消,不会留下半截线。 确认第一点后,在第二个极值附近单击,标记消失、趋势线成型。这条线分两步完成,模式状态由类内 m_Clicks_Count 区分首次按键和二次单击,存活期与对象实例一致。 Q 线参数独立于 T 模式:比如 T 可设 4px 粗体、长度为极值间距四倍,Q 则能设细线、长度为间距两倍,互不干扰。若手绘线在 D1 与极值重合,切到 H4 往往左缘偏移——因为当日极值不总在日 K 开盘。该程序用 DeepPointSearch 函数在绘制时自动按小周期校正,省去手动切图调边的麻烦,盯 20 个以上图表时尤其减负。 外汇与贵金属波动剧烈、杠杆风险高,这类辅助画线只帮你定位概率性结构,不等于方向确认。打开 MT5 把 Free_Line_Key 设成习惯按键,按 Q 再单击就能验证这套两步绘制逻辑。
class=class="str">"cmt">/* Shortcuts.mqh */ class="type">void CShortcuts::OnChartEvent( class="kw">const class="type">int id, class="kw">const class="type">long &lparam, class="kw">const class="type">class="kw">double &dparam, class="kw">const class="type">class="kw">string &sparam ) { class=class="str">"cmt">//... class="kw">switch(id) { class=class="str">"cmt">//... case CHARTEVENT_KEYDOWN: if(CUtilites::GetCurrentOperationChar(Free_Line_Key) == lparam) { m_graphics.ToggleFreeLineMode(); class=class="str">"cmt">// 切换任意线绘制模式(开/关) if(m_graphics.IsFreeLineMode()){ class=class="str">"cmt">// 若模式已开启 m_graphics.DrawFreeLine(CMouse::Bar(),CMouse::Above()); class=class="str">"cmt">// 以当前鼠标位画第一点标记 } } class=class="str">"cmt">//... class=class="str">"cmt">/* Shortcuts.mqh */ class=class="str">"cmt">//... case CHARTEVENT_CLICK: ChartClick_Handler(); class=class="str">"cmt">// 图表单击统一进处理函数 break; class=class="str">"cmt">//... } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Processing a click on a free chart field | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CShortcuts::ChartClick_Handler() { class=class="str">"cmt">//--- if(m_graphics.IsFreeLineMode()){ class=class="str">"cmt">// 仅当处于自由线模式才响应 m_graphics.DrawFreeLine( CMouse::Bar(),CMouse::Above() class=class="str">"cmt">// 取单击处柱号与价格,完成第二点并连线 ); } } class=class="str">"cmt">/* Graphics.mqh */ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Draws a line by arbitrary specified extrema. In the current | class=class="str">"cmt">//| implementation, the first extremum is set by a hot key | class=class="str">"cmt">//| (Q by class="kw">default), the second is set by clicking near the | class=class="str">"cmt">//| required top | class=class="str">"cmt">//+------------------------------------------------------------------+