MQL5 细则手册:指标子窗口控件 - 滚动条·综合运用
📘

MQL5 细则手册:指标子窗口控件 - 滚动条·综合运用

第 3/3 篇

◍ 把品种交易参数一次性读进面板

做价格行为分析前,先得搞清楚手里的品种到底怎么计费。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 可能差出十几倍,这是仓位计算的第一道关。

MQL5 / C++
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 各看一眼,就能验证哪些字段在当前券商环境下有数、哪些常年为空。外汇与贵金属杠杆高,保证金参数随券商调整,验证后以实盘返回值为准。

MQL5 / C++
      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,把分支号接到你的面板索引上,就能在盯盘时直接读到这些字段,不用手动翻规格书。

MQL5 / C++
   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 把下面代码塞进你的信息面板函数里即可验证。

MQL5 / C++
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)),就能在日志里直接读出当前品种算仓逻辑,免去翻文档。杠杆类品种波动剧烈,高杠杆下爆仓概率显著上升,仅作技术核验用。

MQL5 / C++
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——持仓过周末成本可能直接归零,这是肉眼看报价永远看不出的数据点。

MQL5 / C++
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。

MQL5 / C++
   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(dayspecifiedspecified_day) StringAdd(str," / "); }
if(day){ StringAdd(str,"Day"); if(specifiedspecified_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 把这两段函数跑一下,就能看清手头品种的真实约束。

MQL5 / C++
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,图标转灰,提示数据会话可能已断。

MQL5 / C++
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,正隔夜息给亮绿,负给红——外汇与贵金属杠杆品种这里常为负值,属高风险特征,肉眼扫一眼配色就能过滤掉成本高的一侧。

MQL5 / C++
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 阈值,就能在实盘前直观看到同步状态。

MQL5 / C++
   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 干扰点击。 外汇与贵金属波动剧烈,这类界面控件只负责展示,任何信号都只是概率参考,实盘前请在策略测试器里跑一遍确认坐标不漂移。

MQL5 / C++
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,拖一下子窗口大小就能看到滑块自动贴边。

MQL5 / C++
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 验证命中范围。

MQL5 / C++
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 用鼠标实拖一遍,肉眼看滑块会不会在边缘跳变。

MQL5 / C++
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 策略测试器外的真实图表手动拖一遍验证。

MQL5 / C++
  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 图。

MQL5 / C++
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 即可看到面板实时滚动。

MQL5 / C++
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 复原。外汇与贵金属品种波动剧烈,这类界面钩子异常可能干扰盯盘,建议先在模拟盘验证。

MQL5 / C++
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 边界,就能看见悬停反馈是否顺手。

MQL5 / C++
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);
     }
  }

常见问题

用品种信息枚举把交易参数批量抓进数组,再循环写进子窗口面板文本,避免手动逐个查。
按交易时段枚举过滤成交记录与保证金字段,汇总后直接输出到指标子窗口,不用切图表看。
可以,小布能读取品种属性与交易限制字段,自动翻译成白话面板,你打开对应品种页就能看。
多半是漏了当时段杠杆或结算快照的枚举抓取,把持仓与结算状态一并拉进来再比对即可。
建一个映射函数把枚举值转成中文标签,再绑定到子窗口控件,滚动查看不用来回查文档。