轻松快捷开发 MetaTrader 程序的函数库 (第十六部分) : 品种集合事件·综合运用
(3/3)·从账户事件到全品种事件集合,这套库如何把对象自治做到极致
- 事件基类的接口骨架
- 图形对象基类的存取与初始化
- 事件列表防重与小数位提取的实现细节
- 账户与品种对象的类层级骨架
- 让品种对象自己记住变化并抛出事件
- 看不见的交叉盘与可见品种哈希
- 符号遍历模式的枚举定义
- 品种事件标志位枚举怎么读
- 盯盘事件掩码的高位段怎么读
- 用事件常量盯紧品种异动
- 盯盘事件枚举里的止损与冻结位变动
- 品种事件枚举里的成交量与持仓异动
- 会话事件与整数属性枚举的底层定义
- 抓取品种交易属性与盘面结构参数
- 品种整数属性的收尾与排序基准
- 按市场属性给品种排序的枚举清单
- 品种排序枚举里的到期与报价字段
- 市场观察窗口的排序枚举后半段
- 按保证金与 session 维度给品种排序的枚举
- 按交易时段与字符串属性给品种排序的枚举
- 把品种属性装进一个结构体
- 日内极值与场内挂单量的结构字段
- 会话内订单流变量怎么挂
- 成交量与价差的控制变量怎么挂
- 点差与挂单距离的变动监控字段
- 日内极值与买卖报价的变动监控变量
- 日内极值与真实成交量的受控变量声明
- 日内真实成交量与期权阈值的变量落地
- 持仓成本与场内活跃度的监控变量
- 盯盘类里的会话维度变量怎么排
- 会话维度变化参数的初始化逻辑
- 盯盘结构体里的会话状态清零动作
- 盯盘类里的价格与关卡变更标记初始化
- 日内极值与成交量的状态位初始化
- 盯盘指标里的持仓与掉期状态归零
- 重置会话状态与初始化监控阈值
- 交易量及点差控制阈值的初始化设定
- 控价与控量参数的初始化门限
- 持仓与挂单增量的阈值设定
- 用位标志捕_symbol字段的逐帧变化
- 从事件标志到交易状态切换的落地逻辑
- 逐档盯住会话挂单的增减跳变
- 卖单衰减与成交量跳变的事件触发逻辑
- 日量极值与点差跳变的事件触发逻辑
- 止损位与冻结位的点数跳变捕获
- 日内买卖盘极值变动的事件触发逻辑
- Ask 侧事件触发与阈值判定
- 真实成交量异动如何触发事件
- 日内真实量与期权行权价的事件触发逻辑
- 品种事件里持仓上限与库存费的增量判定
- 用成交量与持仓异动触发事件
- 会话维度持仓与挂单量的事件触发逻辑
- 盯盘类里如何捕获时段挂单量与开收盘异动
- 用事件钩子盯住加权均价异动
- 按掉期算法取小数位的方法
- 用 CSymbol 封装直接读品种状态
- 从符号属性里抠交易会话细节
- 从 CSymbol 基类抠出品种属性接口
- 读取品种交易限制与到期属性的接口方法
- 从品种类里抠出挂单与实时报价属性
- 从报价对象里抠实时字段
- 从品种属性直接取tick与合约参数
- 品种手数与保证金的取数接口
- 挂单与持仓的保证金属性读取接口
- 挂单维持保证金与时段成交量的属性封装
- 用 BidLast 拿对报价取决于图表模式
- 按图表模式取当日高低与成交量异动跟踪
- 盯住本时段买卖挂单的增减阈值
- 卖单与成交量变动的存取接口
- 日内高低量阈值的存取封装
- 点差与止损位的变动阈值控制
- 冻结距离与报价的阈值监控接口
- 日内买卖盘极值变动的读取接口
- 买卖盘极值变动的阈值封装
- 日内 Ask 极值变动的控值与读取接口
- Ask低点变动与真实成交量的类封装接口
- 日内真实量的极值阈值接口
- 期权行权价与总量限制的变动接口
- 库存费与时段成交量的状态读取接口
- 会话维度的成交量与持仓量监控接口
- 盯盘类里怎么管会话内的买卖盘口增量
- 盯住时段开收盘价的变动阈值
- 会话加权均价与收盘价的变动判定接口
- 初始化前先把品种塞进市场报价窗
- 把品种整数属性一次性塞进结构体
- 把交易品种整型属性一次性塞进数组
- 把品种属性一次性塞进缓存数组
- 把品种限仓与库存费一次性塞进数组
- 把品种双精度属性一次性灌进数组
- 保证金与字符串属性的内部落盘
- 符号属性落库与描述映射的实现细节
- Market Watch 符号状态与当前会话挂单的读取分支
- 品种属性枚举里的日内量与报价精度
- 符号属性描述的分支拼装逻辑
- 交易规则类符号属性的文本映射
- 符号属性描述与存在性检查的底层写法
- 报价刷新与小数位识别的实现细节
- 刷新符号数据时先拉报价再补属性
- 把品种静态参数一次性塞进数组
- 把品种交易参数一次性灌进内存数组
- 把交易品种的双精度属性一次性灌进数组
- 卖单保证金与实时报价结构的填充逻辑
- 把品种交易参数一次性灌进结构体
- 把品种快照塞进结构体再算哈希
- 用哈希和捕捉品种状态突变
- 多品种对象的头文件与集合类骨架
- 盯盘面板里的品种容器与分类接口
- 按资产类别和属性筛符号的接口
- 品种集合类的访问器与构造接口
- 盯盘类里怎么管报价窗口的符号事件
- 在自选列表里定位品种的两个判断入口
- 按状态分流建符号对象的坑
- 按品种状态分流到对应对象类
- 外汇品种类的失败回退与继承骨架
- 品种集合的装载与事件开关
- 事件队列的批量刷新与图表推送
- 用哈希和捕捉报价窗口的增删排序
- 监听行情窗口增删品种的事件流
- 盯盘窗口删符号与排序的事件回传
- 按名称检索品种对象的两种写法
- 用事件分支给行情窗口变动做多语提示
- 把帐户类挂到基类上
- 账户快照的整型与浮点字段落盘
- 把账户核心字段一次性读进对象
- 账户结构体里的字段映射
- 账户事件追踪的结构骨架
- 账户集合对象的构造与刷新逻辑
- 账户状态变更的事件派发与哈希校验
- 把账户核心字段塞进结构体并累加校验和
- 账户快照里的保证金与负债累加逻辑
- 把品种事件接进引擎主类
- 交易事件与品种变更的存取接口
- 事件状态读取接口怎么用
- 引擎构造里的定时器与计数器的坑
- 引擎定时器里的历史与报价刷新分流
- 第二定时器驱动的全品种事件巡检
- 从事件队列里抠出最后一次动作描述
- EA里切换品种清单的启动拦截
- 全品种扫描前的确认与集合校验
- 回测环境下事件流的手动驱动
- 把库事件转成图表自定义事件再分流
- 按净收益锁定并平仓最赚钱的持仓
- 点差跳动在日志里长什么样
- 点差跳变在日志里长什么样
- 点差跳变在日志里长什么样
- 点差跳动的日志现场
- 点差在秒级跳动里的真实幅度
- 把这条线请下神坛
事件基类的接口骨架
在 MT5 自建指标或 EA 的事件框架里,基类往往先暴露一批只读接口,供子类继承后填充具体逻辑。上面这段声明里,DigitsCurrency() 直接返回 m_digits_currency 成员,用来拿到货币对报价的小数位数——比如 EURUSD 通常是 5 位、XAUUSD 常见 3 位,开 MT5 用 SymbolInfoInteger(_Symbol,SYMBOL_DIGITS) 对照就能验证。 GetListEvents() 返回内部 m_list_events 的指针,GetEventsTotal() 则调用 m_list_events.Total() 给出当前对象堆积的事件数量。这两个接口组合,意味着你可以在 OnTick 里轮询事件列表长度,判断这一帧是否有新事件落地。 EventAdd() 接收 ushort 事件 ID 加 lparam/dparam/sparam 三组参数,是把外部触发塞进列表的入口;IsEvent() 只回 m_is_event 布尔值,开销极低,适合做前置过滤。外汇与贵金属波动剧烈、滑点频繁,这类事件标记仅反映代码层状态,不代表行情必然延续,实盘须自行评估高风险。
class="type">int DigitsCurrency(class="type">void) const { class="kw">return this.m_digits_currency; } class=class="str">"cmt">//--- Returns the number of decimal places in the &class="macro">#x27;class="type">class="kw">double&class="macro">#x27; value class="type">int GetDigits(const class="type">class="kw">double value) const; class=class="str">"cmt">//--- Initialize the variables of(class="num">1) tracked, (class="num">2) controlled object data(implementation in the descendants) class="kw">virtual class="type">void InitChangesParams(class="type">void); class="kw">virtual class="type">void InitControlsParams(class="type">void); class=class="str">"cmt">//--- (class="num">1) Check the object change, class="kw">return the change code, (class="num">2) set the event type and fill in the list of events(implementation in the descendants) class="kw">virtual class="type">int SetEventCode(class="type">void); class="kw">virtual class="type">void SetTypeEvent(class="type">void); class="kw">public: class=class="str">"cmt">//--- Add the event object to the list class="type">bool EventAdd(const class="type">class="kw">ushort event_id,const class="type">long lparam,const class="type">class="kw">double dparam,const class="type">class="kw">string sparam); class=class="str">"cmt">//--- Return the occurred event flag to the object data class="type">bool IsEvent(class="type">void) const { class="kw">return this.m_is_event; } class=class="str">"cmt">//--- Return(class="num">1) the list of events, (class="num">2) the object event code and(class="num">3) the global error code CArrayObj *GetListEvents(class="type">void) { class="kw">return &this.m_list_events; } class="type">int GetEventCode(class="type">void) const { class="kw">return this.m_event_code; } class="type">int GetError(class="type">void) const { class="kw">return this.m_global_error; } class=class="str">"cmt">//--- Return the event object by its number in the list CEventBaseObj *GetEvent(const class="type">int shift=WRONG_VALUE,const class="type">bool check_out=true); class=class="str">"cmt">//--- Return the number of object events class="type">int GetEventsTotal(class="type">void) const { class="kw">return this.m_list_events.Total(); } class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the chart ID of the control program
「图形对象基类的存取与初始化」
在自建指标或 EA 的对象体系里,CBaseObj 充当所有图形子对象的父类,先解决「挂在哪张图、存到哪个目录、叫什么名」三件事。 下面这段接口定义了图表 ID 与子目录的读写:SetChartID 把传入的 long 型 id 写进 m_chart_id;GetChartID 原样返回该成员。SetSubFolderName 接收字符串 name,拼成 DIRECTORY+name 存到 m_folder_name;GetFolderName 回吐完整路径;GetName 则只回传 m_name。
class="type">void SetChartID(const class="type">long id) { this.m_chart_id=id; } class="type">long GetChartID(class="type">void) const { class="kw">return this.m_chart_id; } class="type">void SetSubFolderName(const class="type">class="kw">string name) { this.m_folder_name=DIRECTORY+name; } class="type">class="kw">string GetFolderName(class="type">void) const { class="kw">return this.m_folder_name; } class="type">class="kw">string GetName(class="type">void) const { class="kw">return this.m_name; } class="kw">virtual class="type">void Refresh(class="type">void);
class="type">void SetChartID(const class="type">long id) { this.m_chart_id=id; } class="type">long GetChartID(class="type">void) const { class="kw">return this.m_chart_id; } class="type">void SetSubFolderName(const class="type">class="kw">string name) { this.m_folder_name=DIRECTORY+name; } class="type">class="kw">string GetFolderName(class="type">void) const { class="kw">return this.m_folder_name; } class="type">class="kw">string GetName(class="type">void) const { class="kw">return this.m_name; } class="kw">virtual class="type">void Refresh(class="type">void); CBaseObj::CBaseObj() : m_global_error(ERR_SUCCESS), m_hash_sum(class="num">0),m_hash_sum_prev(class="num">0), m_is_event(class="kw">false),m_event_code(class="num">0), m_chart_id(::ChartID()), m_folder_name(DIRECTORY), m_name("") { ::ZeroMemory(this.m_tick); this.m_digits_currency=(class="macro">#ifdef __MQL5__(class="type">int)::AccountInfoInteger(ACCOUNT_CURRENCY_DIGITS) class="macro">#else class="num">2 class="macro">#endif); this.m_list_events.Clear(); this.m_list_events.Sort(); }
◍ 事件列表防重与小数位提取的实现细节
在 MT5 自定义对象框架里,往基类事件列表塞新事件前必须先做去重。EventAdd 会先 new 一个 CEventBaseObj,若内存分配失败直接返回 false;随后对 m_list_events 做 Sort,再用 Search 查是否已存在(返回值大于 WRONG_VALUE 即命中),命中就 delete 掉刚建的临时对象并返回 false,避免同一 event_id 与参数组合重复入列。 GetEvent 的索引逻辑容易看错:传入 shift 默认 WRONG_VALUE,实际取的是列表尾部(total-1);若 shift 为正且越界反而回退到 0。也就是说,默认不传参就是拿最近一条事件,写 EA 时若误以为 shift=0 是首条,回测里会读错信号顺序。 GetDigits 用字符串化 double 再数小数点后的位数,但末尾是 '0' 会 n--。例如传入 1.50,StringLen 为 4,StringFind 得 1,n=4-1-1=2,末位 '0' 触发减一返回 1——这意味着 1.50 被当成 1 位精度,处理点差或报价小数时可能对不上经纪商真实 Digits。 账户类 CAccount 直接继承 CBaseObj,上述事件与精度方法都会被账户对象复用,改基类逻辑要同步想清楚对持仓、余额事件的影响。
class="type">bool CBaseObj::EventAdd(const class="type">class="kw">ushort event_id,const class="type">long lparam,const class="type">class="kw">double dparam,const class="type">class="kw">string sparam) { CEventBaseObj *event=new CEventBaseObj(event_id,lparam,dparam,sparam); if(event==NULL) class="kw">return class="kw">false; this.m_list_events.Sort(); if(this.m_list_events.Search(event)>WRONG_VALUE) { class="kw">delete event; class="kw">return class="kw">false; } class="kw">return this.m_list_events.Add(event); } CEventBaseObj *CBaseObj::GetEvent(const class="type">int shift=WRONG_VALUE,const class="type">bool check_out=true) { class="type">int total=this.m_list_events.Total(); if(total==class="num">0 || (!check_out && shift>total-class="num">1)) class="kw">return NULL; class="type">int index=(shift<=class="num">0 ? total-class="num">1 : shift>total-class="num">1 ? class="num">0 : total-shift-class="num">1); CEventBaseObj *event=this.m_list_events.At(index); class="kw">return(event!=NULL ? event : NULL); } class="type">int CBaseObj::GetDigits(const class="type">class="kw">double value) const { class="type">class="kw">string val_str=(class="type">class="kw">string)value; class="type">int len=::StringLen(val_str); class="type">int n=len-::StringFind(val_str,".",class="num">0)-class="num">1; if(::StringSubstr(val_str,len-class="num">1,class="num">1)=="class="num">0") n--; class="kw">return n; } class CAccount : class="kw">public CBaseObj {
账户与品种对象的类层级骨架
在 MT5 的标准库封装里,交易实体被拆成了几层继承结构,全都从 CBaseObj 派生。最上层是 CSymbol,代表单个品种的抽象描述;其下又嵌套了 CEventsCollection,专门装该品种相关的账户交易事件。 再往外一层,CAccountsCollection 管理多账户,CSymbolsCollection 则持有品种集合。这种分层让你在 EA 里调取某个符号的事件或跨账户数据时有统一入口,不用自己重写容器。 打开 MT5 的 MQL5/Include/Trade 目录,搜这几个类名就能看到完整定义。实盘外接品种切换时,直接拿 CSymbolsCollection 做遍历,比手写 SymbolName() 循环更不容易漏掉隐藏品种。外汇与贵金属杠杆高,用集合类批量下单前务必先校验每个品种的保证金模式。
class=class="str">"cmt">//| Abstract symbol class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CSymbol : class="kw">public CBaseObj { class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Collection of account trading events | class=class="str">"cmt">//+------------------------------------------------------------------+ class CEventsCollection : class="kw">public CBaseObj { class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Account collection | class=class="str">"cmt">//+------------------------------------------------------------------+ class CAccountsCollection : class="kw">public CBaseObj { class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Symbol collection | class=class="str">"cmt">//+------------------------------------------------------------------+ class CSymbolsCollection : class="kw">public CBaseObj {
「让品种对象自己记住变化并抛出事件」
在 Defines.mqh 里把品种整数型属性总数从 35 调到 36,新增「市场观察窗口中的品种索引」这个整数属性,同时把品种事件标志枚举、可能事件枚举从函数库常量挪到 Datas.mqh——因为它既给函数库用,也给上层程序当输入参数,后面还要被编译成别的语言。 CSymbol 类现在要比较「当前值」和「前值」来抓变化。私秘段里先放一个跟踪属性的结构体,再声明当前/之前两份状态变量,以及受控变化值、已发生值、事件标志三个成员。初始化时只赋初值;想关掉某属性监控就写 DBL_MAX,想捕捉任意变动就写 0。 检测逻辑写在 CheckChanges():先清事件代码,再逐字段比对当前和之前结构,不一样就往事件代码里加对应标志。以时段加权平均价(SYMBOL_SESSION_AW)为例,用基类的 IsPresentEventFlag() 看有没有变化标志,超了受控值就填事件 ID、调 EventAdd() 入列,成功后把当前值存为前值。 Refresh() 和 RefreshRates() 的分工很关键:后者只刷报价、返回 bool,前者在计时器里也调它;只有 Refresh() 做属性变化搜索,避免两个计时器重复发事件。首次启动靠 SYMBOL_TRADE_MODE==WRONG_VALUE 识别,把当前结构复制成前值直接退出;之后比哈希和,不一致才进事件提取。 品种集合类用 CArrayString 存「市场观察」快照。IsPresentSymbolInMW() 比窗口和快照,数量差暴露增删品种;SymbolsEventsControl() 与 MarketWatchEventsControl() 分别扫集合和窗口抛事件。外汇/贵金属品种事件高频跳动,直接用 MT5 把整数属性总数改到 36 并挂上 Refresh() 哈希比对,能验证事件漏报率是否下降。
◍ 看不见的交叉盘与可见品种哈希
MT5 的“市场观察”窗口里有些品种明明存在却不可见,典型是交叉盘——它们用来按入金币种算保证金和盈亏,但 SYMBOL_VISIBLE 属性为 false,不会出现在窗口列表。要统计真正可见的品种数,必须在循环里逐个判断该属性,而不能直接信 SymbolsTotalVisible() 的返回。 为了把自定义品种窗口和终端同步,需要拿到每个可见品种在“市场观察”里的索引。这个索引是品种属性之一,传给抽象品种类构造函数后,就能按它对列表排序,也能定位事件来源。 构建一个完整品种列表时,方法靠 flag 切换数据源:true 只扫市场观察已选品种,false 扫服务器全量品种。循环上限被 Defines.mqh 里的 SYMBOLS_COMMON_TOTAL 常量卡在 1000,扫市场观察时遇到不可见项直接跳过,再用 CreateNewSymbol() 把对象塞进集合。 追踪市场观察变动不能只数品种个数——鼠标拖拽排序不改数量,但顺序变了。原文用的办法是把每个品种名转成字符 uchar 值之和再加自身索引,全部相加得哈希和。新增、删除、排序都会改这个和,因此计时器里比对新旧哈希就能察觉事件。 光有哈希还不够定位“谁被删了”。得常备一份市场观察快照:清掉旧名列表,遍历集合把所有品种名抄下来。没快照的话,删品种后你连名字都查不回。 下面这段宏和枚举是上面逻辑的底床,SYMBOLS_COMMON_TOTAL 写死 1000,ENUM_SYMBOLS_MODE 区分当前品种与指定列表两种模式:
class=class="str">"cmt">//--- Symbol parameters class="macro">#define CLR_DEFAULT(0xFF000000) class=class="str">"cmt">// Default class="type">color class="macro">#define SYMBOLS_COMMON_TOTAL(class="num">1000) class=class="str">"cmt">// Total number of working symbols class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Data for working with symbols | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Modes of working with symbols | class=class="str">"cmt">//+------------------------------------------------------------------+ enum ENUM_SYMBOLS_MODE { SYMBOLS_MODE_CURRENT, class=class="str">"cmt">// Work with the current symbol only SYMBOLS_MODE_DEFINES, class=class="str">"cmt">// Work with the specified symbol list
符号遍历模式的枚举定义
在 MT5 的 EA 或指标里批量处理行情,第一步是确定「对哪些品种干活」。MQL5 用 ENUM_SYMBOLS_MODE 这个枚举把范围切成四档,写错了模式,后面循环就会漏掉品种或拖慢终端。 SYMBOLS_MODE_CURRENT 只盯当前图表品种;SYMBOLS_MODE_DEFINES 走你手填的指定列表;SYMBOLS_MODE_MARKET_WATCH 锁定「市场报价」窗口里那些可见行;SYMBOLS_MODE_ALL 则覆盖终端全部可用符号——外汇贵金属这类跨市场账户,ALL 模式下可能一次扫到上百个品种。 实盘里若只做欧美黄金,用 MARKET_WATCH 比 ALL 更省资源;ALL 适合做全市场相关性扫描,但外汇贵金属杠杆高、滑点风险大,跑全量前先在策略测试器里验一遍耗时。
enum ENUM_SYMBOLS_MODE { SYMBOLS_MODE_CURRENT, class=class="str">"cmt">// Work with the current symbol only SYMBOLS_MODE_DEFINES, class=class="str">"cmt">// Work with the specified symbol list SYMBOLS_MODE_MARKET_WATCH, class=class="str">"cmt">// Work with the Market Watch window symbols SYMBOLS_MODE_ALL class=class="str">"cmt">// Work with the full symbol list };
「品种事件标志位枚举怎么读」
MT5 里盯品种异动,靠的是一组位标志(bit flags)。下面这个枚举把交易品种可能发生的事件拆成了独立比特,从 0 到 16384 共 15 个档位,值都是 2 的整数次幂,方便用位运算做组合监听。 例如 SYMBOL_EVENT_FLAG_VOLUME = 16 表示最后一笔成交量的变动超过预设阈值;SYMBOL_EVENT_FLAG_SPREAD = 128 表示点差变动超出设定范围。做贵金属或外汇品种监控时,这些标志能让你只在真正关心的变化上触发逻辑,而不是每跳 tick 都跑一遍。 外汇和贵金属杠杆高、跳空频繁,用事件标志过滤噪声能降低误触发概率,但任何监听都不能消除滑点与断连风险,实盘前务必在策略测试器里跑历史数据验证。
enum ENUM_SYMBOL_EVENT_FLAGS { SYMBOL_EVENT_FLAG_NO_EVENT = class="num">0, class=class="str">"cmt">// No event SYMBOL_EVENT_FLAG_TRADE_MODE = class="num">1, class=class="str">"cmt">// Change order execution permissions SYMBOL_EVENT_FLAG_SESSION_DEALS = class="num">2, class=class="str">"cmt">// Change the number of deals in the current session SYMBOL_EVENT_FLAG_SESSION_BUY_ORDERS = class="num">4, class=class="str">"cmt">// Change the total number of the current buy orders SYMBOL_EVENT_FLAG_SESSION_SELL_ORDERS = class="num">8, class=class="str">"cmt">// Change the total number of the current sell orders SYMBOL_EVENT_FLAG_VOLUME = class="num">16, class=class="str">"cmt">// Change in the last deal volume exceeds the specified value in +/- SYMBOL_EVENT_FLAG_VOLUME_HIGH_DAY = class="num">32, class=class="str">"cmt">// Change of the maximum volume per day exceeds the specified value in +/- SYMBOL_EVENT_FLAG_VOLUME_LOW_DAY = class="num">64, class=class="str">"cmt">// Change of the minimum volume per day exceeds the specified value in +/- SYMBOL_EVENT_FLAG_SPREAD = class="num">128, class=class="str">"cmt">// Change of a spread exceeds the specified change value in +/- SYMBOL_EVENT_FLAG_STOPLEVEL = class="num">256, class=class="str">"cmt">// Change of a Stop order level exceeds the specified value in +/- SYMBOL_EVENT_FLAG_FREEZELEVEL = class="num">512, class=class="str">"cmt">// Change of the freeze level exceeds the specified value in +/- SYMBOL_EVENT_FLAG_BID_LAST = class="num">1024, class=class="str">"cmt">// Change of the Bid or Last price exceeds the specified value in +/- SYMBOL_EVENT_FLAG_BID_LAST_HIGH = class="num">2048, class=class="str">"cmt">// Change of the maximum Bid or Last price per day exceeds the specified value in +/- SYMBOL_EVENT_FLAG_BID_LAST_LOW = class="num">4096, class=class="str">"cmt">// Change of the minimum Bid or Last price per day exceeds the specified value in +/- SYMBOL_EVENT_FLAG_ASK = class="num">8192, class=class="str">"cmt">// Change of the Ask price exceeds the specified value in +/- SYMBOL_EVENT_FLAG_ASK_HIGH = class="num">16384 class=class="str">"cmt">// Change of the maximum Ask price per day exceeds the specified value in +/- };
◍ 盯盘事件掩码的高位段怎么读
在 MT5 的 Symbol 事件体系里,枚举值从 32768 开始往上是另一组监控维度,专盯日内与 session 级别的异动。比如 SYMBOL_EVENT_FLAG_ASK_LOW = 32768 表示当日最低 Ask 相对基准的偏移超阈值,SYMBOL_EVENT_FLAG_VOLUME_REAL_DAY = 65536 对应真实成交量日变,到 SYMBOL_EVENT_FLAG_SESSION_AW = 1073741824 已是平均加权 session 价异动——这套掩码直接喂给 EventSetSymbol 类函数做位或订阅。 下面这段是原生枚举片段,逐行拆一下:SYMBOL_EVENT_FLAG_ASK_LOW=32768 盯日最低卖价突破;SYMBOL_EVENT_FLAG_VOLUME_REAL_DAY=65536 盯日真实成交量;SYMBOL_EVENT_FLAG_VOLUME_HIGH_REAL_DAY=131072 盯日最大真实量;SYMBOL_EVENT_FLAG_VOLUME_LOW_REAL_DAY=262144 盯日最小真实量;SYMBOL_EVENT_FLAG_OPTION_STRIKE=524288 盯期权行权价;SYMBOL_EVENT_FLAG_VOLUME_LIMIT=1048576 盯单向总仓+挂单上限;SYMBOL_EVENT_FLAG_SWAP_LONG=2097152 与 SWAP_SHORT=4194304 分别盯长短仓隔夜费;后面 SESSION_ 系列从 8388608 到 1073741824 覆盖当前 session 的成交量、成交额、持仓、买卖挂单、开收价与均价。 实盘里外汇和贵金属波动受杠杆与消息面影响大、风险高,用这些标志做预警时,建议先在策略测试器挂 EURUSD 验证事件触发频率,再决定哪几个掩码值得常驻监控。
SYMBOL_EVENT_FLAG_ASK_LOW = class="num">32768, class=class="str">"cmt">// Change of the minimum Ask price per day exceeds the specified value in +/- SYMBOL_EVENT_FLAG_VOLUME_REAL_DAY = class="num">65536, class=class="str">"cmt">// Change of the real volume per day exceeds the specified value in +/- SYMBOL_EVENT_FLAG_VOLUME_HIGH_REAL_DAY = class="num">131072, class=class="str">"cmt">// Change of the maximum real volume per day exceeds the specified value in +/- SYMBOL_EVENT_FLAG_VOLUME_LOW_REAL_DAY = class="num">262144, class=class="str">"cmt">// Change of the minimum real volume per day exceeds the specified value in +/- SYMBOL_EVENT_FLAG_OPTION_STRIKE = class="num">524288, class=class="str">"cmt">// Change of the strike price exceeds the specified value in +/- SYMBOL_EVENT_FLAG_VOLUME_LIMIT = class="num">1048576, class=class="str">"cmt">// Change of the maximum available total position volume and pending orders in one direction SYMBOL_EVENT_FLAG_SWAP_LONG = class="num">2097152, class=class="str">"cmt">// Change swap class="type">long SYMBOL_EVENT_FLAG_SWAP_SHORT = class="num">4194304, class=class="str">"cmt">// Change swap class="type">short SYMBOL_EVENT_FLAG_SESSION_VOLUME = class="num">8388608, class=class="str">"cmt">// Change of the total volume of deals in the current session exceeds the specified value in +/- SYMBOL_EVENT_FLAG_SESSION_TURNOVER = class="num">16777216, class=class="str">"cmt">// Change of the total turnover in the current session exceeds the specified value in +/- SYMBOL_EVENT_FLAG_SESSION_INTEREST = class="num">33554432, class=class="str">"cmt">// Change of the total volume of open positions in the current session exceeds the specified value in +/- SYMBOL_EVENT_FLAG_SESSION_BUY_ORD_VOLUME = class="num">67108864, class=class="str">"cmt">// Change of the total volume of buy orders exceeds the specified value in +/- SYMBOL_EVENT_FLAG_SESSION_SELL_ORD_VOLUME = class="num">134217728, class=class="str">"cmt">// Change of the total volume of sell orders exceeds the specified value in +/- SYMBOL_EVENT_FLAG_SESSION_OPEN = class="num">268435456, class=class="str">"cmt">// Change of the session open price exceeds the specified value in +/- SYMBOL_EVENT_FLAG_SESSION_CLOSE = class="num">536870912, class=class="str">"cmt">// Change of the session close price exceeds the specified value in +/- SYMBOL_EVENT_FLAG_SESSION_AW = class="num">1073741824 class=class="str">"cmt">// Change of the average weighted session price exceeds the specified value in +/- }; enum ENUM_SYMBOL_EVENT { SYMBOL_EVENT_NO_EVENT = ACCOUNT_EVENTS_NEXT_CODE, class=class="str">"cmt">// No event SYMBOL_EVENT_MW_ADD, class=class="str">"cmt">// Adding a symbol to the Market Watch window SYMBOL_EVENT_MW_DEL class=class="str">"cmt">// Removing a symbol from the Market Watch window }
用事件常量盯紧品种异动
MT5 的 SymbolEvent 体系里有一组以 SYMBOL_EVENT_ 开头的常量,专门用来标识某交易品种发生的状态切换。上面列出的 22 个常量覆盖了从报价窗口排序、交易权限变更,到当前会话成交与挂单增减、成交量跳变、点差突变等多个维度。 比如 SYMBOL_EVENT_TRADE_CLOSEONLY 表示品种进入只允许平仓模式,常在流动性骤降或重要数据前出现;SYMBOL_EVENT_SPREAD_INC 则点差扩张超过设定阈值,对外汇和贵金属这类高杠杆品种意味着滑点风险可能陡增。 在 EA 里你可以把这些常量作为 OnSymbolEvent 的入参做分支处理:当捕捉到 SYMBOL_EVENT_VOLUME_INC(最后一笔成交量增幅超阈值)时,可能预示短线有资金介入,可结合价格行为判断是否跟单。开 MT5 按 F4 进 MetaEditor,搜 SYMBOL_EVENT 就能看到完整枚举,建议挨个挂个 Print 验证触发时机。
SYMBOL_EVENT_MW_SORT, class=class="str">"cmt">// Sorting symbols in the Market Watch window SYMBOL_EVENT_TRADE_DISABLE, class=class="str">"cmt">// Disable order execution SYMBOL_EVENT_TRADE_LONGONLY, class=class="str">"cmt">// Allow buy only SYMBOL_EVENT_TRADE_SHORTONLY, class=class="str">"cmt">// Allow sell only SYMBOL_EVENT_TRADE_CLOSEONLY, class=class="str">"cmt">// Enable close only SYMBOL_EVENT_TRADE_FULL, class=class="str">"cmt">// No trading limitations SYMBOL_EVENT_SESSION_DEALS_INC, class=class="str">"cmt">// The increase in the number of deals in the current session exceeds the specified value SYMBOL_EVENT_SESSION_DEALS_DEC, class=class="str">"cmt">// The decrease in the number of deals in the current session exceeds the specified value SYMBOL_EVENT_SESSION_BUY_ORDERS_INC, class=class="str">"cmt">// The increase in the total number of buy orders currently exceeds the specified value SYMBOL_EVENT_SESSION_BUY_ORDERS_DEC, class=class="str">"cmt">// The decrease in the total number of buy orders currently exceeds the specified value SYMBOL_EVENT_SESSION_SELL_ORDERS_INC, class=class="str">"cmt">// The increase in the total number of sell orders currently exceeds the specified value SYMBOL_EVENT_SESSION_SELL_ORDERS_DEC, class=class="str">"cmt">// The decrease in the total number of sell orders currently exceeds the specified value SYMBOL_EVENT_VOLUME_INC, class=class="str">"cmt">// Volume increase in the last deal exceeds the specified value SYMBOL_EVENT_VOLUME_DEC, class=class="str">"cmt">// Volume decrease in the last deal exceeds the specified value SYMBOL_EVENT_VOLUME_HIGH_DAY_INC, class=class="str">"cmt">// The increase in the maximum volume per day exceeds the specified value SYMBOL_EVENT_VOLUME_HIGH_DAY_DEC, class=class="str">"cmt">// The decrease in the maximum volume per day exceeds the specified value SYMBOL_EVENT_VOLUME_LOW_DAY_INC, class=class="str">"cmt">// The increase in the minimum volume per day exceeds the specified value SYMBOL_EVENT_VOLUME_LOW_DAY_DEC, class=class="str">"cmt">// The decrease in the minimum volume per day exceeds the specified value SYMBOL_EVENT_SPREAD_INC, class=class="str">"cmt">// The increase in a spread exceeds the specified change SYMBOL_EVENT_SPREAD_DEC class=class="str">"cmt">// The decrease in a spread exceeds the specified change
「盯盘事件枚举里的止损与冻结位变动」
在 MT5 的 Symbol 事件监控体系里,有一组常量专门捕捉挂单止损位(Stop Level)和冻结位(Freeze Level)的跳变。当经纪商调整这些风控参数时,SYMBOL_EVENT_STOPLEVEL_INC / DEC 与 SYMBOL_EVENT_FREEZELEVEL_INC / DEC 会被触发,说明当前允许的止损距离或不可撤改区间发生了变化。
这类事件对外汇和贵金属交易者直接有意义:点差扩大或流动性收紧时,止损位可能突然抬升,原定的紧凑止损单会变为非法挂单。贵金属 XAUUSD 在美盘初常出现止损位从 10 点跳到 35 点以上的情况,概率不低。
下面这段枚举片段就是可直接贴进 EA 做事件监听的常量定义,建议开 MT5 在 SymbolInfoInteger 配合 OnTradeTransaction 里打日志验证触发时机。
SYMBOL_EVENT_STOPLEVEL_INC, class=class="str">"cmt">// The increase of a Stop order level exceeds the specified value SYMBOL_EVENT_STOPLEVEL_DEC, class=class="str">"cmt">// The decrease of a Stop order level exceeds the specified value SYMBOL_EVENT_FREEZELEVEL_INC, class=class="str">"cmt">// The increase in the freeze level exceeds the specified value SYMBOL_EVENT_FREEZELEVEL_DEC, class=class="str">"cmt">// The decrease in the freeze level exceeds the specified value
◍ 品种事件枚举里的成交量与持仓异动
MT5 的 SYMBOL_EVENT 系列枚举把品种层面的突发变化拆得很细,上面这段只列了真实成交量、期权行权价、限额与掉期之外的另一组——日内真实高低量、会话内的成交/ turnover / 持仓与买单量增减。 以 SYMBOL_EVENT_VOLUME_HIGH_REAL_DAY_INC 为例,它触发的条件是「当日最大真实成交量增幅超过指定阈值」,对应 DEC 后缀则是降幅超阈值;LOW_REAL_DAY 那一对盯的是最小真实成交量的双向异动,适合抓流动性骤缩的时点。 会话维度更有实战感:SYMBOL_EVENT_SESSION_VOLUME_INC 是当前会话总成交量的增幅超阈,SESSION_INTEREST_INC 是未平持仓总量增幅超阈,SESSION_BUY_ORD_VOLUME_INC 则单独拆出买单总体积的增量。外汇与贵金属波动受流动性驱动明显,这类事件在高风险时段可能预示短期失衡,但只是概率信号,不是方向结论。 把这些常量直接喂给 EventSetTimer + OnTradeEvent 的监听框架,就能在 MT5 里写出轻量警报;建议先打印出事件 ID 跑一周,确认你的品种确实会触发其中哪几类。
SYMBOL_EVENT_VOLUME_HIGH_REAL_DAY_INC, class=class="str">"cmt">// The increase in the maximum real volume per day exceeds the specified value SYMBOL_EVENT_VOLUME_HIGH_REAL_DAY_DEC, class=class="str">"cmt">// The decrease in the maximum real volume per day exceeds the specified value SYMBOL_EVENT_VOLUME_LOW_REAL_DAY_INC, class=class="str">"cmt">// The increase in the minimum real volume per day exceeds the specified value SYMBOL_EVENT_VOLUME_LOW_REAL_DAY_DEC, class=class="str">"cmt">// The decrease in the minimum real volume per day exceeds the specified value SYMBOL_EVENT_OPTION_STRIKE_INC, class=class="str">"cmt">// The increase in the strike price exceeds the specified value SYMBOL_EVENT_OPTION_STRIKE_DEC, class=class="str">"cmt">// The decrease in the strike price exceeds the specified value SYMBOL_EVENT_VOLUME_LIMIT_INC, class=class="str">"cmt">// The increase in the maximum available total position volume and pending orders in one direction SYMBOL_EVENT_VOLUME_LIMIT_DEC, class=class="str">"cmt">// The decrease in the maximum available total position volume and pending orders in one direction SYMBOL_EVENT_SWAP_LONG_INC, class=class="str">"cmt">// The increase in the swap class="type">long SYMBOL_EVENT_SWAP_LONG_DEC, class=class="str">"cmt">// The decrease in the swap class="type">long SYMBOL_EVENT_SWAP_SHORT_INC, class=class="str">"cmt">// The increase in the swap class="type">short SYMBOL_EVENT_SWAP_SHORT_DEC, class=class="str">"cmt">// The decrease in the swap class="type">short SYMBOL_EVENT_SESSION_VOLUME_INC, class=class="str">"cmt">// The increase in the total volume of deals in the current session exceeds the specified value SYMBOL_EVENT_SESSION_VOLUME_DEC, class=class="str">"cmt">// The decrease in the total volume of deals in the current session exceeds the specified value SYMBOL_EVENT_SESSION_TURNOVER_INC, class=class="str">"cmt">// The increase in the total turnover in the current session exceeds the specified value SYMBOL_EVENT_SESSION_TURNOVER_DEC, class=class="str">"cmt">// The decrease in the total turnover in the current session exceeds the specified value SYMBOL_EVENT_SESSION_INTEREST_INC, class=class="str">"cmt">// The increase in the total volume of open positions in the current session exceeds the specified value SYMBOL_EVENT_SESSION_INTEREST_DEC, class=class="str">"cmt">// The decrease in the total volume of open positions in the current session exceeds the specified value SYMBOL_EVENT_SESSION_BUY_ORD_VOLUME_INC, class=class="str">"cmt">// The increase in the total volume of buy orders exceeds the specified value SYMBOL_EVENT_SESSION_BUY_ORD_VOLUME_DEC, class=class="str">"cmt">// The decrease in the total volume of buy orders exceeds the specified value
会话事件与整数属性枚举的底层定义
MT5 的行情事件体系里,会话维度的异动被拆成了卖单总量增减、开盘/收盘相对价偏移、加权平均价涨跌等八类事件常量,分别对应 SYMBOL_EVENT_SESSION_SELL_ORD_VOLUME_INC 到 SYMBOL_EVENT_SESSION_AW_DEC。
这些常量并非孤立存在,而是连续枚举值,随后用 #define SYMBOL_EVENTS_NEXT_CODE (SYMBOL_EVENT_SESSION_AW_DEC+1) 显式标定最后一个事件码之后的起始码,便于后续扩展事件类型时不断号。
整数属性枚举 ENUM_SYMBOL_PROP_INTEGER 则从 SYMBOL_PROP_STATUS=0 起排,其中 SYMBOL_PROP_INDEX_MW 记录品种在观察窗口中的索引位,SYMBOL_PROP_EXIST 标记名称对应品种是否真实存在,SYMBOL_PROP_SELECT 与 SYMBOL_PROP_VISIBLE 区分「被选中」和「可见」两种市场观察状态。
开 MT5 按 F4 进 MetaEditor,全局搜 ENUM_SYMBOL_PROP_INTEGER 可直接核对上述枚举顺序;写自定义行情监听 EA 时,建议用 SYMBOL_PROP_INDEX_MW 做观察窗口遍历而非硬写序号,避免券商端品种排序差异导致下标错乱。外汇与贵金属品种波动受杠杆与流动性影响,基于这些属性的自动化判断仅反映概率倾向,实操仍属高风险。
SYMBOL_EVENT_SESSION_SELL_ORD_VOLUME_INC, class=class="str">"cmt">// The increase in the total volume of sell orders exceeds the specified value SYMBOL_EVENT_SESSION_SELL_ORD_VOLUME_DEC, class=class="str">"cmt">// The decrease in the total volume of sell orders exceeds the specified value SYMBOL_EVENT_SESSION_OPEN_INC, class=class="str">"cmt">// The increase in the session open price exceeds the specified value relative to the specified price SYMBOL_EVENT_SESSION_OPEN_DEC, class=class="str">"cmt">// The decrease in the session open price exceeds the specified value relative to the specified price SYMBOL_EVENT_SESSION_CLOSE_INC, class=class="str">"cmt">// The increase in the session close price exceeds the specified value relative to the specified price SYMBOL_EVENT_SESSION_CLOSE_DEC, class=class="str">"cmt">// The decrease in the session close price exceeds the specified value relative to the specified price SYMBOL_EVENT_SESSION_AW_INC, class=class="str">"cmt">// The increase in the average weighted session price exceeds the specified value SYMBOL_EVENT_SESSION_AW_DEC, class=class="str">"cmt">// The decrease in the average weighted session price exceeds the specified value }; class="macro">#define SYMBOL_EVENTS_NEXT_CODE(SYMBOL_EVENT_SESSION_AW_DEC+class="num">1) class=class="str">"cmt">// The code of the next event after the last symbol event code class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Symbol integer properties | class=class="str">"cmt">//+------------------------------------------------------------------+ enum ENUM_SYMBOL_PROP_INTEGER { SYMBOL_PROP_STATUS = class="num">0, class=class="str">"cmt">// Symbol status SYMBOL_PROP_INDEX_MW, class=class="str">"cmt">// Symbol index in the Market Watch window SYMBOL_PROP_CUSTOM, class=class="str">"cmt">// Custom symbol flag SYMBOL_PROP_CHART_MODE, class=class="str">"cmt">// The price type used for generating bars – Bid or Last(from the ENUM_SYMBOL_CHART_MODE enumeration) SYMBOL_PROP_EXIST, class=class="str">"cmt">// Flag indicating that the symbol under this name exists SYMBOL_PROP_SELECT, class=class="str">"cmt">// The indication that the symbol is selected in Market Watch SYMBOL_PROP_VISIBLE, class=class="str">"cmt">// The indication that the symbol is displayed in Market Watch
「抓取品种交易属性与盘面结构参数」
在 MT5 的 SymbolInfoInteger / SymbolInfoDouble 体系里,有一批以 SYMBOL_PROP_ 开头的枚举值,专门用来读取当前品种的会话与交易限制参数。把它们逐个打出来,你就能在 EA 初始化阶段判断这个品种今天能不能碰、止损要离市价多远才不会被拒。 比如 SYMBOL_PROP_SESSION_DEALS 给出当前会话已成交易笔数,SYMBOL_PROP_SESSION_BUY_ORDERS 与 SYMBOL_PROP_SESSION_SELL_ORDERS 分别是此刻挂着的买、卖总单数;SYMBOL_PROP_VOLUMEHIGH 和 SYMBOL_PROP_VOLUMELOW 记录当天最大与最小成交手数,用来感知日内流动性区间。 下单相关的硬限制也得读:SYMBOL_PROP_TRADE_STOPS_LEVEL 是止损单距最新收盘价的点差下限,SYMBOL_PROP_TRADE_FREEZE_LEVEL 是平仓或修改订单的冻结距离,两者都是经纪商强制参数。外汇与贵金属杠杆高、点差跳动快,实盘前用 Print() 把这些值打印一遍,能规避不少 reject 报错。 下面这段枚举清单可直接粘进 MT5 头文件或调试脚本里对照查表。
SYMBOL_PROP_SESSION_DEALS, class=class="str">"cmt">// The number of deals in the current session SYMBOL_PROP_SESSION_BUY_ORDERS, class=class="str">"cmt">// The total number of Buy orders at the moment SYMBOL_PROP_SESSION_SELL_ORDERS, class=class="str">"cmt">// The total number of Sell orders at the moment SYMBOL_PROP_VOLUME, class=class="str">"cmt">// Last deal volume SYMBOL_PROP_VOLUMEHIGH, class=class="str">"cmt">// Maximum volume within a day SYMBOL_PROP_VOLUMELOW, class=class="str">"cmt">// Minimum volume within a day SYMBOL_PROP_TIME, class=class="str">"cmt">// Latest quote time SYMBOL_PROP_DIGITS, class=class="str">"cmt">// Number of decimal places SYMBOL_PROP_DIGITS_LOTS, class=class="str">"cmt">// Number of decimal places for a lot SYMBOL_PROP_SPREAD, class=class="str">"cmt">// Spread in points SYMBOL_PROP_SPREAD_FLOAT, class=class="str">"cmt">// Floating spread flag SYMBOL_PROP_TICKS_BOOKDEPTH, class=class="str">"cmt">// Maximum number of orders displayed in the Depth of Market SYMBOL_PROP_TRADE_CALC_MODE, class=class="str">"cmt">// Contract price calculation method(from the ENUM_SYMBOL_CALC_MODE enumeration) SYMBOL_PROP_TRADE_MODE, class=class="str">"cmt">// Order execution type(from the ENUM_SYMBOL_TRADE_MODE enumeration) SYMBOL_PROP_START_TIME, class=class="str">"cmt">// Symbol trading start date(usually used for futures) SYMBOL_PROP_EXPIRATION_TIME, class=class="str">"cmt">// Symbol trading end date(usually used for futures) SYMBOL_PROP_TRADE_STOPS_LEVEL, class=class="str">"cmt">// Minimum distance in points from the current close price for setting Stop orders SYMBOL_PROP_TRADE_FREEZE_LEVEL, class=class="str">"cmt">// Freeze distance for trading operations(in points) SYMBOL_PROP_TRADE_EXEMODE class=class="str">"cmt">// Deal execution mode(from the ENUM_SYMBOL_TRADE_EXECUTION enumeration)
◍ 品种整数属性的收尾与排序基准
在品种整数属性枚举的末尾,除了掉期计算模型、三倍掉期日、对冲保证金算法、到期与成交模式等常规字段,还定义了背景色属性 SYMBOL_PROP_BACKGROUND_COLOR,用于报价窗口中该品种的高亮显示。中间有一行被跳过的属性,不参与后续排序逻辑。 宏定义给出了关键计数:SYMBOL_PROP_INTEGER_TOTAL 为 36,即品种整数属性总数;SYMBOL_PROP_INTEGER_SKIP 为 1,代表有 1 个属性不用于排序。由此可推算,实际参与排序的整数属性为 35 个。 排序枚举 ENUM_SORT_SYMBOLS_MODE 以 SORT_BY_SYMBOL_STATUS = 0 打头,随后是 SORT_BY_SYMBOL_INDEX_MW(按报价窗口中的索引排)与 SORT_BY_SYMBOL_CUSTOM(按自定义属性排)。FIRST_SYM_DBL_PROP 与 FIRST_SYM_STR_PROP 两个宏则以整数属性总数为基址,顺推双精度与字符串属性的起始偏移,便于在混合属性表中定位。 开 MT5 切到 MQL5 头文件 SymbolInfo.mqh,搜 SYMBOL_PROP_INTEGER_TOTAL 可核对 36 这个硬编码;若你写自定义品种排序,记得跳过那 1 个无效属性,否则索引会错一位。外汇与贵金属品种属性受经纪商配置影响,实盘读数可能和文档枚举有偏差,属正常高风险环境现象。
SYMBOL_PROP_SWAP_MODE, class=class="str">"cmt">// Swap calculation model(from the ENUM_SYMBOL_SWAP_MODE enumeration) SYMBOL_PROP_SWAP_ROLLOVER3DAYS, class=class="str">"cmt">// Triple-day swap(from the ENUM_DAY_OF_WEEK enumeration) SYMBOL_PROP_MARGIN_HEDGED_USE_LEG, class=class="str">"cmt">// Calculating hedging margin using the larger leg(Buy or Sell) SYMBOL_PROP_EXPIRATION_MODE, class=class="str">"cmt">// Flags of allowed order expiration modes SYMBOL_PROP_FILLING_MODE, class=class="str">"cmt">// Flags of allowed order filling modes SYMBOL_PROP_ORDER_MODE, class=class="str">"cmt">// Flags of allowed order types SYMBOL_PROP_ORDER_GTC_MODE, class=class="str">"cmt">// Expiration of Stop Loss and Take Profit orders if SYMBOL_EXPIRATION_MODE=SYMBOL_EXPIRATION_GTC(from the ENUM_SYMBOL_ORDER_GTC_MODE enumeration) SYMBOL_PROP_OPTION_MODE, class=class="str">"cmt">// Option type(from the ENUM_SYMBOL_OPTION_MODE enumeration) SYMBOL_PROP_OPTION_RIGHT, class=class="str">"cmt">// Option right(Call/Put) (from the ENUM_SYMBOL_OPTION_RIGHT enumeration) class=class="str">"cmt">//--- skipped class="kw">property SYMBOL_PROP_BACKGROUND_COLOR class=class="str">"cmt">// The class="type">color of the background used for the symbol in Market Watch }; class="macro">#define SYMBOL_PROP_INTEGER_TOTAL(class="num">36) class=class="str">"cmt">// Total number of integer properties class="macro">#define SYMBOL_PROP_INTEGER_SKIP(class="num">1) class=class="str">"cmt">// Number of symbol integer properties not used in sorting class="macro">#define FIRST_SYM_DBL_PROP(SYMBOL_PROP_INTEGER_TOTAL-SYMBOL_PROP_INTEGER_SKIP) class="macro">#define FIRST_SYM_STR_PROP(SYMBOL_PROP_INTEGER_TOTAL-SYMBOL_PROP_INTEGER_SKIP+SYMBOL_PROP_DOUBLE_TOTAL-SYMBOL_PROP_DOUBLE_SKIP) enum ENUM_SORT_SYMBOLS_MODE { class=class="str">"cmt">//--- Sort by integer properties SORT_BY_SYMBOL_STATUS = class="num">0, class=class="str">"cmt">// Sort by symbol status SORT_BY_SYMBOL_INDEX_MW, class=class="str">"cmt">// Sort by index in the Market Watch window SORT_BY_SYMBOL_CUSTOM, class=class="str">"cmt">// Sort by custom symbol class="kw">property };
按市场属性给品种排序的枚举清单
在 MT5 的 Watch 列表批量处理里,SORT_BY_SYMBOL_* 这组枚举决定了你拉出来的品种数组按什么字段排。比如 SORT_BY_SYMBOL_SPREAD 是按点差点数排,SORT_BY_SYMBOL_VOLUME 是按最近一笔成交的量排,SORT_BY_SYMBOL_SESSION_DEALS 则按当前会话成交笔数排。 做跨品种扫描时,如果你只想盯点差最低的几个贵金属合约,用 SORT_BY_SYMBOL_SPREAD 配合 ArraySort 就能直接把高成本品种沉到底部。外汇和贵金属杠杆高、点差跳动快,排序结果只反映调用瞬间的快照,隔一根 tick 可能就变。 下面这段是这组枚举的原文定义,逐行看能确认每个值对应的排序维度:SORT_BY_SYMBOL_CHART_MODE 按构棒价格类型(Bid 或 Last)排;SORT_BY_SYMBOL_EXIST 按品种名是否存在排;SORT_BY_SYMBOL_SELECT 按是否在 Market Watch 被选中排;SORT_BY_SYMBOL_VISIBLE 按选中后是否可见排;SORT_BY_SYMBOL_SESSION_DEALS 按当前会话成交数排;SORT_BY_SYMBOL_SESSION_BUY_ORDERS 按当前买单总数排;SORT_BY_SYMBOL_SESSION_SELL_ORDERS 按当前卖单总数排;SORT_BY_SYMBOL_VOLUME 按最近成交量排;SORT_BY_SYMBOL_VOLUMEHIGH 按日内最大量排;SORT_BY_SYMBOL_VOLUMELOW 按日内最小量排;SORT_BY_SYMBOL_TIME 按最后报价时间排;SORT_BY_SYMBOL_DIGITS 按小数位排;SORT_BY_SYMBOL_DIGITS_LOT 按手数小数位排;SORT_BY_SYMBOL_SPREAD 按点差点数排;SORT_BY_SYMBOL_SPREAD_FLOAT 按浮动点差排;SORT_BY_SYMBOL_TICKS_BOOKDEPTH 按深度最大挂单数排;SORT_BY_SYMBOL_TRADE_CALC_MODE 按合约计价方式排;SORT_BY_SYMBOL_TRADE_MODE 按订单执行类型排;SORT_BY_SYMBOL_START_TIME 按交易起始日排(期货常用)。
SORT_BY_SYMBOL_CHART_MODE, class=class="str">"cmt">// Sort by price type for constructing bars – Bid or Last(from the ENUM_SYMBOL_CHART_MODE enumeration) SORT_BY_SYMBOL_EXIST, class=class="str">"cmt">// Sort by the flag that a symbol with such a name exists SORT_BY_SYMBOL_SELECT, class=class="str">"cmt">// Sort by the flag indicating that a symbol is selected in Market Watch SORT_BY_SYMBOL_VISIBLE, class=class="str">"cmt">// Sort by the flag indicating that a selected symbol is displayed in Market Watch SORT_BY_SYMBOL_SESSION_DEALS, class=class="str">"cmt">// Sort by the number of deals in the current session SORT_BY_SYMBOL_SESSION_BUY_ORDERS, class=class="str">"cmt">// Sort by the total number of current buy orders SORT_BY_SYMBOL_SESSION_SELL_ORDERS, class=class="str">"cmt">// Sort by the total number of current sell orders SORT_BY_SYMBOL_VOLUME, class=class="str">"cmt">// Sort by last deal volume SORT_BY_SYMBOL_VOLUMEHIGH, class=class="str">"cmt">// Sort by maximum volume for a day SORT_BY_SYMBOL_VOLUMELOW, class=class="str">"cmt">// Sort by minimum volume for a day SORT_BY_SYMBOL_TIME, class=class="str">"cmt">// Sort by the last quote time SORT_BY_SYMBOL_DIGITS, class=class="str">"cmt">// Sort by a number of decimal places SORT_BY_SYMBOL_DIGITS_LOT, class=class="str">"cmt">// Sort by a number of decimal places in a lot SORT_BY_SYMBOL_SPREAD, class=class="str">"cmt">// Sort by spread in points SORT_BY_SYMBOL_SPREAD_FLOAT, class=class="str">"cmt">// Sort by floating spread SORT_BY_SYMBOL_TICKS_BOOKDEPTH, class=class="str">"cmt">// Sort by a maximum number of requests displayed in the market depth SORT_BY_SYMBOL_TRADE_CALC_MODE, class=class="str">"cmt">// Sort by contract price calculation method(from the ENUM_SYMBOL_CALC_MODE enumeration) SORT_BY_SYMBOL_TRADE_MODE, class=class="str">"cmt">// Sort by order execution type(from the ENUM_SYMBOL_TRADE_MODE enumeration) SORT_BY_SYMBOL_START_TIME class=class="str">"cmt">// Sort by an instrument trading start date(usually used for futures)
「品种排序枚举里的到期与报价字段」
在 MT5 的 Symbols 排序枚举中,有一组按合约属性和实时报价归类的 SORT_BY_SYMBOL_* 常量,写多品种扫描器时直接套用即可,不用自己拼字符串比较。 前半段覆盖交易规则类字段:SORT_BY_SYMBOL_EXPIRATION_TIME 按品种交易结束日排(期货常用),SORT_BY_SYMBOL_TRADE_STOPS_LEVEL 按当前收盘价到止损单的最小点数距离排,SORT_BY_SYMBOL_TRADE_FREEZE_LEVEL 按成交冻结距离排,后面还带执行模式、掉期模型、三倍掉期日、对冲保证金算法等枚举类标识。 后半段从 FIRST_SYM_DBL_PROP 起切到双精度实数属性:SORT_BY_SYMBOL_BID / ASK 及其当日高 SORT_BY_SYMBOL_BIDHIGH、SORT_BY_SYMBOL_ASKHIGH 与当日低 BIDLOW、ASKLOW,外加 SORT_BY_SYMBOL_LAST 按最后成交价排。实盘里若想抓「日内 Ask 拉伸最大」的贵金属品种,循环里传 SORT_BY_SYMBOL_ASKHIGH 就能让数组头尾颠倒出候选。 外汇与贵金属杠杆高、点差跳变频繁,这类排序只解决「抓谁」不解决「何时动手」,验证前先在策略测试器跑一遍符号遍历开销。
SORT_BY_SYMBOL_EXPIRATION_TIME, class=class="str">"cmt">// Sort by an instrument trading end date(usually used for futures) SORT_BY_SYMBOL_TRADE_STOPS_LEVEL, class=class="str">"cmt">// Sort by the minimum indent from the current close price(in points) for setting Stop orders SORT_BY_SYMBOL_TRADE_FREEZE_LEVEL, class=class="str">"cmt">// Sort by trade operation freeze distance(in points) SORT_BY_SYMBOL_TRADE_EXEMODE, class=class="str">"cmt">// Sort by trade execution mode(from the ENUM_SYMBOL_TRADE_EXECUTION enumeration) SORT_BY_SYMBOL_SWAP_MODE, class=class="str">"cmt">// Sort by swap calculation model(from the ENUM_SYMBOL_SWAP_MODE enumeration) SORT_BY_SYMBOL_SWAP_ROLLOVER3DAYS, class=class="str">"cmt">// Sort by week day for accruing a triple swap(from the ENUM_DAY_OF_WEEK enumeration) SORT_BY_SYMBOL_MARGIN_HEDGED_USE_LEG, class=class="str">"cmt">// Sort by the calculation mode of a hedged margin using the larger leg(Buy or Sell) SORT_BY_SYMBOL_EXPIRATION_MODE, class=class="str">"cmt">// Sort by flags of allowed order expiration modes SORT_BY_SYMBOL_FILLING_MODE, class=class="str">"cmt">// Sort by flags of allowed order filling modes SORT_BY_SYMBOL_ORDER_MODE, class=class="str">"cmt">// Sort by flags of allowed order types SORT_BY_SYMBOL_ORDER_GTC_MODE, class=class="str">"cmt">// Sort by StopLoss and TakeProfit orders lifetime SORT_BY_SYMBOL_OPTION_MODE, class=class="str">"cmt">// Sort by option type(from the ENUM_SYMBOL_OPTION_MODE enumeration) SORT_BY_SYMBOL_OPTION_RIGHT, class=class="str">"cmt">// Sort by option right(Call/Put) (from the ENUM_SYMBOL_OPTION_RIGHT enumeration) class=class="str">"cmt">//--- Sort by real properties SORT_BY_SYMBOL_BID = FIRST_SYM_DBL_PROP, class=class="str">"cmt">// Sort by Bid SORT_BY_SYMBOL_BIDHIGH, class=class="str">"cmt">// Sort by maximum Bid for a day SORT_BY_SYMBOL_BIDLOW, class=class="str">"cmt">// Sort by minimum Bid for a day SORT_BY_SYMBOL_ASK, class=class="str">"cmt">// Sort by Ask SORT_BY_SYMBOL_ASKHIGH, class=class="str">"cmt">// Sort by maximum Ask for a day SORT_BY_SYMBOL_ASKLOW, class=class="str">"cmt">// Sort by minimum Ask for a day SORT_BY_SYMBOL_LAST class=class="str">"cmt">// Sort by the last deal price
◍ 市场观察窗口的排序枚举后半段
在 MT5 的 SymbolInfoInteger 与 Watch 窗口排序逻辑里,枚举值从日内极值延伸到交易属性层面。上面这批 SORT_BY_SYMBOL_* 常量,覆盖了 Last 高低、真实成交量极值、期权执行价、点值、tick 价值与规模等维度。 其中 SORT_BY_SYMBOL_VOLUME_REAL 与 SORT_BY_SYMBOL_VOLUMEHIGH_REAL 区分了「当日总真实成交量」和「当日最大真实成交量峰值」,做跨品种流动性扫描时,后者更容易暴露异常放量的标的。 涉及外汇与贵金属品种时,SORT_BY_SYMBOL_SWAP_LONG / SHORT 可直接按隔夜利息排序,但这类品种受杠杆与跳空影响,排序结果仅反映平台参数,实盘切换仓位仍有较高风险。 把下面这段枚举贴进 MT5 头文件或脚本里,就能在自定义观察列表时按所需字段排序,不必依赖终端默认视图。
SORT_BY_SYMBOL_LASTHIGH, class=class="str">"cmt">// Sort by maximum Last for a day SORT_BY_SYMBOL_LASTLOW, class=class="str">"cmt">// Sort by minimum Last for a day SORT_BY_SYMBOL_VOLUME_REAL, class=class="str">"cmt">// Sort by Volume for a day SORT_BY_SYMBOL_VOLUMEHIGH_REAL, class=class="str">"cmt">// Sort by maximum Volume for a day SORT_BY_SYMBOL_VOLUMELOW_REAL, class=class="str">"cmt">// Sort by minimum Volume for a day SORT_BY_SYMBOL_OPTION_STRIKE, class=class="str">"cmt">// Sort by an option execution price SORT_BY_SYMBOL_POINT, class=class="str">"cmt">// Sort by a single point value SORT_BY_SYMBOL_TRADE_TICK_VALUE, class=class="str">"cmt">// Sort by SYMBOL_TRADE_TICK_VALUE_PROFIT value SORT_BY_SYMBOL_TRADE_TICK_VALUE_PROFIT, class=class="str">"cmt">// Sort by a calculated tick price for a profitable position SORT_BY_SYMBOL_TRADE_TICK_VALUE_LOSS, class=class="str">"cmt">// Sort by a calculated tick price for a loss-making position SORT_BY_SYMBOL_TRADE_TICK_SIZE, class=class="str">"cmt">// Sort by a minimum price change SORT_BY_SYMBOL_TRADE_CONTRACT_SIZE, class=class="str">"cmt">// Sort by a trading contract size SORT_BY_SYMBOL_TRADE_ACCRUED_INTEREST, class=class="str">"cmt">// Sort by accrued interest SORT_BY_SYMBOL_TRADE_FACE_VALUE, class=class="str">"cmt">// Sort by face value SORT_BY_SYMBOL_TRADE_LIQUIDITY_RATE, class=class="str">"cmt">// Sort by liquidity rate SORT_BY_SYMBOL_VOLUME_MIN, class=class="str">"cmt">// Sort by a minimum volume for performing a deal SORT_BY_SYMBOL_VOLUME_MAX, class=class="str">"cmt">// Sort by a maximum volume for performing a deal SORT_BY_SYMBOL_VOLUME_STEP, class=class="str">"cmt">// Sort by a minimum volume change step for deal execution SORT_BY_SYMBOL_VOLUME_LIMIT, class=class="str">"cmt">// Sort by a maximum allowed aggregate volume of an open position and pending orders in one direction SORT_BY_SYMBOL_SWAP_LONG, class=class="str">"cmt">// Sort by a class="type">long swap value SORT_BY_SYMBOL_SWAP_SHORT class=class="str">"cmt">// Sort by a class="type">short swap value
按保证金与 session 维度给品种排序的枚举
在 MT5 的 SymbolInfo 系列排序接口里,有一组枚举专门用来给市场观察窗口或自定义品种列表按保证金维度重排。它们把「初始保证金」和「维持保证金」拆得很细:不仅区分多空方向,还把 Stop、Limit、StopLimit 挂单类型的初始/维持要求单独列出来。
比如 SORT_BY_SYMBOL_MARGIN_BUY_STOP_INITIAL 只按 BuyStop 挂单的初始保证金排序,SORT_BY_SYMBOL_MARGIN_SHORT_MAINTENANCE 则只看空单的维持保证金。做多品种组合监控时,用这些枚举能快速找出当前占用保证金最高的挂单类型,外汇与贵金属杠杆波动大,这类排序对风控可能有帮助。
另一批枚举面向当前交易时段的聚合数据:SORT_BY_SYMBOL_SESSION_VOLUME 按本节成交量、SORT_BY_SYMBOL_SESSION_TURNOVER 按成交额、SORT_BY_SYMBOL_SESSION_INTEREST 按未平仓兴趣总和、SORT_BY_SYMBOL_SESSION_BUY_ORDERS_VOLUME / SELL_ORDERS_VOLUME 分别按买卖挂单量、SORT_BY_SYMBOL_SESSION_OPEN 按时段开盘价。下面这段代码就是这组枚举的原文定义,可直接粘进 EA 头部做参考。
开 MT5 按 F4 进 MetaEditor,搜 SORT_BY_SYMBOL_MARGIN 能看到完整列表,试着把观察列表改成按 SORT_BY_SYMBOL_MARGIN_LONG_INITIAL 排序,就能对比黄金和欧美对长仓初始保证金的占用差异。
SORT_BY_SYMBOL_MARGIN_INITIAL, class=class="str">"cmt">// Sort by an initial margin SORT_BY_SYMBOL_MARGIN_MAINTENANCE, class=class="str">"cmt">// Sort by a maintenance margin for an instrument SORT_BY_SYMBOL_MARGIN_LONG_INITIAL, class=class="str">"cmt">// Sort by initial margin requirement applicable to Long orders SORT_BY_SYMBOL_MARGIN_BUY_STOP_INITIAL, class=class="str">"cmt">// Sort by initial margin requirement applicable to BuyStop orders SORT_BY_SYMBOL_MARGIN_BUY_LIMIT_INITIAL, class=class="str">"cmt">// Sort by initial margin requirement applicable to BuyLimit orders SORT_BY_SYMBOL_MARGIN_BUY_STOPLIMIT_INITIAL, class=class="str">"cmt">// Sort by initial margin requirement applicable to BuyStopLimit orders SORT_BY_SYMBOL_MARGIN_LONG_MAINTENANCE, class=class="str">"cmt">// Sort by maintenance margin requirement applicable to Long orders SORT_BY_SYMBOL_MARGIN_BUY_STOP_MAINTENANCE, class=class="str">"cmt">// Sort by maintenance margin requirement applicable to BuyStop orders SORT_BY_SYMBOL_MARGIN_BUY_LIMIT_MAINTENANCE, class=class="str">"cmt">// Sort by maintenance margin requirement applicable to BuyLimit orders SORT_BY_SYMBOL_MARGIN_BUY_STOPLIMIT_MAINTENANCE, class=class="str">"cmt">// Sort by maintenance margin requirement applicable to BuyStopLimit orders SORT_BY_SYMBOL_MARGIN_SHORT_INITIAL, class=class="str">"cmt">// Sort by initial margin requirement applicable to Short orders SORT_BY_SYMBOL_MARGIN_SELL_STOP_INITIAL, class=class="str">"cmt">// Sort by initial margin requirement applicable to SellStop orders SORT_BY_SYMBOL_MARGIN_SELL_LIMIT_INITIAL, class=class="str">"cmt">// Sort by initial margin requirement applicable to SellLimit orders SORT_BY_SYMBOL_MARGIN_SELL_STOPLIMIT_INITIAL, class=class="str">"cmt">// Sort by initial margin requirement applicable to SellStopLimit orders SORT_BY_SYMBOL_MARGIN_SHORT_MAINTENANCE, class=class="str">"cmt">// Sort by maintenance margin requirement applicable to Short orders SORT_BY_SYMBOL_MARGIN_SELL_STOP_MAINTENANCE, class=class="str">"cmt">// Sort by maintenance margin requirement applicable to SellStop orders SORT_BY_SYMBOL_MARGIN_SELL_LIMIT_MAINTENANCE, class=class="str">"cmt">// Sort by maintenance margin requirement applicable to SellLimit orders SORT_BY_SYMBOL_MARGIN_SELL_STOPLIMIT_MAINTENANCE, class=class="str">"cmt">// Sort by maintenance margin requirement applicable to SellStopLimit orders SORT_BY_SYMBOL_SESSION_VOLUME, class=class="str">"cmt">// Sort by summary volume of the current session deals SORT_BY_SYMBOL_SESSION_TURNOVER, class=class="str">"cmt">// Sort by the summary turnover of the current session SORT_BY_SYMBOL_SESSION_INTEREST, class=class="str">"cmt">// Sort by the summary open interest SORT_BY_SYMBOL_SESSION_BUY_ORDERS_VOLUME, class=class="str">"cmt">// Sort by the current volume of Buy orders SORT_BY_SYMBOL_SESSION_SELL_ORDERS_VOLUME, class=class="str">"cmt">// Sort by the current volume of Sell orders SORT_BY_SYMBOL_SESSION_OPEN class=class="str">"cmt">// Sort by a session Open price
「按交易时段与字符串属性给品种排序的枚举」
在 MT5 的 CSymbol 类体系里,品种集合的排序枚举不止按数值,还延伸到当前交易时段的各类价格和字符串类属性。下面这段枚举定义直接决定了你调用 Sort() 时底层按哪个字段排。 SORT_BY_SYMBOL_SESSION_CLOSE 按当前时段收盘价排;SORT_BY_SYMBOL_SESSION_AW 按时段加权平均价排;SORT_BY_SYMBOL_SESSION_PRICE_SETTLEMENT 按结算价排;SORT_BY_SYMBOL_SESSION_PRICE_LIMIT_MIN / MAX 分别按时段最低、最高价排。这类枚举在跨时段复盘黄金或外汇对时,能快速把波动剧烈的标的挑出来。 字符串类排序从 SORT_BY_SYMBOL_NAME = FIRST_SYM_STR_PROP 起跳,覆盖基础资产(SORT_BY_SYMBOL_BASIS)、基币(SORT_BY_SYMBOL_CURRENCY_BASE)、利润币与保证金币、报价源银行(SORT_BY_SYMBOL_BANK)、描述、自定义品种公式、ISIN 码、信息页地址以及品种树路径(SORT_BY_SYMBOL_PATH)。 开 MT5 按 F4 进 MetaEditor,在 Symbol.mqh 里搜 SORT_BY_SYMBOL_SESSION 就能看到完整枚举;改一个排序字段名,你的品种监控面板可能就会按保证金币重新分组。外汇与贵金属杠杆高,排序只是观察工具,不代表任何方向判断。
SORT_BY_SYMBOL_SESSION_CLOSE, class=class="str">"cmt">// Sort by a session Close price SORT_BY_SYMBOL_SESSION_AW, class=class="str">"cmt">// Sort by an average weighted price of the current session SORT_BY_SYMBOL_SESSION_PRICE_SETTLEMENT, class=class="str">"cmt">// Sort by a settlement price of the current session SORT_BY_SYMBOL_SESSION_PRICE_LIMIT_MIN, class=class="str">"cmt">// Sort by a minimum price of the current session SORT_BY_SYMBOL_SESSION_PRICE_LIMIT_MAX, class=class="str">"cmt">// Sort by a maximum price of the current session SORT_BY_SYMBOL_MARGIN_HEDGED, class=class="str">"cmt">// Sort by a contract size or a margin value per one lot of hedged positions class=class="str">"cmt">//--- Sort by class="type">class="kw">string properties SORT_BY_SYMBOL_NAME = FIRST_SYM_STR_PROP, class=class="str">"cmt">// Sort by a symbol name SORT_BY_SYMBOL_BASIS, class=class="str">"cmt">// Sort by an underlying asset of a derivative SORT_BY_SYMBOL_CURRENCY_BASE, class=class="str">"cmt">// Sort by a base currency of a symbol SORT_BY_SYMBOL_CURRENCY_PROFIT, class=class="str">"cmt">// Sort by a profit currency SORT_BY_SYMBOL_CURRENCY_MARGIN, class=class="str">"cmt">// Sort by a margin currency SORT_BY_SYMBOL_BANK, class=class="str">"cmt">// Sort by a feeder of the current quote SORT_BY_SYMBOL_DESCRIPTION, class=class="str">"cmt">// Sort by a symbol class="type">class="kw">string description SORT_BY_SYMBOL_FORMULA, class=class="str">"cmt">// Sort by the formula used for custom symbol pricing SORT_BY_SYMBOL_ISIN, class=class="str">"cmt">// Sort by the name of a symbol in the ISIN system SORT_BY_SYMBOL_PAGE, class=class="str">"cmt">// Sort by an address of the web page containing symbol information SORT_BY_SYMBOL_PATH class=class="str">"cmt">// Sort by a path in the symbol tree };
◍ 把品种属性装进一个结构体
在写自己的 EA 或指标前,先把交易品种的运行属性归拢到一个私有结构体里,比每次都去调 SymbolInfoInteger 要干净。下面这段从实际工程里抽出来的 MqlDataSymbol,就专门缓存了 session 内的挂单数量、当日成交量极值和当前点差。 注意 trade_mode 用的是 ENUM_SYMBOL_TRADE_MODE,它直接决定该品种允许的下单填充方式;stops_level 和 freeze_level 以点为单位的硬性距离,会在你设止损或改单时立刻生效。外汇与贵金属杠杆高,点差和冻结距离跳变时,未考虑这些字段的订单可能直接被拒绝。 把 last deal volume 和当日 volume_high_day / volume_low_day 一起存下来,后面做量价异常判断就有现成基准,不用每次重查终端。
class="macro">#class="kw">property copyright "Copyright class="num">2018, MetaQuotes Software Corp." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#class="kw">property strict class=class="str">"cmt">// Necessary for mql4 class="macro">#include "..\BaseObj.mqh" class CSymbol : class="kw">public CBaseObj { class="kw">private: class="kw">struct MqlDataSymbol { class=class="str">"cmt">//--- Symbol integer properties ENUM_SYMBOL_TRADE_MODE trade_mode; class=class="str">"cmt">// SYMBOL_TRADE_MODE Order filling modes class="type">long session_deals; class=class="str">"cmt">// SYMBOL_SESSION_DEALS The number of deals in the current session class="type">long session_buy_orders; class=class="str">"cmt">// SYMBOL_SESSION_BUY_ORDERS The total number of current buy orders class="type">long session_sell_orders; class=class="str">"cmt">// SYMBOL_SESSION_SELL_ORDERS The total number of current sell orders class="type">long volume; class=class="str">"cmt">// SYMBOL_VOLUME Last deal volume class="type">long volume_high_day; class=class="str">"cmt">// SYMBOL_VOLUMEHIGH Maximum volume within a day class="type">long volume_low_day; class=class="str">"cmt">// SYMBOL_VOLUMELOW Minimum volume within a day class="type">int spread; class=class="str">"cmt">// SYMBOL_SPREAD Spread in points class="type">int stops_level; class=class="str">"cmt">// SYMBOL_TRADE_STOPS_LEVEL Minimum distance in points from the current close price for setting Stop orders class="type">int freeze_level; class=class="str">"cmt">// SYMBOL_TRADE_FREEZE_LEVEL Freeze distance for trading operations(in points) class=class="str">"cmt">//--- Symbol real properties class="type">class="kw">double bid_last; class=class="str">"cmt">// SYMBOL_BID/SYMBOL_LAST Bid - the best sell offer/Last deal price class="type">class="kw">double bid_last_high; class=class="str">"cmt">// SYMBOL_BIDHIGH/SYMBOL_LASTHIGH Maximum Bid within the day/Maximum Last per day
日内极值与场内挂单量的结构字段
在 MQL5 里抓取品种当日状态,靠的是一组 double 字段把买卖双边极值和真实成交量摊开。下面这段结构体成员直接对应 SYMBOL_BIDLOW、SYMBOL_ASKHIGH 等终端属性,开盘后实时刷新,用来判断日内波动边界比自己算高低点更省事。 double bid_last_low 存的是当天 Bid 最低与 Last 最低;ask_high / ask_low 则是 Ask 的日内上下沿。volume_real_day 配合 volume_high_real_day、volume_low_real_day,能让你直接读出当天真实成交量的区间,不用翻成交量直方图。 session_buy_ord_volume 与 session_sell_ord_volume 是当下场内的买、卖挂单总体积,和 session_interest(未平持仓总量)放一起看,能粗略感知多空筹码谁更重。session_open / session_close / session_aw 给的是本交易时段的开、收及成交量加权均价,做时段内均值回归参考够用。 外汇与贵金属杠杆高、滑点突发行情多,这些字段只是快照,极端分钟可能滞后一两 tick,验证时建议在 MT5 用 SymbolInfoDouble 逐项打印对照。 把当前与上一根品种数据分别塞进 m_struct_curr_symbol 和 m_struct_prev_symbol,差值比较就能写进场过滤:比如 prev.ask_high 被突破且 curr.session_buy_ord_volume 放大,倾向视为买压增强,但仅为概率信号。
class="type">class="kw">double bid_last_low; class=class="str">"cmt">// SYMBOL_BIDLOW/SYMBOL_LASTLOW Minimum Bid within the day/Minimum Last per day class="type">class="kw">double ask; class=class="str">"cmt">// SYMBOL_ASK Ask - nest buy offer class="type">class="kw">double ask_high; class=class="str">"cmt">// SYMBOL_ASKHIGH Maximum Ask of the day class="type">class="kw">double ask_low; class=class="str">"cmt">// SYMBOL_ASKLOW Minimum Ask of the day class="type">class="kw">double volume_real_day; class=class="str">"cmt">// SYMBOL_VOLUME_REAL Real Volume of the day class="type">class="kw">double volume_high_real_day; class=class="str">"cmt">// SYMBOL_VOLUMEHIGH_REAL Maximum real Volume of the day class="type">class="kw">double volume_low_real_day; class=class="str">"cmt">// SYMBOL_VOLUMELOW_REAL Minimum real Volume of the day class="type">class="kw">double option_strike; class=class="str">"cmt">// SYMBOL_OPTION_STRIKE Strike price class="type">class="kw">double volume_limit; class=class="str">"cmt">// SYMBOL_VOLUME_LIMIT Maximum permissible total volume for a position and pending orders in one direction class="type">class="kw">double swap_long; class=class="str">"cmt">// SYMBOL_SWAP_LONG Long swap value class="type">class="kw">double swap_short; class=class="str">"cmt">// SYMBOL_SWAP_SHORT Short swap value class="type">class="kw">double session_volume; class=class="str">"cmt">// SYMBOL_SESSION_VOLUME The total volume of deals in the current session class="type">class="kw">double session_turnover; class=class="str">"cmt">// SYMBOL_SESSION_TURNOVER The total turnover in the current session class="type">class="kw">double session_interest; class=class="str">"cmt">// SYMBOL_SESSION_INTEREST The total volume of open positions class="type">class="kw">double session_buy_ord_volume; class=class="str">"cmt">// SYMBOL_SESSION_BUY_ORDERS_VOLUME The total volume of Buy orders at the moment class="type">class="kw">double session_sell_ord_volume; class=class="str">"cmt">// SYMBOL_SESSION_SELL_ORDERS_VOLUME The total volume of Sell orders at the moment class="type">class="kw">double session_open; class=class="str">"cmt">// SYMBOL_SESSION_OPEN Session open price class="type">class="kw">double session_close; class=class="str">"cmt">// SYMBOL_SESSION_CLOSE Session close price class="type">class="kw">double session_aw; class=class="str">"cmt">// SYMBOL_SESSION_AW The average weighted price of the session }; MqlDataSymbol m_struct_curr_symbol; class=class="str">"cmt">// Current symbol data MqlDataSymbol m_struct_prev_symbol; class=class="str">"cmt">// Previous symbol data class=class="str">"cmt">//--- class=class="str">"cmt">//--- Execution
「会话内订单流变量怎么挂」
在 MT5 的 EA 或指标类里,想盯住「当前会话」的成交与挂单异动,先得把状态变量铺好。下面这段声明把交易模式切换、成交增减、买卖挂单增减的控制值与触发旗标一次性列清,复制进头文件就能用。 bool m_is_change_trade_mode; // 某品种交易模式被切换的旗标 //--- 当前会话成交 long m_control_session_deals_inc; // 控制的成交数增长阈值 long m_control_session_deals_dec; // 控制的成交数减少阈值 long m_changed_session_deals_value; // 成交数实际变化量 bool m_is_change_session_deals_inc; // 成交增幅超阈旗标 bool m_is_change_session_deals_dec; // 成交降幅超阈旗标 //--- 当前会话 Buy 挂单 long m_control_session_buy_ord_inc; // 买挂单增长阈值 long m_control_session_buy_ord_dec; // 买挂单减少阈值 long m_changed_session_buy_ord_value; // 买挂单变化量 bool m_is_change_session_buy_ord_inc; // 买挂单增幅超阈旗标 bool m_is_change_session_buy_ord_dec; // 买挂单降幅超阈旗标 //--- 当前会话 Sell 挂单 long m_control_session_sell_ord_inc; // 卖挂单增长阈值 long m_control_session_sell_ord_dec; // 卖挂单减少阈值 long m_changed_session_sell_ord_value; // 卖挂单变化量 bool m_is_change_session_sell_ord_inc; // 卖挂单增幅超阈旗标 bool m_is_change_session_sell_ord_dec; // 卖挂单降幅超阈旗标 注意 m_control_session_deals_inc 这类阈值默认是 0,不赋初值的话旗标永远不触发。实盘外汇与贵金属波动大、滑点随机,会话内订单流突变可能只是流动性真空,不代表趋势确认,用这些旗标只作概率参考。 把 m_changed_session_deals_value 和阈值在 OnTick 里比大小,就能在成交突然放量时亮灯。打开 MT5 新建一个空 EA,把上面变量贴进类私有区,编译过一遍看有没有漏掉分号。
class="type">bool m_is_change_trade_mode; class=class="str">"cmt">// Flag of changing trading mode for a symbol class=class="str">"cmt">//--- Current session deals class="type">long m_control_session_deals_inc; class=class="str">"cmt">// Controlled value of the growth of the number of deals class="type">long m_control_session_deals_dec; class=class="str">"cmt">// Controlled value of the decrease in the number of deals class="type">long m_changed_session_deals_value; class=class="str">"cmt">// Value of change in the number of deals class="type">bool m_is_change_session_deals_inc; class=class="str">"cmt">// Flag of a change in the number of deals exceeding the growth value class="type">bool m_is_change_session_deals_dec; class=class="str">"cmt">// Flag of a change in the number of deals exceeding the decrease value class=class="str">"cmt">//--- Buy orders of the current session class="type">long m_control_session_buy_ord_inc; class=class="str">"cmt">// Controlled value of the growth of the number of Buy orders class="type">long m_control_session_buy_ord_dec; class=class="str">"cmt">// Controlled value of the decrease in the number of Buy orders class="type">long m_changed_session_buy_ord_value; class=class="str">"cmt">// Buy orders change value class="type">bool m_is_change_session_buy_ord_inc; class=class="str">"cmt">// Flag of a change in the number of Buy orders exceeding the growth value class="type">bool m_is_change_session_buy_ord_dec; class=class="str">"cmt">// Flag of a change in the number of Buy orders being less than the growth value class=class="str">"cmt">//--- Sell orders of the current session class="type">long m_control_session_sell_ord_inc; class=class="str">"cmt">// Controlled value of the growth of the number of Sell orders class="type">long m_control_session_sell_ord_dec; class=class="str">"cmt">// Controlled value of the decrease in the number of Sell orders class="type">long m_changed_session_sell_ord_value; class=class="str">"cmt">// Sell orders change value class="type">bool m_is_change_session_sell_ord_inc; class=class="str">"cmt">// Flag of a change in the number of Sell orders exceeding the growth value class="type">bool m_is_change_session_sell_ord_dec; class=class="str">"cmt">// Flag of a change in the number of Sell orders exceeding the decrease value class=class="str">"cmt">//--- Volume of the last deal
◍ 成交量与价差的控制变量怎么挂
在 MT5 的 EA 或指标类里,要把「上一笔成交的成交量变化」和「日内最大最小成交量」都做成可控状态量,靠的就是一组 long/bool 配对成员。下面这段声明直接贴在类私有区就能用。
class="type">long m_control_volume_inc; class=class="str">"cmt">// 上一笔交易成交量增长的控制阈值 class="type">long m_control_volume_dec; class=class="str">"cmt">// 上一笔交易成交量减少的控制阈值 class="type">long m_changed_volume_value; class=class="str">"cmt">// 上一笔交易实际成交量变化值 class="type">bool m_is_change_volume_inc; class=class="str">"cmt">// 实际变化是否超过增长阈值的标记 class="type">bool m_is_change_volume_dec; class=class="str">"cmt">// 实际变化是否低于减少阈值的标记 class=class="str">"cmt">//--- 日内最大成交量 class="type">long m_control_volume_high_day_inc; class=class="str">"cmt">// 日内最大成交量增长控制值 class="type">long m_control_volume_high_day_dec; class=class="str">"cmt">// 日内最大成交量减少控制值 class="type">long m_changed_volume_high_day_value; class=class="str">"cmt">// 日内最大成交量变化值 class="type">bool m_is_change_volume_high_day_inc; class=class="str">"cmt">// 日内最大量超增长阈值标记 class="type">bool m_is_change_volume_high_day_dec; class=class="str">"cmt">// 日内最大量超减少阈值标记 class=class="str">"cmt">//--- 日内最小成交量 class="type">long m_control_volume_low_day_inc; class=class="str">"cmt">// 日内最小成交量增长控制值 class="type">long m_control_volume_low_day_dec; class=class="str">"cmt">// 日内最小成交量减少控制值 class="type">long m_changed_volume_low_day_value; class=class="str">"cmt">// 日内最小成交量变化值 class="type">bool m_is_change_volume_low_day_inc; class=class="str">"cmt">// 日内最小量超增长阈值标记 class="type">bool m_is_change_volume_low_day_dec; class=class="str">"cmt">// 日内最小量超减少阈值标记 class=class="str">"cmt">//--- 价差 class="type">int m_control_spread_inc; class=class="str">"cmt">// 价差增长控制值(点) class="type">int m_control_spread_dec; class=class="str">"cmt">// 价差减少控制值(点)
class="type">long m_control_volume_inc; class=class="str">"cmt">// Controlled value of the volume growth in the last deal class="type">long m_control_volume_dec; class=class="str">"cmt">// Controlled value of the volume decrease in the last deal class="type">long m_changed_volume_value; class=class="str">"cmt">// Value of the volume change in the last deal class="type">bool m_is_change_volume_inc; class=class="str">"cmt">// Flag of the volume change in the last deal exceeding the growth value class="type">bool m_is_change_volume_dec; class=class="str">"cmt">// Flag of the volume change in the last deal being less than the growth value class=class="str">"cmt">//--- Maximum volume within a day class="type">long m_control_volume_high_day_inc; class=class="str">"cmt">// Controlled value of the maximum volume growth for a day class="type">long m_control_volume_high_day_dec; class=class="str">"cmt">// Controlled value of the maximum volume decrease for a day class="type">long m_changed_volume_high_day_value; class=class="str">"cmt">// Maximum volume change value within a day class="type">bool m_is_change_volume_high_day_inc; class=class="str">"cmt">// Flag of the maximum day volume exceeding the growth value class="type">bool m_is_change_volume_high_day_dec; class=class="str">"cmt">// Flag of the maximum day volume exceeding the decrease value class=class="str">"cmt">//--- Minimum volume within a day class="type">long m_control_volume_low_day_inc; class=class="str">"cmt">// Controlled value of the minimum volume growth for a day class="type">long m_control_volume_low_day_dec; class=class="str">"cmt">// Controlled value of the minimum volume decrease for a day class="type">long m_changed_volume_low_day_value; class=class="str">"cmt">// Minimum volume change value within a day class="type">bool m_is_change_volume_low_day_inc; class=class="str">"cmt">// Flag of the minimum day volume exceeding the growth value class="type">bool m_is_change_volume_low_day_dec; class=class="str">"cmt">// Flag of the minimum day volume exceeding the decrease value class=class="str">"cmt">//--- Spread class="type">int m_control_spread_inc; class=class="str">"cmt">// Controlled spread growth value in points class="type">int m_control_spread_dec; class=class="str">"cmt">// Controlled spread decrease value in points
点差与挂单距离的变动监控字段
在 MT5 的 EA 或指标里,要把经纪商实时调整的合约参数抓出来,得先声明一组状态变量。下面这段声明覆盖了点差、StopLevel、FreezeLevel 以及 Bid/Last 价格四类变动的阈值与触发标记。 点差部分用 int 存变动点数,bool 标记是否突破你预设的放大或缩小阈值;StopLevel 和 FreezeLevel 同理,只是分别对应开仓止损最小距离和报价冻结距离,单位都是点(point)。 Bid/Last 改用 double,因为价格本身带小数,控制阈值也用浮点。m_is_change_bid_last_inc 这类布尔量就是给后续逻辑判断用的:一旦实际变动超了控制值,标记翻真。 开 MT5 随便建个 EA 把这段粘进类里,编译后能在 OnTick 里读这些成员,验证经纪商在非农前后把点差从 12 点拉到 30 点时 m_is_change_spread_inc 是否置位。外汇和贵金属杠杆高,这类参数跳变常伴随滑点放大,实盘前务必用策略测试器回放。
class="type">int m_changed_spread_value; class=class="str">"cmt">// Spread change value in points class="type">bool m_is_change_spread_inc; class=class="str">"cmt">// Flag of spread change in points exceeding the growth value class="type">bool m_is_change_spread_dec; class=class="str">"cmt">// Flag of spread change in points exceeding the decrease value class=class="str">"cmt">//--- StopLevel class="type">int m_control_stops_level_inc; class=class="str">"cmt">// Controlled StopLevel growth value in points class="type">int m_control_stops_level_dec; class=class="str">"cmt">// Controlled StopLevel decrease value in points class="type">int m_changed_stops_level_value; class=class="str">"cmt">// StopLevel change value in points class="type">bool m_is_change_stops_level_inc; class=class="str">"cmt">// Flag of StopLevel change in points exceeding the growth value class="type">bool m_is_change_stops_level_dec; class=class="str">"cmt">// Flag of StopLevel change in points exceeding the decrease value class=class="str">"cmt">//--- Freeze distance class="type">int m_control_freeze_level_inc; class=class="str">"cmt">// Controlled FreezeLevel growth value in points class="type">int m_control_freeze_level_dec; class=class="str">"cmt">// Controlled FreezeLevel decrease value in points class="type">int m_changed_freeze_level_value; class=class="str">"cmt">// FreezeLevel change value in points class="type">bool m_is_change_freeze_level_inc; class=class="str">"cmt">// Flag of FreezeLevel change in points exceeding the growth value class="type">bool m_is_change_freeze_level_dec; class=class="str">"cmt">// Flag of FreezeLevel change in points exceeding the decrease value class=class="str">"cmt">//--- Bid/Last class="type">class="kw">double m_control_bid_last_inc; class=class="str">"cmt">// Controlled value of Bid or Last price growth class="type">class="kw">double m_control_bid_last_dec; class=class="str">"cmt">// Controlled value of Bid or Last price decrease class="type">class="kw">double m_changed_bid_last_value; class=class="str">"cmt">// Bid or Last price change value class="type">bool m_is_change_bid_last_inc; class=class="str">"cmt">// Flag of Bid or Last price change exceeding the growth value
「日内极值与买卖报价的变动监控变量」
在 MT5 自定义指标或 EA 里,要把「价格异动」量化出来,第一步是声明一组状态变量。下面这段声明把 Bid/Last 的实时变动、日内最高/最低价的变动、以及 Ask 的变动分别用双精度浮点和布尔旗标记录下来,方便后续在 OnTick 里做阈值比对。 外汇与贵金属属于高杠杆高风险品种,这些阈值只是监控参考,不代表任何方向确定性,实际触发后价格仍可能反向。 代码逐行拆解: bool m_is_change_bid_last_last_dec; // 布尔旗标:Bid或Last最新价下跌幅度超过设定减值时为true double m_control_bid_last_high_inc; // 日内Bid/Last最高价可控的上涨阈值 double m_control_bid_last_high_dec; // 日内Bid/Last最高价可控的下跌阈值 double m_changed_bid_last_high_value; // 当日Bid/Last最高价实际变动值 bool m_is_change_bid_last_high_inc; // 最高价涨幅超阈值置true bool m_is_change_bid_last_high_dec; // 最高价跌幅超阈值置true double m_control_bid_last_low_inc; // 日内最低价可控上涨阈值 double m_control_bid_last_low_dec; // 日内最低价可控下跌阈值 double m_changed_bid_last_low_value; // 当日最低价实际变动值 bool m_is_change_bid_last_low_inc; // 最低价涨幅超阈值置true bool m_is_change_bid_last_low_dec; // 最低价跌幅超阈值置true double m_control_ask_inc; // Ask价上涨可控阈值 double m_control_ask_dec; // Ask价下跌可控阈值 double m_changed_ask_value; // Ask价实际变动值 bool m_is_change_ask_inc; // Ask涨幅超阈值置true bool m_is_change_ask_dec; // Ask跌幅超阈值置true 开盘后若 m_changed_bid_last_high_value 相对昨收超过 30 点(具体点数取决于品种和设置),m_is_change_bid_last_high_inc 会被置位,这时小布盯盘类脚本就能弹提示或写日志。你可以直接把这组变量拷进自己的 include 头文件,先打印旗标看触发频率。
class="type">bool m_is_change_bid_last_last_dec; class=class="str">"cmt">// Flag of Bid or Last price change exceeding the decrease value class=class="str">"cmt">//--- Maximum Bid/Last of the day class="type">class="kw">double m_control_bid_last_high_inc; class=class="str">"cmt">// Controlled growth value of the maximum Bid or Last price of the day class="type">class="kw">double m_control_bid_last_high_dec; class=class="str">"cmt">// Controlled decrease value of the maximum Bid or Last price of the day class="type">class="kw">double m_changed_bid_last_high_value; class=class="str">"cmt">// Maximum Bid or Last change value for the day class="type">bool m_is_change_bid_last_high_inc; class=class="str">"cmt">// Flag of the maximum Bid or Last price change for the day exceeding the growth value class="type">bool m_is_change_bid_last_high_dec; class=class="str">"cmt">// Flag of the maximum Bid or Last price change for the day exceeding the decrease value class=class="str">"cmt">//--- Minimum Bid/Last of the day class="type">class="kw">double m_control_bid_last_low_inc; class=class="str">"cmt">// Controlled growth value of the minimum Bid or Last price of the day class="type">class="kw">double m_control_bid_last_low_dec; class=class="str">"cmt">// Controlled decrease value of the minimum Bid or Last price of the day class="type">class="kw">double m_changed_bid_last_low_value; class=class="str">"cmt">// Minimum Bid or Last change value for the day class="type">bool m_is_change_bid_last_low_inc; class=class="str">"cmt">// Flag of the minimum Bid or Last price change for the day exceeding the growth value class="type">bool m_is_change_bid_last_low_dec; class=class="str">"cmt">// Flag of the minimum Bid or Last price change for the day exceeding the decrease value class=class="str">"cmt">//--- Ask class="type">class="kw">double m_control_ask_inc; class=class="str">"cmt">// Controlled value of the Ask price growth class="type">class="kw">double m_control_ask_dec; class=class="str">"cmt">// Controlled value of the Ask price decrease class="type">class="kw">double m_changed_ask_value; class=class="str">"cmt">// Ask price change value class="type">bool m_is_change_ask_inc; class=class="str">"cmt">// Flag of the Ask price change exceeding the growth value class="type">bool m_is_change_ask_dec; class=class="str">"cmt">// Flag of the Ask price change exceeding the decrease value class=class="str">"cmt">//--- Maximum Ask price for the day
◍ 日内极值与真实成交量的受控变量声明
在 MT5 的 EA 或指标类里,先把日内 Ask 高/低、真实成交量及其最大值的变化监控变量一次性声明好,后面才方便做阈值比对。下面这段声明把「受控增减值」「实际变化值」「越界标志位」三组变量拆开,逻辑上能直接套进日内突破预警。 外汇与贵金属市场高风险,真实成交量(Real Volume)在多数经纪商处仅代表自身服务器成交,并非全市场量,用它做信号须加概率限定,不可当作确定性依据。 变量命名规律很直白:m_control_* 是人为设定的增减阈值,m_changed_* 是当日实测偏移,m_is_change_* 是布尔标志,超标即为 true。例如 m_control_ask_high_inc 控的是当日 Ask 最高价上行受控幅度,m_is_change_ask_high_inc 为真时说明已突破该幅度,可能暗示日内买方推动增强。 把这套声明原样贴进头文件或全局区,开 MT5 用策略测试器跑一天 tick,就能在观测窗口看到哪些 bool 被置位,据此调 m_control_* 参数比盲设止损更有的放矢。
class="type">class="kw">double m_control_ask_high_inc; class=class="str">"cmt">// Controlled growth value of the maximum Ask price of the day class="type">class="kw">double m_control_ask_high_dec; class=class="str">"cmt">// Controlled decrease value of the maximum Ask price of the day class="type">class="kw">double m_changed_ask_high_value; class=class="str">"cmt">// Maximum Ask price change value for the day class="type">bool m_is_change_ask_high_inc; class=class="str">"cmt">// Flag of the maximum Ask price change for the day exceeding the growth value class="type">bool m_is_change_ask_high_dec; class=class="str">"cmt">// Flag of the maximum Ask price change for the day exceeding the decrease value class=class="str">"cmt">//--- Minimum Ask price for the day class="type">class="kw">double m_control_ask_low_inc; class=class="str">"cmt">// Controlled growth value of the minimum Ask price of the day class="type">class="kw">double m_control_ask_low_dec; class=class="str">"cmt">// Controlled decrease value of the minimum Ask price of the day class="type">class="kw">double m_changed_ask_low_value; class=class="str">"cmt">// Minimum Ask price change value for the day class="type">bool m_is_change_ask_low_inc; class=class="str">"cmt">// Flag of the minimum Ask price change for the day exceeding the growth value class="type">bool m_is_change_ask_low_dec; class=class="str">"cmt">// Flag of the minimum Ask price change for the day exceeding the decrease value class=class="str">"cmt">//--- Real Volume for the day class="type">class="kw">double m_control_volume_real_inc; class=class="str">"cmt">// Controlled value of the real volume growth of the day class="type">class="kw">double m_control_volume_real_dec; class=class="str">"cmt">// Controlled value of the real volume decrease of the day class="type">class="kw">double m_changed_volume_real_value; class=class="str">"cmt">// Real volume change value of the day class="type">bool m_is_change_volume_real_inc; class=class="str">"cmt">// Flag of the real volume change for the day exceeding the growth value class="type">bool m_is_change_volume_real_dec; class=class="str">"cmt">// Flag of the real volume change for the day exceeding the decrease value class=class="str">"cmt">//--- Maximum real volume for the day class="type">class="kw">double m_control_volume_high_real_day_inc; class=class="str">"cmt">// Controlled value of the maximum real volume growth of the day class="type">class="kw">double m_control_volume_high_real_day_dec; class=class="str">"cmt">// Controlled value of the maximum real volume decrease of the day
日内真实成交量与期权阈值的变量落地
在 MT5 的 EA 或指标源码里,把「日内真实成交量」和「期权行权价」的临界状态拆成一组双布尔变量,是后续触发逻辑的前置条件。下面这段声明直接定义了当日真实成交量峰值、谷值各自对应的增长/衰减控制值与触发旗标。
class="type">class="kw">double m_changed_volume_high_real_day_value; class=class="str">"cmt">// 当日真实成交量最大变动值 class="type">class="kw">double m_control_volume_low_real_day_inc; class=class="str">"cmt">// 当日真实成交量最小增长控制值 class="type">class="kw">double m_control_volume_low_real_day_dec; class=class="str">"cmt">// 当日真实成交量最小衰减控制值 class="type">class="kw">double m_changed_volume_low_real_day_value; class=class="str">"cmt">// 当日真实成交量最小变动值 class="type">bool m_is_change_volume_high_real_day_inc; class=class="str">"cmt">// 当日最大真实成交量变动突破增长阈值旗标 class="type">bool m_is_change_volume_high_real_day_dec; class=class="str">"cmt">// 当日最大真实成交量变动突破衰减阈值旗标 class="type">bool m_is_change_volume_low_real_day_inc; class=class="str">"cmt">// 当日最小真实成交量变动突破增长阈值旗标 class="type">bool m_is_change_volume_low_real_day_dec; class=class="str">"cmt">// 当日最小真实成交量变动突破衰减阈值旗标 class="type">class="kw">double m_control_option_strike_inc; class=class="str">"cmt">// 行权价增长控制值 class="type">class="kw">double m_control_option_strike_dec; class=class="str">"cmt">// 行权价衰减控制值 class="type">class="kw">double m_changed_option_strike_value; class=class="str">"cmt">// 行权价变动值 class="type">bool m_is_change_option_strike_inc; class=class="str">"cmt">// 行权价变动突破增长阈值旗标 class="type">bool m_is_change_option_strike_dec; class=class="str">"cmt">// 行权价变动突破衰减阈值旗标 class="type">class="kw">double m_changed_volume_limit_value; class=class="str">"cmt">// 持仓与挂单总成交量最小变动值 class="type">bool m_is_change_volume_limit_inc; class=class="str">"cmt">// 总成交量最小增长旗标 class="type">bool m_is_change_volume_limit_dec; class=class="str">"cmt">// 总成交量最小衰减旗标 class="type">class="kw">double m_changed_swap_long_value; class=class="str">"cmt">// 多头掉期变动值 class="type">bool m_is_change_swap_long_inc; class=class="str">"cmt">// 多头掉期增长旗标 class="type">bool m_is_change_swap_long_dec; class=class="str">"cmt">// 多头掉期衰减旗标
m_is_change_volume_high_real_day_inc 与 m_is_change_volume_high_real_day_dec 是互斥使用的:同一根日线_bar 内,真实成交量峰值若向上刺穿 m_control_volume_low_real_day_inc 则置 inc,向下跌破 m_control_volume_low_real_day_dec 则置 dec。外汇与贵金属保证金交易带高杠杆,这类阈值突破只代表微观流动性异动,价格后续方向仍属概率事件。
开 MT5 把上面变量塞进你的结构体,给 inc/dec 旗标接一个 Print() 跟踪,跑一周 EURUSD 的 M5 数据,能直接看到日内真实成交量峰谷突破的发生频率。
class="type">class="kw">double m_changed_volume_high_real_day_value; class=class="str">"cmt">// Maximum real volume change value of the day class="type">bool m_is_change_volume_high_real_day_inc; class=class="str">"cmt">// Flag of the maximum real volume change for the day exceeding the growth value class="type">bool m_is_change_volume_high_real_day_dec; class=class="str">"cmt">// Flag of the maximum real volume change for the day exceeding the decrease value class=class="str">"cmt">//--- Minimum real volume for the day class="type">class="kw">double m_control_volume_low_real_day_inc; class=class="str">"cmt">// Controlled value of the minimum real volume growth of the day class="type">class="kw">double m_control_volume_low_real_day_dec; class=class="str">"cmt">// Controlled value of the minimum real volume decrease of the day class="type">class="kw">double m_changed_volume_low_real_day_value; class=class="str">"cmt">// Minimum real volume change value of the day class="type">bool m_is_change_volume_low_real_day_inc; class=class="str">"cmt">// Flag of the minimum real volume change for the day exceeding the growth value class="type">bool m_is_change_volume_low_real_day_dec; class=class="str">"cmt">// Flag of the minimum real volume change for the day exceeding the decrease value class=class="str">"cmt">//--- Strike price class="type">class="kw">double m_control_option_strike_inc; class=class="str">"cmt">// Controlled value of the strike price growth class="type">class="kw">double m_control_option_strike_dec; class=class="str">"cmt">// Controlled value of the strike price decrease class="type">class="kw">double m_changed_option_strike_value; class=class="str">"cmt">// Strike price change value class="type">bool m_is_change_option_strike_inc; class=class="str">"cmt">// Flag of the strike price change exceeding the growth value class="type">bool m_is_change_option_strike_dec; class=class="str">"cmt">// Flag of the strike price change exceeding the decrease value class=class="str">"cmt">//--- Total volume of positions and orders class="type">class="kw">double m_changed_volume_limit_value; class=class="str">"cmt">// Minimum total volume change value class="type">bool m_is_change_volume_limit_inc; class=class="str">"cmt">// Flag of the minimum total volume increase class="type">bool m_is_change_volume_limit_dec; class=class="str">"cmt">// Flag of the minimum total volume decrease class=class="str">"cmt">//--- Swap class="type">long class="type">class="kw">double m_changed_swap_long_value; class=class="str">"cmt">// Swap class="type">long change value class="type">bool m_is_change_swap_long_inc; class=class="str">"cmt">// Flag of the swap class="type">long increase class="type">bool m_is_change_swap_long_dec; class=class="str">"cmt">// Flag of the swap class="type">long decrease
「持仓成本与场内活跃度的监控变量」
做日内或会话级(session)统计时,光看价格不够,掉期成本与场内真实成交密度才是判断资金倾向的暗线。下面这组变量把「卖单掉期变动」和「当前会话的成交量、成交额、未平持仓量」都拆成了可控阈值与触发标志。 外汇与贵金属属于高杠杆品种,掉期(swap)在隔夜持仓时可能直接吃掉短线利润,统计短单掉期变化要单独留变量。代码中 m_changed_swap_short_value 记录实际变动值,m_is_change_swap_short_inc / dec 两个布尔量分别标记是否突破你预设的增、减阈值。 当前会话总成交量用 m_control_session_volume_inc / dec 存控制线,m_changed_session_volume_value 是实测偏差,超线就置 m_is_change_session_volume_inc / dec 为真。成交额(turnover)与未平兴趣(interest)同理,各有 inc/dec 双阈值与对应标志位,方便后续逻辑直接读布尔量而不是重新比大小。 在 MT5 自建 EA 时,把这套变量塞进 CStruct 或全局作用域,初始化时从 SymbolInfoDouble 抓实时值填 control 线,就能在 OnTick 里用几个 if 捕获异常放量或掉期跳变。
class=class="str">"cmt">//--- Swap class="type">short class="type">class="kw">double m_changed_swap_short_value; class=class="str">"cmt">// Swap class="type">short change value class="type">bool m_is_change_swap_short_inc; class=class="str">"cmt">// Flag of the swap class="type">short increase class="type">bool m_is_change_swap_short_dec; class=class="str">"cmt">// Flag of the swap class="type">short decrease class=class="str">"cmt">//--- The total volume of deals in the current session class="type">class="kw">double m_control_session_volume_inc; class=class="str">"cmt">// Controlled value of the total trade volume growth in the current session class="type">class="kw">double m_control_session_volume_dec; class=class="str">"cmt">// Controlled value of the total trade volume decrease in the current session class="type">class="kw">double m_changed_session_volume_value; class=class="str">"cmt">// The total deal volume change value in the current session class="type">bool m_is_change_session_volume_inc; class=class="str">"cmt">// Flag of total trade volume change in the current session exceeding the growth value class="type">bool m_is_change_session_volume_dec; class=class="str">"cmt">// Flag of total trade volume change in the current session exceeding the decrease value class=class="str">"cmt">//--- The total turnover in the current session class="type">class="kw">double m_control_session_turnover_inc; class=class="str">"cmt">// Controlled value of the total turnover growth in the current session class="type">class="kw">double m_control_session_turnover_dec; class=class="str">"cmt">// Controlled value of the total turnover decrease in the current session class="type">class="kw">double m_changed_session_turnover_value; class=class="str">"cmt">// Total turnover change value in the current session class="type">bool m_is_change_session_turnover_inc; class=class="str">"cmt">// Flag of total turnover change in the current session exceeding the growth value class="type">bool m_is_change_session_turnover_dec; class=class="str">"cmt">// Flag of total turnover change in the current session exceeding the decrease value class=class="str">"cmt">//--- The total volume of open positions class="type">class="kw">double m_control_session_interest_inc; class=class="str">"cmt">// Controlled value of the total open position volume growth in the current session class="type">class="kw">double m_control_session_interest_dec; class=class="str">"cmt">// Controlled value of the total open position volume decrease in the current session class="type">class="kw">double m_changed_session_interest_value; class=class="str">"cmt">// Change value of the open positions total volume in the current session class="type">bool m_is_change_session_interest_inc; class=class="str">"cmt">// Flag of total open positions&class="macro">#x27; volume change in the current session exceeding the growth value
◍ 盯盘类里的会话维度变量怎么排
写 MT5 盯盘 EA 时,把「当前会话」相关的状态量集中成一组成员变量,能避免每次 tick 都重算。下面这段声明覆盖了持仓兴趣、买卖挂单总量、会话开收盘价三类监控对象,每类都带增长/下降的控制阈值与越界标志。 外汇与贵金属属于高杠杆品种,会话阈值设错可能频繁误触发信号,实盘前务必在策略测试器用历史数据回放验证。 代码逐行拆解: bool m_is_change_session_interest_dec —— 布尔标志,当前会话总持仓量降幅突破预设下降值置真。 //--- The total volume of Buy orders at the moment 注释分隔,以下是买单总量监控 double m_control_session_buy_ord_volume_inc —— 控制的买单总量增长阈值 double m_control_session_buy_ord_volume_dec —— 控制的买单总量下降阈值 double m_changed_session_buy_ord_volume_value —— 当前买单总量实际变化值 bool m_is_change_session_buy_ord_volume_inc —— 买单增量超阈标志 bool m_is_change_session_buy_ord_volume_dec —— 买单减量超阈标志 卖单部分对称:m_control_session_sell_ord_volume_inc/dec 为阈值,m_changed_session_sell_ord_volume_value 为变化值,两个 bool 标志对应超增、超减。 会话开收价各三组:m_control_session_open_inc/dec 与 m_changed_session_open_value 及两个越界 bool;close 同理用 m_control_session_close_inc/dec、m_changed_session_close_value 加两个标志。 把这些变量初始化放在 OnInit,每根会话首根 K 线重置,就能在 OnTick 里直接读标志做条件判断。
class="type">bool m_is_change_session_interest_dec; class=class="str">"cmt">// Flag of total open positions&class="macro">#x27; volume change in the current session exceeding the decrease value class=class="str">"cmt">//--- The total volume of Buy orders at the moment class="type">class="kw">double m_control_session_buy_ord_volume_inc; class=class="str">"cmt">// Controlled value of the current total buy order volume growth class="type">class="kw">double m_control_session_buy_ord_volume_dec; class=class="str">"cmt">// Controlled value of the current total buy order volume decrease class="type">class="kw">double m_changed_session_buy_ord_volume_value; class=class="str">"cmt">// Change value of the current total buy order volume class="type">bool m_is_change_session_buy_ord_volume_inc; class=class="str">"cmt">// Flag of changing the current total buy orders volume exceeding the growth value class="type">bool m_is_change_session_buy_ord_volume_dec; class=class="str">"cmt">// Flag of changing the current total buy orders volume exceeding the decrease value class=class="str">"cmt">//--- The total volume of Sell orders at the moment class="type">class="kw">double m_control_session_sell_ord_volume_inc; class=class="str">"cmt">// Controlled value of the current total sell order volume growth class="type">class="kw">double m_control_session_sell_ord_volume_dec; class=class="str">"cmt">// Controlled value of the current total sell order volume decrease class="type">class="kw">double m_changed_session_sell_ord_volume_value; class=class="str">"cmt">// Change value of the current total sell order volume class="type">bool m_is_change_session_sell_ord_volume_inc; class=class="str">"cmt">// Flag of changing the current total sell orders volume exceeding the growth value class="type">bool m_is_change_session_sell_ord_volume_dec; class=class="str">"cmt">// Flag of changing the current total sell orders volume exceeding the decrease value class=class="str">"cmt">//--- Session open price class="type">class="kw">double m_control_session_open_inc; class=class="str">"cmt">// Controlled value of the session open price growth class="type">class="kw">double m_control_session_open_dec; class=class="str">"cmt">// Controlled value of the session open price decrease class="type">class="kw">double m_changed_session_open_value; class=class="str">"cmt">// Session open price change value class="type">bool m_is_change_session_open_inc; class=class="str">"cmt">// Flag of the session open price change exceeding the growth value class="type">bool m_is_change_session_open_dec; class=class="str">"cmt">// Flag of the session open price change exceeding the decrease value class=class="str">"cmt">//--- Session close price class="type">class="kw">double m_control_session_close_inc; class=class="str">"cmt">// Controlled value of the session close price growth class="type">class="kw">double m_control_session_close_dec; class=class="str">"cmt">// Controlled value of the session close price decrease class="type">class="kw">double m_changed_session_close_value; class=class="str">"cmt">// Session close price change value
会话维度变化参数的初始化逻辑
监控品种状态变化时,除了盘口和成交价,还要单独盯住「会话收尾价」与「会话加权均价」两个序列。源码里用两组 bool 标记分别记录它们的向上/向下变动是否突破了预设阈值:m_is_change_session_close_inc/dec 管收盘价,m_is_change_session_aw_inc/dec 管加权均价。 具体阈值存在 m_control_session_aw_inc / m_control_session_aw_dec 这两个 double 字段里,实际变动幅度则由 m_changed_session_aw_value 承载。这样在后续事件判定时,只需比对「变动值」与「控制值」就能得出突破方向,不用重复计算。 InitChangesParams() 是每轮刷新的入口。函数开头先 m_list_events.Clear() 清空事件容器,再 Sort() 保证触发顺序稳定;随后把 m_is_change_trade_mode 置 false,并把当前会话成交数 m_changed_session_deals_value 归 0,对应增量/减量标记 m_is_change_session_deals_inc/dec 也复位为 false。 在 MT5 里自己写监听 EA 时,可以直接抄这套结构体:把会话级字段和即时字段分开声明,每次 OnTick 前调用类似 InitChangesParams 的复位函数。外汇与贵金属波动受会话切换影响明显,这类标记对捕捉伦敦-纽约交接时段的异动有帮助,但信号本身不代表方向,突破后价格仍可能反向,属高风险博弈。
class="type">bool m_is_change_session_close_inc; class=class="str">"cmt">// Flag of the session close price change exceeding the growth value class="type">bool m_is_change_session_close_dec; class=class="str">"cmt">// Flag of the session close price change exceeding the decrease value class=class="str">"cmt">//--- The average weighted session price class="type">class="kw">double m_control_session_aw_inc; class=class="str">"cmt">// Controlled value of the average weighted session price growth class="type">class="kw">double m_control_session_aw_dec; class=class="str">"cmt">// Controlled value of the average weighted session price decrease class="type">class="kw">double m_changed_session_aw_value; class=class="str">"cmt">// The average weighted session price change value class="type">bool m_is_change_session_aw_inc; class=class="str">"cmt">// Flag of the average weighted session price change value exceeding the growth value class="type">bool m_is_change_session_aw_dec; class=class="str">"cmt">// Flag of the average weighted session price change value exceeding the decrease value class=class="str">"cmt">//--- Initialize the variables of(class="num">1) tracked, (class="num">2) controlled symbol data class="kw">virtual class="type">void InitChangesParams(class="type">void); class="kw">virtual class="type">void InitControlsParams(class="type">void) class=class="str">"cmt">//--- Check symbol changes, class="kw">return a change code class="kw">virtual class="type">int SetEventCode(class="type">void); class=class="str">"cmt">//--- Set an event type and fill in the event list class="kw">virtual class="type">void SetTypeEvent(class="type">void); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Initialize the variables of tracked symbol data | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CSymbol::InitChangesParams(class="type">void) { class=class="str">"cmt">//--- List and code of changes this.m_list_events.Clear(); class=class="str">"cmt">// Clear the change list this.m_list_events.Sort(); class=class="str">"cmt">// Sort the change list class=class="str">"cmt">//--- Execution this.m_is_change_trade_mode=class="kw">false; class=class="str">"cmt">// Flag of changing trading mode for a symbol class=class="str">"cmt">//--- Current session deals this.m_changed_session_deals_value=class="num">0; class=class="str">"cmt">// Value of change in the number of deals this.m_is_change_session_deals_inc=class="kw">false; class=class="str">"cmt">// Flag of a change in the number of deals exceeding the growth value this.m_is_change_session_deals_dec=class="kw">false; class=class="str">"cmt">// Flag of a change in the number of deals exceeding the decrease value
「盯盘结构体里的会话状态清零动作」
在 MT5 的 EA 或指标初始化阶段,把当前会话的买卖挂单变动量先归零,是避免上一根 K 线残留数据污染信号判断的常见写法。下面这段把 Buy/Sell 订单数变化值、增减标志位一口气置 0 或 false,相当于给「本时段触发器」做冷启动。 //--- Buy orders of the current session this.m_changed_session_buy_ord_value=0; // Buy orders change value this.m_is_change_session_buy_ord_inc=false; // Flag of a change in the number of Buy orders exceeding the growth value this.m_is_change_session_buy_ord_dec=false; // Flag of a change in the number of Buy orders exceeding the decrease value //--- Sell orders of the current session this.m_changed_session_sell_ord_value=0; // Sell orders change value this.m_is_change_session_sell_ord_inc=false; // Flag of a change in the number of Sell orders exceeding the growth value this.m_is_change_session_sell_ord_dec=false; // Flag of a change in the number of Sell orders exceeding the decrease value //--- Volume of the last deal this.m_changed_volume_value=0; // Value of the volume change in the last deal this.m_is_change_volume_inc=false; // Flag of the volume change in the last deal exceeding the growth value this.m_is_change_volume_dec=false; // Flag of the volume change in the last deal exceeding the decrease value //--- Maximum volume within a day this.m_changed_volume_high_day_value=0; // Maximum volume change value within a day this.m_is_change_volume_high_day_inc=false; // Flag of the maximum day volume exceeding the growth value this.m_is_change_volume_high_day_dec=false; // Flag of the maximum day volume exceeding the decrease value //--- Minimum volume within a day this.m_changed_volume_low_day_value=0; // Minimum volume change value within a day this.m_is_change_volume_low_day_inc=false; // Flag of the minimum day volume exceeding the growth value this.m_is_change_volume_low_day_dec=false; // Flag of the minimum day volume exceeding the decrease value //--- Spread this.m_changed_spread_value=0; // Spread change value in points this.m_is_change_spread_inc=false; // Flag of spread change in points exceeding the growth value this.m_is_change_spread_dec=false; // Flag of spread change in points exceeding the decrease value //--- StopLevel this.m_changed_stops_level_value=0; // StopLevel change value in points 逐行看:m_changed_session_buy_ord_value=0 把本会话买单变动量清零;m_is_change_session_buy_ord_inc/dec=false 则关掉「买单数增幅/降幅越限」两个布尔开关。卖单部分对称处理。 成交量维度分了三块:最近一笔成交的量变、日内最大量变、日内最小量变,各自带 inc/dec 标志。外汇与贵金属点差和 StopLevel 也单独留了以点为单位的变动值与越限标志——伦敦金在美盘开盘瞬间点差可能从 15 点跳到 40 点以上,这类字段不初始化就可能误触发。 别把布尔开关当摆设 这些 false 不是装饰。若不在 OnInit 或新会话起点重置,旧会话的 m_is_change_spread_inc=true 会一直挂着想触发告警,导致你看到假信号。开 MT5 跑一段日志,确认每个新会话开头这三个字段确实回到 0/false,比事后猜为何错报更省时间。
class=class="str">"cmt">//--- Buy orders of the current session this.m_changed_session_buy_ord_value=class="num">0; class=class="str">"cmt">// Buy orders change value this.m_is_change_session_buy_ord_inc=class="kw">false; class=class="str">"cmt">// Flag of a change in the number of Buy orders exceeding the growth value this.m_is_change_session_buy_ord_dec=class="kw">false; class=class="str">"cmt">// Flag of a change in the number of Buy orders exceeding the decrease value class=class="str">"cmt">//--- Sell orders of the current session this.m_changed_session_sell_ord_value=class="num">0; class=class="str">"cmt">// Sell orders change value this.m_is_change_session_sell_ord_inc=class="kw">false; class=class="str">"cmt">// Flag of a change in the number of Sell orders exceeding the growth value this.m_is_change_session_sell_ord_dec=class="kw">false; class=class="str">"cmt">// Flag of a change in the number of Sell orders exceeding the decrease value class=class="str">"cmt">//--- Volume of the last deal this.m_changed_volume_value=class="num">0; class=class="str">"cmt">// Value of the volume change in the last deal this.m_is_change_volume_inc=class="kw">false; class=class="str">"cmt">// Flag of the volume change in the last deal exceeding the growth value this.m_is_change_volume_dec=class="kw">false; class=class="str">"cmt">// Flag of the volume change in the last deal exceeding the decrease value class=class="str">"cmt">//--- Maximum volume within a day this.m_changed_volume_high_day_value=class="num">0; class=class="str">"cmt">// Maximum volume change value within a day this.m_is_change_volume_high_day_inc=class="kw">false; class=class="str">"cmt">// Flag of the maximum day volume exceeding the growth value this.m_is_change_volume_high_day_dec=class="kw">false; class=class="str">"cmt">// Flag of the maximum day volume exceeding the decrease value class=class="str">"cmt">//--- Minimum volume within a day this.m_changed_volume_low_day_value=class="num">0; class=class="str">"cmt">// Minimum volume change value within a day this.m_is_change_volume_low_day_inc=class="kw">false; class=class="str">"cmt">// Flag of the minimum day volume exceeding the growth value this.m_is_change_volume_low_day_dec=class="kw">false; class=class="str">"cmt">// Flag of the minimum day volume exceeding the decrease value class=class="str">"cmt">//--- Spread this.m_changed_spread_value=class="num">0; class=class="str">"cmt">// Spread change value in points this.m_is_change_spread_inc=class="kw">false; class=class="str">"cmt">// Flag of spread change in points exceeding the growth value this.m_is_change_spread_dec=class="kw">false; class=class="str">"cmt">// Flag of spread change in points exceeding the decrease value class=class="str">"cmt">//--- StopLevel this.m_changed_stops_level_value=class="num">0; class=class="str">"cmt">// StopLevel change value in points
◍ 盯盘类里的价格与关卡变更标记初始化
在 MT5 自定义指标或 EA 的构造函数里,把行情相关的变更状态一次性清零,是后续做增量判断的前提。下面这段初始化覆盖了 StopLevel、FreezeLevel、Bid/Last、日内高低以及 Ask 共 5 类价格字段,每类都拆成「变动点数」「向上超阈标志」「向下超阈标志」三个成员。 以 StopLevel 为例:m_changed_stops_level_value 存实际变动点数,m_is_change_stops_level_inc 与 m_is_change_stops_level_dec 分别记录是否突破你设定的增长/减少阈值。外汇与贵金属点差跳变频繁,这类标志若不清零,上一根 K 线的突破状态会污染当前 tick 的判断,可能给出错误的警报。 Bid/Last 和 Ask 分开处理很关键。Bid 是持仓盯盈亏的基准,Ask 是开仓成本基准;m_changed_bid_last_high_value 与 m_changed_ask_high_value 只记当日极值变动,回测时你能直接用它们过滤「日内波幅是否已透支」的时段。 把下面代码直接贴进你的类构造函数,编译后开 EURUSD 的 M1 图,用 Print() 把 m_changed_bid_last_value 打出来,就能确认每 tick 的初始化是否真的生效。
this.m_is_change_stops_level_inc=class="kw">false; class=class="str">"cmt">// Flag of StopLevel change in points exceeding the growth value this.m_is_change_stops_level_dec=class="kw">false; class=class="str">"cmt">// Flag of StopLevel change in points exceeding the decrease value class=class="str">"cmt">//--- Freeze distance this.m_changed_freeze_level_value=class="num">0; class=class="str">"cmt">// FreezeLevel change value in points this.m_is_change_freeze_level_inc=class="kw">false; class=class="str">"cmt">// Flag of FreezeLevel change in points exceeding the growth value this.m_is_change_freeze_level_dec=class="kw">false; class=class="str">"cmt">// Flag of FreezeLevel change in points exceeding the decrease value class=class="str">"cmt">//--- Bid/Last this.m_changed_bid_last_value=class="num">0; class=class="str">"cmt">// Bid or Last price change value this.m_is_change_bid_last_inc=class="kw">false; class=class="str">"cmt">// Flag of Bid or Last price change exceeding the growth value this.m_is_change_bid_last_dec=class="kw">false; class=class="str">"cmt">// Flag of Bid or Last price change exceeding the decrease value class=class="str">"cmt">//--- Maximum Bid/Last of the day this.m_changed_bid_last_high_value=class="num">0; class=class="str">"cmt">// Maximum Bid or Last change value for the day this.m_is_change_bid_last_high_inc=class="kw">false; class=class="str">"cmt">// Flag of the maximum Bid or Last price change for the day exceeding the growth value this.m_is_change_bid_last_high_dec=class="kw">false; class=class="str">"cmt">// Flag of the maximum Bid or Last price change for the day exceeding the decrease value class=class="str">"cmt">//--- Minimum Bid/Last of the day this.m_changed_bid_last_low_value=class="num">0; class=class="str">"cmt">// Minimum Bid or Last change value for the day this.m_is_change_bid_last_low_inc=class="kw">false; class=class="str">"cmt">// Flag of the minimum Bid or Last price change for the day exceeding the growth value this.m_is_change_bid_last_low_dec=class="kw">false; class=class="str">"cmt">// Flag of the minimum Bid or Last price change for the day exceeding the decrease value class=class="str">"cmt">//--- Ask this.m_changed_ask_value=class="num">0; class=class="str">"cmt">// Ask price change value this.m_is_change_ask_inc=class="kw">false; class=class="str">"cmt">// Flag of the Ask price change exceeding the growth value this.m_is_change_ask_dec=class="kw">false; class=class="str">"cmt">// Flag of the Ask price change exceeding the decrease value class=class="str">"cmt">//--- Maximum Ask price for the day this.m_changed_ask_high_value=class="num">0; class=class="str">"cmt">// Maximum Ask price change value for the day
日内极值与成交量的状态位初始化
在指标或 EA 的构造函数里,需要把当日 Ask 高低点、真实成交量、期权行权价以及限价总持仓量的跟踪变量先清零并复位布尔标志。这些标志后续用于判断某一维度变动是否突破预设的增减阈值,是日内微观结构监测的基础。 以 Ask 最高价为例,m_changed_ask_high_value 存变动值,m_is_change_ask_high_inc / dec 分别标记突破增长值与下降值。若当日最高 Ask 较基准抬升幅度超过设定阈值,inc 置 true,策略可能据此判定买压增强,但外汇与贵金属属高风险品种,此信号仅作概率参考。 真实成交量(Real Volume)分组同理:日总量、日最高、日最低各配一组 value 与 inc/dec 标志。MT5 中 Real Volume 来自经纪商 tick 汇总,不同品种精度差异大,XAUUSD 常见日真实量在数万盎司级,EURUSD 则常超十亿单位,写回测时别把阈值写死。 行权价与限价总仓量(volume limit)也走同一套结构。把下面这段代码直接丢进你的类 Init() 里,编译后开 MT5 用专家日志打印各 flag,就能确认状态机起点是否干净。
this.m_is_change_ask_high_inc=class="kw">false; class=class="str">"cmt">// Flag of the maximum day Ask exceeding the growth value this.m_is_change_ask_high_dec=class="kw">false; class=class="str">"cmt">// Flag of the maximum day Ask exceeding the decrease value class=class="str">"cmt">//--- Minimum Ask price for the day this.m_changed_ask_low_value=class="num">0; class=class="str">"cmt">// Minimum Ask price change value for the day this.m_is_change_ask_low_inc=class="kw">false; class=class="str">"cmt">// Flag of the minimum Ask volume exceeding the growth value this.m_is_change_ask_low_dec=class="kw">false; class=class="str">"cmt">// Flag of the minimum Ask volume exceeding the decrease value class=class="str">"cmt">//--- Real Volume for the day this.m_changed_volume_real_value=class="num">0; class=class="str">"cmt">// Real volume change value of the day this.m_is_change_volume_real_inc=class="kw">false; class=class="str">"cmt">// Flag of the real volume change for the day exceeding the growth value this.m_is_change_volume_real_dec=class="kw">false; class=class="str">"cmt">// Flag of the real volume change for the day exceeding the decrease value class=class="str">"cmt">//--- Maximum real volume for the day this.m_changed_volume_high_real_day_value=class="num">0; class=class="str">"cmt">// Maximum real volume change value of the day this.m_is_change_volume_high_real_day_inc=class="kw">false; class=class="str">"cmt">// Flag of the maximum real volume change for the day exceeding the growth value this.m_is_change_volume_high_real_day_dec=class="kw">false; class=class="str">"cmt">// Flag of the maximum real volume change for the day exceeding the decrease value class=class="str">"cmt">//--- Minimum real volume for the day this.m_changed_volume_low_real_day_value=class="num">0; class=class="str">"cmt">// Minimum real volume change value of the day this.m_is_change_volume_low_real_day_inc=class="kw">false; class=class="str">"cmt">// Flag of the minimum real volume change for the day exceeding the growth value this.m_is_change_volume_low_real_day_dec=class="kw">false; class=class="str">"cmt">// Flag of the minimum real volume change for the day exceeding the decrease value class=class="str">"cmt">//--- Strike price this.m_changed_option_strike_value=class="num">0; class=class="str">"cmt">// Strike price change value this.m_is_change_option_strike_inc=class="kw">false; class=class="str">"cmt">// Flag of the strike price change exceeding the growth value this.m_is_change_option_strike_dec=class="kw">false; class=class="str">"cmt">// Flag of the strike price change exceeding the decrease value class=class="str">"cmt">//--- Total volume of positions and orders this.m_changed_volume_limit_value=class="num">0; class=class="str">"cmt">// Minimum total volume change value this.m_is_change_volume_limit_inc=class="kw">false; class=class="str">"cmt">// Flag of the minimum total volume increase this.m_is_change_volume_limit_dec=class="kw">false; class=class="str">"cmt">// Flag of the minimum total volume decrease
「盯盘指标里的持仓与掉期状态归零」
在 MT5 自定义指标或 EA 的初始化阶段,把多空掉期、当前时段成交量、成交额、未平持仓量以及买卖挂单量这些跟踪变量一次性复位,是避免上一次行情会话的脏数据污染新 K 线的关键动作。 下面这段构造函数里的赋值,把多空 swap 的变动值与增减标志全部置 0 / false:多头掉期变动值 m_changed_swap_long_value 归零,对应上涨、下跌标志 m_is_change_swap_long_inc / dec 均关掉;空头同理。这样开盘第一根 bar 推送时,不会误触发旧阈值。 当前会话维度也照此处理:m_changed_session_volume_value(成交量变动)与 m_is_change_session_volume_inc/dec(增减越限标志)清零;m_changed_session_turnover_value(成交额变动)及对应增减标志同样复位;m_changed_session_interest_value(未平量变动)和增减标志归零。 挂单侧,m_changed_session_buy_ord_volume_value(买挂单量变动)与 m_changed_session_sell_ord_volume_value(卖挂单量变动)都先置 0,后续由 OnBookEvent 或定时器按真实深度重建。外汇与贵金属品种受周末掉期三倍计费影响,这类复位能让你周一早盘看到的 swap 跳变是真实值而非累计残差,但杠杆品种本身高风险,复位逻辑只解决数据干净度,不预示任何方向。
class=class="str">"cmt">//--- Swap class="type">long this.m_changed_swap_long_value=class="num">0; class=class="str">"cmt">// Swap class="type">long change value this.m_is_change_swap_long_inc=class="kw">false; class=class="str">"cmt">// Flag of the swap class="type">long increase this.m_is_change_swap_long_dec=class="kw">false; class=class="str">"cmt">// Flag of the swap class="type">long decrease class=class="str">"cmt">//--- Swap class="type">short this.m_changed_swap_short_value=class="num">0; class=class="str">"cmt">// Swap class="type">short change value this.m_is_change_swap_short_inc=class="kw">false; class=class="str">"cmt">// Flag of the swap class="type">short increase this.m_is_change_swap_short_dec=class="kw">false; class=class="str">"cmt">// Flag of the swap class="type">short decrease class=class="str">"cmt">//--- The total volume of deals in the current session this.m_changed_session_volume_value=class="num">0; class=class="str">"cmt">// The total deal volume change value in the current session this.m_is_change_session_volume_inc=class="kw">false; class=class="str">"cmt">// Flag of total trade volume change in the current session exceeding the growth value this.m_is_change_session_volume_dec=class="kw">false; class=class="str">"cmt">// Flag of total trade volume change in the current session exceeding the decrease value class=class="str">"cmt">//--- The total turnover in the current session this.m_changed_session_turnover_value=class="num">0; class=class="str">"cmt">// Total turnover change value in the current session this.m_is_change_session_turnover_inc=class="kw">false; class=class="str">"cmt">// Flag of total turnover change in the current session exceeding the growth value this.m_is_change_session_turnover_dec=class="kw">false; class=class="str">"cmt">// Flag of total turnover change in the current session exceeding the decrease value class=class="str">"cmt">//--- The total volume of open positions this.m_changed_session_interest_value=class="num">0; class=class="str">"cmt">// Change value of the open positions total volume in the current session this.m_is_change_session_interest_inc=class="kw">false; class=class="str">"cmt">// Flag of total open positions&class="macro">#x27; volume change in the current session exceeding the growth value this.m_is_change_session_interest_dec=class="kw">false; class=class="str">"cmt">// Flag of total open positions&class="macro">#x27; volume change in the current session exceeding the decrease value class=class="str">"cmt">//--- The total volume of Buy orders at the moment this.m_changed_session_buy_ord_volume_value=class="num">0; class=class="str">"cmt">// Change value of the current total buy order volume this.m_is_change_session_buy_ord_volume_inc=class="kw">false; class=class="str">"cmt">// Flag of changing the current total buy orders volume exceeding the growth value this.m_is_change_session_buy_ord_volume_dec=class="kw">false; class=class="str">"cmt">// Flag of changing the current total buy orders volume exceeding the decrease value class=class="str">"cmt">//--- The total volume of Sell orders at the moment this.m_changed_session_sell_ord_volume_value=class="num">0; class=class="str">"cmt">// Change value of the current total sell order volume
◍ 重置会话状态与初始化监控阈值
在 CSymbol 类里,每次重建会话状态都要先把一批内部标记归零。卖单总量增减标记、会话开盘价/收盘价/加权平均价的变动值与增减标记,全部置为 false 或 0,避免上一根 K 线残留的触发状态污染新会话的判断。 紧接着的 InitControlsParams 方法负责给监控阈值打底。当前会话的成交笔数增减控制值、Buy/Sell 挂单增减控制值,全部初始化为 10,意味着当某会话内成交或挂单数量相对基准变动达到 10 笔时,才有可能触发后续的异动逻辑。 外汇与贵金属市场跳空和流动性断裂频繁,这类基于固定阈值的会话监控在重大数据行情中可能瞬间连发信号,实战中建议先在 MT5 策略测试器用历史 tick 跑一遍,确认 10 这一默认值在你的品种上不会过于灵敏。
this.m_is_change_session_sell_ord_volume_inc=class="kw">false; class=class="str">"cmt">// 卖单总成交量增长超阈值的标记:先清零 this.m_is_change_session_sell_ord_volume_dec=class="kw">false; class=class="str">"cmt">// 卖单总成交量减少超阈值的标记:先清零 class=class="str">"cmt">//--- Session open price this.m_changed_session_open_value=class="num">0; class=class="str">"cmt">// 会话开盘价变动值:复位为0 this.m_is_change_session_open_inc=class="kw">false; class=class="str">"cmt">// 开盘价涨幅超阈值标记:清零 this.m_is_change_session_open_dec=class="kw">false; class=class="str">"cmt">// 开盘价跌幅超阈值标记:清零 class=class="str">"cmt">//--- Session close price this.m_changed_session_close_value=class="num">0; class=class="str">"cmt">// 会话收盘价变动值:复位为0 this.m_is_change_session_close_inc=class="kw">false; class=class="str">"cmt">// 收盘价涨幅超阈值标记:清零 this.m_is_change_session_close_dec=class="kw">false; class=class="str">"cmt">// 收盘价跌幅超阈值标记:清零 class=class="str">"cmt">//--- The average weighted session price this.m_changed_session_aw_value=class="num">0; class=class="str">"cmt">// 会话加权平均价变动值:复位为0 this.m_is_change_session_aw_inc=class="kw">false; class=class="str">"cmt">// 加权均价涨幅超阈值标记:清零 this.m_is_change_session_aw_dec=class="kw">false; class=class="str">"cmt">// 加权均价跌幅超阈值标记:清零 } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Initialize the variables of controlled symbol data | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CSymbol::InitControlsParams(class="type">void) { class=class="str">"cmt">//--- Current session deals this.m_control_session_deals_inc=class="num">10; class=class="str">"cmt">// 成交笔数增长的控制阈值:10笔 this.m_control_session_deals_dec=class="num">10; class=class="str">"cmt">// 成交笔数减少的控制阈值:10笔 class=class="str">"cmt">//--- Buy orders of the current session this.m_control_session_buy_ord_inc=class="num">10; class=class="str">"cmt">// 买入挂单增长控制阈值:10笔 this.m_control_session_buy_ord_dec=class="num">10; class=class="str">"cmt">// 买入挂单减少控制阈值:10笔 class=class="str">"cmt">//--- Sell orders of the current session this.m_control_session_sell_ord_inc=class="num">10; class=class="str">"cmt">// 卖出挂单增长控制阈值:10笔 this.m_control_session_sell_ord_dec=class="num">10; class=class="str">"cmt">// 卖出挂单减少控制阈值:10笔 class=class="str">"cmt">//--- Volume of the last deal
交易量及点差控制阈值的初始化设定
在 EA 的初始化阶段,需要给一系列市场状态控制变量赋初值,用来界定「触发报警或干预」的增减边界。下面这段赋值直接决定了策略对成交量突变、点差扩张的敏感度。 单笔成交量的增减控制值设为 10,即上一笔交易后 volume 变动超过 10 才被记为异常波动;日内最大与最小成交量的增减控制值均为 50,框定了当天流动性异常区间。 点差、StopLevel、FreezeLevel 的增减控制统一设为 2 点,意味着这些交易环境参数偏离 2 个点以上才会进入监控逻辑。外汇与贵金属杠杆品种点差跳变频繁,这类阈值偏低时容易频繁误报,实盘前建议在 MT5 策略测试器里按品种特性放宽。 价格类变量(Bid/Last 及日内高低)的增减控制赋为 DBL_MAX,等于暂时关闭价格维度的自动控制,把控制权完全留给后续逻辑或人工。若你想让价格跳空也触发检查,把 DBL_MAX 改成具体点数即可。
this.m_control_volume_inc=class="num">10; class=class="str">"cmt">// Controlled value of the volume growth in the last deal this.m_control_volume_dec=class="num">10; class=class="str">"cmt">// Controlled value of the volume decrease in the last deal class=class="str">"cmt">//--- Maximum volume within a day this.m_control_volume_high_day_inc=class="num">50; class=class="str">"cmt">// Controlled value of the maximum volume growth for a day this.m_control_volume_high_day_dec=class="num">50; class=class="str">"cmt">// Controlled value of the maximum volume decrease for a day class=class="str">"cmt">//--- Minimum volume within a day this.m_control_volume_low_day_inc=class="num">50; class=class="str">"cmt">// Controlled value of the minimum volume growth for a day this.m_control_volume_low_day_dec=class="num">50; class=class="str">"cmt">// Controlled value of the minimum volume decrease for a day class=class="str">"cmt">//--- Spread this.m_control_spread_inc=class="num">2; class=class="str">"cmt">// Controlled spread growth value in points this.m_control_spread_dec=class="num">2; class=class="str">"cmt">// Controlled spread decrease value in points class=class="str">"cmt">//--- StopLevel this.m_control_stops_level_inc=class="num">2; class=class="str">"cmt">// Controlled StopLevel growth value in points this.m_control_stops_level_dec=class="num">2; class=class="str">"cmt">// Controlled StopLevel decrease value in points class=class="str">"cmt">//--- Freeze distance this.m_control_freeze_level_inc=class="num">2; class=class="str">"cmt">// Controlled FreezeLevel growth value in points this.m_control_freeze_level_dec=class="num">2; class=class="str">"cmt">// Controlled FreezeLevel decrease value in points class=class="str">"cmt">//--- Bid/Last this.m_control_bid_last_inc=DBL_MAX; class=class="str">"cmt">// Controlled value of Bid or Last price growth this.m_control_bid_last_dec=DBL_MAX; class=class="str">"cmt">// Controlled value of Bid or Last price decrease class=class="str">"cmt">//--- Maximum Bid/Last of the day this.m_control_bid_last_high_inc=DBL_MAX; class=class="str">"cmt">// Controlled growth value of the maximum Bid or Last price of the day this.m_control_bid_last_high_dec=DBL_MAX; class=class="str">"cmt">// Controlled decrease value of the maximum Bid or Last price of the day class=class="str">"cmt">//--- Minimum Bid/Last of the day this.m_control_bid_last_low_inc=DBL_MAX; class=class="str">"cmt">// Controlled growth value of the minimum Bid or Last price of the day this.m_control_bid_last_low_dec=DBL_MAX; class=class="str">"cmt">// Controlled decrease value of the minimum Bid or Last price of the day class=class="str">"cmt">//--- Ask
「控价与控量参数的初始化门限」
在 EA 的构造函数里,对 Ask 价格相关的控制变量统一赋值为 DBL_MAX,意味着默认不限制买价涨跌及其当日高低点的变动幅度。价格类阈值放开,通常是因为这类标的波动受行情驱动,硬卡数值反而容易漏掉信号。 真实成交量(real volume)与日内极值则给了具体门限:日真实量增减控制为 50,日内最大真实量增减 20,最小真实量增减 10。这些整数代表「允许偏离基准多少手」的触发线,调小会让过滤器更敏感。 期权行权价增减控制为 0,说明该品种或模式下不追踪行权价变动。当前会话的总成交量增减门限是 10,总成交额增 1000、减 500——成交额阈值不对称,反映对缩量比放量更宽容。外汇与贵金属波动剧烈,这类阈值仅作监控触发,不代表方向判断。 把下面这段直接贴进 MT5 的 EA 初始化段,就能看到默认控制变量落点,方便你后续改数验证。
this.m_control_ask_inc=DBL_MAX; class=class="str">"cmt">// Controlled value of the Ask price growth this.m_control_ask_dec=DBL_MAX; class=class="str">"cmt">// Controlled value of the Ask price decrease class=class="str">"cmt">//--- Maximum Ask price for the day this.m_control_ask_high_inc=DBL_MAX; class=class="str">"cmt">// Controlled growth value of the maximum Ask price of the day this.m_control_ask_high_dec=DBL_MAX; class=class="str">"cmt">// Controlled decrease value of the maximum Ask price of the day class=class="str">"cmt">//--- Minimum Ask price for the day this.m_control_ask_low_inc=DBL_MAX; class=class="str">"cmt">// Controlled growth value of the minimum Ask price of the day this.m_control_ask_low_dec=DBL_MAX; class=class="str">"cmt">// Controlled decrease value of the minimum Ask price of the day class=class="str">"cmt">//--- Real Volume for the day this.m_control_volume_real_inc=class="num">50; class=class="str">"cmt">// Controlled value of the real volume growth of the day this.m_control_volume_real_dec=class="num">50; class=class="str">"cmt">// Controlled value of the real volume decrease of the day class=class="str">"cmt">//--- Maximum real volume for the day this.m_control_volume_high_real_day_inc=class="num">20; class=class="str">"cmt">// Controlled value of the maximum real volume growth of the day this.m_control_volume_high_real_day_dec=class="num">20; class=class="str">"cmt">// Controlled value of the maximum real volume decrease of the day class=class="str">"cmt">//--- Minimum real volume for the day this.m_control_volume_low_real_day_inc=class="num">10; class=class="str">"cmt">// Controlled value of the minimum real volume growth of the day this.m_control_volume_low_real_day_dec=class="num">10; class=class="str">"cmt">// Controlled value of the minimum real volume decrease of the day class=class="str">"cmt">//--- Strike price this.m_control_option_strike_inc=class="num">0; class=class="str">"cmt">// Controlled value of the strike price growth this.m_control_option_strike_dec=class="num">0; class=class="str">"cmt">// Controlled value of the strike price decrease class=class="str">"cmt">//--- The total volume of deals in the current session this.m_control_session_volume_inc=class="num">10; class=class="str">"cmt">// Controlled value of the total trade volume growth in the current session this.m_control_session_volume_dec=class="num">10; class=class="str">"cmt">// Controlled value of the total trade volume decrease in the current session class=class="str">"cmt">//--- The total turnover in the current session this.m_control_session_turnover_inc=class="num">1000; class=class="str">"cmt">// Controlled value of the total turnover growth in the current session this.m_control_session_turnover_dec=class="num">500; class=class="str">"cmt">// Controlled value of the total turnover decrease in the current session class=class="str">"cmt">//--- The total volume of open positions
◍ 持仓与挂单增量的阈值设定
在 CSymbol 类的初始化里,把当前会话的持仓总量增减、买卖挂单总量增减都写成了可控阈值。持仓总量增长控在 50、缩减控在 20;Buy 挂单量增长 50、缩减 20;Sell 挂单量增长 50、缩减 20。这些数字直接决定后续事件触发时,什么级别的变动算「显著」。 开盘价、收盘价与加权平均价三组则默认填 0,意味着初始化阶段不预设方向阈值,留待运行时按行情回填。 SetEventCode 方法负责比对前后两帧品种快照,把差异翻译成事件码。trade_mode 不同就叠加 SYMBOL_EVENT_FLAG_TRADE_MODE,session_deals 变化叠加 SESSION_DEALS,买挂单数变叠加 SESSION_BUY_ORDERS,卖挂单数变叠加 SESSION_SELL_ORDERS。 开 MT5 把这几行粘进自己的 CSymbol 派生类,改掉 50 / 20 这组数,就能直观看到小布盯盘里「异动提醒」的灵敏度变化。外汇与贵金属波动剧烈,阈值设太小会被噪音刷屏,设太大可能漏掉真实突破,建议先在历史回放里跑一遍。
this.m_control_session_interest_inc=class="num">50; class=class="str">"cmt">// Controlled value of the total open position volume growth in the current session this.m_control_session_interest_dec=class="num">20; class=class="str">"cmt">// Controlled value of the total open position volume decrease in the current session class=class="str">"cmt">//--- The total volume of Buy orders at the moment this.m_control_session_buy_ord_volume_inc=class="num">50; class=class="str">"cmt">// Controlled value of the current total buy order volume growth this.m_control_session_buy_ord_volume_dec=class="num">20; class=class="str">"cmt">// Controlled value of the current total buy order volume decrease class=class="str">"cmt">//--- The total volume of Sell orders at the moment this.m_control_session_sell_ord_volume_inc=class="num">50; class=class="str">"cmt">// Controlled value of the current total sell order volume growth this.m_control_session_sell_ord_volume_dec=class="num">20; class=class="str">"cmt">// Controlled value of the current total sell order volume decrease class=class="str">"cmt">//--- Session open price this.m_control_session_open_inc=class="num">0; class=class="str">"cmt">// Controlled value of the session open price growth this.m_control_session_open_dec=class="num">0; class=class="str">"cmt">// Controlled value of the session open price decrease class=class="str">"cmt">//--- Session close price this.m_control_session_close_inc=class="num">0; class=class="str">"cmt">// Controlled value of the session close price growth this.m_control_session_close_dec=class="num">0; class=class="str">"cmt">// Controlled value of the session close price decrease class=class="str">"cmt">//--- The average weighted session price this.m_control_session_aw_inc=class="num">0; class=class="str">"cmt">// Controlled value of the average weighted session price growth this.m_control_session_aw_dec=class="num">0; class=class="str">"cmt">// Controlled value of the average weighted session price decrease } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Check symbol changes, class="kw">return a change code | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CSymbol::SetEventCode(class="type">void) { this.m_event_code=SYMBOL_EVENT_FLAG_NO_EVENT; if(this.m_struct_curr_symbol.trade_mode!=this.m_struct_prev_symbol.trade_mode) this.m_event_code+=SYMBOL_EVENT_FLAG_TRADE_MODE; if(this.m_struct_curr_symbol.session_deals!=this.m_struct_prev_symbol.session_deals) this.m_event_code+=SYMBOL_EVENT_FLAG_SESSION_DEALS; if(this.m_struct_curr_symbol.session_buy_orders!=this.m_struct_prev_symbol.session_buy_orders) this.m_event_code+=SYMBOL_EVENT_FLAG_SESSION_BUY_ORDERS; if(this.m_struct_curr_symbol.session_sell_orders!=this.m_struct_prev_symbol.session_sell_orders) this.m_event_code+=SYMBOL_EVENT_FLAG_SESSION_SELL_ORDERS;
用位标志捕_symbol字段的逐帧变化
在 MT5 的自定义品种监控类里,把当前帧与上一帧的 MqlTick/MqlMarketInfo 结构体逐字段比对,是识别「哪些市场属性刚变」的最低开销做法。下面这段逻辑一口气比对了 20 个字段,任一不等就把对应事件位写进 m_event_code,调用方之后只需做一次位与就能知道本次刷新动了什么。 注意 spread、stops_level、freeze_level 这类字段在流动性切换时可能连续几帧跳动,而 bid_last_high / ask_low 只在创出当日极值时才翻转,二者触发频率差可能达 50 倍以上,写告警逻辑时要分开对待。 别把正态当圣经:实盘里 session_volume 与 volume_real_day 在跨session时往往同时变,若你的 EA 对「日量突变」和「时段量突变」分别下单,可能在同一根蜡烛触发两次,需在做单去重里显式屏蔽。 让小布替你跑这套:把下面代码贴进你的 CSymbolWatcher::CheckUpdate() 后,用 Print 出 m_event_code 的十六进制,连看 30 分钟 EURUSD,就能直观看到哪些位在伦敦盘活跃、哪些只在纽约开盘闪一下。外汇与贵金属杠杆高,字段跳动不代表方向,仅作微观结构参考。
if(this.m_struct_curr_symbol.volume!=this.m_struct_prev_symbol.volume) this.m_event_code+=SYMBOL_EVENT_FLAG_VOLUME; if(this.m_struct_curr_symbol.volume_high_day!=this.m_struct_prev_symbol.volume_high_day) this.m_event_code+=SYMBOL_EVENT_FLAG_VOLUME_HIGH_DAY; if(this.m_struct_curr_symbol.volume_low_day!=this.m_struct_prev_symbol.volume_low_day) this.m_event_code+=SYMBOL_EVENT_FLAG_VOLUME_LOW_DAY; if(this.m_struct_curr_symbol.spread!=this.m_struct_prev_symbol.spread) this.m_event_code+=SYMBOL_EVENT_FLAG_SPREAD; if(this.m_struct_curr_symbol.stops_level!=this.m_struct_prev_symbol.stops_level) this.m_event_code+=SYMBOL_EVENT_FLAG_STOPLEVEL; if(this.m_struct_curr_symbol.freeze_level!=this.m_struct_prev_symbol.freeze_level) this.m_event_code+=SYMBOL_EVENT_FLAG_FREEZELEVEL; if(this.m_struct_curr_symbol.bid_last!=this.m_struct_prev_symbol.bid_last) this.m_event_code+=SYMBOL_EVENT_FLAG_BID_LAST; if(this.m_struct_curr_symbol.bid_last_high!=this.m_struct_prev_symbol.bid_last_high) this.m_event_code+=SYMBOL_EVENT_FLAG_BID_LAST_HIGH; if(this.m_struct_curr_symbol.bid_last_low!=this.m_struct_prev_symbol.bid_last_low) this.m_event_code+=SYMBOL_EVENT_FLAG_BID_LAST_LOW; if(this.m_struct_curr_symbol.ask!=this.m_struct_prev_symbol.ask) this.m_event_code+=SYMBOL_EVENT_FLAG_ASK; if(this.m_struct_curr_symbol.ask_high!=this.m_struct_prev_symbol.ask_high) this.m_event_code+=SYMBOL_EVENT_FLAG_ASK_HIGH; if(this.m_struct_curr_symbol.ask_low!=this.m_struct_prev_symbol.ask_low) this.m_event_code+=SYMBOL_EVENT_FLAG_ASK_LOW; if(this.m_struct_curr_symbol.volume_real_day!=this.m_struct_prev_symbol.volume_real_day) this.m_event_code+=SYMBOL_EVENT_FLAG_VOLUME_REAL_DAY; if(this.m_struct_curr_symbol.volume_high_real_day!=this.m_struct_prev_symbol.volume_high_real_day) this.m_event_code+=SYMBOL_EVENT_FLAG_VOLUME_HIGH_REAL_DAY; if(this.m_struct_curr_symbol.volume_low_real_day!=this.m_struct_prev_symbol.volume_low_real_day) this.m_event_code+=SYMBOL_EVENT_FLAG_VOLUME_LOW_REAL_DAY; if(this.m_struct_curr_symbol.option_strike!=this.m_struct_prev_symbol.option_strike) this.m_event_code+=SYMBOL_EVENT_FLAG_OPTION_STRIKE; if(this.m_struct_curr_symbol.volume_limit!=this.m_struct_prev_symbol.volume_limit) this.m_event_code+=SYMBOL_EVENT_FLAG_VOLUME_LIMIT; if(this.m_struct_curr_symbol.swap_long!=this.m_struct_prev_symbol.swap_long) this.m_event_code+=SYMBOL_EVENT_FLAG_SWAP_LONG; if(this.m_struct_curr_symbol.swap_short!=this.m_struct_prev_symbol.swap_short) this.m_event_code+=SYMBOL_EVENT_FLAG_SWAP_SHORT; if(this.m_struct_curr_symbol.session_volume!=this.m_struct_prev_symbol.session_volume) this.m_event_code+=SYMBOL_EVENT_FLAG_SESSION_VOLUME;
「从事件标志到交易状态切换的落地逻辑」
这段 CSymbol 类的片段,负责把前后两帧品种快照的差异翻译成事件码,再映射成具体事件。前面一组 if 比对的是 session 维度的字段:成交额、未平仓兴趣、买卖挂单量、_session_open/close 以及加权平均价(session_aw),任一不等就往 m_event_code 累加对应 SYMBOL_EVENT_FLAG_ 位掩码,最后 return 出去供上层判断。 SetTypeEvent() 里先调 InitChangesParams() 清零控制变量,再按位标志分流。比如命中 TRADE_MODE 标志时,用三元表达式把 TradeMode() 的四种枚举映射成禁用/只多/只空/只平/完全交易的事件 ID,并把 m_is_change_trade_mode 置真;若 EventAdd 成功,才把 prev 的 trade_mode 同步成 curr,避免下一 tick 重复触发。 session_deals 的处理更值得盯:用 curr 减 prev 得到 m_changed_session_deals_value,当增量超过 m_control_session_deals_inc 阈值才标 m_is_change_session_deals_inc 并派发 SYMBOL_EVENT_SESSION_DEALS_INC。外汇与贵金属品种在美盘重叠时段该值可能单 tick 跳变上百笔,阈值设太小会让事件流被刷屏,设太大又漏掉流动性突变,建议在 MT5 策略测试器里用真实点差环境回放 EURUSD 与 XAUUSD 各 3 个月 tick 数据校准。 把下面代码直接贴进你的 CSymbol 派生类,编译后开 MT5 用「品种事件」面板观察 session_aw 与 trade_mode 的触发频率,能直观验证位掩码累加是否如预期。
if(this.m_struct_curr_symbol.session_turnover!=this.m_struct_prev_symbol.session_turnover) this.m_event_code+=SYMBOL_EVENT_FLAG_SESSION_TURNOVER; if(this.m_struct_curr_symbol.session_interest!=this.m_struct_prev_symbol.session_interest) this.m_event_code+=SYMBOL_EVENT_FLAG_SESSION_INTEREST; if(this.m_struct_curr_symbol.session_buy_ord_volume!=this.m_struct_prev_symbol.session_buy_ord_volume) this.m_event_code+=SYMBOL_EVENT_FLAG_SESSION_BUY_ORD_VOLUME; if(this.m_struct_curr_symbol.session_sell_ord_volume!=this.m_struct_prev_symbol.session_sell_ord_volume) this.m_event_code+=SYMBOL_EVENT_FLAG_SESSION_SELL_ORD_VOLUME; if(this.m_struct_curr_symbol.session_open!=this.m_struct_prev_symbol.session_open) this.m_event_code+=SYMBOL_EVENT_FLAG_SESSION_OPEN; if(this.m_struct_curr_symbol.session_close!=this.m_struct_prev_symbol.session_close) this.m_event_code+=SYMBOL_EVENT_FLAG_SESSION_CLOSE; if(this.m_struct_curr_symbol.session_aw!=this.m_struct_prev_symbol.session_aw) this.m_event_code+=SYMBOL_EVENT_FLAG_SESSION_AW; class=class="str">"cmt">//--- class="kw">return this.m_event_code; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Set a symbol object event type | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CSymbol::SetTypeEvent(class="type">void) { this.InitChangesParams(); ENUM_SYMBOL_EVENT event_id=SYMBOL_EVENT_NO_EVENT; class=class="str">"cmt">//--- Change of trading modes on a symbol if(this.IsPresentEventFlag(SYMBOL_EVENT_FLAG_TRADE_MODE)) { event_id= ( this.TradeMode()==SYMBOL_TRADE_MODE_DISABLED ? SYMBOL_EVENT_TRADE_DISABLE : this.TradeMode()==SYMBOL_TRADE_MODE_LONGONLY ? SYMBOL_EVENT_TRADE_LONGONLY : this.TradeMode()==SYMBOL_TRADE_MODE_SHORTONLY ? SYMBOL_EVENT_TRADE_SHORTONLY : this.TradeMode()==SYMBOL_TRADE_MODE_CLOSEONLY ? SYMBOL_EVENT_TRADE_CLOSEONLY : SYMBOL_EVENT_TRADE_FULL ); this.m_is_change_trade_mode=true; if(this.EventAdd(event_id,this.TickTime(),this.TradeMode(),this.Name())) this.m_struct_prev_symbol.trade_mode=this.m_struct_curr_symbol.trade_mode; } class=class="str">"cmt">//--- Change of the number of deals in the current session if(this.IsPresentEventFlag(SYMBOL_EVENT_FLAG_SESSION_DEALS)) { this.m_changed_session_deals_value=this.m_struct_curr_symbol.session_deals-this.m_struct_prev_symbol.session_deals; if(this.m_changed_session_deals_value>this.m_control_session_deals_inc) { this.m_is_change_session_deals_inc=true; event_id=SYMBOL_EVENT_SESSION_DEALS_INC;
◍ 逐档盯住会话挂单的增减跳变
这段逻辑紧接上文对 session_deals 的监测,把视线移到当前会话的买/卖挂单总量上。它先靠 IsPresentEventFlag 判断对应事件标志是否开启,再算当前帧与上一帧的差值 m_changed_session_buy_ord_value / m_changed_session_sell_ord_value,拿去和阈值 m_control_session_buy_ord_inc / _dec 比大小。 差值突破正向阈值就置 m_is_change_session_buy_ord_inc 为真,派发 SYMBOL_EVENT_SESSION_BUY_ORDERS_INC;跌破负向阈值则发 DEC 事件。卖单分支完全同构,只是变量换成 session_sell_orders 与对应宏。 EventAdd 返回成功后才会把 prev 结构刷新成 curr,这意味着若事件写入失败,上一帧数据保留、下一 tick 会重新判定。实盘里把 m_control_session_buy_ord_dec 设成 -5,就能在买挂单瞬时撤单超 5 笔时捕获一次事件——外汇与贵金属杠杆高,这类会话内挂单异动可能预示短期流动性收缩,但仅为概率信号,需结合盘口验证。
if(this.IsPresentEventFlag(SYMBOL_EVENT_FLAG_SESSION_BUY_ORDERS)) { this.m_changed_session_buy_ord_value=this.m_struct_curr_symbol.session_buy_orders-this.m_struct_prev_symbol.session_buy_orders; if(this.m_changed_session_buy_ord_value>this.m_control_session_buy_ord_inc) { this.m_is_change_session_buy_ord_inc=true; event_id=SYMBOL_EVENT_SESSION_BUY_ORDERS_INC; if(this.EventAdd(event_id,this.TickTime(),this.m_changed_session_buy_ord_value,this.Name())) this.m_struct_prev_symbol.session_buy_orders=this.m_struct_curr_symbol.session_buy_orders; } else if(this.m_changed_session_buy_ord_value<-this.m_control_session_buy_ord_dec) { this.m_is_change_session_buy_ord_dec=true; event_id=SYMBOL_EVENT_SESSION_BUY_ORDERS_DEC; if(this.EventAdd(event_id,this.TickTime(),this.m_changed_session_buy_ord_value,this.Name())) this.m_struct_prev_symbol.session_buy_orders=this.m_struct_curr_symbol.session_buy_orders; } } class=class="str">"cmt">//--- Change of the total number of the current sell orders if(this.IsPresentEventFlag(SYMBOL_EVENT_FLAG_SESSION_SELL_ORDERS)) { this.m_changed_session_sell_ord_value=this.m_struct_curr_symbol.session_sell_orders-this.m_struct_prev_symbol.session_sell_orders; if(this.m_changed_session_sell_ord_value>this.m_control_session_sell_ord_inc) { this.m_is_change_session_sell_ord_inc=true; event_id=SYMBOL_EVENT_SESSION_SELL_ORDERS_INC; if(this.EventAdd(event_id,this.TickTime(),this.m_changed_session_sell_ord_value,this.Name())) this.m_struct_prev_symbol.session_sell_orders=this.m_struct_curr_symbol.session_sell_orders; }
卖单衰减与成交量跳变的事件触发逻辑
这段逻辑接在 session 卖单监测之后,专门处理最新成交量和日内最大成交量的突变。代码里用 m_control_ 开头的阈值做比对,只有偏离超过阈值才置位标志并发事件,普通噪音被直接过滤掉。 卖单段里,若本次 session 卖单数减少量大于 m_control_session_sell_ord_dec,就把 m_is_change_session_sell_ord_dec 置真,事件 ID 设为 SYMBOL_EVENT_SESSION_SELL_ORDERS_DEC,随后通过 EventAdd 把tick时间、变化值和对象名塞进事件队列,成功才回写 prev 结构。 成交量部分分两层:一是单笔最新成交量(volume)相对上次的增减,超 m_control_volume_inc / dec 分别触发 INC / DEC 事件;二是日内最高成交量(volume_high_day)的增减,对应 SYMBOL_EVENT_VOLUME_HIGH_DAY_INC / DEC。 在 MT5 里把 m_control_volume_dec 设成 -0.10、m_control_volume_high_day_inc 设成 1.5 这类具体数,回测 EURUSD 的 M1 往往能在流动性骤降时抓到事件,但外汇杠杆品种波动无序,这类信号只提示概率倾向,实盘须自担高风险。
else if(this.m_changed_session_sell_ord_value<-this.m_control_session_sell_ord_dec) { this.m_is_change_session_sell_ord_dec=true; event_id=SYMBOL_EVENT_SESSION_SELL_ORDERS_DEC; if(this.EventAdd(event_id,this.TickTime(),this.m_changed_session_sell_ord_value,this.Name())) this.m_struct_prev_symbol.session_sell_orders=this.m_struct_curr_symbol.session_sell_orders; } } class=class="str">"cmt">//--- Volume change in the last deal if(this.IsPresentEventFlag(SYMBOL_EVENT_FLAG_VOLUME)) { this.m_changed_volume_value=this.m_struct_curr_symbol.volume-this.m_struct_prev_symbol.volume; if(this.m_changed_volume_value>this.m_control_volume_inc) { this.m_is_change_volume_inc=true; event_id=SYMBOL_EVENT_VOLUME_INC; if(this.EventAdd(event_id,this.TickTime(),this.m_changed_volume_value,this.Name())) this.m_struct_prev_symbol.volume=this.m_struct_curr_symbol.volume; } else if(this.m_changed_volume_value<-this.m_control_volume_dec) { this.m_is_change_volume_dec=true; event_id=SYMBOL_EVENT_VOLUME_DEC; if(this.EventAdd(event_id,this.TickTime(),this.m_changed_volume_value,this.Name())) this.m_struct_prev_symbol.volume=this.m_struct_curr_symbol.volume; } } class=class="str">"cmt">//--- Maximum volume change for a day if(this.IsPresentEventFlag(SYMBOL_EVENT_FLAG_VOLUME_HIGH_DAY)) { this.m_changed_volume_high_day_value=this.m_struct_curr_symbol.volume_high_day-this.m_struct_prev_symbol.volume_high_day; if(this.m_changed_volume_high_day_value>this.m_control_volume_high_day_inc) { this.m_is_change_volume_high_day_inc=true; event_id=SYMBOL_EVENT_VOLUME_HIGH_DAY_INC; if(this.EventAdd(event_id,this.TickTime(),this.m_changed_volume_high_day_value,this.Name())) this.m_struct_prev_symbol.volume_high_day=this.m_struct_curr_symbol.volume_high_day; } else if(this.m_changed_volume_high_day_value<-this.m_control_volume_high_day_dec) { this.m_is_change_volume_high_day_dec=true; event_id=SYMBOL_EVENT_VOLUME_HIGH_DAY_DEC; if(this.EventAdd(event_id,this.TickTime(),this.m_changed_volume_high_day_value,this.Name()))
「日量极值与点差跳变的事件触发逻辑」
在自定义品种监视类里,日最低成交量(volume_low_day)和日最高成交量(volume_high_day)被当成独立事件源处理。每次 tick 先算当前值与上一快照的差值:若增量突破 m_control_volume_low_day_inc 阈值就置位 m_is_change_volume_low_day_inc 并派发 SYMBOL_EVENT_VOLUME_LOW_DAY_INC;若减量低于负向阈值 m_control_volume_low_day_dec 则派发 DEC 事件。外汇与贵金属点差受流动性影响大,这类跳变往往出现在数据行情前后,属于高风险时段。 点差(spread)和止损层级(stops_level)走同一套比较框架:用 m_struct_curr_symbol 减 m_struct_prev_symbol 得到变化量,再分别对照 m_control_spread_inc / dec 与 m_control_stops_level_inc / dec。只有越过阈值才写事件队列,EventAdd 成功后把 prev 快照刷新为 curr,避免同一次跳变重复触发。 下面这段代码展示了日低量、点差两段的核心分支,注意 else if 的负阈值比较用了负号前置写法,直接拿差值跟 -control 比,少一次取反运算。复制进 MT5 的 CSymbol 监视派生类,把 control 阈值设成你品种的典型波动点数,就能在策略测试器里抓到真实触发次数。
if(this.IsPresentEventFlag(SYMBOL_EVENT_FLAG_VOLUME_LOW_DAY)) { this.m_changed_volume_low_day_value=this.m_struct_curr_symbol.volume_low_day-this.m_struct_prev_symbol.volume_low_day; if(this.m_changed_volume_low_day_value>this.m_control_volume_low_day_inc) { this.m_is_change_volume_low_day_inc=true; event_id=SYMBOL_EVENT_VOLUME_LOW_DAY_INC; if(this.EventAdd(event_id,this.TickTime(),this.m_changed_volume_low_day_value,this.Name())) this.m_struct_prev_symbol.volume_low_day=this.m_struct_curr_symbol.volume_low_day; } else if(this.m_changed_volume_low_day_value<-this.m_control_volume_low_day_dec) { this.m_is_change_volume_low_day_dec=true; event_id=SYMBOL_EVENT_VOLUME_LOW_DAY_DEC; if(this.EventAdd(event_id,this.TickTime(),this.m_changed_volume_low_day_value,this.Name())) this.m_struct_prev_symbol.volume_low_day=this.m_struct_curr_symbol.volume_low_day; } } class=class="str">"cmt">//--- Spread value change in points if(this.IsPresentEventFlag(SYMBOL_EVENT_FLAG_SPREAD)) { this.m_changed_spread_value=this.m_struct_curr_symbol.spread-this.m_struct_prev_symbol.spread; if(this.m_changed_spread_value>this.m_control_spread_inc) { this.m_is_change_spread_inc=true; event_id=SYMBOL_EVENT_SPREAD_INC; if(this.EventAdd(event_id,this.TickTime(),this.m_changed_spread_value,this.Name())) this.m_struct_prev_symbol.spread=this.m_struct_curr_symbol.spread; } else if(this.m_changed_spread_value<-this.m_control_spread_dec) { this.m_is_change_spread_dec=true; event_id=SYMBOL_EVENT_SPREAD_DEC; if(this.EventAdd(event_id,this.TickTime(),this.m_changed_spread_value,this.Name())) this.m_struct_prev_symbol.spread=this.m_struct_curr_symbol.spread; } }
◍ 止损位与冻结位的点数跳变捕获
这段逻辑盯的是经纪商实时改 stop level 和 freeze level 的动作。当当前品种的 stops_level 减去上笔快照的差值超过 m_control_stops_level_inc 阈值,就置 m_is_change_stops_level_inc 并抛 SYMBOL_EVENT_STOPLEVEL_INC 事件;反向跌破 -m_control_stops_level_dec 则走 DEC 分支,两个分支都靠 EventAdd 把变动值和时间戳塞进事件队列。 冻结位(freeze_level)的处理完全同构:差值算在 points 单位上,越过 m_control_freeze_level_inc / dec 就分别触发 FREEZELEVEL_INC / DEC。外汇与贵金属的 freeze_level 突变往往意味着 broker 在重大数据前收紧挂单距离,属于高风险时段,仓位需主动降杠杆。 Bid/Last 的变动监控只比前两者少一层:m_changed_bid_last_value 超 m_control_bid_last_inc 即记 BID_LAST_INC 事件。EventAdd 返回真才回写 prev 快照,避免同 tick 内重复计事件。 开 MT5 把这几个 m_control_* 阈值设成 extern 输入,就能在策略测试器里观察不同阈值下事件触发密度,验证经纪商在什么波动率下改参数最频繁。
{
this.m_is_change_stops_level_inc=true;
event_id=SYMBOL_EVENT_STOPLEVEL_INC;
if(this.EventAdd(event_id,this.TickTime(),this.m_changed_stops_level_value,this.Name()))
this.m_struct_prev_symbol.stops_level=this.m_struct_curr_symbol.stops_level;
}
else if(this.m_changed_stops_level_value<-this.m_control_stops_level_dec)
{
this.m_is_change_stops_level_dec=true;
event_id=SYMBOL_EVENT_STOPLEVEL_DEC;
if(this.EventAdd(event_id,this.TickTime(),this.m_changed_stops_level_value,this.Name()))
this.m_struct_prev_symbol.stops_level=this.m_struct_curr_symbol.stops_level;
}
}
class=class="str">"cmt">//--- FreezeLevel change in points
if(this.IsPresentEventFlag(SYMBOL_EVENT_FLAG_FREEZELEVEL))
{
this.m_changed_freeze_level_value=this.m_struct_curr_symbol.freeze_level-this.m_struct_prev_symbol.freeze_level;
if(this.m_changed_freeze_level_value>this.m_control_freeze_level_inc)
{
this.m_is_change_freeze_level_inc=true;
event_id=SYMBOL_EVENT_FREEZELEVEL_INC;
if(this.EventAdd(event_id,this.TickTime(),this.m_changed_freeze_level_value,this.Name()))
this.m_struct_prev_symbol.freeze_level=this.m_struct_curr_symbol.freeze_level;
}
else if(this.m_changed_freeze_level_value<-this.m_control_freeze_level_dec)
{
this.m_is_change_freeze_level_dec=true;
event_id=SYMBOL_EVENT_FREEZELEVEL_DEC;
if(this.EventAdd(event_id,this.TickTime(),this.m_changed_freeze_level_value,this.Name()))
this.m_struct_prev_symbol.freeze_level=this.m_struct_curr_symbol.freeze_level;
}
}
class=class="str">"cmt">//--- Bid/Last price change
if(this.IsPresentEventFlag(SYMBOL_EVENT_FLAG_BID_LAST))
{
this.m_changed_bid_last_value=this.m_struct_curr_symbol.bid_last-this.m_struct_prev_symbol.bid_last;
if(this.m_changed_bid_last_value>this.m_control_bid_last_inc)
{
this.m_is_change_bid_last_inc=true;
event_id=SYMBOL_EVENT_BID_LAST_INC;
if(this.EventAdd(event_id,this.TickTime(),this.m_changed_bid_last_value,this.Name()))
this.m_struct_prev_symbol.bid_last=this.m_struct_curr_symbol.bid_last;
}日内买卖盘极值变动的事件触发逻辑
这段逻辑紧接上一节对最新买卖价的监控,继续处理日内最高买价(Bid/Last High)与最低买价(Bid/Last Low)的相对变动。它依赖事件标志位 SYMBOL_EVENT_FLAG_BID_LAST_HIGH 和 SYMBOL_EVENT_FLAG_BID_LAST_LOW 判断是否启用对应监控,外汇与贵金属品种日内波幅可能突然放大,这类极值跳变往往暗示流动性断层或算法单扫单。
计算方式都是用当前结构体值减去上一次保留值:例如 m_changed_bid_last_high_value = m_struct_curr_symbol.bid_last_high - m_struct_prev_symbol.bid_last_high。若增量超过正向阈值 m_control_bid_last_high_inc 或减量小于负向阈值 m_control_bid_last_high_dec,就把对应布尔位设真并派发事件。
事件派发靠 EventAdd(event_id, TickTime(), 变动值, Name()),成功才把 prev 结构体同步成 curr,避免重复触发。你在 MT5 里把 m_control_bid_last_high_dec 设成 -0.00050(EURUSD 点值 5 个点),就能捕捉日内买价低点快速下移的可能异动。
else if(this.m_changed_bid_last_value<-this.m_control_bid_last_dec) { this.m_is_change_bid_last_dec=true; event_id=SYMBOL_EVENT_BID_LAST_DEC; if(this.EventAdd(event_id,this.TickTime(),this.m_changed_bid_last_value,this.Name())) this.m_struct_prev_symbol.bid_last=this.m_struct_curr_symbol.bid_last; } } class=class="str">"cmt">//--- Maximum Bid/Last change for a day if(this.IsPresentEventFlag(SYMBOL_EVENT_FLAG_BID_LAST_HIGH)) { this.m_changed_bid_last_high_value=this.m_struct_curr_symbol.bid_last_high-this.m_struct_prev_symbol.bid_last_high; if(this.m_changed_bid_last_high_value>this.m_control_bid_last_high_inc) { this.m_is_change_bid_last_high_inc=true; event_id=SYMBOL_EVENT_BID_LAST_HIGH_INC; if(this.EventAdd(event_id,this.TickTime(),this.m_changed_bid_last_high_value,this.Name())) this.m_struct_prev_symbol.bid_last_high=this.m_struct_curr_symbol.bid_last_high; } else if(this.m_changed_bid_last_high_value<-this.m_control_bid_last_high_dec) { this.m_is_change_bid_last_high_dec=true; event_id=SYMBOL_EVENT_BID_LAST_HIGH_DEC; if(this.EventAdd(event_id,this.TickTime(),this.m_changed_bid_last_high_value,this.Name())) this.m_struct_prev_symbol.bid_last_high=this.m_struct_curr_symbol.bid_last_high; } } class=class="str">"cmt">//--- Minimum Bid/Last change for a day if(this.IsPresentEventFlag(SYMBOL_EVENT_FLAG_BID_LAST_LOW)) { this.m_changed_bid_last_low_value=this.m_struct_curr_symbol.bid_last_low-this.m_struct_prev_symbol.bid_last_low; if(this.m_changed_bid_last_low_value>this.m_control_bid_last_low_inc) { this.m_is_change_bid_last_low_inc=true; event_id=SYMBOL_EVENT_BID_LAST_LOW_INC; if(this.EventAdd(event_id,this.TickTime(),this.m_changed_bid_last_low_value,this.Name())) this.m_struct_prev_symbol.bid_last_low=this.m_struct_curr_symbol.bid_last_low; } else if(this.m_changed_bid_last_low_value<-this.m_control_bid_last_low_dec) { this.m_is_change_bid_last_low_dec=true; event_id=SYMBOL_EVENT_BID_LAST_LOW_DEC; if(this.EventAdd(event_id,this.TickTime(),this.m_changed_bid_last_low_value,this.Name()))
「Ask 侧事件触发与阈值判定」
卖价(Ask)的变动监控和 Bid 侧同构,但关注的是报价上行 / 下行的突破幅度。代码先判断当前 Tick 是否携带 SYMBOL_EVENT_FLAG_ASK 标志,再计算 m_struct_curr_symbol.ask 与上一帧 ask 的差值 m_changed_ask_value。 当差值大于 m_control_ask_inc 时,标记 m_is_change_ask_inc 为真,事件号置为 SYMBOL_EVENT_ASK_INC;若 EventAdd 入队成功,才把 prev 的 ask 更新为 curr。反之小于 -m_control_ask_dec 则走 SYMBOL_EVENT_ASK_DEC 分支,逻辑对称。 日内最高 Ask(ask_high)与最低 Ask(ask_low)也各有一套阈值:m_control_ask_high_inc / dec 与 m_control_ask_low_inc / dec。只有跨过对应控制值,才会置位 m_is_change_ask_high_inc 等布尔量并发事件。 实盘里这几个 control 值建议按品种点差中位数来设,例如 XAUUSD 的 ask_high 日波动常超 1000 点,control 若只填 50 点会导致事件被全天刷屏。外汇与贵金属杠杆高、滑点跳变频繁,阈值过松或过紧都可能漏掉真实拐点。
if(this.IsPresentEventFlag(SYMBOL_EVENT_FLAG_ASK)) { this.m_changed_ask_value=this.m_struct_curr_symbol.ask-this.m_struct_prev_symbol.ask; if(this.m_changed_ask_value>this.m_control_ask_inc) { this.m_is_change_ask_inc=true; event_id=SYMBOL_EVENT_ASK_INC; if(this.EventAdd(event_id,this.TickTime(),this.m_changed_ask_value,this.Name())) this.m_struct_prev_symbol.ask=this.m_struct_curr_symbol.ask; } else if(this.m_changed_ask_value<-this.m_control_ask_dec) { this.m_is_change_ask_dec=true; event_id=SYMBOL_EVENT_ASK_DEC; if(this.EventAdd(event_id,this.TickTime(),this.m_changed_ask_value,this.Name())) this.m_struct_prev_symbol.ask=this.m_struct_curr_symbol.ask; } } class=class="str">"cmt">//--- Maximum As change for a day if(this.IsPresentEventFlag(SYMBOL_EVENT_FLAG_ASK_HIGH)) { this.m_changed_ask_high_value=this.m_struct_curr_symbol.ask_high-this.m_struct_prev_symbol.ask_high; if(this.m_changed_ask_high_value>this.m_control_ask_high_inc) { this.m_is_change_ask_high_inc=true; event_id=SYMBOL_EVENT_ASK_HIGH_INC; if(this.EventAdd(event_id,this.TickTime(),this.m_changed_ask_high_value,this.Name())) this.m_struct_prev_symbol.ask_high=this.m_struct_curr_symbol.ask_high; } else if(this.m_changed_ask_high_value<-this.m_control_ask_high_dec) { this.m_is_change_ask_high_dec=true; event_id=SYMBOL_EVENT_ASK_HIGH_DEC; if(this.EventAdd(event_id,this.TickTime(),this.m_changed_ask_high_value,this.Name())) this.m_struct_prev_symbol.ask_high=this.m_struct_curr_symbol.ask_high; } } class=class="str">"cmt">//--- Minimum Ask change for a day if(this.IsPresentEventFlag(SYMBOL_EVENT_FLAG_ASK_LOW)) { this.m_changed_ask_low_value=this.m_struct_curr_symbol.ask_low-this.m_struct_prev_symbol.ask_low; if(this.m_changed_ask_low_value>this.m_control_ask_low_inc) { this.m_is_change_ask_low_inc=true;
◍ 真实成交量异动如何触发事件
在 MT5 的自定义品种监控类里,真实成交量(volume_real_day)和日内最大真实成交量(volume_high_real_day)的变动会被单独拎出来判异动。代码先确认事件标志位开启,再算当前结构体与上一Tick结构体的差值,越过阈值就发事件。 以真实成交量为例:m_changed_volume_real_value 是当前日总量减去上一刻日总量。若它大于 m_control_volume_real_inc 则记一次增量事件 SYMBOL_EVENT_VOLUME_REAL_DAY_INC;若小于负的 m_control_volume_real_dec 则记减量事件。EventAdd 成功才把 prev 结构体刷新成 curr,避免重复触发。 日内最大真实成交量走同一套逻辑,只是字段换成 volume_high_real_day 与对应控制阈值。实盘里把 m_control_volume_real_inc 设小一点(如 50 手),就能在黄金跳空时段更早抓到真实流动性涌入,外汇与贵金属波动剧烈,此类信号仅作概率参考,务必自担高风险。
if(this.IsPresentEventFlag(SYMBOL_EVENT_FLAG_VOLUME_REAL_DAY)) { this.m_changed_volume_real_value=this.m_struct_curr_symbol.volume_real_day-this.m_struct_prev_symbol.volume_real_day; if(this.m_changed_volume_real_value>this.m_control_volume_real_inc) { this.m_is_change_volume_real_inc=true; event_id=SYMBOL_EVENT_VOLUME_REAL_DAY_INC; if(this.EventAdd(event_id,this.TickTime(),this.m_changed_volume_real_value,this.Name())) this.m_struct_prev_symbol.volume_real_day=this.m_struct_curr_symbol.volume_real_day; } else if(this.m_changed_volume_real_value<-this.m_control_volume_real_dec) { this.m_is_change_volume_real_dec=true; event_id=SYMBOL_EVENT_VOLUME_REAL_DAY_DEC; if(this.EventAdd(event_id,this.TickTime(),this.m_changed_volume_real_value,this.Name())) this.m_struct_prev_symbol.volume_real_day=this.m_struct_curr_symbol.volume_real_day; } } class=class="str">"cmt">//--- Maximum real volume change for a day if(this.IsPresentEventFlag(SYMBOL_EVENT_FLAG_VOLUME_HIGH_REAL_DAY)) { this.m_changed_volume_high_real_day_value=this.m_struct_curr_symbol.volume_high_real_day-this.m_struct_prev_symbol.volume_high_real_day; if(this.m_changed_volume_high_real_day_value>this.m_control_volume_high_real_day_inc) { this.m_is_change_volume_high_real_day_inc=true; event_id=SYMBOL_EVENT_VOLUME_HIGH_REAL_DAY_INC; if(this.EventAdd(event_id,this.TickTime(),this.m_changed_volume_high_real_day_value,this.Name())) this.m_struct_prev_symbol.volume_high_real_day=this.m_struct_curr_symbol.volume_high_real_day; }
日内真实量与期权行权价的事件触发逻辑
这段逻辑紧接着前面的最高真实量判断,处理的是日内最低真实成交量(volume_low_real_day)的双向越界。先算差值 m_changed_volume_low_real_day_value = 当前值减上一帧值,若高于控制阈值 m_control_volume_low_real_day_inc 就置 inc 标志并派发 SYMBOL_EVENT_VOLUME_LOW_REAL_DAY_INC;低于负向阈值 m_control_volume_low_real_day_dec 则走 dec 分支,派发对应减事件。 事件派发靠 EventAdd(event_id, TickTime(), 变化量, Name()),成功才把 prev 结构里的字段刷新为 curr,避免重复触发。外汇与贵金属市场真实成交量数据延迟或缺失概率不低,这类事件在 XAUUSD 上可能一整天都不触发。 最后一段是期权行权价(option_strike)的监控:同样先取差值,向上突破 m_control_option_strike_inc 发 INC 事件,向下跌破负阈值发 DEC 事件。现货交易者一般不用管这块,但做 XAU 期权对冲时,行权价异动往往领先于隐含波动率跳变。 让小布替你跑这套 把上面三段直接塞进你的 CSymbol 类 CheckEvents() 里,用 EURUSD 的 M1 tick 跑一晚,看 volume_low_real_day 事件在伦敦时段触发几次,就能确认阈值设得是否合理。
else if(this.m_changed_volume_high_real_day_value<-this.m_control_volume_high_real_day_dec) { this.m_is_change_volume_high_real_day_dec=true; event_id=SYMBOL_EVENT_VOLUME_HIGH_REAL_DAY_DEC; if(this.EventAdd(event_id,this.TickTime(),this.m_changed_volume_high_real_day_value,this.Name())) this.m_struct_prev_symbol.volume_high_real_day=this.m_struct_curr_symbol.volume_high_real_day; } } class=class="str">"cmt">//--- Minimum real volume change for a day if(this.IsPresentEventFlag(SYMBOL_EVENT_FLAG_VOLUME_LOW_REAL_DAY)) { this.m_changed_volume_low_real_day_value=this.m_struct_curr_symbol.volume_low_real_day-this.m_struct_prev_symbol.volume_low_real_day; if(this.m_changed_volume_low_real_day_value>this.m_control_volume_low_real_day_inc) { this.m_is_change_volume_low_real_day_inc=true; event_id=SYMBOL_EVENT_VOLUME_LOW_REAL_DAY_INC; if(this.EventAdd(event_id,this.TickTime(),this.m_changed_volume_low_real_day_value,this.Name())) this.m_struct_prev_symbol.volume_low_real_day=this.m_struct_curr_symbol.volume_low_real_day; } else if(this.m_changed_volume_low_real_day_value<-this.m_control_volume_low_real_day_dec) { this.m_is_change_volume_low_real_day_dec=true; event_id=SYMBOL_EVENT_VOLUME_LOW_REAL_DAY_DEC; if(this.EventAdd(event_id,this.TickTime(),this.m_changed_volume_low_real_day_value,this.Name())) this.m_struct_prev_symbol.volume_low_real_day=this.m_struct_curr_symbol.volume_low_real_day; } } class=class="str">"cmt">//--- Strike price change if(this.IsPresentEventFlag(SYMBOL_EVENT_FLAG_OPTION_STRIKE)) { this.m_changed_option_strike_value=this.m_struct_curr_symbol.option_strike-this.m_struct_prev_symbol.option_strike; if(this.m_changed_option_strike_value>this.m_control_option_strike_inc) { this.m_is_change_option_strike_inc=true; event_id=SYMBOL_EVENT_OPTION_STRIKE_INC; if(this.EventAdd(event_id,this.TickTime(),this.m_changed_option_strike_value,this.Name())) this.m_struct_prev_symbol.option_strike=this.m_struct_curr_symbol.option_strike; } else if(this.m_changed_option_strike_value<-this.m_control_option_strike_dec) { this.m_is_change_option_strike_dec=true; event_id=SYMBOL_EVENT_OPTION_STRIKE_DEC;
「品种事件里持仓上限与库存费的增量判定」
这段逻辑紧接期权行权价之后,处理的是经纪商在运行时动态修改的三个参数:单向总持仓与挂单上限 volume_limit、多单库存费 swap_long、空单库存费 swap_short。每一类都先通过 IsPresentEventFlag 确认事件标志位被置起,再算新旧结构体差值。 以 volume_limit 为例,代码用 m_struct_curr_symbol.volume_limit 减去 m_struct_prev_symbol.volume_limit,差值大于 0 就把 m_is_change_volume_limit_inc 置真并派发 SYMBOL_EVENT_VOLUME_LIMIT_INC,否则走递减分支。EventAdd 成功才把 prev 结构体刷新为 curr,保证下一次比对基准正确。 swap_long 与 swap_short 完全同构,只是字段换成 swap_long / swap_short 和对应事件枚举。实战上,黄金 XAUUSD 在美联储议息夜曾出现过 swap_long 从 -3.2 跳到 -9.8 的情况,这类监听能让你在 MT5 策略测试器里复现并记录异动时点。 外汇与贵金属杠杆高、经纪商参数随时可能调,直接抄下面片段到 EA 的 OnTick 跟踪类里,把事件 id 打到日志,就能验证你账户品种的刷新频率。
if(this.IsPresentEventFlag(SYMBOL_EVENT_FLAG_VOLUME_LIMIT)) { this.m_changed_volume_limit_value=this.m_struct_curr_symbol.volume_limit-this.m_struct_prev_symbol.volume_limit; if(this.m_changed_volume_limit_value>class="num">0) { this.m_is_change_volume_limit_inc=true; event_id=SYMBOL_EVENT_VOLUME_LIMIT_INC; } else { this.m_is_change_volume_limit_dec=true; event_id=SYMBOL_EVENT_VOLUME_LIMIT_DEC; } if(this.EventAdd(event_id,this.TickTime(),this.m_changed_volume_limit_value,this.Name())) this.m_struct_prev_symbol.volume_limit=this.m_struct_curr_symbol.volume_limit; } class=class="str">"cmt">//--- Swap class="type">long change if(this.IsPresentEventFlag(SYMBOL_EVENT_FLAG_SWAP_LONG)) { this.m_changed_swap_long_value=this.m_struct_curr_symbol.swap_long-this.m_struct_prev_symbol.swap_long; if(this.m_changed_swap_long_value>class="num">0) { this.m_is_change_swap_long_inc=true; event_id=SYMBOL_EVENT_SWAP_LONG_INC; } else { this.m_is_change_swap_long_dec=true; event_id=SYMBOL_EVENT_SWAP_LONG_DEC; } if(this.EventAdd(event_id,this.TickTime(),this.m_changed_swap_long_value,this.Name())) this.m_struct_prev_symbol.swap_long=this.m_struct_curr_symbol.swap_long; } class=class="str">"cmt">//--- Swap class="type">short change if(this.IsPresentEventFlag(SYMBOL_EVENT_FLAG_SWAP_SHORT)) { this.m_changed_swap_short_value=this.m_struct_curr_symbol.swap_short-this.m_struct_prev_symbol.swap_short; if(this.m_changed_swap_short_value>class="num">0) { this.m_is_change_swap_short_inc=true; event_id=SYMBOL_EVENT_SWAP_SHORT_INC; } else { this.m_is_change_swap_short_dec=true; event_id=SYMBOL_EVENT_SWAP_SHORT_DEC; } if(this.EventAdd(event_id,this.TickTime(),this.m_changed_swap_short_value,this.Name()))
◍ 用成交量与持仓异动触发事件
在 MT5 自定义指标或 EA 里监控品种活动,不能只盯价格。会话内的累计成交量、成交额和未平仓兴趣一旦越过阈值,往往意味着资金在重新布局,外汇与贵金属这类高杠杆品种尤其容易在此时出现短期波动放大。
下面这段逻辑先判断当前品种是否挂了对应的事件标志位,再拿本次 tick 的结构体与上一 tick 的快照做差。若差值向上突破 m_control_session_volume_inc 这类控制阈值,就把增量事件塞进队列,并把旧快照刷新成新值,避免下一 tick 重复触发。
成交额(turnover)和持仓兴趣(interest)的处理完全同构,只是换了结构字段与控制变量。实战中建议把 m_control_session_volume_inc 设成近 20 根分钟 bar 平均成交量的 1.5 倍左右,过滤掉常态噪音;具体倍数开 MT5 用 SymbolInfoInteger 拉历史自己标定。
要注意,事件触发只说明异动发生,不代表方向。外汇和贵金属受消息面影响大,异动后价格可能反向扫损,任何基于成交异动的动作都需配合止损。
this.m_struct_prev_symbol.swap_short=this.m_struct_curr_symbol.swap_short; class=class="str">"cmt">//--- Change of the total volume of deals during the current session if(this.IsPresentEventFlag(SYMBOL_EVENT_FLAG_SESSION_VOLUME)) { this.m_changed_session_volume_value=this.m_struct_curr_symbol.session_volume-this.m_struct_prev_symbol.session_volume; if(this.m_changed_session_volume_value>this.m_control_session_volume_inc) { this.m_is_change_session_volume_inc=true; event_id=SYMBOL_EVENT_SESSION_VOLUME_INC; if(this.EventAdd(event_id,this.TickTime(),this.m_changed_session_volume_value,this.Name())) this.m_struct_prev_symbol.session_volume=this.m_struct_curr_symbol.session_volume; } else if(this.m_changed_session_volume_value<-this.m_control_session_volume_dec) { this.m_is_change_session_volume_dec=true; event_id=SYMBOL_EVENT_SESSION_VOLUME_DEC; if(this.EventAdd(event_id,this.TickTime(),this.m_changed_session_volume_value,this.Name())) this.m_struct_prev_symbol.session_volume=this.m_struct_curr_symbol.session_volume; } } class=class="str">"cmt">//--- Change of the total turnover during the current session if(this.IsPresentEventFlag(SYMBOL_EVENT_FLAG_SESSION_TURNOVER)) { this.m_changed_session_turnover_value=this.m_struct_curr_symbol.session_turnover-this.m_struct_prev_symbol.session_turnover; if(this.m_changed_session_turnover_value>this.m_control_session_turnover_inc) { this.m_is_change_session_turnover_inc=true; event_id=SYMBOL_EVENT_SESSION_TURNOVER_INC; if(this.EventAdd(event_id,this.TickTime(),this.m_changed_session_turnover_value,this.Name())) this.m_struct_prev_symbol.session_turnover=this.m_struct_curr_symbol.session_turnover; } else if(this.m_changed_session_turnover_value<-this.m_control_session_turnover_dec) { this.m_is_change_session_turnover_dec=true; event_id=SYMBOL_EVENT_SESSION_TURNOVER_DEC; if(this.EventAdd(event_id,this.TickTime(),this.m_changed_session_turnover_value,this.Name())) this.m_struct_prev_symbol.session_turnover=this.m_struct_curr_symbol.session_turnover; } } class=class="str">"cmt">//--- Change of the total volume of open positions during the current session if(this.IsPresentEventFlag(SYMBOL_EVENT_FLAG_SESSION_INTEREST)) { this.m_changed_session_interest_value=this.m_struct_curr_symbol.session_interest-this.m_struct_prev_symbol.session_interest;
会话维度持仓与挂单量的事件触发逻辑
这段逻辑盯的是 MT5 symbol 结构里「会话级别」的增量数据,而不是全天累计值。它只在对应的事件标志位被开启时才进入判断,避免每 tick 都无意义地比较。 利息差与会话买卖挂单量共用同一套阈值模式:当前值减前值超过正向控制量(m_control_session_interest_inc 等)就置位上涨标志并抛事件;低于负向控制量则抛下跌事件。EventAdd 成功后才把 prev 结构刷新为 curr,保证下次比较基准正确。 以买挂单体积为例,m_changed_session_buy_ord_volume_value 由 curr.session_buy_ord_volume 减 prev 得到;若大于 m_control_session_buy_ord_volume_inc 则 m_is_change_session_buy_ord_volume_inc=true 且 event_id=SYMBOL_EVENT_SESSION_BUY_ORD_VOLUME_INC。外汇与贵金属这类带会话利息的工具,该机制可能在换会话时产生事件风暴,实盘前应在策略测试器调小控制阈值验证。
if(this.m_changed_session_interest_value>this.m_control_session_interest_inc) { this.m_is_change_session_interest_inc=true; event_id=SYMBOL_EVENT_SESSION_INTEREST_INC; if(this.EventAdd(event_id,this.TickTime(),this.m_changed_session_interest_value,this.Name())) this.m_struct_prev_symbol.session_interest=this.m_struct_curr_symbol.session_interest; } else if(this.m_changed_session_interest_value<-this.m_control_session_interest_dec) { this.m_is_change_session_interest_dec=true; event_id=SYMBOL_EVENT_SESSION_INTEREST_DEC; if(this.EventAdd(event_id,this.TickTime(),this.m_changed_session_interest_value,this.Name())) this.m_struct_prev_symbol.session_interest=this.m_struct_curr_symbol.session_interest; } } class=class="str">"cmt">//--- Change of the current total volume of buy orders if(this.IsPresentEventFlag(SYMBOL_EVENT_FLAG_SESSION_BUY_ORD_VOLUME)) { this.m_changed_session_buy_ord_volume_value=this.m_struct_curr_symbol.session_buy_ord_volume-this.m_struct_prev_symbol.session_buy_ord_volume; if(this.m_changed_session_buy_ord_volume_value>this.m_control_session_buy_ord_volume_inc) { this.m_is_change_session_buy_ord_volume_inc=true; event_id=SYMBOL_EVENT_SESSION_BUY_ORD_VOLUME_INC; if(this.EventAdd(event_id,this.TickTime(),this.m_changed_session_buy_ord_volume_value,this.Name())) this.m_struct_prev_symbol.session_buy_ord_volume=this.m_struct_curr_symbol.session_buy_ord_volume; } else if(this.m_changed_session_buy_ord_volume_value<-this.m_control_session_buy_ord_volume_dec) { this.m_is_change_session_buy_ord_volume_dec=true; event_id=SYMBOL_EVENT_SESSION_BUY_ORD_VOLUME_DEC; if(this.EventAdd(event_id,this.TickTime(),this.m_changed_session_buy_ord_volume_value,this.Name())) this.m_struct_prev_symbol.session_buy_ord_volume=this.m_struct_curr_symbol.session_buy_ord_volume; } } class=class="str">"cmt">//--- Change of the current total volume of sell orders if(this.IsPresentEventFlag(SYMBOL_EVENT_FLAG_SESSION_SELL_ORD_VOLUME)) { this.m_changed_session_sell_ord_volume_value=this.m_struct_curr_symbol.session_sell_ord_volume-this.m_struct_prev_symbol.session_sell_ord_volume; if(this.m_changed_session_sell_ord_volume_value>this.m_control_session_sell_ord_volume_inc) { this.m_is_change_session_sell_ord_volume_inc=true; event_id=SYMBOL_EVENT_SESSION_SELL_ORD_VOLUME_INC;
「盯盘类里如何捕获时段挂单量与开收盘异动」
在 MT5 自建的 symbol 监视类里,时段卖单量(session_sell_ord_volume)的增减需要靠阈值过滤噪声。代码先算当前帧与上一帧的差值 m_changed_session_sell_ord_volume_value,若高于 m_control_session_sell_ord_volume_inc 判定为增量事件,低于负的 m_control_session_sell_ord_volume_dec 则判为减量事件,两者都会调用 EventAdd 并回写 prev 结构。 时段开盘价同理:仅当 IsPresentEventFlag(SESSION_OPEN) 为真才进入判断,差值突破 m_control_session_open_inc / dec 时分别抛出 SESSION_OPEN_INC / DEC。收盘价分支结构完全一致,用 m_changed_session_close_value 比对 m_control_session_close_dec 等控制量。 这类阈值最好按品种波动率设,例如 XAUUSD 的 session_open 控制量设在 0.5 美元以上才报警,能过滤掉亚盘流动性稀薄时的毛刺。外汇与贵金属杠杆高,事件触发只是概率信号,不能直接当作方向依据,开 MT5 把这几段粘进你的 CSymbol 类即可验证。
if(this.EventAdd(event_id,this.TickTime(),this.m_changed_session_sell_ord_volume_value,this.Name())) this.m_struct_prev_symbol.session_sell_ord_volume=this.m_struct_curr_symbol.session_sell_ord_volume; } else if(this.m_changed_session_sell_ord_volume_value<-this.m_control_session_sell_ord_volume_dec) { this.m_is_change_session_sell_ord_volume_dec=true; event_id=SYMBOL_EVENT_SESSION_SELL_ORD_VOLUME_DEC; if(this.EventAdd(event_id,this.TickTime(),this.m_changed_session_sell_ord_volume_value,this.Name())) this.m_struct_prev_symbol.session_sell_ord_volume=this.m_struct_curr_symbol.session_sell_ord_volume; } } class=class="str">"cmt">//--- Session open price change if(this.IsPresentEventFlag(SYMBOL_EVENT_FLAG_SESSION_OPEN)) { this.m_changed_session_open_value=this.m_struct_curr_symbol.session_open-this.m_struct_prev_symbol.session_open; if(this.m_changed_session_open_value>this.m_control_session_open_inc) { this.m_is_change_session_open_inc=true; event_id=SYMBOL_EVENT_SESSION_OPEN_INC; if(this.EventAdd(event_id,this.TickTime(),this.m_changed_session_open_value,this.Name())) this.m_struct_prev_symbol.session_open=this.m_struct_curr_symbol.session_open; } else if(this.m_changed_session_open_value<-this.m_control_session_open_dec) { this.m_is_change_session_open_dec=true; event_id=SYMBOL_EVENT_SESSION_OPEN_DEC; if(this.EventAdd(event_id,this.TickTime(),this.m_changed_session_open_value,this.Name())) this.m_struct_prev_symbol.session_open=this.m_struct_curr_symbol.session_open; } } class=class="str">"cmt">//--- Session close price change if(this.IsPresentEventFlag(SYMBOL_EVENT_FLAG_SESSION_CLOSE)) { this.m_changed_session_close_value=this.m_struct_curr_symbol.session_close-this.m_struct_prev_symbol.session_close; if(this.m_changed_session_close_value>this.m_control_session_close_inc) { this.m_is_change_session_close_inc=true; event_id=SYMBOL_EVENT_SESSION_CLOSE_INC; if(this.EventAdd(event_id,this.TickTime(),this.m_changed_session_close_value,this.Name())) this.m_struct_prev_symbol.session_close=this.m_struct_curr_symbol.session_close; } else if(this.m_changed_session_close_value<-this.m_control_session_close_dec)
◍ 用事件钩子盯住加权均价异动
在 MT5 的自定义品种监控类里,会话收盘事件和加权平均价(session_aw)变动是分开处理的。上面这段逻辑先判断是否存在 SYMBOL_EVENT_FLAG_SESSION_AW 标志,存在才进入加权均价比较分支,避免无谓计算。 具体看,m_changed_session_aw_value 取当前与前一结构体的 session_aw 差值。若该值大于阈值 m_control_session_aw_inc,就把 m_is_change_session_aw_inc 置真,并调用 EventAdd 把 SYMBOL_EVENT_SESSION_AW_INC 事件压入队列,时间戳用 TickTime(),载荷为变动值。事件登记成功后,才把 prev 的 session_aw 更新为 curr,保证下次比较基于已确认状态。 下跌方向对称:差值小于负的 m_control_session_aw_dec 时走 DEC 分支。两个阈值由外部参数控制,实战中可针对 XAUUSD 这种跳空频繁的品种把 inc/dec 阈值放宽到 0.5~1.2 美元,过滤噪音。外汇与贵金属杠杆高,事件触发只代表价格结构偏移,不预示方向,须结合仓位风控。 下方 protected 区暴露了 CSymbol 的整型属性读取接口,如 SymbolExists、SymbolChartMode 等,均带 const 限定,说明这些查询不修改对象状态,可安全在回调里反复调用。
{
this.m_is_change_session_close_dec=true;
event_id=SYMBOL_EVENT_SESSION_CLOSE_DEC;
if(this.EventAdd(event_id,this.TickTime(),this.m_changed_session_close_value,this.Name()))
this.m_struct_prev_symbol.session_close=this.m_struct_curr_symbol.session_close;
}
}
class=class="str">"cmt">//--- Average weighted session price change
if(this.IsPresentEventFlag(SYMBOL_EVENT_FLAG_SESSION_AW))
{
this.m_changed_session_aw_value=this.m_struct_curr_symbol.session_aw-this.m_struct_prev_symbol.session_aw;
if(this.m_changed_session_aw_value>this.m_control_session_aw_inc)
{
this.m_is_change_session_aw_inc=true;
event_id=SYMBOL_EVENT_SESSION_AW_INC;
if(this.EventAdd(event_id,this.TickTime(),this.m_changed_session_aw_value,this.Name()))
this.m_struct_prev_symbol.session_aw=this.m_struct_curr_symbol.session_aw;
}
else if(this.m_changed_session_aw_value<-this.m_control_session_aw_dec)
{
this.m_is_change_session_aw_dec=true;
event_id=SYMBOL_EVENT_SESSION_AW_DEC;
if(this.EventAdd(event_id,this.TickTime(),this.m_changed_session_aw_value,this.Name()))
this.m_struct_prev_symbol.session_aw=this.m_struct_curr_symbol.session_aw;
}
}
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">protected:
class=class="str">"cmt">//--- Protected parametric constructor
CSymbol(ENUM_SYMBOL_STATUS symbol_status,const class="type">class="kw">string name,const class="type">int index);
class=class="str">"cmt">//--- Get and class="kw">return integer properties of a selected symbol from its parameters
class="type">bool SymbolExists(const class="type">class="kw">string name) const;
class="type">long SymbolExists(class="type">void) const;
class="type">long SymbolCustom(class="type">void) const;
class="type">long SymbolChartMode(class="type">void) const;
class="type">long SymbolMarginHedgedUseLEG(class="type">void) const;按掉期算法取小数位的方法
CSymbol 类里有一组返回交易品种属性的 const 方法,覆盖成交流程、订单类型、到期与 GTC 模式、期权属性、背景色、计价与掉期模式等。真正容易在写 EA 时踩坑的是掉期相关的两个:SymbolDigitsLot 与 SymbolDigitsBySwap,前者拿普通报价小数位,后者按掉期计算方式动态决定该看几位小数。 SymbolDigitsBySwap 的逻辑很直接:当 SwapMode 是 POINTS、REOPEN_CURRENT 或 REOPEN_BID 时,返回品种自身的 Digits();若是三种 CURRENCY 类(SYMBOL / MARGIN / DEPOSIT),则返回计价货币的小数位 DigitsCurrency();若是两种 INTEREST 类,固定返回 1;其余情况返回 0。 这意味着同样做隔夜仓,镑美在 POINTS 模式下掉期按 5 位小数显示,切到 CURRENCY_DEPOSIT 后可能只精确到计价币的 2 位。外汇与贵金属杠杆高、隔夜成本波动大,调 EA 前应在 MT5 用 SymbolInfoInteger(Symbol(),SYMBOL_SWAP_MODE) 确认当前品种掉期模式,再决定小数精度,避免利息计算被截断。
class="type">long SymbolOrderFillingMode(class="type">void) const; class="type">long SymbolOrderMode(class="type">void) const; class="type">long SymbolExpirationMode(class="type">void) const; class="type">long SymbolOrderGTCMode(class="type">void) const; class="type">long SymbolOptionMode(class="type">void) const; class="type">long SymbolOptionRight(class="type">void) const; class="type">long SymbolBackgroundColor(class="type">void) const; class="type">long SymbolCalcMode(class="type">void) const; class="type">long SymbolSwapMode(class="type">void) const; class="type">long SymbolDigitsLot(class="type">void); class="type">int SymbolDigitsBySwap(class="type">void); class=class="str">"cmt">//--- Get and class="kw">return real properties of a selected symbol from its parameters class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return the number of decimal places | class=class="str">"cmt">//| depending on the swap calculation method | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CSymbol::SymbolDigitsBySwap(class="type">void) { class="kw">return ( this.SwapMode()==SYMBOL_SWAP_MODE_POINTS || this.SwapMode()==SYMBOL_SWAP_MODE_REOPEN_CURRENT || this.SwapMode()==SYMBOL_SWAP_MODE_REOPEN_BID ? this.Digits() : this.SwapMode()==SYMBOL_SWAP_MODE_CURRENCY_SYMBOL || this.SwapMode()==SYMBOL_SWAP_MODE_CURRENCY_MARGIN || this.SwapMode()==SYMBOL_SWAP_MODE_CURRENCY_DEPOSIT ? this.DigitsCurrency(): this.SwapMode()==SYMBOL_SWAP_MODE_INTEREST_CURRENT || this.SwapMode()==SYMBOL_SWAP_MODE_INTEREST_OPEN ? class="num">1 : class="num">0 ); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- Update all symbol data class="kw">virtual class="type">void Refresh(class="type">void); class=class="str">"cmt">//--- Update quote data by a symbol
「用 CSymbol 封装直接读品种状态」
在 MT5 的自定义类里,把品种对象的常用属性做成内联方法,能少写很多重复调用。下面这段是从一个 CSymbol 类里抽出来的简化访问层,只包了整数和存在性相关的属性。 RefreshRates() 负责强制刷新报价,返回 bool 表示成功与否;EventDescription(ENUM_SYMBOL_EVENT event) 则把品种事件枚举转成可读字符串,方便在日志里排查。 真正的省事点在后面这组 const 方法:Status() 取 SYMBOL_PROP_STATUS,IndexInMarketWatch() 取 SYMBOL_PROP_INDEX_MW 并转 int,IsCustom() 判断是否为自定义品种。ColorBackground() 和 ChartMode() 分别拿背景色与图表模式枚举,IsExist() 有两个重载——无参看当前对象,传 name 字符串则查指定品种是否在终端里。 外汇和贵金属品种在盘后可能 IsExist() 返回 false,属正常;用这些方法前先 RefreshRates() 可能降低读到陈旧属性的概率。
class="type">bool RefreshRates(class="type">void); class=class="str">"cmt">//--- Return description of symbol events class="type">class="kw">string EventDescription(const ENUM_SYMBOL_EVENT event); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Methods of a simplified access to the order object properties | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- Integer properties class="type">long Status(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_STATUS); } class="type">int IndexInMarketWatch(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(SYMBOL_PROP_INDEX_MW); } class="type">bool IsCustom(class="type">void) const { class="kw">return (class="type">bool)this.GetProperty(SYMBOL_PROP_CUSTOM); } class="type">color ColorBackground(class="type">void) const { class="kw">return (class="type">color)this.GetProperty(SYMBOL_PROP_BACKGROUND_COLOR); } ENUM_SYMBOL_CHART_MODE ChartMode(class="type">void) const { class="kw">return (ENUM_SYMBOL_CHART_MODE)this.GetProperty(SYMBOL_PROP_CHART_MODE); } class="type">bool IsExist(class="type">void) const { class="kw">return (class="type">bool)this.GetProperty(SYMBOL_PROP_EXIST); } class="type">bool IsExist(const class="type">class="kw">string name) const { class="kw">return this.SymbolExists(name); }
◍ 从符号属性里抠交易会话细节
封装在符号类里的这几个内联方法,本质是把 MT5 的 SYMBOL_PROP_* 枚举透出来,省得每次手写 GetProperty 还做类型强转。 IsSelect 返回该品种在当前行情窗口是否被用户手动选中,IsVisible 则是判断它有没有在 Market Watch 里显示;这两个布尔值常被用来过滤掉隐藏的交叉盘,避免脚本误扫不活跃品种。 会话维度的几个 long 返回值值得盯:SessionDeals 是当日已成交总笔数,SessionBuyOrders / SessionSellOrders 分别给出买挂单和卖挂单的未平量,这三个数配合 Volume、VolumeHigh、VolumeLow(后者是当日创出的成交量极值)能快速估算盘口热度。外汇与贵金属杠杆高、跳空频繁,用这些属性做品种轮动筛选时,仍须以实盘点差和滑点校准,信号仅代表概率倾向。 下面这段代码就是上述八个方法的原始定义,逐行看:bool IsSelect() const 用 (bool) 强转 SYMBOL_PROP_SELECT 的返回值;bool IsVisible() const 同理取 SYMBOL_PROP_VISIBLE;long SessionDeals() const 直接返回 SYMBOL_PROP_SESSION_DEALS 的 long;SessionBuyOrders / SessionSellOrders 各取对应买/卖挂单属性;Volume 取 SYMBOL_PROP_VOLUME,VolumeHigh / VolumeLow 取当日量能高低极值,均无强转。
class="type">bool IsSelect(class="type">void) const { class="kw">return (class="type">bool)this.GetProperty(SYMBOL_PROP_SELECT); } class="type">bool IsVisible(class="type">void) const { class="kw">return (class="type">bool)this.GetProperty(SYMBOL_PROP_VISIBLE); } class="type">long SessionDeals(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_SESSION_DEALS); } class="type">long SessionBuyOrders(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_SESSION_BUY_ORDERS); } class="type">long SessionSellOrders(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_SESSION_SELL_ORDERS); } class="type">long Volume(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_VOLUME); } class="type">long VolumeHigh(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_VOLUMEHIGH); } class="type">long VolumeLow(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_VOLUMELOW); }
从 CSymbol 基类抠出品种属性接口
在 MT5 的自定义品种封装里,CSymbol 基类把一堆品种属性做成了内联 getter,调用成本极低,适合在 EA 的 OnTick 里高频取数。下面这段是从基类摘出的真实接口声明,没有经过任何改写。 这些函数全部走 GetProperty 统一取属性,返回类型已经做好强转:Time() 给最近报价时间,Digits() 是价格小数位,DigitsLot() 是手数小数位,Spread() 返回当前点差(以点数计)。外汇与贵金属点差在重大数据前可能瞬间拉大,属正常高风险现象。 IsSpreadFloat() 告诉你点差是浮动还是固定;TicksBookdepth() 返回深度行情挂单层数,若券商只给 1 层就别指望做盘口博弈。TradeCalcMode() 和 TradeMode() 返回枚举,决定保证金计算和交易权限,写仓位管理前必须先判。 让小布替你跑这套 把下面代码贴进 MT5 的 CSymbol 派生类,用 Print(Symbol.Digits(), Symbol.Spread()) 在日志里看真实值,比看文档直观。
class="type">class="kw">datetime Time(class="type">void) const { class="kw">return (class="type">class="kw">datetime)this.GetProperty(SYMBOL_PROP_TIME); } class="type">int Digits(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(SYMBOL_PROP_DIGITS); } class="type">int DigitsLot(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(SYMBOL_PROP_DIGITS_LOTS); } class="type">int Spread(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(SYMBOL_PROP_SPREAD); } class="type">bool IsSpreadFloat(class="type">void) const { class="kw">return (class="type">bool)this.GetProperty(SYMBOL_PROP_SPREAD_FLOAT); } class="type">int TicksBookdepth(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(SYMBOL_PROP_TICKS_BOOKDEPTH); } ENUM_SYMBOL_CALC_MODE TradeCalcMode(class="type">void) const { class="kw">return (ENUM_SYMBOL_CALC_MODE)this.GetProperty(SYMBOL_PROP_TRADE_CALC_MODE); } ENUM_SYMBOL_TRADE_MODE TradeMode(class="type">void) const { class="kw">return (ENUM_SYMBOL_TRADE_MODE)this.GetProperty(SYMBOL_PROP_TRADE_MODE); } class="type">class="kw">datetime StartTime(class="type">void) const { class="kw">return (class="type">class="kw">datetime)this.GetProperty(SYMBOL_PROP_START_TIME); }
「读取品种交易限制与到期属性的接口方法」
在封装品种对象的类里,有一组 const 方法专门向外暴露交易所层面的硬性规则。它们全部走 this.GetProperty() 读取符号属性,返回类型依属性而定,调用时不会修改对象内部状态。 ExpirationTime() 返回 datetime 类型的合约到期时间,对贵金属期货类品种有意义;TradeStopLevel() 与 TradeFreezeLevel() 分别给出止损最小距离和订单冻结_level,单位通常是点,实操中若设单距离小于该值会被服务器拒单。 TradeExecutionMode() 返回 ENUM_SYMBOL_TRADE_EXECUTION,用来判断是即时成交还是询价模式;FillingModeFlags() 与 OrderModeFlags() 给出成交和订单类型掩码,开仓前用位运算测一下能避开「不支持市价单」这类报错。 SwapMode() 和 SwapRollover3Days() 告诉你隔夜息计算方式与哪一天收三倍息,做黄金套息仓时得先确认周三还是周五。IsMarginHedgedUseLeg() 表明对冲仓是否按单边腿算保证金,这直接关系仓位杠杆。
class="type">class="kw">datetime ExpirationTime(class="type">void) const { class="kw">return (class="type">class="kw">datetime)this.GetProperty(SYMBOL_PROP_EXPIRATION_TIME); } class="type">int TradeStopLevel(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(SYMBOL_PROP_TRADE_STOPS_LEVEL); } class="type">int TradeFreezeLevel(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(SYMBOL_PROP_TRADE_FREEZE_LEVEL); } ENUM_SYMBOL_TRADE_EXECUTION TradeExecutionMode(class="type">void) const { class="kw">return (ENUM_SYMBOL_TRADE_EXECUTION)this.GetProperty(SYMBOL_PROP_TRADE_EXEMODE); } ENUM_SYMBOL_SWAP_MODE SwapMode(class="type">void) const { class="kw">return (ENUM_SYMBOL_SWAP_MODE)this.GetProperty(SYMBOL_PROP_SWAP_MODE); } ENUM_DAY_OF_WEEK SwapRollover3Days(class="type">void) const { class="kw">return (ENUM_DAY_OF_WEEK)this.GetProperty(SYMBOL_PROP_SWAP_ROLLOVER3DAYS); } class="type">bool IsMarginHedgedUseLeg(class="type">void) const { class="kw">return (class="type">bool)this.GetProperty(SYMBOL_PROP_MARGIN_HEDGED_USE_LEG); } class="type">int ExpirationModeFlags(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(SYMBOL_PROP_EXPIRATION_MODE); } class="type">int FillingModeFlags(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(SYMBOL_PROP_FILLING_MODE); } class="type">int OrderModeFlags(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(SYMBOL_PROP_ORDER_MODE); }
◍ 从品种类里抠出挂单与实时报价属性
在 MT5 的自定义品种封装类里,有一组轻量 getter 专门回传订单执行规则和盘面实时价。它们不发起网络请求,只是把存在 SYMBOL 对象里的属性按类型 cast 出来,调用成本极低。 OrderModeGTC 返回 ENUM_SYMBOL_ORDER_GTC_MODE,告诉你该品种挂单的 GTC(成交前有效)处理方式;OptionMode 与 OptionRight 则只在品种是期权时才有意义,分别给出期权类型和买卖权方向。外汇与贵金属现货这两个值通常返回空枚举,别拿去当信号用。 真正的盘口数据在后面这组:Bid / BidHigh / BidLow 和 Ask / AskHigh / AskLow。它们直接映射 SYMBOL_PROP_BID 等实时属性,其中 High/Low 是「当前交易日」已触到的极值。以 XAUUSD 为例,亚盘若 AskLow 停在 1980.50,欧美盘拉到 Ask 2010.30,说明当日买价区间已扩张近 30 美元,贵金属的高波动风险在这组数里一眼可辨。 把这些 getter 塞进 EA 的 OnTick,你能在不调 SymbolInfoDouble 的前提下,用 this.Bid() 这种写法直接拿价。回测时它们读的是历史 tick 快照,和实盘一致性较高,适合做微观价差统计。
ENUM_SYMBOL_ORDER_GTC_MODE OrderModeGTC(class="type">void) const { class="kw">return (ENUM_SYMBOL_ORDER_GTC_MODE)this.GetProperty(SYMBOL_PROP_ORDER_GTC_MODE); } ENUM_SYMBOL_OPTION_MODE OptionMode(class="type">void) const { class="kw">return (ENUM_SYMBOL_OPTION_MODE)this.GetProperty(SYMBOL_PROP_OPTION_MODE); } ENUM_SYMBOL_OPTION_RIGHT OptionRight(class="type">void) const { class="kw">return (ENUM_SYMBOL_OPTION_RIGHT)this.GetProperty(SYMBOL_PROP_OPTION_RIGHT); } class=class="str">"cmt">//--- Real properties class=class="str">"cmt">//--- Real properties class="type">class="kw">double Bid(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_BID); } class="type">class="kw">double BidHigh(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_BIDHIGH); } class="type">class="kw">double BidLow(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_BIDLOW); } class="type">class="kw">double Ask(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_ASK); } class="type">class="kw">double AskHigh(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_ASKHIGH); } class="type">class="kw">double AskLow(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_ASKLOW); }
从报价对象里抠实时字段
在 MT5 自建的报价封装类里,这一组方法把品种运行时的关键数值直接暴露出来,省得每次都去翻 SymbolInfoDouble。
class="type">class="kw">double Last(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_LAST); } class="type">class="kw">double LastHigh(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_LASTHIGH); } class="type">class="kw">double LastLow(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_LASTLOW); } class="type">class="kw">double VolumeReal(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_VOLUME_REAL); } class="type">class="kw">double VolumeHighReal(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_VOLUMEHIGH_REAL); } class="type">class="kw">double VolumeLowReal(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_VOLUMELOW_REAL); } class="type">class="kw">double OptionStrike(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_OPTION_STRIKE); } class="type">class="kw">double Point(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_POINT); }
sym.Point() 比反复调 SymbolInfoDouble(_Symbol, SYMBOL_POINT) 更短,也方便统一做点差和止损距离计算。外汇与贵金属杠杆高,实时字段跳动快,回测和实盘的数值可能偏差明显,验证时请在 MT5 用脚本打印对照。
class="type">class="kw">double Last(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_LAST); } class="type">class="kw">double LastHigh(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_LASTHIGH); } class="type">class="kw">double LastLow(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_LASTLOW); } class="type">class="kw">double VolumeReal(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_VOLUME_REAL); } class="type">class="kw">double VolumeHighReal(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_VOLUMEHIGH_REAL); } class="type">class="kw">double VolumeLowReal(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_VOLUMELOW_REAL); } class="type">class="kw">double OptionStrike(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_OPTION_STRIKE); } class="type">class="kw">double Point(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_POINT); }
「从品种属性直接取tick与合约参数」
封装交易品种的属性读取,最省事的办法是直接调 GetProperty 拿系统算好的值,而不是自己用价格差去倒推 tick 价值。下面这组方法覆盖了tick金额、tick点位、合约大小、最小手数等八个关键字段,外汇和贵金属品种都通用,但贵金属隔夜利息和流动性参数波动大,读取后最好打日志核对。 以 XAUUSD 为例,TradeContractSize 通常返回 100(每手 100 盎司),TradeTickSize 多为 0.01,TradeTickValue 随报价变化可能在 0.1~1.0 美元之间浮动。LotsMin 在多数券商是 0.01,但个别平台黄金最小开仓是 0.1,不读就硬写常量容易下单失败。 把这些封装成 const 方法后,EA 里随时能取,不用每次 SymbolInfoDouble 查一遍。开 MT5 把代码丢进你的 CSymbol 类里,编译后 Print 一下这几个返回值,就能确认当前品种参数有没有被券商改过。
class="type">class="kw">double TradeTickValue(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_TRADE_TICK_VALUE); } class="type">class="kw">double TradeTickValueProfit(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_TRADE_TICK_VALUE_PROFIT); } class="type">class="kw">double TradeTickValueLoss(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_TRADE_TICK_VALUE_LOSS); } class="type">class="kw">double TradeTickSize(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_TRADE_TICK_SIZE); } class="type">class="kw">double TradeContractSize(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_TRADE_CONTRACT_SIZE); } class="type">class="kw">double TradeAccuredInterest(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_TRADE_ACCRUED_INTEREST); } class="type">class="kw">double TradeFaceValue(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_TRADE_FACE_VALUE); } class="type">class="kw">double TradeLiquidityRate(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_TRADE_LIQUIDITY_RATE); } class="type">class="kw">double LotsMin(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_VOLUME_MIN); }
◍ 品种手数与保证金的取数接口
在 MT5 自建品种封装类里,这一组方法把交易前必须核对的合约参数直接暴露成只读函数,省去每次手敲 SymbolInfoDouble 的麻烦。 下面这段是典型实现,全部走 this.GetProperty 拿预缓存的品种属性:
class="type">class="kw">double LotsMax(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_VOLUME_MAX); } class="type">class="kw">double LotsStep(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_VOLUME_STEP); } class="type">class="kw">double VolumeLimit(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_VOLUME_LIMIT); } class="type">class="kw">double SwapLong(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_SWAP_LONG); } class="type">class="kw">double SwapShort(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_SWAP_SHORT); } class="type">class="kw">double MarginInitial(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_INITIAL); } class="type">class="kw">double MarginMaintenance(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_MAINTENANCE); } class="type">class="kw">double MarginLongInitial(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_LONG_INITIAL); } class="type">class="kw">double MarginBuyStopInitial(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_BUY_STOP_INITIAL); }
class="type">class="kw">double LotsMax(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_VOLUME_MAX); } class="type">class="kw">double LotsStep(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_VOLUME_STEP); } class="type">class="kw">double VolumeLimit(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_VOLUME_LIMIT); } class="type">class="kw">double SwapLong(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_SWAP_LONG); } class="type">class="kw">double SwapShort(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_SWAP_SHORT); } class="type">class="kw">double MarginInitial(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_INITIAL); } class="type">class="kw">double MarginMaintenance(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_MAINTENANCE); } class="type">class="kw">double MarginLongInitial(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_LONG_INITIAL); } class="type">class="kw">double MarginBuyStopInitial(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_BUY_STOP_INITIAL); }
挂单与持仓的保证金属性读取接口
在 MT5 的自定义品种封装里,保证金相关参数被拆成了初始保证金与维持保证金两套,且对市价单、Stop、Limit、StopLimit 各类委托分别暴露了独立读取函数。下面这组方法全部走 this.GetProperty() 取符号属性,返回值为 double 类型,代表当前品种对应委托方向的保证金比例或金额。
以 MarginBuyLimitInitial 为例,它读取 SYMBOL_PROP_MARGIN_BUY_LIMIT_INITIAL,即买入限价挂单的初始保证金要求;而 MarginLongMaintenance 对应的是 SYMBOL_PROP_MARGIN_LONG_MAINTENANCE,即多头持仓的维持保证金。两者数值可能不同——部分经纪商对挂单只收市价单初始保证金的 50%,但维持保证金通常等于或高于初始值。
做跨品种风控时,直接调这些接口比手动硬编码杠杆倍数更稳。外汇与贵金属保证金随杠杆和品种剧烈波动,属高风险参数,实盘前应在策略测试仪里打印各品种返回值核对。
class="type">class="kw">double MarginBuyLimitInitial(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_BUY_LIMIT_INITIAL); } class="type">class="kw">double MarginBuyStopLimitInitial(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_BUY_STOPLIMIT_INITIAL); } class="type">class="kw">double MarginLongMaintenance(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_LONG_MAINTENANCE); } class="type">class="kw">double MarginBuyStopMaintenance(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_BUY_STOP_MAINTENANCE); } class="type">class="kw">double MarginBuyLimitMaintenance(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_BUY_LIMIT_MAINTENANCE); } class="type">class="kw">double MarginBuyStopLimitMaintenance(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_BUY_STOPLIMIT_MAINTENANCE); } class="type">class="kw">double MarginShortInitial(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_SHORT_INITIAL); } class="type">class="kw">double MarginSellStopInitial(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_SELL_STOP_INITIAL); } class="type">class="kw">double MarginSellLimitInitial(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_SELL_LIMIT_INITIAL); } class="type">class="kw">double MarginSellStopLimitInitial(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_SELL_STOPLIMIT_INITIAL); } class="type">class="kw">double MarginShortMaintenance(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_SHORT_MAINTENANCE); }
「挂单维持保证金与时段成交量的属性封装」
在 MT5 的自定义品种类封装里,挂单类型的维持保证金是通过只读方法直接映射到 SYMBOL_PROP_MARGIN_* 系列属性 ID 的。Sell Stop、Sell Limit、Sell Stop Limit 三种未成交空单各自对应一个 Maintenance 方法,返回的是 broker 端对该类挂单占用维持保证金的比例或绝对值。 时段类数据则集中在 Session 前缀的方法组:从 SessionVolume(时段总成交量)到 SessionOpen / SessionClose(时段首笔与末笔成交价),共 9 个方法全部走 GetProperty 取符号属性。这类接口在回测自定义品种时尤其关键——若 SYMBOL_PROP_SESSION_TURNOVER 返回 0,说明该品种当节未注入成交额数据,订单簿重建会失真。 开 MT5 用 MetaEditor 把下面这段塞进你的 CSymbol 派生类,编译后调用 SymbolInfoDouble 对照 SYMBOL_PROP_SESSION_BUY_ORDERS_VOLUME,能直接验证封装是否漏了买挂单量。外汇与贵金属品种属性受经纪商限制,实测返回值可能偏离文档,属正常高风险偏差。
class="type">class="kw">double MarginSellStopMaintenance(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_SELL_STOP_MAINTENANCE); } class="type">class="kw">double MarginSellLimitMaintenance(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_SELL_LIMIT_MAINTENANCE); } class="type">class="kw">double MarginSellStopLimitMaintenance(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_SELL_STOPLIMIT_MAINTENANCE); } class="type">class="kw">double SessionVolume(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_SESSION_VOLUME); } class="type">class="kw">double SessionTurnover(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_SESSION_TURNOVER); } class="type">class="kw">double SessionInterest(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_SESSION_INTEREST); } class="type">class="kw">double SessionBuyOrdersVolume(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_SESSION_BUY_ORDERS_VOLUME); } class="type">class="kw">double SessionSellOrdersVolume(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_SESSION_SELL_ORDERS_VOLUME); } class="type">class="kw">double SessionOpen(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_SESSION_OPEN); } class="type">class="kw">double SessionClose(class="type">void) const { class="kw">return this.GetProperty(SYMBOL_PROP_SESSION_CLOSE); }
◍ 用 BidLast 拿对报价取决于图表模式
CSymbol 类里有一组会话与保证金属性读取函数,都是薄封装:SessionAW 取周中累计成交量,SessionPriceSettlement 取结算价,SessionPriceLimitMin/Max 取会话涨跌限制,MarginHedged 取对冲保证金占用。这些直接走 GetProperty,回测或实盘里想拿对应字段不用自己记枚举。 值得单独拎出来的是 BidLast 这组:BidLast、BidLastHigh、BidLastLow 并非永远返回卖价。MT5 图表存在两种构建模式,SYMBOL_CHART_MODE_BID 下取 BID,否则取 LAST。 下面这段是 BidLast 的实现本体,逻辑就一行三元判断:
class="type">class="kw">double CSymbol::BidLast(class="type">void) const { class="kw">return(this.ChartMode()==SYMBOL_CHART_MODE_BID ? this.GetProperty(SYMBOL_PROP_BID) : this.GetProperty(SYMBOL_PROP_LAST)); }
class="type">class="kw">double CSymbol::BidLast(class="type">void) const { class="kw">return(this.ChartMode()==SYMBOL_CHART_MODE_BID ? this.GetProperty(SYMBOL_PROP_BID) : this.GetProperty(SYMBOL_PROP_LAST)); }
按图表模式取当日高低与成交量异动跟踪
MT5 里图表可走 Bid 价也可走 Last 成交价,取当日极值不能直接写死。下面两个方法按当前 ChartMode 自动切换:Bid 模式读 SYMBOL_PROP_BIDHIGH / BIDLOW,Last 模式读 SYMBOL_PROP_LASTHIGH / LASTLOW,避免拿错序列。 成交量异动靠一组会话内控制量实现。SetControlSessionDealsInc / Dec 用 fabs 收绝对值,分别设定当前会话成交笔数「增长超多少、萎缩超多少」的阈值;GetValueChangedSessionDeals 回传实际变化笔数,配合 IsChangedTradeMode 可判断品种交易模式是否已被动过。 外汇与贵金属杠杆高、跳空频繁,会话内成交突变可能只是流动性真空,别单凭这几个值直接押方向,拿到 MT5 用「EURUSD+H1」跑一遍看 m_changed_session_deals_value 的实际分布更稳妥。
class="type">class="kw">double CSymbol::BidLastHigh(class="type">void) const { class="kw">return(this.ChartMode()==SYMBOL_CHART_MODE_BID ? this.GetProperty(SYMBOL_PROP_BIDHIGH) : this.GetProperty(SYMBOL_PROP_LASTHIGH)); } class="type">class="kw">double CSymbol::BidLastLow(class="type">void) const { class="kw">return(this.ChartMode()==SYMBOL_CHART_MODE_BID ? this.GetProperty(SYMBOL_PROP_BIDLOW) : this.GetProperty(SYMBOL_PROP_LASTLOW)); } class="type">bool IsChangedTradeMode(class="type">void) const { class="kw">return this.m_is_change_trade_mode; } class="type">void SetControlSessionDealsInc(const class="type">long value) { this.m_control_session_deals_inc=::fabs(value); } class="type">void SetControlSessionDealsDec(const class="type">long value) { this.m_control_session_deals_dec=::fabs(value); } class="type">long GetValueChangedSessionDeals(class="type">void) const { class="kw">return this.m_changed_session_deals_value; }
「盯住本时段买卖挂单的增减阈值」
在 MT5 的 EA 类里,可以把「当前交易时段」的订单数量变化单独抽出来监控,而不混在全天总持仓里。上面这组方法针对 Session(时段)维度的 Deal 与 Buy/Sell 挂单,分别给出增长、减少两个方向的布尔开关。 SetControlSessionBuyOrdInc 与 SetControlSessionBuyOrdDec 接收 long 型参数,内部用 ::fabs(value) 取绝对值后写入控制阈值。这意味着你传 -5 和 5 效果一样,控制的是「偏离量」而非方向。 IsIncreaseSessionBuyOrders / IsDecreaseSessionBuyOrders 返回 m_is_change_session_buy_ord_inc / _dec 标志位,它们在每 tick 更新后告诉你:本时段 Buy 挂单数是否突破了你设的增、减临界。GetValueChangedSessionBuyOrders 则直接吐出具体变化量(long),比如从 12 变到 17 就返回 5。 实盘里若发现亚盘时段 Buy 挂单骤增 8 笔以上,往往伴随突破尝试,但外汇与贵金属杠杆高、滑点大,该信号只能作为概率参考,不能直接当作方向确认。
class="type">bool IsIncreaseSessionDeals(class="type">void) const { class="kw">return this.m_is_change_session_deals_inc; } class="type">bool IsDecreaseSessionDeals(class="type">void) const { class="kw">return this.m_is_change_session_deals_dec; } class=class="str">"cmt">//--- Buy orders of the current session class=class="str">"cmt">//--- setting the controlled value of(class="num">1) growth, (class="num">2) decrease in the current number of Buy orders class=class="str">"cmt">//--- getting(class="num">3) the current number of Buy orders change value, class=class="str">"cmt">//--- getting the flag of the current Buy orders&class="macro">#x27; number change exceeding the(class="num">4) growth, (class="num">5) decrease value class="type">void SetControlSessionBuyOrdInc(const class="type">long value) { this.m_control_session_buy_ord_inc=::fabs(value); } class="type">void SetControlSessionBuyOrdDec(const class="type">long value) { this.m_control_session_buy_ord_dec=::fabs(value); } class="type">long GetValueChangedSessionBuyOrders(class="type">void) const { class="kw">return this.m_changed_session_buy_ord_value; } class="type">bool IsIncreaseSessionBuyOrders(class="type">void) const { class="kw">return this.m_is_change_session_buy_ord_inc; } class="type">bool IsDecreaseSessionBuyOrders(class="type">void) const { class="kw">return this.m_is_change_session_buy_ord_dec; } class=class="str">"cmt">//--- Sell orders of the current session class=class="str">"cmt">//--- setting the controlled value of(class="num">1) growth, (class="num">2) decrease in the current number of Sell orders class=class="str">"cmt">//--- getting(class="num">3) the current number of Sell orders change value, class=class="str">"cmt">//--- getting the flag of the current Sell orders&class="macro">#x27; number change exceeding the(class="num">4) growth, (class="num">5) decrease value class="type">void SetControlSessionSellOrdInc(const class="type">long value) { this.m_control_session_sell_ord_inc=::fabs(value); } class="type">void SetControlSessionSellOrdDec(const class="type">long value) { this.m_control_session_sell_ord_dec=::fabs(value); }
◍ 卖单与成交量变动的存取接口
这段类方法集中暴露了会话内卖单变动和单笔成交量的监控接口。GetValueChangedSessionSellOrders 返回 m_changed_session_sell_ord_value,即当前会话卖单数相比基准的净变化量;IsIncreaseSessionSellOrders 与 IsDecreaseSessionSellOrders 分别读取 m_is_change_session_sell_ord_inc 和 m_is_change_session_sell_ord_dec 布尔标志,用于判断卖单是增还是减。 成交量维度由 SetControlVolumeInc 与 SetControlVolumeDec 接管阈值设定,二者都对入参取 fabs 绝对值后写入 m_control_volume_inc / m_control_volume_dec。也就是说,无论你传正还是负,控制器只认幅度不认方向。 GetValueChangedVolume 拿出 m_changed_volume_value 作为最近一笔成交的体量偏移,IsIncreaseVolume / IsDecreaseVolume 则对应 m_is_change_volume_inc / m_is_change_volume_dec 标志位。在 MT5 里接这段逻辑时,建议先打印 m_changed_volume_value 与两个 control 阈值,确认黄金或欧元兑美元品种在高波动时段是否会频繁触发 inc 标志——外汇与贵金属杠杆高,信号误触发可能带来频繁重仓倾向。
class="type">long GetValueChangedSessionSellOrders(class="type">void) const { class="kw">return this.m_changed_session_sell_ord_value; } class="type">bool IsIncreaseSessionSellOrders(class="type">void) const { class="kw">return this.m_is_change_session_sell_ord_inc; } class="type">bool IsDecreaseSessionSellOrders(class="type">void) const { class="kw">return this.m_is_change_session_sell_ord_dec; } class=class="str">"cmt">//--- Volume of the last deal class=class="str">"cmt">//--- setting the last deal volume controlled(class="num">1) growth, (class="num">2) decrease value class=class="str">"cmt">//--- getting(class="num">3) volume change values in the last deal, class=class="str">"cmt">//--- getting the flag of the volume change in the last deal exceeding the(class="num">4) growth, (class="num">5) decrease value class="type">void SetControlVolumeInc(const class="type">long value) { this.m_control_volume_inc=::fabs(value); } class="type">void SetControlVolumeDec(const class="type">long value) { this.m_control_volume_dec=::fabs(value); } class="type">long GetValueChangedVolume(class="type">void) const { class="kw">return this.m_changed_volume_value; } class="type">bool IsIncreaseVolume(class="type">void) const { class="kw">return this.m_is_change_volume_inc; } class="type">bool IsDecreaseVolume(class="type">void) const { class="kw">return this.m_is_change_volume_dec; } class=class="str">"cmt">//--- Maximum volume within a day class=class="str">"cmt">//--- setting the maximum day volume controlled(class="num">1) growth, (class="num">2) decrease value class=class="str">"cmt">//--- getting(class="num">3) the maximum volume change value within a day, class=class="str">"cmt">//--- getting the flag of the maximum day volume change exceeding the(class="num">4) growth, (class="num">5) decrease value
日内高低量阈值的存取封装
这个类片段把「日内最高量 / 最低量」的增减监控做成了成对接口,分别管增量阈值与减量阈值。设置函数统一用 ::fabs(value) 取绝对值写入成员变量,意味着你传负数也会被当正数处理,避免阈值符号错乱。 读取侧分两组:GetValueChangedVolumeHigh / Low 回传实际变化量(long 类型),IsIncrease* / IsDecrease* 回传布尔标志,告诉你当日高低量是否越过了对应阈值的增或减边界。 在 MT5 里若想捕捉「亚盘最低量较昨日后半段萎缩超 200 手」这类事件,直接 SetControlVolumeLowDec(200) 再轮询 IsDecreaseVolumeLow() 即可。外汇与贵金属波动受消息驱动,量价异动仅代表概率倾斜,实盘仍属高风险。
class="type">void SetControlVolumeHighInc(const class="type">long value) { this.m_control_volume_high_day_inc=::fabs(value); } class="type">void SetControlVolumeHighDec(const class="type">long value) { this.m_control_volume_high_day_dec=::fabs(value); } class="type">long GetValueChangedVolumeHigh(class="type">void) const { class="kw">return this.m_changed_volume_high_day_value; } class="type">bool IsIncreaseVolumeHigh(class="type">void) const { class="kw">return this.m_is_change_volume_high_day_inc; } class="type">bool IsDecreaseVolumeHigh(class="type">void) const { class="kw">return this.m_is_change_volume_high_day_dec; } class=class="str">"cmt">//--- Minimum volume within a day class=class="str">"cmt">//--- setting the minimum day volume controlled(class="num">1) growth, (class="num">2) decrease value class=class="str">"cmt">//--- getting(class="num">3) the minimum volume change value within a day, class=class="str">"cmt">//--- getting the flag of the minimum day volume change exceeding the(class="num">4) growth, (class="num">5) decrease value class="type">void SetControlVolumeLowInc(const class="type">long value) { this.m_control_volume_low_day_inc=::fabs(value); } class="type">void SetControlVolumeLowDec(const class="type">long value) { this.m_control_volume_low_day_dec=::fabs(value); } class="type">long GetValueChangedVolumeLow(class="type">void) const { class="kw">return this.m_changed_volume_low_day_value; } class="type">bool IsIncreaseVolumeLow(class="type">void) const { class="kw">return this.m_is_change_volume_low_day_inc; } class="type">bool IsDecreaseVolumeLow(class="type">void) const { class="kw">return this.m_is_change_volume_low_day_dec; }
「点差与止损位的变动阈值控制」
在 MT5 的 EA 或指标类里,点差(Spread)和止损位(StopLevel)的跳动常常比价格本身更先释放流动性信号。把这两个量的「增长 / 收缩」用可控阈值管起来,才能在刷 tick 时过滤掉噪声级的微小变动。 下面这段接口把点差和止损位各自的增减阈值分开设置,统一用 fabs 取绝对值,避免传入负数把控制逻辑搞反。
class=class="str">"cmt">//--- Spread class=class="str">"cmt">//--- setting the controlled spread decrease(class="num">1) growth, (class="num">2) decrease value in points class=class="str">"cmt">//--- getting(class="num">3) spread change value in points, class=class="str">"cmt">//--- getting the flag of the spread change in points exceeding the(class="num">4) growth, (class="num">5) decrease value class="type">void SetControlSpreadInc(const class="type">int value) { this.m_control_spread_inc=::fabs(value); } class="type">void SetControlSpreadDec(const class="type">int value) { this.m_control_spread_dec=::fabs(value); } class="type">int GetValueChangedSpread(class="type">void) const { class="kw">return this.m_changed_spread_value; } class="type">bool IsIncreaseSpread(class="type">void) const { class="kw">return this.m_is_change_spread_inc; } class="type">bool IsDecreaseSpread(class="type">void) const { class="kw">return this.m_is_change_spread_dec; } class=class="str">"cmt">//--- StopLevel class=class="str">"cmt">//--- setting the controlled StopLevel decrease(class="num">1) growth, (class="num">2) decrease value in points class=class="str">"cmt">//--- getting(class="num">3) StopLevel change value in points, class=class="str">"cmt">//--- getting the flag of StopLevel change in points exceeding the(class="num">4) growth, (class="num">5) decrease value class="type">void SetControlStopLevelInc(const class="type">int value) { this.m_control_stops_level_inc=::fabs(value); } class="type">void SetControlStopLevelDec(const class="type">int value) { this.m_control_stops_level_dec=::fabs(value); } class="type">int GetValueChangedStopLevel(class="type">void) const { class="kw">return this.m_changed_stops_level_value; }
class=class="str">"cmt">//--- Spread class=class="str">"cmt">//--- setting the controlled spread decrease(class="num">1) growth, (class="num">2) decrease value in points class=class="str">"cmt">//--- getting(class="num">3) spread change value in points, class=class="str">"cmt">//--- getting the flag of the spread change in points exceeding the(class="num">4) growth, (class="num">5) decrease value class="type">void SetControlSpreadInc(const class="type">int value) { this.m_control_spread_inc=::fabs(value); } class="type">void SetControlSpreadDec(const class="type">int value) { this.m_control_spread_dec=::fabs(value); } class="type">int GetValueChangedSpread(class="type">void) const { class="kw">return this.m_changed_spread_value; } class="type">bool IsIncreaseSpread(class="type">void) const { class="kw">return this.m_is_change_spread_inc; } class="type">bool IsDecreaseSpread(class="type">void) const { class="kw">return this.m_is_change_spread_dec; } class=class="str">"cmt">//--- StopLevel class=class="str">"cmt">//--- setting the controlled StopLevel decrease(class="num">1) growth, (class="num">2) decrease value in points class=class="str">"cmt">//--- getting(class="num">3) StopLevel change value in points, class=class="str">"cmt">//--- getting the flag of StopLevel change in points exceeding the(class="num">4) growth, (class="num">5) decrease value class="type">void SetControlStopLevelInc(const class="type">int value) { this.m_control_stops_level_inc=::fabs(value); } class="type">void SetControlStopLevelDec(const class="type">int value) { this.m_control_stops_level_dec=::fabs(value); } class="type">int GetValueChangedStopLevel(class="type">void) const { class="kw">return this.m_changed_stops_level_value; }
◍ 冻结距离与报价的阈值监控接口
在 EA 里做价格行为监控时, broker 端的 StopLevel 和 FreezeLevel 会在极端流动性下动态变化,直接挂单可能被拒。下面这组类方法就是把「止损位距离」和「冻结距离」的增减做成可读取的布尔标志,方便策略在盘中去判定当前是否进入了禁修改区。 冻结距离部分提供了 SetControlFreezeLevelInc / SetControlFreezeLevelDec 来设定监控的增长、回撤点数阈值,内部用 ::fabs 取绝对值避免负参数。之后通过 IsIncreaseFreezeLevel / IsDecreaseFreezeLevel 拿到布尔量,GetValueChangedFreezeLevel 回传实际变动的点数。 报价侧则用 SetControlBidLastInc / SetControlBidLastDec 以 double 类型设定 Bid 或 Last 价的变化阈值,同样走 fabs 归一。若黄金 XAUUSD 在 MT5 默认报价小数点后三位(即 0.001 = 1 点)环境,设 50 点阈值就意味着 Bid 跳动 0.050 才触发标志,回测时可用这点过滤噪音。 别把 fabs 当多余写法 源码里每个 Set 都套了 ::fabs(value),这是为了防止调用层误传负数把阈值逻辑搞反;你自己接壳时若省略,可能让 IsDecreaseXxx 永远为假,单测时不会报错但实盘挂单会异常。
class="type">bool IsIncreaseStopLevel(class="type">void) const { class="kw">return this.m_is_change_stops_level_inc; } class="type">bool IsDecreaseStopLevel(class="type">void) const { class="kw">return this.m_is_change_stops_level_dec; } class=class="str">"cmt">//--- Freeze distance class=class="str">"cmt">//--- setting the controlled FreezeLevel decrease(class="num">1) growth, (class="num">2) decrease value in points class=class="str">"cmt">//--- getting(class="num">3) FreezeLevel change value in points, class=class="str">"cmt">//--- getting the flag of FreezeLevel change in points exceeding the(class="num">4) growth, (class="num">5) decrease value class="type">void SetControlFreezeLevelInc(const class="type">int value) { this.m_control_freeze_level_inc=::fabs(value); } class="type">void SetControlFreezeLevelDec(const class="type">int value) { this.m_control_freeze_level_dec=::fabs(value); } class="type">int GetValueChangedFreezeLevel(class="type">void) const { class="kw">return this.m_changed_freeze_level_value; } class="type">bool IsIncreaseFreezeLevel(class="type">void) const { class="kw">return this.m_is_change_freeze_level_inc; } class="type">bool IsDecreaseFreezeLevel(class="type">void) const { class="kw">return this.m_is_change_freeze_level_dec; } class=class="str">"cmt">//--- Bid/Last class=class="str">"cmt">//--- setting the Bid or Last price controlled(class="num">1) growth, (class="num">2) decrease value class=class="str">"cmt">//--- getting(class="num">3) Bid or Last price change value, class=class="str">"cmt">//--- getting the flag of the Bid or Last price change exceeding the(class="num">4) growth, (class="num">5) decrease value class="type">void SetControlBidLastInc(const class="type">class="kw">double value) { this.m_control_bid_last_inc=::fabs(value); } class="type">void SetControlBidLastDec(const class="type">class="kw">double value) { this.m_control_bid_last_dec=::fabs(value); }
日内买卖盘极值变动的读取接口
这段类方法专门处理 Bid 或 Last 价在日内极值附近的变动捕获。它把「最新一次变动」和「触及日内最高/最低时的变动」拆成两套接口,方便策略层分别判断微观脉冲与边界突破。 GetValueChangedBidLast 返回上一次 Bid/Last 变动的绝对数值,IsIncreaseBidLast 与 IsDecreaseBidLast 则给出方向标志位。注意这些都是 const 方法,不会改写对象内部状态,适合在 OnTick 里高频轮询。 SetControlBidLastHighInc 和 SetControlBidLastHighDec 用 ::fabs(value) 强制把阈值转成正值,意味着你传 -5.0 和 5.0 效果一样,边界控制只认幅度不认符号。 GetValueChangedBidLastHigh 拿的是日内最高价那一侧的变动量,配合 IsIncreaseBidLastHigh / IsDecreaseBidLastHigh 就能知道价格撞到当日上轨时是放量刺穿还是缩量回落。外汇与贵金属波动受杠杆放大,这类边界误判可能触发连续止损,实盘前务必在 MT5 策略测试器用历史数据跑一遍阈值灵敏度。 最低价一侧的接口注释已预留同样结构,只是对象成员换成 m_control_bid_last_low_* 系列,复制这套写法即可补齐下轨监控。
class="type">class="kw">double GetValueChangedBidLast(class="type">void) const { class="kw">return this.m_changed_bid_last_value; } class="type">bool IsIncreaseBidLast(class="type">void) const { class="kw">return this.m_is_change_bid_last_inc; } class="type">bool IsDecreaseBidLast(class="type">void) const { class="kw">return this.m_is_change_bid_last_dec; } class=class="str">"cmt">//--- Maximum Bid/Last of the day class=class="str">"cmt">//--- setting the maximum Bid or Last price controlled(class="num">1) growth, (class="num">2) decrease value class=class="str">"cmt">//--- getting the(class="num">3) maximum Bid or Last price change value, class=class="str">"cmt">//--- getting the flag of the maximum Bid or Last price change exceeding the(class="num">4) growth, (class="num">5) decrease value class="type">void SetControlBidLastHighInc(const class="type">class="kw">double value) { this.m_control_bid_last_high_inc=::fabs(value); } class="type">void SetControlBidLastHighDec(const class="type">class="kw">double value) { this.m_control_bid_last_high_dec=::fabs(value); } class="type">class="kw">double GetValueChangedBidLastHigh(class="type">void) const { class="kw">return this.m_changed_bid_last_high_value; } class="type">bool IsIncreaseBidLastHigh(class="type">void) const { class="kw">return this.m_is_change_bid_last_high_inc; } class="type">bool IsDecreaseBidLastHigh(class="type">void) const { class="kw">return this.m_is_change_bid_last_high_dec; } class=class="str">"cmt">//--- Minimum Bid/Last of the day class=class="str">"cmt">//--- setting the minimum Bid or Last price controlled(class="num">1) growth, (class="num">2) decrease value class=class="str">"cmt">//--- getting the(class="num">3) minimum Bid or Last price change value, class=class="str">"cmt">//--- getting the flag of the minimum Bid or Last price change exceeding the(class="num">4) growth, (class="num">5) decrease value
「买卖盘极值变动的阈值封装」
在盯盘逻辑里,Bid 与 Ask 的上一档极值(last low)变动需要被量化管控,否则噪声会淹没真实流向。下面这组方法把「控制阈值写入」和「变动状态读取」拆开,Bid 侧关注 last low 的抬升与下移,Ask 侧关注报价本身的扩张与收缩。 设置函数统一用 ::fabs(value) 取绝对值,意味着你传正负数都行,内部只认幅度不认方向。例如 SetControlBidLastLowInc(-0.0002) 与传 0.0002 效果一致,实际控制阈值都是 2 个点。 读取端分两层:GetValueChangedXxx 回传实际变动数值,IsIncrease/IsDecreaseXxx 回传布尔标志。实战中可先判布尔确认「是否突破阈值」,再取数值算突破深度,避免在 MT5 里对未触发的微小跳动做无效处理。外汇与贵金属杠杆高,盘口跳动可能瞬间反转,阈值需结合品种点值压力测试。
class="type">void SetControlBidLastLowInc(const class="type">class="kw">double value) { this.m_control_bid_last_low_inc=::fabs(value); } class="type">void SetControlBidLastLowDec(const class="type">class="kw">double value) { this.m_control_bid_last_low_dec=::fabs(value); } class="type">class="kw">double GetValueChangedBidLastLow(class="type">void) const { class="kw">return this.m_changed_bid_last_low_value; } class="type">bool IsIncreaseBidLastLow(class="type">void) const { class="kw">return this.m_is_change_bid_last_low_inc; } class="type">bool IsDecreaseBidLastLow(class="type">void) const { class="kw">return this.m_is_change_bid_last_low_dec; } class=class="str">"cmt">//--- Ask class=class="str">"cmt">//--- setting the Ask price controlled(class="num">1) growth, (class="num">2) decrease value class=class="str">"cmt">//--- getting(class="num">3) Ask price change value, class=class="str">"cmt">//--- getting the flag of the Ask price change exceeding the(class="num">4) growth, (class="num">5) decrease value class="type">void SetControlAskInc(const class="type">class="kw">double value) { this.m_control_ask_inc=::fabs(value); } class="type">void SetControlAskDec(const class="type">class="kw">double value) { this.m_control_ask_dec=::fabs(value); } class="type">class="kw">double GetValueChangedAsk(class="type">void) const { class="kw">return this.m_changed_ask_value; } class="type">bool IsIncreaseAsk(class="type">void) const { class="kw">return this.m_is_change_ask_inc; }
◍ 日内 Ask 极值变动的控值与读取接口
做日内价格行为监控时,光看当前 Ask 不够,还得盯住当天 Ask 的最高、最低位置相对基准的偏移幅度。下面这组方法把「最高 Ask 的受控增减阈值」和「最低 Ask 的受控增减阈值」分开设置,方便后续判断突破是属于噪音还是真实异动。 设置函数都用了 ::fabs(value) 取绝对值,意味着你传进去的阈值正负无所谓,内部统一按非负控值处理。例如 SetControlAskHighInc(0.0005) 与 SetControlAskHighInc(-0.0005) 效果相同,都是把当日最高 Ask 相对基准上涨的监控线锁在 5 个点(以 EURUSD 小数点后四位计)。 读取侧提供了 GetValueChangedAskHigh() 拿实际变动数值,以及 IsIncreaseAskHigh() / IsDecreaseAskHigh() 这类布尔标志。实盘里你可以在 EA 的 OnTick 中轮询这些 flag:当 IsIncreaseAskHigh() 返回 true,说明当日最高 Ask 的涨幅已越过你设的控值,黄金或外汇品种此时流动性可能阶段性抽紧,需结合 spread 变化评估滑点风险。 最低 Ask 一侧的 SetControlAskLowInc / SetControlAskLowDec 逻辑完全对称。把高低两端阈值都设好,你的盯盘脚本才具备「日内 ask 摆动边界」的量化能力,而不是凭肉眼估。
class="type">bool IsDecreaseAsk(class="type">void) const { class="kw">return this.m_is_change_ask_dec; } class=class="str">"cmt">//--- Maximum Ask price for the day class=class="str">"cmt">//--- setting the maximum day Ask controlled(class="num">1) growth, (class="num">2) decrease value class=class="str">"cmt">//--- getting(class="num">3) the maximum Ask change value within a day, class=class="str">"cmt">//--- getting the flag of the maximum day Ask change exceeding the(class="num">4) growth, (class="num">5) decrease value class="type">void SetControlAskHighInc(const class="type">class="kw">double value) { this.m_control_ask_high_inc=::fabs(value); } class="type">void SetControlAskHighDec(const class="type">class="kw">double value) { this.m_control_ask_high_dec=::fabs(value); } class="type">class="kw">double GetValueChangedAskHigh(class="type">void) const { class="kw">return this.m_changed_ask_high_value; } class="type">bool IsIncreaseAskHigh(class="type">void) const { class="kw">return this.m_is_change_ask_high_inc; } class="type">bool IsDecreaseAskHigh(class="type">void) const { class="kw">return this.m_is_change_ask_high_dec; } class=class="str">"cmt">//--- Minimum Ask price for the day class=class="str">"cmt">//--- setting the minimum day Ask controlled(class="num">1) growth, (class="num">2) decrease value class=class="str">"cmt">//--- getting(class="num">3) the minimum Ask change value within a day, class=class="str">"cmt">//--- getting the flag of the minimum day Ask change exceeding the(class="num">4) growth, (class="num">5) decrease value class="type">void SetControlAskLowInc(const class="type">class="kw">double value) { this.m_control_ask_low_inc=::fabs(value); } class="type">void SetControlAskLowDec(const class="type">class="kw">double value) { this.m_control_ask_low_dec=::fabs(value); }
Ask低点变动与真实成交量的类封装接口
这段类方法把卖价低点(Ask Low)的变动和真实成交量(Real Volume)的日内变化都收进了同一个访问器集合,方便策略层直接读标志位而不是自己算差值。 double GetValueChangedAskLow(void) const { return this.m_changed_ask_low_value; } bool IsIncreaseAskLow(void) const { return this.m_is_change_ask_low_inc; } bool IsDecreaseAskLow(void) const { return this.m_is_change_ask_low_dec; } 上面三行分别暴露:Ask低点相较基准的变动数值、是否向上突破控制值、是否向下跌破控制值。m_changed_ask_low_value 是带符号的差值,正负直接区分方向。 真实成交量部分用 SetControlVolumeRealInc / SetControlVolumeRealDec 写入阈值,内部统一走 ::fabs(value) 取绝对值,避免外部误传负数导致阈值失效。GetValueChangedVolumeReal 返回当日真实量的净变化,IsIncreaseVolumeReal / IsDecreaseVolumeReal 给出是否越阈的布尔标志。 在 MT5 里接 broker 提供的真实成交量数据(外汇品种多数无此数据,贵金属 XAUUSD 在部分券商有),把控制值设成前一日均量的 1.5 倍,盘中若 IsIncreaseVolumeReal() 返回 true 且 IsDecreaseAskLow() 同时为真,价格倾向出现买方承接。外汇与贵金属杠杆高,此类信号仅作概率参考,实盘需自担风险。
class="type">class="kw">double GetValueChangedAskLow(class="type">void) const { class="kw">return this.m_changed_ask_low_value; } class="type">bool IsIncreaseAskLow(class="type">void) const { class="kw">return this.m_is_change_ask_low_inc; } class="type">bool IsDecreaseAskLow(class="type">void) const { class="kw">return this.m_is_change_ask_low_dec; } class=class="str">"cmt">//--- Real Volume for the day class=class="str">"cmt">//--- setting the real day volume controlled(class="num">1) growth, (class="num">2) decrease value class=class="str">"cmt">//--- getting(class="num">3) the change value of the real day volume, class=class="str">"cmt">//--- getting the flag of the real day volume change exceeding the(class="num">4) growth, (class="num">5) decrease value class="type">void SetControlVolumeRealInc(const class="type">class="kw">double value) { this.m_control_volume_real_inc=::fabs(value); } class="type">void SetControlVolumeRealDec(const class="type">class="kw">double value) { this.m_control_volume_real_dec=::fabs(value); } class="type">class="kw">double GetValueChangedVolumeReal(class="type">void) const { class="kw">return this.m_changed_volume_real_value; } class="type">bool IsIncreaseVolumeReal(class="type">void) const { class="kw">return this.m_is_change_volume_real_inc; } class="type">bool IsDecreaseVolumeReal(class="type">void) const { class="kw">return this.m_is_change_volume_real_dec; } class=class="str">"cmt">//--- Maximum real volume for the day class=class="str">"cmt">//--- setting the maximum real day volume controlled(class="num">1) growth, (class="num">2) decrease value class=class="str">"cmt">//--- getting(class="num">3) the change value of the maximum real day volume, class=class="str">"cmt">//--- getting the flag of the maximum real day volume change exceeding the(class="num">4) growth, (class="num">5) decrease value
「日内真实量的极值阈值接口」
这组方法把「日内最高真实量」和「日内最低真实量」的增减监控拆成了独立 setter/getter。设置阈值时用 ::fabs(value) 包一层,意味着你传负数也会被转成绝对值,实际控制边界只认大小不认方向。 最高量部分:SetControlVolumeHighRealInc / Dec 写入成员 m_control_volume_high_real_day_inc / dec;GetValueChangedVolumeHighReal 返回 m_changed_volume_high_real_day_value 这个实际偏差值;IsIncrease / IsDecrease 两个布尔接口直接吐出是否越过阈值的标志位。 最低量对称实现:SetControlVolumeLowRealInc / Dec 对应 m_control_volume_low_real_day_inc / dec,GetValueChangedVolumeLowReal 回传 m_changed_volume_low_real_day_value,IsIncrease / IsDecrease 回读 m_is_change_volume_low_real_day_inc / dec。 在 MT5 里接这套类时,先调 Set 系方法灌入你想要的阈值(比如 0.5 手或按账户净值比例算出的量),再在 OnTick 里轮询 Is 系布尔量,就能在真实量突破日高/日低阈值时触发后续逻辑。外汇与贵金属杠杆高,量异动可能只是流动性真空的噪声,阈值需结合品种平均日量回测后定。
class="type">void SetControlVolumeHighRealInc(const class="type">class="kw">double value) { this.m_control_volume_high_real_day_inc=::fabs(value); } class="type">void SetControlVolumeHighRealDec(const class="type">class="kw">double value) { this.m_control_volume_high_real_day_dec=::fabs(value); } class="type">class="kw">double GetValueChangedVolumeHighReal(class="type">void) const { class="kw">return this.m_changed_volume_high_real_day_value; } class="type">bool IsIncreaseVolumeHighReal(class="type">void) const { class="kw">return this.m_is_change_volume_high_real_day_inc; } class="type">bool IsDecreaseVolumeHighReal(class="type">void) const { class="kw">return this.m_is_change_volume_low_real_day_dec; } class=class="str">"cmt">//--- Minimum real volume for the day class=class="str">"cmt">//--- setting the minimum real day volume controlled(class="num">1) growth, (class="num">2) decrease value class=class="str">"cmt">//--- getting(class="num">3) the change value of the minimum real day volume, class=class="str">"cmt">//--- getting the flag of the minimum real day volume change exceeding the(class="num">4) growth, (class="num">5) decrease value class="type">void SetControlVolumeLowRealInc(const class="type">class="kw">double value) { this.m_control_volume_low_real_day_inc=::fabs(value); } class="type">void SetControlVolumeLowRealDec(const class="type">class="kw">double value) { this.m_control_volume_low_real_day_dec=::fabs(value); } class="type">class="kw">double GetValueChangedVolumeLowReal(class="type">void) const { class="kw">return this.m_changed_volume_low_real_day_value; } class="type">bool IsIncreaseVolumeLowReal(class="type">void) const { class="kw">return this.m_is_change_volume_low_real_day_inc; } class="type">bool IsDecreaseVolumeLowReal(class="type">void) const { class="kw">return this.m_is_change_volume_low_real_day_dec; } class=class="str">"cmt">//--- Strike price class=class="str">"cmt">//--- setting the strike price controlled(class="num">1) growth, (class="num">2) decrease value class=class="str">"cmt">//--- getting(class="num">3) the change value of the strike price, class=class="str">"cmt">//--- getting the flag of the strike price change exceeding the(class="num">4) growth, (class="num">5) decrease value
◍ 期权行权价与总量限制的变动接口
在 MT5 的自定义控制类里,行权价增减和单向持仓总限额的变动都通过一组 setter / getter 暴露给外部逻辑。
下面这段代码直接给出了成员函数的实现,注意所有设定值都套了 ::fabs(),意味着传入负值会被强制取绝对值,避免行权间距出现负步长。
class="type">void SetControlOptionStrikeInc(const class="type">class="kw">double value) { this.m_control_option_strike_inc=::fabs(value); } class="type">void SetControlOptionStrikeDec(const class="type">class="kw">double value) { this.m_control_option_strike_dec=::fabs(value); } class="type">class="kw">double GetValueChangedOptionStrike(class="type">void) const { class="kw">return this.m_changed_option_strike_value; } class="type">bool IsIncreaseOptionStrike(class="type">void) const { class="kw">return this.m_is_change_option_strike_inc; } class="type">bool IsDecreaseOptionStrike(class="type">void) const { class="kw">return this.m_is_change_option_strike_dec; } class="type">class="kw">double GetValueChangedVolumeLimit(class="type">void) const { class="kw">return this.m_changed_volume_limit_value; } class="type">bool IsIncreaseVolumeLimit(class="type">void) const { class="kw">return this.m_is_change_volume_limit_inc; } class="type">bool IsDecreaseVolumeLimit(class="type">void) const { class="kw">return this.m_is_change_volume_limit_dec; } class="type">class="kw">double GetValueChangedSwapLong(class="type">void) const { class="kw">return this.m_changed_swap_long_value; }
SetControlOptionStrikeInc 和 SetControlOptionStrikeDec 只负责存绝对值;真正的变动幅度由 GetValueChangedOptionStrike 返回,方向靠 IsIncreaseOptionStrike / IsDecreaseOptionStrike 两个布尔量判断。
总量限制部分结构完全一致——GetValueChangedVolumeLimit 给出具体手数变化,IsIncreaseVolumeLimit / IsDecreaseVolumeLimit 给出方向旗标,可用于在 EA 里动态卡死同向总敞口。
外汇与贵金属保证金交易杠杆高,这类总量阀值若设错,可能在几分钟內放大穿仓风险,参数务必在策略测试器里用历史tick跑过再上实盘。
开 MT5 把上面函数塞进你的 CControl 派生类,编译后打印 GetValueChangedSwapLong() 的返回值,能直接确认掉期多单字段有没有被外部正确写入。
class="type">void SetControlOptionStrikeInc(const class="type">class="kw">double value) { this.m_control_option_strike_inc=::fabs(value); } class="type">void SetControlOptionStrikeDec(const class="type">class="kw">double value) { this.m_control_option_strike_dec=::fabs(value); } class="type">class="kw">double GetValueChangedOptionStrike(class="type">void) const { class="kw">return this.m_changed_option_strike_value; } class="type">bool IsIncreaseOptionStrike(class="type">void) const { class="kw">return this.m_is_change_option_strike_inc; } class="type">bool IsDecreaseOptionStrike(class="type">void) const { class="kw">return this.m_is_change_option_strike_dec; } class="type">class="kw">double GetValueChangedVolumeLimit(class="type">void) const { class="kw">return this.m_changed_volume_limit_value; } class="type">bool IsIncreaseVolumeLimit(class="type">void) const { class="kw">return this.m_is_change_volume_limit_inc; } class="type">bool IsDecreaseVolumeLimit(class="type">void) const { class="kw">return this.m_is_change_volume_limit_dec; } class="type">class="kw">double GetValueChangedSwapLong(class="type">void) const { class="kw">return this.m_changed_swap_long_value; }
库存费与时段成交量的状态读取接口
在 MT5 的自定义类里,区分多空两侧库存费(swap)的变动方向,是判断经纪商调仓信号的基础。下面这组方法把「多单库存费是否上调 / 下调」「空单库存费是否上调 / 下调」拆成独立布尔量,外部策略只需轮询 IsIncreaseSwapLong() 与 IsDecreaseSwapShort() 之类接口,就能在 EA 里触发对应逻辑。 时段成交量(session volume)的监控则分三步:先用 SetControlSessionVolumeInc() / SetControlSessionVolumeDec() 写入控制阈值,注意内部用 ::fabs(value) 取绝对值,所以传负参数也会被纠正为正阈值。随后 GetValueChangedSessionVolume() 返回当前会话相比基准的成交量偏移量,IsIncreaseSessionVolume() 告知是否突破增长阈值。 外汇与贵金属自带高杠杆与隔夜风险,库存费突变往往伴随流动性重定价,上述接口仅作状态探测,不代表任何方向确定性。
class="type">bool IsIncreaseSwapLong(class="type">void) const { class="kw">return this.m_is_change_swap_long_inc; } class="type">bool IsDecreaseSwapLong(class="type">void) const { class="kw">return this.m_is_change_swap_long_dec; } class=class="str">"cmt">//--- Swap class="type">short class=class="str">"cmt">//--- (class="num">1) getting the swap class="type">short change value, class=class="str">"cmt">//--- getting the flag of(class="num">2) increasing, (class="num">3) decreasing the swap class="type">short class="type">class="kw">double GetValueChangedSwapShort(class="type">void) const { class="kw">return this.m_changed_swap_short_value; } class="type">bool IsIncreaseSwapShort(class="type">void) const { class="kw">return this.m_is_change_swap_short_inc; } class="type">bool IsDecreaseSwapShort(class="type">void) const { class="kw">return this.m_is_change_swap_short_dec; } class=class="str">"cmt">//--- The total volume of deals in the current session class=class="str">"cmt">//--- setting the controlled value of(class="num">1) growth, (class="num">2) decrease in the total volume of deals during the current session class=class="str">"cmt">//--- getting(class="num">3) the total deal volume change value in the current session, class=class="str">"cmt">//--- getting the flag of the total deal volume change during the current session exceeding the(class="num">4) growth, (class="num">5) decrease value class="type">void SetControlSessionVolumeInc(const class="type">class="kw">double value) { this.m_control_session_volume_inc=::fabs(value); } class="type">void SetControlSessionVolumeDec(const class="type">class="kw">double value) { this.m_control_session_volume_dec=::fabs(value); } class="type">class="kw">double GetValueChangedSessionVolume(class="type">void) const { class="kw">return this.m_changed_session_volume_value; } class="type">bool IsIncreaseSessionVolume(class="type">void) const { class="kw">return this.m_is_change_session_volume_inc; }
class="type">bool IsIncreaseSwapLong(class="type">void) const { class="kw">return this.m_is_change_swap_long_inc; } class="type">bool IsDecreaseSwapLong(class="type">void) const { class="kw">return this.m_is_change_swap_long_dec; } class=class="str">"cmt">//--- Swap class="type">short class=class="str">"cmt">//--- (class="num">1) getting the swap class="type">short change value, class=class="str">"cmt">//--- getting the flag of(class="num">2) increasing, (class="num">3) decreasing the swap class="type">short class="type">class="kw">double GetValueChangedSwapShort(class="type">void) const { class="kw">return this.m_changed_swap_short_value; } class="type">bool IsIncreaseSwapShort(class="type">void) const { class="kw">return this.m_is_change_swap_short_inc; } class="type">bool IsDecreaseSwapShort(class="type">void) const { class="kw">return this.m_is_change_swap_short_dec; } class=class="str">"cmt">//--- The total volume of deals in the current session class=class="str">"cmt">//--- setting the controlled value of(class="num">1) growth, (class="num">2) decrease in the total volume of deals during the current session class=class="str">"cmt">//--- getting(class="num">3) the total deal volume change value in the current session, class=class="str">"cmt">//--- getting the flag of the total deal volume change during the current session exceeding the(class="num">4) growth, (class="num">5) decrease value class="type">void SetControlSessionVolumeInc(const class="type">class="kw">double value) { this.m_control_session_volume_inc=::fabs(value); } class="type">void SetControlSessionVolumeDec(const class="type">class="kw">double value) { this.m_control_session_volume_dec=::fabs(value); } class="type">class="kw">double GetValueChangedSessionVolume(class="type">void) const { class="kw">return this.m_changed_session_volume_value; } class="type">bool IsIncreaseSessionVolume(class="type">void) const { class="kw">return this.m_is_change_session_volume_inc; }
「会话维度的成交量与持仓量监控接口」
在 MT5 的自定义类里,把「当前会话」的成交量、总成交额、未平仓总仓量单独抽出来做阈值监控,是过滤噪音信号的一种硬手段。下面这组方法不负责采集数据,只负责设阈值和给状态位,真正数值由类内部在每根会话边界重置后累积。 成交量部分用 IsDecreaseSessionVolume 返回 bool,标识本会话成交量是否低于你用 SetControlSessionVolumeDec 设的控制值;SetControl 系列统一用 ::fabs(value) 取绝对值,避免手滑传负数把阈值逻辑搞反。 成交额(turnover)和持仓量(interest)接口结构完全一致:SetControlSessionTurnoverInc/Dec 与 SetControlSessionInterestInc/Dec 各自存正负向阈值,GetValueChangedSession* 回传实际变动值,IsIncrease/IsDecrease 回传是否越线。实盘里黄金 XAUUSD 在伦敦-纽约重叠时段成交额变动常超 20%,用这组接口卡掉小幅波动,可能让策略触发次数下降三成以上。 开 MT5 把这段直接塞进你的会话监控类,先只挂 IsIncreaseSessionTurnover 打印日志,跑一周看越线分布再决定阈值,外汇和贵金属杠杆高,误触加仓可能放大回撤。
class="type">bool IsDecreaseSessionVolume(class="type">void) const { class="kw">return this.m_is_change_session_volume_dec; } class=class="str">"cmt">//--- The total turnover in the current session class=class="str">"cmt">//--- setting the controlled value of the turnover(class="num">1) growth, (class="num">2) decrease during the current session class=class="str">"cmt">//--- getting(class="num">3) the total turnover change value in the current session, class=class="str">"cmt">//--- getting the flag of the total turnover change during the current session exceeding the(class="num">4) growth, (class="num">5) decrease value class="type">void SetControlSessionTurnoverInc(const class="type">class="kw">double value) { this.m_control_session_turnover_inc=::fabs(value); } class="type">void SetControlSessionTurnoverDec(const class="type">class="kw">double value) { this.m_control_session_turnover_dec=::fabs(value); } class="type">class="kw">double GetValueChangedSessionTurnover(class="type">void) const { class="kw">return this.m_changed_session_turnover_value; } class="type">bool IsIncreaseSessionTurnover(class="type">void) const { class="kw">return this.m_is_change_session_turnover_inc; } class="type">bool IsDecreaseSessionTurnover(class="type">void) const { class="kw">return this.m_is_change_session_turnover_dec; } class=class="str">"cmt">//--- The total volume of open positions class=class="str">"cmt">//--- setting the controlled value of(class="num">1) growth, (class="num">2) decrease in the total volume of open positions during the current session class=class="str">"cmt">//--- getting(class="num">3) the change value of the open positions total volume in the current session, class=class="str">"cmt">//--- getting the flag of the open positions total volume change during the current session exceeding the(class="num">4) growth, (class="num">5) decrease value class="type">void SetControlSessionInterestInc(const class="type">class="kw">double value) { this.m_control_session_interest_inc=::fabs(value); } class="type">void SetControlSessionInterestDec(const class="type">class="kw">double value) { this.m_control_session_interest_dec=::fabs(value); } class="type">class="kw">double GetValueChangedSessionInterest(class="type">void) const { class="kw">return this.m_changed_session_interest_value; }
◍ 盯盘类里怎么管会话内的买卖盘口增量
做 MT5 盯盘 EA 时,如果只抓 tick 不区分会话(session),很容易把隔夜跳空或盘前挂单误判成「资金涌入」。下面这段代码把「会话内兴趣度变化」和「会话内买卖单总成交量变化」拆成了两组只读/只写接口,方便在 OnTick 里做阈值判断。 会话兴趣度部分用两个 bool 常量方法暴露状态:IsIncreaseSessionInterest 读 m_is_change_session_interest_inc,IsDecreaseSessionInterest 读 m_is_change_session_interest_dec。这两个标志通常由类内部在跨 session 或成交量突变时赋值,外部只问「是不是增 / 是不是减」,不直接改。 买盘音量控制这组最实用:SetControlSessionBuyOrdVolumeInc / Dec 用 ::fabs(value) 把外部传入的阈值强制转正值,存进 m_control_session_buy_ord_volume_inc / dec;GetValueChangedSessionBuyOrdVolume 返回实际变化量 m_changed_session_buy_ord_volume_value(double,可正可负);IsIncrease / IsDecreaseSessionBuyOrdVolume 则返回是否突破你设的增 / 减阈值。卖盘对称地用 SetControlSessionSellOrdVolumeInc / Dec 与对应 m_ 成员,逻辑完全一致。 实盘验证建议:在策略测试器里给 Inc 阈值设 0.5 手、Dec 设 0.3 手,跑 EURUSD 的 London session,观察 IsIncreaseSessionBuyOrdVolume 触发次数。外汇与贵金属杠杆高、滑点大,阈值触发只代表「盘口动量偏向」,不预示方向,需结合价格行为确认。
class="type">bool IsIncreaseSessionInterest(class="type">void) const { class="kw">return this.m_is_change_session_interest_inc; } class="type">bool IsDecreaseSessionInterest(class="type">void) const { class="kw">return this.m_is_change_session_interest_dec; } class=class="str">"cmt">//--- The total volume of Buy orders at the moment class=class="str">"cmt">//--- setting the controlled value of(class="num">1) growth, (class="num">2) decrease in the current total buy order volume class=class="str">"cmt">//--- getting(class="num">3) the change value of the current total buy order volume, class=class="str">"cmt">//--- getting the flag of the current total buy orders&class="macro">#x27; volume change exceeding the(class="num">4) growth, (class="num">5) decrease value class="type">void SetControlSessionBuyOrdVolumeInc(const class="type">class="kw">double value) { this.m_control_session_buy_ord_volume_inc=::fabs(value); } class="type">void SetControlSessionBuyOrdVolumeDec(const class="type">class="kw">double value) { this.m_control_session_buy_ord_volume_dec=::fabs(value); } class="type">class="kw">double GetValueChangedSessionBuyOrdVolume(class="type">void) const { class="kw">return this.m_changed_session_buy_ord_volume_value; } class="type">bool IsIncreaseSessionBuyOrdVolume(class="type">void) const { class="kw">return this.m_is_change_session_buy_ord_volume_inc; } class="type">bool IsDecreaseSessionBuyOrdVolume(class="type">void) const { class="kw">return this.m_is_change_session_buy_ord_volume_dec; } class=class="str">"cmt">//--- The total volume of Sell orders at the moment class=class="str">"cmt">//--- setting the controlled value of(class="num">1) growth, (class="num">2) decrease in the current total sell order volume class=class="str">"cmt">//--- getting(class="num">3) the change value of the current total sell order volume, class=class="str">"cmt">//--- getting the flag of the current total sell orders&class="macro">#x27; volume change exceeding the(class="num">4) growth, (class="num">5) decrease value class="type">void SetControlSessionSellOrdVolumeInc(const class="type">class="kw">double value) { this.m_control_session_sell_ord_volume_inc=::fabs(value); } class="type">void SetControlSessionSellOrdVolumeDec(const class="type">class="kw">double value) { this.m_control_session_sell_ord_volume_dec=::fabs(value); } class="type">class="kw">double GetValueChangedSessionSellOrdVolume(class="type">void) const { class="kw">return this.m_changed_session_sell_ord_volume_value; }
盯住时段开收盘价的变动阈值
在 MT5 的自定义类里,时段卖单量增减、时段开盘价与收盘价变动都做成了一组 getter/setter。卖单量部分用 IsIncreaseSessionSellOrdVolume / IsDecreaseSessionSellOrdVolume 两个布尔方法回传内部标志位,直接告诉你当前会话卖单量是扩还是缩。 开盘价控制逻辑集中在四个接口:SetControlSessionPriceOpenInc 与 SetControlSessionPriceOpenDec 负责写入监控阈值,内部统一用 ::fabs(value) 取绝对值,避免传负参数导致阈值失效。GetValueChangedSessionPriceOpen 回传实际变动幅度,IsIncreaseSessionPriceOpen / IsDecreaseSessionPriceOpen 则回传是否越过了你设的增、减边界。 收盘价一侧完全对称:SetControlSessionPriceCloseInc / SetControlSessionPriceCloseDec 写阈值,GetValueChangedSessionPriceClose 取变动值。实盘里把这组阈值设成 ATR(14) 的 0.3~0.5 倍,对外汇与贵金属这类高波动品种更可能过滤掉噪音跳动,但杠杆交易风险高,参数须自己在策略测试器里跑过再上。 别把 fabs 当免死金牌 传参前就确认数值单位跟品种报价精度一致,否则 ::fabs 虽不报错,却可能把 0.00015 这种点数误当成合理阈值,回测时一条都触发不了。
class="type">bool IsIncreaseSessionSellOrdVolume(class="type">void) const { class="kw">return this.m_is_change_session_sell_ord_volume_inc; } class="type">bool IsDecreaseSessionSellOrdVolume(class="type">void) const { class="kw">return this.m_is_change_session_sell_ord_volume_dec; } class=class="str">"cmt">//--- Session open price class=class="str">"cmt">//--- setting the session open price controlled(class="num">1) growth, (class="num">2) decrease value class=class="str">"cmt">//--- getting(class="num">3) the change value of the session open price, class=class="str">"cmt">//--- getting the flag of the session open price change exceeding the(class="num">4) growth, (class="num">5) decrease value class="type">void SetControlSessionPriceOpenInc(const class="type">class="kw">double value) { this.m_control_session_open_inc=::fabs(value); } class="type">void SetControlSessionPriceOpenDec(const class="type">class="kw">double value) { this.m_control_session_open_dec=::fabs(value); } class="type">class="kw">double GetValueChangedSessionPriceOpen(class="type">void) const { class="kw">return this.m_changed_session_open_value; } class="type">bool IsIncreaseSessionPriceOpen(class="type">void) const { class="kw">return this.m_is_change_session_open_inc; } class="type">bool IsDecreaseSessionPriceOpen(class="type">void) const { class="kw">return this.m_is_change_session_open_dec; } class=class="str">"cmt">//--- Session close price class=class="str">"cmt">//--- setting the session close price controlled(class="num">1) growth, (class="num">2) decrease value class=class="str">"cmt">//--- getting(class="num">3) the change value of the session close price, class=class="str">"cmt">//--- getting the flag of the session close price change exceeding the(class="num">4) growth, (class="num">5) decrease value class="type">void SetControlSessionPriceCloseInc(const class="type">class="kw">double value) { this.m_control_session_close_inc=::fabs(value); } class="type">void SetControlSessionPriceCloseDec(const class="type">class="kw">double value) { this.m_control_session_close_dec=::fabs(value); } class="type">class="kw">double GetValueChangedSessionPriceClose(class="type">void) const { class="kw">return this.m_changed_session_close_value; }
「会话加权均价与收盘价的变动判定接口」
这段类方法暴露了会话维度两类关键状态的读取与设定:一是会话收盘价相对前一会话的涨跌标记,二是会话加权平均价(AW)的受控阈值与实变数值。 IsIncreaseSessionPriceClose 与 IsDecreaseSessionPriceClose 直接返回 m_is_change_session_close_inc / _dec 布尔成员,用于判定收盘价方向;而 SetControlSessionPriceAWInc / Dec 接收 double 入参后先用 ::fabs 取绝对值再赋给 m_control_session_aw_inc / _dec,意味着阈值只认幅度、不区分正负。 GetValueChangedSessionPriceAW 给出 m_changed_session_aw_value 实际偏移量,配合 IsIncreaseSessionPriceAW / IsDecreaseSessionPriceAW 可确认加权均价是否突破你设定的增长或下降控制线。实盘里若 m_control_session_aw_inc 设为 0.00020(EURUSD 两基点),则只有偏移绝对值超该值才置位——外汇与贵金属波动剧烈,这类阈值需按品种 ATR 回测校准,误判概率不低。 下方 CSymbol 闭参构造器接收 ENUM_SYMBOL_STATUS、name、index,先存 m_name 再调 Exist() 校验;若服务器无此品种,打印双语报错并置 m_global_error 为 ERR_MARKET_UNKNOWN_SYMBOL,调用方须先查该错误码再继续。
class="type">bool IsIncreaseSessionPriceClose(class="type">void) const { class="kw">return this.m_is_change_session_close_inc; } class="type">bool IsDecreaseSessionPriceClose(class="type">void) const { class="kw">return this.m_is_change_session_close_dec; } class=class="str">"cmt">//--- The average weighted session price class=class="str">"cmt">//--- setting the average weighted session price controlled(class="num">1) growth, (class="num">2) decrease value class=class="str">"cmt">//--- getting(class="num">3) the change value of the average weighted session price, class=class="str">"cmt">//--- getting the flag of the average weighted session price change exceeding the(class="num">4) growth, (class="num">5) decrease value class="type">void SetControlSessionPriceAWInc(const class="type">class="kw">double value) { this.m_control_session_aw_inc=::fabs(value); } class="type">void SetControlSessionPriceAWDec(const class="type">class="kw">double value) { this.m_control_session_aw_dec=::fabs(value); } class="type">class="kw">double GetValueChangedSessionPriceAW(class="type">void) const { class="kw">return this.m_changed_session_aw_value; } class="type">bool IsIncreaseSessionPriceAW(class="type">void) const { class="kw">return this.m_is_change_session_aw_inc; } class="type">bool IsDecreaseSessionPriceAW(class="type">void) const { class="kw">return this.m_is_change_session_aw_dec; } class=class="str">"cmt">//--- class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Closed parametric constructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CSymbol::CSymbol(ENUM_SYMBOL_STATUS symbol_status,const class="type">class="kw">string name,const class="type">int index) { this.m_name=name; if(!this.Exist()) { ::Print(DFUN_ERR_LINE,"\"",this.m_name,"\"",": ",TextByLanguage("Ошибка. Такого символа нет на сервере","Error. There is no such symbol on the server")); this.m_global_error=ERR_MARKET_UNKNOWN_SYMBOL; }
◍ 初始化前先把品种塞进市场报价窗
在 MT5 里直接读取一个品种的数据前,得先确认它已经在市场报价窗(Market Watch)里。代码先用 SymbolInfoInteger 查 SYMBOL_SELECT,返回 false 就说明当前没被选中,这时调用 SetToMarketWatch 尝试加进去,失败则通过 GetLastError 抓错误码并打印双语报错,便于在日志里定位是权限问题还是品种名拼错。 塞好品种后立刻 ResetLastError 清掉之前的错误状态,再调 SymbolInfoTick 抓最新 tick 数据(卖价、买价、成交量等)。这一步拿不到的话,后续所有基于 m_tick 的计算都会失真,所以同样把错误码存进 m_global_error 并打日志。 随后进入数据初始化:Reset 清空旧状态,InitMarginRates 准备保证金参数,ZeroMemory 把上一份品种结构体 m_struct_prev_symbol 置零,并显式把它的 trade_mode 设为 WRONG_VALUE(-1),避免残留的上一个品种交易模式干扰本次判断。 MQL5 环境下还会单独跑一次 MarginRates(),若返回 false 直接 return 终止构造,因为保证金系数缺失时继续初始化没有意义。最后把 symbol_status、报价窗索引 index、tick.volume 以及 SYMBOL_SELECT / SYMBOL_VISIBLE 等整数属性写进 m_long_prop 数组,供后续调用。外汇与贵金属杠杆品种在此类初始化中常因报价窗未包含而失败,实盘前应在 MT5 里手动确认品种已显示。
class="type">bool select=::SymbolInfoInteger(this.m_name,SYMBOL_SELECT); ::ResetLastError(); if(!select) { if(!this.SetToMarketWatch()) { this.m_global_error=::GetLastError(); ::Print(DFUN_ERR_LINE,"\",this.m_name,\": \",TextByLanguage("Не удалось поместить в обзор рынка. Ошибка: ","Failed to put in the market watch. Error: "),this.m_global_error); } } ::ResetLastError(); if(!::SymbolInfoTick(this.m_name,this.m_tick)) { this.m_global_error=::GetLastError(); ::Print(DFUN_ERR_LINE,"\",this.m_name,\": \",TextByLanguage("Не удалось получить текущие цены. Ошибка: ","Could not get current prices. Error: "),this.m_global_error); } class=class="str">"cmt">//--- Initialize data this.Reset(); this.InitMarginRates(); ::ZeroMemory(this.m_struct_prev_symbol); this.m_struct_prev_symbol.trade_mode=WRONG_VALUE; this.InitChangesParams(); this.InitControlsParams(); class="macro">#ifdef __MQL5__ ::ResetLastError(); if(!this.MarginRates()) { this.m_global_error=::GetLastError(); ::Print(DFUN_ERR_LINE,this.Name(),": ",TextByLanguage("Не удалось получить коэффициенты взимания маржи. Ошибка: ","Failed to get margin rates. Error: "),this.m_global_error); class="kw">return; } class="macro">#endif class=class="str">"cmt">//--- Save integer properties this.m_long_prop[SYMBOL_PROP_STATUS] = symbol_status; this.m_long_prop[SYMBOL_PROP_INDEX_MW] = index; this.m_long_prop[SYMBOL_PROP_VOLUME] = (class="type">long)this.m_tick.volume; this.m_long_prop[SYMBOL_PROP_SELECT] = ::SymbolInfoInteger(this.m_name,SYMBOL_SELECT); this.m_long_prop[SYMBOL_PROP_VISIBLE] = ::SymbolInfoInteger(this.m_name,SYMBOL_VISIBLE);
把品种整数属性一次性塞进结构体
在封装交易品种对象时,把 MT5 终端能直接取到的整数类属性批量写进成员变量数组,是后续逻辑少调 API 的关键一步。下面这段代码在对象初始化阶段连续调用 SymbolInfoInteger,把交易时段内的成交数、买卖挂单量、成交量极值、报价精度等全部缓存。 注意 SYMBOL_PROP_SPREAD_FLOAT 返回的是布尔型整数(0/1),用来判断点差是固定还是浮动;SYMBOL_PROP_TICKS_BOOKDEPTH 告诉你当前品种深度行情支持的档位数,XAUUSD 在多数券商下返回 10 档,而部分外汇对可能只有 1 档。 SYMBOL_TRADE_STOPS_LEVEL 给出的止损最小距离以点为单位,实盘里若设 stop 小于该值会被拒单,外汇与贵金属均属高风险品种,挂单前务必先读这个数。
this.m_long_prop[SYMBOL_PROP_SESSION_DEALS] = ::SymbolInfoInteger(this.m_name,SYMBOL_SESSION_DEALS); this.m_long_prop[SYMBOL_PROP_SESSION_BUY_ORDERS] = ::SymbolInfoInteger(this.m_name,SYMBOL_SESSION_BUY_ORDERS); this.m_long_prop[SYMBOL_PROP_SESSION_SELL_ORDERS] = ::SymbolInfoInteger(this.m_name,SYMBOL_SESSION_SELL_ORDERS); this.m_long_prop[SYMBOL_PROP_VOLUMEHIGH] = ::SymbolInfoInteger(this.m_name,SYMBOL_VOLUMEHIGH); this.m_long_prop[SYMBOL_PROP_VOLUMELOW] = ::SymbolInfoInteger(this.m_name,SYMBOL_VOLUMELOW); this.m_long_prop[SYMBOL_PROP_DIGITS] = ::SymbolInfoInteger(this.m_name,SYMBOL_DIGITS); this.m_long_prop[SYMBOL_PROP_SPREAD] = ::SymbolInfoInteger(this.m_name,SYMBOL_SPREAD); this.m_long_prop[SYMBOL_PROP_SPREAD_FLOAT] = ::SymbolInfoInteger(this.m_name,SYMBOL_SPREAD_FLOAT); this.m_long_prop[SYMBOL_PROP_TICKS_BOOKDEPTH] = ::SymbolInfoInteger(this.m_name,SYMBOL_TICKS_BOOKDEPTH); this.m_long_prop[SYMBOL_PROP_TRADE_MODE] = ::SymbolInfoInteger(this.m_name,SYMBOL_TRADE_MODE); this.m_long_prop[SYMBOL_PROP_START_TIME] = ::SymbolInfoInteger(this.m_name,SYMBOL_START_TIME); this.m_long_prop[SYMBOL_PROP_EXPIRATION_TIME] = ::SymbolInfoInteger(this.m_name,SYMBOL_EXPIRATION_TIME); this.m_long_prop[SYMBOL_PROP_TRADE_STOPS_LEVEL] = ::SymbolInfoInteger(this.m_name,SYMBOL_TRADE_STOPS_LEVEL);
「把交易品种整型属性一次性塞进数组」
在封装交易品种类时,把 MT5 终端返回的整型属性集中缓存到 m_long_prop 数组,能避免后续反复调用 SymbolInfoInteger 造成的开销。下面这段初始化逻辑覆盖了冻结水平、成交执行模式、库存费滚动日等 16 个关键字段。 其中 SYMBOL_TRADE_FREEZE_LEVEL 返回的是止损挂单距市价的最小点数,外汇与贵金属品种通常落在 0~10 点区间,实战中若你的挂单距离小于该值,MT5 会直接拒单。SYMBOL_SWAP_ROLLOVER3DAYS 则标记每周哪一天收三倍库存费,多数经纪商对 XAUUSD 设为周三。 其余如 SYMBOL_PROP_EXIST、SYMBOL_PROP_CUSTOM 等由类内方法取值,说明这些状态在对象生命周期内可能被主动维护,而不是每次都问终端。开 MT5 新建 EA 把这段抄进 OnInit 前的 Refresh 函数,打印数组即可核对当前品种的真实参数。
this.m_long_prop[SYMBOL_PROP_TRADE_FREEZE_LEVEL] = ::SymbolInfoInteger(this.m_name,SYMBOL_TRADE_FREEZE_LEVEL); this.m_long_prop[SYMBOL_PROP_TRADE_EXEMODE] = ::SymbolInfoInteger(this.m_name,SYMBOL_TRADE_EXEMODE); this.m_long_prop[SYMBOL_PROP_SWAP_ROLLOVER3DAYS] = ::SymbolInfoInteger(this.m_name,SYMBOL_SWAP_ROLLOVER3DAYS); this.m_long_prop[SYMBOL_PROP_TIME] = this.TickTime(); this.m_long_prop[SYMBOL_PROP_EXIST] = this.SymbolExists(); this.m_long_prop[SYMBOL_PROP_CUSTOM] = this.SymbolCustom(); this.m_long_prop[SYMBOL_PROP_MARGIN_HEDGED_USE_LEG] = this.SymbolMarginHedgedUseLEG(); this.m_long_prop[SYMBOL_PROP_ORDER_MODE] = this.SymbolOrderMode(); this.m_long_prop[SYMBOL_PROP_FILLING_MODE] = this.SymbolOrderFillingMode(); this.m_long_prop[SYMBOL_PROP_EXPIRATION_MODE] = this.SymbolExpirationMode(); this.m_long_prop[SYMBOL_PROP_ORDER_GTC_MODE] = this.SymbolOrderGTCMode(); this.m_long_prop[SYMBOL_PROP_OPTION_MODE] = this.SymbolOptionMode(); this.m_long_prop[SYMBOL_PROP_OPTION_RIGHT] = this.SymbolOptionRight(); this.m_long_prop[SYMBOL_PROP_BACKGROUND_COLOR] = this.SymbolBackgroundColor(); this.m_long_prop[SYMBOL_PROP_CHART_MODE] = this.SymbolChartMode();
◍ 把品种属性一次性塞进缓存数组
在自定义品种类里,初始化阶段会把交易计算模式和掉期模式先写进长整型属性槽,避免每次调用都去问终端。 紧接着用 SymbolInfoDouble 把一批实时浮点属性拉进来,存到 m_double_prop 对应的索引位置。这里覆盖的字段包括日内 Ask 高/低、Last 高/低、点值、tick 价值与盈亏换算、合约大小、最小/最大/步进成交量等。 以 SYMBOL_POINT 为例,EURUSD 在多数券商下返回 1e-5,而 XAUUSD 常是 1e-3;若你缓存后直接拿去算止损点数,务必确认索引映射没错位,否则黄金和欧美会差出 100 倍量级。外汇与贵金属杠杆高,属性读错可能让仓位计算失控。 下面这段就是原初始化片段,逐行看清楚它存了什么:
this.m_long_prop[SYMBOL_PROP_TRADE_CALC_MODE] = this.SymbolCalcMode(); this.m_long_prop[SYMBOL_PROP_SWAP_MODE] = this.SymbolSwapMode(); class=class="str">"cmt">//--- Save real properties this.m_double_prop[this.IndexProp(SYMBOL_PROP_ASKHIGH)] = ::SymbolInfoDouble(this.m_name,SYMBOL_ASKHIGH); this.m_double_prop[this.IndexProp(SYMBOL_PROP_ASKLOW)] = ::SymbolInfoDouble(this.m_name,SYMBOL_ASKLOW); this.m_double_prop[this.IndexProp(SYMBOL_PROP_LASTHIGH)] = ::SymbolInfoDouble(this.m_name,SYMBOL_LASTHIGH); this.m_double_prop[this.IndexProp(SYMBOL_PROP_LASTLOW)] = ::SymbolInfoDouble(this.m_name,SYMBOL_LASTLOW); this.m_double_prop[this.IndexProp(SYMBOL_PROP_POINT)] = ::SymbolInfoDouble(this.m_name,SYMBOL_POINT); this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_TICK_VALUE)] = ::SymbolInfoDouble(this.m_name,SYMBOL_TRADE_TICK_VALUE); this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_TICK_VALUE_PROFIT)] = ::SymbolInfoDouble(this.m_name,SYMBOL_TRADE_TICK_VALUE_PROFIT); this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_TICK_VALUE_LOSS)] = ::SymbolInfoDouble(this.m_name,SYMBOL_TRADE_TICK_VALUE_LOSS); this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_TICK_SIZE)] = ::SymbolInfoDouble(this.m_name,SYMBOL_TRADE_TICK_SIZE); this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_CONTRACT_SIZE)] = ::SymbolInfoDouble(this.m_name,SYMBOL_TRADE_CONTRACT_SIZE); this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUME_MIN)] = ::SymbolInfoDouble(this.m_name,SYMBOL_VOLUME_MIN); this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUME_MAX)] = ::SymbolInfoDouble(this.m_name,SYMBOL_VOLUME_MAX); this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUME_STEP)] = ::SymbolInfoDouble(this.m_name,SYMBOL_VOLUME_STEP);
把品种限仓与库存费一次性塞进数组
在封装交易品种对象时,这一段把 MT5 返回的双精度属性批量写进类内数组 m_double_prop,下标由 IndexProp 按宏名映射。覆盖的内容从限仓手数 SYMBOL_VOLUME_LIMIT,到多空库存费 SYMBOL_SWAP_LONG / SHORT,再到初始与维持保证金,都是下单前必须核对的风控字段。 外汇与贵金属杠杆高,SYMBOL_MARGIN_INITIAL 和 SYMBOL_MARGIN_MAINTENANCE 若读错,可能让仓位在波动中被强平。session 系列如 SYMBOL_SESSION_BUY_ORDERS_VOLUME、SYMBOL_SESSION_SELL_ORDERS_VOLUME 则反映当前时段买卖挂单量,可用于判断盘口拥挤度。 下面这段即原文赋值逻辑,逐行都是「数组槽位 = SymbolInfoDouble(品种名, 属性宏)」的直写: this.IndexProp(宏) 先算出该属性在数组中的序号,::SymbolInfoDouble 走 MQL5 原生 API 取实时值。例如 SYMBOL_VOLUME_LIMIT 行拿到该品种单户最大可交易手数;SYMBOL_SWAP_LONG 行拿到多单隔夜费,负数代表扣费。 跑起来前,建议在 MT5 终端用 SymbolInfoDouble 单独打印几个值,确认和「品种规格」窗口一致,再放心接进自己的风控模块。
this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUME_LIMIT)] = ::SymbolInfoDouble(this.m_name,SYMBOL_VOLUME_LIMIT); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SWAP_LONG)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SWAP_LONG); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SWAP_SHORT)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SWAP_SHORT); this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_INITIAL)] = ::SymbolInfoDouble(this.m_name,SYMBOL_MARGIN_INITIAL); this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_MAINTENANCE)] = ::SymbolInfoDouble(this.m_name,SYMBOL_MARGIN_MAINTENANCE); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_VOLUME)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_VOLUME); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_TURNOVER)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_TURNOVER); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_INTEREST)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_INTEREST); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_BUY_ORDERS_VOLUME)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_BUY_ORDERS_VOLUME); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_SELL_ORDERS_VOLUME)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_SELL_ORDERS_VOLUME); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_OPEN)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_OPEN); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_CLOSE)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_CLOSE); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_AW)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_AW); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_PRICE_SETTLEMENT)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_PRICE_SETTLEMENT); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_PRICE_LIMIT_MIN)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_PRICE_LIMIT_MIN);
「把品种双精度属性一次性灌进数组」
在自定义品种类里,这段赋值把 MT5 运行期的双精度行情与合约参数集中写进 m_double_prop 数组,索引由 IndexProp() 按枚举定位,避免后续反复调用 SymbolInfoDouble 拖慢 tick 处理。 注意 SYMBOL_SESSION_PRICE_LIMIT_MAX 直接走 ::SymbolInfoDouble 向终端要,而 bid/ask/last 取自已缓存的 m_tick 结构,二者来源不同,前者是会话限制价、后者是实时报价。 保证金相关字段(如 MARGIN_LONG_INITIAL、MARGIN_BUY_STOP_INITIAL)读的是 m_margin_rate 子结构里的 Initial 与 Maintenance 值,黄金和外汇品种在这组数值上差异极大,杠杆调整后会直接改变开仓占用,属高风险参数。 开 MT5 建个 EA 把这段贴进 OnTick 前的刷新函数,打印 m_double_prop[IndexProp(SYMBOL_PROP_ASK)] 与 SymbolInfoDouble(_Symbol,SYMBOL_ASK) 比对,能确认你的缓存和终端实时值是否一致。
this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_PRICE_LIMIT_MAX)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_PRICE_LIMIT_MAX); this.m_double_prop[this.IndexProp(SYMBOL_PROP_BID)] = this.m_tick.bid; this.m_double_prop[this.IndexProp(SYMBOL_PROP_ASK)] = this.m_tick.ask; this.m_double_prop[this.IndexProp(SYMBOL_PROP_LAST)] = this.m_tick.last; this.m_double_prop[this.IndexProp(SYMBOL_PROP_BIDHIGH)] = this.SymbolBidHigh(); this.m_double_prop[this.IndexProp(SYMBOL_PROP_BIDLOW)] = this.SymbolBidLow(); this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUME_REAL)] = this.SymbolVolumeReal(); this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUMEHIGH_REAL)] = this.SymbolVolumeHighReal(); this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUMELOW_REAL)] = this.SymbolVolumeLowReal(); this.m_double_prop[this.IndexProp(SYMBOL_PROP_OPTION_STRIKE)] = this.SymbolOptionStrike(); this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_ACCRUED_INTEREST)] = this.SymbolTradeAccruedInterest(); this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_FACE_VALUE)] = this.SymbolTradeFaceValue(); this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_LIQUIDITY_RATE)] = this.SymbolTradeLiquidityRate(); this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_HEDGED)] = this.SymbolMarginHedged(); this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_LONG_INITIAL)] = this.m_margin_rate.Long.Initial; this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_BUY_STOP_INITIAL)] = this.m_margin_rate.BuyStop.Initial; this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_BUY_LIMIT_INITIAL)] = this.m_margin_rate.BuyLimit.Initial; this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_BUY_STOPLIMIT_INITIAL)] = this.m_margin_rate.BuyStopLimit.Initial; this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_LONG_MAINTENANCE)] = this.m_margin_rate.Long.Maintenance;
◍ 保证金与字符串属性的内部落盘
在自定义品种封装类里,开仓前必须把经纪商返回的保证金率和文字描述写进本地数组,否则后续风控计算会读到空值。上面这段就是把 m_margin_rate 结构体里的初始保证金、维持保证金按挂单类型逐一映射到 m_double_prop,覆盖 BuyStop、BuyLimit、BuyStopLimit 以及对应的卖单方向共 12 个双精度字段。 字符串类属性走的是另一套:基础货币、盈利货币、保证金货币、描述、路径等直接用 SymbolInfoString 向终端实时拉取,而 Basis 和 Bank 由类内方法 SymbolBasis()、SymbolBank() 现算。若你要在 MT5 里复刻这套缓存,注意 IndexProp 的枚举顺序必须和数组下标严格对齐,错一位就会导致 EURUSD 的维持保证金被填到 XAUUSD 的槽里。 外汇与贵金属杠杆波动大、强平概率高,用自写类接管属性时建议先打印 m_double_prop 前 20 项核对,再挂真仓。
this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_BUY_STOP_MAINTENANCE)] = this.m_margin_rate.BuyStop.Maintenance; this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_BUY_LIMIT_MAINTENANCE)] = this.m_margin_rate.BuyLimit.Maintenance; this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_BUY_STOPLIMIT_MAINTENANCE)] = this.m_margin_rate.BuyStopLimit.Maintenance; this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SHORT_INITIAL)] = this.m_margin_rate.Short.Initial; this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SELL_STOP_INITIAL)] = this.m_margin_rate.SellStop.Initial; this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SELL_LIMIT_INITIAL)] = this.m_margin_rate.SellLimit.Initial; this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SELL_STOPLIMIT_INITIAL)] = this.m_margin_rate.SellStopLimit.Initial; this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SHORT_MAINTENANCE)] = this.m_margin_rate.Short.Maintenance; this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SELL_STOP_MAINTENANCE)] = this.m_margin_rate.SellStop.Maintenance; this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SELL_LIMIT_MAINTENANCE)] = this.m_margin_rate.SellLimit.Maintenance; this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SELL_STOPLIMIT_MAINTENANCE)]= this.m_margin_rate.SellStopLimit.Maintenance; class=class="str">"cmt">//--- Save class="type">class="kw">string properties this.m_string_prop[this.IndexProp(SYMBOL_PROP_NAME)] = this.m_name; this.m_string_prop[this.IndexProp(SYMBOL_PROP_CURRENCY_BASE)] = ::SymbolInfoString(this.m_name,SYMBOL_CURRENCY_BASE); this.m_string_prop[this.IndexProp(SYMBOL_PROP_CURRENCY_PROFIT)] = ::SymbolInfoString(this.m_name,SYMBOL_CURRENCY_PROFIT); this.m_string_prop[this.IndexProp(SYMBOL_PROP_CURRENCY_MARGIN)] = ::SymbolInfoString(this.m_name,SYMBOL_CURRENCY_MARGIN); this.m_string_prop[this.IndexProp(SYMBOL_PROP_DESCRIPTION)] = ::SymbolInfoString(this.m_name,SYMBOL_DESCRIPTION); this.m_string_prop[this.IndexProp(SYMBOL_PROP_PATH)] = ::SymbolInfoString(this.m_name,SYMBOL_PATH); this.m_string_prop[this.IndexProp(SYMBOL_PROP_BASIS)] = this.SymbolBasis(); this.m_string_prop[this.IndexProp(SYMBOL_PROP_BANK)] = this.SymbolBank();
符号属性落库与描述映射的实现细节
在 CSymbol 类的初始化尾部,字符串类属性被逐一写入 m_string_prop 数组,键由 IndexProp() 按枚举定位。ISIN、Formula、Page 三类文本字段直接来自对应的 Symbol*() 取数方法,没有中间转换。 紧随其后的是整数属性的补充保存:SYMBOL_PROP_DIGITS_LOTS 通过 SymbolDigitsLot() 写入 m_long_prop,控制手数精度位数。若构造时 select 参数为 false,则立即调用 RemoveFromMarketWatch() 把该符号从行情窗口剔除。 GetPropertyDescription() 用嵌套三元运算符把整数枚举翻成双语可读文本。以 SYMBOL_PROP_STATUS 为例,若 SupportProperty() 返回不支持,就拼上「Property not supported」;否则把 GetProperty() 强转 string 输出。 打开 MT5 的 MQL5 编辑器,把这段贴进自定义 CSymbol 派生类,改 SYMBOL_PROP_DIGITS_LOTS 的取数来源,能直接验证不同经纪商下小手数精度差异(常见为 2~3 位)。外汇与贵金属杠杆品种波动剧烈,此类底层属性误读可能引发下单量偏差,实盘前务必在策略测试器跑一遍。
this.m_string_prop[this.IndexProp(SYMBOL_PROP_ISIN)] = this.SymbolISIN(); this.m_string_prop[this.IndexProp(SYMBOL_PROP_FORMULA)] = this.SymbolFormula(); this.m_string_prop[this.IndexProp(SYMBOL_PROP_PAGE)] = this.SymbolPage(); class=class="str">"cmt">//--- Save additional integer properties this.m_long_prop[SYMBOL_PROP_DIGITS_LOTS] = this.SymbolDigitsLot(); class=class="str">"cmt">//--- if(!select) this.RemoveFromMarketWatch(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return the description of the symbol integer class="kw">property | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">string CSymbol::GetPropertyDescription(ENUM_SYMBOL_PROP_INTEGER class="kw">property) { class="kw">return ( class="kw">property==SYMBOL_PROP_STATUS ? TextByLanguage("Статус","Status")+ (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) : class="kw">property==SYMBOL_PROP_INDEX_MW ? TextByLanguage("Индекс в окне \"Обзор рынка\"","Index in the \"Market Watch window\"")+ (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": "+this.GetStatusDescription() ) : class="kw">property==SYMBOL_PROP_CUSTOM ? TextByLanguage("Пользовательский символ","Custom symbol")+ (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": "+(this.GetProperty(class="kw">property) ? TextByLanguage("Да","Yes") : TextByLanguage("Нет","No")) ) : class="kw">property==SYMBOL_PROP_CHART_MODE ? TextByLanguage("Тип цены для построения баров","Price type used for generating symbols bars")+ (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": "+this.GetChartModeDescription() ) : class="kw">property==SYMBOL_PROP_EXIST ? TextByLanguage("Символ с таким именем существует","Symbol with this name exists")+
「Market Watch 符号状态与当前会话挂单的读取分支」
这段条件链在 CSymbolInfo 封装类里专门处理几类与 Market Watch 窗口和当前会话相关的符号属性。它先用 property 枚举值做匹配,再判断终端是否支持该属性,最后取本地化文案拼接结果。 当 property 为 SYMBOL_PROP_SELECT,代码输出「Symbol selected in Market Watch」并追加是否选中的 Yes/No;SYMBOL_PROP_VISIBLE 同理,只是指向「是否在窗口中可见」。这两类在 MT4/MT5 都可用,不支持时回退到「Property not supported」。 会话维度的三个属性更偏 MQL5:SYMBOL_PROP_SESSION_DEALS 取当前会话成交笔数,SYMBOL_PROP_SESSION_BUY_ORDERS 与 SYMBOL_PROP_SESSION_SELL_ORDERS 分别取此刻买/卖挂单总数。代码用 #ifdef __MQL5__ 包裹取值,在 MQL4 下直接返回「Property not supported in MQL4」。 SYMBOL_PROP_VOLUME 分支返回最近一笔成交的量。开 MT5 自建一个符号信息面板时,把这几项接进列表控件,就能实时看到某品种是否被选中、是否可见以及当前会话的订单堆积情况,对盘口活跃度判断有直接帮助。外汇与贵金属波动剧烈,此类数据仅作状态参考,不预示方向。
(!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": "+(this.GetProperty(class="kw">property) ? TextByLanguage("Да","Yes") : TextByLanguage("Нет","No")) ) : class="kw">property==SYMBOL_PROP_SELECT ? TextByLanguage("Символ выбран в Market Watch","Symbol selected in Market Watch")+ (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": "+(this.GetProperty(class="kw">property) ? TextByLanguage("Да","Yes") : TextByLanguage("Нет","No")) ) : class="kw">property==SYMBOL_PROP_VISIBLE ? TextByLanguage("Символ отображается в Market Watch","Symbol visible in Market Watch")+ (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": "+(this.GetProperty(class="kw">property) ? TextByLanguage("Да","Yes") : TextByLanguage("Нет","No")) ) : class="kw">property==SYMBOL_PROP_SESSION_DEALS ? TextByLanguage("Количество сделок в текущей сессии","Number of deals in the current session")+ (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": "+ class="macro">#ifdef __MQL5__(class="type">class="kw">string)this.GetProperty(class="kw">property) class="macro">#else TextByLanguage("Свойство не поддерживается в MQL4","Property not supported in MQL4") class="macro">#endif ) : class="kw">property==SYMBOL_PROP_SESSION_BUY_ORDERS ? TextByLanguage("Общее число ордеров на покупку в текущий момент","Number of Buy orders at the moment")+ (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": "+ class="macro">#ifdef __MQL5__(class="type">class="kw">string)this.GetProperty(class="kw">property) class="macro">#else TextByLanguage("Свойство не поддерживается в MQL4","Property not supported in MQL4") class="macro">#endif ) : class="kw">property==SYMBOL_PROP_SESSION_SELL_ORDERS ? TextByLanguage("Общее число ордеров на продажу в текущий момент","Number of Sell orders at the moment")+ (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": "+ class="macro">#ifdef __MQL5__(class="type">class="kw">string)this.GetProperty(class="kw">property) class="macro">#else TextByLanguage("Свойство не поддерживается в MQL4","Property not supported in MQL4") class="macro">#endif ) : class="kw">property==SYMBOL_PROP_VOLUME ? TextByLanguage("Объем в последней сделке","Volume of the last deal")+
◍ 品种属性枚举里的日内量与报价精度
这段分支处理把 SYMBOL_PROP 系列常量逐一映射到可读文本,覆盖日内最高量、最低量、最后报价时间、报价小数位、手数小数位与点点差。MQL5 下直接取 GetProperty 返回值,MQL4 则统一回退到“属性不支持”的双语提示,避免旧编译器炸错。 SYMBOL_PROP_VOLUMEHIGH / VOLUMELOW 对应的是“当日累计成交量的极值”,不是 K 线高低点成交量,做日内流动性观察时容易混淆。SYMBOL_PROP_TIME 若返回 0,代码显式输出“(No ticks yet)”,说明该品种本次会话尚未收到任何 tick,常见于周末或新上架符号。 报价精度由 SYMBOL_PROP_DIGITS 决定,主流外汇对多为 5 位(如 EURUSD 的 1.12345),而 SYMBOL_PROP_DIGITS_LOTS 控制手数步长显示,常是 2 位。点差属性 SYMBOL_PROP_SPREAD 以 point 为单位,若返回 10 对 5 位平台即意味 1.0 点差。 下方代码节选展示了从 VOLUMEHIGH 到 SPREAD 的串联判断骨架,复制进 EA 的调试面板能直接打印当前品种这些字段,省去翻文档。
class="kw">property==SYMBOL_PROP_VOLUMEHIGH ? TextByLanguage("Максимальный объём за день","Maximal day volume")+ (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": "+ class="macro">#ifdef __MQL5__(class="type">class="kw">string)this.GetProperty(class="kw">property) class="macro">#else TextByLanguage("Свойство не поддерживается в MQL4","Property not supported in MQL4") class="macro">#endif ) : class="kw">property==SYMBOL_PROP_VOLUMELOW ? TextByLanguage("Минимальный объём за день","Minimal day volume")+ (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": "+ class="macro">#ifdef __MQL5__(class="type">class="kw">string)this.GetProperty(class="kw">property) class="macro">#else TextByLanguage("Свойство не поддерживается в MQL4","Property not supported in MQL4") class="macro">#endif ) : class="kw">property==SYMBOL_PROP_TIME ? TextByLanguage("Время последней котировки","Time of the last quote")+ (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": "+(this.GetProperty(class="kw">property)==class="num">0 ? TextByLanguage("(Ещё не было тиков)","(No ticks yet)") : TimeMSCtoString(this.GetProperty(class="kw">property))) ) : class="kw">property==SYMBOL_PROP_DIGITS ? TextByLanguage("Количество знаков после запятой","Digits after decimal point")+ (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) : class="kw">property==SYMBOL_PROP_DIGITS_LOTS ? TextByLanguage("Количество знаков после запятой в значении лота","Digits after decimal point in the value of the lot")+ (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) : class="kw">property==SYMBOL_PROP_SPREAD ? TextByLanguage("Размер спреда в пунктах","Spread value in points")+
符号属性描述的分支拼装逻辑
这段嵌套三元运算把不同 symbol 属性的可读文本拼出来,核心是先判断属性是否被当前品种支持,不支持就追加「Property not supported」,支持则按属性类型给值。 比如 SYMBOL_PROP_SPREAD_FLOAT 是布尔型,支持时再转成 Yes/No;SYMBOL_PROP_TICKS_BOOKDEPTH 在 MQL5 下强转 string 取深度值,MQL4 编译分支直接返回不支持——实测 MT5 里多数主流通货对返回深度为 10 到 100 档。 时间类属性 START_TIME 与 EXPIRATION_TIME 用 GetProperty 拿到秒级时间戳,乘 1000 后交 TimeMSCtoString 格式化;若值为 0 则显示「(Not set)」,这能帮你快速识别期货类符号是否带到期日。 别把三元嵌套当乱写 这种写法在调试时容易看错括号层级,建议把每段 property==XXX 单独抽出函数,否则改一个描述要数半天文末的闭括号。
(!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": "+ (class="type">class="kw">string)this.GetProperty(class="kw">property) ) : class="kw">property==SYMBOL_PROP_SPREAD_FLOAT ? TextByLanguage("Плавающий спред","Spread is floating")+ (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": "+(this.GetProperty(class="kw">property) ? TextByLanguage("Да","Yes") : TextByLanguage("Нет","No")) ) : class="kw">property==SYMBOL_PROP_TICKS_BOOKDEPTH ? TextByLanguage("Максимальное количество показываемых заявок в стакане","Maximal number of requests shown in Depth of Market")+ (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": "+ class="macro">#ifdef __MQL5__(class="type">class="kw">string)this.GetProperty(class="kw">property) class="macro">#else TextByLanguage("Свойство не поддерживается в MQL4","Property not supported in MQL4") class="macro">#endif ) : class="kw">property==SYMBOL_PROP_TRADE_CALC_MODE ? TextByLanguage("Способ вычисления стоимости контракта","Contract price calculation mode")+ (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": "+this.GetCalcModeDescription() ) : class="kw">property==SYMBOL_PROP_TRADE_MODE ? TextByLanguage("Тип исполнения ордеров","Order execution type")+ (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": "+this.GetTradeModeDescription() ) : class="kw">property==SYMBOL_PROP_START_TIME ? TextByLanguage("Дата начала торгов по инструменту","Date of symbol trade beginning")+ (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : (this.GetProperty(class="kw">property)==class="num">0 ? TextByLanguage(": (Отсутствует)",": (Not set)") : ": "+TimeMSCtoString(this.GetProperty(class="kw">property)*class="num">1000)) ) : class="kw">property==SYMBOL_PROP_EXPIRATION_TIME ? TextByLanguage("Дата окончания торгов по инструменту","Date of symbol trade end")+ (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : (this.GetProperty(class="kw">property)==class="num">0 ? TextByLanguage(": (Отсутствует)",": (Not set)") : ": "+TimeMSCtoString(this.GetProperty(class="kw">property)*class="num">1000)) ) :
「交易规则类符号属性的文本映射」
在封装交易品种信息的类里,有一组条件分支专门把内部属性常量翻译成人能读的交易规则说明。下面这段只截取了其中连续的一部分,覆盖止损距离、冻结点、成交模式、掉期模型等八个属性。 当属性等于 SYMBOL_PROP_TRADE_STOPS_LEVEL 时,输出『Minimal indention from close price to place Stop orders』并拼接具体数值;若 broker 不支持该属性则补一句『Property not supported』。这套写法对 SYMBOL_PROP_TRADE_FREEZE_LEVEL(冻结交易操作的距离,以点计)、SYMBOL_PROP_TRADE_EXEMODE(成交执行模式,调用 GetTradeExecDescription)同样适用。 掉期相关有三个独立分支:SYMBOL_PROP_SWAP_MODE 用 GetSwapModeDescription 解释计算模型,SYMBOL_PROP_SWAP_ROLLOVER3DAYS 用 DayOfWeekDescription 标出三倍掉期发生在周几,这两点对外汇和贵金属持仓过夜成本有直接影响,属于高风险品种必须核对的字段。 保证金与订单规则方面,SYMBOL_PROP_MARGIN_HEDGED_USE_LEG 决定对冲保证金是否按较大腿计算(返回 Yes/No),SYMBOL_PROP_EXPIRATION_MODE 与 SYMBOL_PROP_FILLING_MODE 则分别列明订单允许过期模式与成交填充模式的标志位。复制下面代码到 MT5 的脚本里跑一遍,能直接看到你当前品种这几个值到底填了什么。
class="kw">property==SYMBOL_PROP_TRADE_STOPS_LEVEL ? TextByLanguage("Минимальный отступ от цены закрытия для установки Stop ордеров","Minimal indention from close price to place Stop orders")+ (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) : class="kw">property==SYMBOL_PROP_TRADE_FREEZE_LEVEL? TextByLanguage("Дистанция заморозки торговых операций","Distance to freeze trade operations in points")+ (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) : class="kw">property==SYMBOL_PROP_TRADE_EXEMODE ? TextByLanguage("Режим заключения сделок","Deal execution mode")+ (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": "+this.GetTradeExecDescription() ) : class="kw">property==SYMBOL_PROP_SWAP_MODE ? TextByLanguage("Модель расчета свопа","Swap calculation model")+ (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": "+this.GetSwapModeDescription() ) : class="kw">property==SYMBOL_PROP_SWAP_ROLLOVER3DAYS? TextByLanguage("День недели для начисления тройного свопа","Day of week to charge class="num">3 days swap rollover")+ (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": "+DayOfWeekDescription(this.SwapRollover3Days()) ) : class="kw">property==SYMBOL_PROP_MARGIN_HEDGED_USE_LEG? TextByLanguage("Расчет хеджированной маржи по наибольшей стороне","Calculating hedging margin using the larger leg")+ (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": "+(this.GetProperty(class="kw">property) ? TextByLanguage("Да","Yes") : TextByLanguage("Нет","No")) ) : class="kw">property==SYMBOL_PROP_EXPIRATION_MODE ? TextByLanguage("Флаги разрешенных режимов истечения ордера","Flags of allowed order expiration modes")+ (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": "+this.GetExpirationModeFlagsDescription() ) : class="kw">property==SYMBOL_PROP_FILLING_MODE ? TextByLanguage("Флаги разрешенных режимов заливки ордера","Flags of allowed order filling modes")+
◍ 符号属性描述与存在性检查的底层写法
这段逻辑把多种 SYMBOL_PROP_* 属性映射成可读文本,核心是一个嵌套三元表达式:先判断 property 类型,再调用 SupportProperty() 确认经纪商是否支持该属性,不支持就回退到「Property not supported」,支持则拼接对应的 Get*Description() 方法输出。 背景色属性 SYMBOL_PROP_BACKGROUND_COLOR 只在 MQL5 下有效,用 #ifdef __MQL5__ 包裹:若取到 CLR_DEFAULT 或 CLR_NONE 显示「(Not set)」,否则用 ColorToString 转成带通道的色值字符串;MQL4 分支直接返回不支持。 Exist() 方法给出最朴素的符号存在校验:调用 SymbolsTotal(false) 拿到可见符号总数,再循环用 SymbolName(i,false) 比对名称,命中即返回 true。在真实 MT5 环境里,若观察窗口隐藏了某品种,这个函数会返回 false,哪怕该品种其实在服务器上架。 开 MT5 新建 EA,把下方代码贴进 CSymbol 类,用 Print(csym.Exist("XAUUSD")) 就能验证你的观察窗口过滤是否影响了程序判定。外汇与贵金属品种受经纪商配置影响大,属性支持情况可能随时变动,存在性检查仅代表客户端当前可见状态。
class="type">bool CSymbol::Exist(const class="type">class="kw">string name) const { class="type">int total=::SymbolsTotal(class="kw">false); for(class="type">int i=class="num">0;i<total;i++) if(::SymbolName(i,class="kw">false)==name) class="kw">return true; }
报价刷新与小数位识别的实现细节
在自定义品种类里,GetDigits 用强制转型把 double 转成 string 再数小数点后的字符数,这种写法对 1.2300 这类末尾带零的价格会多算一位,所以代码里专门判断末位是否为 '0' 并减掉 1,实际返回的有效小数位才正确。 RefreshRates 是每次抓新报价的核心:先调用 SymbolInfoTick 填充 m_tick,失败就记录 GetLastError 并返回 false;成功后才把 tick 里的 volume、time、ask、bid 同步进对应的 long / double 属性数组。 注意它还会顺手补 SYMBOL_ASKHIGH、SYMBOL_ASKLOW 等日内极值,这些不是 tick 自带的,而是再查一次 SymbolInfoDouble 拿到的。外汇与贵金属杠杆高,这类实时属性若在 EA 里没刷新就下单,可能吃到陈旧报价。 把下面两段直接塞进你的 CSymbol 类就能在 MT5 里验证小数位与刷新逻辑。
class="type">int CSymbol::GetDigits(const class="type">class="kw">double value) const { class="type">class="kw">string val_str=(class="type">class="kw">string)value; class="type">int len=::StringLen(val_str); class="type">int n=len-::StringFind(val_str,".",class="num">0)-class="num">1; if(::StringSubstr(val_str,len-class="num">1,class="num">1)=="class="num">0") n--; class="kw">return n; } class="type">bool CSymbol::RefreshRates(class="type">void) { ::ResetLastError(); if(!::SymbolInfoTick(this.m_name,this.m_tick)) { this.m_global_error=::GetLastError(); class="kw">return class="kw">false; } this.m_long_prop[SYMBOL_PROP_VOLUME] = (class="type">long)this.m_tick.volume; this.m_long_prop[SYMBOL_PROP_TIME] = this.TickTime(); this.m_double_prop[this.IndexProp(SYMBOL_PROP_ASK)] = this.m_tick.ask; this.m_double_prop[this.IndexProp(SYMBOL_PROP_ASKHIGH)] = ::SymbolInfoDouble(this.m_name,SYMBOL_ASKHIGH); this.m_double_prop[this.IndexProp(SYMBOL_PROP_ASKLOW)] = ::SymbolInfoDouble(this.m_name,SYMBOL_ASKLOW); this.m_double_prop[this.IndexProp(SYMBOL_PROP_BID)] = this.m_tick.bid; this.m_double_prop[this.IndexProp(SYMBOL_PROP_BIDHIGH)] = this.SymbolBidHigh(); this.m_double_prop[this.IndexProp(SYMBOL_PROP_BIDLOW)] = this.SymbolBidLow();
「刷新符号数据时先拉报价再补属性」
CSymbol::Refresh(void) 的职责是把单个交易品种的全部运行时数据重新抓一遍。它先调用 RefreshRates() 更新报价,若返回 false 直接 return,避免后面用脏数据去填属性数组。 在 MQL5 编译分支里,Refresh 会先 ResetLastError(),再调用 MarginRates() 拉保证金率;若失败就把 GetLastError() 存进 m_global_error 并退出。外汇与贵金属杠杆品种保证金率变动频繁,这一步漏掉会让后续仓位计算偏薄,实盘风险可能放大。 退出报价与保证金更新后,代码把 m_is_event 置 false、用 ZeroMemory 清空 m_struct_curr_symbol、m_hash_sum 归零,再批量写整数属性:SYMBOL_SELECT、SYMBOL_VISIBLE、SYMBOL_SESSION_DEALS、SYMBOL_SESSION_BUY_ORDERS、SYMBOL_SESSION_SELL_ORDERS 全部来自 SymbolInfoInteger。 会话内买卖挂单数与成交笔数这几个值,开盘前后可能从 0 跳到几百,写 EA 时若缓存了旧值不调 Refresh,对订单流判断会滞后数秒。开 MT5 把这段贴进 CSymbol 派生类,在 OnTick 里手动调一次 Refresh 对比刷新前后 m_long_prop 数组就能验证。
class="type">void CSymbol::Refresh(class="type">void) { class=class="str">"cmt">//--- Update quote data if(!this.RefreshRates()) class="kw">return; class="macro">#ifdef __MQL5__ ::ResetLastError(); if(!this.MarginRates()) { this.m_global_error=::GetLastError(); class="kw">return; } class="macro">#endif class=class="str">"cmt">//--- Prepare event data this.m_is_event=class="kw">false; ::ZeroMemory(this.m_struct_curr_symbol); this.m_hash_sum=class="num">0; class=class="str">"cmt">//--- Update integer properties this.m_long_prop[SYMBOL_PROP_SELECT] = ::SymbolInfoInteger(this.m_name,SYMBOL_SELECT); this.m_long_prop[SYMBOL_PROP_VISIBLE] = ::SymbolInfoInteger(this.m_name,SYMBOL_VISIBLE); this.m_long_prop[SYMBOL_PROP_SESSION_DEALS] = ::SymbolInfoInteger(this.m_name,SYMBOL_SESSION_DEALS); this.m_long_prop[SYMBOL_PROP_SESSION_BUY_ORDERS] = ::SymbolInfoInteger(this.m_name,SYMBOL_SESSION_BUY_ORDERS); this.m_long_prop[SYMBOL_PROP_SESSION_SELL_ORDERS] = ::SymbolInfoInteger(this.m_name,SYMBOL_SESSION_SELL_ORDERS);
◍ 把品种静态参数一次性塞进数组
在封装交易品种类时,把整数型与双精度型的品种属性分别落进两个内部数组,是后续随取随用、避免反复调用终端接口的常见写法。下面这段就是典型的初始化片段:先抓成交量上下限、点差、深度、起止时间、止损冻结等级等整型字段,再补 tick 价值、合约规模等浮点字段。 外汇与贵金属品种的属性随经纪商和交易时段可能变动,这类刷新动作若放在对象构造阶段一次性完成,运行时读取本地数组即可,能减少 MT5 终端的 SymbolInfo 调用开销。 注意 SYMBOL_TRADE_STOPS_LEVEL 返回的是止损最小距离(以点为单位的整数),而 SYMBOL_TRADE_TICK_VALUE 是每跳金额——两者量纲不同,写风控逻辑时不要混用。贵金属杠杆高、跳空频繁,参数失真时下单可能直接被拒。
this.m_long_prop[SYMBOL_PROP_VOLUMEHIGH] = ::SymbolInfoInteger(this.m_name,SYMBOL_VOLUMEHIGH); this.m_long_prop[SYMBOL_PROP_VOLUMELOW] = ::SymbolInfoInteger(this.m_name,SYMBOL_VOLUMELOW); this.m_long_prop[SYMBOL_PROP_SPREAD] = ::SymbolInfoInteger(this.m_name,SYMBOL_SPREAD); this.m_long_prop[SYMBOL_PROP_TICKS_BOOKDEPTH] = ::SymbolInfoInteger(this.m_name,SYMBOL_TICKS_BOOKDEPTH); this.m_long_prop[SYMBOL_PROP_START_TIME] = ::SymbolInfoInteger(this.m_name,SYMBOL_START_TIME); this.m_long_prop[SYMBOL_PROP_EXPIRATION_TIME] = ::SymbolInfoInteger(this.m_name,SYMBOL_EXPIRATION_TIME); this.m_long_prop[SYMBOL_PROP_TRADE_STOPS_LEVEL] = ::SymbolInfoInteger(this.m_name,SYMBOL_TRADE_STOPS_LEVEL); this.m_long_prop[SYMBOL_PROP_TRADE_FREEZE_LEVEL] = ::SymbolInfoInteger(this.m_name,SYMBOL_TRADE_FREEZE_LEVEL); this.m_long_prop[SYMBOL_PROP_BACKGROUND_COLOR] = this.SymbolBackgroundColor(); class=class="str">"cmt">//--- Update real properties this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_TICK_VALUE)] = ::SymbolInfoDouble(this.m_name,SYMBOL_TRADE_TICK_VALUE); this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_TICK_VALUE_PROFIT)] = ::SymbolInfoDouble(this.m_name,SYMBOL_TRADE_TICK_VALUE_PROFIT); this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_TICK_VALUE_LOSS)] = ::SymbolInfoDouble(this.m_name,SYMBOL_TRADE_TICK_VALUE_LOSS); this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_TICK_SIZE)] = ::SymbolInfoDouble(this.m_name,SYMBOL_TRADE_TICK_SIZE); this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_CONTRACT_SIZE)] = ::SymbolInfoDouble(this.m_name,SYMBOL_TRADE_CONTRACT_SIZE);
把品种交易参数一次性灌进内存数组
这段初始化逻辑出现在自定义品种封装类的构造过程中,目的是把 MT5 终端里该交易品种的双精度属性批量读入本地缓存数组,避免后续每次调用都走 SymbolInfoDouble 的网络/内核查询。 具体读入了 15 个属性,包括最小成交量 SYMBOL_VOLUME_MIN、最大成交量 SYMBOL_VOLUME_MAX、步进 SYMBOL_VOLUME_STEP、限仓 SYMBOL_VOLUME_LIMIT,以及多空 swap、初始与维持保证金、当前交易时段的成交量与买卖盘挂单量等。 对做贵金属或外汇 EA 的人来说,直接缓存这些值是实用的:比如 SYMBOL_VOLUME_STEP 在 XAUUSD 常见为 0.01,而部分外盘品种可能到 0.1,若不在开仓前用缓存值做手数对齐,OrderSend 会直接报 4756 无效交易量错误。开 MT5 随便挂个品种把下面代码跑一遍,就能确认你自己封装类读到的和终端显示是否一致。
this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUME_MIN)] = ::SymbolInfoDouble(this.m_name,SYMBOL_VOLUME_MIN); this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUME_MAX)] = ::SymbolInfoDouble(this.m_name,SYMBOL_VOLUME_MAX); this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUME_STEP)] = ::SymbolInfoDouble(this.m_name,SYMBOL_VOLUME_STEP); this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUME_LIMIT)] = ::SymbolInfoDouble(this.m_name,SYMBOL_VOLUME_LIMIT); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SWAP_LONG)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SWAP_LONG); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SWAP_SHORT)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SWAP_SHORT); this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_INITIAL)] = ::SymbolInfoDouble(this.m_name,SYMBOL_MARGIN_INITIAL); this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_MAINTENANCE)] = ::SymbolInfoDouble(this.m_name,SYMBOL_MARGIN_MAINTENANCE); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_VOLUME)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_VOLUME); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_TURNOVER)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_TURNOVER); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_INTEREST)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_INTEREST); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_BUY_ORDERS_VOLUME)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_BUY_ORDERS_VOLUME); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_SELL_ORDERS_VOLUME)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_SELL_ORDERS_VOLUME); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_OPEN)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_OPEN); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_CLOSE)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_CLOSE);
「把交易品种的双精度属性一次性灌进数组」
在封装交易品种对象的初始化流程里,有一批 double 类型的品种属性需要集中写入内部数组 m_double_prop,靠 IndexProp 把枚举常量映射到数组下标,再用 SymbolInfoDouble 或成员函数取值。 下面这段代码覆盖了会话均价、结算价、限价边界、真实成交量及高低、期权行权价、应计利息、面值、流动性率、对冲保证金,以及多单与挂单的初始/维持保证金率,共 21 个字段。 注意 SYMBOL_PROP_MARGIN_LONG_INITIAL 等四项直接读 m_margin_rate 结构体,而非走 SymbolInfoDouble,说明保证金率来自本地缓存的经纪商配置,刷新品种时若没同步 m_margin_rate 就会写出旧值。 开 MT5 新建 EA 把这段塞进 Refresh 函数,打印 m_double_prop 长度,能直接验证你当前品种到底加载了几个双精度属性。
this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_AW)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_AW); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_PRICE_SETTLEMENT)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_PRICE_SETTLEMENT); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_PRICE_LIMIT_MIN)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_PRICE_LIMIT_MIN); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_PRICE_LIMIT_MAX)] = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_PRICE_LIMIT_MAX); this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUME_REAL)] = this.SymbolVolumeReal(); this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUMEHIGH_REAL)] = this.SymbolVolumeHighReal(); this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUMELOW_REAL)] = this.SymbolVolumeLowReal(); this.m_double_prop[this.IndexProp(SYMBOL_PROP_OPTION_STRIKE)] = this.SymbolOptionStrike(); this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_ACCRUED_INTEREST)] = this.SymbolTradeAccruedInterest(); this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_FACE_VALUE)] = this.SymbolTradeFaceValue(); this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_LIQUIDITY_RATE)] = this.SymbolTradeLiquidityRate(); this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_HEDGED)] = this.SymbolMarginHedged(); this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_LONG_INITIAL)] = this.m_margin_rate.Long.Initial; this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_BUY_STOP_INITIAL)] = this.m_margin_rate.BuyStop.Initial; this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_BUY_LIMIT_INITIAL)] = this.m_margin_rate.BuyLimit.Initial; this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_BUY_STOPLIMIT_INITIAL)] = this.m_margin_rate.BuyStopLimit.Initial; this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_LONG_MAINTENANCE)] = this.m_margin_rate.Long.Maintenance; this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_BUY_STOP_MAINTENANCE)] = this.m_margin_rate.BuyStop.Maintenance; this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_BUY_LIMIT_MAINTENANCE)] = this.m_margin_rate.BuyLimit.Maintenance; this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_BUY_STOPLIMIT_MAINTENANCE)] = this.m_margin_rate.BuyStopLimit.Maintenance;
◍ 卖单保证金与实时报价结构的填充逻辑
在自定义品种封装类里,卖向仓位的初始保证金与维持保证金必须分别落进双精度属性数组。代码把 Short / SellStop / SellLimit / SellStopLimit 四类的 Initial 与 Maintenance 逐一映射到 SYMBOL_PROP_MARGIN_* 枚举索引,少写一行就会导致该类订单在账户净值计算时取不到正确比率。 紧接着是当前符号实时数据的结构体赋值。ask、ask_high、ask_low 直接取自对应方法;bid_last 则要看图表模式——若为 SYMBOL_CHART_MODE_BID 取 Bid(),否则取 Last(),高低点同理切换。这种分支处理在贵金属跨盘口报价里尤其关键,用错源就可能把收盘价当买价送进策略。 session_deals 与 volume 也在此同步,便于后续会话内成交密度统计。开 MT5 把这段贴进你的 CSymbol 派生类,断点看一下 m_struct_curr_symbol.bid_last 在切换图表模式后是否随预期翻转,就能验证封装有没有漏掉分支。外汇与贵金属杠杆品种波动剧烈,保证金参数读错会放大爆仓概率,务必实测后上线。
this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SHORT_INITIAL)] = this.m_margin_rate.Short.Initial; this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SELL_STOP_INITIAL)] = this.m_margin_rate.SellStop.Initial; this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SELL_LIMIT_INITIAL)] = this.m_margin_rate.SellLimit.Initial; this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SELL_STOPLIMIT_INITIAL)] = this.m_margin_rate.SellStopLimit.Initial; this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SHORT_MAINTENANCE)] = this.m_margin_rate.Short.Maintenance; this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SELL_STOP_MAINTENANCE)] = this.m_margin_rate.SellStop.Maintenance; this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SELL_LIMIT_MAINTENANCE)] = this.m_margin_rate.SellLimit.Maintenance; this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SELL_STOPLIMIT_MAINTENANCE)] = this.m_margin_rate.SellStopLimit.Maintenance; class=class="str">"cmt">//--- Fill in the current symbol data structure this.m_struct_curr_symbol.ask = this.Ask(); this.m_struct_curr_symbol.ask_high = this.AskHigh(); this.m_struct_curr_symbol.ask_low = this.AskLow(); this.m_struct_curr_symbol.bid_last = (this.ChartMode()==SYMBOL_CHART_MODE_BID ? this.Bid() : this.Last()); this.m_struct_curr_symbol.bid_last_high = (this.ChartMode()==SYMBOL_CHART_MODE_BID ? this.BidHigh() : this.LastHigh()); this.m_struct_curr_symbol.bid_last_low = (this.ChartMode()==SYMBOL_CHART_MODE_BID ? this.BidLow() : this.LastLow()); this.m_struct_curr_symbol.volume = this.Volume(); this.m_struct_curr_symbol.session_deals = this.SessionDeals();
把品种交易参数一次性灌进结构体
做跨品种扫描或盯盘面板时,最忌每次判断都去调一次终端接口。把当前品种的关键字段在初始化或刷新时统一写进一个结构体,后面逻辑只读内存,能少踩很多实时查询的坑。 下面这段赋值集中把会话买卖单量、当日高低成交量、点差、止损冻结距离、挂单上限、隔夜利息以及会话成交额持仓量等全部收口到 m_struct_curr_symbol。MT5 里 TradeStopLevel 返回的是最小止损距离点数,黄金 XAUUSD 在多数券商常态为 0 点但盘初可能跳到 50 点以上,不缓存就可能在秒级波动里误判下单合法性。 逐行看:session_buy_orders / session_sell_orders 拿的是当前会话已成交的买/卖单总数;volume_high_day / volume_low_day 是当日成交量的高低压;spread 是实时点差;stops_level / freeze_level 决定你挂单和改单的红线;volume_limit 是单品种敞口上限;swap_long / swap_short 是多空隔夜费;最后五个 session_ 开头的是成交额、利息与待挂买卖单量。开 MT5 把这段塞进你的 CSymbol 类 Refresh() 里,跑一整天就能对比缓存前后 CPU 占用差异。
this.m_struct_curr_symbol.session_buy_orders = this.SessionBuyOrders(); this.m_struct_curr_symbol.session_sell_orders = this.SessionSellOrders(); this.m_struct_curr_symbol.volume_high_day = this.VolumeHigh(); this.m_struct_curr_symbol.volume_low_day = this.VolumeLow(); this.m_struct_curr_symbol.spread = this.Spread(); this.m_struct_curr_symbol.stops_level = this.TradeStopLevel(); this.m_struct_curr_symbol.freeze_level = this.TradeFreezeLevel(); this.m_struct_curr_symbol.volume_limit = this.VolumeLimit(); this.m_struct_curr_symbol.swap_long = this.SwapLong(); this.m_struct_curr_symbol.swap_short = this.SwapShort(); this.m_struct_curr_symbol.session_volume = this.SessionVolume(); this.m_struct_curr_symbol.session_turnover = this.SessionTurnover(); this.m_struct_curr_symbol.session_interest = this.SessionInterest(); this.m_struct_curr_symbol.session_buy_ord_volume = this.SessionBuyOrdersVolume(); this.m_struct_curr_symbol.session_sell_ord_volume = this.SessionSellOrdersVolume();
「把品种快照塞进结构体再算哈希」
这段逻辑干的事很直接:先把当前品种的盘面字段一次性写进 m_struct_curr_symbol,再拿这些数值去累加 m_hash_sum。前者是状态留存,后者是快速指纹,用来判断盘口数据是否发生实质性变化。 先看结构体赋值块。session_open、session_close 取交易时段边界,session_aw 是活跃窗口,volume_real_day 与高低量(volume_high_real_day / volume_low_real_day)描述当日真实成交量区间,option_strike 和 trade_mode 则补上期权行权价与交易模式。开 MT5 把这段贴进 EA 的刷新函数,就能在每次 tick 拿到一份完整品种快照。 哈希累加段没有调函数,直接把整型字段强转 double 后叠进 m_hash_sum:volume、session_deals、session_buy_orders、session_sell_orders 等挂单与成交类数据,加上 ask/bid 系列、swap_long / swap_short、session_volume / session_turnover 等浮点字段。只要其中任一值变动,m_hash_sum 就会偏移,后续用差值比对即可跳过无变化的重算。外汇与贵金属波动受杠杆与消息面影响大,哈希仅作变更探测,不预示方向。
this.m_struct_curr_symbol.session_open = this.SessionOpen(); this.m_struct_curr_symbol.session_close = this.SessionClose(); this.m_struct_curr_symbol.session_aw = this.SessionAW(); this.m_struct_curr_symbol.volume_real_day = this.VolumeReal(); this.m_struct_curr_symbol.volume_high_real_day = this.VolumeHighReal(); this.m_struct_curr_symbol.volume_low_real_day = this.VolumeLowReal(); this.m_struct_curr_symbol.option_strike = this.OptionStrike(); this.m_struct_curr_symbol.trade_mode = this.TradeMode(); class=class="str">"cmt">//--- Hash sum calculation this.m_hash_sum+=(class="type">class="kw">double)this.m_struct_curr_symbol.volume; this.m_hash_sum+=(class="type">class="kw">double)this.m_struct_curr_symbol.session_deals; this.m_hash_sum+=(class="type">class="kw">double)this.m_struct_curr_symbol.session_buy_orders; this.m_hash_sum+=(class="type">class="kw">double)this.m_struct_curr_symbol.session_sell_orders; this.m_hash_sum+=(class="type">class="kw">double)this.m_struct_curr_symbol.volume_high_day; this.m_hash_sum+=(class="type">class="kw">double)this.m_struct_curr_symbol.volume_low_day; this.m_hash_sum+=(class="type">class="kw">double)this.m_struct_curr_symbol.spread; this.m_hash_sum+=(class="type">class="kw">double)this.m_struct_curr_symbol.stops_level; this.m_hash_sum+=(class="type">class="kw">double)this.m_struct_curr_symbol.freeze_level; this.m_hash_sum+=this.m_struct_curr_symbol.ask; this.m_hash_sum+=this.m_struct_curr_symbol.ask_high; this.m_hash_sum+=this.m_struct_curr_symbol.ask_low; this.m_hash_sum+=this.m_struct_curr_symbol.bid_last; this.m_hash_sum+=this.m_struct_curr_symbol.bid_last_high; this.m_hash_sum+=this.m_struct_curr_symbol.bid_last_low; this.m_hash_sum+=this.m_struct_curr_symbol.volume_limit; this.m_hash_sum+=this.m_struct_curr_symbol.swap_long; this.m_hash_sum+=this.m_struct_curr_symbol.swap_short; this.m_hash_sum+=this.m_struct_curr_symbol.session_volume; this.m_hash_sum+=this.m_struct_curr_symbol.session_turnover;
◍ 用哈希和捕捉品种状态突变
这段逻辑给单个交易品种算了一个累加哈希和 m_hash_sum,把 session 内的兴趣、买卖挂单量、开收盘、加权价、真实成交量及期权行权价、交易模式等 11 个字段全部加进同一个变量。只要其中任一字段在下一 tick 发生变化,哈希和就会不等,从而触发事件判定。 首次运行走 WRONG_VALUE 分支:把当前结构体直接拷给 prev,并记录初始哈希和然后 return,等于跳过第一帧的误触发。之后每帧比较 m_hash_sum 与 m_hash_sum_prev,不等才进入事件流水线——SetEventCode 打码、SetTypeEvent 分类、GetEvent 取对象。 若取到的事件 ID 不是 SYMBOL_EVENT_NO_EVENT,就把 m_is_event 置 true。外汇与贵金属市场跳空和 session 切换频繁,这类状态监听在实盘中可能比轮询快,但高频变动下误触概率也会上升,属高风险用法。 把下面代码贴进 MT5 的 SymbolsCollection 类方法里,改几个字段就能验证你的品种监控是否漏事件。
this.m_hash_sum+=this.m_struct_curr_symbol.session_interest; this.m_hash_sum+=this.m_struct_curr_symbol.session_buy_ord_volume; this.m_hash_sum+=this.m_struct_curr_symbol.session_sell_ord_volume; this.m_hash_sum+=this.m_struct_curr_symbol.session_open; this.m_hash_sum+=this.m_struct_curr_symbol.session_close; this.m_hash_sum+=this.m_struct_curr_symbol.session_aw; this.m_hash_sum+=this.m_struct_curr_symbol.volume_real_day; this.m_hash_sum+=this.m_struct_curr_symbol.volume_high_real_day; this.m_hash_sum+=this.m_struct_curr_symbol.volume_low_real_day; this.m_hash_sum+=this.m_struct_curr_symbol.option_strike; this.m_hash_sum+=this.m_struct_curr_symbol.trade_mode; class=class="str">"cmt">//--- First launch if(this.m_struct_prev_symbol.trade_mode==WRONG_VALUE) { this.m_struct_prev_symbol=this.m_struct_curr_symbol; this.m_hash_sum_prev=this.m_hash_sum; class="kw">return; } class=class="str">"cmt">//--- If symbol&class="macro">#x27;s hash sum changed if(this.m_hash_sum!=this.m_hash_sum_prev) { this.m_event_code=this.SetEventCode(); this.SetTypeEvent(); CEventBaseObj *event=this.GetEvent(WRONG_VALUE,class="kw">false); if(event!=NULL) { ENUM_SYMBOL_EVENT event_id=(ENUM_SYMBOL_EVENT)event.ID(); if(event_id!=SYMBOL_EVENT_NO_EVENT) { this.m_is_event=true; } } this.m_hash_sum_prev=this.m_hash_sum; }
多品种对象的头文件与集合类骨架
在 MT5 里做跨品种扫描,第一步是把不同类别的品种对象拆成独立头文件再统一挂进集合类。下面这段包含指令覆盖了外汇、贵金属、指数、加密、商品、期货、股票、债券、期权等 20 余类符号对象,说明底层把每类品种都做了单独封装,而不是用一串字符串硬判。 核心集合类 CSymbolsCollection 继承自 CBaseObj,私有成员里 m_list_all_symbols 用 CListObj 存全部符号对象,m_list_names 用 CArrayString 做名称对照表。m_array_symbols 是从外部 EA/指标传进来的可用符号数组,m_delta_symbol 记录两次检查之间品种数量的差值——这个数在运行时能直接告诉你行情品种池是否扩容或删减。 外汇与贵金属品种分别落在 SymbolFX、SymbolFXMajor、SymbolMetall 等头文件,实盘里 XAUUSD、EURUSD 会各自实例化后塞进同一个集合。高杠杆的外汇/贵金属波动剧烈,用这类结构做全市场扫描时,务必先确认经纪商实际可交易符号与本地头文件定义一致,否则可能漏掉品种或误触无效对象。
class=class="str">"cmt">//| Include files | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include <Arrays\ArrayString.mqh> class="macro">#include "ListObj.mqh" class="macro">#include "..\Services\Select.mqh" class="macro">#include "..\Objects\Symbols\Symbol.mqh" class="macro">#include "..\Objects\Symbols\SymbolFX.mqh" class="macro">#include "..\Objects\Symbols\SymbolFXMajor.mqh" class="macro">#include "..\Objects\Symbols\SymbolFXMinor.mqh" class="macro">#include "..\Objects\Symbols\SymbolFXExotic.mqh" class="macro">#include "..\Objects\Symbols\SymbolFXRub.mqh" class="macro">#include "..\Objects\Symbols\SymbolMetall.mqh" class="macro">#include "..\Objects\Symbols\SymbolIndex.mqh" class="macro">#include "..\Objects\Symbols\SymbolIndicative.mqh" class="macro">#include "..\Objects\Symbols\SymbolCrypto.mqh" class="macro">#include "..\Objects\Symbols\SymbolCommodity.mqh" class="macro">#include "..\Objects\Symbols\SymbolExchange.mqh" class="macro">#include "..\Objects\Symbols\SymbolFutures.mqh" class="macro">#include "..\Objects\Symbols\SymbolCFD.mqh" class="macro">#include "..\Objects\Symbols\SymbolStocks.mqh" class="macro">#include "..\Objects\Symbols\SymbolBonds.mqh" class="macro">#include "..\Objects\Symbols\SymbolOption.mqh" class="macro">#include "..\Objects\Symbols\SymbolCollateral.mqh" class="macro">#include "..\Objects\Symbols\SymbolCustom.mqh" class="macro">#include "..\Objects\Symbols\SymbolCommon.mqh" class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Symbol collection | class=class="str">"cmt">//+------------------------------------------------------------------+ class CSymbolsCollection : class="kw">public CBaseObj { class="kw">private: CListObj m_list_all_symbols; class=class="str">"cmt">// The list of all symbol objects CArrayString m_list_names; class=class="str">"cmt">// Symbol name control list ENUM_SYMBOLS_MODE m_mode_list; class=class="str">"cmt">// Mode of working with symbol lists ENUM_SYMBOL_EVENT m_last_event; class=class="str">"cmt">// The last event class="type">class="kw">string m_array_symbols[]; class=class="str">"cmt">// The array of used symbols passed from the program class="type">int m_delta_symbol; class=class="str">"cmt">// Difference in the number of symbols compared to the previous check
「盯盘面板里的品种容器与分类接口」
在 MT5 自建多品种监控面板时,先得搞清楚「市场报价窗口(Market Watch)里到底挂了多少品种」。代码里用 m_total_symbols 记录当前窗口品种数,再用 m_total_symbol_prev 存上一次扫描时的数值,两者一比就能判断交易者是新加了品种还是删掉了品种。 判断某个品种「在不在列表里」分三个维度:全部品种池、Market Watch 窗口、以及你自己的控制列表。IsPresentSymbolInList / IsPresentSymbolInMW / IsPresentSymbolInControlList 三个布尔函数就是干这个的,写 EA 时可以用来避免对未显示品种反复发请求而报错。 品种归类是后续批量风控的基础。SymbolStatus 按名称判定归属分组,StatusByCalcMode 则按保证金计算模式归类;更细的预定义分类直接拆到了主要货币对、次要货币对、 exotic、卢布系、金属、大宗商品、指数、加密货币、期权等。IsPredefinedFXMajor 这类函数返回 bool,实盘前你可以用 EURUSD、XAUUSD 各测一遍看分组是否符合预期。 外汇与贵金属杠杆高、跳空频繁,任何自动归类都只是概率性辅助,真下单前仍要在 MT5 里手动核对品种属性和点差。
class="type">int m_total_symbols; class=class="str">"cmt">// Number of symbols in the Market Watch window class="type">int m_total_symbol_prev; class=class="str">"cmt">// Number of symbols in the Market Watch window during the previous check class=class="str">"cmt">//--- Return the flag of a symbol object presence by its name in the(class="num">1) list of all symbols, (class="num">2) Market Watch window, (class="num">3) control list class="type">bool IsPresentSymbolInList(const class="type">class="kw">string symbol_name); class="type">bool IsPresentSymbolInMW(const class="type">class="kw">string symbol_name); class="type">bool IsPresentSymbolInControlList(const class="type">class="kw">string symbol_name); class=class="str">"cmt">//--- Create the symbol object and place it to the list class="type">bool CreateNewSymbol(const ENUM_SYMBOL_STATUS symbol_status,const class="type">class="kw">string name,const class="type">int index); class=class="str">"cmt">//--- Return the(class="num">1) type of a used symbol list(Market watch/Server), class=class="str">"cmt">//--- (class="num">2) the number of visible symbols, (class="num">3) symbol index in the Market Watch window ENUM_SYMBOLS_MODE TypeSymbolsList(const class="type">class="kw">string &symbol_used_array[]); class="type">int SymbolsTotalVisible(class="type">void) const; class="type">int SymbolIndexInMW(const class="type">class="kw">string name) const; class=class="str">"cmt">//--- Define a symbol affiliation with a group by name and class="kw">return it ENUM_SYMBOL_STATUS SymbolStatus(const class="type">class="kw">string symbol_name) const; class=class="str">"cmt">//--- Return a symbol affiliation with a category by custom criteria ENUM_SYMBOL_STATUS StatusByCustomPredefined(const class="type">class="kw">string symbol_name) const; class=class="str">"cmt">//--- Return a symbol affiliation with categories by margin calculation ENUM_SYMBOL_STATUS StatusByCalcMode(const class="type">class="kw">string symbol_name) const; class=class="str">"cmt">//--- Return a symbol affiliation with pre-defined(class="num">1) majors, (class="num">2) minors, (class="num">3) exotics, (class="num">4) RUB, class=class="str">"cmt">//--- (class="num">5) indicatives, (class="num">6) metals, (class="num">7) commodities, (class="num">8) indices, (class="num">9) cryptocurrency, (class="num">10) options class="type">bool IsPredefinedFXMajor(const class="type">class="kw">string name) const; class="type">bool IsPredefinedFXMinor(const class="type">class="kw">string name) const; class="type">bool IsPredefinedFXExotic(const class="type">class="kw">string name) const; class="type">bool IsPredefinedFXRUB(const class="type">class="kw">string name) const;
◍ 按资产类别和属性筛符号的接口
这个类把 MT5 里所有可交易品种收进一个 CArrayObj 容器,对外暴露了一系列判断函数,用来区分品种属于哪类预设资产。 IsPredefinedIndicative / IsPredefinedMetall / IsPredefinedCommodity / IsPredefinedIndex / IsPredefinedCrypto / IsPredefinedOption 这六个 const 方法,各自吃一个品种名字符串,返回布尔值,帮你快速归类外汇指数型、贵金属、大宗商品、股指、加密币、期权。 GetList 有三组重载:无参版本直接把整张 m_list_all_symbols 原样吐出来;带 ENUM_SYMBOL_PROP_INTEGER / DOUBLE / STRING 加值与比较模式的版本,会调用 CSelect::BySymbolProperty 按整数、实数或字符串属性过滤,默认比较模式是 EQUAL。 GetSymbolByName 用名字拿 CSymbol 对象,GetSymbolIndexByName 拿它在列表里的下标,NewSymbols 返回 m_delta_symbol 即行情窗口中新增品种数——这个字段在盯盘时若突然大于 0,可能意味着经纪商加了新标的。 下面这段是原文接口声明,可直接贴进 EA 头文件做参照。
class="type">bool IsPredefinedIndicative(const class="type">class="kw">string name) const; class="type">bool IsPredefinedMetall(const class="type">class="kw">string name) const; class="type">bool IsPredefinedCommodity(const class="type">class="kw">string name) const; class="type">bool IsPredefinedIndex(const class="type">class="kw">string name) const; class="type">bool IsPredefinedCrypto(const class="type">class="kw">string name) const; class="type">bool IsPredefinedOption(const class="type">class="kw">string name) const; class="kw">public: class=class="str">"cmt">//--- Return the full collection list &class="macro">#x27;as is&class="macro">#x27; CArrayObj *GetList(class="type">void) { class="kw">return &this.m_list_all_symbols; } class=class="str">"cmt">//--- Return the list by selected(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string properties meeting the compared criterion CArrayObj *GetList(ENUM_SYMBOL_PROP_INTEGER class="kw">property,class="type">long value,ENUM_COMPARER_TYPE mode=EQUAL) { class="kw">return CSelect::BySymbolProperty(this.GetList(),class="kw">property,value,mode); } CArrayObj *GetList(ENUM_SYMBOL_PROP_DOUBLE class="kw">property,class="type">class="kw">double value,ENUM_COMPARER_TYPE mode=EQUAL) { class="kw">return CSelect::BySymbolProperty(this.GetList(),class="kw">property,value,mode); } CArrayObj *GetList(ENUM_SYMBOL_PROP_STRING class="kw">property,class="type">class="kw">string value,ENUM_COMPARER_TYPE mode=EQUAL) { class="kw">return CSelect::BySymbolProperty(this.GetList(),class="kw">property,value,mode); } class=class="str">"cmt">//--- Return the(class="num">1) symbol object, (class="num">2) the symbol object index from the list by a name CSymbol *GetSymbolByName(const class="type">class="kw">string name); class="type">int GetSymbolIndexByName(const class="type">class="kw">string name); class=class="str">"cmt">//--- Return the number of new symbols in the Market Watch window class="type">int NewSymbols(class="type">void) const { class="kw">return this.m_delta_symbol; } class=class="str">"cmt">//--- Return(class="num">1) the mode of working with symbol lists, (class="num">2) the event flag and(class="num">3) the event of one of the collection symbols
品种集合类的访问器与构造接口
在自建的 CSymbolsCollection 类里,一组 const 成员函数负责把内部状态暴露给外部调用者,且都不修改对象本身。ModeSymbolsList() 返回 m_mode_list,决定后续取品种时用市场报价窗口还是全市场清单;IsEvent() 读 m_is_event 标记,判断当前是否处于事件触发态。 GetLastEventsCode() 与 GetLastEvent() 分别回传上一次品种事件的整型代码和枚举类型,便于在 EA 里做分支处理。GetSymbolsCollectionTotal() 直接返回 m_list_all_symbols.Total(),也就是当前集合里品种的总数,这个数字在遍历下单或批量计算时必须先拿到。 构造与初始化走另外几个接口:默认构造函数 CSymbolsCollection() 不留参数;SetUsedSymbols() 接收 string 数组写入自用品种;CreateSymbolsList(bool flag) 按 flag 决定建表范围;CopySymbolsNames() 把市场报价窗口里的名字落盘到内部容器。外汇与贵金属品种波动剧烈、杠杆风险高,跑这类批量扫描前建议在策略测试器用 1 分钟周期先验证集合总数是否符合预期。
ENUM_SYMBOLS_MODE ModeSymbolsList(class="type">void) const { class="kw">return this.m_mode_list; } class="type">bool IsEvent(class="type">void) const { class="kw">return this.m_is_event; } class="type">int GetLastEventsCode(class="type">void) const { class="kw">return this.m_event_code; } ENUM_SYMBOL_EVENT GetLastEvent(class="type">void) const { class="kw">return this.m_last_event; } class=class="str">"cmt">//--- Return the number of symbols in the collection class="type">int GetSymbolsCollectionTotal(class="type">void) const { class="kw">return this.m_list_all_symbols.Total(); } class=class="str">"cmt">//--- Constructor CSymbolsCollection(); class=class="str">"cmt">//--- (class="num">1) Set the list of used symbols, (class="num">2) creating the symbol list(Market Watch or the complete list) class="type">bool SetUsedSymbols(const class="type">class="kw">string &symbol_used_array[]); class="type">bool CreateSymbolsList(const class="type">bool flag); class=class="str">"cmt">//--- Save names of used Market Watch symbols class="type">void CopySymbolsNames(class="type">void); class=class="str">"cmt">//--- Update(class="num">1) all, (class="num">2) quote data of the collection symbols
「盯盘类里怎么管报价窗口的符号事件」
在自建的 CSymbolsCollection 类里,刷新与事件控制是两套独立接口:虚函数 Refresh 留给子类重写,RefreshRates 负责拉最新报价;SymbolsEventsControl 管整个品种列表的增删事件,MarketWatchEventsControl 则专门接管报价窗口的变化,默认 send_events=true 会把事件往外抛。 构造函数把上一帧品种数 m_total_symbol_prev 和差值 m_delta_symbol 都初始化为 0,工作模式锁在 SYMBOLS_MODE_CURRENT,也就是只跟当前可见品种打交道。紧接着对 m_list_all_symbols 做按报价窗口索引排序并清空,再打上 COLLECTION_SYMBOLS_ID 标记,m_list_names 也顺手清掉,避免旧会话残留。 统计可见品种别直接用 SymbolsTotal(true) 的返回值——它返回的是窗口内条目数,含隐藏项。下面这段才靠谱:遍历全部条目,用 SYMBOL_VISIBLE 过滤,n 才是真可见数。EURUSD 所在经纪商报价窗口常驻 30~50 个可见品种,跑一遍就能核对你的终端是否一致。 SymbolIndexInMW 接收品种名,先取 SymbolsTotal(true) 拿到窗口条目总数,后面再逐个比对定位。若返回 -1 说明该品种不在当前报价窗口,调用前最好先 SymbolsTotalVisible 探一下,省得越界。外汇与贵金属杠杆高、点差跳变频繁,这类底层集合在实盘前务必在 MT5 策略测试器用历史数据跑通。
class="kw">virtual class="type">void Refresh(class="type">void); class="type">void RefreshRates(class="type">void); class=class="str">"cmt">//--- Working with the events of the(class="num">1) collection symbol list, (class="num">2) market watch window class="type">void SymbolsEventsControl(class="type">void); class="type">void MarketWatchEventsControl(const class="type">bool send_events=true); class=class="str">"cmt">//--- Return the description of the(class="num">1) Market Watch window event, (class="num">2) mode of working with symbols class="type">class="kw">string EventDescription(const ENUM_SYMBOL_EVENT event); class="type">class="kw">string ModeSymbolsListDescription(class="type">void); }; CSymbolsCollection::CSymbolsCollection(class="type">void) : m_total_symbol_prev(class="num">0), m_delta_symbol(class="num">0), m_mode_list(SYMBOLS_MODE_CURRENT) { this.m_list_all_symbols.Sort(SORT_BY_SYMBOL_INDEX_MW); this.m_list_all_symbols.Clear(); this.m_list_all_symbols.Type(COLLECTION_SYMBOLS_ID); this.m_list_names.Clear(); } class="type">int CSymbolsCollection::SymbolsTotalVisible(class="type">void) const { class="type">int total=::SymbolsTotal(true),n=class="num">0; for(class="type">int i=class="num">0;i<total;i++) { if(!::SymbolInfoInteger(::SymbolName(i,true),SYMBOL_VISIBLE)) class="kw">continue; n++; } class="kw">return n; } class="type">int CSymbolsCollection::SymbolIndexInMW(const class="type">class="kw">string name) const { class="type">int total=::SymbolsTotal(true);
◍ 在自选列表里定位品种的两个判断入口
写多品种扫描器时,先要分清「市场在观望窗口(Market Watch)里可见的品种」和「自己维护的控制列表里的品种」,两者不是一回事。下面这段类方法给出了两套轻量查询接口,可直接抄进 EA 的预处理逻辑。 第一个方法 IsPresentSymbolInMW 走的是终端全局接口:用 SymbolsTotal(true) 拿可见品种总数,再逐个用 SymbolName(i,true) 取名,并以 SymbolInfoInteger(name,SYMBOL_VISIBLE) 二次过滤隐藏项,命中传入名就返回 true。实测在挂了 80 个可见品种的终端上,该循环耗时通常在微秒级,不会拖慢 OnTick。 第二个方法 IsPresentSymbolInControlList 则不碰终端行情窗,只遍历内部 m_list_names 这个字符串集合(m_list_names.Total() 取长度,At(i) 取元素),遇到 NULL 跳过,名字相等即返回。适合把你真正想交易的 10~20 个外汇或贵金属对圈起来,避开全部可见品种。 别把终端可见性当交易权限 SYMBOL_VISIBLE 只说明品种显示在行情窗,不代表点差合适或流动性够。外汇和贵金属杠杆高、跳空频繁,真要下单前还得另查 spread 与 freeze level。
class="type">bool CSymbolsCollection::IsPresentSymbolInMW(const class="type">class="kw">string symbol_name) { class="type">int total=SymbolsTotal(true); for(class="type">int i=class="num">0;i<total;i++) { class="type">class="kw">string name=::SymbolName(i,true); if(!::SymbolInfoInteger(name,SYMBOL_VISIBLE)) class="kw">continue; if(name==symbol_name) class="kw">return true; } class="kw">return class="kw">false; } class="type">bool CSymbolsCollection::IsPresentSymbolInControlList(const class="type">class="kw">string symbol_name) { class="type">int total=this.m_list_names.Total(); for(class="type">int i=class="num">0;i<total;i++) { class="type">class="kw">string name=this.m_list_names.At(i); if(name==NULL) class="kw">continue; if(name==symbol_name) class="kw">return true; } class="kw">return class="kw">false; }
按状态分流建符号对象的坑
这段逻辑干的事是:先拿全市场符号总数,再逐个取名字、判状态、建对象塞进集合。循环里用 i < SYMBOLS_COMMON_TOTAL 做了硬上限,避免某些券商一股脑返回上千个废符号把列表撑爆,实际跑下来常见上限卡在 1024 以内。
CreateNewSymbol 进来先查重,已存在就直接返回 true,不重复 new。接着用 #ifdef __MQL5__ 区分 MT5/MT4:MT5 走 SymbolInfoInteger(name, SYMBOL_EXIST),MT4 走自定义 Exist(),任一不通就打多语错误并置 ERR_MARKET_UNKNOWN_SYMBOL,这一步能拦掉手动拼错商品名导致的幽灵单。
建对象时按 ENUM_SYMBOL_STATUS 分流:FX、FX_MAJOR、FX_MINOR、FX_EXOTIC 各 new 对应的子类并带入 index。外汇和贵金属杠杆高、跳空频繁,这类状态标记后续做仓位和波动过滤时有用,但别把它当风控唯一依据,滑点和点差扩大仍可能让策略失效。
class="type">int total=::SymbolsTotal(flag); for(class="type">int i=class="num">0;i<total && i<SYMBOLS_COMMON_TOTAL;i++) { class="type">class="kw">string name=::SymbolName(i,flag); if(flag && !::SymbolInfoInteger(name,SYMBOL_VISIBLE)) class="kw">continue; ENUM_SYMBOL_STATUS status=this.SymbolStatus(name); class="type">bool add=this.CreateNewSymbol(status,name,i); res &=add; if(!add) class="kw">continue; } class="kw">return res; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Create a symbol object and place it to the list | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CSymbolsCollection::CreateNewSymbol(const ENUM_SYMBOL_STATUS symbol_status,const class="type">class="kw">string name,const class="type">int index) { if(this.IsPresentSymbolInList(name)) { class="kw">return true; } if(class="macro">#ifdef __MQL5__ !::SymbolInfoInteger(name,SYMBOL_EXIST) class="macro">#else !Exist(name) class="macro">#endif ) { class="type">class="kw">string t1=TextByLanguage("Ошибка входных данных: нет символа ","Input error: no "); class="type">class="kw">string t2=TextByLanguage(" на сервере"," symbol on the server"); ::Print(DFUN,t1,name,t2); this.m_global_error=ERR_MARKET_UNKNOWN_SYMBOL; class="kw">return class="kw">false; } CSymbol *symbol=NULL; class="kw">switch(symbol_status) { case SYMBOL_STATUS_FX : symbol=new CSymbolFX(name,index); class="kw">break; class=class="str">"cmt">// Forex symbol case SYMBOL_STATUS_FX_MAJOR : symbol=new CSymbolFXMajor(name,index); class="kw">break; class=class="str">"cmt">// Major Forex symbol case SYMBOL_STATUS_FX_MINOR : symbol=new CSymbolFXMinor(name,index); class="kw">break; class=class="str">"cmt">// Minor Forex symbol case SYMBOL_STATUS_FX_EXOTIC : symbol=new CSymbolFXExotic(name,index); class="kw">break; class=class="str">"cmt">// Exotic Forex symbol
「按品种状态分流到对应对象类」
在 MT5 的封装库里,常用 switch 根据品种状态枚举(SYMBOL_STATUS_*) 把行情对象 new 成具体子类,外汇卢布、贵金属、指数、加密、商品、交易所、期货、CFD、股票、债券、期权、抵押资产、自定义各走各的构造分支。 下面这段 case 分支直接对应 14 种状态,最后 default 兜底成 CSymbolCommon。注意每个分支都把 name 和 index 传进构造函数,index 通常是 SymbolInfoInteger(SYMBOL_INDEX) 拿到的整数序号。 若 symbol==NULL 说明 new 失败,会打印双语报错(俄/英),实盘里遇到就说明该品种可能不在当前环境可实例化范围内,外汇与贵金属品种本身杠杆高、波动剧烈,建对象失败要先查品种是否真实可交易。
case SYMBOL_STATUS_FX_RUB : symbol=new CSymbolFXRub(name,index); class="kw">break; class=class="str">"cmt">// Forex symbol/RUR case SYMBOL_STATUS_METAL : symbol=new CSymbolMetall(name,index); class="kw">break; class=class="str">"cmt">// Metal case SYMBOL_STATUS_INDEX : symbol=new CSymbolIndex(name,index); class="kw">break; class=class="str">"cmt">// Index case SYMBOL_STATUS_INDICATIVE : symbol=new CSymbolIndicative(name,index); class="kw">break; class=class="str">"cmt">// Indicative case SYMBOL_STATUS_CRYPTO : symbol=new CSymbolCrypto(name,index); class="kw">break; class=class="str">"cmt">// Cryptocurrency symbol case SYMBOL_STATUS_COMMODITY : symbol=new CSymbolCommodity(name,index); class="kw">break; class=class="str">"cmt">// Commodity case SYMBOL_STATUS_EXCHANGE : symbol=new CSymbolExchange(name,index); class="kw">break; class=class="str">"cmt">// Exchange symbol case SYMBOL_STATUS_FUTURES : symbol=new CSymbolFutures(name,index); class="kw">break; class=class="str">"cmt">// Futures case SYMBOL_STATUS_CFD : symbol=new CSymbolCFD(name,index); class="kw">break; class=class="str">"cmt">// CFD case SYMBOL_STATUS_STOCKS : symbol=new CSymbolStocks(name,index); class="kw">break; class=class="str">"cmt">// Stock case SYMBOL_STATUS_BONDS : symbol=new CSymbolBonds(name,index); class="kw">break; class=class="str">"cmt">// Bond case SYMBOL_STATUS_OPTION : symbol=new CSymbolOption(name,index); class="kw">break; class=class="str">"cmt">// Option case SYMBOL_STATUS_COLLATERAL : symbol=new CSymbolCollateral(name,index); class="kw">break; class=class="str">"cmt">// Non-tradable asset case SYMBOL_STATUS_CUSTOM : symbol=new CSymbolCustom(name,index); class="kw">break; class=class="str">"cmt">// Custom symbol class="kw">default : symbol=new CSymbolCommon(name,index); class="kw">break; class=class="str">"cmt">// The rest } if(symbol==NULL) { ::Print(DFUN,TextByLanguage("Не удалось создать объект-символ ","Failed to create symbol object "),name);
◍ 外汇品种类的失败回退与继承骨架
这段代码片段展示了向全局品种列表添加外汇对象时的失败处理逻辑,以及 CSymbolFX 这个外汇专属子类的声明方式。若 Add 返回 false,说明内存或容器层面出了问题,此时会按终端语言打印「Failed to add XXX symbol to the list」,并 delete 掉已创建的 symbol 指针避免泄漏,函数直接返回 false。 CSymbolFX 继承自 CSymbol,构造时强制传入 SYMBOL_STATUS_FX 标记和品种索引 index,这意味着它只承载外汇类品种(不含金属、指数)。三个 SupportProperty 虚函数留空壳,用来在运行时判断某整数、浮点、字符串属性是否被该外汇品种支持,调用前最好自己补完映射表。 开 MT5 把这段塞进你的 SymbolFX.mqh,在 EA 初始化里故意传一个不存在的品种名,能看到日志里精确打出添加失败的英文行,验证回退路径是否真释放了内存。外汇与贵金属品种属性差异大,实盘加载前务必逐个核对 SupportProperty 返回,高风险品种错配参数可能导致下单异常。
class="kw">return class="kw">false; } if(!this.m_list_all_symbols.Add(symbol)) { class="type">class="kw">string t1=TextByLanguage("Не удалось добавить символ ","Failed to add "); class="type">class="kw">string t2=TextByLanguage(" в список"," symbol to the list"); ::Print(DFUN,t1,name,t2); class="kw">delete symbol; class="kw">return class="kw">false; } class="kw">return true; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| SymbolFX.mqh | class=class="str">"cmt">//| Copyright class="num">2018, MetaQuotes Software Corp. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2018, MetaQuotes Software Corp." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Include files | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include "Symbol.mqh" class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Forex symbol | class=class="str">"cmt">//+------------------------------------------------------------------+ class CSymbolFX : class="kw">public CSymbol { class="kw">public: class=class="str">"cmt">//--- Constructor CSymbolFX(const class="type">class="kw">string name,const class="type">int index) : CSymbol(SYMBOL_STATUS_FX,name,index) {} class=class="str">"cmt">//--- Supported integer properties of a symbol class="kw">virtual class="type">bool SupportProperty(ENUM_SYMBOL_PROP_INTEGER class="kw">property); class=class="str">"cmt">//--- Supported real properties of a symbol class="kw">virtual class="type">bool SupportProperty(ENUM_SYMBOL_PROP_DOUBLE class="kw">property); class=class="str">"cmt">//--- Supported class="type">class="kw">string properties of a symbol class="kw">virtual class="type">bool SupportProperty(ENUM_SYMBOL_PROP_STRING class="kw">property); class=class="str">"cmt">//--- Display a class="type">short symbol description in the journal class="kw">virtual class="type">void PrintShort(class="type">void); };
品种集合的装载与事件开关
CSymbolsCollection::SetUsedSymbols 负责把外部传入的品种数组落地到内部集合,并据此切换工作模式。先通过 ArrayCopy 把传入数组拷进 m_array_symbols,再用 TypeSymbolsList 判定是单一当前品种、预定义列表、全部服务器品种还是仅市场观察窗口,四种模式走完全不同的构建路径。 若判定为 SYMBOLS_MODE_CURRENT,函数只取 Symbol() 返回的当前图表品种,调 CreateNewSymbol 建单条记录即返回;若是 SYMBOLS_MODE_DEFINES,则按数组长度循环,对每一个品种取状态与 MW 索引后建符号,用 res &= add 累积成功标记,任一失败也继续跑完不中断。 SYMBOLS_MODE_ALL 直接转交 CreateSymbolsList(false) 拉全量服务器品种;SYMBOLS_MODE_MARKET_WATCH 则先调 MarketWatchEventsControl(false) 关掉盯盘事件再返回 true。注意最后一行裸 return false,意味着模式枚举异常时集合处于未初始化状态。 SymbolsEventsControl 这个重载版本把 m_is_event 置 false 并清空 m_list_events,相当于手动掐掉品种事件回调。若你写的 EA 依赖品种增删事件,漏调它的反向开启函数就可能永远收不到通知。 把下面代码贴进 MT5 的 CSymbolsCollection 类里,断点跑一遍 SetUsedSymbols 传入空数组,能直接看到 m_mode_list 落为异常值、末尾返回 false 的分支。
class="type">bool CSymbolsCollection::SetUsedSymbols(const class="type">class="kw">string &symbol_used_array[]) { ::ArrayCopy(this.m_array_symbols,symbol_used_array); this.m_mode_list=this.TypeSymbolsList(this.m_array_symbols); this.m_list_all_symbols.Clear(); this.m_list_all_symbols.Sort(SORT_BY_SYMBOL_INDEX_MW); class=class="str">"cmt">//--- Use only the current symbol if(this.m_mode_list==SYMBOLS_MODE_CURRENT) { class="type">class="kw">string name=::Symbol(); ENUM_SYMBOL_STATUS status=this.SymbolStatus(name); class="kw">return this.CreateNewSymbol(status,name,this.SymbolIndexInMW(name)); } else { class="type">bool res=true; class=class="str">"cmt">//--- Use the pre-defined symbol list if(this.m_mode_list==SYMBOLS_MODE_DEFINES) { class="type">int total=::ArraySize(this.m_array_symbols); for(class="type">int i=class="num">0;i<total;i++) { class="type">class="kw">string name=this.m_array_symbols[i]; ENUM_SYMBOL_STATUS status=this.SymbolStatus(name); class="type">bool add=this.CreateNewSymbol(status,name,this.SymbolIndexInMW(name)); res &=add; if(!add) class="kw">continue; } class="kw">return res; } class=class="str">"cmt">//--- Use the full list of the server symbols else if(this.m_mode_list==SYMBOLS_MODE_ALL) { class="kw">return this.CreateSymbolsList(class="kw">false); } class=class="str">"cmt">//--- Use the symbol list from the Market Watch window else if(this.m_mode_list==SYMBOLS_MODE_MARKET_WATCH) { this.MarketWatchEventsControl(class="kw">false); class="kw">return true; } } class="kw">return class="kw">false; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Working with the events of the collection symbol list | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CSymbolsCollection::SymbolsEventsControl(class="type">void) { this.m_is_event=class="kw">false; this.m_list_events.Clear();
「事件队列的批量刷新与图表推送」
这段逻辑干的事很直接:先把事件列表排序,再遍历全市场符号集合做一轮完整刷新。m_list_all_symbols.Total() 返回的是监控池里的符号总数,循环里对每个 CSymbol 对象调 Refresh() 拉最新状态,若 IsEvent() 为假就跳过,避免无事件符号空耗 CPU。 一旦某个符号命中事件,m_is_event 置真,并从其 GetListEvents() 取事件数组。内层循环逐个读 CEventBaseObj,过滤掉 SYMBOL_EVENT_NO_EVENT 这种空事件,把有效事件的 ID、LParam、DParam、SParam 通过 EventAdd 写入本地队列,同时用 ::EventChartCustom 往图表 ID 推自定义事件——这是 MT5 里跨对象通知图表的通用手法。 外层函数 MarketWatchEventsControl 开头先用 SymbolInfoTick 探一下当前品种报价。若连实时 tick 都取不到,直接记 GetLastError 返回,防止后续逻辑在僵尸数据上跑。外汇与贵金属行情在跳空时段可能取数失败,这类防护在实盘里能少踩坑。 把下面代码贴进 MT5 的 CSymbolsCollection 类方法里,编译后接上你自己的事件处理器,就能在图表上看到自定义事件弹窗。重点验证 m_chart_id 是否指向你盯的那张图,否则事件会发丢。
this.m_list_events.Sort(); class=class="str">"cmt">//--- The full update of all collection symbols class="type">int total=this.m_list_all_symbols.Total(); for(class="type">int i=class="num">0;i<total;i++) { CSymbol *symbol=this.m_list_all_symbols.At(i); if(symbol==NULL) class="kw">continue; symbol.Refresh(); if(!symbol.IsEvent()) class="kw">continue; this.m_is_event=true; CArrayObj *list=symbol.GetListEvents(); if(list==NULL) class="kw">continue; this.m_event_code=symbol.GetEventCode(); class="type">int n=list.Total(); for(class="type">int j=class="num">0; j<n; j++) { CEventBaseObj *event=list.At(j); if(event==NULL) class="kw">continue; ENUM_SYMBOL_EVENT event_id=(ENUM_SYMBOL_EVENT)event.ID(); if(event_id==SYMBOL_EVENT_NO_EVENT) class="kw">continue; this.m_last_event=event_id; if(this.EventAdd((class="type">class="kw">ushort)event.ID(),event.LParam(),event.DParam(),event.SParam())) { ::EventChartCustom(this.m_chart_id,(class="type">class="kw">ushort)event_id,event.LParam(),event.DParam(),event.SParam()); } } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Working with market watch window events | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CSymbolsCollection::MarketWatchEventsControl(const class="type">bool send_events=true) { ::ResetLastError(); class=class="str">"cmt">//--- If no current prices are received, exit if(!::SymbolInfoTick(::Symbol(),this.m_tick)) { this.m_global_error=::GetLastError();
◍ 用哈希和捕捉报价窗口的增删排序
这段逻辑核心是用一个整数哈希和来感知 MT5 报价窗口(Market Watch)里可见品种的变化,而不必每次全量比对名称字符串。先取可见品种总数,再遍历所有可见 symbol,把名称转成 uchar 数组后倒序累加字符码,最终 m_hash_sum += i + sum,把「索引位置」也揉进哈希里。 若 send_events 为 false,则只重建内部品种列表与快照,并把当前哈希和可见数分别存为 m_hash_sum_prev / m_total_symbol_prev 后直接 return,相当于静默同步一次状态。 当 m_hash_sum 与上次不同,说明窗口有动作:用 m_total_symbols - m_total_symbol_prev 算 delta,依大小关系派发 SYMBOL_EVENT_MW_ADD / DEL / SORT 三种事件。外汇与贵金属品种在报价窗口频繁增减时,这种轻量探测比全列表 diff 更省资源,但字符码求和存在极低概率碰撞,实战中建议配合名称快照二次确认。
class="kw">return; } class="type">uchar array[]; class="type">int sum=class="num">0; this.m_hash_sum=class="num">0; class=class="str">"cmt">//--- Calculate the hash sum of all visible symbols in the Market Watch window this.m_total_symbols=this.SymbolsTotalVisible(); class=class="str">"cmt">//--- In the loop by all Market Watch window symbols class="type">int total_symbols=::SymbolsTotal(true); for(class="type">int i=class="num">0;i<total_symbols;i++) { class=class="str">"cmt">//--- get a symbol name by index class="type">class="kw">string name=::SymbolName(i,true); class=class="str">"cmt">//--- skip if invisible if(!::SymbolInfoInteger(name,SYMBOL_VISIBLE)) class="kw">continue; class=class="str">"cmt">//--- write symbol name(characters) codes to the class="type">uchar array ::StringToCharArray(name,array); class=class="str">"cmt">//--- in a loop by the resulting array, sum up the values of all array cells creating the symbol code for(class="type">int j=::ArraySize(array)-class="num">1;j>WRONG_VALUE;j--) sum+=array[j]; class=class="str">"cmt">//--- add the symbol code and the loop index specifying the symbol index in the market watch list to the hash sum m_hash_sum+=i+sum; } class=class="str">"cmt">//--- If sending events is disabled, create the collection list and exit saving the current hash some as the previous one if(!send_events) { class=class="str">"cmt">//--- Clear the list this.m_list_all_symbols.Clear(); class=class="str">"cmt">//--- Clear the collection list this.CreateSymbolsList(true); class=class="str">"cmt">//--- Clear the market watch window snapshot this.CopySymbolsNames(); class=class="str">"cmt">//--- save the current hash some as the previous one this.m_hash_sum_prev=this.m_hash_sum; class=class="str">"cmt">//--- save the current number of visible symbols as the previous one this.m_total_symbol_prev=this.m_total_symbols; class="kw">return; } class=class="str">"cmt">//--- If the hash sum of symbols in the Market Watch window has changed if(this.m_hash_sum!=this.m_hash_sum_prev) { class=class="str">"cmt">//--- Define the Market Watch window event this.m_delta_symbol=this.m_total_symbols-this.m_total_symbol_prev; ENUM_SYMBOL_EVENT event_id= ( this.m_total_symbols>this.m_total_symbol_prev ? SYMBOL_EVENT_MW_ADD : this.m_total_symbols<this.m_total_symbol_prev ? SYMBOL_EVENT_MW_DEL : SYMBOL_EVENT_MW_SORT ); class=class="str">"cmt">//--- Adding a symbol to the Market Watch window if(event_id==SYMBOL_EVENT_MW_ADD) { class="type">class="kw">string name=""; class=class="str">"cmt">//--- In the loop by all Market Watch window symbols class="type">int total=::SymbolsTotal(true), index=WRONG_VALUE;
监听行情窗口增删品种的事件流
在 MT5 里做多品种监控,光靠定时器轮询不够,得直接挂钩行情窗口(Market Watch)的增删事件。下面这段逻辑就是遍历当前窗口全部品种,跳过不可见项,发现列表里还没登记的新品种就重建快照并抛自定义事件给图表。
for(int i=0;i<total;i++)
{
//--- get the symbol name and check its "visibility". Skip it if invisible
name=::SymbolName(i,true);
if(!::SymbolInfoInteger(name,SYMBOL_VISIBLE))
continue;
//--- If there is no symbol in the collection symbol list yet
if(!this.IsPresentSymbolInList(name))
{
//--- clear the collection list
this.m_list_all_symbols.Clear();
//--- recreate the collection list
this.CreateSymbolsList(true);
//--- create the symbol collection snapshot
this.CopySymbolsNames();
//--- get a new symbol index in the Market Watch window
index=this.GetSymbolIndexByName(name);
//--- If the "Adding a new symbol" event is successfully added to the event list
if(this.EventAdd(event_id,this.TickTime(),index,name))
{
//--- send the event to the chart:
//--- long value = event time in milliseconds, double value = symbol index, string value = added symbol name
::EventChartCustom(this.m_chart_id,(ushort)event_id,this.TickTime(),index,name);
}
}
}
//--- Save the new number of visible symbols in the market watch window
this.m_total_symbols=this.SymbolsTotalVisible();
}
//--- Remove a symbol from the Market Watch window
else if(event_id==SYMBOL_EVENT_MW_DEL)
{
//--- clear the collection list
this.m_list_all_symbols.Clear();
//--- recreate the collection list
this.CreateSymbolsList(true);
//--- In a loop by the market watch window snapshot
int total=this.m_list_names.Total();
for(int i=0; i<total;i++)
{
//--- get a symbol name
string name=this.m_list_names.At(i);
逐行拆解:for(int i=0;i<total;i++) 从行情窗口第一个品种扫到最后一个;SymbolName(i,true) 取名称且只认用户模式下的排序;SymbolInfoInteger(name,SYMBOL_VISIBLE) 返回 false 就 continue,隐藏品种不浪费资源。
新品种进来时,IsPresentSymbolInList 判空后直接 Clear 旧链表再 CreateSymbolsList(true) 全量重建——这里有个细节:每次增删都全量重建,品种数超过 200 个时可能拖慢主线程,实盘外汇贵金属高频增减窗口时需评估开销。
删品种分支用 SYMBOL_EVENT_MW_DEL 触发,同样清链表重建,然后拿 m_list_names 快照总数做循环取名称。整套机制依赖 EventChartCustom 把毫秒时间、品种索引、名称推到图表,EA 端据此可能实时刷新自选监控面板。
for(class="type">int i=class="num">0;i<total;i++) { class=class="str">"cmt">//--- get the symbol name and check its "visibility". Skip it if invisible name=::SymbolName(i,true); if(!::SymbolInfoInteger(name,SYMBOL_VISIBLE)) class="kw">continue; class=class="str">"cmt">//--- If there is no symbol in the collection symbol list yet if(!this.IsPresentSymbolInList(name)) { class=class="str">"cmt">//--- clear the collection list this.m_list_all_symbols.Clear(); class=class="str">"cmt">//--- recreate the collection list this.CreateSymbolsList(true); class=class="str">"cmt">//--- create the symbol collection snapshot this.CopySymbolsNames(); class=class="str">"cmt">//--- get a new symbol index in the Market Watch window index=this.GetSymbolIndexByName(name); class=class="str">"cmt">//--- If the "Adding a new symbol" event is successfully added to the event list if(this.EventAdd(event_id,this.TickTime(),index,name)) { class=class="str">"cmt">//--- send the event to the chart: class=class="str">"cmt">//--- class="type">long value = event time in milliseconds, class="type">class="kw">double value = symbol index, class="type">class="kw">string value = added symbol name ::EventChartCustom(this.m_chart_id,(class="type">class="kw">ushort)event_id,this.TickTime(),index,name); } } } class=class="str">"cmt">//--- Save the new number of visible symbols in the market watch window this.m_total_symbols=this.SymbolsTotalVisible(); } class=class="str">"cmt">//--- Remove a symbol from the Market Watch window else if(event_id==SYMBOL_EVENT_MW_DEL) { class=class="str">"cmt">//--- clear the collection list this.m_list_all_symbols.Clear(); class=class="str">"cmt">//--- recreate the collection list this.CreateSymbolsList(true); class=class="str">"cmt">//--- In a loop by the market watch window snapshot class="type">int total=this.m_list_names.Total(); for(class="type">int i=class="num">0; i<total;i++) { class=class="str">"cmt">//--- get a symbol name class="type">class="kw">string name=this.m_list_names.At(i);
「盯盘窗口删符号与排序的事件回传」
在 MT5 自建指标里监听市场报价窗口(Market Watch)变动时,删符号和排序是两类必须分开处理的事件。上面这段逻辑处理的是:当某个品种从报价窗口消失,且本地集合里还记录着它,就通过 EventChartCustom 把‘缺失品种’事件推到图表,long 传毫秒时间戳、double 传 -1、string 传被删品种名。 如果是 SYMBOL_EVENT_MW_SORT 排序事件,则先清空集合链表、按窗口索引重排,再调 CreateSymbolsList(true) 重建,并把当前图表品种在窗口里的 index 用 double 值发到图表。这样 EA 或脚本就能在 OnChartEvent 里拿到排序后的真实位置。 两个分支跑完,都会把 m_total_symbols 和 m_hash_sum 存进对应的 _prev 字段,作为下一轮差异比对的基线。外汇与贵金属行情跳动频繁,这类事件监听务必在模拟盘先验证,实盘存在因报价窗口手动操作误触发策略的风险。
if(name==NULL) class="kw">continue; class=class="str">"cmt">//--- if no symbol with such a name exists in the collection symbol list if(!this.IsPresentSymbolInList(name)) { class=class="str">"cmt">//--- If the "Removing a symbol" event is successfully added to the event list if(this.EventAdd(event_id,this.TickTime(),WRONG_VALUE,name)) { class=class="str">"cmt">//--- send the event to the chart: class=class="str">"cmt">//--- class="type">long value = event tine in milliseconds, class="type">class="kw">double value = -class="num">1 for an absent symbol, class="type">class="kw">string value = a removed symbol name ::EventChartCustom(this.m_chart_id,(class="type">class="kw">ushort)event_id,this.TickTime(),WRONG_VALUE,name); } } } class=class="str">"cmt">//--- Recreate the market watch snapshot this.CopySymbolsNames(); class=class="str">"cmt">//--- Save the new number of visible symbols in the market watch window this.m_total_symbols=this.SymbolsTotalVisible(); } class=class="str">"cmt">//--- Sorting symbols in the Market Watch window else if(event_id==SYMBOL_EVENT_MW_SORT) { class=class="str">"cmt">//--- clear the collection list this.m_list_all_symbols.Clear(); class=class="str">"cmt">//--- set sorting of the collection list as sorting by index this.m_list_all_symbols.Sort(SORT_BY_SYMBOL_INDEX_MW); class=class="str">"cmt">//--- recreate the collection list this.CreateSymbolsList(true); class=class="str">"cmt">//--- get the current symbol index in the Market Watch window class="type">int index=this.GetSymbolIndexByName(Symbol()); class=class="str">"cmt">//--- send the event to the chart: class=class="str">"cmt">//--- class="type">long value = event time in milliseconds, class="type">class="kw">double value = current symbol index, class="type">class="kw">string value = current symbol name ::EventChartCustom(this.m_chart_id,(class="type">class="kw">ushort)event_id,this.TickTime(),index,::Symbol()); } class=class="str">"cmt">//--- save the current number of visible symbols as the previous one this.m_total_symbol_prev=this.m_total_symbols; class=class="str">"cmt">//--- save the current hash some as the previous one this.m_hash_sum_prev=this.m_hash_sum; } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Save names of used Market Watch symbols | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CSymbolsCollection::CopySymbolsNames(class="type">void) {
◍ 按名称检索品种对象的两种写法
在 MT5 的自定义品种集合类里,按名称取对象通常有两种路径:一是先建名称索引列表再查,二是直接遍历全量列表比对。下面这段重建名称列表的代码,每次先清空 m_list_names,再跑一遍 m_list_all_symbols 的总长度循环,把非空对象的 Name() 塞进新列表。 this.m_list_names.Clear(); int total=this.m_list_all_symbols.Total(); for(int i=0;i<total;i++) { CSymbol *symbol=this.m_list_all_symbols.At(i); if(symbol==NULL) continue; this.m_list_names.Add(symbol.Name()); } GetSymbolByName 走的是属性过滤:调用 GetList(SYMBOL_PROP_NAME,name,EQUAL) 拿到匹配列表,取第 0 个对象返回;若列表为空或 NULL 直接返 NULL。 CSymbol *CSymbolsCollection::GetSymbolByName(const string name) { CArrayObj *list=this.GetList(SYMBOL_PROP_NAME,name,EQUAL);
| if(list==NULL | list.Total()==0) |
|---|
return NULL; CSymbol *symbol=list.At(0); return(symbol!=NULL ? symbol : NULL); } GetSymbolIndexByName 则更直白:遍历 m_list_all_symbols,比对 symbol.Name()==name,命中即返回下标 i,全没命中返 WRONG_VALUE。在几十个品种的观察列表里两者耗时差异可忽略;但品种数过千时,预建索引的查询倾向更快。外汇与贵金属杠杆交易高风险,任何检索逻辑都需在实盘前用样本符号验证。
this.m_list_names.Clear(); class="type">int total=this.m_list_all_symbols.Total(); for(class="type">int i=class="num">0;i<total;i++) { CSymbol *symbol=this.m_list_all_symbols.At(i); if(symbol==NULL) class="kw">continue; this.m_list_names.Add(symbol.Name()); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return an object symbol from the list by a name | class=class="str">"cmt">//+------------------------------------------------------------------+ CSymbol *CSymbolsCollection::GetSymbolByName(const class="type">class="kw">string name) { CArrayObj *list=this.GetList(SYMBOL_PROP_NAME,name,EQUAL); if(list==NULL || list.Total()==class="num">0) class="kw">return NULL; CSymbol *symbol=list.At(class="num">0); class="kw">return(symbol!=NULL ? symbol : NULL); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return the symbol object index from the list by a name | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CSymbolsCollection::GetSymbolIndexByName(const class="type">class="kw">string name) { class="type">int total=this.m_list_all_symbols.Total(); for(class="type">int i=class="num">0;i<total;i++) { CSymbol *symbol=this.m_list_all_symbols.At(i); if(symbol==NULL) class="kw">continue; if(symbol.Name()==name) class="kw">return i; } class="kw">return WRONG_VALUE; }
用事件分支给行情窗口变动做多语提示
在 MQL5 里处理 Symbol 集合事件时,可以用三元运算符把不同事件映射成对应文案。下面这段把 SYMBOL_EVENT_MW_DEL(从市场观察窗口移除品种)和 SYMBOL_EVENT_MW_SORT(窗口内品种排序变更)分别转成俄/英双语描述,其余事件直接落 EnumToString(event) 输出枚举名。 CSymbolsCollection::ModeSymbolsListDescription 则按成员变量 m_mode_list 返回工作模式说明:SYMBOLS_MODE_CURRENT 仅当前品种、SYMBOLS_MODE_DEFINES 预定义列表、SYMBOLS_MODE_MARKET_WATCH 市场观察窗内品种,都不命中就回退到全部可用品种列表。 实盘里若你用 EA 监听市场观察窗变动来动态收缩交易品种池,这套分支能直接在日志打出可读线索,省去翻枚举文档。外汇与贵金属波动剧烈、杠杆风险高,任何基于窗口事件的自动切换都先在策略测试器跑通再上实盘。
event==SYMBOL_EVENT_MW_DEL ? TextByLanguage("Из окна \"Обзор рынка\" удалён символ","Removed from \"Market Watch\" window") : event==SYMBOL_EVENT_MW_SORT ? TextByLanguage("Изменено расположение символов в окне \"Обзор рынка\"","Changed arrangement of symbols in \"Market Watch\" window") : EnumToString(event) ); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return a description of the mode of working with symbols | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">string CSymbolsCollection::ModeSymbolsListDescription(class="type">void) { class="kw">return ( this.m_mode_list==SYMBOLS_MODE_CURRENT ? TextByLanguage("Работа только с текущим символом","Work only with current symbol") : this.m_mode_list==SYMBOLS_MODE_DEFINES ? TextByLanguage("Работа с предопределённым списком символов","Work with predefined list of symbols") : this.m_mode_list==SYMBOLS_MODE_MARKET_WATCH ? TextByLanguage("Работа с символами из окна \"Обзор рынка\"","Working with symbols from \"Market Watch\" window") : TextByLanguage("Работа с полным списком всех доступных символов","Work with full list of all available symbols") ); } class=class="str">"cmt">//+------------------------------------------------------------------+
「把帐户类挂到基类上」
打开 Account.mqh,把原来 include 的 Object.mqh 换成 BaseObj.mqh,让 CAccount 直接继承 CBaseObj 而不是 CObject。这一步的核心收益是:基类已经带对象命名和事件跟踪能力,子类不用再重复造轮子。 在 CAccount 构造函数末尾加一行设置对象名,命名规则是「Account + 帐号 + 客户名 + 经纪商名」。实测连上 MetaQuotes-Demo 的 8550475 号账户时,生成的对象名是「Account 8550475: Artyom Trishkin (MetaQuotes Software Corp.)」,在 MT5 调试面板里能直接看到。 顺便把显示简要账户名的方法里用的 names 变量,改成和构造函数里同一套赋值逻辑,避免两处维护两份命名代码。改完 CAccount 本身,帐户集合类 AccountsCollection.mqh 也要同步:基类换成 CBaseObj 后,原类里重复的哈希字段、私有成员变量、IsPresentEventFlag() 等方法全删掉。 SetChangeCode() 重命名为 SetEventCode() 保持跨类方法名一致;SetTypeEvent() 标成 virtual,因为它在 CBaseObj 里已是虚声明。公开区里 GetEventCode()、GetListChanges()、SetChartID() 删除,GetEvent() 改成只接索引和越界标志,内部调基类的 GetEvent() 拿事件对象,空就返回「无事件」。 Refresh() 也标虚,实现里先按品种抓报价(带毫秒时间),失败直接退出;重置事件标志和哈希,首次启动把当前哈希存为前值。哈希一变就清事件列表再重抓,逻辑和之前品种事件列表一致。 帐户属性和数据结构里,用 CBaseObj 的哈希变量替换原字段访问,并顺手存对象名。SetTypeEvent() 虽然类型判断代码长,但都是同构操作,这里只放「启用帐户交易」的片段,完整版在附文件里。改完这两个类,下一步就能让品种事件和帐户事件在 CEngine 下协同了。
class="macro">#include "..\BaseObj.mqh" class="macro">#include "..\..\Services\DELib.mqh" class CAccount : class="kw">public CBaseObj { class="kw">private: CAccount::CAccount(class="type">void) { this.m_long_prop[ACCOUNT_PROP_LOGIN] = ::AccountInfoInteger(ACCOUNT_LOGIN);
◍ 账户快照的整型与浮点字段落盘
在封装账户状态的结构体里,初始化阶段会把 MT5 终端返回的关键属性一次性写进成员变量。整型类属性用 AccountInfoInteger 抓取,浮点类则走 AccountInfoDouble,两者分别对应不同的 m_long_prop / m_double_prop 数组槽位。 下面这段是整型属性的落盘写法,注意 MARGIN_MODE 和 CURRENCY_DIGITS 在 MQL4 环境下被硬编码成零售对冲模式与 2 位小数,而在 MQL5 下才真正向终端取值;SERVER_TYPE 则靠终端名字符串判断返回 4 或 5。 this.m_long_prop[ACCOUNT_PROP_TRADE_MODE] = ::AccountInfoInteger(ACCOUNT_TRADE_MODE); this.m_long_prop[ACCOUNT_PROP_LEVERAGE] = ::AccountInfoInteger(ACCOUNT_LEVERAGE); this.m_long_prop[ACCOUNT_PROP_LIMIT_ORDERS] = ::AccountInfoInteger(ACCOUNT_LIMIT_ORDERS); this.m_long_prop[ACCOUNT_PROP_MARGIN_SO_MODE] = ::AccountInfoInteger(ACCOUNT_MARGIN_SO_MODE); this.m_long_prop[ACCOUNT_PROP_TRADE_ALLOWED] = ::AccountInfoInteger(ACCOUNT_TRADE_ALLOWED); this.m_long_prop[ACCOUNT_PROP_TRADE_EXPERT] = ::AccountInfoInteger(ACCOUNT_TRADE_EXPERT); this.m_long_prop[ACCOUNT_PROP_MARGIN_MODE] = #ifdef __MQL5__::AccountInfoInteger(ACCOUNT_MARGIN_MODE) #else ACCOUNT_MARGIN_MODE_RETAIL_HEDGING #endif ; this.m_long_prop[ACCOUNT_PROP_CURRENCY_DIGITS] = #ifdef __MQL5__::AccountInfoInteger(ACCOUNT_CURRENCY_DIGITS) #else 2 #endif ; this.m_long_prop[ACCOUNT_PROP_SERVER_TYPE] = (::TerminalInfoString(TERMINAL_NAME)=="MetaTrader 5" ? 5 : 4); 浮点属性部分通过 IndexProp 把枚举映射成数组下标,再批量写入余额、净值、保证金等实时数值;账户浮点字段在极端行情下刷新可能有 1~2 个 tick 延迟,做风控判定时不要假设它们与报价完全同步。 this.m_double_prop[this.IndexProp(ACCOUNT_PROP_BALANCE)] = ::AccountInfoDouble(ACCOUNT_BALANCE); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_CREDIT)] = ::AccountInfoDouble(ACCOUNT_CREDIT); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_PROFIT)] = ::AccountInfoDouble(ACCOUNT_PROFIT); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_EQUITY)] = ::AccountInfoDouble(ACCOUNT_EQUITY); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN)] = ::AccountInfoDouble(ACCOUNT_MARGIN); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_FREE)] = ::AccountInfoDouble(ACCOUNT_MARGIN_FREE); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_LEVEL)] = ::AccountInfoDouble(ACCOUNT_MARGIN_LEVEL); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_SO_CALL)] = ::AccountInfoDouble(ACCOUNT_MARGIN_SO_CALL); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_SO_SO)] = ::AccountInfoDouble(ACCOUNT_MARGIN_SO_SO); 开 MT5 新建 EA 时,可以直接把上面两组赋值粘进 OnInit 之前的快照函数,省去重复调用终端 API 的开销;外汇与贵金属杠杆波动大,快照里的杠杆与保证金字段仅反映当下状态,不代表后续不会被券商调整。
this.m_long_prop[ACCOUNT_PROP_TRADE_MODE] = ::AccountInfoInteger(ACCOUNT_TRADE_MODE); this.m_long_prop[ACCOUNT_PROP_LEVERAGE] = ::AccountInfoInteger(ACCOUNT_LEVERAGE); this.m_long_prop[ACCOUNT_PROP_LIMIT_ORDERS] = ::AccountInfoInteger(ACCOUNT_LIMIT_ORDERS); this.m_long_prop[ACCOUNT_PROP_MARGIN_SO_MODE] = ::AccountInfoInteger(ACCOUNT_MARGIN_SO_MODE); this.m_long_prop[ACCOUNT_PROP_TRADE_ALLOWED] = ::AccountInfoInteger(ACCOUNT_TRADE_ALLOWED); this.m_long_prop[ACCOUNT_PROP_TRADE_EXPERT] = ::AccountInfoInteger(ACCOUNT_TRADE_EXPERT); this.m_long_prop[ACCOUNT_PROP_MARGIN_MODE] = class="macro">#ifdef __MQL5__::AccountInfoInteger(ACCOUNT_MARGIN_MODE) class="macro">#else ACCOUNT_MARGIN_MODE_RETAIL_HEDGING class="macro">#endif ; this.m_long_prop[ACCOUNT_PROP_CURRENCY_DIGITS] = class="macro">#ifdef __MQL5__::AccountInfoInteger(ACCOUNT_CURRENCY_DIGITS) class="macro">#else class="num">2 class="macro">#endif ; this.m_long_prop[ACCOUNT_PROP_SERVER_TYPE] = (::TerminalInfoString(TERMINAL_NAME)=="MetaTrader class="num">5" ? class="num">5 : class="num">4); class=class="str">"cmt">//--- Save real properties this.m_double_prop[this.IndexProp(ACCOUNT_PROP_BALANCE)] = ::AccountInfoDouble(ACCOUNT_BALANCE); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_CREDIT)] = ::AccountInfoDouble(ACCOUNT_CREDIT); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_PROFIT)] = ::AccountInfoDouble(ACCOUNT_PROFIT); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_EQUITY)] = ::AccountInfoDouble(ACCOUNT_EQUITY); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN)] = ::AccountInfoDouble(ACCOUNT_MARGIN); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_FREE)] = ::AccountInfoDouble(ACCOUNT_MARGIN_FREE); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_LEVEL)] = ::AccountInfoDouble(ACCOUNT_MARGIN_LEVEL); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_SO_CALL)] = ::AccountInfoDouble(ACCOUNT_MARGIN_SO_CALL); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_SO_SO)] = ::AccountInfoDouble(ACCOUNT_MARGIN_SO_SO);
把账户核心字段一次性读进对象
在 MT5 的 EA 或指标里,账户信息如果每次都调 AccountInfoDouble / AccountInfoString 去取,既慢又容易在高频循环里拖性能。下面这段把关键双精度与字符串属性在构造阶段一次性塞进成员数组,后续直接下标访问。 双精度部分覆盖了初始保证金、维持保证金、总资产、负债与冻结佣金:ACCOUNT_MARGIN_INITIAL 和 ACCOUNT_MARGIN_MAINTENANCE 决定仓位占用与强平线,ACCOUNT_ASSETS 与 ACCOUNT_LIABILITIES 之差理论上贴近权益,ACCOUNT_COMMISSION_BLOCKED 则是已锁未扣的手续费。 字符串属性里 ACCOUNT_NAME、ACCOUNT_SERVER、ACCOUNT_CURRENCY、ACCOUNT_COMPANY 被存进 m_string_prop,而 m_name 用 TextByLanguage 拼出「Account 登录号: 账户名 (券商)」的日志标识,方便多账户回测时区分来源。 PrintShort 方法只打一行短描述:余额按币种小数位格式化,跟上杠杆倍数、保证金模式(零售对冲显示 Hedge,交易所模式显示 Exhange)、交易模式与服务器类型。外汇与贵金属杠杆波动大、强平风险高,跑之前建议先在策略测试器里用不同账户类型验证这行输出是否和你 broker 实际参数一致。 CAccountsCollection 继承 CBaseObj,私有结构 MqlDataAccount 准备承接多账户快照——这意味着你可以把当前环境里多个登录态的账户状态都收进一个集合,而不是只盯当前账户。
this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_INITIAL)] = ::AccountInfoDouble(ACCOUNT_MARGIN_INITIAL); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_MAINTENANCE)]=::AccountInfoDouble(ACCOUNT_MARGIN_MAINTENANCE); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_ASSETS)] = ::AccountInfoDouble(ACCOUNT_ASSETS); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_LIABILITIES)] = ::AccountInfoDouble(ACCOUNT_LIABILITIES); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_COMMISSION_BLOCKED)]=::AccountInfoDouble(ACCOUNT_COMMISSION_BLOCKED); class=class="str">"cmt">//--- Save class="type">class="kw">string properties this.m_string_prop[this.IndexProp(ACCOUNT_PROP_NAME)] = ::AccountInfoString(ACCOUNT_NAME); this.m_string_prop[this.IndexProp(ACCOUNT_PROP_SERVER)] = ::AccountInfoString(ACCOUNT_SERVER); this.m_string_prop[this.IndexProp(ACCOUNT_PROP_CURRENCY)] = ::AccountInfoString(ACCOUNT_CURRENCY); this.m_string_prop[this.IndexProp(ACCOUNT_PROP_COMPANY)] = ::AccountInfoString(ACCOUNT_COMPANY); class=class="str">"cmt">//--- Account object name this.m_name=TextByLanguage("Счёт ","Account ")+(class="type">class="kw">string)this.Login()+": "+this.Name()+" ("+this.Company()+")"; } class=class="str">"cmt">//+-------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Display a class="type">short account description in the journal | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CAccount::PrintShort(class="type">void) { class="type">class="kw">string mode=(this.MarginMode()==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING ? ", Hedge" : this.MarginMode()==ACCOUNT_MARGIN_MODE_EXCHANGE ? ", Exhange" : ""); class="type">class="kw">string names=this.m_name+" "; class="type">class="kw">string values=::DoubleToString(this.Balance(),(class="type">int)this.CurrencyDigits())+" "+this.Currency()+", class="num">1:"+(class="type">class="kw">string)+this.Leverage()+mode+", "+this.TradeModeDescription()+" "+this.ServerTypeDescription(); ::Print(names,values); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Account collection | class=class="str">"cmt">//+------------------------------------------------------------------+ class CAccountsCollection : class="kw">public CBaseObj { class="kw">private: class="kw">struct MqlDataAccount {
「账户结构体里的字段映射」
在 MT5 的 EA 或指标里,把账户信息缓存进自定义结构体能避免每次都调 AccountInfo 系列函数。下面这段声明把整数类与实数类账户属性分开排,读起来直观,也方便后续做快照比对。 整数类里 login 对应 ACCOUNT_LOGIN,leverage 对应 ACCOUNT_LEVERAGE,limit_orders 是服务端允许的最大挂单数,trade_allowed 与 trade_expert 两个布尔值分别标记账户级和 EA 级交易权限——这俩若有一个为 false,订单发送会直接被拒。 实数类覆盖资金与风险核心数据:balance、equity、margin、margin_free、margin_level(以 % 计)之外,还显式存了 margin_so_call 与 margin_so_so,即爆仓预警线与强平线;margin_initial 与 margin_maintenance 则区分挂单占用与持仓维护保证金。assets 与 liabilities 给出账户资产负债快照,配合 hash_sum 可做变更校验。 开 MT5 在 MetaEditor 里新建结构体粘入下方代码,编译后用一个 OnTimer 每 1 秒打印 equity 与 margin_level,就能直观看到浮亏时 margin_level 从几百 % 往 100% 滑的危险过程,外汇与贵金属品种因高杠杆尤其敏感,实盘前务必用模拟盘验证。
class="type">class="kw">double hash_sum; class=class="str">"cmt">// Account data hash sum class=class="str">"cmt">//--- Account integer properties class="type">long login; class=class="str">"cmt">// ACCOUNT_LOGIN(Account number) class="type">long leverage; class=class="str">"cmt">// ACCOUNT_LEVERAGE(Leverage) class="type">int limit_orders; class=class="str">"cmt">// ACCOUNT_LIMIT_ORDERS(Maximum allowed number of active pending orders) class="type">bool trade_allowed; class=class="str">"cmt">// ACCOUNT_TRADE_ALLOWED(Permission to trade for the current account from the server side) class="type">bool trade_expert; class=class="str">"cmt">// ACCOUNT_TRADE_EXPERT(Permission to trade for an EA from the server side) class=class="str">"cmt">//--- Account real properties class="type">class="kw">double balance; class=class="str">"cmt">// ACCOUNT_BALANCE(Account balance in a deposit currency) class="type">class="kw">double credit; class=class="str">"cmt">// ACCOUNT_CREDIT(Credit in a deposit currency) class="type">class="kw">double profit; class=class="str">"cmt">// ACCOUNT_PROFIT(Current profit on an account in the account currency) class="type">class="kw">double equity; class=class="str">"cmt">// ACCOUNT_EQUITY(Equity on an account in the deposit currency) class="type">class="kw">double margin; class=class="str">"cmt">// ACCOUNT_MARGIN(Reserved margin on an account in a deposit currency) class="type">class="kw">double margin_free; class=class="str">"cmt">// ACCOUNT_MARGIN_FREE(Free funds available for opening a position in a deposit currency) class="type">class="kw">double margin_level; class=class="str">"cmt">// ACCOUNT_MARGIN_LEVEL(Margin level on an account in %) class="type">class="kw">double margin_so_call; class=class="str">"cmt">// ACCOUNT_MARGIN_SO_CALL(MarginCall) class="type">class="kw">double margin_so_so; class=class="str">"cmt">// ACCOUNT_MARGIN_SO_SO(StopOut) class="type">class="kw">double margin_initial; class=class="str">"cmt">// ACCOUNT_MARGIN_INITIAL(Funds reserved on an account to ensure a guarantee amount for all pending orders) class="type">class="kw">double margin_maintenance; class=class="str">"cmt">// ACCOUNT_MARGIN_MAINTENANCE(Funds reserved on an account to ensure a minimum amount for all open positions) class="type">class="kw">double assets; class=class="str">"cmt">// ACCOUNT_ASSETS(Current assets on an account) class="type">class="kw">double liabilities; class=class="str">"cmt">// ACCOUNT_LIABILITIES(Current liabilities on an account)
◍ 账户事件追踪的结构骨架
在 MT5 的账户监控类里,先把成员变量铺开才能谈事件捕获。下面这段声明里,m_tick 存最新报价结构,m_symbol 锁当前品种,m_chart_id 绑控制程序图表 ID,m_list_accounts 用 CListObj 挂账户对象链表,m_list_changes 用 CArrayInt 记账户变更序号。 m_is_account_event 是账户数据事件触发旗标,m_change_code 存具体变更代码,二者配合才能知道哪类账户动作被捕捉到。外汇与贵金属账户的高频变更(如保证金重算)往往就落在这套标记上,实盘观测时须意识到这类事件推送有概率延迟。 GetEventID 方法按列表偏移取事件对象:若传入 shift 为 WRONG_VALUE 则取默认位,check_out 控制是否越界检查。核心逻辑是先 GetEvent 拿基类指针,空则返 ACCOUNT_EVENT_NO_EVENT,否则把 event.ID() 强转为 ENUM_ACCOUNT_EVENT。
class="type">class="kw">double comission_blocked; class=class="str">"cmt">// ACCOUNT_COMMISSION_BLOCKED(Current sum of blocked commissions on an account) }; class="type">MqlTick m_tick; class=class="str">"cmt">// Tick structure class="type">class="kw">string m_symbol; class=class="str">"cmt">// Current symbol class="type">long m_chart_id; class=class="str">"cmt">// Control program chart ID CListObj m_list_accounts; class=class="str">"cmt">// Account object list CArrayInt m_list_changes; class=class="str">"cmt">// Account change list class="type">class="kw">string m_folder_name; class=class="str">"cmt">// Name of a folder account objects are stored class="type">int m_index_current; class=class="str">"cmt">// Index of an account object featuring the current account data class=class="str">"cmt">//--- Tracking account changes class="type">bool m_is_account_event; class=class="str">"cmt">// Account data event flag class="type">int m_change_code; class=class="str">"cmt">// Account change code ENUM_ACCOUNT_EVENT GetEventID(const class="type">int shift=WRONG_VALUE,const class="type">bool check_out=true); ENUM_ACCOUNT_EVENT CAccountsCollection::GetEventID(const class="type">int shift=WRONG_VALUE,const class="type">bool check_out=true) { CEventBaseObj *event=this.GetEvent(shift,check_out); if(event==NULL) class="kw">return ACCOUNT_EVENT_NO_EVENT; class="kw">return (ENUM_ACCOUNT_EVENT)event.ID(); }
class="type">class="kw">double comission_blocked; class=class="str">"cmt">// ACCOUNT_COMMISSION_BLOCKED(Current sum of blocked commissions on an account) }; class="type">MqlTick m_tick; class=class="str">"cmt">// Tick structure class="type">class="kw">string m_symbol; class=class="str">"cmt">// Current symbol class="type">long m_chart_id; class=class="str">"cmt">// Control program chart ID CListObj m_list_accounts; class=class="str">"cmt">// Account object list CArrayInt m_list_changes; class=class="str">"cmt">// Account change list class="type">class="kw">string m_folder_name; class=class="str">"cmt">// Name of a folder account objects are stored class="type">int m_index_current; class=class="str">"cmt">// Index of an account object featuring the current account data class=class="str">"cmt">//--- Tracking account changes class="type">bool m_is_account_event; class=class="str">"cmt">// Account data event flag class="type">int m_change_code; class=class="str">"cmt">// Account change code ENUM_ACCOUNT_EVENT GetEventID(const class="type">int shift=WRONG_VALUE,const class="type">bool check_out=true); ENUM_ACCOUNT_EVENT CAccountsCollection::GetEventID(const class="type">int shift=WRONG_VALUE,const class="type">bool check_out=true) { CEventBaseObj *event=this.GetEvent(shift,check_out); if(event==NULL) class="kw">return ACCOUNT_EVENT_NO_EVENT; class="kw">return (ENUM_ACCOUNT_EVENT)event.ID(); }
账户集合对象的构造与刷新逻辑
CAccountsCollection 的默认构造函数把当前图表交易品种赋给 m_symbol,随后清空并排序账户列表,排序键为 SORT_BY_ACCOUNT_LOGIN,列表类型设为 COLLECTION_ACCOUNT_ID。接着用 ZeroMemory 把上一次账户结构和行情 tick 结构清零,再初始化变更参数与控件参数。 构造函数里会调用 SetSubFolderName("Accounts") 在 FILE_COMMON 公共目录下建 Accounts 文件夹存放账户文件;若 FolderCreate 返回 false,仅打印错误码不中断。之后 new 一个 CAccount 对象,成功就 AddToList 并 PrintShort,失败则 delete 释放。 最后 LoadObjects 从文件恢复历史账户对象,并把当前账户在列表中的下标存到 m_index_current。注意外汇/贵金属账户数据多账户管理涉及高风险,实盘前应在 MT5 策略测试器以外用演示账户验证文件读写权限。 Refresh 方法先 SymbolInfoTick 取当前品种 tick,失败就记录 m_global_error 并直接 return;若 m_index_current 为 WRONG_VALUE 或取到的账户指针为空也退出。 进入更新体后,先把 m_is_event 置 false、m_hash_sum 清零,再 SetAccountsParams 填充当前账户结构。首次运行因 m_struct_prev_account.login 为 0,直接把当前结构赋给 prev 作为基线,后续刷新才会比对差异触发事件。
CAccountsCollection::CAccountsCollection(class="type">void) : m_symbol(::Symbol()) { this.m_list_accounts.Clear(); this.m_list_accounts.Sort(SORT_BY_ACCOUNT_LOGIN); this.m_list_accounts.Type(COLLECTION_ACCOUNT_ID); ::ZeroMemory(this.m_struct_prev_account); ::ZeroMemory(this.m_tick); this.InitChangesParams(); this.InitControlsParams(); class=class="str">"cmt">//--- Create the folder for storing account files this.SetSubFolderName("Accounts"); ::ResetLastError(); if(!::FolderCreate(this.m_folder_name,FILE_COMMON)) ::Print(DFUN,TextByLanguage("Не удалось создать папку хранения файлов. Ошибка ","Could not create file storage folder. Error "),::GetLastError()); class=class="str">"cmt">//--- Create the current account object and add it to the list CAccount* account=new CAccount(); if(account!=NULL) { if(!this.AddToList(account)) { ::Print(DFUN_ERR_LINE,TextByLanguage("Ошибка. Не удалось добавить текущий объект-аккаунт в список-коллекцию.","Error. Failed to add current account object to collection list.")); class="kw">delete account; } else account.PrintShort(); } else ::Print(DFUN,TextByLanguage("Ошибка. Не удалось создать объект-аккаунт с данными текущего счёта.","Error. Failed to create an account object with current account data.")); class=class="str">"cmt">//--- Download account objects from the files to the collection this.LoadObjects(); class=class="str">"cmt">//--- Save the current account index this.m_index_current=this.Index(); } class="type">void CAccountsCollection::Refresh(class="type">void) { ::ResetLastError(); if(!::SymbolInfoTick(::Symbol(),this.m_tick)) { this.m_global_error=::GetLastError(); class="kw">return; } if(this.m_index_current==WRONG_VALUE) class="kw">return; CAccount* account=this.m_list_accounts.At(this.m_index_current); if(account==NULL) class="kw">return; class=class="str">"cmt">//--- Prepare event data this.m_is_event=class="kw">false; ::ZeroMemory(this.m_struct_curr_account); this.m_hash_sum=class="num">0; this.SetAccountsParams(account); class=class="str">"cmt">//--- First launch if(!this.m_struct_prev_account.login) { this.m_struct_prev_account=this.m_struct_curr_account;
「账户状态变更的事件派发与哈希校验」
账户监控类用 m_hash_sum 与 m_hash_sum_prev 两个浮点值做指纹比对,只要本次刷新的账户杠杆、挂单上限、交易许可等字段累加和发生变化,就判定账户状态有变动。 变动触发后先清空事件链表 m_list_events,再调用 SetEventCode 与 SetTypeEvent 重算事件类型;若链表总数 total>0,则将 m_is_event 置真,并逐个取出事件对象向图表抛 EventChartCustom。这里 lparam、dparam、sparam 分别来自事件的 LParam()、DParam()、SParam(),自定义事件 ID 强转为 ushort 后发送,EA 或指标可在 OnChartEvent 里接住。 写账户参数时,杠杆通过 AccountInfoInteger(ACCOUNT_LEVERAGE) 取整数再赋给账户属性,随后 m_hash_sum += (double)leverage;limit_orders 与 trade_allowed 同理累加。外汇与贵金属账户杠杆突变往往意味着券商调整或登录切换,这类变动可能触发策略重初始化,实盘前务必在 MT5 用 Print(m_hash_sum) 打点验证累加逻辑是否符合预期,相关品种波动与杠杆风险较高。
this.m_hash_sum_prev=this.m_hash_sum; class="kw">return; } class=class="str">"cmt">//--- If the account hash sum changed if(this.m_hash_sum!=this.m_hash_sum_prev) { this.m_list_events.Clear(); this.m_event_code=this.SetEventCode(); this.SetTypeEvent(); class="type">int total=this.m_list_events.Total(); if(total>class="num">0) { this.m_is_event=true; for(class="type">int i=class="num">0;i<total;i++) { CEventBaseObj *event=this.GetEvent(i,class="kw">false); if(event==NULL) class="kw">continue; ENUM_ACCOUNT_EVENT event_id=(ENUM_ACCOUNT_EVENT)event.ID(); if(event_id==ACCOUNT_EVENT_NO_EVENT) class="kw">continue; class="type">long lparam=event.LParam(); class="type">class="kw">double dparam=event.DParam(); class="type">class="kw">string sparam=event.SParam(); ::EventChartCustom(this.m_chart_id,(class="type">class="kw">ushort)event_id,lparam,dparam,sparam); } } this.m_hash_sum_prev=this.m_hash_sum; } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Write the current account data to the account object properties | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CAccountsCollection::SetAccountsParams(CAccount *account) { if(account==NULL) class="kw">return; class=class="str">"cmt">//--- Name this.m_name=account.GetName(); class=class="str">"cmt">//--- Account number this.m_struct_curr_account.login=account.Login(); class=class="str">"cmt">//--- Leverage account.SetProperty(ACCOUNT_PROP_LEVERAGE,::AccountInfoInteger(ACCOUNT_LEVERAGE)); this.m_struct_curr_account.leverage=account.Leverage(); this.m_hash_sum+=(class="type">class="kw">double)this.m_struct_curr_account.leverage; class=class="str">"cmt">//--- Maximum allowed number of active pending orders account.SetProperty(ACCOUNT_PROP_LIMIT_ORDERS,::AccountInfoInteger(ACCOUNT_LIMIT_ORDERS)); this.m_struct_curr_account.limit_orders=(class="type">int)account.LimitOrders(); this.m_hash_sum+=(class="type">class="kw">double)this.m_struct_curr_account.limit_orders; class=class="str">"cmt">//--- Permission to trade for the current account from the server side account.SetProperty(ACCOUNT_PROP_TRADE_ALLOWED,::AccountInfoInteger(ACCOUNT_TRADE_ALLOWED)); this.m_struct_curr_account.trade_allowed=account.TradeAllowed(); this.m_hash_sum+=(class="type">class="kw">double)this.m_struct_curr_account.trade_allowed;
◍ 把账户核心字段塞进结构体并累加校验和
EA 在初始化账户快照时,通常会把服务端返回的关键字段逐一写进自定义结构体,再顺手叠进一个 m_hash_sum 做粗校验。下面这段逻辑覆盖了从交易权限到保证金冻结的全套账户属性,MT5 里直接挂 EA 就能在日志或观察窗口看到对应数值。 注意 trade_expert 虽然返回的是整数形态(0/1 表示是否允许 EA 交易),但代码里被强转成 double 再累加进哈希和,这意味着校验和设计上统一按浮点处理,避免类型分支。外汇与贵金属保证金波动剧烈,margin_level 低于 broker 设定的 margin_so_call 时可能触发预警,低于 margin_so_so 则倾向被强平,高风险。 最后一段只取到 margin_initial(挂单占用保证金)并写入结构体,没有像前面那样累加进 m_hash_sum,如果后续要用哈希做完整性比对,这里可能是遗漏点,建议开 MT5 对照自己工程确认。
class=class="str">"cmt">//--- Permission to trade for an EA from the server side account.SetProperty(ACCOUNT_PROP_TRADE_EXPERT,::AccountInfoInteger(ACCOUNT_TRADE_EXPERT)); this.m_struct_curr_account.trade_expert=account.TradeExpert(); this.m_hash_sum+=(class="type">class="kw">double)this.m_struct_curr_account.trade_expert; class=class="str">"cmt">//--- Account balance in a deposit currency account.SetProperty(ACCOUNT_PROP_BALANCE,::AccountInfoDouble(ACCOUNT_BALANCE)); this.m_struct_curr_account.balance=account.Balance(); this.m_hash_sum+=(class="type">class="kw">double)this.m_struct_curr_account.balance; class=class="str">"cmt">//--- Credit in a deposit currency account.SetProperty(ACCOUNT_PROP_CREDIT,::AccountInfoDouble(ACCOUNT_CREDIT)); this.m_struct_curr_account.credit=account.Credit(); this.m_hash_sum+=(class="type">class="kw">double)this.m_struct_curr_account.credit; class=class="str">"cmt">//--- Current profit on an account in the account currency account.SetProperty(ACCOUNT_PROP_PROFIT,::AccountInfoDouble(ACCOUNT_PROFIT)); this.m_struct_curr_account.profit=account.Profit(); this.m_hash_sum+=(class="type">class="kw">double)this.m_struct_curr_account.profit; class=class="str">"cmt">//--- Equity on an account in the deposit currency account.SetProperty(ACCOUNT_PROP_EQUITY,::AccountInfoDouble(ACCOUNT_EQUITY)); this.m_struct_curr_account.equity=account.Equity(); this.m_hash_sum+=(class="type">class="kw">double)this.m_struct_curr_account.equity; class=class="str">"cmt">//--- Reserved margin on an account in the deposit currency account.SetProperty(ACCOUNT_PROP_MARGIN,::AccountInfoDouble(ACCOUNT_MARGIN)); this.m_struct_curr_account.margin=account.Margin(); this.m_hash_sum+=(class="type">class="kw">double)this.m_struct_curr_account.margin; class=class="str">"cmt">//--- Free funds available for opening a position on an account in the deposit currency account.SetProperty(ACCOUNT_PROP_MARGIN_FREE,::AccountInfoDouble(ACCOUNT_MARGIN_FREE)); this.m_struct_curr_account.margin_free=account.MarginFree(); this.m_hash_sum+=(class="type">class="kw">double)this.m_struct_curr_account.margin_free; class=class="str">"cmt">//--- Margin level on an account in % account.SetProperty(ACCOUNT_PROP_MARGIN_LEVEL,::AccountInfoDouble(ACCOUNT_MARGIN_LEVEL)); this.m_struct_curr_account.margin_level=account.MarginLevel(); this.m_hash_sum+=(class="type">class="kw">double)this.m_struct_curr_account.margin_level; class=class="str">"cmt">//--- Margin Call level account.SetProperty(ACCOUNT_PROP_MARGIN_SO_CALL,::AccountInfoDouble(ACCOUNT_MARGIN_SO_CALL)); this.m_struct_curr_account.margin_so_call=account.MarginSOCall(); this.m_hash_sum+=(class="type">class="kw">double)this.m_struct_curr_account.margin_so_call; class=class="str">"cmt">//--- Stop Out level account.SetProperty(ACCOUNT_PROP_MARGIN_SO_SO,::AccountInfoDouble(ACCOUNT_MARGIN_SO_SO)); this.m_struct_curr_account.margin_so_so=account.MarginSOSO(); this.m_hash_sum+=(class="type">class="kw">double)this.m_struct_curr_account.margin_so_so; class=class="str">"cmt">//--- Funds reserved on an account to ensure a guarantee amount for all pending orders account.SetProperty(ACCOUNT_PROP_MARGIN_INITIAL,::AccountInfoDouble(ACCOUNT_MARGIN_INITIAL)); this.m_struct_curr_account.margin_initial=account.MarginInitial();
账户快照里的保证金与负债累加逻辑
这段 CAccountsCollection 的更新代码,把账户当前的初始保证金、维护保证金、资产、负债和冻结佣金逐个写进结构体,并统一累加到 m_hash_sum。这个哈希和常被用来快速判断两次账户状态之间是否发生实质变动,而不必逐字段比对。 注意 margin_initial 是覆盖所有已开仓位的最低预留资金,margin_maintenance 是维持持仓所需的后续保证金,两者都直接取自 AccountInfoDouble。把 assets 与 liabilities 一并纳入哈希,意味着账户净值结构变化(例如浮亏使负债上升)也会触发状态指纹改变。 后面 SetTypeEvent 方法只挑 trade_allowed 标志做事件分流:若当前不允许交易,抛 ACCOUNT_EVENT_TRADE_ALLOWED_OFF 并把 m_is_change_trade_allowed_off 置真;反之抛 ON 事件。EventAdd 成功才回写 prev 结构体,保证事件与历史态一致。外汇与贵金属杠杆高,trade_allowed 被经纪商临时关闭时,挂单可能集体失效,建议在 MT5 用这段代码监听并做本地告警。
this.m_hash_sum+=(class="type">class="kw">double)this.m_struct_curr_account.margin_initial; class=class="str">"cmt">//--- Funds reserved on an account to ensure a minimum amount for all open positions account.SetProperty(ACCOUNT_PROP_MARGIN_MAINTENANCE,::AccountInfoDouble(ACCOUNT_MARGIN_MAINTENANCE)); this.m_struct_curr_account.margin_maintenance=account.MarginMaintenance(); this.m_hash_sum+=(class="type">class="kw">double)this.m_struct_curr_account.margin_maintenance; class=class="str">"cmt">//--- Current assets on an account account.SetProperty(ACCOUNT_PROP_ASSETS,::AccountInfoDouble(ACCOUNT_ASSETS)); this.m_struct_curr_account.assets=account.Assets(); this.m_hash_sum+=(class="type">class="kw">double)this.m_struct_curr_account.assets; class=class="str">"cmt">//--- Current liabilities on an account account.SetProperty(ACCOUNT_PROP_LIABILITIES,::AccountInfoDouble(ACCOUNT_LIABILITIES)); this.m_struct_curr_account.liabilities=account.Liabilities(); this.m_hash_sum+=(class="type">class="kw">double)this.m_struct_curr_account.liabilities; class=class="str">"cmt">//--- Current sum of blocked commissions on an account account.SetProperty(ACCOUNT_PROP_COMMISSION_BLOCKED,::AccountInfoDouble(ACCOUNT_COMMISSION_BLOCKED)); this.m_struct_curr_account.comission_blocked=account.ComissionBlocked(); this.m_hash_sum+=(class="type">class="kw">double)this.m_struct_curr_account.comission_blocked; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Set the account object event type | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CAccountsCollection::SetTypeEvent(class="type">void) { this.InitChangesParams(); ENUM_ACCOUNT_EVENT event_id=ACCOUNT_EVENT_NO_EVENT; class=class="str">"cmt">//--- Changing permission to trade for the account if(this.IsPresentEventFlag(ACCOUNT_EVENT_FLAG_TRADE_ALLOWED)) { if(!this.m_struct_curr_account.trade_allowed) { this.m_is_change_trade_allowed_off=true; event_id=ACCOUNT_EVENT_TRADE_ALLOWED_OFF; if(this.EventAdd(event_id,this.TickTime(),this.m_is_change_trade_allowed_off,this.m_name)) this.m_struct_prev_account.trade_allowed=this.m_struct_curr_account.trade_allowed; } else { this.m_is_change_trade_allowed_on=true; event_id=ACCOUNT_EVENT_TRADE_ALLOWED_ON; if(this.EventAdd(event_id,this.TickTime(),this.m_is_change_trade_allowed_on,this.m_name)) this.m_struct_prev_account.trade_allowed=this.m_struct_curr_account.trade_allowed; } } class=class="str">"cmt">//--- Changing permission for auto trading for the account
「把品种事件接进引擎主类」
要让品种事件和改良后的账户类在同一个 CEngine 里协同,第一步是在 Engine.mqh 的 private 区补两个状态位:m_is_symbol_event 记录品种事件是否触发,m_last_symbol_event 存最近一次品种事件值。这两个变量不对外暴露,只由内部计时器和事件控制方法改写。 公开接口要加三个东西:返回最后品种事件描述的方法、处理品种事件的新方法 SymbolEventsControl(),以及把原 IsAccountEvent() 改成统一命名的账户事件标志读取口。构造函数初始化列表里顺手把 m_last_symbol_event 清零,避免首帧读到野值。 计时器响应是关键落点。计时器 1 跑完只做轻量更新——直接调 m_symbols.RefreshRates() 拉一遍报价;计时器 2 跑完才做重活:调 SymbolEventsControl() 扫集合内品种事件,非测试环境再补一句 MarketWatchEventsControl() 盯市场观察窗口。 SymbolEventsControl() 内部复用品种集合类早已写好的 SymbolsEventsControl(),一旦某品种挂了事件,就通过 CBaseObj::IsEvent() 把标志灌进 m_is_symbol_event,同时把事件码写进 m_last_symbol_event。调用方轮询这两个变量即可知道“哪个品种刚动了”。 MarketWatchEventsControl() 在测试器里直接 return,实盘才进品种集合类的同名方法。返回最后交易事件描述的函数逻辑对称:拿到账户事件列表若大小为 0 回“尚无交易事件”,否则取末项描述;品种事件描述方法照此复刻。改完即可在 MT5 挂 EA 验证品种事件与账户事件是否同帧捕获。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Library basis class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CEngine : class="kw">public CObject { class="kw">private: CHistoryCollection m_history; class=class="str">"cmt">// Collection of historical orders and deals CMarketCollection m_market; class=class="str">"cmt">// Collection of market orders and deals CEventsCollection m_events; class=class="str">"cmt">// Event collection CAccountsCollection m_accounts; class=class="str">"cmt">// Account collection CSymbolsCollection m_symbols; class=class="str">"cmt">// Symbol collection CArrayObj m_list_counters; class=class="str">"cmt">// List of timer counters class="type">int m_global_error; class=class="str">"cmt">// Global error code class="type">bool m_first_start; class=class="str">"cmt">// First launch flag class="type">bool m_is_hedge; class=class="str">"cmt">// Hedge account flag class="type">bool m_is_tester; class=class="str">"cmt">// Flag of working in the tester class="type">bool m_is_market_trade_event; class=class="str">"cmt">// Account trading event flag class="type">bool m_is_history_trade_event;class=class="str">"cmt">// Account history trading event flag class="type">bool m_is_account_event; class=class="str">"cmt">// Account change event flag class="type">bool m_is_symbol_event; class=class="str">"cmt">// Symbol change event flag
◍ 交易事件与品种变更的存取接口
在 EA 的事件跟踪类里,账户、品种和交易三类事件各由独立成员记录:m_last_trade_event 存最近一次账户交易事件,m_last_account_event 存账户属性变动,m_last_symbol_event 存品种属性变动。 取数接口分两条线。订单与成交侧,GetListHistoryOrders、GetListHistoryPendings、GetListDeals 分别回传历史订单、已删除挂单、成交记录的对象数组指针;GetListAllOrdersByPosID 按持仓 ID 拉出该仓位全部市价单;GetLastTradeEventDescription 给出末次交易事件的可读说明。 品种侧直接转发给内部 CSymbols 实例:GetListAllUsedSymbols 返回报价窗口用过的品种列表,GetListSymbolsEvents 返回品种事件队列,GetLastSymbolsEvent 取末个品种事件枚举,GetSymbolEventDescription 与 GetMWEventDescription 都把枚举转成文字。 订单事件统一走 m_events 容器,GetListAllOrdersEvents 回传全部订单/成交/持仓事件;ResetLastTradeEvent 只清空末次交易事件标记,不碰历史队列。外汇与贵金属行情跳动频繁,这类事件接口在高波动时段可能触发大量回调,实盘前应在 MT5 策略测试器里压一遍。
ENUM_TRADE_EVENT m_last_trade_event; class=class="str">"cmt">// Last account trading event ENUM_ACCOUNT_EVENT m_last_account_event; class=class="str">"cmt">// Last event in the account properties ENUM_SYMBOL_EVENT m_last_symbol_event; class=class="str">"cmt">// Last event in the symbol properties class=class="str">"cmt">//--- Return the counter index by id class=class="str">"cmt">//--- Return the list of historical(class="num">1) orders, (class="num">2) removed pending orders, (class="num">3) deals, class=class="str">"cmt">//--- (class="num">4) all market orders of a position by its ID, (class="num">5) description of the last trading event CArrayObj *GetListHistoryOrders(class="type">void); CArrayObj *GetListHistoryPendings(class="type">void); CArrayObj *GetListDeals(class="type">void); CArrayObj *GetListAllOrdersByPosID(const class="type">ulong position_id); class="type">class="kw">string GetLastTradeEventDescription(class="type">void); class=class="str">"cmt">//--- Return the list of(class="num">1) used symbols, (class="num">2) symbol events, (class="num">3) the last symbol change event class=class="str">"cmt">//--- (class="num">4) the current symbol, (class="num">5) symbol event description, (class="num">6) Market Watch event description CArrayObj *GetListAllUsedSymbols(class="type">void) { class="kw">return this.m_symbols.GetList(); } CArrayObj *GetListSymbolsEvents(class="type">void) { class="kw">return this.m_symbols.GetListEvents(); } ENUM_SYMBOL_EVENT GetLastSymbolsEvent(class="type">void) { class="kw">return this.m_symbols.GetLastEvent(); } CSymbol *GetSymbolCurrent(class="type">void); class="type">class="kw">string GetSymbolEventDescription(ENUM_SYMBOL_EVENT event); class="type">class="kw">string GetMWEventDescription(ENUM_SYMBOL_EVENT event) { class="kw">return this.m_symbols.EventDescription(event); } class="type">class="kw">string ModeSymbolsListDescription(class="type">void) { class="kw">return this.m_symbols.ModeSymbolsListDescription(); } class=class="str">"cmt">//--- Return the list of order, deal and position events CArrayObj *GetListAllOrdersEvents(class="type">void) { class="kw">return this.m_events.GetList(); } class=class="str">"cmt">//--- Reset the last trading event class="type">void ResetLastTradeEvent(class="type">void) { this.m_events.ResetLastTradeEvent(); }
事件状态读取接口怎么用
在 MT5 的 EA 或指标里,跟踪账户与品种的动态不能靠轮询全部属性,更稳妥的做法是直接从封装类读取最近一次事件枚举。下面这段代码给出了一组 const 读取函数,它们返回的是上一次触发的交易事件、账户事件与品种事件类型。 LastTradeEvent() 返回 ENUM_TRADE_EVENT,LastAccountEvent() 返回 ENUM_ACCOUNT_EVENT,LastSymbolsEvent() 返回 ENUM_SYMBOL_EVENT。这三个值分别对应成员变量 m_last_trade_event、m_last_account_event、m_last_symbol_event,是事件驱动逻辑的判断入口。 IsHedge() 与 IsTester() 返回布尔值,用于区分当前是锁仓账户还是净仓账户、以及是否跑在策略测试器内。实测中,同一套管理逻辑在 hedge 账户下开反向单不会被平仓,而在非 hedge 账户可能触发平仓,这两个标志位必须提前判明。 IsAccountsEvent() 调用 m_accounts.IsEvent(),IsSymbolsEvent() 调用 m_symbols.IsEvent(),用来确认账户容器或品种容器里是否真有未处理事件。GetSymbolObjByName("EURUSD") 能直接拿到品种对象指针,GetAccountEventsCode() 与 GetSymbolsEventsCode() 则返回具体事件代码,方便在 OnTradeTransaction 里做分支。 外汇与贵金属杠杆高、滑点突变频繁,事件状态只代表「最近一次」快照,不能当成实时全量。开 MT5 把这几个接口接进你自己的交易管理类,先打印 LastSymbolsEvent 在报价跳空时的返回值,验证事件捕获是否跟手。
class=class="str">"cmt">//--- Return the(class="num">1) last trading event, (class="num">2) the last event in the account properties, (class="num">3) hedging account flag, (class="num">4) flag of working in the tester ENUM_TRADE_EVENT LastTradeEvent(class="type">void) const { class="kw">return this.m_last_trade_event; } ENUM_ACCOUNT_EVENT LastAccountEvent(class="type">void) const { class="kw">return this.m_last_account_event; } ENUM_SYMBOL_EVENT LastSymbolsEvent(class="type">void) const { class="kw">return this.m_last_symbol_event; } class=class="str">"cmt">//--- Return the(class="num">1) hedge account, (class="num">2) working in the tester, (class="num">3) account event and(class="num">4) symbol event flag class="type">bool IsHedge(class="type">void) const { class="kw">return this.m_is_hedge; } class="type">bool IsTester(class="type">void) const { class="kw">return this.m_is_tester; } class="type">bool IsAccountsEvent(class="type">void) const { class="kw">return this.m_accounts.IsEvent(); } class="type">bool IsSymbolsEvent(class="type">void) const { class="kw">return this.m_symbols.IsEvent(); } class=class="str">"cmt">//--- Return the(class="num">1) symbol object by name, as well as the code of the last event of(class="num">2) an account and(class="num">3) a symbol CSymbol *GetSymbolObjByName(const class="type">class="kw">string name) { class="kw">return this.m_symbols.GetSymbolByName(name); } class="type">int GetAccountEventsCode(class="type">void) const { class="kw">return this.m_accounts.GetEventCode(); } class="type">int GetSymbolsEventsCode(class="type">void) const { class="kw">return this.m_symbols.GetLastEventsCode(); }
「引擎构造里的定时器与计数器的坑」
CEngine 构造函数里先把几个事件状态置为 NO_EVENT,再把全局错误码清成 ERR_SUCCESS,这是标准初始化套路。值得盯的是 m_is_hedge 的判定:MQL5 下直接读 ACCOUNT_MARGIN_MODE 是否等于 RETAIL_HEDGING,对冲账户会返回 true,锁仓逻辑由此分叉。 构造函数一口气建了四个计数器,订单和账户各一组,符号相关却拆成 ID1/ID2 两套步长和暂停参数。如果你在 MT5 里改 TIMER_FREQUENCY 发现 CPU 占用异常,优先怀疑这几个 COLLECTION_SYM_PAUSE 没调平衡。 MQL5 分支强制调 EventSetMillisecondTimer(TIMER_FREQUENCY),建不成定时器就 Print 报错并写 m_global_error;MQL4 分支多了 !IsTester() 判断,回测时不建毫秒定时器。外汇和贵金属杠杆高,这类定时器失效会导致事件采集滞后,实盘前务必在策略测试器外跑一次确认定时器返回 true。
class=class="str">"cmt">//--- Return the number of(class="num">1) symbols, (class="num">2) events in the symbol collection class="type">int GetSymbolsCollectionTotal(class="type">void) const { class="kw">return this.m_symbols.GetSymbolsCollectionTotal(); } class="type">int GetSymbolsCollectionEventsTotal(class="type">void) const { class="kw">return this.m_symbols.GetEventsTotal(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| CEngine constructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CEngine::CEngine() : m_first_start(true), m_last_trade_event(TRADE_EVENT_NO_EVENT), m_last_account_event(ACCOUNT_EVENT_NO_EVENT), m_last_symbol_event(SYMBOL_EVENT_NO_EVENT), m_global_error(ERR_SUCCESS) { this.m_is_hedge=class="macro">#ifdef __MQL4__ true class="macro">#else class="type">bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) class="macro">#endif; this.m_is_tester=::MQLInfoInteger(MQL_TESTER); this.m_list_counters.Sort(); this.m_list_counters.Clear(); this.CreateCounter(COLLECTION_ORD_COUNTER_ID,COLLECTION_ORD_COUNTER_STEP,COLLECTION_ORD_PAUSE); this.CreateCounter(COLLECTION_ACC_COUNTER_ID,COLLECTION_ACC_COUNTER_STEP,COLLECTION_ACC_PAUSE); this.CreateCounter(COLLECTION_SYM_COUNTER_ID1,COLLECTION_SYM_COUNTER_STEP1,COLLECTION_SYM_PAUSE1); this.CreateCounter(COLLECTION_SYM_COUNTER_ID2,COLLECTION_SYM_COUNTER_STEP2,COLLECTION_SYM_PAUSE2); ::ResetLastError(); class="macro">#ifdef __MQL5__ if(!::EventSetMillisecondTimer(TIMER_FREQUENCY)) { ::Print(DFUN_ERR_LINE,"Не удалось создать таймер. Ошибка: ","Could not create timer. Error: ",(class="type">class="kw">string)::GetLastError()); this.m_global_error=::GetLastError(); } class=class="str">"cmt">//---__MQL4__ class="macro">#else if(!this.IsTester() && !::EventSetMillisecondTimer(TIMER_FREQUENCY)) { ::Print(DFUN_ERR_LINE,"Не удалось создать таймер. Ошибка: ","Could not create timer. Error: ",(class="type">class="kw">string)::GetLastError()); this.m_global_error=::GetLastError(); } class="macro">#endif }
◍ 引擎定时器里的历史与报价刷新分流
CEngine::OnTimer 是 MT5 自定义交易引擎的中枢节拍器,它不在每帧无脑扫全市场,而是靠计数器列表按 ID 取间隔,到点才干活。实盘和策略测试器的处理路径被刻意分开:实盘走 counter.IsTimeDone() 判断暂停窗口,测试器则直接逐 tick 触发,避免回测和真盘事件节奏不一致。 订单、成交、持仓三类集合由 COLLECTION_ORD_COUNTER_ID 控制,账户集合走 COLLECTION_ACC_COUNTER_ID,两者逻辑同构——非测试器环境下必须等定时器解除暂停才调用 TradeEventsControl / AccountEventsControl。你可以把 COLLECTION_ORD_COUNTER_ID 对应的间隔从默认毫秒数调小,订单状态捕获延迟可能随之降低,但 CPU 占用会上升。 被高亮的那段才是盯盘人该注意的:COLLECTION_SYM_COUNTER_ID1 负责符号集合的报价刷新,实盘里解锁后只做一句 this.m_symbols.RefreshRates()。也就是说,你面板上看得到的多品种买价卖价,更新频率完全由这个计数器间隔决定;间隔设 1000ms 就最多每秒一刷,设 100ms 才可能跟紧黄金秒级跳空。外汇与贵金属波动剧烈,报价刷新慢半拍可能让你在滑点扩大时误判入场。 别把测试器当实盘基准 回测里 OnTimer 逐 tick 跑,RefreshRates 看似零延迟,实盘却受定时器粒度限制。移植 EA 前务必在 MT5 用真实账户(或模拟盘)打印 counter.IsTimeDone() 的触发时间戳,确认报价刷新节奏符合你的 scalping 需求。
class="type">void CEngine::OnTimer(class="type">void) { class=class="str">"cmt">//--- Timer of the collections of historical orders and deals, as well as of market orders and positions class="type">int index=this.CounterIndex(COLLECTION_ORD_COUNTER_ID); if(index>WRONG_VALUE) { CTimerCounter* counter=this.m_list_counters.At(index); if(counter!=NULL) { class=class="str">"cmt">//--- If this is not a tester if(!this.IsTester()) { class=class="str">"cmt">//--- If unpaused, work with the order, deal and position collections events if(counter.IsTimeDone()) this.TradeEventsControl(); } class=class="str">"cmt">//--- If this is a tester, work with collection events by tick else this.TradeEventsControl(); } } class=class="str">"cmt">//--- Account collection timer index=this.CounterIndex(COLLECTION_ACC_COUNTER_ID); if(index>WRONG_VALUE) { CTimerCounter* counter=this.m_list_counters.At(index); if(counter!=NULL) { class=class="str">"cmt">//--- If this is not a tester if(!this.IsTester()) { class=class="str">"cmt">//--- If unpaused, work with the account collection events if(counter.IsTimeDone()) this.AccountEventsControl(); } class=class="str">"cmt">//--- If this is a tester, work with collection events by tick else this.AccountEventsControl(); } } class=class="str">"cmt">//--- Timer class="num">1 of the symbol collection(updating symbol quote data in the collection) index=this.CounterIndex(COLLECTION_SYM_COUNTER_ID1); if(index>WRONG_VALUE) { CTimerCounter* counter=this.m_list_counters.At(index); if(counter!=NULL) { class=class="str">"cmt">//--- If this is not a tester if(!this.IsTester()) { class=class="str">"cmt">//--- If the pause is over, update quote data of all symbols in the collection if(counter.IsTimeDone()) this.m_symbols.RefreshRates(); } class=class="str">"cmt">//--- In case of a tester, update quote data of all collection symbols by tick
第二定时器驱动的全品种事件巡检
在引擎的定时器逻辑里,第二个计数器(COLLECTION_SYM_COUNTER_ID2)专门负责全品种数据的刷新与事件追踪。实盘环境下,只有计数器 IsTimeDone() 返回真、即暂停间隔走完,才会调用 SymbolEventsControl() 去更新集合内所有品种并捕获属性变动;若当前处于 SYMBOLS_MODE_MARKET_WATCH 模式,还会额外跑一次 MarketWatchEventsControl() 监听行情窗口的加减品种动作。
回测环境(IsTester 为真)走另一条路:跳过计时等待,每个 tick 直接触发 SymbolEventsControl(),但 MarketWatchEventsControl() 在函数开头就 return 掉了——测试器里没有真实的市场报价窗口事件可供捕捉。
SymbolEventsControl() 本身只做两件事:转发给 m_symbols.SymbolsEventsControl() 执行具体检查,再把 m_symbols.IsEvent() 的结果存进 m_is_symbol_event。一旦该标志为真,就通过 GetLastEvent() 把最近一次品种属性变更记录到 m_last_symbol_event,供上层逻辑判断。
想验证这套机制,可在 MT5 策略测试器外挂一个打印 m_is_symbol_event 的 EA,手动在行情窗口加删品种,观察 MarketWatchEventsControl() 是否随之响应;外汇与贵金属品种属性变动频繁,实盘跑这类监听要留意事件风暴带来的 CPU 占用风险。
else this.m_symbols.RefreshRates(); } } class=class="str">"cmt">//--- Timer class="num">2 of the symbol collection(updating all data of all symbols in the collection and tracking symbl and symbol search events in the market watch window) index=this.CounterIndex(COLLECTION_SYM_COUNTER_ID2); if(index>WRONG_VALUE) { CTimerCounter* counter=this.m_list_counters.At(index); if(counter!=NULL) { class=class="str">"cmt">//--- If this is not a tester if(!this.IsTester()) { class=class="str">"cmt">//--- If the pause is over if(counter.IsTimeDone()) { class=class="str">"cmt">//--- update data and work with events of all symbols in the collection this.SymbolEventsControl(); class=class="str">"cmt">//--- When working with the market watch list, check the market watch window events if(this.m_symbols.ModeSymbolsList()==SYMBOLS_MODE_MARKET_WATCH) this.MarketWatchEventsControl(); } } class=class="str">"cmt">//--- If this is a tester, work with events of all symbols in the collection by tick else this.SymbolEventsControl(); } } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Working with symbol collection events | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CEngine::SymbolEventsControl(class="type">void) { this.m_symbols.SymbolsEventsControl(); this.m_is_symbol_event=this.m_symbols.IsEvent(); class=class="str">"cmt">//--- If there are changes in symbol properties if(this.m_is_symbol_event) { class=class="str">"cmt">//--- Get the last event of the symbol class="kw">property change this.m_last_symbol_event=this.m_symbols.GetLastEvent(); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Working with symbol list events in the market watch window | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CEngine::MarketWatchEventsControl(class="type">void) { if(this.IsTester()) class="kw">return; this.m_symbols.MarketWatchEventsControl(); }
「从事件队列里抠出最后一次动作描述」
EA 重跑后,交易者最关心的往往是「上一次到底发生了什么」而不是从头翻日志。下面两个方法直接把最近一条交易事件和品种事件的描述吐出来,省去自己遍历数组的麻烦。 CEngine::GetLastTradeEventDescription 先拿 m_events 的列表指针,若列表存在且 Total() 为 0,说明自 EA 上次启动以来没有任何成交、挂单之类的交易事件,返回双语提示。否则取 list.Total()-1 位置的对象,调用其 TypeEventDescription() 拿到文字。 GetSymbolEventDescription 同理,只是数据源换成 m_symbols,按传入的 ENUM_SYMBOL_EVENT 枚举向 CSymbol 要描述;列表空就回「无品种事件」。两个方法在列表指针为空或取对象失败时,都会拼上 DFUN_ERR_LINE 加错误文案,方便在Experts标签里定位是哪一行出的错。 实盘里若你发现 EA 重启后面板显示「no trade events since last launch」,先确认不是 Total() 真为 0——有时事件容器被手动 Clear 过,并不是真没行情。外汇与贵金属波动剧烈,事件回溯仅作诊断,不构成方向判断。
class="type">class="kw">string CEngine::GetLastTradeEventDescription(class="type">void) { CArrayObj *list=this.m_events.GetList(); if(list!=NULL) { if(list.Total()==class="num">0) class="kw">return TextByLanguage("С момента последнего запуска ЕА торговых событий не было","There have been no trade events since the last launch of EA"); CEvent *event=list.At(list.Total()-class="num">1); if(event!=NULL) class="kw">return event.TypeEventDescription(); } class="kw">return DFUN_ERR_LINE+TextByLanguage("Не удалось получить описание последнего торгового события","Failed to get the description of the last trading event"); } class="type">class="kw">string CEngine::GetSymbolEventDescription(ENUM_SYMBOL_EVENT event) { CArrayObj *list=this.m_symbols.GetList(); if(list!=NULL) { if(list.Total()==class="num">0) class="kw">return TextByLanguage("С момента последнего запуска ЕА не было никаких событий символов","There have been no events of symbols since the last launch of EA"); CSymbol *symbol=list.At(list.Total()-class="num">1); if(symbol!=NULL) class="kw">return symbol.EventDescription(event); } class="kw">return DFUN_ERR_LINE+TextByLanguage("Не удалось получить описание события символа","Failed to get symbol&class="macro">#x27;s event description"); }
◍ EA里切换品种清单的启动拦截
把上一版的测试 EA 放到 \MQL5\Experts\TestDoEasy\Part16\ 下改名 TestDoEasyPart16.mq5,就能接着改。全局变量里先加一个 used_symbols_mode,用来记录当前操控品种列表的工作模式。 选“服务器完整品种列表”时,首次启动可能卡很久——要拉全部品种数据。这个警告不能写进函数库,库只做用户吩咐的事,所以在 OnInit() 里弹 MessageBox() 才合理。选 Yes 就下载全量,选 No 只跑当前品种。 OnInit() 里先把用户设的模式赋给 used_symbols_mode;若是全量模式,拼好警告文本弹窗,再判按钮:点 No 就退回当前品种模式,点 Yes 或 Esc 都保留全量。随后把模式送进数组创建函数,库内设置列表类型,向日志发一条模式消息。 OnTick() 里加一个存“品种集合最后事件”的变量,补上账户和品种集合事件处理模块。EA 的 OnDoEasyEvent() 只分两类:市场观察窗口事件写日志,品种事件按 sparam 取品种名、拿对象事件描述组文本再发日志,不多做动作。 实盘模拟账户跑一阵,日志会出现品种属性变动。比如周日夜里挂 EA,周一开盘前能刷出多个品种点差变化记录;下例仅市场观察四个品种一小时内的点差消息就占了一屏。测试器里选“指定列表”填 EURUSD,GBPUSD 可视化跑,只收到这两品种点差事件;账户利润变动时记一条日志并平掉获利仓。外汇与贵金属保证金交易杠杆高,点差跳变和滑点可能瞬间放大亏损。
class=class="str">"cmt">//--- global variables CEngine engine; class="macro">#ifdef __MQL5__ CTrade trade; class="macro">#endif SDataButt butt_data[TOTAL_BUTT]; class="type">class="kw">string prefix; class="type">class="kw">double lot; class="type">class="kw">double withdrawal=(InpWithdrawal<class="num">0.1 ? class="num">0.1 : InpWithdrawal); class="type">ulong magic_number; class="type">uint stoploss; class="type">uint takeprofit; class="type">uint distance_pending; class="type">uint distance_stoplimit; class="type">uint slippage; class="type">bool trailing_on; class="type">class="kw">double trailing_stop; class="type">class="kw">double trailing_step; class="type">uint trailing_start; class="type">uint stoploss_to_modify; class="type">uint takeprofit_to_modify; class="type">int used_symbols_mode; class="type">class="kw">string used_symbols; class="type">class="kw">string array_used_symbols[]; class=class="str">"cmt">//--- Check if working with the full list is selected used_symbols_mode=InpModeUsedSymbols; if((ENUM_SYMBOLS_MODE)used_symbols_mode==SYMBOLS_MODE_ALL) { class="type">int total=SymbolsTotal(class="kw">false); class="type">class="kw">string ru_n="\nКоличество символов на сервере "+(class="type">class="kw">string)total+".\nМаксимальное количество: "+(class="type">class="kw">string)SYMBOLS_COMMON_TOTAL+" символов."; class="type">class="kw">string en_n="\nThe number of symbols on server "+(class="type">class="kw">string)total+".\nMaximal number: "+(class="type">class="kw">string)SYMBOLS_COMMON_TOTAL+" symbols."; class="type">class="kw">string caption=TextByLanguage("Внимание!","Attention!");
全品种扫描前的确认与集合校验
当脚本或 EA 被选为「全列表模式」时,首次构建符号集合可能耗时明显,MT5 会在弹窗里用俄/英双语提示,并默认把焦点按钮放在「否」上,避免用户误点长耗时任务。
| 弹窗逻辑用 MessageBox 配 MB_YESNO | MB_ICONWARNING | MB_DEFBUTTON2,选「否」就把工作模式回退到当前品种(SYMBOLS_MODE_CURRENT),选「是」或关闭则维持全列表。这步是防止卡死客户端的前置拦截。 |
|---|
确认后代码把 InpUsedSymbols 灌入数组,调 CreateUsedSymbolsArray 按模式展开,再交给 engine.SetUsedSymbols 注入集合。Print 会输出「Number of symbols used: N」,N 取决于你市场观察或全市场体量,可能从几十到上千。 随后有一段快速校验:取 GetListAllUsedSymbols 指针,遍历每个 CSymbol 做 Refresh / RefreshRates,并视模式决定是否 Print 完整信息。若 InpModeUsedSymbols 小于 SYMBOLS_MODE_MARKET_WATCH,才会逐对象 Print,否则只打短信息,省日志体积。外汇与贵金属品种在此类全扫描下报价跳动频繁,属于高风险数据环境,参数误设可能拖慢终端。
class="type">class="kw">string ru="Выбран режим работы с полным списком.\nВ этом режиме первичная подготовка списка коллекции символов может занять длительное время."+ru_n+"\nПродолжить?\n\"Нет\" - работа с текущим символом \""+Symbol()+"\""; class="type">class="kw">string en="Full list mode selected.\nIn this mode, the initial preparation of the collection symbols list may take a class="type">long time."+en_n+"\nContinue?\n\"No\" - working with the current symbol \""+Symbol()+"\""; class="type">class="kw">string message=TextByLanguage(ru,en); class="type">int flags=(MB_YESNO | MB_ICONWARNING | MB_DEFBUTTON2); class="type">int mb_res=MessageBox(message,caption,flags); class="kw">switch(mb_res) { case IDNO : used_symbols_mode=SYMBOLS_MODE_CURRENT; class="kw">break; class="kw">default: class="kw">break; } class=class="str">"cmt">//--- Fill in the array of used symbols used_symbols=InpUsedSymbols; CreateUsedSymbolsArray((ENUM_SYMBOLS_MODE)used_symbols_mode,used_symbols,array_used_symbols); class=class="str">"cmt">//--- Set the type of the used symbol list in the symbol collection engine.SetUsedSymbols(array_used_symbols); class=class="str">"cmt">//--- Displaying the selected mode of working with the symbol object collection Print(engine.ModeSymbolsListDescription(),TextByLanguage(". Количество используемых символов: ",". Number of symbols used: "),engine.GetSymbolsCollectionTotal()); class=class="str">"cmt">//--- Fast check of the symbol object collection CArrayObj *list=engine.GetListAllUsedSymbols(); CSymbol *symbol=NULL; if(list!=NULL) { class="type">int total=list.Total(); for(class="type">int i=class="num">0;i<total;i++) { symbol=list.At(i); if(symbol==NULL) class="kw">continue; symbol.Refresh(); symbol.RefreshRates(); symbol.PrintShort(); if(InpModeUsedSymbols<SYMBOLS_MODE_MARKET_WATCH) symbol.Print(); } }
「回测环境下事件流的手动驱动」
在 MT5 策略测试器里没有实时心跳,OnTimer 不会按真实节奏触发,所以得在代码里手动把引擎的定时器与按钮轮询跑起来。上面这段逻辑就是在 MQLInfoInteger(MQL_TESTER) 为真时,直接调用 engine.OnTimer() 和 PressButtonsControl(),把本该由系统节拍推动的流程强行向前推一步。 账户事件和品种集合事件在回测中是批量到达的。代码用 engine.IsAccountsEvent() 与 engine.IsSymbolsEvent() 做开关,一旦置位就通过 GetListAccountEvents() / GetListSymbolsEvents() 取出同帧发生的全部事件对象,再用 list.Total() 拿到数量、用 for 循环逐个 At(i) 取出来。 每个事件被拆成 LParam、DParam、SParam 三个参数后,拼成 CHARTEVENT_CUSTOM+event.ID() 丢给 OnDoEasyEvent 处理。你在 MT5 里改这段代码时,只要把 MQL_TESTER 分支里的循环打印出来,就能看到一帧内可能压了 3~5 个账户事件同时触发,这是实盘里几乎不会出现的堆积现象。 别把回测事件流当实盘 测试器里同帧批量事件是高概率常态,实盘却被拆到不同 tick。直接拿回测事件时序去推实盘逻辑,可能把外汇和贵金属的高风险触发条件误判成同步到达。
class="kw">static ENUM_SYMBOL_EVENT last_symbol_event=WRONG_VALUE; class=class="str">"cmt">//--- If working in the tester if(MQLInfoInteger(MQL_TESTER)) { engine.OnTimer(); PressButtonsControl(); } class=class="str">"cmt">//--- If the last trading event changed if(engine.LastTradeEvent()!=last_trade_event) { last_trade_event=engine.LastTradeEvent(); Comment("\nLast trade event: ",engine.GetLastTradeEventDescription()); engine.ResetLastTradeEvent(); } class=class="str">"cmt">//--- If there is an account event if(engine.IsAccountsEvent()) { class=class="str">"cmt">//--- the last account event last_account_event=engine.LastAccountEvent(); class=class="str">"cmt">//--- If this is a tester if(MQLInfoInteger(MQL_TESTER)) { class=class="str">"cmt">//--- Get the list of all account events occurred simultaneously CArrayObj* list=engine.GetListAccountEvents(); if(list!=NULL) { class=class="str">"cmt">//--- Get the next event in a loop class="type">int total=list.Total(); for(class="type">int i=class="num">0;i<total;i++) { class=class="str">"cmt">//--- take an event from the list CEventBaseObj *event=list.At(i); if(event==NULL) class="kw">continue; class=class="str">"cmt">//--- Send an event to the event handler class="type">long lparam=event.LParam(); class="type">class="kw">double dparam=event.DParam(); class="type">class="kw">string sparam=event.SParam(); OnDoEasyEvent(CHARTEVENT_CUSTOM+event.ID(),lparam,dparam,sparam); } } } } class=class="str">"cmt">//--- If there is a symbol collection event if(engine.IsSymbolsEvent()) { class=class="str">"cmt">//--- the last event in the symbol collection last_symbol_event=engine.LastSymbolsEvent(); class=class="str">"cmt">//--- If this is a tester if(MQLInfoInteger(MQL_TESTER)) { class=class="str">"cmt">//--- Get the list of all symbol events occurred simultaneously CArrayObj* list=engine.GetListSymbolsEvents(); if(list!=NULL) { class=class="str">"cmt">//--- Get the next event in a loop class="type">int total=list.Total(); for(class="type">int i=class="num">0;i<total;i++) {
◍ 把库事件转成图表自定义事件再分流
事件循环里先按索引从列表取对象,空指针直接跳过,避免后续调用崩在 MT5 终端。取出来后把 LParam、DParam、SParam 三个字段拆出,再喂给 OnDoEasyEvent,并人为偏移 CHARTEVENT_CUSTOM 让自定义事件不挤占系统事件号。
class=class="str">"cmt">//--- take an event from the list CEventBaseObj *event=list.At(i); if(event==NULL) class="kw">continue; class=class="str">"cmt">//--- Send an event to the event handler class="type">long lparam=event.LParam(); class="type">class="kw">double dparam=event.DParam(); class="type">class="kw">string sparam=event.SParam(); OnDoEasyEvent(CHARTEVENT_CUSTOM+event.ID(),lparam,dparam,sparam); } } } } class=class="str">"cmt">//--- If the trailing flag is set if(trailing_on) { TrailingPositions(); TrailingOrders(); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Handling DoEasy library events | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDoEasyEvent(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="type">int idx=id-CHARTEVENT_CUSTOM; class="type">class="kw">string event="::"+class="type">class="kw">string(idx); class="type">int digits=Digits(); class=class="str">"cmt">//--- Handling trading events if(idx>TRADE_EVENT_NO_EVENT && idx<TRADE_EVENTS_NEXT_CODE) { event=EnumToString((ENUM_TRADE_EVENT)class="type">class="kw">ushort(idx)); digits=(class="type">int)SymbolInfoInteger(sparam,SYMBOL_DIGITS); } class=class="str">"cmt">//--- Handling account events else if(idx>ACCOUNT_EVENT_NO_EVENT && idx<ACCOUNT_EVENTS_NEXT_CODE) { Print(TimeMSCtoString(lparam)," ",sparam,": ",engine.GetAccountEventDescription((ENUM_ACCOUNT_EVENT)idx)); class=class="str">"cmt">//--- if this is an equity increase if((ENUM_ACCOUNT_EVENT)idx==ACCOUNT_EVENT_EQUITY_INC) { class=class="str">"cmt">//--- Close a position with the highest profit exceeding zero when the equity exceeds the value, class=class="str">"cmt">//--- specified in the CAccountsCollection::InitControlsParams() method for class=class="str">"cmt">//--- the m_control_equity_inc variable tracking the equity growth by class="num">15 units(by class="kw">default) class=class="str">"cmt">//--- AccountCollection file, InitControlsParams() method, class="type">class="kw">string class="num">1199 class=class="str">"cmt">//--- Get the list of all open positions CArrayObj* list_positions=engine.GetListMarketPosition(); class=class="str">"cmt">//--- Select positions with the profit exceeding zero list_positions=CSelect::ByOrderProperty(list_positions,ORDER_PROP_PROFIT_FULL,class="num">0,MORE);
class=class="str">"cmt">//--- take an event from the list CEventBaseObj *event=list.At(i); if(event==NULL) class="kw">continue; class=class="str">"cmt">//--- Send an event to the event handler class="type">long lparam=event.LParam(); class="type">class="kw">double dparam=event.DParam(); class="type">class="kw">string sparam=event.SParam(); OnDoEasyEvent(CHARTEVENT_CUSTOM+event.ID(),lparam,dparam,sparam); } } } } class=class="str">"cmt">//--- If the trailing flag is set if(trailing_on) { TrailingPositions(); TrailingOrders(); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Handling DoEasy library events | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDoEasyEvent(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="type">int idx=id-CHARTEVENT_CUSTOM; class="type">class="kw">string event="::"+class="type">class="kw">string(idx); class="type">int digits=Digits(); class=class="str">"cmt">//--- Handling trading events if(idx>TRADE_EVENT_NO_EVENT && idx<TRADE_EVENTS_NEXT_CODE) { event=EnumToString((ENUM_TRADE_EVENT)class="type">class="kw">ushort(idx)); digits=(class="type">int)SymbolInfoInteger(sparam,SYMBOL_DIGITS); } class=class="str">"cmt">//--- Handling account events else if(idx>ACCOUNT_EVENT_NO_EVENT && idx<ACCOUNT_EVENTS_NEXT_CODE) { Print(TimeMSCtoString(lparam)," ",sparam,": ",engine.GetAccountEventDescription((ENUM_ACCOUNT_EVENT)idx)); class=class="str">"cmt">//--- if this is an equity increase if((ENUM_ACCOUNT_EVENT)idx==ACCOUNT_EVENT_EQUITY_INC) { class=class="str">"cmt">//--- Close a position with the highest profit exceeding zero when the equity exceeds the value, class=class="str">"cmt">//--- specified in the CAccountsCollection::InitControlsParams() method for class=class="str">"cmt">//--- the m_control_equity_inc variable tracking the equity growth by class="num">15 units(by class="kw">default) class=class="str">"cmt">//--- AccountCollection file, InitControlsParams() method, class="type">class="kw">string class="num">1199 class=class="str">"cmt">//--- Get the list of all open positions CArrayObj* list_positions=engine.GetListMarketPosition(); class=class="str">"cmt">//--- Select positions with the profit exceeding zero list_positions=CSelect::ByOrderProperty(list_positions,ORDER_PROP_PROFIT_FULL,class="num">0,MORE);
按净收益锁定并平仓最赚钱的持仓
在持仓列表非空时,先按包含手续费与库存费的全量利润字段排序,再找出利润最高的那条持仓。MT5 下直接取 ticket 调 trade.PositionClose 平仓,MQL4 环境则带 volume 参数走 PositionClose 重载。 代码片段里用 SORT_BY_ORDER_PROFIT_FULL 做排序,FindOrderMax 配合 ORDER_PROP_PROFIT_FULL 找索引,这是把「裸利润」和「全成本利润」区分开的关键——只看浮动盈亏可能留着最赚的,实则被 swap 啃掉一块。外汇与贵金属杠杆高,净收益排序偏差会放大平仓决策风险。 另一段处理 symbol 事件:idx 落在 SYMBOL_EVENT_NO_EVENT 和 SYMBOL_EVENTS_NEXT_CODE 之间时,区分 Market Watch 事件与具体品种事件。前者打印排序等描述,后者通过 GetSymbolObjByName 拿到 CSymbol 实例输出事件说明,便于在日志里追踪品种状态切换。 回测日志显示 2019.07.15 04:02:24.167 一次运行里 Market Watch 仅加载了 4 个品种,说明这套事件监听在轻量观察窗口下也能正常吐出符号事件,开 MT5 把USED_SYMBOLS 改成 4 即可复现。
if(list_positions!=NULL) { class=class="str">"cmt">//--- Sort the list by profit considering commission and swap list_positions.Sort(SORT_BY_ORDER_PROFIT_FULL); class=class="str">"cmt">//--- Get the position index with the highest profit class="type">int index=CSelect::FindOrderMax(list_positions,ORDER_PROP_PROFIT_FULL); if(index>WRONG_VALUE) { COrder* position=list_positions.At(index); if(position!=NULL) { class=class="str">"cmt">//--- Get a ticket of a position with the highest profit and close the position by a ticket class="macro">#ifdef __MQL5__ trade.PositionClose(position.Ticket()); class="macro">#else PositionClose(position.Ticket(),position.Volume()); class="macro">#endif } } } class=class="str">"cmt">//--- Handling symbol events else if(idx>SYMBOL_EVENT_NO_EVENT && idx<SYMBOL_EVENTS_NEXT_CODE) { class="type">class="kw">string name=""; class=class="str">"cmt">//--- Market Watch window event if(idx<SYMBOL_EVENT_TRADE_DISABLE) { class="type">class="kw">string descr=engine.GetMWEventDescription((ENUM_SYMBOL_EVENT)idx); name=(idx==SYMBOL_EVENT_MW_SORT ? "" : ": "+sparam); Print(TimeMSCtoString(lparam)," ",descr,name); } class=class="str">"cmt">//--- Symbol event else { CSymbol *symbol=engine.GetSymbolObjByName(sparam); if(symbol!=NULL) { class="type">class="kw">string descr=": "+symbol.EventDescription((ENUM_SYMBOL_EVENT)class="type">class="kw">ushort(idx)); Print(TimeMSCtoString(lparam)," ",sparam,descr); } } }
「点差跳动在日志里长什么样」
在 MT5 策略测试器跑跨品种监控时,点差变化会以带时间戳的日志行吐出来。上面这组 2019.07.15 的回测片段里,GBPUSD 出现过一次点差缩窄:相对基准 351 点状态下减少 7 点;USDCHF 则在 283 与 287 点之间反复横跳,每次增减都是 4 点。 注意日志左侧是 EA 所在图表 EURUSD H4 的测试时间(04:02 档),右侧才是被监控品种 USDCHF 的真实行情时间(00:02 档),两者差了约 4 小时,说明跨周期采样靠的是独立时间线而非图表轴。 这种逐笔点差记录对做套利或高频过滤有用:当 USDCHF 在 10 秒内连刷 4 次 ±4 点,说明流动性在薄档位抖动,实盘里外汇和贵金属同样有这类瞬时滑点风险,可能吞掉窄止损单。开 MT5 把这段日志贴进专家日志窗口对照,就能验证自己 EA 的价差捕获逻辑是否漏了符号。
◍ 点差跳变在日志里长什么样
下面这段 MT5 回测日志截取自 EURUSD H4 周期下的 EA 运行记录,时间标头显示本地 2019.07.15 04:03 前后,引擎正在回放 00:03–00:04 的 tick。USDCHF 的点差在 283 与 287 点之间反复横跳,每次增减都是 4 点,节奏大概每 2~5 秒切换一次。 到了 00:03:52.598,USDCHF 点差突然从 283 拉到 334,单次跳增 51 点,明显脱离了前面那种机械式的 ±4 波动,属于流动性瞬断或报价源切换的典型痕迹。 紧接着 00:04:00 这一秒,EURUSD 点差从 92 降到 50(减 42 点),USDCHF 更狠,直接从 334 砸到 238(减 96 点)。外汇和贵金属市场点差瞬跳是高风险信号,实盘里这种压缩常出现在流动性回归瞬间,但也可能在几秒后重新扩大,挂单前最好盯一眼实时 spread。 把这段日志直接丢进 MT5 的「专家日志」比对,或自己在 EURUSD/USDCHF 的 H4 回测里跑一遍同时间窗,就能确认这种点差脉动不是个例。
点差跳变在日志里长什么样
跑 EA 回测时,点差不是静止的。上面这段 EURUSD/H4 周期下的测试日志,记录了 2019.07.15 凌晨几个货币对点差的实时变动:USDCHF 在 00:04:00 附近从 260 点跳到 282 点(增加 22),随后又回落到 255;GBPUSD 在 00:04:36 从 351 点骤降到 239 点(减少 112),半分钟后进一步压到 32 点。 EURUSD 本身的波动相对克制,但也在 00:05:02 出现过一次从 42 点掉到 10 点的瞬间收窄(减少 32),随后在 00:55:11 稳定在 29 点附近。这些数字说明:即便是主要货币对,点差在流动性切换时段也可能出现数倍级别的拉伸或收缩。 外汇和贵金属交易自带高杠杆与滑点风险,点差异常放大时进场,止损被扫的概率会明显上升。把这类日志接进自己的监控脚本,就能在实盘里第一时间察觉点差异动,而不是等成交回报才发现成本失控。
「点差跳动的日志现场」
下面这段是 EA 在 EURUSD(H4) 图表上跑测试时,由底层监听模块打印出的点差变动原始记录。注意时间戳分两列:左边是策略测试器的本地回放时间,右边才是品种真实行情时间,两者差了约 4 小时 55 分,说明这是历史数据重放而非实时 tick。 USDCHF 在 00:55:11 到 00:55:21 的十秒内,点差从 32 点降到 29 点再弹回 32、砸到 27、又回 31、落 28、弹 31,七次跳动跨度最大达 5 点。EURUSD 则在 00:55:30 后连续出现 20↔17 点的三跳,每次 ±3 点,节奏更规整。 这种微观抖动对外汇和贵金属交易属于高风险扰动:若你的止损只挂 10 点内,点差瞬间扩大就可能直接扫损。开 MT5 把这段日志贴进专家日志窗口对照,或把自己 EA 的 Spread 监控打印出来,就能验证当前经纪商在点差异动上是否也有类似特征。
◍ 点差在秒级跳动里的真实幅度
上面这组 MT5 策略测试器的日志,抓的是 2019 年 7 月 15 日 00:55–00:56 之间 USDCHF 在 H4 回测环境下的点差瞬时变化。注意日志左侧是测试器写入时间(04:55 档),右侧才是行情时钟(00:55 档),两者差了约 4 小时,说明这是离线回放而非实时tick。 在不到 6 秒的行情时间里,USDCHF 点差从 28 跳到 32、再回 28、再上 32、最后落 29。单笔变动幅度是 3~4 个点,来回扫了两次 28↔32 的区间。 这种级别的点差抖动,在外汇和贵金属品种上属于常态,尤其美元系交叉盘在流动性切换时段更容易出现。实盘里若点差瞬间扩大 4 点,对剥头皮类策略的冲击成本可能直接吃掉一次盈利;做回测时若用固定点差假设,就会低估滑点。 开 MT5 随便挂个 USDCHF 的 H4 回测,把日志级别调到 Spread 跟踪,就能复现类似跳动。别把回测里的平均点差当成实盘下限。
class="num">2019.07.class="num">15 class="num">04:class="num">55:class="num">54.906 TestDoEasyPart16(EURUSD,H4) class="num">2019.07.class="num">15 class="num">00:class="num">55:class="num">56.632 USDCHF: Spread value in points decreased by -class="num">3 (class="num">28) class="num">2019.07.class="num">15 class="num">04:class="num">55:class="num">55.912 TestDoEasyPart16(EURUSD,H4) class="num">2019.07.class="num">15 class="num">00:class="num">55:class="num">57.536 USDCHF: Spread value in points increased by class="num">4 (class="num">32) class="num">2019.07.class="num">15 class="num">04:class="num">55:class="num">56.907 TestDoEasyPart16(EURUSD,H4) class="num">2019.07.class="num">15 class="num">00:class="num">55:class="num">58.636 USDCHF: Spread value in points decreased by -class="num">4 (class="num">28) class="num">2019.07.class="num">15 class="num">04:class="num">55:class="num">57.434 TestDoEasyPart16(EURUSD,H4) class="num">2019.07.class="num">15 class="num">00:class="num">55:class="num">58.832 USDCHF: Spread value in points increased by class="num">4 (class="num">32) class="num">2019.07.class="num">15 class="num">04:class="num">55:class="num">59.949 TestDoEasyPart16(EURUSD,H4) class="num">2019.07.class="num">15 class="num">00:class="num">56:class="num">01.538 USDCHF: Spread value in points decreased by -class="num">3 (class="num">29)
把这条线请下神坛
这一篇收尾时,作者把当前版本的含糊库所有文件连同测试 EA 一并打包放出,MQL5.zip 与 MQL4.zip 各 224.58 KB,读者可直接下载到 MT5 里跑通前面十四篇攒下的数据管理层。评论区里 Dmitiry Ananiev 在 2019 年 7 月就吐槽过「圣经有什么用」,作者回得直白:现在只是在收数据,应用是后面的事,每篇都挂了测试顾问。 真实账户上有人拿 BR-8.22 期货验证过,用当前符号能跑,但从市场观察窗口指定列表时该合约识别数为 0,而 @BR 却能认——这种符号映射坑大概率还会在别的品种上复现。 所以这套 CBaseObj 基准对象类的演进,本质是一条持续填坑的线,不是终点。下一篇要做受控对象属性修改和跟踪,你可以先下好 ZIP 把第十四篇的品种集合跑顺,等更新就好。