MQL5 细则手册:指标子窗口控件 - 滚动条·综合运用
◍ 把品种交易参数一次性读进面板
做价格行为分析前,先得搞清楚手里的品种到底怎么计费。MT5 里 SYMBOL_TRADE_* 和 SYMBOL_VOLUME_* 这组属性,直接决定了每跳盈亏、最小手数和合约规模,不读清楚就容易把止损点数算错。 下面这段 switch 分支把 33~42 号参数集中取出:33 是每跳亏损金额(保留 2 位),34 是当前品种的点值步长(用 _Digits 对齐精度),35 合约大小,36/37/38 分别是最小、最大、步进手数,全部 DoubleToString 转成字符串塞进 str。
- 号比较特殊,SYMBOL_VOLUME_LIMIT 为 0 时代表无持仓上限,代码里返回 "Unlimited" 而不是数字;40/41 的 swap 多空隔夜费若为 0 则标记 "false",42 的初始保证金为 0 显示 "-"。外汇和贵金属杠杆高,这些参数随经纪商变动,实盘前务必在 MT5 终端用 Symbol 窗口核对一遍。
把这套 case 直接贴进你的信息面板 EA,编译后切到 XAUUSD 或 EURUSD,就能看到实时数值。不同品种 tick value loss 可能差出十几倍,这是仓位计算的第一道关。
case class="num">33 : str=DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_VALUE_LOSS),class="num">2); break; case class="num">34 : str=DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_SIZE),_Digits); break; case class="num">35 : str=DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_TRADE_CONTRACT_SIZE),class="num">2); break; case class="num">36 : str=DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN),class="num">2); break; case class="num">37 : str=DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX),class="num">2); break; case class="num">38 : str=DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP),class="num">2); break; case class="num">39 : d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_LIMIT); str=(d_check_value==class="num">0) ? "Unlimited" : DoubleToString(d_check_value,class="num">2); break; case class="num">40 : d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_SWAP_LONG); str=(d_check_value==class="num">0) ? "false" : DoubleToString(d_check_value,class="num">2); break; case class="num">41 : d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_SWAP_SHORT); str=(d_check_value==class="num">0) ? "false" : DoubleToString(d_check_value,class="num">2); break; case class="num">42 : d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_MARGIN_INITIAL); str=(d_check_value==class="num">0) ? "-" : DoubleToString(d_check_value,class="num">2); break; case class="num">43 :
保证金与时段成交量的枚举抓取
这段 switch 分支集中处理 MT5 合约属性里的保证金与当前时段统计类字段,从 case 43 到 case 50 一共 8 个枚举值,覆盖了维持保证金、多空单边保证金、限价/止损单保证金以及会话成交量与成交额。 每个分支都是先用 SymbolInfoDouble(_Symbol, 宏) 取双精度数值赋给 d_check_value,再判断是否为 0:若为 0 则 str 置为 "-",否则用 DoubleToString(d_check_value, 2) 保留两位小数转字符串。 实盘里 SYMBOL_MARGIN_MAINTENANCE 在多数外汇品种上返回 0,而 SYMBOL_SESSION_VOLUME 在休市时段也倾向为 0,所以这两个分支大概率走 "-" 分支;对比之下 SYMBOL_MARGIN_LONG / SHORT 在黄金 XAUUSD 上常返回非零值(如 100.00 表示每手 100 美元保证金)。 把这套枚举直接塞进你已有的 switch 结构里,编译后开 MT5 的 EURUSD 和 XAUUSD 各看一眼,就能验证哪些字段在当前券商环境下有数、哪些常年为空。外汇与贵金属杠杆高,保证金参数随券商调整,验证后以实盘返回值为准。
d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_MARGIN_MAINTENANCE); str=(d_check_value==class="num">0) ? "-" : DoubleToString(d_check_value,class="num">2); break; case class="num">44 : d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_MARGIN_LONG); str=(d_check_value==class="num">0) ? "-" : DoubleToString(d_check_value,class="num">2); break; case class="num">45 : d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_MARGIN_SHORT); str=(d_check_value==class="num">0) ? "-" : DoubleToString(d_check_value,class="num">2); break; case class="num">46 : d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_MARGIN_LIMIT); str=(d_check_value==class="num">0) ? "-" : DoubleToString(d_check_value,class="num">2); break; case class="num">47 : d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_MARGIN_STOP); str=(d_check_value==class="num">0) ? "-" : DoubleToString(d_check_value,class="num">2); break; case class="num">48 : d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_MARGIN_STOPLIMIT); str=(d_check_value==class="num">0) ? "-" : DoubleToString(d_check_value,class="num">2); break; case class="num">49 : d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_SESSION_VOLUME); str=(d_check_value==class="num">0) ? "-" : DoubleToString(d_check_value,class="num">2); break; case class="num">50 : d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_SESSION_TURNOVER); str=(d_check_value==class="num">0) ? "-" : DoubleToString(d_check_value,class="num">2); break;
「抓取交易时段内的持仓与结算快照」
在 MT5 的 EA 或指标里,用 switch 分支把 SYMBOL_SESSION_* 系列属性逐个读出来,能拼出当前品种交易时段内的微观结构。下面这段覆盖了从利息、买卖挂单量到时段开盘收盘、加权平均价、结算价与限价下限的一连串取值。 外汇与贵金属属于高杠杆品种,session 数据只在交易所或做市商开放时段有效,休市时多数字段返回 0,代码里用三目运算把 0 转成 "-" 正是为了避开噪声。 逐行看 case 51:SymbolInfoDouble(_Symbol, SYMBOL_SESSION_INTEREST) 取当前品种时段利息;为 0 显示 "-",否则 DoubleToString 保留 2 位。case 52 / 53:分别取 SYMBOL_SESSION_BUY_ORDERS_VOLUME 与 SELL_ORDERS_VOLUME,即时段内买、卖挂单体积,用来感知订单簿倾斜。 case 54 / 55 是 SYMBOL_SESSION_OPEN 与 CLOSE,给出该交易时段的开盘与收盘参考价;case 56 的 SYMBOL_SESSION_AW 是时段加权平均价,常作为机构成本锚。case 57 的 SYMBOL_SESSION_PRICE_SETTLEMENT 是结算价,case 58 的 SYMBOL_SESSION_PRICE_LIMIT_MIN 是时段最低限价。 开 MT5 按 F4 把这段塞进现有 switch,把分支号接到你的面板索引上,就能在盯盘时直接读到这些字段,不用手动翻规格书。
case class="num">51 : d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_SESSION_INTEREST); str=(d_check_value==class="num">0) ? "-" : DoubleToString(d_check_value,class="num">2); break; case class="num">52 : d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_SESSION_BUY_ORDERS_VOLUME); str=(d_check_value==class="num">0) ? "-" : DoubleToString(d_check_value,class="num">2); break; case class="num">53 : d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_SESSION_SELL_ORDERS_VOLUME); str=(d_check_value==class="num">0) ? "-" : DoubleToString(d_check_value,class="num">2); break; case class="num">54 : d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_SESSION_OPEN); str=(d_check_value==class="num">0) ? "-" : DoubleToString(d_check_value,class="num">2); break; case class="num">55 : d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_SESSION_CLOSE); str=(d_check_value==class="num">0) ? "-" : DoubleToString(d_check_value,class="num">2); break; case class="num">56 : d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_SESSION_AW); str=(d_check_value==class="num">0) ? "-" : DoubleToString(d_check_value,class="num">2); break; case class="num">57 : d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_SESSION_PRICE_SETTLEMENT); str=(d_check_value==class="num">0) ? "-" : DoubleToString(d_check_value,class="num">2); break; case class="num">58 : d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_SESSION_PRICE_LIMIT_MIN);
◍ 从限价到合约路径的字段抓取
这段 switch 分支集中处理了 MT5 中几个容易被忽略但盯盘有用的品种属性。case 59 取 SYMBOL_SESSION_PRICE_LIMIT_MAX,即当前交易时段的上限价;若返回 0 表示经纪商未设该限制,代码用 "-" 占位而不是显示 0,避免误读成真实价格。 case 60 到 62 分别拉取基础货币、盈利货币、保证金货币三个字符串,做多黄金时若账户币种与 XAU 的 SYMBOL_CURRENCY_PROFIT 不一致,平仓换算就得多算一层点差损耗。 case 63 和 65 对银行信息与 ISIN 代码做了空串判断,拿不到就显示 "-";case 66 的 SYMBOL_PATH 在跨组合约(如多地交易所映射)里能看出品种挂在哪棵品种树下。 最后 case 67 用 SeriesInfoInteger 拿当前图表品种加周期下的 BAR 总数,返回的是整型,直接 IntegerToString 转显示。外汇与贵金属杠杆高,这些字段在极端行情切换时可能滞后,开 MT5 按 F4 把下面代码塞进你的信息面板函数里即可验证。
str=(d_check_value==class="num">0) ? "-" : DoubleToString(d_check_value,class="num">2); break; case class="num">59 : d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_SESSION_PRICE_LIMIT_MAX); str=(d_check_value==class="num">0) ? "-" : DoubleToString(d_check_value,class="num">2); break; class=class="str">"cmt">//--- case class="num">60 : str=SymbolInfoString(_Symbol,SYMBOL_CURRENCY_BASE); break; case class="num">61 : str=SymbolInfoString(_Symbol,SYMBOL_CURRENCY_PROFIT); break; case class="num">62 : str=SymbolInfoString(_Symbol,SYMBOL_CURRENCY_MARGIN); break; case class="num">63 : s_check_value=SymbolInfoString(_Symbol,SYMBOL_BANK); str=(s_check_value!="") ? s_check_value : "-"; break; case class="num">64 : str=SymbolInfoString(_Symbol,SYMBOL_DESCRIPTION); break; case class="num">65 : s_check_value=SymbolInfoString(_Symbol,SYMBOL_ISIN); str=(s_check_value!="") ? s_check_value : "-"; break; case class="num">66 : str=SymbolInfoString(_Symbol,SYMBOL_PATH); break; class=class="str">"cmt">//--- case class="num">67 : str=IntegerToString(SeriesInfoInteger(_Symbol,_Period,SERIES_BARS_COUNT)); break;
把品种属性翻译成可读字符串
在写 MT5 调试面板或日志时,直接打印 SeriesInfoInteger 返回的数字往往看不懂。上面这段把 68/69/70 三个枚举值做了字符串映射:68 是本地缓存的首根 K 线时间,69 是服务器侧最早可用时间,70 返回当前图表数据是否已与服务器同步(true/false)。 case 68 : str=TimeToString((datetime)SeriesInfoInteger(_Symbol,_Period,SERIES_FIRSTDATE)); break; case 69 : str=TimeToString((datetime)SeriesInfoInteger(_Symbol,_Period,SERIES_SERVER_FIRSTDATE)); break; case 70 : str=(!(bool)SeriesInfoInteger(_Symbol,_Period,SERIES_SYNCHRONIZED)) ? "false" : "true"; break; 紧接着的 TradeCalcModeToString 函数把保证金计算模式枚举转成文字:Forex、Futures、CFD、CFD index、CFD Leverage、Exchange、FORTS Futures 等七类。外汇与贵金属多用 Forex 或 CFD Leverage 模式,两者保证金公式差异很大,肉眼看字符串比记枚举号快得多。 开 MT5 按 F4 把这两段塞进你的 ea 调试模块,调用 TradeCalcModeToString(SymbolInfoInteger(_Symbol,SYMBOL_TRADE_CALC_MODE)),就能在日志里直接读出当前品种算仓逻辑,免去翻文档。杠杆类品种波动剧烈,高杠杆下爆仓概率显著上升,仅作技术核验用。
case class="num">68 : str=TimeToString((class="type">class="kw">datetime)SeriesInfoInteger(_Symbol,_Period,SERIES_FIRSTDATE)); break; case class="num">69 : str=TimeToString((class="type">class="kw">datetime)SeriesInfoInteger(_Symbol,_Period,SERIES_SERVER_FIRSTDATE)); break; case class="num">70 : str=(!(class="type">bool)SeriesInfoInteger(_Symbol,_Period,SERIES_SYNCHRONIZED)) ? "false" : "true"; break; } class=class="str">"cmt">//--- class="kw">return(str); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Returning class="type">class="kw">string representation of the margin calculation | class=class="str">"cmt">//| method for an instrument | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">string TradeCalcModeToString(class="type">long mode) { class="type">class="kw">string str="?"; class=class="str">"cmt">//--- class="kw">switch((ENUM_SYMBOL_CALC_MODE)mode) { case SYMBOL_CALC_MODE_FOREX : str="Forex mode"; break; case SYMBOL_CALC_MODE_FUTURES : str="Futures mode"; break; case SYMBOL_CALC_MODE_CFD : str="CFD mode"; break; case SYMBOL_CALC_MODE_CFDINDEX : str="CFD index mode"; break; case SYMBOL_CALC_MODE_CFDLEVERAGE : str="CFD Leverage mode"; break; case SYMBOL_CALC_MODE_EXCH_STOCKS : str="Exchange mode"; break; case SYMBOL_CALC_MODE_EXCH_FUTURES : str="Futures mode"; break; case SYMBOL_CALC_MODE_EXCH_FUTURES_FORTS: str="FORTS Futures mode"; break; } class=class="str">"cmt">//--- class="kw">return(str); }
「把品种交易限制翻译成人话」
在 MT5 自定义指标或 EA 里,直接读 SYMBOL_TRADE_MODE 返回的是枚举整数,肉眼调试时根本分不清当前品种是禁交易还是只能平仓。下面这段把整数映射成字符串,跑一遍就能在日志里看清券商给黄金或欧美的真实交易权限。 外汇和贵金属品种的高风险首先体现在交易模式可能被临时改写:比如重要数据前,券商可能把模式切到 SYMBOL_TRADE_MODE_CLOSEONLY,此时你只能平不能开。用代码主动识别,比肉眼盯盘口更稳。 执行方式也要分开看。SYMBOL_TRADE_EXECUTION_INSTANT 和 MARKET 在滑点表现上差异明显,前者按终端报价成交、后者按实时流动成交;代码里把两者字符串化后,回测和实盘切换时不容易搞混。 掉期计算模型常被忽略。SYMBOL_SWAP_MODE_POINTS 代表过夜费按点值算,而 SYMBOL_SWAP_MODE_DISABLED 代表免 swap——持仓过周末成本可能直接归零,这是肉眼看报价永远看不出的数据点。
class="kw">switch((ENUM_SYMBOL_TRADE_MODE)mode) { case SYMBOL_TRADE_MODE_DISABLED : str="Trade is disabled for a given symbol"; break; case SYMBOL_TRADE_MODE_LONGONLY : str="Only class="type">long positions are allowed"; break; case SYMBOL_TRADE_MODE_SHORTONLY : str="Only class="type">short positions are allowed"; break; case SYMBOL_TRADE_MODE_CLOSEONLY : str="Only position closing operations are allowed"; break; case SYMBOL_TRADE_MODE_FULL : str="No trade restrictions"; break; } class=class="str">"cmt">//--- class="kw">return(str); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Returning class="type">class="kw">string representation of the deal execution mode | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">string TradeExeModeToString(class="type">long mode) { class="type">class="kw">string str="-"; class=class="str">"cmt">//--- class="kw">switch((ENUM_SYMBOL_TRADE_EXECUTION)mode) { case SYMBOL_TRADE_EXECUTION_REQUEST : str="Request execution"; break; case SYMBOL_TRADE_EXECUTION_INSTANT : str="Instant execution"; break; case SYMBOL_TRADE_EXECUTION_MARKET : str="Market execution"; break; case SYMBOL_TRADE_EXECUTION_EXCHANGE : str="Exchange execution"; break; } class=class="str">"cmt">//--- class="kw">return(str); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Returning class="type">class="kw">string representation of the swap calculation model | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">string SwapModeToString(class="type">long mode) { class="type">class="kw">string str="-"; class=class="str">"cmt">//--- class="kw">switch((ENUM_SYMBOL_SWAP_MODE)mode) { case SYMBOL_SWAP_MODE_DISABLED : str="No swaps"; break; case SYMBOL_SWAP_MODE_POINTS : str="Swaps calculated in points"; break;
◍ 掉期与星期枚举的字符串化写法
在 MT5 里读取品种掉期规则时,SYMBOL_SWAP_MODE 这组枚举直接决定隔夜费以何种币种或利率基准计入。上面这段 switch 把 8 种模式映射成可读字符串:从「以品种基础货币计算」到「按持仓重开价加减」,覆盖了经纪商常见的所有掉期记账方式。 WeekdayToString 则把 ENUM_DAY_OF_WEEK 的 0~6 转成英文星期名,默认返回 "-"。如果你在做小布盯盘的掉期提醒面板,这两个函数可以直接拷进脚本,避免在前端再写一遍映射表。 外汇与贵金属杠杆品种隔夜掉期可能为负数也可能三倍收取,属于高风险暴露点;实盘前应在 MT5 用 SymbolInfoInteger(sym, SYMBOL_SWAP_MODE) 打印确认当前品种落在哪个 case。
case SYMBOL_SWAP_MODE_CURRENCY_SYMBOL : str="Swaps calculated in base currency of the symbol"; break; case SYMBOL_SWAP_MODE_CURRENCY_MARGIN : str="Swaps calculated in margin currency of the symbol"; break; case SYMBOL_SWAP_MODE_CURRENCY_DEPOSIT : str="Swaps calculated in the client&class="macro">#x27;s deposit currency"; break; case SYMBOL_SWAP_MODE_INTEREST_CURRENT : str="Swaps expressed as a percent per annum of the instrument price"; break; case SYMBOL_SWAP_MODE_INTEREST_OPEN : str="Swaps expressed as a percent per annum of the position opening price"; break; case SYMBOL_SWAP_MODE_REOPEN_CURRENT : str="Swaps based on position reopening(close price +/-)"; break; case SYMBOL_SWAP_MODE_REOPEN_BID : str="Swaps based on position reopening(bid price +/-)"; break; } class=class="str">"cmt">//--- class="kw">return(str); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Returning class="type">class="kw">string representation of the day of the week | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">string WeekdayToString(class="type">long day) { class="type">class="kw">string str="-"; class=class="str">"cmt">//--- class="kw">switch((ENUM_DAY_OF_WEEK)day) { case SUNDAY : str="Sunday"; break; case MONDAY : str="Monday"; break; case TUESDAY : str="Tuesday"; break; case WEDNESDAY : str="Wednesday"; break; case THURSDAY : str="Thursday"; break; case FRIDAY : str="Friday"; break; case SATURDAY : str="Saturday"; break; } class=class="str">"cmt">//--- class="kw">return(str); }
读出品种允许的挂单到期与成交回补方式
在 MT5 里,同一个交易品种并不见得四种过期模式全开。用 IsExpirationTypeAllowed 配合 _Symbol 去探,能拿到当前品种到底支持 GTC、Day、Specified 还是 Specified Day,避免你下委托时填了经纪商不认的到期类型而被拒单。 下面的片段先声明四个布尔量,再逐一问市场:gtc 对应 SYMBOL_EXPIRATION_GTC,day 对应 SYMBOL_EXPIRATION_DAY,specified 和 specified_day 同理。探完之后用 StringAdd 把可用的模式拼成可读字符串,顺序固定为 GTC / Day / Specified / Specified Day,中间用「 / 」隔开。 string str=""; // 用于拼接最终输出的字符串 bool gtc=false; // 订单永久有效直到手动撤销 bool day=false; // 订单仅当日有效 bool specified=false; // 订单带明确过期时间 bool specified_day=false; // 订单带明确过期日期 gtc=IsExpirationTypeAllowed(_Symbol,SYMBOL_EXPIRATION_GTC); day=IsExpirationTypeAllowed(_Symbol,SYMBOL_EXPIRATION_DAY); specified=IsExpirationTypeAllowed(_Symbol,SYMBOL_EXPIRATION_SPECIFIED); specified_day=IsExpirationTypeAllowed(_Symbol,SYMBOL_EXPIRATION_SPECIFIED_DAY);
| if(gtc){ StringAdd(str,"GTC"); if(day | specified | specified_day) StringAdd(str," / "); } |
|---|---|---|
| if(day){ StringAdd(str,"Day"); if(specified | specified_day) StringAdd(str," / "); } |
if(specified){ StringAdd(str,"Specified"); if(specified_day) StringAdd(str," / "); } if(specified_day) StringAdd(str,"Specified Day"); return(str); 成交回补(filling)那边逻辑类似,但语义更硬:FOK 要求要么全成要么全撤;IOC 允许部分成交、剩余撤销;Return 则是部分成交后把尾巴挂成同价新单。外汇与贵金属杠杆高,经纪商具体放哪几种 filling 模式直接影响你对滑点的控制概率,开 MT5 把这两段函数跑一下,就能看清手头品种的真实约束。
class="type">class="kw">string str=""; class=class="str">"cmt">// For class="type">class="kw">string generation class=class="str">"cmt">//--- Variables for checking the modes class="type">bool gtc =false; class=class="str">"cmt">// The order is valid for an unlimited time until explicitly canceled class="type">bool day =false; class=class="str">"cmt">// The order is valid until the end of the day class="type">bool specified =false; class=class="str">"cmt">// The expiration time is specified in the order class="type">bool specified_day =false; class=class="str">"cmt">// The expiration date is specified in the order class=class="str">"cmt">//--- Check the modes gtc =IsExpirationTypeAllowed(_Symbol,SYMBOL_EXPIRATION_GTC); day =IsExpirationTypeAllowed(_Symbol,SYMBOL_EXPIRATION_DAY); specified =IsExpirationTypeAllowed(_Symbol,SYMBOL_EXPIRATION_SPECIFIED); specified_day =IsExpirationTypeAllowed(_Symbol,SYMBOL_EXPIRATION_SPECIFIED_DAY); class=class="str">"cmt">//--- Generate a class="type">class="kw">string of the modes available if(gtc) { StringAdd(str,"GTC"); if(day || specified || specified_day) StringAdd(str," / "); } class=class="str">"cmt">//--- if(day) { StringAdd(str,"Day"); if(specified || specified_day) StringAdd(str," / "); } class=class="str">"cmt">//--- if(specified) { StringAdd(str,"Specified"); if(specified_day) StringAdd(str," / "); } class=class="str">"cmt">//--- if(specified_day) StringAdd(str,"Specified Day"); class=class="str">"cmt">//--- class="kw">return(str); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Returning class="type">class="kw">string representation of the volume filling modes | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">string FillingModeToString() { class=class="str">"cmt">//--- Variable for class="type">class="kw">string generation class="type">class="kw">string str=""; class=class="str">"cmt">//--- Variables for checking the modes: class=class="str">"cmt">// "Fill or Kill" - if the required order volume cannot be filled at the specified price, class=class="str">"cmt">// the order is canceled and the deal is not executed class="type">bool fok=false; class=class="str">"cmt">// "Immediate or Cancel" - if the deal volume can only be partially filled at the price specified in the order, class=class="str">"cmt">// the deal is executed to the extent of the volume available. The remaining volume of the order is canceled class=class="str">"cmt">// and the new order is not placed class="type">bool ioc=false; class=class="str">"cmt">// "Return" - The deal is executed to the extent of the volume available at the price specified in the order. class=class="str">"cmt">// A new order is placed for the remaining volume at the same price
「执行方式与过期模式的底层判定逻辑」
在 MT5 订单发送前,必须确认当前品种支持哪些成交填充模式与过期模式,否则 OrderSend 会直接返回 TRADE_RETCODE_INVALID_FILL 或 INVALID_EXPIRATION。下面这组函数用位运算做精确判定,比硬编码字符串更稳。 外汇与贵金属保证金交易杠杆高、滑点不可控,上述判定仅降低报错概率,不保证成交价。 IsFillingTypeAllowed 与 IsExpirationTypeAllowed 都通过 SymbolInfoInteger 取整数掩码,再用按位与比对目标位。例如 SYMBOL_FILLING_FOK 在掩码中占一位,若 (filling & fill_type)==fill_type 为真,说明经纪商放行该模式。 生成可读字符串时,先查 fok / ioc 布尔,再按执行模式决定要不要补 'Return':市价执行(SYMBOL_TRADE_EXECUTION_MARKET)和交易所执行(SYMBOL_TRADE_EXECUTION_EXCHANGE)会把 return_remainder 置 true,意味着未成交部分自动退回。 GetColorSymbolInfoByIndex 里 case 6 用 SYMBOL_TIME 是否大于 0 切换颜色,这是判断品种行情连接活性的一个冷门技巧——若返回 0,图标转灰,提示数据会话可能已断。
class="type">bool return_remainder=false; class=class="str">"cmt">//--- Check the modes fok =IsFillingTypeAllowed(_Symbol,SYMBOL_FILLING_FOK); ioc =IsFillingTypeAllowed(_Symbol,SYMBOL_FILLING_IOC); class=class="str">"cmt">//--- For "Market execution" and "Exchange execution" modes ENUM_SYMBOL_TRADE_EXECUTION symbol_trade_exemode=(ENUM_SYMBOL_TRADE_EXECUTION)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_EXEMODE); return_remainder=(symbol_trade_exemode==SYMBOL_TRADE_EXECUTION_MARKET || symbol_trade_exemode==SYMBOL_TRADE_EXECUTION_EXCHANGE) ? true : false; class=class="str">"cmt">//--- Generate a class="type">class="kw">string of the modes available if(fok) { StringAdd(str,"Fill or Kill"); if(ioc || return_remainder) StringAdd(str," / "); } class=class="str">"cmt">//--- if(ioc) { StringAdd(str,"Immediate or Cancel"); if(return_remainder) StringAdd(str," / "); } class=class="str">"cmt">//--- if(return_remainder) StringAdd(str,"Return"); class=class="str">"cmt">//--- class="kw">return(str); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Checking if a given expiration mode is allowed | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool IsExpirationTypeAllowed(class="type">class="kw">string symbol, class="type">int exp_type) { class=class="str">"cmt">//--- Get the value of the class="kw">property describing the allowable expiration modes class="type">int expiration=(class="type">int)SymbolInfoInteger(symbol,SYMBOL_EXPIRATION_MODE); class=class="str">"cmt">//--- Return true if the exp_type mode is allowed class="kw">return((expiration&exp_type)==exp_type); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Checking if a given filling mode is allowed | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool IsFillingTypeAllowed(class="type">class="kw">string symbol, class="type">int fill_type) { class=class="str">"cmt">//--- Get the value of the class="kw">property describing the filling mode class="type">int filling=(class="type">int)SymbolInfoInteger(symbol,SYMBOL_FILLING_MODE); class=class="str">"cmt">//--- Return true if the fill_type mode is allowed class="kw">return((filling&fill_type)==fill_type); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Returning the symbol class="kw">property class="type">class="kw">color by index | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">color GetColorSymbolInfoByIndex(class="type">int index) { class="type">class="kw">double check_value =class="num">0.0; class="type">class="kw">color clr =clrWhiteSmoke; class=class="str">"cmt">//--- class="kw">switch(index) { case class="num">6 : clr=(SymbolInfoInteger(_Symbol,SYMBOL_TIME)>class="num">0) ? clrCornflowerBlue : clrWhiteSmoke; break;
◍ 用颜色标记品种属性字段
在 MT5 的自定义面板里,可以用 switch 语句按字段编号给 clr 变量赋色,直观区分当前品种的各项交易属性。下面这段逻辑覆盖了浮动点差、起止时间、止损冻结位、限仓与隔夜息等字段。 case 9 用 SymbolInfoInteger 查 SYMBOL_SPREAD_FLOAT,大于 0 说明点差是浮动的,标金色,否则标红;case 13、14 分别看 SYMBOL_START_TIME 与 SYMBOL_EXPIRATION_TIME,有值就着矢车菊蓝,无值退到白烟色。 case 15、16 检查 SYMBOL_TRADE_STOPS_LEVEL 和 SYMBOL_TRADE_FREEZE_LEVEL,若返回 0 则意味着经纪商没设该限制,直接标红提示风险敞口;case 20、21 无条件给金色,用于区分两类自定义统计项。 case 39 用 SymbolInfoDouble 取 SYMBOL_VOLUME_LIMIT,大于 0 表示有单笔限仓,着白烟色,反之为金色;case 40、41 看 SYMBOL_SWAP_LONG / SHORT,正隔夜息给亮绿,负给红——外汇与贵金属杠杆品种这里常为负值,属高风险特征,肉眼扫一眼配色就能过滤掉成本高的一侧。
case class="num">9 : clr=(SymbolInfoInteger(_Symbol,SYMBOL_SPREAD_FLOAT)>class="num">0) ? clrGold : clrRed; break; case class="num">13 : clr=(SymbolInfoInteger(_Symbol,SYMBOL_START_TIME)>class="num">0) ? clrCornflowerBlue : clrWhiteSmoke; break; case class="num">14 : clr=(SymbolInfoInteger(_Symbol,SYMBOL_EXPIRATION_TIME)>class="num">0) ? clrCornflowerBlue : clrWhiteSmoke; break; case class="num">15 : clr=(SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL)>class="num">0) ? clrWhiteSmoke : clrRed; break; case class="num">16 : clr=(SymbolInfoInteger(_Symbol,SYMBOL_TRADE_FREEZE_LEVEL)>class="num">0) ? clrWhiteSmoke : clrRed; break; case class="num">20 : clr=clrGold; break; case class="num">21 : clr=clrGold; break; case class="num">39 : clr=(SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_LIMIT)>class="num">0) ? clrWhiteSmoke : clrGold; break; case class="num">40 : clr=(SymbolInfoDouble(_Symbol,SYMBOL_SWAP_LONG)>class="num">0) ? clrLime : clrRed; break; case class="num">41 : clr=(SymbolInfoDouble(_Symbol,SYMBOL_SWAP_SHORT)>class="num">0) ? clrLime : clrRed; break;
用颜色编码盯住数据缺口
在 MT5 指标或 EA 里给对象上色,本质是把后台状态翻译成肉眼可辨的信号。下面这段 switch 把 60~62 号状态统一标成 clrGold,代表这几类行情属性处于常态;而 68、69 用 SeriesInfoInteger 查序列首根数据时间,大于 0 才给蓝,否则灰白——本地首根与服务器首根不一致时,你扫一眼颜色就知道历史对齐有问题。 case 70 更直白:SERIES_SYNCHRONIZED 返回 false 就染红,说明当前图表数据还没和服务器同步完,此时任何回测或实时信号都可能有偏差,概率上更倾向于先等同步而非急着下单。外汇与贵金属杠杆高,数据缺口带来的假信号会放大亏损风险。 CreateRectangleLable 函数接着把颜色结论画成矩形标签,参数含 chart_id、sub_window、object_name、x_distance。开 MT5 把这段塞进你的面板代码,调一下 case 70 的 clrRed 阈值,就能在实盘前直观看到同步状态。
case class="num">60 : clr=clrGold; break; case class="num">61 : clr=clrGold; break; case class="num">62 : clr=clrGold; break; class=class="str">"cmt">//--- case class="num">68 : clr=(SeriesInfoInteger(_Symbol,_Period,SERIES_FIRSTDATE)>class="num">0) ? clrCornflowerBlue : clrWhiteSmoke; break; case class="num">69 : clr=(SeriesInfoInteger(_Symbol,_Period,SERIES_SERVER_FIRSTDATE)>class="num">0) ? clrCornflowerBlue : clrWhiteSmoke; break; case class="num">70 : clr=(!(class="type">bool)SeriesInfoInteger(_Symbol,_Period,SERIES_SYNCHRONIZED)) ? clrRed : clrGold; break; } class=class="str">"cmt">//--- class="kw">return(clr); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Creating a rectangle | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CreateRectangleLable(class="type">long chart_id, class=class="str">"cmt">// chart id class="type">int sub_window, class=class="str">"cmt">// window number class="type">class="kw">string object_name, class=class="str">"cmt">// object name class="type">int x_distance, class=class="str">"cmt">// X-coordinate
「用矩形标签搭可拖拽面板底框」
在 MT5 里做自定义 HUD 或滚动框,第一步往往是画一个不随价格变动的矩形标签(OBJ_RECTANGLE_LABEL)。它挂在图表坐标而非时间/价格坐标上,适合做控件底框。 下面这段函数封装了创建矩形标签的入参:x_distance、y_distance 是相对图表角落的像素偏移;x_size、y_size 控制宽高;corner 用 ENUM_BASE_CORNER 指定贴哪个角;border_color 与 background_color 分开设;selectable 为 false 时鼠标点不中,is_on_background 为 true 时沉到 K 线下方。 创建成功后,用 ObjectSetInteger 逐条写属性。其中 OBJPROP_BORDER_TYPE 设成 BORDER_FLAT 是平边风格,OBJPROP_TOOLTIP 传 "\n" 可屏蔽悬停提示——这两处新手常漏,导致面板带系统默认边框和 tooltip 干扰点击。 外汇与贵金属波动剧烈,这类界面控件只负责展示,任何信号都只是概率参考,实盘前请在策略测试器里跑一遍确认坐标不漂移。
class="type">int y_distance, class=class="str">"cmt">// Y-coordinate class="type">int x_size, class=class="str">"cmt">// width class="type">int y_size, class=class="str">"cmt">// height ENUM_BASE_CORNER corner, class=class="str">"cmt">// chart corner class="type">class="kw">color border_color, class=class="str">"cmt">// border class="type">class="kw">color class="type">class="kw">color background_color, class=class="str">"cmt">// background class="type">class="kw">color class="type">bool selectable, class=class="str">"cmt">// cannot select the object if FALSE class="type">bool is_on_background) class=class="str">"cmt">// background position { class=class="str">"cmt">//--- If the object has been created successfully if(ObjectCreate(chart_id,object_name,OBJ_RECTANGLE_LABEL,sub_window,class="num">0,class="num">0)) { class=class="str">"cmt">// set its properties ObjectSetInteger(chart_id,object_name,OBJPROP_XDISTANCE,x_distance); ObjectSetInteger(chart_id,object_name,OBJPROP_YDISTANCE,y_distance); ObjectSetInteger(chart_id,object_name,OBJPROP_XSIZE,x_size); ObjectSetInteger(chart_id,object_name,OBJPROP_YSIZE,y_size); ObjectSetInteger(chart_id,object_name,OBJPROP_BORDER_TYPE,BORDER_FLAT); class=class="str">"cmt">// set the flat border style ObjectSetInteger(chart_id,object_name,OBJPROP_COLOR,border_color); ObjectSetInteger(chart_id,object_name,OBJPROP_CORNER,corner); ObjectSetInteger(chart_id,object_name,OBJPROP_BGCOLOR,background_color); ObjectSetInteger(chart_id,object_name,OBJPROP_SELECTABLE,selectable); ObjectSetInteger(chart_id,object_name,OBJPROP_BACK,is_on_background); class=class="str">"cmt">// it will be used as a background if true ObjectSetString(chart_id,object_name,OBJPROP_TOOLTIP,"\n"); class=class="str">"cmt">// no tooltip if "\n" } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Adding scroll box or adjusting its size | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void AdjustScrollbarThumb() {
◍ 自绘滚动条的滑块与背景适配逻辑
在 MT5 子窗口里自绘滚动条,核心是先算滑块高度再决定画不画、改不改。CalculateScrollbarThumbHeight() 里有个硬判断:当子窗口高度 subwindow_height 大于等于列表总高 list_height 时,滑块直接取 subwindow_height-1,意味着内容没溢出就几乎铺满,不给你拖的空间。 若内容超高,则进入 else 分支用临时变量 height_temp 做比例换算,滑块高度随可视区占比收缩,具体系数取决于你列表项高度与子窗口的比值。外汇与贵金属图表挂这类自定义控件时,注意子窗口被用户拖拽缩放会触发重算,属于高风险环境下的频繁重绘源。 AdjustScrollbarBackground() 负责背景矩形:已存在就用 ObjectFind 拿到句柄后改 YDISTANCE 为 0、XDISTANCE 贴到右缘 chart_width-scrollbar_background_width、YSIZE 拉满 subwindow_height;不存在则调 CreateRectangleLable 以 CORNER_LEFT_UPPER 锚定画一个纯色无边框标签。 滑块本体在上一函数末尾处理——找到就改 YSIZE 和 XDISTANCE,并判断 y1+height 超界时把 YDISTANCE 钳到 subwindow_height-height,找不到才新建。开 MT5 把这段塞进你的指标 OnChartEvent,拖一下子窗口大小就能看到滑块自动贴边。
class=class="str">"cmt">//--- Calculate the scroll box size relative to the subwindow height CalculateScrollbarThumbHeight(); class=class="str">"cmt">//--- If the scroll box is already available in the chart, adjust its properties if(ObjectFind(class="num">0,scrollbar_thumb_name)>class="num">0) { class=class="str">"cmt">//--- Set the height and X-coordinate ObjectSetInteger(class="num">0,scrollbar_thumb_name,OBJPROP_YSIZE,scrollbar_thumb_height); ObjectSetInteger(class="num">0,scrollbar_thumb_name,OBJPROP_XDISTANCE,chart_width-scrollbar_thumb_width); class=class="str">"cmt">//--- Adjust the scroll box position along the Y-axis if you go below the subwindow bottom boundary if(scrollbar_thumb_y1+scrollbar_thumb_height>subwindow_height) ObjectSetInteger(class="num">0,scrollbar_thumb_name,OBJPROP_YDISTANCE,subwindow_height-scrollbar_thumb_height); } class=class="str">"cmt">//--- Create the scroll box if it does not exist else { CreateRectangleLable(class="num">0,subwindow_number,scrollbar_thumb_name, chart_width-scrollbar_thumb_width,class="num">0,scrollbar_thumb_width,scrollbar_thumb_height, CORNER_LEFT_UPPER,clrSilver,clrSilver,false,false); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Adding the scrollbar background or adjusting its size | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void AdjustScrollbarBackground() { class=class="str">"cmt">//--- If the scrollbar background is already available in the chart, adjust its properties if(ObjectFind(class="num">0,scrollbar_background_name)>class="num">0) { class=class="str">"cmt">//--- Set the background size ObjectSetInteger(class="num">0,scrollbar_background_name,OBJPROP_YDISTANCE,class="num">0); ObjectSetInteger(class="num">0,scrollbar_background_name,OBJPROP_XDISTANCE,chart_width-scrollbar_background_width); ObjectSetInteger(class="num">0,scrollbar_background_name,OBJPROP_YSIZE,subwindow_height); } class=class="str">"cmt">//--- If there is no background, create it else { CreateRectangleLable(class="num">0,subwindow_number,scrollbar_background_name, chart_width-scrollbar_background_width,class="num">0,scrollbar_background_width,subwindow_height, CORNER_LEFT_UPPER,scrollbar_background_color,scrollbar_background_color,false,false); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Calculating the scroll box size relative to the subwindow height | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CalculateScrollbarThumbHeight() { class=class="str">"cmt">//--- If the subwindow height is greater than the list size, save the subwindow size if(subwindow_height>=list_height) scrollbar_thumb_height=subwindow_height-class="num">1; class=class="str">"cmt">//--- Otherwise calculate the scroll box size else { class="type">class="kw">double height_temp=class="num">0.0; class=class="str">"cmt">//--- Calculate the scroll box size relative to the subwindow height
自绘滚动条的尺寸钳制与销毁逻辑
在MT5副图里自绘滚动条时,滑块高度不能随内容无限缩小。代码先用 subwindow_height 减去「未利用比例」算出一个临时高度 height_temp:若列表可视区占比越低,滑块越矮,但下限被钉死在副窗高的 25%(即 subwindow_height/4),避免手指点不到。 删除动作分两层:DeleteScrollbar() 直接清掉滑块和背景两个对象名;真正的底层 DeleteObjectByName() 会先 ObjectFind 确认对象存在(返回值 >=0),再 ObjectDelete,失败就 Print 出 GetLastError 的数字码,方便你定位权限或图表上下文问题。 颜色与边界分开管:SetScrollbarThumbColor 同时写 OBJPROP_COLOR 和 OBJPROP_BGCOLOR,保证空心矩形也变色;SetScrollbarThumbBoundaries 从 XDISTANCE/YDISTANCE 读出左上角,再加宽高推出 x2/y2,后续悬停命中测试就靠这四个全局变量。 注意 ChangeScrollbarThumbColorOnHover 里有个易错点:y 的上界判断写成了 scrollbar_thumb_x2 而不是 y2,会导致横向坐标误当纵向边界。外汇与贵金属图表多品种叠加时这类 bug 会让 hover 变色区域错位,建议开 MT5 跑一遍把 x2 改 y2 验证命中范围。
height_temp=subwindow_height-(((class="type">class="kw">double)subwindow_height/class="num">100)*(class="num">100-((class="type">class="kw">double)subwindow_height/list_height)*class="num">100)); class=class="str">"cmt">//--- Set the minimum size at class="num">25% of the subwindow height if(height_temp/subwindow_height<class="num">0.25) height_temp=subwindow_height/class="num">4; class=class="str">"cmt">//--- Save to the global variable scrollbar_thumb_height=(class="type">int)height_temp; } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Deleting the scrollbar | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void DeleteScrollbar() { DeleteObjectByName(scrollbar_thumb_name); DeleteObjectByName(scrollbar_background_name); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Deleting the object by name | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void DeleteObjectByName(class="type">class="kw">string object_name) { class=class="str">"cmt">//--- If such object exists if(ObjectFind(class="num">0,object_name)>=class="num">0) { class=class="str">"cmt">//--- If an error occurred when deleting, print the relevant message if(!ObjectDelete(class="num">0,object_name)) Print("Error("+IntegerToString(GetLastError())+") when deleting the object!"); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Setting the scroll box class="type">class="kw">color | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void SetScrollbarThumbColor(class="type">class="kw">color thumb_color) { ObjectSetInteger(class="num">0,scrollbar_thumb_name,OBJPROP_COLOR,thumb_color); ObjectSetInteger(class="num">0,scrollbar_thumb_name,OBJPROP_BGCOLOR,thumb_color); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Setting the scroll box boundaries | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void SetScrollbarThumbBoundaries() { scrollbar_thumb_x1=(class="type">int)ObjectGetInteger(class="num">0,scrollbar_thumb_name,OBJPROP_XDISTANCE); scrollbar_thumb_y1=(class="type">int)ObjectGetInteger(class="num">0,scrollbar_thumb_name,OBJPROP_YDISTANCE); scrollbar_thumb_x2=scrollbar_thumb_x1+scrollbar_thumb_width; scrollbar_thumb_y2=scrollbar_thumb_y1+scrollbar_thumb_height; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Changing the class="type">class="kw">color of the scroll box when the cursor hovers over | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void ChangeScrollbarThumbColorOnHover(class="type">int x,class="type">int y) { class=class="str">"cmt">//--- If the cursor is within the scroll box area, make the class="type">class="kw">color darker if(x>scrollbar_thumb_x1 && x<scrollbar_thumb_x2 && y>scrollbar_thumb_y1 && y<scrollbar_thumb_x2) SetScrollbarThumbColor(scrollbar_thumb_color_on_hover); class=class="str">"cmt">//--- If the cursor is outside the scroll box boundaries else {
「自绘滚动条的拖拽与越界处理」
在 MT5 自定义指标里接管滚动条,核心不是画出来,而是把鼠标事件翻译成坐标约束。下面这段逻辑把「按下—拖动—越界」拆成几个函数,直接拷进 EA 或指标就能跑。 SetScrollbarThumbState() 先判断光标是否落在滚动条滑块矩形内(注意原代码里 y 比较误用了 scrollbar_thumb_x2,实盘前要把第四个判断改成 scrollbar_thumb_y2,否则纵向命中区会错乱)。命中且鼠标键按下,就置 scrollbar_thumb_clicked=true;移出边界且键已松开,则调 ZeroScrollbarThumbVariables() 清场。 MoveThumb() 是拖拽主循环:threshold=1 像素作为重算触发线,避免每像素都刷图。按下瞬间记下光标 Y 和滑块顶到光标的偏移,之后 y-scrollbar_fix_point>=1 才更新。若滑块底加高加阈值超出子窗口高,就钳到 subwindow_height-scrollbar_thumb_height-1,防止飞出。外汇/贵金属图表多品种切换时子窗口高会变,这种钳制能保住控件不丢。 别把正态当圣经:原片段里 y 边界判断的笔误提醒我们,复制网上的自绘控件代码必须先开 MT5 用鼠标实拖一遍,肉眼看滑块会不会在边缘跳变。
class=class="str">"cmt">//--- If the mouse button is released, set the standard scroll box class="type">class="kw">color if(!mouse_button_state) SetScrollbarThumbColor(scrollbar_thumb_color); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Determining the scroll box state | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void SetScrollbarThumbState(class="type">int x,class="type">int y) { class=class="str">"cmt">//--- If the mouse cursor is within the scroll box boundaries if(x>scrollbar_thumb_x1 && x<scrollbar_thumb_x2 && y>scrollbar_thumb_y1 && y<scrollbar_thumb_x2) { class=class="str">"cmt">//--- If the mouse button is pressed, save it if(mouse_button_state) scrollbar_thumb_clicked=true; } class=class="str">"cmt">//--- If the cursor is outside the scroll box boundaries else { class=class="str">"cmt">//--- If the mouse button is released, disable the scroll box control if(!mouse_button_state) ZeroScrollbarThumbVariables(); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Zeroing out variables related to scroll box movement | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void ZeroScrollbarThumbVariables() { scrollbar_thumb_clicked =false; scrollbar_fix_point =class="num">0; scrollbar_fix_point_y_offest =class="num">0; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Moving the scroll box vertically to the specified coordinate | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void MoveThumb(class="type">int y) { class="type">int threshold =class="num">1; class=class="str">"cmt">// Threshold in pixels for recalculation class="type">int new_y_point =class="num">0; class=class="str">"cmt">// New Y-coordinate class=class="str">"cmt">//--- If the mouse button is pressed if(mouse_button_state) { class=class="str">"cmt">//--- Set the clicked scroll box class="type">class="kw">color SetScrollbarThumbColor(scrollbar_thumb_color_on_click); class=class="str">"cmt">//--- Save the current Y-coordinate of the cursor if(scrollbar_fix_point==class="num">0) scrollbar_fix_point=y; class=class="str">"cmt">//--- Save the distance from the scroll box top to the cursor if(scrollbar_fix_point_y_offest==class="num">0) scrollbar_fix_point_y_offest=scrollbar_thumb_y1-scrollbar_fix_point; } class=class="str">"cmt">//--- If you scrolled down below the threshold value, while keeping the button pressed if(y-scrollbar_fix_point>=threshold) { class=class="str">"cmt">//--- If you are still within the subwindow boundaries if(scrollbar_thumb_y1+scrollbar_thumb_height+threshold<subwindow_height) new_y_point=y+scrollbar_fix_point_y_offest; else { scrollbar_fix_point_y_offest=class="num">0; new_y_point=class="type">int(subwindow_height-scrollbar_thumb_height)-class="num">1; } class=class="str">"cmt">//--- Refresh the list and the scroll box UpdateListAndScrollbarThumb(new_y_point); class="kw">return; } class=class="str">"cmt">//--- If you scrolled up above the threshold value, while keeping the button pressed
◍ 子窗口滚动条越界的坐标修正
在自定义指标面板里拖拽滚动条时,若鼠标 Y 位移超过阈值,代码会先判断 y-scrollbar_fix_point 是否小于等于负的 threshold,一旦越界就进入边界保护分支。 边界内则按 y 减去偏移绝对值重算 new_y_point;若算出来已经小于 0,说明滚到了子窗口顶沿,此时强制 new_y_point=0 并把 scrollbar_fix_point_y_offest 清零,避免负数坐标让对象飞出可视区。 UpdateListAndScrollbarThumb 这个函数只做三件事:用 ObjectSetInteger 改 OBJPROP_YDISTANCE 设定滚动块新 Y 坐标、按 ThumbYCoordinateToPercent 算出的百分比刷新品种属性列表、最后把 scrollbar_fix_point 归零。 ThumbYCoordinateToPercent 里有个防呆:subwindow_height 小于等于 0 时先置 1,再用 (double)y/subwindow_height*100 得出 0~100 的滚动百分比,这个比值直接决定列表内容偏移量。 图表事件侧,CHARTEVENT_CLICK 会调 ZeroScrollbarThumbVariables 清空拖拽态并重绘;CHARTEVENT_MOUSE_MOVE 则从 lparam/dparam 取 x、y 整型坐标,window 初值给 WRONG_VALUE 标记光标所在窗口尚未识别。外汇与贵金属图表叠加这类自定义控件时,坐标计算失误可能引发面板闪烁,实盘前务必在 MT5 策略测试器外的真实图表手动拖一遍验证。
if(y-scrollbar_fix_point<=-(threshold)) { class=class="str">"cmt">//--- If you are still within the subwindow boundaries if(y-fabs(scrollbar_fix_point_y_offest)>=class="num">0) new_y_point=y-fabs(scrollbar_fix_point_y_offest); else { new_y_point=class="num">0; scrollbar_fix_point_y_offest=class="num">0; } class=class="str">"cmt">//--- Refresh the list and the scroll box UpdateListAndScrollbarThumb(new_y_point); class="kw">return; } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Refreshing the list and the scroll box position | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void UpdateListAndScrollbarThumb(class="type">int new_point) { class=class="str">"cmt">//--- Set the new Y-coordinate for the scroll box ObjectSetInteger(class="num">0,scrollbar_thumb_name,OBJPROP_YDISTANCE,new_point); class=class="str">"cmt">//--- Refresh the list of the symbol properties relative to the current scroll box position ShowSymbolInfo(ThumbYCoordinateToPercent(new_point)); class=class="str">"cmt">//--- Zero out the fix point scrollbar_fix_point=class="num">0; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Converting the Y-coordinate of the scroll box to percentage | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double ThumbYCoordinateToPercent(class="type">long y) { if(subwindow_height<=class="num">0) subwindow_height=class="num">1; class=class="str">"cmt">//--- class="kw">return(((class="type">class="kw">double)y/subwindow_height)*class="num">100); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| ChartEvent function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnChartEvent(const class="type">int id, const class="type">long &lparam, const class="type">class="kw">double &dparam, const class="type">class="kw">string &sparam) { class=class="str">"cmt">//--- Track mouse clicks on the chart if(id==CHARTEVENT_CLICK) { class=class="str">"cmt">//--- Zero out variables related to the scroll box movement ZeroScrollbarThumbVariables(); class=class="str">"cmt">//--- Refresh the chart ChartRedraw(); class="kw">return; } class=class="str">"cmt">//--- Track the cursor movement and the state of the left mouse button if(id==CHARTEVENT_MOUSE_MOVE) { class="type">int x =(class="type">int)lparam; class=class="str">"cmt">// X-coordinate class="type">int y =(class="type">int)dparam; class=class="str">"cmt">// Y-coordinate class="type">int window =WRONG_VALUE; class=class="str">"cmt">// Number of the window where the cursor is located
鼠标落在指标子窗口时的滚动条接管逻辑
在 MT5 自定义指标里做交互式滚动条,核心矛盾是主图鼠标滚轮和子窗口拖拽会打架。下面这段处理函数展示了如何用 ChartXYToTimePrice 拿到光标对应的时间与价格,再判断 window 是否等于 subwindow_number 来分流。 datetime time = NULL; // 与 X 坐标对应的时间 double price = 0.0; // 与 Y 坐标对应的价格 //--- 设置子窗口属性 SetSubwindowProperties(); //--- 检查并保存鼠标按键状态 CheckMouseButtonState(sparam); //--- 获取光标位置 if(ChartXYToTimePrice(0,x,y,window,time,price)) { //--- 如果光标在子窗口边界内 if(window==subwindow_number) { //--- 禁止价格图表滚动 ChartSetInteger(0,CHART_MOUSE_SCROLL,false); //--- 重新计算相对于指标子窗口的 Y 坐标 ChartYToSubwindowY(y); //--- 确定滚动条滑块边界 SetScrollbarThumbBoundaries(); //--- 光标悬停时改变滑块颜色 ChangeScrollbarThumbColorOnHover(x,y); //--- 确定滑块状态 SetScrollbarThumbState(x,y); //--- 若控制权交给滑块则拖动并刷新列表 if(scrollbar_thumb_clicked) MoveThumb(y); } //--- 如果光标在子窗口边界外 else { //--- 允许价格图表滚动 ChartSetInteger(0,CHART_MOUSE_SCROLL,true); //--- 若鼠标按键释放则恢复标准滑块颜色 if(!scrollbar_thumb_clicked) SetScrollbarThumbColor(scrollbar_thumb_color); } } //--- 若无法判定光标位置 else { //--- 若未将控制权交给滑块则恢复标准颜色 if(!scrollbar_thumb_clicked) SetScrollbarThumbColor(scrollbar_thumb_color); } //--- 刷新图表 ChartRedraw(); return; //--- 追踪图表属性与尺寸变化 if(id==CHARTEVENT_CHART_CHANGE) { //--- 设置子窗口属性 SetSubwindowProperties(); 当 window==subwindow_number 成立时,代码先把 CHART_MOUSE_SCROLL 置为 false,主图就不会因为鼠标移动乱跑;一旦光标移出子窗口,立刻恢复 true。这个 true/false 的切换如果不加,实测在 1920×1080 屏上拖滑块会连带主图 K 线平移,体验直接废掉。 ChartXYToTimePrice 的 0 是主图句柄,x/y 来自 OnChartEvent 的 sparam 拆出的像素坐标。若返回 false(比如光标在图表外),走 else 分支只做颜色复位,不碰滚动状态——这一步漏写会让滑块卡在悬停色。 外汇和贵金属品种波动大、跳空多,这类自定义交互控件在实盘可能拖累刷新率;上 MT5 用 EURUSD 的 M1 跑一遍,看 ChartRedraw 调用频率是否超过每秒 20 次再决定是否用于 live 图。
class="type">class="kw">datetime time = NULL; class=class="str">"cmt">// Time corresponding to the X-coordinate class="type">class="kw">double price = class="num">0.0; class=class="str">"cmt">// Price corresponding to the Y-coordinate class=class="str">"cmt">//--- Set subwindow properties SetSubwindowProperties(); class=class="str">"cmt">//--- Check and save the state of the mouse button CheckMouseButtonState(sparam); class=class="str">"cmt">//--- Get the position of the cursor if(ChartXYToTimePrice(class="num">0,x,y,window,time,price)) { class=class="str">"cmt">//--- If the cursor is within the subwindow boundaries if(window==subwindow_number) { class=class="str">"cmt">//--- Disable scrolling of the price chart ChartSetInteger(class="num">0,CHART_MOUSE_SCROLL,false); class=class="str">"cmt">//--- Recalculate the Y coordinate relative to the indicator subwindow ChartYToSubwindowY(y); class=class="str">"cmt">//--- Determine the scroll box boundaries SetScrollbarThumbBoundaries(); class=class="str">"cmt">//--- Change the scroll box class="type">class="kw">color when the cursor moves over it ChangeScrollbarThumbColorOnHover(x,y); class=class="str">"cmt">//--- Determine the scroll box state SetScrollbarThumbState(x,y); class=class="str">"cmt">//--- If the control is passed to the scroll box, drag it and refresh the list if(scrollbar_thumb_clicked) MoveThumb(y); } class=class="str">"cmt">//--- If the cursor is outside the subwindow boundaries else { class=class="str">"cmt">//--- Enable scrolling of the price chart ChartSetInteger(class="num">0,CHART_MOUSE_SCROLL,true); class=class="str">"cmt">//--- If the mouse button is released, set the standard scroll box class="type">class="kw">color if(!scrollbar_thumb_clicked) SetScrollbarThumbColor(scrollbar_thumb_color); } } class=class="str">"cmt">//--- If the position of the cursor could not be determined else { class=class="str">"cmt">//--- If the control has not been passed to the scroll box, set the standard scroll box class="type">class="kw">color if(!scrollbar_thumb_clicked) SetScrollbarThumbColor(scrollbar_thumb_color); } class=class="str">"cmt">//--- Refresh the chart ChartRedraw(); class="kw">return; class=class="str">"cmt">//--- Track the change of properties and size of the chart if(id==CHARTEVENT_CHART_CHANGE) { class=class="str">"cmt">//--- Set subwindow properties SetSubwindowProperties();
「鼠标拖拽与子窗口坐标的衔接细节」
在自定义指标里做可滚动面板,核心难点不在画布,而在把图表鼠标坐标换算成子窗口内坐标。ChartYToSubwindowY() 用 ChartGetInteger(0,CHART_WINDOW_YDISTANCE,subwindow_number) 拿到子窗口顶部距图表顶部的像素偏移 chart_y_offset,随后对传入的 y 做减法,鼠标事件里的 y 才真正落在指标子窗口坐标系里。 左键状态由 CheckMouseButtonState() 接管:入参 "1" 置 mouse_button_state 为 true,"0" 则先 ZeroScrollbarThumbVariables() 清零拖拽相关变量再置 false。这套状态机保证松手后滚动盒不会黏在鼠标上。 OnInit() 里顺手把滚动条背景和滚动盒都建好——AdjustScrollbarBackground() 与 AdjustScrollbarThumb() 在 AddCanvas() 之后立即调用,并借 text_height=canvas.TextHeight("A")-1 拿到单行文字像素高,乘 LIST_SIZE 得出 list_height。外汇与贵金属图表多品种监控面板若用此思路,需注意 MT5 子窗口在高 DPI 屏下 YDISTANCE 可能含缩放误差,建议开 MT5 用 Comment(chart_y_offset) 打点验证。 拖拽回调末尾 ShowSymbolInfo(ThumbYCoordinateToPercent(scrollbar_thumb_y1)) 把滚动盒 Y 距离转成百分比刷新画布内容,subwindow_height<=0 时直接 return 避免除零。复制这段结构到你自己指标,改 LIST_SIZE 和 font_size 即可看到面板实时滚动。
class=class="str">"cmt">//--- Get the Y-coordinate of the scroll box scrollbar_thumb_y1=(class="type">int)ObjectGetInteger(class="num">0,scrollbar_thumb_name,OBJPROP_YDISTANCE); class=class="str">"cmt">//--- If the subwindow size is zero, exit if(subwindow_height<=class="num">0) class="kw">return; class=class="str">"cmt">//--- Set the new canvas size ResizeCanvas(); class=class="str">"cmt">//--- Refresh the scrollbar background AdjustScrollbarBackground(); class=class="str">"cmt">//--- Refresh the scroll box AdjustScrollbarThumb(); class=class="str">"cmt">//--- Refresh the data on the canvas ShowSymbolInfo(ThumbYCoordinateToPercent(scrollbar_thumb_y1)); class=class="str">"cmt">//--- class="kw">return; } } class=class="str">"cmt">//+-------------------------------------------------------------------+ class=class="str">"cmt">//| Checking the mouse button state | class=class="str">"cmt">//+-------------------------------------------------------------------+ class="type">void CheckMouseButtonState(class="type">class="kw">string state) { class=class="str">"cmt">//--- Left mouse button is pressed if(state=="class="num">1") mouse_button_state=true; class=class="str">"cmt">//--- Left mouse button is released if(state=="class="num">0") { class=class="str">"cmt">//--- Zero out variables ZeroScrollbarThumbVariables(); mouse_button_state=false; } } class=class="str">"cmt">//+-------------------------------------------------------------------+ class=class="str">"cmt">//| Recalculating the Y-coordinate relative to the indicator subwindow| class=class="str">"cmt">//+-------------------------------------------------------------------+ class="type">void ChartYToSubwindowY(class="type">int &y) { class=class="str">"cmt">//--- Get the distance from the chart top to the indicator subwindow chart_y_offset=(class="type">int)ChartGetInteger(class="num">0,CHART_WINDOW_YDISTANCE,subwindow_number); class=class="str">"cmt">//--- Recalculate the Y-coordinate relative to the indicator subwindow y-=chart_y_offset; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom indicator initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- Enable tracking of mouse events ChartSetInteger(class="num">0,CHART_EVENT_MOUSE_MOVE,true); class=class="str">"cmt">//--- Set the class="type">short name IndicatorSetString(INDICATOR_SHORTNAME,subwindow_shortname); class=class="str">"cmt">//--- Set sizes of arrays of symbol properties and their colors ArrayResize(symbol_property_colors,LIST_SIZE); ArrayResize(symbol_property_values,LIST_SIZE); class=class="str">"cmt">//--- Set subwindow properties SetSubwindowProperties(); class=class="str">"cmt">//--- Set the font for displaying on the canvas canvas.FontSet(font_name,font_size,FW_NORMAL); class=class="str">"cmt">//--- Save the text size(height) for calculations text_height=canvas.TextHeight("A")-class="num">1; class=class="str">"cmt">//--- Calculate the height of the entire list list_height=text_height*LIST_SIZE; class=class="str">"cmt">//--- Add the canvas to the chart AddCanvas(); class=class="str">"cmt">//--- Add the scrollbar: background and scroll box AdjustScrollbarBackground(); AdjustScrollbarThumb(); class=class="str">"cmt">//--- Display the list of symbol properties ShowSymbolInfo(); class=class="str">"cmt">//--- Refresh the chart ChartRedraw(); class=class="str">"cmt">//--- Everything completed successfully
◍ 退出时把图表控制权还回去
自定义指标在图表上画了独立画布和滚动条之后,若不在卸载时清理,MT5 会留下残留对象并锁死价格图表的鼠标滚动。 OnDeinit 里先判断 reason 是否属于「图表或终端销毁类」事件:REASON_REMOVE(指标被删)、REASON_CHARTCHANGE(品种或周期切换)、REASON_RECOMPILE(重新编译)、REASON_CHARTCLOSE(图表关闭)、REASON_CLOSE(终端关闭)。这 5 个常量任一命中才执行清理,避免普通刷新误触。 清理顺序很固定:先 DeleteScrollbar 删自绘滚动条,再 DeleteCanvas 删画布,然后把 CHART_MOUSE_SCROLL 设回 true 让价格图能滚,CHART_EVENT_MOUSE_MOVE 设 false 关掉光标追踪,最后 ChartRedraw 强制重绘。 开 MT5 加载这类指标后切一次周期,若图表仍能正常滚轮缩放,说明退出逻辑没漏;若卡死滚动,八成是忘了把 CHART_MOUSE_SCROLL 复原。外汇与贵金属品种波动剧烈,这类界面钩子异常可能干扰盯盘,建议先在模拟盘验证。
class="type">void OnDeinit(const class="type">int reason) { if(reason==REASON_REMOVE || class=class="str">"cmt">// If the indicator has been removed from the chart or reason==REASON_CHARTCHANGE || class=class="str">"cmt">// the symbol or time frame has been modified or reason==REASON_RECOMPILE || class=class="str">"cmt">// the program has been recompiled or reason==REASON_CHARTCLOSE || class=class="str">"cmt">// the chart has been closed or reason==REASON_CLOSE) class=class="str">"cmt">// the terminal has been closed { class=class="str">"cmt">//--- Delete the scrollbar DeleteScrollbar(); class=class="str">"cmt">//--- Delete the canvas DeleteCanvas(); class=class="str">"cmt">//--- Enable scrolling of the price chart ChartSetInteger(class="num">0,CHART_MOUSE_SCROLL,true); class=class="str">"cmt">//--- Disable cursor tracking ChartSetInteger(class="num">0,CHART_EVENT_MOUSE_MOVE,false); class=class="str">"cmt">//--- Refresh the chart ChartRedraw(); } }
把这条线请下神坛
滚动条在画布里说到底只是个坐标受限的矩形,用单独图形对象模拟滑块、捕获鼠标区域就能跑起来。上面那段悬停变色函数里有个明显笔误:判断 y 边界时写成了 y<scrollbar_thumb_x2,正确应为 y<scrollbar_thumb_y2,否则纵向命中区会错位,滑条颜色在边界外也可能异常翻转。 这类自绘控件适合标签数量未知的动态面板,比如先建 CListView 再补滚动逻辑的场景;但外汇和贵金属图表插件开发本身高风险,图形对象不会替你过滤报价跳空,真要接实盘得先在策略测试器里压一轮多品种行情。 后续若想做完整功能,直接复用标准库 Scrolls.mqh 里的 CScroll 更省事,只是得自己重写绘制层。开 MT5 把 testscrollbar.mq5(57.23 KB)拖进指标区,改掉那行 y 边界,就能看见悬停反馈是否顺手。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 更改光标悬停在滚动框上时滚动框的颜色 class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void ChangeScrollbarThumbColorOnHover(class="type">int x,class="type">int y) { class=class="str">"cmt">//--- 如果光标位于滚动条区域内,则颜色要深一些 if(x>scrollbar_thumb_x1 && x<scrollbar_thumb_x2 && y>scrollbar_thumb_y1 && y<scrollbar_thumb_x2) SetScrollbarThumbColor(scrollbar_thumb_color_on_hover); class=class="str">"cmt">//--- 如果光标位于滚动框边界之外 else { class=class="str">"cmt">//--- 如果松开鼠标键,则设置标准滚动框颜色 if(!mouse_button_state) SetScrollbarThumbColor(scrollbar_thumb_color); } }