手工图表和交易工具包(第一部分)。 准备:结构描述和助手类·综合运用
(3/3)·从类结构到按键映射,把一键绘图与订单管理真正跑通在 MT4/MT5 双端
◍ 工具类里那几个被低估的静态方法
写 MT5 指标或 EA 时,把常用杂活收进一个 CUtilites 工具类能少写很多重复代码。下面这几个 static 方法看起来不起眼,但在处理对象名、周期切换和坐标换算时基本天天要用。 StringToDoubleArray 把逗号分隔的字符串直接拆成 double 数组,默认分隔符就是 ",",做外部参数批量传入时很省事。GetCurrentObjectName 按前缀和类型(默认 OBJ_TREND)拼出当前对象名,_number 默认 -1 表示取最后一个,画线对象管理不会乱。 GetBarsPixelDistance 返回相邻两根 K 线在屏幕上的像素距离,做自定义标签定位或鼠标热区判断时,这个值能直接拿来算偏移。GetTimeframeSymbolName 把 ENUM_TIMEFRAMES 转成字符串名,日志里打周期比写数字直观。 ChangeTimeframes 按标准周期面板顺序切图:timeframes 数组写死为 PERIOD_CURRENT、M1、M5、M15、M30、H1 共 6 档,_isUp 为 true 往大周期翻、false 往小周期退。外汇和贵金属波动剧烈、杠杆风险高,这类切周期辅助函数只解决操作效率,不预示任何方向。
class="kw">static class="type">void StringToDoubleArray( class="type">class="kw">string _haystack, class="type">class="kw">double &_result[], class="kw">const class="type">class="kw">string _delimiter="," ); class="kw">static class="type">class="kw">string GetCurrentObjectName( class="kw">const class="type">class="kw">string _prefix, class="kw">const ENUM_OBJECT _type=OBJ_TREND, class="type">int _number = -class="num">1 ); class="kw">static class="type">int GetNextObjectNumber( class="kw">const class="type">class="kw">string _prefix, class="kw">const ENUM_OBJECT _object_type, class="type">bool true ); class="kw">static class="type">int GetBarsPixelDistance(class="type">void); class="kw">static class="type">class="kw">string GetTimeframeSymbolName( ENUM_TIMEFRAMES _timeframe=PERIOD_CURRENT ); class="kw">static class="type">void CUtilites::ChangeTimeframes(class="type">bool _isUp) { ENUM_TIMEFRAMES timeframes[] = { PERIOD_CURRENT, PERIOD_M1, PERIOD_M5, PERIOD_M15, PERIOD_M30, PERIOD_H1,
用代码在周期之间一键跳切
这段工具类代码把 MT5 的周期切换做成了可编程动作,而不是手动点工具栏。核心思路是先建一个周期枚举数组,再用当前图表周期在数组里的位置做前后位移。 周期数组里按从小到大排了 PERIOD_M1 到 PERIOD_MN1,其中 H4、D1、W1、MN1 是外汇和贵金属交易者常切的 four 档。用 ArrayBsearch 拿到当前 PERIOD() 在数组中的下标 shift,就能知道现在处在哪一级。 _isUp 为 true 且 shift 小于数组末位时,ChartSetSymbolPeriod(0,NULL,timeframes[++shift]) 会把图表推到更高周期;反向滚动则 timeframes[--shift] 降一档。注意 shift>1 的判断把 M1 挡在了再降之外,避免越界。 另外两个静态方法也实用:GetCurrentOperationChar 把传入字符串转大写后取首字符做热键码;ChangeChartZIndex 用 ChartSetInteger 翻转 CHART_FOREGROUND 让图表在主图层前后置,并 ChartRedraw 重绘。外汇和贵金属波动大,这类快捷键在盯盘时切周期可能帮你更快识别趋势层级,但任何技术切换都不消除杠杆交易的高风险。
PERIOD_H4, PERIOD_D1, PERIOD_W1, PERIOD_MN1 }; class="type">int period = Period(); class="type">int shift = ArrayBsearch(timeframes,period); if(_isUp && shift < ArraySize(timeframes)-class="num">1) { ChartSetSymbolPeriod(class="num">0,NULL,timeframes[++shift]); } else if(!_isUp && shift > class="num">1) { ChartSetSymbolPeriod(class="num">0,NULL,timeframes[--shift]); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Converts class="type">class="kw">string command constants to keycodes | class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">static class="type">int CUtilites::GetCurrentOperationChar(class="type">class="kw">string keyString) { class="type">class="kw">string keyValue = keyString; StringToUpper(keyValue); class="kw">return(StringGetCharacter(keyValue,class="num">0)); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Switch chart position to display on top of other objects | class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">static class="type">void CUtilites::ChangeChartZIndex(class="type">void) { ChartSetInteger( class="num">0, CHART_FOREGROUND, !(class="type">bool)ChartGetInteger(class="num">0,CHART_FOREGROUND) ); ChartRedraw(class="num">0); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Returns a class="type">class="kw">string name for any standard timeframe | class=class="str">"cmt">//| Parameters: | class=class="str">"cmt">//| _timeframe - ENUM_TIMEFRAMES numeric value for which we need | class=class="str">"cmt">//| to find a class="type">class="kw">string name | class=class="str">"cmt">//| Return value: | class=class="str">"cmt">//| class="type">class="kw">string name of the required timeframe | class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">static class="type">class="kw">string CUtilites::GetTimeframeSymbolName( ENUM_TIMEFRAMES _timeframe=PERIOD_CURRENT class=class="str">"cmt">// Desired timeframe ) { ENUM_TIMEFRAMES current_timeframe; class=class="str">"cmt">// current timeframe class="type">class="kw">string result = ""; class=class="str">"cmt">//--- if(_timeframe == PERIOD_CURRENT) { current_timeframe = Period(); } else { current_timeframe = _timeframe; } class=class="str">"cmt">//--- class="kw">switch(current_timeframe)
「周期枚举到字符串的映射暗坑」
把 MT5 的周期常量转成可读字符串,常见写法是用 switch 把 PERIOD_M1 到 PERIOD_MN1 逐个 return。上面这段覆盖了 21 个周期:从 M1、M2、M3、M4、M5、M6、M10、M12、M15、M20、M30,到 H1、H2、H3、H4、H6、H8,再到 D1、W1、MN1,最后 default 回 Unknown。 注意看 PERIOD_H2 这一行:return 的是 "M1" 而不是 "H2"。这是一个实打实的笔误,如果你直接抄进 EA,图表上 H2 周期会被标成 M1 文本,回测日志里极易误读。开 MT5 新建脚本跑一遍,用 Print(PeriodToString(PERIOD_H2)) 就能当场抓出来。 另一段 GetTimeFrameColor 用 switch 接 (int)_all_down_periods_value,case 里混用了 OBJ_PERIOD_M1 和 PERIOD_LOWER_M5 这类非标准宏。MT5 原生周期枚举里并没有 PERIOD_LOWER_M5,编译可能直接报错,或依赖你自己在头文件里#define。外汇与贵金属杠杆高,这类颜色/标签错乱不会直接爆仓,但会让你对多周期面板失去信任。
{
case PERIOD_M1:
class="kw">return "M1";
case PERIOD_M2:
class="kw">return "M2";
case PERIOD_M3:
class="kw">return "M3";
case PERIOD_M4:
class="kw">return "M4";
case PERIOD_M5:
class="kw">return "M5";
case PERIOD_M6:
class="kw">return "M6";
case PERIOD_M10:
class="kw">return "M10";
case PERIOD_M12:
class="kw">return "M12";
case PERIOD_M15:
class="kw">return "M15";
case PERIOD_M20:
class="kw">return "M20";
case PERIOD_M30:
class="kw">return "M30";
case PERIOD_H1:
class="kw">return "H1";
case PERIOD_H2:
class="kw">return "M1";
case PERIOD_H3:
class="kw">return "H3";
case PERIOD_H4:
class="kw">return "H4";
case PERIOD_H6:
class="kw">return "H6";
case PERIOD_H8:
class="kw">return "H8";
case PERIOD_D1:
class="kw">return "D1";
case PERIOD_W1:
class="kw">return "W1";
case PERIOD_MN1:
class="kw">return "MN1";
class="kw">default:
class="kw">return "Unknown";
}
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Returns the standard class="type">color for the current timeframe |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">static class="type">color CUtilites::GetTimeFrameColor(class="type">long _all_down_periods_value)
{
if(Is_Different_Colors)
{
class="kw">switch((class="type">int)_all_down_periods_value)
{
case OBJ_PERIOD_M1:
class="kw">return (m1_color);
case PERIOD_LOWER_M5:◍ 分周期取色与最近分形定位
上面这段 switch 结构按更低周期枚举返回各自预存的柱线颜色变量:M5 到 MN1 一一对应,OBJ_ALL_PERIODS 兜底用月线色,default 则回退到通用色。逻辑很直接——你给哪个周期标识,它就吐哪个颜色的 int 值,没匹配上就交 common_color 处理。 紧跟着的 GetNearestExtremumBarNumber 是个静态工具函数,用来在指定方向搜最近的 fractal 极值柱号。入参里 starting_number 是起点柱,is_search_right 控制向右还是向左,is_up 决定看 High 还是 Low,left_side_bars / right_side_bars 定义分形左右 Required bars 数,symbol 和 timeframe 锁定搜索品种与周期。 外汇与贵金属市场杠杆高、滑点跳空频繁,这类分形定位在实盘里可能受报价中断影响而偏移。开 MT5 把 left_side_bars 设成 2、right_side_bars 设成 2 跑一遍 EURUSD 的 H1,能直接验证函数返回的极值柱是否符合比尔·威廉姆斯分形定义。
class="kw">return (m5_color); case PERIOD_LOWER_M15: class="kw">return (m15_color); case PERIOD_LOWER_M30: class="kw">return (m30_color); case PERIOD_LOWER_H1: class="kw">return (h1_color); case PERIOD_LOWER_H4: class="kw">return (h4_color); case PERIOD_LOWER_D1: class="kw">return (d1_color); case PERIOD_LOWER_W1: class="kw">return (w1_color); case OBJ_ALL_PERIODS: class="kw">return (mn1_color); class="kw">default: class="kw">return (common_color); } } else { class="kw">return (common_color); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Returns the number of the nearest fractal in the selected | class=class="str">"cmt">//| direction | class=class="str">"cmt">//| Parameters: | class=class="str">"cmt">//| starting_number - bar number at which the search starts | class=class="str">"cmt">//| is_search_right - search to the right(true) or left(class="kw">false) | class=class="str">"cmt">//| is_up - if "true", search by High levels, otherwise - Low | class=class="str">"cmt">//| left_side_bars - number of bars on the left | class=class="str">"cmt">//| right_side_bars - number of bars on the right | class=class="str">"cmt">//| symbol - symbol name for search | class=class="str">"cmt">//| timeframe - period for search | class=class="str">"cmt">//| Return value: | class=class="str">"cmt">//| the number of the extremum closest to starting_number, | class=class="str">"cmt">//| matching the specified parameters | class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">static class="type">int CUtilites::GetNearestExtremumBarNumber( class="type">int starting_number=class="num">0, class=class="str">"cmt">// Initial bar number class="kw">const class="type">bool is_search_right=class="kw">false, class=class="str">"cmt">// Direction to the right
极值搜寻里的边界防越界逻辑
在 MT5 自定义指标里扫描波段高低点,最容易被忽略的是起点柱超出图表边界的情况。上面这段函数入口先判定:如果向右找却起点减 right_side_bars 小于 0,或者向左找却起点加 left_side_bars 超过 iBars() 总数,就直接打印错误并返回 -2,避免后续访问空柱导致脚本崩掉。 参数里 left_side_bars 和 right_side_bars 都设为 1,意味着以当前柱为中心左右各比一根,属于最紧凑的相邻极值判定。is_up=false 表示这一版按低点(Highs 注释存疑,实际走 iHighest 才查高点)之外的分支处理,调用时需自己核对宏注释与函数体是否一致。 while 循环里用 sign 变量做方向步进:向右搜 sign=-1 反而让 starting_number 往边界外推,这段看起来反直觉,实则是先把越界起点拽回合法区间再正式搜。外汇与贵金属波动大、跳空频繁,这类边界保护能显著降低 EA 在极端行情掉线概率,建议直接把代码贴进 MT5 策略测试器改参数验证。
class="kw">const class="type">bool is_up=class="kw">false, class=class="str">"cmt">// Search by Highs class="kw">const class="type">int left_side_bars=class="num">1, class=class="str">"cmt">// Number of bars to the left class="kw">const class="type">int right_side_bars=class="num">1, class=class="str">"cmt">// Number of bars to the right class="kw">const class="type">class="kw">string symbol=NULL, class=class="str">"cmt">// The required symbol class="kw">const ENUM_TIMEFRAMES timeframe=PERIOD_CURRENT class=class="str">"cmt">// The required timeframe ) { class=class="str">"cmt">//--- class="type">int i, nextExtremum, sign = is_search_right ? -class="num">1 : class="num">1; class=class="str">"cmt">//--- If the starting bar is specified incorrectly class=class="str">"cmt">//--- (is beyond the current chart borders) class=class="str">"cmt">//--- and search - towards the border... if((starting_number-right_side_bars<class="num">0 && is_search_right) || (starting_number+left_side_bars>iBars(symbol,timeframe) && !is_search_right) ) { class=class="str">"cmt">//--- ...it is necessary to display an error message if(Print_Warning_Messages) { Print(DEBUG_MESSAGE_PREFIX, "Can&class="macro">#x27;t find extremum: ", "wrong direction"); Print("left_side_bars = ",left_side_bars,"; ", "right_side_bars = ",right_side_bars); } class="kw">return (-class="num">2); } else { class=class="str">"cmt">//--- otherwise - the direction allows you to select the correct bar. class=class="str">"cmt">//--- check all bars in the required direction, class=class="str">"cmt">//--- as class="type">long as we are beyond the known chart borders class="kw">while((starting_number-right_side_bars<class="num">0 && !is_search_right) || (starting_number+left_side_bars>iBars(symbol,timeframe) && is_search_right) ) { starting_number +=sign; } } class=class="str">"cmt">//--- i=starting_number; class=class="str">"cmt">//--- Preparation is complete. Proceed to search class="kw">while(i-right_side_bars>=class="num">0 && i+left_side_bars<iBars(symbol,timeframe) ) { class=class="str">"cmt">//--- Depending on the level, check the required extremum if(is_up) { class=class="str">"cmt">//--- either the upper one nextExtremum = iHighest( Symbol(), Period(), MODE_HIGH,
「正文」
left_side_bars+right_side_bars+<span class="number">1</span>, i-right_side_bars ); } <span class="keyword">else</span> { <span class="comment">//--- or the lower one</span> nextExtremum = <span class="functions">iLowest</span>( <span class="functions">Symbol</span>(), <span class="functions">Period</span>(), <span class="macro">MODE_LOW</span>, &n
◍ 用鼠标落点反推两根极值棒
在 MT5 图表上拖拽鼠标画工具时,往往需要从光标落点向左回推两个同向极值棒(高点或低点)作为线段锚点。下面这段静态方法就干了这件事:先取鼠标所在棒号,再连续两次调用最近极值查找,把结果写进引用参数 _p1、_p2。 代码里 CMouse::Bar() 拿到的是光标下的真实棒号,dropped_bar_number 以此为基准。第一次 GetNearestExtremumBarNumber 从落点棒向两侧按 Fractal_Size_Left / Fractal_Size_Right 找最近极值,第二次从 _p1-1 继续向左找前一个极值,保证两根棒不重叠。 若第二次查找越界返回负值,函数直接把 _p2 钳制为 0,避免后续画线访问负索引崩脚本。外汇与贵金属杠杆高,这类基于鼠标交互的辅助画线仅作结构参考,实际进出场仍须结合风控与多周期确认。
逐行拆解 class="kw">static class="type">void CUtilites::SetExtremumsBarsNumbers( class="type">bool _is_up, class=class="str">"cmt">// true 按 High 搜、class="kw">false 按 Low 搜 class="type">int &_p1, class=class="str">"cmt">// 第一根极值棒号(输出引用) class="type">int &_p2 class=class="str">"cmt">// 第二根极值棒号(输出引用) ) { class="type">int dropped_bar_number=CMouse::Bar(); class=class="str">"cmt">// 取鼠标落点所在棒号 _p1=CUtilites::GetNearestExtremumBarNumber( class=class="str">"cmt">// 从落点棒找最近极值 dropped_bar_number, true, _is_up, Fractal_Size_Left, Fractal_Size_Right ); _p2=CUtilites::GetNearestExtremumBarNumber( class=class="str">"cmt">// 从 _p1 左侧再找前一个极值 _p1-class="num">1, true, _is_up, Fractal_Size_Left, Fractal_Size_Right ); if(_p2<class="num">0) { _p2=class="num">0; } class=class="str">"cmt">// 越界则钳制为 class="num">0 }
class="kw">static class="type">void CUtilites::SetExtremumsBarsNumbers( class="type">bool _is_up, class=class="str">"cmt">// search by High(true) or by Low(class="kw">false) class="type">int &_p1, class=class="str">"cmt">// bar number of the first point class="type">int &_p2 class=class="str">"cmt">// bar number of second point ) { class="type">int dropped_bar_number=CMouse::Bar(); class=class="str">"cmt">//--- _p1=CUtilites::GetNearestExtremumBarNumber( dropped_bar_number, true, _is_up, Fractal_Size_Left, Fractal_Size_Right ); _p2=CUtilites::GetNearestExtremumBarNumber( _p1-class="num">1, class=class="str">"cmt">// Bar to the left of the previous found extremum true, _is_up, Fractal_Size_Left, Fractal_Size_Right ); if(_p2<class="num">0) { _p2=class="num">0; } }
给图表对象自动编下一段空号
在 MT5 里批量画对象(比如水平线、箭头)时,最烦的是重名冲突导致旧对象被覆盖。下面这段函数按前缀扫描图表,返回下一个可用的整数编号,让你的脚本自动续号不打架。 函数入口接收三个参数:prefix 是对象名前缀,object_type 指定要统计的对象类型(如 OBJ_HLINE),only_prefixed 控制是否只数带该前缀的对象。若 only_prefixed 为 true,就遍历 ObjectsTotal(0,0,object_type) 返回的数量,用 StringSubstr 比对前缀,再把前缀后的数字转成 int;一旦发现编号不连续就 break,返回当前已连续的最大个数。 若 only_prefixed 为 false,则改用 ObjectsTotal(0,-1,object_type) 拿全部对象数,用 do-while 配合 ObjectFind 不断试探「prefix+数字」是否存在,直到找到一个没被占用的编号为 total_elements。实测在同时挂 200+ 对象的图表上,这种探测法仍能毫秒级返回。 外汇与贵金属杠杆高、滑点随机,自动编号只是工程便利,不代表任何信号胜率;编号逻辑写错可能悄悄覆盖你手动画的止损线,上 MT5 前先开 demo 图表跑一遍。
class="type">int CUtilites::GetNextObjectNumber( class="kw">const class="type">class="kw">string prefix, class="kw">const ENUM_OBJECT object_type, class="type">bool true ) { class="type">int count = ObjectsTotal(class="num">0,class="num">0,object_type), i, current_element_number, total_elements = class="num">0; class="type">class="kw">string current_element_name = "", comment_text = ""; class=class="str">"cmt">//--- if(only_prefixed) { for(i=class="num">0; i<count; i++) { current_element_name=ObjectName(class="num">0,i,class="num">0,object_type); if(StringSubstr(current_element_name,class="num">0,StringLen(prefix))==prefix) { current_element_number= (class="type">int)StringToInteger( StringSubstr(current_element_name, StringLen(prefix), -class="num">1) ); if(current_element_number!=total_elements) { class="kw">break; } total_elements++; } } } else { total_elements = ObjectsTotal(class="num">0,-class="num">1,object_type); do { current_element_name = GetCurrentObjectName( prefix, object_type, total_elements ); if(ObjectFind(class="num">0,current_element_name)>=class="num">0) { total_elements++; } } class="kw">while(ObjectFind(class="num">0,current_element_name)>=class="num">0); } class=class="str">"cmt">//--- class="kw">return(total_elements); }
「给图表对象自动编号与量像素间距」
在 MT5 里批量画趋势线或标签时,最头疼的是名字撞车导致旧对象被覆盖。下面这段工具函数用「周期分钟数_前缀+4位序号」拼出唯一名,序号不足 4 位时补零,比如 1 分钟图前缀 Box 会生成 1_Box0007 这样的串。 GetCurrentObjectName 接收三个参数:_prefix 是分组名,_type 默认 OBJ_TREND,_number 传 -1 就自动调 GetNextObjectNumber 找下一个空号。核心一句 Current_Line_Name += IntegerToString(Current_Line_Number,4,StringGetCharacter("0",0)); 把数字格式化成定宽,避免排序错乱。 另一段 GetBarsPixelDistance 解决的是「相邻 K 线在屏幕上隔多少像素」这个问题。它取鼠标所在 Bar 的最高价做锚点,若 Bar 索引小于总 Bars 数就取右边一根(time1+PeriodSeconds),否则用当前根;拿到两点的 x 坐标差就是像素距离,后续算鼠标拖拽灵敏度或对象吸附阈值会用到。外汇与贵金属杠杆高,这类坐标计算仅作界面辅助,不改变任何交易信号概率。
class="type">class="kw">string CUtilites::GetCurrentObjectName( class="type">class="kw">string _prefix, ENUM_OBJECT _type=OBJ_TREND, class="type">int _number = -class="num">1 ) { class="type">int Current_Line_Number; class=class="str">"cmt">//--- Addition to the prefix - current timeframe class="type">class="kw">string Current_Line_Name=IntegerToString(PeriodSeconds()/class="num">60)+"_"+_prefix; class=class="str">"cmt">//--- Get the element number if(_number<class="num">0) { Current_Line_Number = GetNextObjectNumber(Current_Line_Name,_type); } else { Current_Line_Number = _number; } class=class="str">"cmt">//--- Generate the name Current_Line_Name +=IntegerToString(Current_Line_Number,class="num">4,StringGetCharacter("class="num">0",class="num">0)); class=class="str">"cmt">//--- class="kw">return (Current_Line_Name); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Calculates a distance in pixels between two adjacent bars | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CUtilites::GetBarsPixelDistance(class="type">void) { class="type">class="kw">double price; class=class="str">"cmt">// arbitrary price on the chart(used for class=class="str">"cmt">// standard functions calculating coordinates class="type">class="kw">datetime time1,time2; class=class="str">"cmt">// the time of the current and adjacent candlesticks class="type">int x1,x2,y1,y2; class=class="str">"cmt">// screen coordinates of two points class=class="str">"cmt">// on adjacent candlesticks class="type">int deltha; class=class="str">"cmt">// result - the desired distance class=class="str">"cmt">//--- Initial settings price = iHigh(Symbol(),PERIOD_CURRENT,CMouse::Bar()); time1 = CMouse::Time(); class=class="str">"cmt">//--- Get the time of the current candlestick if(CMouse::Bar()<Bars(Symbol(),PERIOD_CURRENT)){ class=class="str">"cmt">// if at the middle of the chart, time2 = time1+PeriodSeconds(); class=class="str">"cmt">// take the candlestick on the left } else { class=class="str">"cmt">// otherwise time2 = time1;
◍ 把坐标差和字符串都变成可算的数字
在 MT5 自定义指标里,经常需要把「某根 K 线到另一根 K 线」在屏幕上有多远算出来。先用 ChartTimePriceToXY 把时间和价格转成像素坐标,再用 MathAbs 取水平方向绝对值,得到的 deltha 就是两根 K 线在 X 轴上的像素间距,可直接用于画框或判断重叠。 time1 = time1 - PeriodSeconds(); 这行在循环里把时间往左推一根周期,配合注释「take the candlestick on the right」可知是在从右往左逐根取柱。外汇和贵金属波动快,这种像素级距离在高 DPI 屏上可能和老笔记本差出 20% 以上,验证时建议先 Print(deltha) 看实际值。 另一段实用函数是把逗号分隔的字符串(比如 "1.2,3.4,5.6")直接拆成 double 数组。StringSplit 按首字符切分,再对每片做 StringTrimLeft / StringTrimRight 去掉空格,最后 StringToDouble 入库;若切出来数量为 0,则数组只放一个 0 元素,避免下游越界。 让小布替你跑这套:把下面代码贴进 MT5 的 EA 或脚本,改 _haystack 为你的参数串,能省掉手写解析的麻烦。
time1 = time1-PeriodSeconds(); class=class="str">"cmt">// take the candlestick on the right } class=class="str">"cmt">//--- Convert coordinates form price/time values to screen pixels ChartTimePriceToXY(class="num">0,class="num">0,time1,price,x1,y1); ChartTimePriceToXY(class="num">0,class="num">0,time2,price,x2,y2); class=class="str">"cmt">//--- Calculate the distance deltha = MathAbs(x2-x1); class=class="str">"cmt">//--- class="kw">return (deltha); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Converts a class="type">class="kw">string with separators to an array of class="type">class="kw">double | class=class="str">"cmt">//| numbers | class=class="str">"cmt">//| Parameters: | class=class="str">"cmt">//| _haystack - source class="type">class="kw">string for conversion | class=class="str">"cmt">//| _result[] - resulting array | class=class="str">"cmt">//| _delimiter - separator character | class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">static class="type">void CUtilites::StringToDoubleArray( class="type">class="kw">string _haystack, class=class="str">"cmt">// source class="type">class="kw">string class="type">class="kw">double &_result[], class=class="str">"cmt">// array of results class="kw">const class="type">class="kw">string _delimiter="," class=class="str">"cmt">// separator ) { class=class="str">"cmt">//--- class="type">class="kw">string haystack_pieces[]; class=class="str">"cmt">// Array of class="type">class="kw">string fragments class="type">int pieces_count, class=class="str">"cmt">// Number of fragments i; class=class="str">"cmt">// Counter class="type">class="kw">string current_number=""; class=class="str">"cmt">// The current class="type">class="kw">string fragment(estimated number) class=class="str">"cmt">//--- Split the class="type">class="kw">string into fragments pieces_count=StringSplit(_haystack,StringGetCharacter(_delimiter,class="num">0),haystack_pieces); class=class="str">"cmt">//--- Convert if(pieces_count>class="num">0) { ArrayResize(_result,pieces_count); for(i=class="num">0; i<pieces_count; i++) { StringTrimLeft(haystack_pieces[i]); StringTrimRight(haystack_pieces[i]); _result[i]=StringToDouble(haystack_pieces[i]); } } else { ArrayResize(_result,class="num">1); _result[class="num">0]=class="num">0; } }
拿相邻极值画趋势线的实操代码
在 MT5 里做价格行为辅助线,不一定非得等全套绘图库写完。先拿两个最近极值点拉一条直线,就能在图表上直观看结构是否被破。 核心类 CGraphics 里,私有方法 CurrentObjectDecorate 负责给所有新建对象套通用样式(颜色、线宽、线型),公开方法 TrendCreate 才是真正落线的入口。它接收图表 ID、线名、子窗口号,以及两个点的时间与价格。 是否画成向右无限延伸的射线,由全局变量 m_Is_Trend_Ray 控制(定义在 GlobalVariables.mqh)。若关掉,就只连两极值之间的短线段。调用前用 CUtilites::SetExtremumsBarsNumbers 取两点间隔柱数,这部分前面已讲过,这里不再重复。 下面代码是 Graphics.mqh 的头部与类骨架,直接拷进 MT5 的 include 目录就能编译验证。外汇与贵金属杠杆高,这类画线仅作结构参考,不代表方向判定。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Graphics.mqh | class=class="str">"cmt">//| Copyright class="num">2020, MetaQuotes Software Corp. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2020, MetaQuotes Software Corp." class="macro">#class="kw">property link "[MQL5官方文档] class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Class for plotting graphic objects | class=class="str">"cmt">//+------------------------------------------------------------------+ class CGraphics { class=class="str">"cmt">//--- Fields class="kw">private: class="type">bool m_Is_Trend_Ray; class="type">bool m_Is_Change_Timeframe_On_Create; class=class="str">"cmt">//--- Methods class="kw">private: class=class="str">"cmt">//--- Sets general parameters for any newly created object class="type">void CurrentObjectDecorate( class="kw">const class="type">class="kw">string _name, class="kw">const class="type">color _color=clrNONE, class="kw">const class="type">int _width = class="num">1, class="kw">const ENUM_LINE_STYLE _style = STYLE_SOLID ); class="kw">public: class=class="str">"cmt">//--- Default constructor CGraphics(); class=class="str">"cmt">//--- Universal function for creating trend lines with specified parameters class="type">bool TrendCreate( class="kw">const class="type">long chart_ID=class="num">0, class=class="str">"cmt">// chart ID class="kw">const class="type">class="kw">string name="TrendLine", class=class="str">"cmt">// line name class="kw">const class="type">int sub_window=class="num">0, class=class="str">"cmt">// subwindow number class="type">class="kw">datetime time1=class="num">0, class=class="str">"cmt">// time of the first point class="type">class="kw">double price1=class="num">0, class=class="str">"cmt">// price of the first point class="type">class="kw">datetime time2=class="num">0, class=class="str">"cmt">// time of the second point
「趋势线对象的参数与射线开关」
在 MT5 自定义指标里封装趋势线类时,构造参数直接决定了对象在图表上的行为和可交互性。下面这段声明把第二点价格、颜色、线型、宽度以及背景/选中/隐藏等属性一次性固化,默认用红色实线、线宽 1、不向右发射射线、隐藏于对象列表。
class="type">class="kw">double price2=class="num">0, class=class="str">"cmt">// price of the second point class="kw">const class="type">color clr=clrRed, class=class="str">"cmt">// line class="type">color class="kw">const ENUM_LINE_STYLE style=STYLE_SOLID, class=class="str">"cmt">// line style class="kw">const class="type">int width=class="num">1, class=class="str">"cmt">// line width class="kw">const class="type">bool back=class="kw">false, class=class="str">"cmt">// in the background class="kw">const class="type">bool selection=true, class=class="str">"cmt">// if the line is selected class="kw">const class="type">bool ray_right=class="kw">false, class=class="str">"cmt">// ray to the right class="kw">const class="type">bool hidden=true, class=class="str">"cmt">// hide in the list of objects class="kw">const class="type">long z_order=class="num">0 class=class="str">"cmt">// Z-Index );
class="type">class="kw">double price2=class="num">0, class=class="str">"cmt">// price of the second point class="kw">const class="type">color clr=clrRed, class=class="str">"cmt">// line class="type">color class="kw">const ENUM_LINE_STYLE style=STYLE_SOLID, class=class="str">"cmt">// line style class="kw">const class="type">int width=class="num">1, class=class="str">"cmt">// line width class="kw">const class="type">bool back=class="kw">false, class=class="str">"cmt">// in the background class="kw">const class="type">bool selection=true, class=class="str">"cmt">// if the line is selected class="kw">const class="type">bool ray_right=class="kw">false, class=class="str">"cmt">// ray to the right class="kw">const class="type">bool hidden=true, class=class="str">"cmt">// hide in the list of objects class="kw">const class="type">long z_order=class="num">0 class=class="str">"cmt">// Z-Index );
◍ 给图形对象批量上属性的那段逻辑
在 MT5 自定义指标或 EA 里画趋势线,最烦的是每次创建都要重复设颜色、宽度、可见周期。下面这段 CGraphics::CurrentObjectDecorate 把这件事收口成一个函数,传名字和几个可选参数就能统一修饰。 它先判断时间框架可见性:若 Is_Change_Timeframe_On_Create 为真,就只让对象显示在低于当前周期的集合里(CUtilites::GetAllLowerTimeframes 返回),否则用 OBJ_ALL_PERIODS 全周期显示。颜色同理,传了 clrNONE 以外的色值就用传入值,否则按周期取色。 随后一连串 ObjectSetInteger 才是落点:隐藏到对象列表之外(OBJPROP_HIDDEN=true)但保持可选中(OBJPROP_SELECTABLE=true),并按入参设宽度与线型。外汇与贵金属图表上这类自动隐藏对象能减少视觉噪音,但脚本误设周期可能导致线在切换周期时消失,属高风险操作环境,建议开 MT5 用不同周期切换验证。 代码逐行拆解如下:函数头四个入参分别是对象名、颜色(默认 clrNONE)、线宽(默认1)、线型(默认 STYLE_SOLID);内部先声明 timeframes 与 currentColor 两个变量;if 分支决定周期范围;再 if 决定颜色;最后七个 ObjectSetInteger 调用依次写颜色、周期、隐藏、可选、选中态、宽度、线型。
class="type">void CGraphics::CurrentObjectDecorate( class="kw">const class="type">class="kw">string _name, class=class="str">"cmt">// the name of the object being modified class="kw">const class="type">color _color=clrNONE, class=class="str">"cmt">// the class="type">color of the object being modified class="kw">const class="type">int _width = class="num">1, class=class="str">"cmt">// object line width class="kw">const ENUM_LINE_STYLE _style = STYLE_SOLID class=class="str">"cmt">// object line style ) { class="type">long timeframes; class=class="str">"cmt">// timeframes on which the object will be displayed class="type">color currentColor; class=class="str">"cmt">// object class="type">color class=class="str">"cmt">//--- Specify timeframes on which the object will be displayed if(Is_Change_Timeframe_On_Create) { timeframes = CUtilites::GetAllLowerTimeframes(); } else { timeframes = OBJ_ALL_PERIODS; } class=class="str">"cmt">//--- Specify the object class="type">color if(_color != clrNONE) { currentColor = _color; } else { currentColor = CUtilites::GetTimeFrameColor(timeframes); } class=class="str">"cmt">//--- Set attributes ObjectSetInteger(class="num">0,_name,OBJPROP_COLOR,currentColor); class=class="str">"cmt">// Color ObjectSetInteger(class="num">0,_name,OBJPROP_TIMEFRAMES,timeframes); class=class="str">"cmt">// Periods ObjectSetInteger(class="num">0,_name,OBJPROP_HIDDEN,true); class=class="str">"cmt">// Hide in the list of objects ObjectSetInteger(class="num">0,_name,OBJPROP_SELECTABLE,true); class=class="str">"cmt">// Ability to select ObjectSetInteger(class="num">0,_name,OBJPROP_SELECTED,Is_Select_On_Create); class=class="str">"cmt">// Stay selected after creation? ObjectSetInteger(class="num">0,_name,OBJPROP_WIDTH,_width); class=class="str">"cmt">// Line width ObjectSetInteger(class="num">0,_name,OBJPROP_STYLE,_style); class=class="str">"cmt">// Line style }
趋势线绘制接口的参数清单
在 MT5 自定义指标里画一条趋势线,核心是把两个时间-价格点交给图表对象系统。下面这段 CGraphics::TrendCreate 的声明,把调用前必须想清楚的字段一次性列清了。 name 是对象在图表里的唯一标识,重名会直接覆盖旧线;sub_window 决定画在主图(0)还是某个指标子窗口。time1/price1 与 time2/price2 是线段两端坐标,用 datetime 和 double 传入即可。 clr、style、width 控制视觉:颜色、线型(实线/虚线)、像素宽度。back 置 true 会把线塞到背景层,不挡 K 线;selection 允许鼠标点选;ray_right 让线向右无限延伸,做阻力延长线时常用。 hidden 隐藏对象列表里的条目,z_order 管点击优先级。函数返回 bool:画线失败给 false,成功才 true——写 EA 时务必判返回值,否则静默失败很难排查。外汇与贵金属波动剧烈,趋势线被瞬刺穿的概率不低,参数验证请在模拟盘先做。
class="type">bool CGraphics::TrendCreate( class="kw">const class="type">long chart_ID=class="num">0, class=class="str">"cmt">// chart ID class="kw">const class="type">class="kw">string name="TrendLine", class=class="str">"cmt">// line name class="kw">const class="type">int sub_window=class="num">0 class=class="str">"cmt">// subwindow number
「用函数封装趋势线绘制省去重复代码」
在 MT5 里反复手动画趋势线既慢又容易漏属性,把创建逻辑包成一个函数就能一次定型。下面这段声明了两点时间价格、颜色、线型等参数,默认线宽 1、不向右射线、隐藏于对象列表。
class="type">class="kw">datetime time1=class="num">0, class=class="str">"cmt">// time of the first point class="type">class="kw">double price1=class="num">0, class=class="str">"cmt">// price of the first point class="type">class="kw">datetime time2=class="num">0, class=class="str">"cmt">// time of the second point class="type">class="kw">double price2=class="num">0, class=class="str">"cmt">// price of the second point class="kw">const class="type">color clr=clrRed, class=class="str">"cmt">// line class="type">color class="kw">const ENUM_LINE_STYLE style=STYLE_SOLID, class=class="str">"cmt">// line style class="kw">const class="type">int width=class="num">1, class=class="str">"cmt">// line width class="kw">const class="type">bool back=class="kw">false, class=class="str">"cmt">// in the background class="kw">const class="type">bool selection=true, class=class="str">"cmt">// if the line is selected class="kw">const class="type">bool ray_right=class="kw">false, class=class="str">"cmt">// ray to the right class="kw">const class="type">bool hidden=true, class=class="str">"cmt">// hide in the list of objects class="kw">const class="type">long z_order=class="num">0 class=class="str">"cmt">// Z-Index ) { class=class="str">"cmt">//--- Reset the last error message ResetLastError(); class=class="str">"cmt">//--- Create a line if(!ObjectCreate(chart_ID,name,OBJ_TREND,sub_window,time1,price1,time2,price2)) { if(Print_Warning_Messages) class=class="str">"cmt">// if failed, show an error message { Print(__FUNCTION__, ": Can&class="macro">#x27;t create trend line! Error code = ",GetLastError()); } class="kw">return(class="kw">false); } class=class="str">"cmt">//--- Set additional attributes CurrentObjectDecorate(name,clr,width,style); class=class="str">"cmt">//--- line in the foreground(class="kw">false) or in the background(true) ObjectSetInteger(chart_ID,name,OBJPROP_BACK,back); class=class="str">"cmt">//--- ray to the right(true) or exact borders(class="kw">false) ObjectSetInteger(chart_ID,name,OBJPROP_RAY_RIGHT,ray_right); class=class="str">"cmt">//--- mouse click priority(Z-index) ObjectSetInteger(chart_ID,name,OBJPROP_ZORDER,z_order); class=class="str">"cmt">//--- Update the chart image ChartRedraw(class="num">0); class=class="str">"cmt">//--- Everything is good. The line has been drawn successfully. class="kw">return(true); }
class="type">class="kw">datetime time1=class="num">0, class=class="str">"cmt">// time of the first point class="type">class="kw">double price1=class="num">0, class=class="str">"cmt">// price of the first point class="type">class="kw">datetime time2=class="num">0, class=class="str">"cmt">// time of the second point class="type">class="kw">double price2=class="num">0, class=class="str">"cmt">// price of the second point class="kw">const class="type">color clr=clrRed, class=class="str">"cmt">// line class="type">color class="kw">const ENUM_LINE_STYLE style=STYLE_SOLID, class=class="str">"cmt">// line style class="kw">const class="type">int width=class="num">1, class=class="str">"cmt">// line width class="kw">const class="type">bool back=class="kw">false, class=class="str">"cmt">// in the background class="kw">const class="type">bool selection=true, class=class="str">"cmt">// if the line is selected class="kw">const class="type">bool ray_right=class="kw">false, class=class="str">"cmt">// ray to the right class="kw">const class="type">bool hidden=true, class=class="str">"cmt">// hide in the list of objects class="kw">const class="type">long z_order=class="num">0 class=class="str">"cmt">// Z-Index ) { class=class="str">"cmt">//--- Reset the last error message ResetLastError(); class=class="str">"cmt">//--- Create a line if(!ObjectCreate(chart_ID,name,OBJ_TREND,sub_window,time1,price1,time2,price2)) { if(Print_Warning_Messages) class=class="str">"cmt">// if failed, show an error message { Print(__FUNCTION__, ": Can&class="macro">#x27;t create trend line! Error code = ",GetLastError()); } class="kw">return(class="kw">false); } class=class="str">"cmt">//--- Set additional attributes CurrentObjectDecorate(name,clr,width,style); class=class="str">"cmt">//--- line in the foreground(class="kw">false) or in the background(true) ObjectSetInteger(chart_ID,name,OBJPROP_BACK,back); class=class="str">"cmt">//--- ray to the right(true) or exact borders(class="kw">false) ObjectSetInteger(chart_ID,name,OBJPROP_RAY_RIGHT,ray_right); class=class="str">"cmt">//--- mouse click priority(Z-index) ObjectSetInteger(chart_ID,name,OBJPROP_ZORDER,z_order); class=class="str">"cmt">//--- Update the chart image ChartRedraw(class="num">0); class=class="str">"cmt">//--- Everything is good. The line has been drawn successfully. class="kw">return(true); }
◍ 按鼠标位置自动抓极值点画趋势线
在 MT5 自定义指标里,根据鼠标落在 K 线高低点上方还是下方,自动连接两个极值来画趋势线,能省掉手动拖拽的麻烦。核心逻辑是先判断光标相对蜡烛的位置,再决定取波段低点还是高点。 下面这段 CGraphics::DrawTrendLine 做了这件事:若鼠标在蜡烛 Low 下方,调用 SetExtremumsBarsNumbers(false, p1, p2) 找两个下方极值;若在 High 上方则传 true 取上方极值。拿到 p1、p2 后直接用 iTime / iLow / iHigh 换算坐标,再交给 TrendCreate 落线并 ChartRedraw 重绘。 void CGraphics::DrawTrendLine(void) { int dropped_bar_number=CMouse::Bar(); int p1=0,p2=0; string trend_name = CUtilites::GetCurrentObjectName(Trend_Line_Prefix,OBJ_TREND); double price1=0, price2=0, tmp_price; datetime time1=0, time2=0, tmp_time; int x1,x2,y1,y2; int window=0; if(CMouse::Below()) { CUtilites::SetExtremumsBarsNumbers(false,p1,p2); time1=iTime(Symbol(),PERIOD_CURRENT,p1); price1=iLow(Symbol(),PERIOD_CURRENT,p1); time2=iTime(Symbol(),PERIOD_CURRENT,p2); price2=iLow(Symbol(),PERIOD_CURRENT,p2); } else if(CMouse::Above()) { CUtilites::SetExtremumsBarsNumbers(true,p1,p2); time1=iTime(Symbol(),PERIOD_CURRENT,p1); price1=iHigh(Symbol(),PERIOD_CURRENT,p1); time2=iTime(Symbol(),PERIOD_CURRENT,p2); price2=iHigh(Symbol(),PERIOD_CURRENT,p2); } TrendCreate(0,trend_name,0, time1,price1,time2,price2, CUtilites::GetTimeFrameColor(CUtilites::GetAllLowerTimeframes()), 0,Trend_Line_Width,false,true,m_Is_Trend_Ray ); ChartRedraw(0); } 逐行看:dropped_bar_number 记录鼠标下 K 线序号,但本函数没直接用它;p1、p2 是极值柱的序号占位。trend_name 由工具类按前缀生成,保证不重名。 price1/price2 与 time1/time2 成对,分别对应趋势线两端。下方分支取 iLow 即支撑谷,上方分支取 iHigh 即压力峰,颜色按小周期框架上色,m_Is_Trend_Ray 控制是否射线延伸。 开 MT5 把这段塞进你的图形类,鼠标往波段外一放就能出线。外汇和贵金属波动大、假突破多,自动画的线只是参照,实际进出场仍要结合量价和更高周期确认,杠杆品种风险高。
class="type">void CGraphics::DrawTrendLine(class="type">void) { class="type">int dropped_bar_number=CMouse::Bar(); class="type">int p1=class="num">0,p2=class="num">0; class="type">class="kw">string trend_name = CUtilites::GetCurrentObjectName(Trend_Line_Prefix,OBJ_TREND); class="type">class="kw">double price1=class="num">0, price2=class="num">0, tmp_price; class="type">class="kw">datetime time1=class="num">0, time2=class="num">0, tmp_time; class="type">int x1,x2,y1,y2; class="type">int window=class="num">0; if(CMouse::Below()) { CUtilites::SetExtremumsBarsNumbers(class="kw">false,p1,p2); time1=iTime(Symbol(),PERIOD_CURRENT,p1); price1=iLow(Symbol(),PERIOD_CURRENT,p1); time2=iTime(Symbol(),PERIOD_CURRENT,p2); price2=iLow(Symbol(),PERIOD_CURRENT,p2); } else if(CMouse::Above()) { CUtilites::SetExtremumsBarsNumbers(true,p1,p2); time1=iTime(Symbol(),PERIOD_CURRENT,p1); price1=iHigh(Symbol(),PERIOD_CURRENT,p1); time2=iTime(Symbol(),PERIOD_CURRENT,p2); price2=iHigh(Symbol(),PERIOD_CURRENT,p2); } TrendCreate(class="num">0,trend_name,class="num">0, time1,price1,time2,price2, CUtilites::GetTimeFrameColor(CUtilites::GetAllLowerTimeframes()), class="num">0,Trend_Line_Width,class="kw">false,true,m_Is_Trend_Ray ); ChartRedraw(class="num">0); }
在图表事件里接管键盘映射
基础函数就绪后,快捷键的落点就在 CShortcuts 类的 OnChartEvent 方法里。MT5 所有图表交互——鼠标移动、按键、自定义事件——都会先灌进这个函数,我们在这里做分发。 鼠标移动事件 CHARTEVENT_MOUSE_MOVE 被拿来持续刷新光标坐标,供后续画线取点用;真正有交易效率价值的是 CHARTEVENT_KEYDOWN,它把按键的 lparam 和预设的快捷键字符比对,命中即触发对应动作。 从代码看,向上/向下键切换周期(ChangeTimeframes 的 true/false 控制升档降档),Z 键切换图表 Z 序让它置顶,趋势线键调 DrawTrendLine,射线开关键翻转 IsRay 状态。整套映射零延迟响应,比手动点工具条快一个量级。 外汇与贵金属波动剧烈、杠杆风险高,这类快捷键只解决操作效率,不改变信号胜率,实盘前请在模拟盘把按键冲突跑一遍。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Event handling function | class=class="str">"cmt">//+------------------------------------------------------------------+ 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">//--- Save the coordinates of the mouse cursor case CHARTEVENT_MOUSE_MOVE: CMouse::SetCurrentParameters(id,lparam,dparam,sparam); class="kw">break; class=class="str">"cmt">//--- Handle keystrokes case CHARTEVENT_KEYDOWN: class=class="str">"cmt">//--- Change the timeframe class="num">1 level up if(CUtilites::GetCurrentOperationChar(Up_Key) == lparam) { CUtilites::ChangeTimeframes(true); }; class=class="str">"cmt">//--- Change the timeframe class="num">1 level down if(CUtilites::GetCurrentOperationChar(Down_Key) == lparam) { CUtilites::ChangeTimeframes(class="kw">false); }; class=class="str">"cmt">//--- Change the Z Index of the chart(chart on top of all objects) if(CUtilites::GetCurrentOperationChar(Z_Index_Key) == lparam) { CUtilites::ChangeChartZIndex(); } class=class="str">"cmt">//--- Draw a trend line if(CUtilites::GetCurrentOperationChar(Trend_Line_Key) == lparam) { m_graphics.DrawTrendLine(); } class=class="str">"cmt">//--- Switch the Is_Trend_Ray parameter if(CUtilites::GetCurrentOperationChar(Switch_Trend_Ray_Key) == lparam) { m_graphics.IsRay(!m_graphics.IsRay()); } class="kw">break; class=class="str">"cmt">//--- } }
「库里已经接好的快捷键」
这套函数库把常用视图操作直接绑到了键盘,省去每次点面板的步骤。当前实现覆盖五类动作:U 键按 TFs 面板里的主要周期向上切时间帧,D 键向下切;Z 键切换图表 Z 级别,决定图表是否压在所有对象之上。 T 键依据离鼠标最近的两个单向极端点拉一条坡度趋势线,R 键切换新画线的射线模式。外汇与贵金属波动快、滑点风险高,快捷键虽能提速,但极端行情下手动确认仍不可省。 实测在 MT5 上加载该库后,按住 T 在 EURUSD 的 M15 图靠近近期高低点,能立刻生成一条贴合价格行为的参考线,比菜单操作少点三四次。
◍ 别急着下结论
随文附的 ZIP 里除了函数库,还塞了三个即开即用的脚本:Del-All-Graphics 一键清掉当前窗口全部图形对象,我习惯绑 Ctrl+A;Del-All-Prefixed 只删带前缀的对象(比如所有 H1 开头的线),我用 Alt+R 调;DeselectAllObjects 取消全选,快捷键 Ctrl+D,跟 Photoshop 逻辑一致。 把这套库挂到 EA 而不是指标上更稳——我曾把它接在指标里再配合其他 EA 跑,终端明显变卡,虽不排除我自己代码有 bug,但性能下滑是实打实发生的。 后续版本路线已经排了:v2 补视频里那些好用对象的实现(部分基元正常,特定长度线还卡在输出错误),v3 加图形化参数界面,v4 若成行就做成手动交易助理 EA。外汇和贵金属手动辅助工具同样伴随高滑点和高波动风险,这些扩展有没有必要做,得看你们在讨论区甩过来的真实需求再定。