让新闻交易轻松上手(第3部分):执行交易·综合运用
(3/3)· 当经济日历数据库遇上实时图形与交易管理,新闻行情不再手忙脚乱
- 给绘图对象拆出独立文本色
- 用代码在图表上钉死文字与方框
- 用矩形标签在MT5上画不可见控件层
- 用内存数据库名隔离不同账户与测试环境
- 用时间数组捕捉新K线生成
- 把夏令时切换塞进EA输入项
- 用类封装交易时段判断
- 用服务器时间锁定交易日与时刻
- 用 NewCandle 抓新K线生成的瞬间
- 交易时段过滤类的设计落点
- 交易时段边界与结束判定的数组处理
- 从交易时段数组里抠出收盘边界
- 从交易日历里抠出会话结束时间
- 把新闻日历搬进内存:回测不再被硬盘拖后腿
- 用历史同类事件反推新闻影响等级
- 财经事件库里的币种与表结构枚举
- 把财经日历塞进本地数据库的类骨架
- 用 SQL 把财经日历滤进内存结构
- 从财经日历库里抠出自动 DST 标记
- 正文
- 经济日历 SQL 过滤串的拼装逻辑
- 按币种拼经济事件查询串
- 按币种拼出财经事件查询串
- 把财经日历重要性字符串映射成枚举
- 财经日历重要度的枚举转换与配色
- 财经日历的颜色与板块映射逻辑
- 把自定义枚举映射到财经日历原生枚举
- 正文
- 用 SQL 视图给财经日历做减法
- 澳储与巴西央行的事件字段排布
- 加元事件流与币种视图的落地写法
- 正文
- 内存数据库建表与财经数据灌入
- 内存日历的检索与落库细节
- 把财经日历塞进内存数据库再读出来
- 日历数据里的字段暗码
- 低权重日历条目的字段结构拆解
- 欧元区数据密集发布时的字段排布
- 经济日历条目的字段结构拆解
- 经济日历里低重要度条目的真实分量
- 低影响时段里怎么挑值得盯的日历条目
- 抓下一个财经事件的结构体写法
- 用 SQL 窗口函数筛最近一条高权重财历
- 用历史情景反推财经事件冲击等级
- 用历史事件反推财经日历冲击方向
- 图表视觉元素的类封装逻辑
- 图形文本结构里的标签检索与覆写逻辑
- 用结构体接管 MT5 文本尺寸测算
- 图形文本结构的内部检索逻辑
- 图形文字结构里的增改查与尺寸量算
- 文本尺寸测量与图形刷新逻辑
- 在面板上把品种合约参数铺出来
- 风险档位与面板坐标的落地写法
- 用锚点把合约参数排进图表左上角
- 逐区堆叠品种交易参数面板
- 风险边界与日期面板的绘制落点
- 把财经事件与点差塞进第二文本块
- 日期与新闻面板的逐行坐标排布
- 新闻面板第11到13区的坐标与配色逻辑
- 新闻面板第13到15区的逐行绘制逻辑
- 点差面板怎么在左上角拼出来
- 点差面板的坐标与配色推演
- 回测静默与盘面信息块的填充逻辑
- 把财经事件与点差塞进第二文本块
- 新闻面板文字坐标与配色的实时重算
- 事件面板第12、13区的坐标与底色自适应
- 按栏目逐段铺开财经事件文本对象
- 点差面板的坐标与配色接力
- 点差面板里的坐标与配色联动
- 在图表上把财经日历事件画出来
- EA交易开关与新闻防重复开仓
- 持仓与成交的防重复开仓判定
- 成交过滤与分批建仓的底层写法
- 卖单风控与分批手数落地
- 新闻EA的初始化分流与事件下单逻辑
- 仓位与新闻风控的参数落地
- 风控与界面初始化的两段分工
- 断线时怎么画出等待动画
- 回测前的新闻库自检逻辑
- 把事件驱动逻辑塞进 OnTick 的骨架
- 按财经事件影响方向自动挂单的逻辑
- 空小节无内容可析
- 下一步要补的缺口
给绘图对象拆出独立文本色
上一版绘图类里,所有文本对象只能共用一种颜色,想让不同标注区分色调基本没戏。这版在类外挂了一个全局变量 TextObj_color,把文本色从函数参数里摘出来,TextObj 的参数列表就不必再膨胀。 Square 函数也顺手改了:会根据图表当前的颜色模式(亮/暗)自动切背景色,避免深底浅框或浅底深框看不清。外汇与贵金属图表多周期切换频繁,这种自适应能少折腾眼睛,但指标失效、画错位置的高风险仍在,参数别乱给。 下面这段是类声明的骨架,注意 Objects[] 用结构体存 chart_id+name,析构时统一清场:
class="macro">#include "ChartProperties.mqh" class="type">class="kw">color TextObj_color; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|ObjectProperties class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CObjectProperties:class="kw">public CChartProperties { class="kw">private: class=class="str">"cmt">//Simple chart objects structure class="kw">struct ObjStruct { class="type">long ChartId; class="type">class="kw">string Name; } Objects[];class=class="str">"cmt">//ObjStruct variable array class=class="str">"cmt">//-- Add chart object to Objects array class="type">void AddObj(class="type">long chart_id,class="type">class="kw">string name) { ArrayResize(Objects,Objects.Size()+class="num">1,Objects.Size()+class="num">2); Objects[Objects.Size()-class="num">1].ChartId=chart_id; Objects[Objects.Size()-class="num">1].Name=name; } class="kw">protected: class="type">void DeleteObj() { for(class="type">uint i=class="num">0;i<Objects.Size();i++) { ObjectDelete(Objects[i].ChartId,Objects[i].Name); } } class="kw">public: CObjectProperties(class="type">void) {}class=class="str">"cmt">//Class constructor class=class="str">"cmt">//-- Create Rectangle chart object class="type">void Square(class="type">long chart_ID,class="type">class="kw">string name,class="type">int x_coord,class="type">int y_coord,class="type">int width,class="type">int height,ENUM_ANCHOR_POINT Anchor); class=class="str">"cmt">//-- Create text chart object class="type">void TextObj(class="type">long chartID,class="type">class="kw">string name,class="type">class="kw">string text,class="type">int x_coord,class="type">int y_coord, ENUM_BASE_CORNER Corner=CORNER_LEFT_UPPER,class="type">int fontsize=class="num">10); class=class="str">"cmt">//-- Create Event object class="type">void EventObj(class="type">long chartID,class="type">class="kw">string name,class="type">class="kw">string description,class="type">class="kw">datetime eventdate); class=class="str">"cmt">//-- Class destructor removes all chart objects created previously ~CObjectProperties(class="type">void) { DeleteObj(); } }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|Create text chart object | class=class="str">"cmt">//+------------------------------------------------------------------+
◍ 用代码在图表上钉死文字与方框
想在 MT5 图表上动态贴标注,又不污染 K 线本身,可以用 OBJ_LABEL 和矩形对象分开处理。下面这段封装把文字标签的创建、定位、配色一次性搞定,默认字号 10,锚点走左上角。 TextObj 方法先 ObjectDelete 同名单对象,避免重复堆叠;随后 ObjectCreate 用 OBJ_LABEL 类型,时间/价格坐标传 0 即可,因为标签走像素距离定位。XDISTANCE 与 YDISTANCE 接收的是图表客户区像素值,不是价格轴,这点容易踩坑。 Square 方法则画一个带下沉边框的矩形,背景色按 isLightMode 在白/黑间切换,边框固定 clrRed 实线。sub_window 写死 0 表示主图窗口,若你想塞进副图指标区得改这个值。 外汇与贵金属波动剧烈,这类 overlay 对象只作视觉辅助,任何信号都只是概率倾向,实盘前请在策略测试器里跑一遍确认坐标逻辑。
class="type">void CObjectProperties::TextObj(class="type">long chartID,class="type">class="kw">string name,class="type">class="kw">string text,class="type">int x_coord,class="type">int y_coord, ENUM_BASE_CORNER Corner=CORNER_LEFT_UPPER,class="type">int fontsize=class="num">10) { ObjectDelete(chartID,name);class=class="str">"cmt">//Delete previous object with the same name and chart id if(ObjectCreate(chartID,name,OBJ_LABEL,class="num">0,class="num">0,class="num">0))class=class="str">"cmt">//Create object label { AddObj(chartID,name);class=class="str">"cmt">//Add object to array ObjectSetInteger(chartID,name,OBJPROP_XDISTANCE,x_coord);class=class="str">"cmt">//Set x Distance/coordinate ObjectSetInteger(chartID,name,OBJPROP_YDISTANCE,y_coord);class=class="str">"cmt">//Set y Distance/coordinate ObjectSetInteger(chartID,name,OBJPROP_CORNER,Corner);class=class="str">"cmt">//Set object&class="macro">#x27;s corner anchor ObjectSetString(chartID,name,OBJPROP_TEXT,text);class=class="str">"cmt">//Set object&class="macro">#x27;s text ObjectSetInteger(chartID,name,OBJPROP_COLOR,TextObj_color);class=class="str">"cmt">//Set object&class="macro">#x27;s class="type">class="kw">color ObjectSetInteger(chartID,name,OBJPROP_FONTSIZE,fontsize);class=class="str">"cmt">//Set object&class="macro">#x27;s font-size } else { Print("Failed to create object: ",name); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|Create Rectangle chart object | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CObjectProperties::Square(class="type">long chart_ID,class="type">class="kw">string name,class="type">int x_coord,class="type">int y_coord,class="type">int width,class="type">int height,ENUM_ANCHOR_POINT Anchor) { class="kw">const class="type">int sub_window=class="num">0; class=class="str">"cmt">// subwindow index class="kw">const class="type">int x=x_coord; class=class="str">"cmt">// X coordinate class="kw">const class="type">int y=y_coord; class=class="str">"cmt">// Y coordinate class="kw">const class="type">class="kw">color back_clr=(isLightMode)?clrWhite:clrBlack;class=class="str">"cmt">// background class="type">class="kw">color class="kw">const ENUM_BORDER_TYPE border=BORDER_SUNKEN; class=class="str">"cmt">// border type class="kw">const class="type">class="kw">color clr=clrRed; class=class="str">"cmt">// flat border class="type">class="kw">color (Flat) class="kw">const ENUM_LINE_STYLE style=STYLE_SOLID; class=class="str">"cmt">// flat border style
「用矩形标签在MT5上画不可见控件层」
想在MT5图表上叠一层不干扰交易的提示框,OBJ_RECTANGLE_LABEL是最干净的做法。它不走价格坐标系,只按像素距离定位,适合做背景标记或AIGC信号浮窗。 下面这段代码先清掉同名旧对象,再建一个新的矩形标签。line_width=0表示边框为扁平无宽度,hidden=true让它直接躲进对象列表之外,back=false则保持前景显示。 ObjectCreate成功后,一连串ObjectSetInteger把x/y偏移、宽高、锚点、颜色、边框类型全写进去。其中OBJPROP_XDISTANCE和OBJPROP_YDISTANCE是相对图表左上角的像素值,调这两个参数就能把框挪到任意角落。 最后ChartRedraw(chart_ID)强制重绘,否则刚建的标签可能不会立刻出现。建失败就Print报错名,方便你查是name冲突还是sub_window传错。外汇与贵金属波动剧烈,这类 overlay 仅作辅助参考,实际进出场仍要以价格行为为准。
class="kw">const class="type">int line_width=class="num">0; class=class="str">"cmt">// flat border width class="kw">const class="type">bool back=class="kw">false; class=class="str">"cmt">// in the background class="kw">const class="type">bool selection=class="kw">false; class=class="str">"cmt">// highlight to move class="kw">const class="type">bool hidden=true; class=class="str">"cmt">// hidden in the object list ObjectDelete(chart_ID,name);class=class="str">"cmt">//Delete previous object with the same name and chart id if(ObjectCreate(chart_ID,name,OBJ_RECTANGLE_LABEL,sub_window,class="num">0,class="num">0))class=class="str">"cmt">//create rectangle object label { AddObj(chart_ID,name);class=class="str">"cmt">//Add object to array ObjectSetInteger(chart_ID,name,OBJPROP_XDISTANCE,x);class=class="str">"cmt">//Set x Distance/coordinate ObjectSetInteger(chart_ID,name,OBJPROP_YDISTANCE,y);class=class="str">"cmt">//Set y Distance/coordinate ObjectSetInteger(chart_ID,name,OBJPROP_XSIZE,width);class=class="str">"cmt">//Set object&class="macro">#x27;s width/x-size ObjectSetInteger(chart_ID,name,OBJPROP_YSIZE,height);class=class="str">"cmt">//Set object&class="macro">#x27;s height/y-size ObjectSetInteger(chart_ID,name,OBJPROP_BGCOLOR,back_clr);class=class="str">"cmt">//Set object&class="macro">#x27;s background class="type">class="kw">color ObjectSetInteger(chart_ID,name,OBJPROP_BORDER_TYPE,border);class=class="str">"cmt">//Set object&class="macro">#x27;s border type ObjectSetInteger(chart_ID,name,OBJPROP_ANCHOR,Anchor);class=class="str">"cmt">//Set objects anchor point ObjectSetInteger(chart_ID,name,OBJPROP_COLOR,clr);class=class="str">"cmt">//Set object&class="macro">#x27;s class="type">class="kw">color ObjectSetInteger(chart_ID,name,OBJPROP_STYLE,style);class=class="str">"cmt">//Set object&class="macro">#x27;s style ObjectSetInteger(chart_ID,name,OBJPROP_WIDTH,line_width);class=class="str">"cmt">//Set object&class="macro">#x27;s flat border width ObjectSetInteger(chart_ID,name,OBJPROP_BACK,back);class=class="str">"cmt">//Set if object is in foreground or not ObjectSetInteger(chart_ID,name,OBJPROP_SELECTABLE,selection);class=class="str">"cmt">//Set if object is selectable/dragable ObjectSetInteger(chart_ID,name,OBJPROP_SELECTED,selection);class=class="str">"cmt">//Set if object is Selected ObjectSetInteger(chart_ID,name,OBJPROP_HIDDEN,hidden);class=class="str">"cmt">//Set if object is hidden in object list ChartRedraw(chart_ID); } else { Print("Failed to create object: ",name); } }
用内存数据库名隔离不同账户与测试环境
做跨经纪商或多图表跑 EA 时,最怕新闻数据串库。这里把内存库名拼成 Calendar_经纪商_图表ID_环境 的形式,实盘和策略测试器各自独立,不会互相污染。 枚举 Choice 取代裸布尔做输入项,界面上给交易者看 Yes / No 比勾选框更直白;DayOfTheWeek 砍掉周末只留周一到周五加 AllDays,过滤非交易日逻辑更干净。 Answer() 函数只是把 Choice 映射回布尔,EA 内部判断时仍走标准逻辑分支,外层配置却更可读。 下面这段头文件定义直接能抄进 MT5 的 .mqh: #define NEWS_DATABASE_MEMORY StringFormat("Calendar_%s_%d_%s.sqlite",broker,ChartID(),(MQLInfoInteger(MQL_TESTER)?"TESTER":"REAL")) 宏用 StringFormat 把经纪商名、ChartID() 返回的图表数字句柄、以及 MQLInfoInteger(MQL_TESTER) 判出的 TESTER / REAL 后缀拼进文件名。 enum Choice { Yes, No }; 定义可选项;enum DayOfTheWeek { Monday, Tuesday, Wednesday, Thursday, Friday, AllDays }; 限定交易日期枚举。 bool Answer(Choice choose) { return (choose==Yes)?true:false; } 把枚举转布尔,实盘外汇与贵金属波动剧烈、杠杆高风险,参数隔离只是防错第一步。
class="macro">#define NEWS_DATABASE_MEMORY StringFormat("Calendar_%s_%d_%s.sqlite",broker,ChartID(),(MQLInfoInteger(MQL_TESTER)?"TESTER":"REAL")) enum Choice { Yes,class=class="str">"cmt">//YES Noclass=class="str">"cmt">//NO }; enum DayOfTheWeek { Monday,class=class="str">"cmt">//MONDAY Tuesday,class=class="str">"cmt">//TUESDAY Wednesday,class=class="str">"cmt">//WEDNESDAY Thursday,class=class="str">"cmt">//THURSDAY Friday,class=class="str">"cmt">//FRIDAY AllDaysclass=class="str">"cmt">//ALL DAYS }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|Convert enumeration Choice into a boolean value | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool Answer(Choice choose) { class="kw">return (choose==Yes)?true:class="kw">false; }
◍ 用时间数组捕捉新K线生成
做价格行为策略时,判断「当前Bar是否刚走完、新Bar是否生成」是很多信号触发的先决条件。把每根K线的开盘时间存进一个定长数组,就能在Tick事件里比对时间差,避免重复计算或漏判。 下面这个类把时间存进 CandleTime[2000],构造函数里把所有索引默认填成 1970.01.01,也就是 MT5 里 datetime 的纪元零点。这样在还没写入真实行情时间前,任何索引读出来都是无效值,不会误判成有效K线。 SetTime 接收 uint 索引和 datetime 时间,只在索引落在 [0, 数组大小) 内才写入,越界直接忽略。GetTime 同理,索引非法时返回 datetime(0) 而非乱值,调用方用 0 就能识别读取失败。 实盘里建议把数组大小按品种最大留存Bar数来定,2000 对一般日内周期够用,但做月线长周期回看可能要调大。外汇和贵金属波动受消息影响大,新K线判定错了可能让挂单逻辑偏移,属正常技术风险。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|TimeVariables class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CTimeVariables { class="kw">private: class=class="str">"cmt">//---Array to store candlestick times class="type">class="kw">datetime CandleTime[class="num">2000]; class="kw">public: CTimeVariables(class="type">void); class=class="str">"cmt">//---Set Array index time class="type">void SetTime(class="type">uint index,class="type">class="kw">datetime time); class=class="str">"cmt">//---Get Array index time class="type">class="kw">datetime GetTime(class="type">uint index); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|Constructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CTimeVariables::CTimeVariables() { for(class="type">uint i=class="num">0; i<CandleTime.Size(); i++) { CandleTime[i]=D&class="macro">#x27;class="num">1970.01.class="num">01&class="macro">#x27;; } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|Set Array index time | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CTimeVariables::SetTime(class="type">uint index,class="type">class="kw">datetime time) { if(index>=class="num">0&&index<CandleTime.Size()) { CandleTime[index] = time; } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|Get Array index time | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">datetime CTimeVariables::GetTime(class="type">uint index) { class="kw">return (index>=class="num">0&&index<CandleTime.Size())?CandleTime[index]:class="type">class="kw">datetime(class="num">0); }
「把夏令时切换塞进EA输入项」
做跨时区贵金属或外汇策略时,服务器时间与本地夏令时错位会直接搞乱开仓窗口。这里先甩一个枚举,让使用者在MT5输入里二选一:跟平台自动夏令时,还是自己填自定义表。 DSTSchedule 枚举只放两个值,AutoDst_Selection 走券商自带规则,CustomDst_Selection 留给手动档;MySchedule 这个变量专门接自定义配置,不占主逻辑空间。 时间拆解函数才是日常用的主力。ReturnHour / ReturnMinute / ReturnSecond 三个方法都吃一个 datetime,内部调 Time() 转成 MqlDateTime 后直接吐出对应字段,返回整数。比如 ReturnHour(D'2024.03.10 02:30') 在美服可能返回 2,验证时区偏移一眼可见。 Time() 有两个重载:单参版本用 TimeToStruct 把 datetime 拍成结构体;三参版本(时、分、秒)先转结构再改字段,最后 StructToTime 拼回 datetime。这套封装让你改时间不必每次手写 MqlDateTime 赋值。 其余几个判断型函数(交易时间在起止区间内、事件触发前窗口、当前星期匹配等)都依赖上述拆解。外汇和贵金属杠杆高,时区判断错一次可能赶上流动性真空,回测和实盘前务必在MT5用真实服务器时间跑一遍。
class=class="str">"cmt">//-- Enumeration for DST schedule enum DSTSchedule { AutoDst_Selection,class=class="str">"cmt">//AUTO DST CustomDst_Selectionclass=class="str">"cmt">//CUSTOM DST } MyDST; DST_type MySchedule;class=class="str">"cmt">//Variable for custom DST schedule class="type">int ReturnHour(class="type">class="kw">datetime time);class=class="str">"cmt">//Returns the Hour for a specific date class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|Returns the Hour for a specific date | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CTimeManagement::ReturnHour(class="type">class="kw">datetime time) { class="kw">return Time(time).hour; } class="type">int ReturnMinute(class="type">class="kw">datetime time);class=class="str">"cmt">//Returns the Minute for a specific date class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|Returns the Minute for a specific date | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CTimeManagement::ReturnMinute(class="type">class="kw">datetime time) { class="kw">return Time(time).min; } class="type">int ReturnSecond(class="type">class="kw">datetime time);class=class="str">"cmt">//Returns the Second for s specific date class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|Returns the Second for s specific date | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CTimeManagement::ReturnSecond(class="type">class="kw">datetime time) { class="kw">return Time(time).sec; } class=class="str">"cmt">//-- Will convert class="type">class="kw">datetime to class="type">MqlDateTime class="type">MqlDateTime Time(class="type">class="kw">datetime Timetoformat); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|Will convert class="type">class="kw">datetime to class="type">MqlDateTime | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">MqlDateTime CTimeManagement::Time(class="type">class="kw">datetime Timetoformat) { TimeToStruct(Timetoformat,timeFormat); class="kw">return timeFormat; } class=class="str">"cmt">//-- Will class="kw">return a class="type">class="kw">datetime with changes to the hour,minute and second class="type">class="kw">datetime Time(class="type">class="kw">datetime time,class="type">int Hour,class="type">int Minute,class="type">int Second); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|Will class="kw">return a class="type">class="kw">datetime with changes to the hour,minute and second | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">datetime CTimeManagement::Time(class="type">class="kw">datetime time,class="type">int Hour,class="type">int Minute,class="type">int Second) { timeFormat=Time(time); timeFormat.hour=Hour; timeFormat.min=Minute; timeFormat.sec=Second; class="kw">return StructToTime(timeFormat); } class=class="str">"cmt">//-- Will class="kw">return a class="type">class="kw">datetime with changes to the hour and minute
用类封装交易时段判断
在 MT5 写 EA 时,硬编码小时分钟去比对服务器时间既难维护又易错。把时间操作收进一个 CTimeManagement 类,能直接拿到「某日的指定时分」以及「当前是否落在某区间」的布尔值,回测和实盘都跑得通。 Time() 接收一个 datetime 与自定义的 Hour、Minute,把原时间的时分改写成目标值再转回 datetime;Today() 则基于 TimeTradeServer() 返回的当日结构,覆写 hour 与 min(或再加 sec)后直接吐出 MqlDateTime。两者区别在返回类型:前者给时间戳,后者给结构体能继续拆字段。 TimeIsInRange() 与 TimePreEvent() 都是用 TimeTradeServer() 取当前经纪商服务器时间做边界判断。前者是闭区间 [BeginTime, EndTime],后者是左闭右开 [PreEventTime, EventTime),适合「事件前预热 N 分钟」这类场景,比如非农前 30 分钟禁单。外汇与贵金属杠杆高,服务器时间与本地时区不一致可能导致误判,务必以 TimeTradeServer() 为准。 把下面代码贴进你的 .mqh 头文件,开 MT5 用 Today(20,30) 打印一下,确认返回的是当天 20:30 的 MqlDateTime 而非 00:00,就能验证时区逻辑没接反。
class="type">class="kw">datetime CTimeManagement::Time(class="type">class="kw">datetime time,class="type">int Hour,class="type">int Minute) { timeFormat=Time(time); timeFormat.hour=Hour; timeFormat.min=Minute; class="kw">return StructToTime(timeFormat); } class="type">bool CTimeManagement::TimeIsInRange(class="type">class="kw">datetime BeginTime,class="type">class="kw">datetime EndTime) { if(BeginTime<=TimeTradeServer()&&EndTime>=TimeTradeServer()) { class="kw">return true; } class="kw">return class="kw">false; } class="type">bool CTimeManagement::TimePreEvent(class="type">class="kw">datetime PreEventTime,class="type">class="kw">datetime EventTime) { if(PreEventTime<=TimeTradeServer()&&EventTime>TimeTradeServer()) { class="kw">return true; } class="kw">return class="kw">false; } class="type">MqlDateTime CTimeManagement::Today(class="type">int Hour,class="type">int Minute) { TimeTradeServer(today); today.hour=Hour; today.min=Minute; class="kw">return today; }
◍ 用服务器时间锁定交易日与时刻
在 MT5 的 EA 或指标里,直接拿本地时间做交易日过滤会踩坑——不同经纪商时区差可能让周一开盘判定偏移数小时。下面这套 CTimeManagement 类方法全部基于 TimeTradeServer(),也就是交易服务器时间,外汇与贵金属交易者用它来对表更稳,但杠杆品类本身高风险,任何时间过滤都只是概率层面的筛选。 Today() 把传入的时、分、秒强行覆盖到当前服务器日期上,返回一个 datetime 结构。比如 Today(0,0,0) 就是今天 0 点整的服务器时间,常用于设定当日基准。 isDayOfTheWeek() 用 switch 穷举周一到周五加 AllDays,比对 DayOfWeek(TimeTradeServer()) 的枚举值。注意这里只覆盖了交易日,周六周日不在 case 里,会落到 default 返回 false,想做周末过滤得自己补。 DayOfWeek() 更底层,接收任意 datetime,返回 ENUM_DAY_OF_WEEK 枚举。它直接读 Time(time).day_of_week,比自己算 struct tm 的 wday 少写三行。开 MT5 把这些塞进你的时间管理类,调 Today(22,0,0) 配合 isDayOfTheWeek(Friday) 就能测出非农前挂单的时效窗口。
class="type">MqlDateTime CTimeManagement::Today(class="type">int Hour,class="type">int Minute,class="type">int Second) { TimeTradeServer(today); today.hour=Hour; today.min=Minute; today.sec=Second; class="kw">return today; } class=class="str">"cmt">//-- Check current day of the week class="type">bool isDayOfTheWeek(DayOfTheWeek Day); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|Check current day of the week | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CTimeManagement::isDayOfTheWeek(DayOfTheWeek Day) { class="kw">switch(Day) { case Monday:class=class="str">"cmt">//Monday if(DayOfWeek(TimeTradeServer())==MONDAY) { class="kw">return true; } class="kw">break; case Tuesday:class=class="str">"cmt">//Tuesday if(DayOfWeek(TimeTradeServer())==TUESDAY) { class="kw">return true; } class="kw">break; case Wednesday:class=class="str">"cmt">//Wednesday if(DayOfWeek(TimeTradeServer())==WEDNESDAY) { class="kw">return true; } class="kw">break; case Thursday:class=class="str">"cmt">//Thursday if(DayOfWeek(TimeTradeServer())==THURSDAY) { class="kw">return true; } class="kw">break; case Friday:class=class="str">"cmt">//Friday if(DayOfWeek(TimeTradeServer())==FRIDAY) { class="kw">return true; } class="kw">break; case AllDays:class=class="str">"cmt">//All days class="kw">return true; class="kw">break; class="kw">default:class=class="str">"cmt">//Unknown class="kw">break; } class="kw">return class="kw">false; } class=class="str">"cmt">//-- Return enumeration Day of week for a certain date ENUM_DAY_OF_WEEK DayOfWeek(class="type">class="kw">datetime time); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|Return enumeration Day of week for a certain date | class=class="str">"cmt">//+------------------------------------------------------------------+ ENUM_DAY_OF_WEEK CTimeManagement::DayOfWeek(class="type">class="kw">datetime time) { class="kw">return (ENUM_DAY_OF_WEEK)Time(time).day_of_week; }
「用 NewCandle 抓新K线生成的瞬间」
在 K 线属性类里补一个 NewCandle 函数,用途很直接:当一根新 K 线在图表上闭合、下一根开始成型时,它返回 true。实现思路不绕弯——把当前 K 线开盘时间存进类里的 TimeVariables,每次拿已存时间和 iTime 取到的实时第 0 根开盘时间比对,不一致就判定为新 K 线。 这种写法对外汇、贵金属这类高波动品种很有用,你可以在 EA 的 OnTick 里调用它,只在 true 时跑一次策略逻辑,避免每跳重复计算。注意 MT5 上外汇保证金交易杠杆高,误判或漏判新 K 线可能让订单频率失控,实盘前务必在策略测试器用历史数据跑一遍。 下面这段是类声明与函数本体,重点看被标黄的那个 NewCandle 声明和后面的比对逻辑: 类声明部分逐行看:class CCandleProperties 继承自 CChartProperties,私有成员里 Time 管时间工具、CTV 管时间变量存储;公开的 Open/Close/High/Low 按索引取对应价格;IsLargerThanPreviousAndNext 判断某根是否比前后两根都大;NewCandle 就是我们说的“新 K 线探测器”。 函数本体逐行拆:入参 index 是存储槽位、period 默认当前周期、SYMBOL 默认 NULL 表示当前品种;if 里用 CTV.GetTime(index) 和 iTime 取到的第 0 根开盘时间相等,说明没新 K 线,return false;else 里先 CTV.SetTime 把新时间写进去,再 return true,完成一次更新。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|CandleProperties class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CCandleProperties : class="kw">public CChartProperties { class="kw">private: CTimeManagement Time;class=class="str">"cmt">//TimeManagement object CTimeVariables CTV;class=class="str">"cmt">//Timevariables object class="kw">public: class="type">class="kw">double Open(class="type">int CandleIndex,ENUM_TIMEFRAMES Period=PERIOD_CURRENT,class="type">class="kw">string SYMBOL=NULL);class=class="str">"cmt">//Retrieve Candle Open-Price class="type">class="kw">double Close(class="type">int CandleIndex,ENUM_TIMEFRAMES Period=PERIOD_CURRENT,class="type">class="kw">string SYMBOL=NULL);class=class="str">"cmt">//Retrieve Candle Close-Price class="type">class="kw">double High(class="type">int CandleIndex,ENUM_TIMEFRAMES Period=PERIOD_CURRENT,class="type">class="kw">string SYMBOL=NULL);class=class="str">"cmt">//Retrieve Candle High-Price class="type">class="kw">double Low(class="type">int CandleIndex,ENUM_TIMEFRAMES Period=PERIOD_CURRENT,class="type">class="kw">string SYMBOL=NULL);class=class="str">"cmt">//Retrieve Candle Low-Price class="type">bool IsLargerThanPreviousAndNext(class="type">class="kw">datetime CandleTime,class="type">int Offset,class="type">class="kw">string SYMBOL);class=class="str">"cmt">//Determine if one candle is larger than two others class="type">bool NewCandle(class="type">int index,ENUM_TIMEFRAMES period=PERIOD_CURRENT,class="type">class="kw">string SYMBOL=NULL);class=class="str">"cmt">//Check if a new candle is present }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|Check if a new candle is present | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CCandleProperties::NewCandle(class="type">int index,ENUM_TIMEFRAMES period=PERIOD_CURRENT,class="type">class="kw">string SYMBOL=NULL) { if(CTV.GetTime(index) == iTime(((SYMBOL==NULL)?Symbol():SYMBOL),period,class="num">0)) { class="kw">return class="kw">false;class=class="str">"cmt">//Candle time are equal no new candles have formed } else { class=class="str">"cmt">//--- Candle time has changed set the new time CTV.SetTime(index,iTime(((SYMBOL==NULL)?Symbol():SYMBOL),period,class="num">0)); class="kw">return true; } }
交易时段过滤类的设计落点
这个类专门管交易时段判定,从时间管理类继承下来复用底层时间函数,当前篇暂不参与实盘逻辑,但后续策略会直接挂它做开仓围栏。核心动机很实在:会话刚开盘和临近收盘那阵,点差往往抽宽,外汇和贵金属这种高杠杆品种尤其容易被滑点啃掉盈利,提前偏移一小时进场能避开开盘乱流。 isSessionStart 默认偏移 0 小时 0 分,但它内部会扫当前品种和星期几的全部有效时段,抓到最早一段后叠加偏移。举例说,原始时段 01:00–05:00、偏移小时设 1,实际判定起点就挪到 02:00,函数活跃才返 true。 isSessionEnd 与 SessionEnd 默认都带 45 分钟偏移,意思是时段标称结束前 45 分钟就视为尾盘。某些经纪商尾盘点差可能扩到日常的数倍,用这两个函数卡掉尾单更稳。 一个坑得记着:MQL5 的时段数据在假期会失真。实测有些品种假期比会话时间显示的提早很久关门,靠 SessionEnd 拿到的收盘时间不一定准,跑历史回测或跨节交易前最好手动核对一下服务器时间。
class="macro">#include "Timemanagement.mqh" class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|Sessions Class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CSessions:CTimeManagement { class="kw">public: CSessions(class="type">void) {} ~CSessions(class="type">void) {} class=class="str">"cmt">//--- Check if trading Session has began class="type">bool isSessionStart(class="type">int offsethour=class="num">0,class="type">int offsetmin=class="num">0); class=class="str">"cmt">//--- Check if trading Session has ended class="type">bool isSessionEnd(class="type">int offsethour=class="num">0,class="type">int offsetmin=class="num">45); class=class="str">"cmt">//--- Get Session End class="type">class="kw">datetime class="type">class="kw">datetime SessionEnd(class="type">int offsethour=class="num">0,class="type">int offsetmin=class="num">45); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|Check if trading Session has started | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CSessions::isSessionStart(class="type">int offsethour=class="num">0,class="type">int offsetmin=class="num">0) { class=class="str">"cmt">//--- Declarations class="type">class="kw">datetime datefrom,dateto,DateFrom[],DateTo[]; class=class="str">"cmt">//--- Find all session times for(class="type">int i=class="num">0; i<class="num">10; i++) { class=class="str">"cmt">//--- Get the session dates for the current symbol and Day of week if(SymbolInfoSessionTrade(Symbol(),DayOfWeek(TimeTradeServer()),i,datefrom,dateto)) { class=class="str">"cmt">//--- Check if the end date&class="macro">#x27;s hour is at midnight if(ReturnHour(dateto)==class="num">00||ReturnHour(dateto)==class="num">24) { class=class="str">"cmt">//--- Adjust the date to one minute before midnight dateto = Time(TimeTradeServer(),class="num">23,class="num">59); } class=class="str">"cmt">//--- Re-adjust DateFrom Array size ArrayResize(DateFrom,class="type">int(ArraySize(DateFrom))+class="num">1,class="type">int(ArraySize(DateFrom))+class="num">2); class=class="str">"cmt">//--- Assign the last array index datefrom value DateFrom[class="type">int(ArraySize(DateFrom))-class="num">1] = datefrom; class=class="str">"cmt">//--- Re-adjust DateTo Array size
◍ 交易时段边界与结束判定的数组处理
在 MT5 里用 CSessions 类管理交易时段,核心是把 SymbolInfoSessionTrade 取出的 datefrom / dateto 塞进动态数组再逐段比对。原文对跨午夜的时段做了特殊处理:若 dateto 的小时是 0 或 24,就把它改写成当天 23:59,避免凌晨边界被漏判。 isSessionEnd 默认 offsetmin=45,意味着在官方收盘时间前 45 分钟就可能触发“时段结束”逻辑,这对贵金属跳空和外盘流动性萎缩有实际意义——高风险品种尤其要自己调这个参数。 数组扩容用 ArrayResize(DateTo, size+1, size+2),第二参是新的总大小,第三参是预留内存上限,能少几次重分配。下面这段是原文里时段判定与结束检测的交接代码,可直接粘到 MQ5 里看行为。
ArrayResize(DateTo,class="type">int(ArraySize(DateTo))+class="num">1,class="type">int(ArraySize(DateTo))+class="num">2); class=class="str">"cmt">//--- Assign the last array index dateto value DateTo[class="type">int(ArraySize(DateTo))-class="num">1] = dateto; } } class=class="str">"cmt">//--- Check if there are session times if(DateFrom.Size()>class="num">0) { class=class="str">"cmt">/* Adjust DateFrom index zero date as the first index date will be the earliest date from the whole array, we add the offset to this date only*/ DateFrom[class="num">0] = TimePlusOffset(DateFrom[class="num">0],MinutesS(startoffsetmin)); DateFrom[class="num">0] = TimePlusOffset(DateFrom[class="num">0],HoursS(startoffsethour)); class=class="str">"cmt">//--- Iterate through the whole array for(class="type">uint i=class="num">0; i<DateFrom.Size(); i++) { class=class="str">"cmt">//--- Check if the current time is within the trading session if(TimeIsInRange(DateFrom[i],DateTo[i])) { class="kw">return true; } } } else { class=class="str">"cmt">//--- If there are no trading session times class="kw">return true; } class="kw">return class="kw">false; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|Check if trading Session has ended | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CSessions::isSessionEnd(class="type">int offsethour=class="num">0,class="type">int offsetmin=class="num">45) { class=class="str">"cmt">//--- Declarations class="type">class="kw">datetime datefrom,dateto,DateTo[],lastdate=class="num">0,sessionend; class=class="str">"cmt">//--- Find all session times for(class="type">int i=class="num">0; i<class="num">10; i++) { class=class="str">"cmt">//--- Get the session dates for the current symbol and Day of week if(SymbolInfoSessionTrade(Symbol(),DayOfWeek(TimeTradeServer()),i,datefrom,dateto)) { class=class="str">"cmt">//--- Check if the end date&class="macro">#x27;s hour is at midnight if(ReturnHour(dateto)==class="num">00||ReturnHour(dateto)==class="num">24) { class=class="str">"cmt">//--- Adjust the date to one minute before midnight dateto = Time(TimeTradeServer(),class="num">23,class="num">59); } class=class="str">"cmt">//--- Re-adjust DateTo Array size ArrayResize(DateTo,class="type">int(ArraySize(DateTo))+class="num">1,class="type">int(ArraySize(DateTo))+class="num">2); class=class="str">"cmt">//--- Assign the last array index dateto value DateTo[class="type">int(ArraySize(DateTo))-class="num">1] = dateto; } } class=class="str">"cmt">//--- Check if there are session times
「从交易时段数组里抠出收盘边界」
MT5 里 SymbolInfoSessionTrade 一次只吐一个时段,想拿当天全部交易 session 的结束时间,得用 0~9 的索引循环去问。代码里写死 for(int i=0;i<10;i++),意味着单品种单日最多认 10 个交易分段,超出的会被静默忽略——做跨市场品种时要留意这个上限。
如果某段结束时间落在 0 点或者 24 点(即跨日无缝),代码不认它当午夜,而是用 Time(TimeTradeServer(),23,59) 强行改成当天 23:59。这一步是防止后面算 sessionend 时把日期推到错误的一天。
拿到所有 dateto 后塞进 DateTo[] 动态数组,再用 ArrayResize 每次扩 1~2 个槽位。随后那段找最大日期的循环,本质是在多个分段里挑最晚收盘的那个——逻辑不复杂,但如果你自己写 EA,直接抄这个数组扩容写法比用 ArrayFree 重来要省事。
外汇和贵金属保证金交易杠杆高、滑点可能在休市前后放大,用这套时段判断去卡单子进场,仍可能踩到流动性真空,概率上不是百分百避坑。
class="type">class="kw">datetime CSessions::SessionEnd(class="type">int offsethour=class="num">0,class="type">int offsetmin=class="num">45) { class=class="str">"cmt">//--- Declarations class="type">class="kw">datetime datefrom,dateto,DateTo[],lastdate=class="num">0,sessionend; class=class="str">"cmt">//--- Find all session times for(class="type">int i=class="num">0;i<class="num">10;i++) { class=class="str">"cmt">//--- Get the session dates for the current symbol and Day of week if(SymbolInfoSessionTrade(Symbol(),DayOfWeek(TimeTradeServer()),i,datefrom,dateto)) { class=class="str">"cmt">//--- Check if the end date&class="macro">#x27;s hour is at midnight if(CTV.ReturnHour(dateto)==class="num">00||CTV.ReturnHour(dateto)==class="num">24) { class=class="str">"cmt">//--- Adjust the date to one minute before midnight dateto = Time(TimeTradeServer(),class="num">23,class="num">59); } class=class="str">"cmt">//--- Re-adjust DateTo Array size ArrayResize(DateTo,class="type">int(ArraySize(DateTo))+class="num">1,class="type">int(ArraySize(DateTo))+class="num">2); class=class="str">"cmt">//--- Assign the last array index dateto value DateTo[class="type">int(ArraySize(DateTo))-class="num">1] = dateto; } } class=class="str">"cmt">//--- Check if there are session times if(DateTo.Size()>class="num">0) { class=class="str">"cmt">//--- Assign lastdate a class="kw">default value lastdate = DateTo[class="num">0];
从交易日历里抠出会话结束时间
这段逻辑干的事很直接:先遍历 DateTo 数组,把里面最大的日期挑出来当作 lastdate,也就是最近一个交易会话的截止日。如果数组里压根没有会话时间,函数直接 return 0,避免后面算出莫名其妙的 datetime。 拿到 lastdate 之后,用 ReturnHour 和 ReturnMinute 把时、分拆出来,再交给 Today() 重组成一个带具体时分的新 datetime 赋给 sessionend。这一步等于把“某天”变成了“某天的某个收盘时刻”。 sessionend 还不是最终值。代码接着调了两次 TimeMinusOffset,分别减去 offsetmin 分钟和 offsethour 小时——这是给用户留的偏移口子,比如夏令时切换或平台延迟结算都能靠这俩参数微调。外汇和贵金属这类高杠杆品种,会话边界差几分钟就可能踏空一两根 K 线,调参前建议在 MT5 策略测试器里用真实品种日历跑一遍确认。 下面把核心片段拆开看:
class=class="str">"cmt">//--- Iterate through the whole array for(class="type">uint i=class="num">0; i<DateTo.Size(); i++) { class=class="str">"cmt">//--- Check for the latest date in the array if(DateTo[i]>lastdate) { lastdate = DateTo[i]; } } } else { class=class="str">"cmt">//--- If there are no trading session times class="kw">return class="num">0; } class=class="str">"cmt">/* get the current time and modify the hour and minute time to the lastdate variable and assign the new class="type">class="kw">datetime to sessionend variable*/ sessionend = Today(ReturnHour(lastdate),ReturnMinute(lastdate)); class=class="str">"cmt">//--- Re-adjust the sessionend dates with the minute and hour offsets sessionend = TimeMinusOffset(sessionend,MinutesS(offsetmin)); sessionend = TimeMinusOffset(sessionend,HoursS(offsethour)); class=class="str">"cmt">//--- class="kw">return sessionend date class="kw">return sessionend; }
◍ 把新闻日历搬进内存:回测不再被硬盘拖后腿
新闻类是整个项目里代码量最重的一块,单类就突破 1000 行。这一节我们不再只依赖公共文件夹里的存储型日历库,而是额外建一个常驻 RAM 的内存数据库——核心动机很直接:回测时若只靠磁盘库,性能会随你电脑配置大幅波动,内存库能把这件事拉平。 内存库相对存储库的优势就两点硬指标:读写走 RAM,延迟显著低于磁盘;吞吐量更高,每秒能扛更多事务。我们仍从存储库抽数灌进内存库,这样回测时既能实时取数又不被本地 IO 拖死。 类外先声明 EA 输入用的句柄与枚举:DBMemoryConnection 存内存库整数连接句柄;Calendar_Importance / Event_Sector / Event_Frequency / Event_Type / Event_Currency 五个枚举分别管事件重要性、领域、频率、类型、货币。UpcomingNews 这个 Calendar 结构放在类外,专门接下一个经济事件明细,别的文件直接读它。 结构数组 CalendarContents 容量从 10 扩到 12,多出的两个视图是 EventInfo_View(事件明细展示)和 Currencies_View(MQL5 日历可用货币)。类里新挂 DBMemory(MQL5CalendarContents 结构)、CalendarData(存公共库全量过滤数据),以及一批 Request_* 函数把枚举翻成 SQL 字符串,GetCalendar 负责按 EA 输入从存储库抽数进 CalendarData.Data。 GetCalendar 的 SQL 把 MQL5Calendar 和 TimeSchedule 按 ID 内连接,再用输入枚举过滤。一个细节:当重要性选“全部”时,故意不转 myImportance,因为库里没有 'Calendar_Importance_All' 这种值,直接取“不等于 All”最省事;选了具体档位才转成 ENUM_CALENDAR_EVENT_IMPORTANCE 去精确匹配。 构造函数里给 EventInfo_View 初始化索引,它的 SQL 把 EVENTTYPE / EVENTSECTOR / EVENTIMPORTANCE 等列前缀 'CALENDAR_*_' 替换掉并改名,按 Name 分组去重,排序先 Country 升序、再 Importance 优先级(HIGH>MODERATE>LOW>其他)、最后 Sector 降序。Currencies_View 则只取 EventCurrency 和 EventCode 的唯一值。 析构时关内存库连接即删库,所以只在不用时才关。UpdateRecords 原三步检查(库存在?对象/SQL 符合?Records 日期=今天?)之外,新增一步:用 EconomicDetails 拉当天新闻存 TodayNews,逐条在 Calendar_NONE 视图里找匹配,只要有一条对不上就触发存储库更新——因为 MQL5 日历源数据会随时间变。 建内存库的函数先开连接、删旧 MQL5Calendar 表、建新表,再把 GetCalendar 的数据插进去,随后清空 DB_Data 并设 MySchedule 的 DST 计划;实盘下不允许手动改 DST,防配错,仅策略测试器里才需要动。 EconomicDetailsMemory 用 WITH 子句(CTE)加 RANK() 窗口函数按 DST_NONE 事件时间排名:同时间多个事件时,EventPrevalue='None' 排 2,其余排 1,只留排名<2 的,避免图表上同一时间叠多个对象。EconomicNextEvent 查询逻辑相同但 LIMIT 1,结果写进类外的 UpcomingNews。外汇与贵金属事件驱动波动剧烈,内存库只是提速手段,信号真假仍靠你自己验证。
「用历史同类事件反推新闻影响等级」
新闻事件的影响枚举 ENUM_CALENDAR_EVENT_IMPACT 在事件未发生当天通常记为 CALENDAR_IMPACT_NA,也就是不可用。只有事件日期过去、且事前同时具备前值(Prev)与预测值(Forecast)之后,系统才可能回填影响;即便条件齐全,少数事件仍会留空。 GetImpact 的逻辑是:当即将发生事件的前值与预测均非 None,就先比大小——前值小于、大于或等于预测,分别对应三种关系。随后在内存库 MQL5Calendar 里翻找相同 EventId、且前值与预测关系一致的「最近一条」历史事件,把它留下的影响等级借过来,作为本次事件的预估影响。 影响本身是按货币衡量的。例如失业率事件,若历史同类前低预高、影响为 CALENDAR_IMPACT_NEGATIVE 且货币是 USD,则美元当时倾向走弱;事件后做 EURUSD,理论上看 EUR 对 USD 升值的概率更高。外汇与贵金属受此类新闻冲击波动剧烈,属高风险场景,历史映射仅作概率参考。 实际 SQL 形态如:选 EventImpact,限定日期早于『2024.08.01 16:30』、EventId=124500001、EventPrevalue<EventForecast 且影响非 NA,按日期降序取一条。下面这段代码给出了日历筛选常用的枚举骨架,可直接拷进 MT5 看结构。
<span class="keyword">class="type">int</span> DBMemoryConnection;<span class="comment">class=class="str">"cmt">//In memory database handle</span> <span class="comment">class=class="str">"cmt">//--- Enumeration for Calendar Importance</span> <span class="keyword">enum</span> <span style="background-class="type">class="kw">color:rgb(class="num">255, class="num">246, class="num">200);">Calendar_Importance</span> { Calendar_Importance_None,<span class="comment">class=class="str">"cmt">//NONE</span> Calendar_Importance_Low,<span class="comment">class=class="str">"cmt">//LOW</span> Calendar_Importance_Moderate,<span class="comment">class=class="str">"cmt">//MODERATE</span> Calendar_Importance_High,<span class="comment">class=class="str">"cmt">//HIGH</span> Calendar_Importance_All<span class="comment">class=class="str">"cmt">//ALL</span> } myImportance; <span class="comment">class=class="str">"cmt">//--- Enumeration for Calendar Sector</span> <span class="keyword">enum</span> <span style="background-class="type">class="kw">color:rgb(class="num">255, class="num">246, class="num">200);">Event_Sector</span> { Event_Sector_None,<span class="comment">class=class="str">"cmt">//NONE</span> Event_Sector_Market,<span class="comment">class=class="str">"cmt">//MARKET</span> Event_Sector_Gdp,<span class="comment">class=class="str">"cmt">//GDP</span> Event_Sector_Jobs,<span class="comment">class=class="str">"cmt">//JOBS</span> Event_Sector_Prices,<span class="comment">class=class="str">"cmt">//PRICES</span> Event_Sector_Money,<span class="comment">class=class="str">"cmt">//MONEY</span> Event_Sector_Trade,<span class="comment">class=class="str">"cmt">//TRADE</span> Event_Sector_Government,<span class="comment">class=class="str">"cmt">//GOVERNMENT</span> Event_Sector_Business,<span class="comment">class=class="str">"cmt">//BUSINESS</span> Event_Sector_Consumer,<span class="comment">class=class="str">"cmt">//CONSUMER</span> Event_Sector_Housing,<span class="comment">class=class="str">"cmt">//HOUSING</span> Event_Sector_Taxes,<span class="comment">class=class="str">"cmt">//TAXES</span> Event_Sector_Holidays,<span class="comment">class=class="str">"cmt">//HOLIDAYS</span> Event_Sector_ALL<span class="comment">class=class="str">"cmt">//ALL</span> } mySector; <span class="comment">class=class="str">"cmt">//--- Enumeration for Calendar Event Frequency</span> <span class="keyword">enum</span> <span style="background-class="type">class="kw">color:rgb(class="num">255, class="num">246, class="num">200);">Event_Frequency</span> { Event_Frequency_None,<span class="comment">class=class="str">"cmt">//NONE</span> Event_Frequency_Week,<span class="comment">class=class="str">"cmt">//WEEK</span> Event_Frequency_Month,<span class="comment">class=class="str">"cmt">//MONTH</span> Event_Frequency_Quarter,<span class="comment">class=class="str">"cmt">//QUARTER</span> Event_Frequency_Year,<span class="comment">class=class="str">"cmt">//YEAR</span> Event_Frequency_Day,<span class="comment">class=class="str">"cmt">//DAY</span> Event_Frequency_ALL<span class="comment">class=class="str">"cmt">//ALL</span> } myFrequency; <span class="comment">class=class="str">"cmt">//--- Enumeration for Calendar Event type</span> <span class="keyword">enum</span> <span style="background-class="type">class="kw">color:rgb(class="num">255, class="num">246, class="num">200);">Event_Type</span> { Event_Type_Event,<span class="comment">class=class="str">"cmt">//EVENT</span> Event_Type_Indicator,<span class="comment">class=class="str">"cmt">//INDICATOR</span> Event_Type_Holiday,<span class="comment">class=class="str">"cmt">//HOLIDAY</span> Event_Type_All<span class="comment">class=class="str">"cmt">//ALL</span> } myType; <span class="comment">class=class="str">"cmt">//--- Enumeration for Calendar Event Currency</span> <span class="keyword">enum</span> <span style="background-class="type">class="kw">color:rgb(class="num">255, class="num">246, class="num">200);">Event_Currency</span> { Event_Currency_Symbol,<span class="comment">class=class="str">"cmt">//SYMBOL CURRENCIES</span> Event_Currency_Margin,<span class="comment">class=class="str">"cmt">//SYMBOL MARGIN</span> Event_Currency_Base,<span class="comment">class=class="str">"cmt">//SYMBOL BASE</span> Event_Currency_Profit,<span class="comment">class=class="str">"cmt">//SYMBOL PROFIT</span> Event_Currency_ALL,<span class="comment">class=class="str">"cmt">//ALL CURRENCIES</span> Event_Currency_NZD_NZ,<span class="comment">class=class="str">"cmt">//NZD -> NZ</span> Event_Currency_EUR_EU,<span class="comment">class=class="str">"cmt">//EUR -> EU</span> Event_Currency_JPY_JP,<span class="comment">class=class="str">"cmt">//JPY -> JP</span> Event_Currency_CAD_CA,<span class="comment">class=class="str">"cmt">//CAD -> CA</span> Event_Currency_AUD_AU,<span class="comment">class=class="str">"cmt">//AUD -> AU</span> Event_Currency_CNY_CN,<span class="comment">class=class="str">"cmt">//CNY -> CN</span> Event_Currency_EUR_IT,<span class="comment">class=class="str">"cmt">//EUR -> IT</span> Event_Currency_SGD_SG,<span class="comment">class=class="str">"cmt">//SGD -> SG</span>
财经事件库里的币种与表结构枚举
在 MT5 自建财经日历模块时,先把涉及的经济体币种写成枚举是最省事的做法。下面这段把 EUR、USD、GBP 等 15 组货币对归属地列进 myCurrency 枚举,最后用 Event_Currency_ALL_WW 兜底全球事件,开 MT5 新建 enum 直接照抄就能用。
Event_Currency_EUR_DE,class=class="str">"cmt">//EUR -> DE Event_Currency_EUR_FR,class=class="str">"cmt">//EUR -> FR Event_Currency_BRL_BR,class=class="str">"cmt">//BRL -> BR Event_Currency_MXN_MX,class=class="str">"cmt">//MXN -> MX Event_Currency_ZAR_ZA,class=class="str">"cmt">//ZAR -> ZA Event_Currency_HKD_HK,class=class="str">"cmt">//HKD -> HK Event_Currency_INR_IN,class=class="str">"cmt">//INR -> IN Event_Currency_NOK_NO,class=class="str">"cmt">//NOK -> NO Event_Currency_USD_US,class=class="str">"cmt">//USD -> US Event_Currency_GBP_GB,class=class="str">"cmt">//GBP -> GB Event_Currency_CHF_CH,class=class="str">"cmt">//CHF -> CH Event_Currency_KRW_KR,class=class="str">"cmt">//KRW -> KW Event_Currency_EUR_ES,class=class="str">"cmt">//EUR -> ES Event_Currency_SEK_SE,class=class="str">"cmt">//SEK -> SE Event_Currency_ALL_WWclass=class="str">"cmt">//ALL -> WW } myCurrency;
Event_Currency_EUR_DE,class=class="str">"cmt">//EUR -> DE Event_Currency_EUR_FR,class=class="str">"cmt">//EUR -> FR Event_Currency_BRL_BR,class=class="str">"cmt">//BRL -> BR Event_Currency_MXN_MX,class=class="str">"cmt">//MXN -> MX Event_Currency_ZAR_ZA,class=class="str">"cmt">//ZAR -> ZA Event_Currency_HKD_HK,class=class="str">"cmt">//HKD -> HK Event_Currency_INR_IN,class=class="str">"cmt">//INR -> IN Event_Currency_NOK_NO,class=class="str">"cmt">//NOK -> NO Event_Currency_USD_US,class=class="str">"cmt">//USD -> US Event_Currency_GBP_GB,class=class="str">"cmt">//GBP -> GB Event_Currency_CHF_CH,class=class="str">"cmt">//CHF -> CH Event_Currency_KRW_KR,class=class="str">"cmt">//KRW -> KW Event_Currency_EUR_ES,class=class="str">"cmt">//EUR -> ES Event_Currency_SEK_SE,class=class="str">"cmt">//SEK -> SE Event_Currency_ALL_WWclass=class="str">"cmt">//ALL -> WW } myCurrency; class=class="str">"cmt">//--- Structure variable to store Calendar next Event data Calendar UpcomingNews; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|News class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CNews : class="kw">private CCandleProperties { class=class="str">"cmt">//Private Declarations Only accessable by this class/header file class="kw">private: class=class="str">"cmt">//-- To keep track of what is in our database enum CalendarComponents { AutoDST_Table,class=class="str">"cmt">//AutoDST Table CalendarAU_View,class=class="str">"cmt">//View for DST_AU CalendarNONE_View,class=class="str">"cmt">//View for DST_NONE CalendarUK_View,class=class="str">"cmt">//View for DST_UK CalendarUS_View,class=class="str">"cmt">//View for DST_US EventInfo_View,class=class="str">"cmt">//View for Event Information Currencies_View,class=class="str">"cmt">//View for Currencies Record_Table,class=class="str">"cmt">// Record Table TimeSchedule_Table,class=class="str">"cmt">//TimeSchedule Table MQL5Calendar_Table,class=class="str">"cmt">//MQL5Calendar Table AutoDST_Trigger,class=class="str">"cmt">//Table Trigger for AutoDST Record_Triggerclass=class="str">"cmt">//Table Trigger for Record }; class=class="str">"cmt">//-- structure to retrieve all the objects in the database class="kw">struct SQLiteMaster { class="type">class="kw">string type;class=class="str">"cmt">//will store object&class="macro">#x27;s type class="type">class="kw">string name;class=class="str">"cmt">//will store object&class="macro">#x27;s name class="type">class="kw">string tbl_name;class=class="str">"cmt">//will store table name class="type">int rootpage;class=class="str">"cmt">//will store rootpage class="type">class="kw">string sql;class=class="str">"cmt">//Will store the sql create statement } DBContents[];class=class="str">"cmt">//Array of type SQLiteMaster class=class="str">"cmt">//-- MQL5CalendarContents inherits from SQLiteMaster structure class="kw">struct MQL5CalendarContents:SQLiteMaster { CalendarComponents Content; class="type">class="kw">string insert;class=class="str">"cmt">//Will store the sql insert statement } CalendarContents[class="num">12],DBMemory;class=class="str">"cmt">//Array to Store objects in our database CTimeManagement CTime;class=class="str">"cmt">//TimeManagement Object declaration CDaylightSavings_UK Savings_UK;class=class="str">"cmt">//DaylightSavings Object for the UK and EU CDaylightSavings_US Savings_US;class=class="str">"cmt">//DaylightSavings Object for the US
◍ 把财经日历塞进本地数据库的类骨架
在 MT5 里做事件驱动策略,第一步往往是把 MQL5 自带的财经日历抓出来落盘。下面这段类声明给出了一个可直接复用的骨架:它同时管时区(含澳洲夏令时对象)、DST 自动识别,以及把事件写进 SQLite 一类数据库的几张表。 类里先挂了 CDaylightSavings_AU Savings_AU 用来处理澳洲经纪商夏令时偏移;AutoDetectDST 通过引用 DST_type 枚举回吐当前识别到的经纪商 DST 类型。InsertIntoTables 接收 Calendar 数组和数据库句柄,把一条条事件批量写表。 CreateAutoDST 会在数据表里写入系统推荐的经纪商 DST;CreateCalendarTable / CreateTimeTable 都带 tableExists 引用参数,避免重复建表报错。CreateCalendarViews 建视图方便按国家、币种筛数据,CreateRecordTable 则记最后一次更新时间,作为增量刷新依据。 CalendarStruct 是个轻量查找函数:遍历 CalendarContents 数组,比对 Content 枚举,命中就返回对应的 MQL5CalendarContents。它不查库,纯内存匹配,在 OnTimer 里反复调也几乎零开销。 真正存数的结构是 CalendarData:从 EventId、Country 到 EventForecast / EventPreval / EventImpact 共 14 个字段,覆盖事件定位、重要度、预期与前值。你照这个 struct 建表,就能用 EA 把每次非农、利率决议的原始字段全留痕,后续回测不用再依赖实时 API。
CDaylightSavings_AU Savings_AU;class=class="str">"cmt">//DaylightSavings Object for the AU class="type">bool AutoDetectDST(DST_type &dstType);class=class="str">"cmt">//Function will determine Broker DST DST_type DSTType;class=class="str">"cmt">//variable of DST_type enumeration declared in the CommonVariables class/header file class="type">bool InsertIntoTables(class="type">int db,Calendar &Evalues[]);class=class="str">"cmt">//Function for inserting Economic Data in to a database&class="macro">#x27;s table class="type">void CreateAutoDST(class="type">int db);class=class="str">"cmt">//Function for creating and inserting Recommend DST for the Broker into a table class="type">bool CreateCalendarTable(class="type">int db,class="type">bool &tableExists);class=class="str">"cmt">//Function for creating a table in a database class="type">bool CreateTimeTable(class="type">int db,class="type">bool &tableExists);class=class="str">"cmt">//Function for creating a table in a database class="type">void CreateCalendarViews(class="type">int db);class=class="str">"cmt">//Function for creating views in a database class="type">void CreateRecordTable(class="type">int db);class=class="str">"cmt">//Creates a table to store the record of when last the Calendar database was updated/created class="type">class="kw">string DropRequest;class=class="str">"cmt">//Variable for dropping tables in the database class=class="str">"cmt">//-- Function for retrieving the MQL5CalendarContents structure for the enumartion type CalendarComponents MQL5CalendarContents CalendarStruct(CalendarComponents Content) { MQL5CalendarContents Calendar; for(class="type">uint i=class="num">0;i<CalendarContents.Size();i++) { if(CalendarContents[i].Content==Content) { class="kw">return CalendarContents[i]; } } class="kw">return Calendar; } class=class="str">"cmt">//--- To Store Calendar DB Data class="kw">struct CalendarData { class="type">int EventId;class=class="str">"cmt">//Event Id class="type">class="kw">string Country;class=class="str">"cmt">//Event Country class="type">class="kw">string EventName;class=class="str">"cmt">//Event Name class="type">class="kw">string EventType;class=class="str">"cmt">//Event Type class="type">class="kw">string EventImportance;class=class="str">"cmt">//Event Importance class="type">class="kw">string EventCurrency;class=class="str">"cmt">//Event Currency class="type">class="kw">string EventCode;class=class="str">"cmt">//Event Code class="type">class="kw">string EventSector;class=class="str">"cmt">//Event Sector class="type">class="kw">string EventForecast;class=class="str">"cmt">//Event Forecast Value class="type">class="kw">string EventPreval;class=class="str">"cmt">//Event Previous Value class="type">class="kw">string EventImpact;class=class="str">"cmt">//Event Impact class="type">class="kw">string EventFrequency;class=class="str">"cmt">//Event Frequency
「用 SQL 把财经日历滤进内存结构」
在 MT5 里做事件驱动型策略,第一步是把存在公共目录的日历库读进内存。下面这段结构定义了四个夏令时字段 DST_UK / DST_US / DST_AU / DST_NONE,并和主事件表 MQ 通过 ID 内连接,读者可以直接照抄字段命名。
| GetCalendar 函数先用 DatabaseOpen 以读写的公共模式开库,三种 flag 并用:DATABASE_OPEN_READWRITE | DATABASE_OPEN_CREATE | DATABASE_OPEN_COMMON。若句柄为 INVALID_HANDLE 且 FileIsExist 在 FILE_COMMON 下也找不到文件,直接 return,避免后续空指针崩在 SQL 阶段。 |
|---|
真正取数靠一条 Inner Join 的 SELECT,把事件 ID、国家、币种、重要性、预测值、前值以及四套夏令时偏移一次性拉出。过滤条件由 Request_Importance / Request_Frequency / Request_Sector / Request_Type / Request_Currency 五个函数动态拼装,意味着你改一个货币对参数就能把数据量从全量压到几百行。 执行侧用 DatabasePrepare 编译 SQL,返回 INVALID_HANDLE 时 Print 出错误码。外汇与贵金属受数据行情跳空影响大,这类日历过滤只是前置,实盘前务必在策略测试器用历史新闻回放验证触发逻辑。
class="type">class="kw">string DST_UK;class=class="str">"cmt">//DST UK class="type">class="kw">string DST_US;class=class="str">"cmt">//DST US class="type">class="kw">string DST_AU;class=class="str">"cmt">//DST AU class="type">class="kw">string DST_NONE;class=class="str">"cmt">//DST NONE } DB_Data[],DB_Cal;class=class="str">"cmt">//Structure variables class=class="str">"cmt">//--- Will Retrieve all relevant Calendar data for DB in Memory from DB in Storage class="type">void GetCalendar(CalendarData &Data[]) { class=class="str">"cmt">//--- Open calendar DB in Storage class="type">int db=DatabaseOpen(NEWS_DATABASE_FILE, DATABASE_OPEN_READWRITE | DATABASE_OPEN_CREATE| DATABASE_OPEN_COMMON); if(db==INVALID_HANDLE)class=class="str">"cmt">//Checks if the database was able to be opened { class=class="str">"cmt">//if opening the database failed if(!FileIsExist(NEWS_DATABASE_FILE,FILE_COMMON))class=class="str">"cmt">//Checks if the database Calendar exists in the common folder { class="kw">return;class=class="str">"cmt">//Returns true when the database was failed to be opened and the file doesn&class="macro">#x27;t exist in the common folder } } class=class="str">"cmt">//--- Get filtered calendar DB data class="type">class="kw">string SqlRequest = StringFormat("Select MQ.EventId,MQ.Country,MQ.EventName,MQ.EventType,MQ.EventImportance,MQ.EventCurrency," "MQ.EventCode,MQ.EventSector,MQ.EventForecast,MQ.EventPreValue,MQ.EventImpact,MQ.EventFrequency," "TS.DST_UK,TS.DST_US,TS.DST_AU,TS.DST_NONE from %s MQ " "Inner Join %s TS on TS.ID=MQ.ID " "Where %s and %s and %s and %s and %s;", CalendarStruct(MQL5Calendar_Table).name,CalendarStruct(TimeSchedule_Table).name, Request_Importance(myImportance),Request_Frequency(myFrequency), Request_Sector(mySector),Request_Type(myType),Request_Currency(myCurrency)); class=class="str">"cmt">//--- Process Sql request class="type">int Request = DatabasePrepare(db,SqlRequest); if(Request==INVALID_HANDLE) { class=class="str">"cmt">//--- Print details if request failed. Print("DB: ",NEWS_DATABASE_FILE, " request failed with code ", GetLastError());
从财经日历库里抠出自动 DST 标记
在 MT5 里做跨时区新闻过滤,最怕夏令时切换把日历事件时间整体偏移一小时。上面这段逻辑直接打开公共目录下的 Calendar 库(NEWS_DATABASE_FILE),用只读方式取 AutoDST 表里的 DST 字段,返回枚举值供后续判断。 打开库先走 DatabaseOpen,带 DATABASE_OPEN_READONLY| DATABASE_OPEN_COMMON 标志;若句柄等于 INVALID_HANDLE,再查 FileIsExist 确认库文件是否真缺失,缺失就 Print("Could not find Database!") 并返回 DST_NONE。这一层容错能保证 EA 在终端没下载财经库时不崩。 SQL 请求写死为 "SELECT DST FROM 'AutoDST'",经 DatabasePrepare 编译。若返回 INVALID_HANDLE,打错误码并关库回退 DST_NONE。 DatabaseRead 成功后才用 DatabaseColumnText(request,0,Sch_Dst) 抓第一列文本;若这步失败,错误码会被 Print 出来。整个过程不碰写权限,实盘跑前你可在策略测试器里把 NEWS_DATABASE_FILE 指到本地副本,验证返回值是 DST_NONE 还是具体偏移枚举。外汇与贵金属受数据跳空影响大,夏令时误判可能让止损挂在错的时间位,属高风险操作。
DST_type GetAutoDST() { class="type">class="kw">string Sch_Dst; class=class="str">"cmt">//--- open the database &class="macro">#x27;Calendar&class="macro">#x27; in the common folder class="type">int db=DatabaseOpen(NEWS_DATABASE_FILE, DATABASE_OPEN_READONLY|DATABASE_OPEN_COMMON); if(db==INVALID_HANDLE)class=class="str">"cmt">//Checks if &class="macro">#x27;Calendar&class="macro">#x27; failed to be opened { if(!FileIsExist(NEWS_DATABASE_FILE,FILE_COMMON))class=class="str">"cmt">//Checks if &class="macro">#x27;Calendar&class="macro">#x27; database exists { Print("Could not find Database!"); class="kw">return DST_NONE;class=class="str">"cmt">//class="kw">return class="kw">default value when failed. } } class=class="str">"cmt">//--- Sql query to get AutoDST value class="type">class="kw">string request_text="SELECT DST FROM &class="macro">#x27;AutoDST&class="macro">#x27;"; class=class="str">"cmt">//--- Process sql request class="type">int request=DatabasePrepare(db,request_text); if(request==INVALID_HANDLE) { Print("DB: ",NEWS_DATABASE_FILE, " request failed with code ", GetLastError()); DatabaseClose(db);class=class="str">"cmt">//Close Database class="kw">return DST_NONE;class=class="str">"cmt">//class="kw">return class="kw">default value when failed. } class=class="str">"cmt">//--- Read Sql request output data if(DatabaseRead(request)) { class=class="str">"cmt">//-- Store the first column data into class="type">class="kw">string variable Sch_Dst if(!DatabaseColumnText(request,class="num">0,Sch_Dst)) { Print("DatabaseRead() failed with code ", GetLastError());
◍ 正文
<span class="functions">DatabaseFinalize</span>(request);<span class="comment">//Finalize request</span> <span class="functions">DatabaseClose</span>(db);<span class="comment">//Closes the database 'Calendar'</span> <span class="keyword">return</span> DST_NONE;<span class="comment">//return default value when failed.</span> } } <span class="functions">DatabaseFinalize</span>(request);<span class="comment">//Finalize request</span> <span class="functions">DatabaseClose</span>(db);<span class="comment">//Closes the database 'Calendar'</span> <span class="keyword">return</span> (Sch_Dst==<span class="string">"DST_UK"</span>)?DST_UK:(Sch_Dst==<span class="string">"DST_US"</span>)?DST_US: (Sch_Dst==<span class="string">"DST_AU"</span>)?DST_AU:DST_NONE;<span class="comment">//Returns the enumeration value for each corresponding string</span> } <span class="comment">//--- Retrieve Sql request string for calendar event Importance</span>
「经济日历 SQL 过滤串的拼装逻辑」
在 MT5 里抓取财经日历数据,核心是把枚举参数翻译成可被内部数据库识别的 SQL 请求串。下面这段分别按板块、类型、货币三个维度拼条件,读者可直接抄进 EA 的日历筛选模块。
Request_Sector 用 switch 区分全板块与具体板块:选 ALL 时返回 MQ.EventSector<>'枚举值',即排除自身占位;其他情况用 MQ.EventSector='枚举值' 精确锁定。注意 EnumToString(mySector) 与 SECTOR(mySector) 两种写法并存,说明代码里对枚举到字符串的映射做了两层封装。
Request_Type 结构完全一致,常量前缀是 MQ.EventType,ALL 态用不等于号,default 态用等于号。
Request_Currency 稍微复杂:前缀 (MQ.EventCurrency、后缀 ') 成对出现;Event_Currency_Symbol 会一次性拼出基础币、保证金币、利润币三个 OR 条件,例如 EURUSD 可能生成 (MQ.EventCurrency='EUR' or MQ.EventCurrency='USD' or MQ.EventCurrency='USD')。外汇与贵金属受财经事件冲击剧烈,这类筛选失误可能导致信号在重大数据发布时失真,实盘前务必在策略测试器里打印请求串核对。
class=class="str">"cmt">//--- class="kw">switch statement for Event_Sector enumeration class="kw">switch(Sector) { case Event_Sector_ALL:class=class="str">"cmt">//String Request for all event sectors class="kw">return constant+"<>&class="macro">#x27;"+EnumToString(mySector)+"&class="macro">#x27;"; class="kw">break; class="kw">default:class=class="str">"cmt">//String Request for any event sector class="kw">return constant+"=&class="macro">#x27;"+EnumToString(SECTOR(mySector))+"&class="macro">#x27;"; class="kw">break; } } class=class="str">"cmt">//--- Retrieve Sql request class="type">class="kw">string for calendar event type class="type">class="kw">string Request_Type(Event_Type Type) { class=class="str">"cmt">//--- Constant request prefix class="type">class="kw">string class="kw">const class="type">class="kw">string constant="MQ.EventType"; class=class="str">"cmt">//--- class="kw">switch statement for Event_Type enumeration class="kw">switch(Type) { case Event_Type_All:class=class="str">"cmt">//String Request for all event types class="kw">return constant+"<>&class="macro">#x27;"+EnumToString(myType)+"&class="macro">#x27;"; class="kw">break; class="kw">default:class=class="str">"cmt">//String request for any event type class="kw">return constant+"=&class="macro">#x27;"+EnumToString(TYPE(myType))+"&class="macro">#x27;"; class="kw">break; } } class=class="str">"cmt">//--- Retrieve Sql request class="type">class="kw">string for calendar event Currency class="type">class="kw">string Request_Currency(Event_Currency Currency) { class=class="str">"cmt">//--- Constant request prefix class="type">class="kw">string and request suffix class="kw">const class="type">class="kw">string constant_prefix="(MQ.EventCurrency",constant_suffix="&class="macro">#x27;)"; class=class="str">"cmt">//--- class="kw">switch statement for Event_Currency enumeration class="kw">switch(Currency) { case Event_Currency_ALL:class=class="str">"cmt">//String Request for all currencies class="kw">return constant_prefix+"<>&class="macro">#x27;"+EnumToString(myCurrency)+constant_suffix; class="kw">break; case Event_Currency_Symbol:class=class="str">"cmt">//String Request for all symbol currencies class="kw">return constant_prefix+"=&class="macro">#x27;"+CSymbol.CurrencyBase()+"&class="macro">#x27; or MQ.EventCurrency=&class="macro">#x27;"+ CSymbol.CurrencyMargin()+"&class="macro">#x27; or MQ.EventCurrency=&class="macro">#x27;"+CSymbol.CurrencyProfit()+constant_suffix; class="kw">break; case Event_Currency_Margin:class=class="str">"cmt">//String Request for Margin currency class="kw">return constant_prefix+"=&class="macro">#x27;"+CSymbol.CurrencyMargin()+constant_suffix; class="kw">break;
按币种拼经济事件查询串
在 MT5 自定义经济日历抓取模块里,不同币种的事件筛选要靠 switch 分支拼出不同的 SQL 风格请求串。基础货币与利润货币直接取 CSymbol 的成员,而具体币种(如 NZD、JPY)则把货币代码和地区事件码(MQ.EventCode)一起写死进条件。 下面这段分支覆盖了 NZD、EUR(含 EU/IT/DE/FR 多个地区码)、JPY、CAD、AUD、CNY、SGD 等十余个常见货币对关联事件。外汇与贵金属受多国宏观数据驱动,这类筛选若漏掉地区码,可能把无关经济区事件混进信号池,放大噪音。 直接看原分支写法,注意 constant_prefix 与 constant_suffix 通常封装了表名与引号后缀,复制时别丢:
case Event_Currency_Base:class=class="str">"cmt">//String Request for Base currency class="kw">return constant_prefix+"=&class="macro">#x27;"+CSymbol.CurrencyBase()+constant_suffix; class="kw">break; case Event_Currency_Profit:class=class="str">"cmt">//String Request for Profit currency class="kw">return constant_prefix+"=&class="macro">#x27;"+CSymbol.CurrencyProfit()+constant_suffix; class="kw">break; case Event_Currency_NZD_NZ:class=class="str">"cmt">//String Request for NZD currency class="kw">return constant_prefix+"=&class="macro">#x27;NZD&class="macro">#x27; and MQ.EventCode=&class="macro">#x27;NZ"+constant_suffix; class="kw">break; case Event_Currency_EUR_EU:class=class="str">"cmt">//String Request for EUR currency and EU code class="kw">return constant_prefix+"=&class="macro">#x27;EUR&class="macro">#x27; and MQ.EventCode=&class="macro">#x27;EU"+constant_suffix; class="kw">break; case Event_Currency_JPY_JP:class=class="str">"cmt">//String Request for JPY currency class="kw">return constant_prefix+"=&class="macro">#x27;JPY&class="macro">#x27; and MQ.EventCode=&class="macro">#x27;JP"+constant_suffix; class="kw">break; case Event_Currency_CAD_CA:class=class="str">"cmt">//String Request for CAD currency class="kw">return constant_prefix+"=&class="macro">#x27;CAD&class="macro">#x27; and MQ.EventCode=&class="macro">#x27;CA"+constant_suffix; class="kw">break; case Event_Currency_AUD_AU:class=class="str">"cmt">//String Request for AUD currency class="kw">return constant_prefix+"=&class="macro">#x27;AUD&class="macro">#x27; and MQ.EventCode=&class="macro">#x27;AU"+constant_suffix; class="kw">break; case Event_Currency_CNY_CN:class=class="str">"cmt">//String Request for CNY currency class="kw">return constant_prefix+"=&class="macro">#x27;CNY&class="macro">#x27; and MQ.EventCode=&class="macro">#x27;CN"+constant_suffix; class="kw">break; case Event_Currency_EUR_IT:class=class="str">"cmt">//String Request for EUR currency and IT code class="kw">return constant_prefix+"=&class="macro">#x27;EUR&class="macro">#x27; and MQ.EventCode=&class="macro">#x27;IT"+constant_suffix; class="kw">break; case Event_Currency_SGD_SG:class=class="str">"cmt">//String Request for SGD currency class="kw">return constant_prefix+"=&class="macro">#x27;SGD&class="macro">#x27; and MQ.EventCode=&class="macro">#x27;SG"+constant_suffix; class="kw">break; case Event_Currency_EUR_DE:class=class="str">"cmt">//String Request for EUR currency and DE code class="kw">return constant_prefix+"=&class="macro">#x27;EUR&class="macro">#x27; and MQ.EventCode=&class="macro">#x27;DE"+constant_suffix; class="kw">break; case Event_Currency_EUR_FR:class=class="str">"cmt">//String Request for EUR currency and FR code class="kw">return constant_prefix+"=&class="macro">#x27;EUR&class="macro">#x27; and MQ.EventCode=&class="macro">#x27;FR"+constant_suffix; class="kw">break; case Event_Currency_BRL_BR:class=class="str">"cmt">//String Request for BRL currency
◍ 按币种拼出财经事件查询串
这段 switch 分支负责把枚举型的币种事件映射成 MQ 事件表的 SQL 风格过滤串,前缀和后缀由 constant_prefix / constant_suffix 统一接管,中间只换币种代码与事件国家码。
例如 BRL 分支返回 constant_prefix+'=\'BRL\' and MQ.EventCode=\'BR'+constant_suffix,意味着巴西雷亚尔相关事件在底层数据源里用 BR 作国家前缀。类似地 MXN→MX、ZAR→ZA、HKD→HK、INR→IN、NOK→NO、USD→US、GBP→GB、CHF→CH、KRW→KR、EUR→ES、SEK→SE、ALL→WW,共覆盖了 13 个币种分支。
在 MT5 里接经济日历 API 时,若你只盯美系与欧系,可删掉 BRL/MXN/ZAR 等新兴市场分支,把 switch 缩短到 7 行以内,减少字符串拼接开销。外汇与贵金属受这些事件冲击的概率偏高,实盘前务必在策略测试器里用历史事件回放验证过滤串是否漏抓。
class="kw">return constant_prefix+"=&class="macro">#x27;BRL&class="macro">#x27; and MQ.EventCode=&class="macro">#x27;BR"+constant_suffix; class="kw">break; case Event_Currency_MXN_MX:class=class="str">"cmt">//String Request for MXN currency class="kw">return constant_prefix+"=&class="macro">#x27;MXN&class="macro">#x27; and MQ.EventCode=&class="macro">#x27;MX"+constant_suffix; class="kw">break; case Event_Currency_ZAR_ZA:class=class="str">"cmt">//String Request for ZAR currency class="kw">return constant_prefix+"=&class="macro">#x27;ZAR&class="macro">#x27; and MQ.EventCode=&class="macro">#x27;ZA"+constant_suffix; class="kw">break; case Event_Currency_HKD_HK:class=class="str">"cmt">//String Request for HKD currency class="kw">return constant_prefix+"=&class="macro">#x27;HKD&class="macro">#x27; and MQ.EventCode=&class="macro">#x27;HK"+constant_suffix; class="kw">break; case Event_Currency_INR_IN:class=class="str">"cmt">//String Request for INR currency class="kw">return constant_prefix+"=&class="macro">#x27;INR&class="macro">#x27; and MQ.EventCode=&class="macro">#x27;IN"+constant_suffix; class="kw">break; case Event_Currency_NOK_NO:class=class="str">"cmt">//String Request for NOK currency class="kw">return constant_prefix+"=&class="macro">#x27;NOK&class="macro">#x27; and MQ.EventCode=&class="macro">#x27;NO"+constant_suffix; class="kw">break; case Event_Currency_USD_US:class=class="str">"cmt">//String Request for USD currency class="kw">return constant_prefix+"=&class="macro">#x27;USD&class="macro">#x27; and MQ.EventCode=&class="macro">#x27;US"+constant_suffix; class="kw">break; case Event_Currency_GBP_GB:class=class="str">"cmt">//String Request for GBP currency class="kw">return constant_prefix+"=&class="macro">#x27;GBP&class="macro">#x27; and MQ.EventCode=&class="macro">#x27;GB"+constant_suffix; class="kw">break; case Event_Currency_CHF_CH:class=class="str">"cmt">//String Request for CHF currency class="kw">return constant_prefix+"=&class="macro">#x27;CHF&class="macro">#x27; and MQ.EventCode=&class="macro">#x27;CH"+constant_suffix; class="kw">break; case Event_Currency_KRW_KR:class=class="str">"cmt">//String Request for KRW currency class="kw">return constant_prefix+"=&class="macro">#x27;KRW&class="macro">#x27; and MQ.EventCode=&class="macro">#x27;KR"+constant_suffix; class="kw">break; case Event_Currency_EUR_ES:class=class="str">"cmt">//String Request for EUR currency and ES code class="kw">return constant_prefix+"=&class="macro">#x27;EUR&class="macro">#x27; and MQ.EventCode=&class="macro">#x27;ES"+constant_suffix; class="kw">break; case Event_Currency_SEK_SE:class=class="str">"cmt">//String Request for SEK currency class="kw">return constant_prefix+"=&class="macro">#x27;SEK&class="macro">#x27; and MQ.EventCode=&class="macro">#x27;SE"+constant_suffix; class="kw">break; case Event_Currency_ALL_WW:class=class="str">"cmt">//String Request for ALL currency class="kw">return constant_prefix+"=&class="macro">#x27;ALL&class="macro">#x27; and MQ.EventCode=&class="macro">#x27;WW"+constant_suffix; class="kw">break;
「把财经日历重要性字符串映射成枚举」
在 MT5 的财经日历封装类里,公开接口暴露了一组可直接通过对象调用的方法:建库、取最新日期、拉指定区间事件、内存态查询与下次事件预读。其中 IMPORTANCE(string) 是个容易被忽略但很实用的小函数,它负责把外部传进来的字符串转回平台原生的 ENUM_CALENDAR_EVENT_IMPORTANCE 枚举。 逻辑不复杂,用 EnumToString 把枚举值翻成字符串再和入参比对:命中 CALENDAR_IMPORTANCE_HIGH 就回 HIGH,命中 MODERATE 回 MODERATE,命中 LOW 回 LOW,其余落进最后的 else 分支。这样写的好处是,从 CSV 或经纪商接口读到的文本重要性,能无缝喂给 Calendar 系列函数,不必手搓一堆 if 判断。 外汇与贵金属受数据行情冲击明显,高影响事件前后点差可能瞬间放大,用这个类做事件过滤时,建议只对 HIGH/MODERATE 做报警或暂停开仓,LOW 和 NONE 可忽略以降低噪音。 下面这段是原文中 default 前一段分支收尾与公开区声明的节选,可对照看类骨架:
class="kw">default:class=class="str">"cmt">//String Request for no currencies class="kw">return constant_prefix+"=&class="macro">#x27;"+constant_suffix; class="kw">break; } } class=class="str">"cmt">//Public declarations accessable via a class&class="macro">#x27;s Object class="kw">public: CNews(class="type">void);class=class="str">"cmt">//Constructor ~CNews(class="type">void);class=class="str">"cmt">//Destructor class="type">void CreateEconomicDatabase();class=class="str">"cmt">//Creates the Calendar database for a specific Broker class="type">class="kw">datetime GetLatestNewsDate();class=class="str">"cmt">//Gets the latest/newest date in the Calendar database class="type">void EconomicDetails(Calendar &NewsTime[],class="type">class="kw">datetime date_from=class="num">0,class="type">class="kw">datetime date_to=class="num">0);class=class="str">"cmt">//Gets values from the MQL5 economic Calendar class="type">void EconomicDetailsMemory(Calendar &NewsTime[],class="type">class="kw">datetime date);class=class="str">"cmt">//Gets values from the MQL5 DB Calendar in Memory class="type">void CreateEconomicDatabaseMemory();class=class="str">"cmt">//Create calendar database in memory class="type">void EconomicNextEvent(class="type">class="kw">datetime date=class="num">0);class=class="str">"cmt">//Will update UpcomingNews structure variable with the next event data class="type">bool UpdateRecords();class=class="str">"cmt">//Checks if the main Calendar database needs an update or not ENUM_CALENDAR_EVENT_IMPACT GetImpact();class=class="str">"cmt">//Will retrieve Upcoming Event Impact data class=class="str">"cmt">//--- Convert Importance class="type">class="kw">string into Calendar Event Importance Enumeration ENUM_CALENDAR_EVENT_IMPORTANCE IMPORTANCE(class="type">class="kw">string Importance) { class=class="str">"cmt">//--- Calendar Importance is High if(Importance==EnumToString(CALENDAR_IMPORTANCE_HIGH)) { class="kw">return CALENDAR_IMPORTANCE_HIGH; } else class=class="str">"cmt">//--- Calendar Importance is Moderate if(Importance==EnumToString(CALENDAR_IMPORTANCE_MODERATE)) { class="kw">return CALENDAR_IMPORTANCE_MODERATE; } else class=class="str">"cmt">//--- Calendar Importance is Low if(Importance==EnumToString(CALENDAR_IMPORTANCE_LOW)) { class="kw">return CALENDAR_IMPORTANCE_LOW; } else class=class="str">"cmt">//--- Calendar Importance is None {
财经日历重要度的枚举转换与配色
在 MT5 的财经日历接口里,自定义枚举 Calendar_Importance 与系统原生 ENUM_CALENDAR_EVENT_IMPORTANCE 是两套值。写 EA 或指标时若直接混用会编译报错,必须做一层映射。 下面这段函数把自定义的重要度转成系统枚举:None 对应 CALENDAR_IMPORTANCE_NONE,Low 对应 LOW,Moderate 对应 MODERATE,High 对应 HIGH,未匹配到的 default 也回落到 NONE,避免脏数据进图表。 转成字符串用 GetImportance(),返回 "HIGH" / "MODERATE" / "LOW" / "NONE" 四种大写标签,方便在 Comment() 或 OBJ_LABEL 里直接输出。 GetImportance_color() 则按重要度取颜色,高重要度事件在贵金属跳空时段可能更值得标红提醒——外汇与贵金属属高杠杆品种,事件行情波动剧烈,仅作参考而非方向暗示。
ENUM_CALENDAR_EVENT_IMPORTANCE IMPORTANCE(Calendar_Importance Importance) { class="kw">switch(Importance) { case Calendar_Importance_None: class="kw">return CALENDAR_IMPORTANCE_NONE; class="kw">break; case Calendar_Importance_Low: class="kw">return CALENDAR_IMPORTANCE_LOW; class="kw">break; case Calendar_Importance_Moderate: class="kw">return CALENDAR_IMPORTANCE_MODERATE; class="kw">break; case Calendar_Importance_High: class="kw">return CALENDAR_IMPORTANCE_HIGH; class="kw">break; class="kw">default: class="kw">return CALENDAR_IMPORTANCE_NONE; class="kw">break; } } class="type">class="kw">string GetImportance(ENUM_CALENDAR_EVENT_IMPORTANCE Importance) { class="kw">switch(Importance) { case CALENDAR_IMPORTANCE_HIGH: class="kw">return "HIGH"; class="kw">break; case CALENDAR_IMPORTANCE_MODERATE: class="kw">return "MODERATE"; class="kw">break; case CALENDAR_IMPORTANCE_LOW: class="kw">return "LOW"; class="kw">break; class="kw">default: class="kw">return "NONE"; class="kw">break; } } class="type">class="kw">color GetImportance_color(ENUM_CALENDAR_EVENT_IMPORTANCE Importance) { class="kw">switch(Importance)
◍ 财经日历的颜色与板块映射逻辑
在 MT5 的财经日历可视化里,事件重要度直接决定标记色:高影响返回红(clrRed),中影响返回橙(clrOrange),低影响则按界面明暗切换蓝或浅蓝。默认无重要度时,亮色模式给黑、暗色模式给小麦色,保证深色背景不掉色。 下面这段 switch 把自定义的 Event_Sector 枚举转成平台原生 ENUM_CALENDAR_EVENT_SECTOR。None 对 NONE,Market 对 MARKET,Gdp 对 GDP,Jobs 对 JOBS,Prices 对 PRICES,Money 对 MONEY,Trade 对 TRADE,Government 对 GOVERNMENT,Business 对 BUSINESS——九个分支一一对应,缺一个都会在日历面板上漏掉板块归类。 实盘接小布盯盘时,若你自写 EA 抓日历,复制这套映射能省掉 80% 的枚举错配 bug。外汇与贵金属受数据跳空影响大,高影响事件前后点差可能瞬间扩到日常 5 倍以上,映射准了才谈得上风控。
{
case CALENDAR_IMPORTANCE_HIGH:class=class="str">"cmt">//High
class="kw">return clrRed;
class="kw">break;
case CALENDAR_IMPORTANCE_MODERATE:class=class="str">"cmt">//Moderate
class="kw">return clrOrange;
class="kw">break;
case CALENDAR_IMPORTANCE_LOW:class=class="str">"cmt">//Low
class="kw">return (isLightMode)?clrBlue:clrLightBlue;
class="kw">break;
class="kw">default:class=class="str">"cmt">//None
class="kw">return (isLightMode)?clrBlack:clrWheat;
class="kw">break;
}
}
class=class="str">"cmt">//--- Convert Event_Sector Enumeration into Calendar Event Sector Enumeration
ENUM_CALENDAR_EVENT_SECTOR SECTOR(Event_Sector Sector)
{
class=class="str">"cmt">//--- class="kw">switch statement for Event_Sector enumeration
class="kw">switch(Sector)
{
case Event_Sector_None:class=class="str">"cmt">//NONE
class="kw">return CALENDAR_SECTOR_NONE;
class="kw">break;
case Event_Sector_Market:class=class="str">"cmt">//MARKET
class="kw">return CALENDAR_SECTOR_MARKET;
class="kw">break;
case Event_Sector_Gdp:class=class="str">"cmt">//GDP
class="kw">return CALENDAR_SECTOR_GDP;
class="kw">break;
case Event_Sector_Jobs:class=class="str">"cmt">//JOBS
class="kw">return CALENDAR_SECTOR_JOBS;
class="kw">break;
case Event_Sector_Prices:class=class="str">"cmt">//PRICES
class="kw">return CALENDAR_SECTOR_PRICES;
class="kw">break;
case Event_Sector_Money:class=class="str">"cmt">//MONEY
class="kw">return CALENDAR_SECTOR_MONEY;
class="kw">break;
case Event_Sector_Trade:class=class="str">"cmt">//TRADE
class="kw">return CALENDAR_SECTOR_TRADE;
class="kw">break;
case Event_Sector_Government:class=class="str">"cmt">//GOVERNMENT
class="kw">return CALENDAR_SECTOR_GOVERNMENT;
class="kw">break;
case Event_Sector_Business:class=class="str">"cmt">//BUSINESS
class="kw">return CALENDAR_SECTOR_BUSINESS;「把自定义枚举映射到财经日历原生枚举」
在 MT5 里做日历事件过滤时,常会先定义一套自己的 Event_Sector、Event_Frequency、Event_Type 枚举,再需要转成平台原生的 ENUM_CALENDAR_EVENT_SECTOR / FREQUENCY / TYPE。上面这段就是两个映射函数的后半段,逻辑全是 switch-case 硬转。 SECTOR() 函数覆盖了 Consumer、Housing、Taxes、Holidays 四个分类,未匹配到的走 default 返回 CALENDAR_SECTOR_NONE;FREQUENCY() 则从 None 到 Year 共 7 档(含 Day/Week/Month/Quarter),同样用 default 兜底为 CALENDAR_FREQUENCY_NONE。 实操上,这种映射最易漏的是新增分类忘了加 case,导致事件被吞进 NONE。开 MT5 把这两段贴进 EA,故意传一个未定义的 Event_Sector 值,看返回是不是 CALENDAR_SECTOR_NONE 就能验证兜底是否生效。外汇与贵金属受宏观数据跳空影响明显,此类映射错误可能让策略漏掉高波动事件,属高风险环节。
class="kw">break; case Event_Sector_Consumer:class=class="str">"cmt">//CONSUMER class="kw">return CALENDAR_SECTOR_CONSUMER; class="kw">break; case Event_Sector_Housing:class=class="str">"cmt">//HOUSING class="kw">return CALENDAR_SECTOR_HOUSING; class="kw">break; case Event_Sector_Taxes:class=class="str">"cmt">//TAXES class="kw">return CALENDAR_SECTOR_TAXES; class="kw">break; case Event_Sector_Holidays:class=class="str">"cmt">//HOLIDAYS class="kw">return CALENDAR_SECTOR_HOLIDAYS; class="kw">break; class="kw">default:class=class="str">"cmt">//Unknown class="kw">return CALENDAR_SECTOR_NONE; class="kw">break; } } class=class="str">"cmt">//--- Convert Event_Frequency Enumeration into Calendar Event Frequency Enumeration ENUM_CALENDAR_EVENT_FREQUENCY FREQUENCY(Event_Frequency Frequency) { class=class="str">"cmt">//--- class="kw">switch statement for Event_Frequency enumeration class="kw">switch(Frequency) { case Event_Frequency_None:class=class="str">"cmt">//NONE class="kw">return CALENDAR_FREQUENCY_NONE; class="kw">break; case Event_Frequency_Day:class=class="str">"cmt">//DAY class="kw">return CALENDAR_FREQUENCY_DAY; class="kw">break; case Event_Frequency_Week:class=class="str">"cmt">//WEEK class="kw">return CALENDAR_FREQUENCY_WEEK; class="kw">break; case Event_Frequency_Month:class=class="str">"cmt">//MONTH class="kw">return CALENDAR_FREQUENCY_MONTH; class="kw">break; case Event_Frequency_Quarter:class=class="str">"cmt">//QUARTER class="kw">return CALENDAR_FREQUENCY_QUARTER; class="kw">break; case Event_Frequency_Year:class=class="str">"cmt">//YEAR class="kw">return CALENDAR_FREQUENCY_YEAR; class="kw">break; class="kw">default:class=class="str">"cmt">//Unknown class="kw">return CALENDAR_FREQUENCY_NONE; class="kw">break; } } class=class="str">"cmt">//--- Convert Event_Type Enumeration into Calendar Event Type Enumeration ENUM_CALENDAR_EVENT_TYPE TYPE(Event_Type Type) {
正文
<span class="comment">//--- switch statement for Event_Type enumeration</span> <span class="keyword">switch</span>(Type) { <span class="keyword">case</span> Event_Type_Event:<span class="comment">//EVENT</span> <span class="keyword">return</span> <span class="macro">CALENDAR_TYPE_EVENT</span>; <span class="keyword">break</span>; <span class="keyword">case</span> Event_Type_Indicator:<span class="comment">//INDICATOR</span> <span class="keyword">return</span> <span class="macro">CALENDAR_TYPE_INDICATOR</span>; <span class="keyword">break</span>; <span class="keyword">case</span> Event_Type_Holiday:<span class="comment">//HOLIDAY</span> <span class="keyword">return</span> <span class="macro">CALENDAR_TYPE_HOLIDAY</span>; <span class="keyword">break</span>;
◍ 用 SQL 视图给财经日历做减法
MT5 内置的财经日历表字段冗长,直接查 MQL5Calendar 会带出一长串 CALENDAR_TYPE_、CALENDAR_SECTOR_ 前缀,读起来费劲。写一条 CREATE VIEW 把前缀 REPLACE 掉,再按国家升序、重要性权重、板块降序排,肉眼筛选效率高得多。 下面这段初始化把视图塞进 CalendarContents[5],视图名定为 Event Info,类型是 view。sql 字符串里 GROUP BY Name 是为了同一事件只留一行,ORDER BY 里的 CASE 把 HIGH 映射为 1、MODERATE 为 2、LOW 为 3,非这三类归 4,等于给重要性做了隐形排序键。
class=class="str">"cmt">//--- initializing properties for the EventInfo view CalendarContents[class="num">5].Content = EventInfo_View; CalendarContents[class="num">5].name = "Event Info"; CalendarContents[class="num">5].sql = "CREATE VIEW IF NOT EXISTS &class="macro">#x27;Event Info " "AS SELECT EVENTID as &class="macro">#x27;ID&class="macro">#x27;,COUNTRY as &class="macro">#x27;Country&class="macro">#x27;,EVENTNAME as &class="macro">#x27;Name&class="macro">#x27;," "REPLACE(EVENTTYPE,&class="macro">#x27;CALENDAR_TYPE_&class="macro">#x27;,&class="macro">#x27;&class="macro">#x27;) as &class="macro">#x27;Type&class="macro">#x27;,REPLACE(EVENTSECTOR,&class="macro">#x27;CALENDAR_SECTOR_&class="macro">#x27;,&class="macro">#x27;&class="macro">#x27;) as &class="macro">#x27;Sector&class="macro">#x27;," "REPLACE(EVENTIMPORTANCE,&class="macro">#x27;CALENDAR_IMPORTANCE_&class="macro">#x27;,&class="macro">#x27;&class="macro">#x27;) as &class="macro">#x27;Importance&class="macro">#x27;,EVENTCURRENCY as &class="macro">#x27;Currency&class="macro">#x27; " "FROM MQL5Calendar GROUP BY \"Name\" ORDER BY \"Country\" Asc," "CASE \"Importance\" WHEN &class="macro">#x27;HIGH&class="macro">#x27; THEN class="num">1 WHEN &class="macro">#x27;MODERATE&class="macro">#x27; THEN class="num">2 WHEN &class="macro">#x27;LOW&class="macro">#x27; THEN class="num">3 ELSE class="num">4 END,\"Sector\" Desc;"; CalendarContents[class="num">5].tbl_name = "Event Info"; CalendarContents[class="num">5].type = "view";
class=class="str">"cmt">//--- initializing properties for the EventInfo view CalendarContents[class="num">5].Content = EventInfo_View; CalendarContents[class="num">5].name = "Event Info"; CalendarContents[class="num">5].sql = "CREATE VIEW IF NOT EXISTS &class="macro">#x27;Event Info " "AS SELECT EVENTID as &class="macro">#x27;ID&class="macro">#x27;,COUNTRY as &class="macro">#x27;Country&class="macro">#x27;,EVENTNAME as &class="macro">#x27;Name&class="macro">#x27;," "REPLACE(EVENTTYPE,&class="macro">#x27;CALENDAR_TYPE_&class="macro">#x27;,&class="macro">#x27;&class="macro">#x27;) as &class="macro">#x27;Type&class="macro">#x27;,REPLACE(EVENTSECTOR,&class="macro">#x27;CALENDAR_SECTOR_&class="macro">#x27;,&class="macro">#x27;&class="macro">#x27;) as &class="macro">#x27;Sector&class="macro">#x27;," "REPLACE(EVENTIMPORTANCE,&class="macro">#x27;CALENDAR_IMPORTANCE_&class="macro">#x27;,&class="macro">#x27;&class="macro">#x27;) as &class="macro">#x27;Importance&class="macro">#x27;,EVENTCURRENCY as &class="macro">#x27;Currency&class="macro">#x27; " "FROM MQL5Calendar GROUP BY \"Name\" ORDER BY \"Country\" Asc," "CASE \"Importance\" WHEN &class="macro">#x27;HIGH&class="macro">#x27; THEN class="num">1 WHEN &class="macro">#x27;MODERATE&class="macro">#x27; THEN class="num">2 WHEN &class="macro">#x27;LOW&class="macro">#x27; THEN class="num">3 ELSE class="num">4 END,\"Sector\" Desc;"; CalendarContents[class="num">5].tbl_name = "Event Info"; CalendarContents[class="num">5].type = "view";
「澳储与巴西央行的事件字段排布」
经济日历的原始数据并非杂乱堆砌,而是按国家代码与事件 ID 顺序线性排列。上面这段截取里,澳大利亚部分从 ID 36030014 的 trimmed mean CPI 开始,连续 6 行覆盖物价类指标与三位助理行长讲话,影响等级统一标为 MODERATE,对应货币 AUD。 中间用「62 lines later...」标记跳过了 62 行其他地区的记录,直接落到巴西区块。巴西从 76020002 的 BCB 利率决议起头,该行影响等级是 HIGH,其余如通胀报告、焦点市场报告、Copom 纪要等多为 MODERATE,货币统一为 BRL。 这种排布说明:抓取日历时若按 ID 数值遍历,澳大利亚与巴西之间天然存在断层,写过滤器时要以国家字段做一级分组,别指望连续行号能贯穿全表。外汇与贵金属受此类央行事件冲击波动可能放大,属高风险博弈,实盘前应在 MT5 历史日历里核对具体发布时间。
加元事件流与币种视图的落地写法
日历数据里巴西服务业月率排在编码 76010014,往后数 98 行才出现加拿大央行行长 Macklem 讲话(124040017),中间夹着大量低权重条目。加元相关的高影响项集中且密集:从 124040003 的 Poloz 讲话、124040006 的利率决议,到 124010011 就业变动、124010021 GDP 月率,一连串 HIGH 级事件都挂在 CAD 下,外汇和贵金属交易者碰上这类重叠窗口要警惕双向扫单的高风险。 下面这段 MQL5 初始化代码直接建了一个 Currencies 视图,把日历表里出现过的币种和对应事件代码去重拉出来。跑完 SELECT 能看到实际落库的币种:NZD/NZ、EUR/EU、JPY/JP、CAD/CA、AUD/AU、CNY/CN、EUR/IT、SGD/SG、EUR/DE——注意 EUR 被意大利和德国的不同事件代码拆成了 IT 与 DE,做币种过滤时不能只按前缀硬匹配。 打开 MT5 把这段视图建好,你就能用一条 SQL 看清自己订阅的日历到底覆盖了哪些经济体,而不是被默认全量推送带着走。
class=class="str">"cmt">//--- initializing properties for the Currencies view CalendarContents[class="num">6].Content = Currencies_View; CalendarContents[class="num">6].name = "Currencies"; CalendarContents[class="num">6].sql = "CREATE VIEW IF NOT EXISTS Currencies AS " "SELECT Distinct EventCurrency as &class="macro">#x27;Currency&class="macro">#x27;,EventCode as &class="macro">#x27;Code&class="macro">#x27; FROM &class="macro">#x27;MQL5Calendar&class="macro">#x27;;" CalendarContents[class="num">6].tbl_name = "Currencies"; CalendarContents[class="num">6].type = "view";
◍ 正文
EUR FR BRL BR MXN MX ZAR ZA HKD HK INR IN NOK NO USD US GBP GB CHF CH KRW KR EUR ES SEK SE ALL WW <span class="comment">//-- initializing properties for the MQL5Calendar table for DB in System Memory</span> DBMemory.Content = MQL5Calendar_Table; DBMemory.name = <span class="string">"MQL5Calendar"</span>; DBMemory.sql = <span class="string">"CREATE TABLE IF NOT EXISTS MQL5Calendar(EVENTID INT NOT NULL,COUNTRY TEXT NOT NULL,"</span> <span class="string">"EVENTNAME TEXT NOT NULL,EVENTTYPE TEXT NOT NULL,EVENTIMPORTANCE TEXT NOT NULL,"</span> <span class="string">"EVENTCURRENCY TEXT NOT NULL,EVENTCODE TEXT NOT NULL,EVENTSECTOR TEXT NOT NULL,"</span> <span class="string">"EVENTFORECAST TEXT NOT NULL,EVENTPREVALUE TEXT NOT NULL,E
「内存数据库建表与财经数据灌入」
在 MT5 里用 DatabaseOpen 配合 DATABASE_OPEN_MEMORY 标志,可以把经济日历直接建在内存里,避免每次读盘。句柄若返回 INVALID_HANDLE,说明内存库没开起来,这时打错误码并 return,后续插入逻辑全部跳过。 建表前先用 DatabaseExecute 跑一句 Drop table IF EXISTS,把同名旧表清掉,否则重复执行会撞结构。紧接着用 DBSql 模板建表,失败同样 Print 错误码退出。 表存在后,从持久化库 GetCalendar 拉全量数据,再 for 循环逐条拼 insert 语句灌进内存表。DB_Data.Size() 决定循环次数,数据量大的时候这一段的耗时倾向随条数线性增长,实盘前最好先打印 Size 估一下。 别把内存当永久存储 内存库随 EA 退出即释放,重启后要重新 CreateEconomicDatabaseMemory 灌数据;想跨周期留数据得落盘到文件库。
class="type">void CNews::CreateEconomicDatabaseMemory() { class=class="str">"cmt">//--- Open/create the database in memory DBMemoryConnection=DatabaseOpen(NEWS_DATABASE_MEMORY,DATABASE_OPEN_MEMORY); if(DBMemoryConnection==INVALID_HANDLE)class=class="str">"cmt">//Checks if the database failed to open/create { Print("DB: ",NEWS_DATABASE_MEMORY, " open failed with code ", GetLastError()); class="kw">return;class=class="str">"cmt">//will terminate execution of the rest of the code below } class=class="str">"cmt">//--- Drop the table if it already exists DatabaseExecute(DBMemoryConnection,StringFormat("Drop table IF EXISTS %s",DBMemory.name)); class=class="str">"cmt">//--- Attempt to create the table if(!DatabaseExecute(DBMemoryConnection,DBMemory.sql)) { Print("DB: create the Calendar table failed with code ", GetLastError()); class="kw">return; } class=class="str">"cmt">//--- Check if the table exists if(DatabaseTableExists(DBMemoryConnection,DBMemory.tbl_name)) { class=class="str">"cmt">//--- Get all news data and time from the database in storage GetCalendar(DB_Data); class=class="str">"cmt">//--- Insert all the news data and times into the table for(class="type">uint i=class="num">0;i<DB_Data.Size();i++) { class="type">class="kw">string request_text=StringFormat(DBMemory.insert,DB_Data[i].EventId,DB_Data[i].Country, DB_Data[i].EventName,DB_Data[i].EventType,DB_Data[i].EventImportance, DB_Data[i].EventCurrency,DB_Data[i].EventCode,DB_Data[i].EventSector, DB_Data[i].EventForecast,DB_Data[i].EventPreval,DB_Data[i].EventImpact,
内存日历的检索与落库细节
把新闻数据写进内存库之后,先调用 ArrayRemove 清空 DB_Data 数组,避免下一次循环把旧记录叠进去。这一行若漏掉,回测时可能出现同一事件被重复统计、事件频率字段翻倍的现象。 MySchedule 的赋值用了三元判断:若处在策略测试器环境且用户选了自动 DST,就走 GetAutoDST() 拿时区表,否则回落到 DST_NONE。外汇与贵金属对夏令时切换极敏感,时区错配会让新闻触发时间偏移 1 小时,实盘风险偏高。 EconomicDetailsMemory 用一段嵌套 SQL 从内存库按日期捞新闻。核心是用 RANK() 窗口函数按 Prevalue、Forecast 是否缺值以及重要性高低排优先级,再 where Ranking<2 只取每个时间点的最优先事件,Group by CTime 去重。 下面这段是 SQL 拼装开头,MySchedule 枚举会替换进 %s 占位符,DBMemory.name 指定内存表名,TimeToString(date) 把查询日传进去。开 MT5 把这段贴进 EA 的调试输出,能直接看到某天实际命中了几条高层级事件。
class="type">class="kw">string request_text=StringFormat("WITH MySubQuery AS(SELECT EventId as &class="macro">#x27;Id&class="macro">#x27;,Country,EventName as &class="macro">#x27;Name&class="macro">#x27;,EventType as &class="macro">#x27;Type&class="macro">#x27;" ",EventImportance as &class="macro">#x27;Importance&class="macro">#x27;,%s as &class="macro">#x27;CTime&class="macro">#x27;,EventCurrency as &class="macro">#x27;Currency&class="macro">#x27;,EventCode as &class="macro">#x27;Code&class="macro">#x27;," "EventSector as &class="macro">#x27;Sector&class="macro">#x27;,EventForecast as &class="macro">#x27;Forecast&class="macro">#x27;,EventPrevalue as &class="macro">#x27;Prevalue&class="macro">#x27;,EventImpact as&class="macro">#x27;Impact&class="macro">#x27;," "EventFrequency as &class="macro">#x27;Freq&class="macro">#x27;,RANK() OVER(PARTITION BY %s Order BY CASE EventPrevalue WHEN &class="macro">#x27;None&class="macro">#x27; " "THEN class="num">2 ELSE class="num">1 END,CASE EventForecast WHEN &class="macro">#x27;None&class="macro">#x27; THEN class="num">2 ELSE class="num">1 END,CASE EventImportance WHEN " "&class="macro">#x27;CALENDAR_IMPORTANCE_HIGH&class="macro">#x27; THEN class="num">1 WHEN &class="macro">#x27;CALENDAR_IMPORTANCE_MODERATE&class="macro">#x27; THEN class="num">2 WHEN &class="macro">#x27;CALENDAR_IMPORTANCE_LOW&class="macro">#x27;" " THEN class="num">3 ELSE class="num">4 END) Ranking FROM %s) SELECT Id,Country,Name,Type,Importance,CTime,Currency,Code,Sector," "Forecast,Prevalue,Impact,Freq FROM MySubQuery where Date(Replace(CTime,&class="macro">#x27;.&class="macro">#x27;,&class="macro">#x27;-&class="macro">#x27;))=Date(Replace(&class="macro">#x27;%s&class="macro">#x27;,&class="macro">#x27;.&class="macro">#x27;,&class="macro">#x27;-&class="macro">#x27;)) and " "Ranking<class="num">2 Group by CTime;",EnumToString(MySchedule),EnumToString(MySchedule),DBMemory.name, TimeToString(date));
◍ 把财经日历塞进内存数据库再读出来
MT5 里用 DatabasePrepare 向内存连接提交 SQL 请求,拿到的是请求句柄而非结果集。句柄若等于 INVALID_HANDLE,说明 SQL 编译或连接出问题,这时候把 GetLastError 和原始 request_text 都打印出来,能直接定位是语法错还是表名错。
class="type">int request=DatabasePrepare(DBMemoryConnection,request_text);class=class="str">"cmt">//Creates a handle of a request, which can then be executed class="kw">using DatabaseRead() if(request==INVALID_HANDLE)class=class="str">"cmt">//Checks if the request failed to be completed { Print("DB: ",NEWS_DATABASE_MEMORY, " request failed with code ", GetLastError()); PrintFormat(request_text); } class=class="str">"cmt">//--- Calendar structure variable Calendar ReadDB_Data; class=class="str">"cmt">//--- Remove any data in the array ArrayRemove(NewsTime,class="num">0,WHOLE_ARRAY); for(class="type">int i=class="num">0; DatabaseReadBind(request,ReadDB_Data); i++)class=class="str">"cmt">//Will read all the results from the sql query/request { class=class="str">"cmt">//--- Resize array NewsTime ArrayResize(NewsTime,i+class="num">1,i+class="num">2); class=class="str">"cmt">//--- Assign calendar structure values into NewsTime array index NewsTime[i] = ReadDB_Data; } class=class="str">"cmt">//--- Removes a request created in DatabasePrepare() DatabaseFinalize(request);
class="type">int request=DatabasePrepare(DBMemoryConnection,request_text);class=class="str">"cmt">//Creates a handle of a request, which can then be executed class="kw">using DatabaseRead() if(request==INVALID_HANDLE)class=class="str">"cmt">//Checks if the request failed to be completed { Print("DB: ",NEWS_DATABASE_MEMORY, " request failed with code ", GetLastError()); PrintFormat(request_text); } class=class="str">"cmt">//--- Calendar structure variable Calendar ReadDB_Data; class=class="str">"cmt">//--- Remove any data in the array ArrayRemove(NewsTime,class="num">0,WHOLE_ARRAY); for(class="type">int i=class="num">0; DatabaseReadBind(request,ReadDB_Data); i++)class=class="str">"cmt">//Will read all the results from the sql query/request { class=class="str">"cmt">//--- Resize array NewsTime ArrayResize(NewsTime,i+class="num">1,i+class="num">2); class=class="str">"cmt">//--- Assign calendar structure values into NewsTime array index NewsTime[i] = ReadDB_Data; } class=class="str">"cmt">//--- Removes a request created in DatabasePrepare() DatabaseFinalize(request);
「日历数据里的字段暗码」
上面这段是某日经济日历的原始条目流,覆盖了澳、法、瑞、西四个经济体,时间锚在 2024.07.30。每条记录用固定位宽拆分:事件 ID、国家、指标名、类型宏、重要度宏、发布时间、货币、板块宏、前值与预期值、影响标记、频率宏、末位序号。 看法国那几行:GDP q/q 标的是 HIGH 重要度,前值 200000、预期同数;GDP y/y 是 MODERATE,前值 1000000、预期 1100000;Consumer Spending m/m 也是 MODERATE,前值 800000、预期 1500000。这种前值预期差,是事件驱动挂单的初级过滤器。 西班牙的 CPI/HICP 同时间 09:00 放出,y/y 版本重要度压到 LOW,但末位序号是 5,比 m/m 的 1 高出一截——说明年度口径在渲染层可能被赋予更高权重。外汇与贵金属受此类数据跳空影响明显,属高风险事件,实操前先在 MT5 日历插件核对时区。 瑞士 KOF 经济晴雨表前值 101500000、预期 102700000,字段里没有单独的影响值占位,CALENDAR_IMPACT_NA 直接顶上。这种结构告诉你:不是每个指标都带冲击评级,低重要度条目常常留空。
低权重日历条目的字段结构拆解
上面这组 2024.07.30 的日历记录里,西班牙 GDP y/y 与瑞典五项信心类指标都被标了 CALENDAR_IMPORTANCE_LOW,发布时间集中在 09:00,频率分别为季度和月度。 以瑞典 Consumer Confidence 为例,前值 95300000、预测 93300000,CALENDAR_IMPACT_NA 说明系统未判定对 SEK 有即时冲击倾向;Business Confidence 前值 95500000、预测 97300000,差值方向偏正但权重等级仍为低。 德国 10:00 的 GDP q/q 是 CALENDAR_IMPORTANCE_HIGH(等级 1),前值 100000、预测 200000;同期的意大利 GDP q/q 为 MODERATE(等级 2)。这种同币种、不同时段的重要性梯度,是做 EUR 相关品种时过滤噪音的第一道筛子。 在 MT5 里打开财经日历接口对照这些宏,能看到 CALENDAR_FREQUENCY_QUARTER 与 MONTH 直接决定数据刷新节奏,低重要度条目默认不弹窗,但可手动写脚本拉全量做统计。外汇与贵金属受此类数据扰动的概率随重要性等级上升而增加,实盘需自担高风险。
◍ 欧元区数据密集发布时的字段排布
上面这一串是某日欧洲时段经济日历的原始记录片段,时间集中在 2024.07.30 的 10:00 到 11:30,全部挂 EUR 计价标的,属于典型的基本面事件窗口。 每条记录从左到右依次是:内部编号、经济体、指标名、类型宏、重要度宏、发布时间、货币、地区码、板块宏、前值、预期、影响宏、频率宏、排序序号。比如意大利 GDP y/y 编号为 380010021,重要度标为 LOW,前值 400000、预期 700000;欧盟 GDP q/q 编号 999030016,重要度 HIGH,前值 200000、预期 300000。 注意同一时间点(11:00)欧盟一口气出了 8 条:GDP 同比环比、工业/服务业/经济景气、消费者信心与价格预期等,板块覆盖 GDP、BUSINESS、CONSUMER。这种堆叠在 MT5 日历对象里往往共用同一时间戳,脚本读取时若不分板块过滤,容易把低重要度的信心指数和高重要度的 GDP 混在同一队列。 实操上,打开 MT5 终端的财经日历,把 2024.07.30 这一天调出来,对照上面编号和前值/预期数字逐条核对;若你自己在写 EA 抓日历,建议按 CALENDAR_SECTOR 宏先分桶,再按 CALENDAR_IMPORTANCE 做权重,避免被 LOW 级噪声干扰。外汇与贵金属受此类数据跳空影响明显,属于高风险品种,任何方向判断都只是概率倾向。
「经济日历条目的字段结构拆解」
MT5 内置经济日历每条记录由固定字段组成,从原始数据看,一行里依次排着事件 ID、国家、事件名、类型宏、重要度宏、时间、货币、国家码、板块宏、前值、预测、影响宏、频率宏和某种计数。 以 2024.07.30 14:00 德国 CPI m/m 为例:ID 为 276010020,重要度 CALENDAR_IMPORTANCE_MODERATE,前值 0、预测 100000,频率 CALENDAR_FREQUENCY_MONTH,行尾计数为 2。同日墨西哥 GDP q/q 重要度标为 CALENDAR_IMPORTANCE_HIGH,是那批低重要度拍卖数据里唯一的高影响事件。 灰色背景行(意大利 5 年期 BTP 拍卖、英国 10 年期 Gilt 拍卖)和白色背景行只是原文排版标记,在终端读取时不影响字段解析;写 EA 时直接用 CalendarValueHistory 或 CalendarEventByCountry 取对应宏即可。 开 MT5 按 F6 调出日历样例,对照上面字段把前值和预测打印到日志,能确认你本地宏定义和服务器推送是否一致。
经济日历里低重要度条目的真实分量
上面这组 2024.07.30 的日历快照里,德国 HICP y/y 与巴西 PPI y/y 都被标成 CALENDAR_IMPORTANCE_LOW,发布时间同为 14:00,但前者影响 EUR、后者影响 BRL,且巴西 PPI 前值 170000、预期 2200000,量级差了十倍出头。 美国时段 15:00 集中冒出 7 条住房类数据,其中 S&P/CS HPI Composite-20 y/y 是 MODERATE,其余全是 LOW;HPI 绝对值 427700000 对比前值 424300000,微增约 0.8%,这类低重要度条目在 MT5 日历里默认灰显,容易被一眼跳过。 16:00 的 JOLTS 职位空缺(7979000 vs 8140000)和 CB 消费者信心(108000000 vs 100400000)才是 HIGH,美元波动大概率由它们驱动。做欧元或贵金属时,别把前面的 LOW 条目当噪声全删——低风险事件也可能在 HIGH 数据前制造流动性真空。
◍ 低影响时段里怎么挑值得盯的日历条目
上面这组 2024.07.30 的财经日历原始记录,混了美、墨、日、澳、欧等多国事件,重要性从 LOW 到 HIGH 都有,但 Impact 字段清一色标着 CALENDAR_IMPACT_NA,说明按系统判定这些发布对盘面没有自动冲击标记。 具体看,德国 GDP q/q 预估 100000、前值 200000,法国 GDP q/q 预估与前值都是 200000,欧盟 GDP q/q 预估 200000、前值 300000——三个 HIGH 级 GDP 同在当天 07:30 到 11:00 之间出炉,可系统仍判为 NA 影响,这种「高重要度却无冲击标记」的矛盾,往往是流动性真空或非美盘交叉段的典型特征。 做价格行为的人,碰到这种日历别直接跳过。把 USD、EUR、JPY 相关条目的 CTime 拉出来排一遍:日本失业率 01:30、澳大利亚建筑许可 03:30、法国 GDP 07:30、德国 GDP 10:00、欧盟 GDP 11:00,中间美元区 Dallas Fed 两个 LOW 条目在 16:30 才来。亚欧时段那串 HIGH 如果实际走出了吞没或假突破,大概率不是日历算法预测的「无影响」,而是市场在消化预期差。 外汇和贵金属这类高杠杆品种,在这种 NA 标记日波动可能突然放大,开 MT5 把 2024.07.30 那天 EURUSD、XAUUSD 的 M15 调出来,对照上面时间轴看影线分布,比信日历的 Impact 字段更实在。
「抓下一个财经事件的结构体写法」
上面那串原始记录是某日财经日历的裸数据:2024.07.30 当天从西班牙商业信心(低重要度、前值 -4100000)到墨西哥财政余额(低重要度、前值 -900000、预期 -174071000),横跨 EUR、BRL、MXN、USD 多个币种,其中美国 JOLTS 职位空缺与墨西哥 GDP 季率被标为高重要度,发布时间分散在 12:00 至 22:30 服务器时间。 这类数据在 MT5 里通常不会裸读,而是先塞进一个 Calendar 结构体,再由函数挑出“下一个”事件。下面这段就是取即将到来事件的核心逻辑,跑通后你能直接挂到 EA 的 OnTick 前面做前置过滤。 别把结构体当万能缓存 UpcomingNews 在调用前必须清空,否则上一次残留的 EventId 可能让你的新闻屏蔽逻辑误判。代码里用 Empty 赋值而非手动逐项归零,是最省事的防脏读办法。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|Will update UpcomingNews structure variable with the next event | class=class="str">"cmt">//|data | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CNews::EconomicNextEvent(class="type">class="kw">datetime date=class="num">0) { class=class="str">"cmt">//--- Declare unassigned Calendar structure variable Empty Calendar Empty; class=class="str">"cmt">//--- assign empty values to Calendar structure variable UpcomingNews UpcomingNews = Empty; class=class="str">"cmt">//--- If date variable is zero then assign current date date = (date==class="num">0)?TimeTradeServer():date; class=class="str">"cmt">//--- Query to retrieve next upcoming event. class="type">class="kw">string request_text=StringFormat("WITH MySubQuery AS(SELECT EventId as &class="macro">#x27;Id&class="macro">#x27;,Country,EventName as &class="macro">#x27;Name&class="macro">#x27;,"
用 SQL 窗口函数筛最近一条高权重财历
这段逻辑把 MT5 内存财历库当成一个轻量 SQL 引擎来用:先用 CTE(MySubQuery)把事件字段改名映射,再用 RANK() OVER (PARTITION BY 时间字段 ORDER BY ...) 给同一时刻的多条事件排优先级。排序键先把无前值、无预测的事件压到后面(CASE 返回 2,否则 1),再按高/中/低重要性映射成 1/2/3,其余归 4。 外层只取 Ranking<2 且时间大于等于入参日期的那一条(LIMIT 1),等于每次只抓「离当前最近、且数据最完整」的那条宏观事件。外汇与贵金属受此类事件跳空影响明显,属于高风险场景,信号仅作概率参考。 代码里 DatabasePrepare 拿到请求句柄后必须判 INVALID_HANDLE,失败就打印错误码和整段 SQL 方便排查;DatabaseReadBind 把结果塞进 UpcomingNews 结构,最后 DatabaseFinalize 释放句柄,漏掉这一步会吃内存。 直接把下面这段 SQL 拼进你的 EA 调试面板,改掉时间字段和表名就能在 MT5 里跑通验证。
WITH MySubQuery AS(SELECT EventId as &class="macro">#x27;Id&class="macro">#x27;,Country,EventName as &class="macro">#x27;Name&class="macro">#x27;,EventType as &class="macro">#x27;Type&class="macro">#x27;,EventImportance as &class="macro">#x27;Importance&class="macro">#x27;, DST_NONE as &class="macro">#x27;CTime&class="macro">#x27;,EventCurrency as &class="macro">#x27;Currency&class="macro">#x27;,EventCode as &class="macro">#x27;Code&class="macro">#x27;,EventSector as &class="macro">#x27;Sector&class="macro">#x27;,EventForecast as &class="macro">#x27;Forecast&class="macro">#x27;, EventPrevalue as &class="macro">#x27;Prevalue&class="macro">#x27;,EventImpact as &class="macro">#x27;Impact&class="macro">#x27;,EventFrequency as &class="macro">#x27;Freq&class="macro">#x27;,RANK() OVER(PARTITION BY DST_NONE Order BY CASE EventPrevalue WHEN &class="macro">#x27;None&class="macro">#x27; THEN class="num">2 ELSE class="num">1 END,CASE EventForecast WHEN &class="macro">#x27;None&class="macro">#x27; THEN class="num">2 ELSE class="num">1 END, CASE EventImportance WHEN &class="macro">#x27;CALENDAR_IMPORTANCE_HIGH&class="macro">#x27; THEN class="num">1 WHEN &class="macro">#x27;CALENDAR_IMPORTANCE_MODERATE&class="macro">#x27; THEN class="num">2 WHEN &class="macro">#x27;CALENDAR_IMPORTANCE_LOW&class="macro">#x27; THEN class="num">3 ELSE class="num">4 END) Ranking FROM MQL5Calendar) SELECT Id,Country,Name,Type, Importance,CTime,Currency,Code,Sector,Forecast,Prevalue,Impact,Freq FROM MySubQuery where
◍ 用历史情景反推财经事件冲击等级
想让 MT5 日历库告诉你「这次数据公布后市场可能怎么动」,不能只盯当前值,得把同一事件 ID 的历史影响拉出来比对。下面这段逻辑就是按 EventPrevalue 与 EventForecast 的大小关系,去筛出最近一条同情景的旧影响记录。
例如 2024.07.30 01:30 的日本失业率(JPY,JP 就业板块,前值 2600000、预测 2500000,Ranking=1),若前值大于预测,代码会限定查 EventPrevalue>EventForecast AND EventImpact<>'CALENDAR_IMPACT_NA' 的历史样本,倾向认为实际公布若延续该情景,冲击分类可能非空。
外汇与贵金属受此类数据跳变影响明显,杠杆品种高风险,历史情景只是概率参考而非方向保证。
核心函数 GetImpact() 先把筛选 SQL 拼出来,再 DatabasePrepare 拿句柄;若返回 INVALID_HANDLE 就打印错误码和原句,方便你直接在 MT5 专家日志里抓 SQL 毛病。
ENUM_CALENDAR_EVENT_IMPACT CNews::GetImpact() { class=class="str">"cmt">//--- Declaration of class="type">class="kw">string variable class="type">class="kw">string impact=NULL; class=class="str">"cmt">//--- Query to get impact data from previous event with the same event id and matching EventPrevalue and EventForecast scenarios. class="type">class="kw">string request_text=StringFormat("SELECT EventImpact FROM %s where Replace(%s,&class="macro">#x27;.&class="macro">#x27;,&class="macro">#x27;-&class="macro">#x27;)<Replace(&class="macro">#x27;%s&class="macro">#x27;,&class="macro">#x27;.&class="macro">#x27;,&class="macro">#x27;-&class="macro">#x27;) AND EventId=%d" " %s ORDER BY %s DESC LIMIT class="num">1;",DBMemory.name,EnumToString(MySchedule),UpcomingNews.EventDate, UpcomingNews.EventId,((UpcomingNews.EventPreval=="None"||UpcomingNews.EventForecast=="None")? "AND EventImpact=&class="macro">#x27;CALENDAR_IMPACT_NA&class="macro">#x27;":(class="type">int(UpcomingNews.EventPreval)<class="type">int(UpcomingNews.EventForecast))? "AND EventPrevalue<EventForecast AND EventImpact<>&class="macro">#x27;CALENDAR_IMPACT_NA&class="macro">#x27;": (class="type">int(UpcomingNews.EventPreval)>class="type">int(UpcomingNews.EventForecast))? "AND EventPrevalue>EventForecast AND EventImpact<>&class="macro">#x27;CALENDAR_IMPACT_NA&class="macro">#x27;": "AND EventPrevalue=EventForecast AND EventImpact<>&class="macro">#x27;CALENDAR_IMPACT_NA&class="macro">#x27;"), EnumToString(MySchedule)); class="type">int request=DatabasePrepare(DBMemoryConnection,request_text);class=class="str">"cmt">//Creates a handle of a request, which can then be executed class="kw">using DatabaseRead() if(request==INVALID_HANDLE)class=class="str">"cmt">//Checks if the request failed to be completed { Print("DB: ",NEWS_DATABASE_MEMORY, " request failed with code ", GetLastError()); PrintFormat(request_text); }
「用历史事件反推财经日历冲击方向」
在 MT5 的 Database 体系里,单条记录读取后必须立刻释放请求句柄,否则内存表会持续占用。下面这段逻辑先尝试读取 'Record' 表里的唯一一行,把第一列文本塞进 impact 变量,随后调用 DatabaseFinalize 清掉 request,避免句柄泄漏。 读取到的 impact 字符串直接映射到 ENUM_CALENDAR_EVENT_IMPACT 枚举:命中 "CALENDAR_IMPACT_POSITIVE" 返回正向冲击,命中 "CALENDAR_IMPACT_NEGATIVE" 返回负向,其余一律归为 CALENDAR_IMPACT_NA。这样上层调度只需判断枚举,不必再处理字符串。 核心查询找的是「同一 EventId 下、日期早于当前事件、且前值/预期关系与本次一致」的最近一条历史冲击。例如实跑出的 SQL 为:SELECT EventImpact FROM MQL5Calendar where Replace(DST_NONE,'.','-')<Replace('2024.08.01 16:30','.','-') AND EventId=124500001 AND EventPrevalue<EventForecast AND EventImpact<>'CALENDAR_IMPACT_NA' ORDER BY DST_NONE DESC LIMIT 1; 只取 LIMIT 1,即紧邻的上一次同场景释放。 外汇与贵金属受此类数据冲击影响剧烈且跳空频繁,历史同场景冲击仅作概率参考,实际入场前应在 MT5 策略测试器或可视化回放中核对 EventId 与日期边界。
if(DatabaseRead(request))class=class="str">"cmt">//Will read the one record in the &class="macro">#x27;Record&class="macro">#x27; table { class=class="str">"cmt">//--- assign first column result into impact variable DatabaseColumnText(request,class="num">0,impact); } class=class="str">"cmt">//--- Removes a request created in DatabasePrepare() DatabaseFinalize(request); class=class="str">"cmt">//--- Return equivalent Event impact in the enumeration ENUM_CALENDAR_EVENT_IMPACT if(impact=="CALENDAR_IMPACT_POSITIVE") { class="kw">return CALENDAR_IMPACT_POSITIVE; } else if(impact=="CALENDAR_IMPACT_NEGATIVE") { class="kw">return CALENDAR_IMPACT_NEGATIVE; } else { class="kw">return CALENDAR_IMPACT_NA; } } class=class="str">"cmt">//--- Query to get impact data from previous event with the same event id and matching EventPrevalue and EventForecast scenarios. class="type">class="kw">string request_text=StringFormat("SELECT EventImpact FROM %s where Replace(%s,&class="macro">#x27;.&class="macro">#x27;,&class="macro">#x27;-&class="macro">#x27;)<Replace(&class="macro">#x27;%s&class="macro">#x27;,&class="macro">#x27;.&class="macro">#x27;,&class="macro">#x27;-&class="macro">#x27;) AND EventId=%d" " %s ORDER BY %s DESC LIMIT class="num">1;",DBMemory.name,EnumToString(MySchedule),UpcomingNews.EventDate, UpcomingNews.EventId,((UpcomingNews.EventPreval=="None"||UpcomingNews.EventForecast=="None")? "AND EventImpact=&class="macro">#x27;CALENDAR_IMPACT_NA&class="macro">#x27;":(class="type">int(UpcomingNews.EventPreval)<class="type">int(UpcomingNews.EventForecast))? "AND EventPrevalue<EventForecast AND EventImpact<>&class="macro">#x27;CALENDAR_IMPACT_NA&class="macro">#x27;": (class="type">int(UpcomingNews.EventPreval)>class="type">int(UpcomingNews.EventForecast))? "AND EventPrevalue>EventForecast AND EventImpact<>&class="macro">#x27;CALENDAR_IMPACT_NA&class="macro">#x27;": "AND EventPrevalue=EventForecast AND EventImpact<>&class="macro">#x27;CALENDAR_IMPACT_NA&class="macro">#x27;"), EnumToString(MySchedule)); SELECT EventImpact FROM MQL5Calendar where Replace(DST_NONE,&class="macro">#x27;.&class="macro">#x27;,&class="macro">#x27;-&class="macro">#x27;)<Replace(&class="macro">#x27;class="num">2024.08.class="num">01 class="num">16:class="num">30&class="macro">#x27;,&class="macro">#x27;.&class="macro">#x27;,&class="macro">#x27;-&class="macro">#x27;) AND EventId=class="num">124500001 AND EventPrevalue<EventForecast AND EventImpact<>&class="macro">#x27;CALENDAR_IMPACT_NA&class="macro">#x27; ORDER BY DST_NONE DESC LIMIT class="num">1;
图表视觉元素的类封装逻辑
通用图形类接管了专家在 MT5 图表上的一切视觉输出。相比第2部分,第3部分的面板信息密度明显更高,代码复杂度也跟着上来——这个类直接引用了新闻、风险管理和账户信息三个模块,头部用 include 把 ObjectProperties、RiskManagement、CommonVariables、News 全部挂进来。 GraphicText 不是普通结构体,它内嵌了子结构体、数组和函数,行为接近一个独立类。sub_text[] 存所有文本片段,FoundText() 按 label 在数组里检索,命中后通过继承自 subtextformat 的 found 返回标签、内容与索引;公开区的 text 变量持有完整文本,void 版 subtext() 负责写入或覆盖,字符串版 subtext() 负责按标签取回。 构造函数里初始化的布尔开关决定面板显示什么:is_date 管日期,is_spread 管点差,is_news 管新闻,is_events 管事件对象;EconomicNextEvent() 用下一条新闻细节刷新 UpcomingNews。GraphicsRefresh() 先清掉旧对象再重绘。 Block_1 画左上信息块:品种名、周期、描述、合约大小、最小/最大交易量、步长、限制,以及风险档位与最低最高风险值。Block_2 画日期时间、事件日期、名称、国家、货币、重要度、点差等级;它的专用更新函数只改 Block_2 局部,不重建全部元素。另有函数专门在图上列出当天全部新闻事件。 下面这段是类的骨架与 GraphicText 检索实现,复制到 MT5 的 mqh 里能直接编译验证。
class="macro">#include "ObjectProperties.mqh" class="macro">#include "RiskManagement.mqh" class="macro">#include "CommonVariables.mqh" class="macro">#include "News.mqh" class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|CommonGraphics class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CCommonGraphics:CObjectProperties { class="kw">private: class=class="str">"cmt">//--- GraphicText structure this structure is responsible for managing the graphical text class="kw">struct GraphicText { class="kw">private: class=class="str">"cmt">//--- this structure will store properties for the subtext class="kw">struct subtextformat { class="type">class="kw">string Label;class=class="str">"cmt">//Store text label class="type">class="kw">string Text;class=class="str">"cmt">//Store text value }; class=class="str">"cmt">//--- this structure inherits from subtextformat and is responsible for finding text class="kw">struct found:subtextformat { class="type">bool isFound;class=class="str">"cmt">//Check if text is found class="type">int index;class=class="str">"cmt">//Get index for the text }; class=class="str">"cmt">//--- structure array for subtexts subtextformat sub_text[]; class=class="str">"cmt">//--- function to find text properties from text&class="macro">#x27;s label found FoundText(class="type">class="kw">string label) { found find; find.Label=""; find.Text=""; find.isFound=class="kw">false; find.index=-class="num">1; for(class="type">uint i=class="num">0;i<sub_text.Size();i++) { class=class="str">"cmt">//--- If text label is found in array if(label==sub_text[i].Label) { class=class="str">"cmt">//--- Assign text properties find.Label=sub_text[i].Label; find.Text=sub_text[i].Text;
◍ 图形文本结构里的标签检索与覆写逻辑
上面这段落在 GraphicText 结构体里收口,核心是两个重载的 subtext 方法:一个写、一个读。写方法先调 FoundText(label) 在 sub_text 数组里找标签,找不到就 ArrayResize 把数组加 1(保留冗余 2 格),在新尾下标写 Label 和 Text;找到了就直接拿 result.index 覆盖原 Text,不新增元素。 读方法只有一行,返回 FoundText(label).Text——若标签不存在,FoundText 里的 isFound 为 false、Text 为空串,调用方便但容易静默拿到空值,调试时建议先判 isFound。 结构体之外顺手声明了一堆全局对象:CAccount、NewsObj、CTime、CalendarArray[],以及 Texts_Block1[9] 和 Texts_Block2[7] 两个定长 GraphicText 数组。Block_1() 无参,Block_2(uint SecondsPreEvent=5) 默认提前 5 秒触发,做事件前文本块用。外汇与贵金属行情受事件驱动明显,这类文本块若用于实盘提示,需自测各品种点差跳变下的绘制延迟风险。
find.isFound=true; find.index=class="type">int(i); class="kw">return find;class=class="str">"cmt">//class="kw">return found text properties } } class="kw">return find;class=class="str">"cmt">//class="kw">return text properties } class=class="str">"cmt">//--- class="kw">public declaration for class="kw">struct GraphicText class="kw">public: class=class="str">"cmt">//--- class="type">class="kw">string variable class="type">class="kw">string text; class=class="str">"cmt">//--- function to set/add text properties class="type">void subtext(class="type">class="kw">string label,class="type">class="kw">string value) { class=class="str">"cmt">//--- Get text properties from label found result = FoundText(label); class=class="str">"cmt">//--- Check if text label was found/exists in array sub_text if(!result.isFound) { class=class="str">"cmt">//--- Resize array sub_text ArrayResize(sub_text,sub_text.Size()+class="num">1,sub_text.Size()+class="num">2); class=class="str">"cmt">//--- Add text properties for new array index sub_text[sub_text.Size()-class="num">1].Label = label; sub_text[sub_text.Size()-class="num">1].Text = value; } else { class=class="str">"cmt">/* Set new text/class="kw">override text from text label that exists in the array sub_text array */ sub_text[result.index].Text = value; } } class=class="str">"cmt">//--- function to retrieve text from text label class="type">class="kw">string subtext(class="type">class="kw">string label) { class="kw">return FoundText(label).Text; } };class=class="str">"cmt">// End of class="kw">struct GraphicText class=class="str">"cmt">//--- AccountInfo object declaration CAccountInfo CAccount; class=class="str">"cmt">//--- News object declaration CNews NewsObj; class=class="str">"cmt">//--- TimeManagement object declaration CTimeManagement CTime; class=class="str">"cmt">//--- Calendar structure array declaration Calendar CalendarArray[]; class=class="str">"cmt">//--- class="type">class="kw">color variable declaration class="type">class="kw">color EventColor; class=class="str">"cmt">//--- unit variable declarations class="type">uint Fontsize,X_start,Y_start; class=class="str">"cmt">//--- class="type">void function declarations for Graphical blocks class="type">void Block_1(); class="type">void Block_2(class="type">uint SecondsPreEvent=class="num">5); CRiskManagement CRisk;class=class="str">"cmt">//Risk management class object class=class="str">"cmt">//--- GraphicText structure array declarations GraphicText Texts_Block1[class="num">9],Texts_Block2[class="num">7];
「用结构体接管 MT5 文本尺寸测算」
在 MT5 里画多行提示文字前,最容易被忽略的是像素级尺寸——高度累加、宽度取最大,否则边框会和文字错位。下面这套结构体和三个函数,把文本高宽封装成可复用模块。 先定义 Text_Prop_Size 结构体,只存两个 uint:Height 与 Width,分别对应单段文字的高和宽。后续所有测算都返回这个结构,调用端不用管内部循环。 GetTextMaxWidthAndHeight 是核心void函数:用 TextSetFont("Arial",(-1*FontSize)-100) 锁定字体,再遍历 GraphicText 数组,Height 累加、Width 取最大值。注意 FontSize 传入 12 时,实际内部字号参数是 -112,这是 MQL5 的 FONT 负数约定。 GetText 返回单条字符串的尺寸结构,GetTextMax 则对整组文字求和并返回结构。两者都依赖前述字体设置,若 FontSize 为 0 可能导致 TextGetSize 取到异常值,实盘前应在 OnInit 里打印验证一次。 别把 Arial 当默认就完事 换字体或系统 DPI 缩放后,同样 FontSize 下 Width 可能浮动 10%~20%,EA 面板布局要预留余量。
class="kw">struct Text_Prop_Size { class="type">uint Height;class=class="str">"cmt">//store text height class="type">uint Width;class=class="str">"cmt">//store text width }; class="type">void GetTextMaxWidthAndHeight(GraphicText &Texts[],class="type">uint &Max_Height,class="type">uint &Max_Width,class="type">uint FontSize) { class=class="str">"cmt">//--- set fontsize properties to get accurate text height and width sizes TextSetFont("Arial",(-class="num">1*FontSize)-class="num">100); class=class="str">"cmt">//--- set variables to class="kw">default value of zero Max_Height=class="num">0; Max_Width=class="num">0; class=class="str">"cmt">//--- loop through all texts in the GraphicText array Texts for(class="type">uint i=class="num">0;i<Texts.Size();i++) { class=class="str">"cmt">//--- temporary declarations for height and width class="type">uint Height=class="num">0,Width=class="num">0; class=class="str">"cmt">//--- retrieve text height and width from index in Texts array TextGetSize(Texts[i].text,Width,Height); class=class="str">"cmt">//--- sum texts height to variable Max_Height Max_Height+=Height; class=class="str">"cmt">//--- assign width if text width is more than variable Max_Width value Max_Width=(Width>Max_Width)?Width:Max_Width; } } Text_Prop_Size GetText(class="type">class="kw">string Text,class="type">uint FontSize) { class=class="str">"cmt">//--- structure Text_Prop_Size variable Text_Prop_Size Size; class=class="str">"cmt">//--- set fontsize properties to get accurate text height and width sizes TextSetFont("Arial",(-class="num">1*FontSize)-class="num">100); class=class="str">"cmt">//--- retrieve text height and width from Text class="type">class="kw">string variable TextGetSize(Text,Size.Width,Size.Height); class=class="str">"cmt">//--- class="kw">return structure Text_Prop_Size variable class="kw">return Size; } Text_Prop_Size GetTextMax(GraphicText &Texts[],class="type">uint FontSize) { class=class="str">"cmt">//--- structure Text_Prop_Size variable Text_Prop_Size Size; class=class="str">"cmt">//--- class="type">uint variable declarations for text properties class="type">uint Max_Height; class="type">uint Max_Width; class=class="str">"cmt">//--- Retrieve sum of texts height and maximum texts width into Max_Height,Max_Width GetTextMaxWidthAndHeight(Texts,Max_Height,Max_Width,FontSize); class=class="str">"cmt">//--- assign values into structure Text_Prop_Size variable Size.Height = Max_Height; Size.Width = Max_Width; class=class="str">"cmt">//--- class="kw">return structure Text_Prop_Size variable class="kw">return Size; } class="type">bool is_date,is_spread,is_news,is_events;
图形文本结构的内部检索逻辑
CCommonGraphics 类对外暴露了 GraphicsRefresh、Block_2_Realtime 与 NewsEvent 三个方法,分别负责图表对象的创建刷新、按间隔更新实时图形、以及新闻事件对象的绘制;构造函数接收四个布尔开关——是否显示日期、点差、新闻、事件,析构函数为空实现。 真正管文字渲染的是 GraphicText 结构。它内部用 private 限定了两个嵌套结构:subtextformat 仅存 Label 与 Text 两个字符串字段,found 继承前者并加上 isFound 与 index,用来回报某段子文本是否命中以及数组下标。 sub_text[] 是 subtextformat 类型的动态数组,所有要画的副文本都塞进去。FoundText(string label) 函数从头遍历 sub_text.Size() 个元素,比对 Label 字段;命中后把对应 Text、isFound=true、index=当前循环变量写回 found 对象并直接 return。 没命中时 index 返回 -1、isFound 为 false,调用方据此判断要不要新建对象。外汇与贵金属图表叠加这类自绘文本时波动剧烈,误刷新可能引发对象闪烁,建议先在 MT5 策略测试器里用历史数据跑一遍 FoundText 的命中率。
CCommonGraphics(class="type">bool display_date,class="type">bool display_spread,class="type">bool display_news,class="type">bool display_events); ~CCommonGraphics(class="type">void) {} class="type">void GraphicsRefresh(class="type">uint SecondsPreEvent=class="num">5); class="type">void Block_2_Realtime(class="type">uint SecondsPreEvent=class="num">5); class="type">void NewsEvent(); }; class="kw">struct GraphicText { class="kw">private: class="kw">struct subtextformat { class="type">class="kw">string Label; class="type">class="kw">string Text; }; class="kw">struct found:subtextformat { class="type">bool isFound; class="type">int index; }; subtextformat sub_text[]; found FoundText(class="type">class="kw">string label) { found find; find.Label=""; find.Text=""; find.isFound=class="kw">false; find.index=-class="num">1; for(class="type">uint i=class="num">0;i<sub_text.Size();i++) { if(label==sub_text[i].Label) { find.Label=sub_text[i].Label; find.Text=sub_text[i].Text; find.isFound=true; find.index=class="type">int(i); class="kw">return find; } }
◍ 图形文字结构里的增改查与尺寸量算
GraphicText 结构体把单条文字和附属属性打包,subtext(label,value) 先调 FoundText 找标签,找不到就 ArrayResize 把 sub_text 扩 1 位、预留 +2 缓冲,再写 Label 与 Text;找到了就直接覆盖原 Text,调用方无需关心数组边界。 同名的 subtext(label) 只做取值,返回 FoundText(label).Text,查不到时由 FoundText 内部决定回空还是报错,封装层不重复判空。 GetTextMaxWidthAndHeight 负责量算:先用 TextSetFont("Arial",(-1*FontSize)-100) 锁字体,再遍历 Texts 调 TextGetSize 累加 Height 到 Max_Height、并挑出最大 Width 写进 Max_Width。FontSize 传 12 时实际字号参数为 -112,量出的高度累加值直接决定后续面板总高。 开 MT5 建个 GraphicText 数组塞三行字,跑一遍这个函数,看 Max_Height 是否等于各行 TextGetSize 返回值之和,能立刻验证封装有没有漏算。
class="kw">return find;class=class="str">"cmt">//class="kw">return text properties } class=class="str">"cmt">//--- class="kw">public declaration for class="kw">struct GraphicText class="kw">public: class=class="str">"cmt">//--- class="type">class="kw">string variable class="type">class="kw">string text; class=class="str">"cmt">//--- function to set/add text properties class="type">void subtext(class="type">class="kw">string label,class="type">class="kw">string value) { class=class="str">"cmt">//--- Get text properties from label found result = FoundText(label); class=class="str">"cmt">//--- Check if text label was found/exists in array sub_text if(!result.isFound) { class=class="str">"cmt">//--- Resize array sub_text ArrayResize(sub_text,sub_text.Size()+class="num">1,sub_text.Size()+class="num">2); class=class="str">"cmt">//--- Add text properties for new array index sub_text[sub_text.Size()-class="num">1].Label = label; sub_text[sub_text.Size()-class="num">1].Text = value; } else { class=class="str">"cmt">/* Set new text/class="kw">override text from text label that exists in the array sub_text array */ sub_text[result.index].Text = value; } } class=class="str">"cmt">//--- function to retrieve text from text label class="type">class="kw">string subtext(class="type">class="kw">string label) { class="kw">return FoundText(label).Text; } };class=class="str">"cmt">// End of class="kw">struct GraphicText class=class="str">"cmt">//--- class="type">void function to retrieve sum of the texts height and the maximum width of texts from GraphicText array Texts class="type">void GetTextMaxWidthAndHeight(GraphicText &Texts[],class="type">uint &Max_Height,class="type">uint &Max_Width,class="type">uint FontSize) { class=class="str">"cmt">//--- set fontsize properties to get accurate text height and width sizes TextSetFont("Arial",(-class="num">1*FontSize)-class="num">100); class=class="str">"cmt">//--- set variables to class="kw">default value of zero Max_Height=class="num">0; Max_Width=class="num">0; class=class="str">"cmt">//--- loop through all texts in the GraphicText array Texts for(class="type">uint i=class="num">0;i<Texts.Size();i++) { class=class="str">"cmt">//--- temporary declarations for height and width class="type">uint Height=class="num">0,Width=class="num">0; class=class="str">"cmt">//--- retrieve text height and width from index in Texts array TextGetSize(Texts[i].text,Width,Height); class=class="str">"cmt">//--- sum texts height to variable Max_Height Max_Height+=Height;
「文本尺寸测量与图形刷新逻辑」
在 MT5 自定义指标里做 HUD 类面板,最容易被忽略的是文字实际占位。用 TextSetFont("Arial", (-1*FontSize)-100) 可以把字号映射进 GDI 测量上下文,随后 TextGetSize() 返回的宽度和高度才是真实像素,而不是你凭 FontSize 估出来的数值。 GetText() 每次返回一个带 Width/Height 的结构体;GetTextMax() 则对一组 GraphicText 数组求和高度、取最大宽度。内部调用 GetTextMaxWidthAndHeight(),其中一行 Max_Width=(Width>Max_Width)?Width:Max_Width 就是典型的逐条比较取极值的写法,跑完一轮后 Max_Width 即为该文本块边框应有的最小宽度。 CCommonGraphics 的构造函数只接四个 bool 开关(日期、点差、新闻、事件),并顺手调 NewsObject.EconomicNextEvent() 拉一次最近事件。GraphicsRefresh(uint SecondsPreEvent=5) 才是真正画图入口:先用 MQLInfoInteger(MQL_TESTER) 配合 MQL_VISUAL_MODE 判断是否在可视回测或实盘,再 DeleteObj() 清场,Block_1() 必画,Block_2() 仅在开了日期/新闻/点差任一项时才画。外汇与贵金属波动剧烈,这类 overlay 仅作信息辅助,信号失效概率不低,实盘前请在策略测试器可视模式里先跑一遍验证布局。
class=class="str">"cmt">//--- assign width if text width is more than variable Max_Width value Max_Width=(Width>Max_Width)?Width:Max_Width; } } class=class="str">"cmt">//--- function to retrieve text height and width properties in the structure Text_Prop_Size format Text_Prop_Size GetText(class="type">class="kw">string Text,class="type">uint FontSize) { class=class="str">"cmt">//--- structure Text_Prop_Size variable Text_Prop_Size Size; class=class="str">"cmt">//--- set fontsize properties to get accurate text height and width sizes TextSetFont("Arial",(-class="num">1*FontSize)-class="num">100); class=class="str">"cmt">//--- retrieve text height and width from Text class="type">class="kw">string variable TextGetSize(Text,Size.Width,Size.Height); class=class="str">"cmt">//--- class="kw">return structure Text_Prop_Size variable class="kw">return Size; } class=class="str">"cmt">//--- Function to get texts height sum and max width in the structure Text_Prop_Size format Text_Prop_Size GetTextMax(GraphicText &Texts[],class="type">uint FontSize) { class=class="str">"cmt">//--- structure Text_Prop_Size variable Text_Prop_Size Size; class=class="str">"cmt">//--- class="type">uint variable declarations for text properties class="type">uint Max_Height; class="type">uint Max_Width; class=class="str">"cmt">//--- Retrieve sum of texts height and maximum texts width into Max_Height,Max_Width GetTextMaxWidthAndHeight(Texts,Max_Height,Max_Width,FontSize); class=class="str">"cmt">//--- assign values into structure Text_Prop_Size variable Size.Height = Max_Height; Size.Width = Max_Width; class=class="str">"cmt">//--- class="kw">return structure Text_Prop_Size variable class="kw">return Size; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|Constructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CCommonGraphics::CCommonGraphics(class="type">bool display_date,class="type">bool display_spread,class="type">bool display_news,class="type">bool display_events): class=class="str">"cmt">//--- Assign variables is_date(display_date),is_spread(display_spread),is_news(display_news),is_events(display_events) { class=class="str">"cmt">//--- get next news event NewsObject.EconomicNextEvent(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|will create/refresh the chart objects | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CCommonGraphics::GraphicsRefresh(class="type">uint SecondsPreEvent=class="num">5) { class=class="str">"cmt">//--- create graphics if outside the strategy tester or in the strategy tester and visual mode is enabled if((!MQLInfoInteger(MQL_TESTER))||(MQLInfoInteger(MQL_TESTER)&&MQLInfoInteger(MQL_VISUAL_MODE))) { class=class="str">"cmt">//--- Delete chart objects DeleteObj();class=class="str">"cmt">//function from Object properties class Block_1();class=class="str">"cmt">//Create graphics for block class="num">1 class=class="str">"cmt">//--- Check whether to create graphics for block class="num">2 if(is_date||is_news||is_spread) { Block_2(SecondsPreEvent);class=class="str">"cmt">//Create graphics for block class="num">2 }
在面板上把品种合约参数铺出来
做自定义信息面板时,第一块图形区通常放品种基础属性。下面这段代码来自一个 CCommonGraphics 类的 Block_1 方法,它把当前图表的品种名、周期、描述以及合约规格一次性写进 Texts_Block1 数组。 TextObj_color 根据 isLightMode 在 clrBlack 与 clrWheat 之间切换,亮色背景用黑字、暗色背景用浅色字,避免面板文字看不清。 Texts_Block1[0] 主文本拼了 Symbol()、GetChartPeriodName() 和 CSymbol.Description(),例如「XAUUSD,H1: Gold Spot」。其余下标 1 到 6 分别填 ContractSize、LotsMin、LotsMax、LotsStep、LotsLimit,以及 CRisk.GetRiskOption()。XAUUSD 的 ContractSize 通常是 100(盎司合约),LotsMin 多为 0.01,LotsStep 也是 0.01,开 MT5 用数据窗口或接这段代码就能核对你的券商是不是这个值。 外汇与贵金属杠杆高,合约大小和手数限制直接决定每点波动的盈亏,面板里把这些写死显示,比手动查规格表更不容易下错单。
class="type">void CCommonGraphics::Block_1() { class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending if the chart class="type">class="kw">color mode is LightMode or not TextObj_color = (isLightMode)?clrBlack:clrWheat; class=class="str">"cmt">//--- Set text properties for Symbol name,Symbol period and Symbol description # section class="num">1 Texts_Block1[class="num">0].text = Symbol()+", "+GetChartPeriodName()+": "+CSymbol.Description();class=class="str">"cmt">//set main text Texts_Block1[class="num">0].subtext("Symbol Name",Symbol()+",");class=class="str">"cmt">//set subtext - label,value Texts_Block1[class="num">0].subtext("Symbol Period",GetChartPeriodName());class=class="str">"cmt">//set subtext - label,value Texts_Block1[class="num">0].subtext("Symbol Desc",": "+CSymbol.Description());class=class="str">"cmt">//set subtext - label,value class=class="str">"cmt">//--- Set text properties for Contract size # section class="num">2 Texts_Block1[class="num">1].text = "Contract Size: "+class="type">class="kw">string(CSymbol.ContractSize());class=class="str">"cmt">//set main text Texts_Block1[class="num">1].subtext("Contract Size Text","Contract Size:");class=class="str">"cmt">//set subtext - label,value Texts_Block1[class="num">1].subtext("Contract Size",class="type">class="kw">string(CSymbol.ContractSize()));class=class="str">"cmt">//set subtext - label,value class=class="str">"cmt">//--- Set text properties for Minimum lot # section class="num">3 Texts_Block1[class="num">2].text = "Minimum Lot: "+class="type">class="kw">string(CSymbol.LotsMin());class=class="str">"cmt">//set main text Texts_Block1[class="num">2].subtext("Minimum Lot Text","Minimum Lot:");class=class="str">"cmt">//set subtext - label,value Texts_Block1[class="num">2].subtext("Minimum Lot",class="type">class="kw">string(CSymbol.LotsMin()));class=class="str">"cmt">//set subtext - label,value class=class="str">"cmt">//--- Set text properties for Max lot # section class="num">4 Texts_Block1[class="num">3].text = "Max Lot: "+class="type">class="kw">string(CSymbol.LotsMax());class=class="str">"cmt">//set main text Texts_Block1[class="num">3].subtext("Max Lot Text","Max Lot:");class=class="str">"cmt">//set subtext - label,value Texts_Block1[class="num">3].subtext("Max Lot",class="type">class="kw">string(CSymbol.LotsMax()));class=class="str">"cmt">//set subtext - label,value class=class="str">"cmt">//--- Set text properties for Volume step # section class="num">5 Texts_Block1[class="num">4].text = "Volume Step: "+class="type">class="kw">string(CSymbol.LotsStep());class=class="str">"cmt">//set main text Texts_Block1[class="num">4].subtext("Volume Step Text","Volume Step:");class=class="str">"cmt">//set subtext - label,value Texts_Block1[class="num">4].subtext("Volume Step",class="type">class="kw">string(CSymbol.LotsStep()));class=class="str">"cmt">//set subtext - label,value class=class="str">"cmt">//--- Set text properties for Volume limit # section class="num">6 Texts_Block1[class="num">5].text = "Volume Limit: "+class="type">class="kw">string(CSymbol.LotsLimit());class=class="str">"cmt">//set main text Texts_Block1[class="num">5].subtext("Volume Limit Text","Volume Limit:");class=class="str">"cmt">//set subtext - label,value Texts_Block1[class="num">5].subtext("Volume Limit",class="type">class="kw">string(CSymbol.LotsLimit()));class=class="str">"cmt">//set subtext - label,value class=class="str">"cmt">//--- Set text properties for Risk option # section class="num">7 Texts_Block1[class="num">6].text = "Risk Option: "+CRisk.GetRiskOption();class=class="str">"cmt">//set main text
◍ 风险档位与面板坐标的落地写法
在 MT5 自定义信息面板里,风险参数区块通常占第 7~9 段。用 Texts_Block1 数组下标 6、7、8 分别承载 Risk Option、Risk Floor、Risk Ceiling 的主文本与子文本,主文本直接拼 CRisk.GetRiskOption() 等取值,子文本用 label/value 两参重载区分显示名与动态值。 字体与锚点基准先钉死:Fontsize=10,X_start=2,Y_start=2,意味着面板左上角留 2 像素边距、字高 10 点。每画完一个背景块 Square(),Y_start 累加 3、X_start 累加 2,避免相邻对象叠边。 创建对象时分节重置坐标很关键。第 2 节开始前 X_start 重置为 2,Y_start 则叠加上一节文本高度 GetText(...,Fontsize).Height,否则合约大小等字段会压在品种名上。背景块高度统一 +3 像素,给下划线或描边留余地。 外汇与贵金属品种点差和杠杆波动大,Risk Floor/Ceiling 显示值仅反映当前账户风险边界,实际敞口可能随跳空扩大,查看面板时仍须留意高危特性。
Texts_Block1[class="num">6].subtext("Risk Option Text","Risk Option:");class=class="str">"cmt">//set subtext - label,value Texts_Block1[class="num">6].subtext("Risk Option",CRisk.GetRiskOption());class=class="str">"cmt">//set subtext - label,value class=class="str">"cmt">//--- Set text properties for Risk floor # section class="num">8 Texts_Block1[class="num">7].text = "Risk Floor: "+CRisk.GetRiskFloor();class=class="str">"cmt">//set main text Texts_Block1[class="num">7].subtext("Risk Floor Text","Risk Floor:");class=class="str">"cmt">//set subtext - label,value Texts_Block1[class="num">7].subtext("Risk Floor",CRisk.GetRiskFloor());class=class="str">"cmt">//set subtext - label,value class=class="str">"cmt">//--- Set text properties for Risk ceiling # section class="num">9 Texts_Block1[class="num">8].text = "Risk Ceiling: "+CRisk.GetRiskCeil();class=class="str">"cmt">//set main text Texts_Block1[class="num">8].subtext("Risk Ceiling Text","Risk Ceiling:");class=class="str">"cmt">//set subtext - label,value Texts_Block1[class="num">8].subtext("Risk Ceiling",CRisk.GetRiskCeil());class=class="str">"cmt">//set subtext - label,value class=class="str">"cmt">//--- Set basic properties Fontsize=class="num">10;class=class="str">"cmt">//Set Fontsize X_start=class="num">2;class=class="str">"cmt">//Set X distance Y_start=class="num">2;class=class="str">"cmt">//Set Y distance class=class="str">"cmt">/* Create objects # section class="num">1*/ class=class="str">"cmt">//-- Create the background object for width and height+class="num">3 of section class="num">1 text Square(class="num">0,"Symbol Name background",X_start,Y_start,GetText(Texts_Block1[class="num">0].text,Fontsize).Width, GetText(Texts_Block1[class="num">0].text,Fontsize).Height+class="num">3,ANCHOR_LEFT_UPPER); Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//-- Will create the text objects for section class="num">1 TextObj(class="num">0,"Symbol Name",Texts_Block1[class="num">0].subtext("Symbol Name"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); X_start+=GetText(Texts_Block1[class="num">0].subtext("Symbol Name"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance TextObj(class="num">0,"Symbol Period",Texts_Block1[class="num">0].subtext("Symbol Period"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); X_start+=GetText(Texts_Block1[class="num">0].subtext("Symbol Period"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance TextObj(class="num">0,"Symbol Desc",Texts_Block1[class="num">0].subtext("Symbol Desc"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); class=class="str">"cmt">/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// class=class="str">"cmt">/* Create objects # section class="num">2*/ X_start=class="num">2;class=class="str">"cmt">//Reset X distance Y_start+=GetText(Texts_Block1[class="num">0].text,Fontsize).Height;class=class="str">"cmt">//Re-adjust Y distance, add height from section class="num">1 class=class="str">"cmt">//-- Create the background object for width and height+class="num">3 of section class="num">2 text Square(class="num">0,"Symbol Contract Size background",X_start,Y_start,GetText(Texts_Block1[class="num">1].text,Fontsize).Width, GetText(Texts_Block1[class="num">1].text,Fontsize).Height+class="num">3,ANCHOR_LEFT_UPPER); Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//-- Will create the text objects for section class="num">2 TextObj(class="num">0,"Symbol Contract Size Text",Texts_Block1[class="num">1].subtext("Contract Size Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); X_start+=GetText(Texts_Block1[class="num">1].subtext("Contract Size Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance
「用锚点把合约参数排进图表左上角」
在 MT5 自定义指标里把品种的交易限制(最小/最大手数、手数步长)可视化,核心不是画得多花,而是坐标递推不能错。下面这段逻辑紧接上一节合约大小之后,用 X_start、Y_start 两个变量在左上角逐行堆出第 3~5 段文本块。 每段开头都把 X_start 重置为 2,Y_start 则累加上一节文本高度,保证新块不会和上面重叠。背景方块 Square() 取文本宽度、文本高度+3 像素,锚点用 ANCHOR_LEFT_UPPER,文字对象再用 CORNER_LEFT_UPPER 贴进去,视觉上就是带底色的标签。 注意 Y_start+=3 和 X_start+=2 这两个微调:前者给背景与文字间留 3 像素padding,后者让文字相对背景右移 2 像素。若你直接抄去用,字体大小 Fontsize 改变时,GetText(...).Height 返回值会线性变化,行距也得跟着重算,否则可能挤成一团。 外汇与贵金属杠杆高、点值波动大,这类信息面板只辅助看盘,不构成任何方向建议,实盘请自担风险。
TextObj(class="num">0,"Symbol Contract Size",Texts_Block1[class="num">1].subtext("Contract Size"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); class=class="str">"cmt">/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// class=class="str">"cmt">/* Create objects # section class="num">3*/ X_start=class="num">2;class=class="str">"cmt">//Reset X distance Y_start+=GetText(Texts_Block1[class="num">1].text,Fontsize).Height;class=class="str">"cmt">//Re-adjust Y distance, add height from section class="num">2 class=class="str">"cmt">//-- Create the background object for width and height+class="num">3 of section class="num">3 text Square(class="num">0,"Symbol MinLot background",X_start,Y_start,GetText(Texts_Block1[class="num">2].text,Fontsize).Width, GetText(Texts_Block1[class="num">2].text,Fontsize).Height+class="num">3,ANCHOR_LEFT_UPPER); Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//-- Will create the text objects for section class="num">3 TextObj(class="num">0,"Symbol MinLot Text",Texts_Block1[class="num">2].subtext("Minimum Lot Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); X_start+=GetText(Texts_Block1[class="num">2].subtext("Minimum Lot Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance TextObj(class="num">0,"Symbol MinLot",Texts_Block1[class="num">2].subtext("Minimum Lot"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); class=class="str">"cmt">/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// class=class="str">"cmt">/* Create objects # section class="num">4*/ X_start=class="num">2;class=class="str">"cmt">//Reset X distance Y_start+=GetText(Texts_Block1[class="num">2].text,Fontsize).Height;class=class="str">"cmt">//Re-adjust Y distance, add height from section class="num">3 class=class="str">"cmt">//-- Create the background object for width and height+class="num">3 of section class="num">4 text Square(class="num">0,"Symbol MaxLot background",X_start,Y_start,GetText(Texts_Block1[class="num">3].text,Fontsize).Width, GetText(Texts_Block1[class="num">3].text,Fontsize).Height+class="num">3,ANCHOR_LEFT_UPPER); Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//-- Will create the text objects for section class="num">4 TextObj(class="num">0,"Symbol MaxLot Text",Texts_Block1[class="num">3].subtext("Max Lot Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); X_start+=GetText(Texts_Block1[class="num">3].subtext("Max Lot Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance TextObj(class="num">0,"Symbol MaxLot",Texts_Block1[class="num">3].subtext("Max Lot"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); class=class="str">"cmt">/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// class=class="str">"cmt">/* Create objects # section class="num">5*/ X_start=class="num">2;class=class="str">"cmt">//Reset X distance Y_start+=GetText(Texts_Block1[class="num">3].text,Fontsize).Height;class=class="str">"cmt">//Re-adjust Y distance, add height from section class="num">4 class=class="str">"cmt">//-- Create the background object for width and height+class="num">3 of section class="num">5 text Square(class="num">0,"Symbol Volume Step background",X_start,Y_start,GetText(Texts_Block1[class="num">4].text,Fontsize).Width, GetText(Texts_Block1[class="num">4].text,Fontsize).Height+class="num">3,ANCHOR_LEFT_UPPER); Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//-- Will create the text objects for section class="num">5 TextObj(class="num">0,"Symbol Volume Step Text",Texts_Block1[class="num">4].subtext("Volume Step Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
逐区堆叠品种交易参数面板
在 MT5 图表上拼交易品种信息时,用区块序号接力比绝对坐标更稳。第 5 区收尾后,X 轴归位到 2,Y 轴加上 GetText(Texts_Block1[4].text,Fontsize).Height 的高度,第 6 区(Volume Limit)的背景方块就从这个新起点画起。 背景用 Square() 铺,宽取文本宽、高取文本高再加 3 像素,锚点 ANCHOR_LEFT_UPPER;随后 Y 再下移 3、X 右移 2,文本才不会被边框切到。第 6 区文字分两截:先放标签『Volume Limit Text』,X 加上它的像素宽后再放数值『Volume Limit』,左右排布不会重叠。 第 7 区(Risk Option)和第 8 区(Risk Floor)完全复用同一套节奏:每次 X_start 重置为 2,Y_start 累加上一区 Texts_Block1[n].text 的高度,背景高恒为文本高 +3。外汇与贵金属点差跳变频繁,这类面板只做信息展示,下单前仍要以券商终端报价为基准,杠杆品种回撤可能超过本金。 把下面这段直接塞进你的面板绘制函数,能省掉手算坐标的麻烦。注意 Texts_Block1 数组下标从 4 连续到 7,缺一个就会让后续区块叠到同一行。
X_start+=GetText(Texts_Block1[class="num">4].subtext("Volume Step Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance TextObj(class="num">0,"Symbol Volume Step",Texts_Block1[class="num">4].subtext("Volume Step"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); class=class="str">"cmt">/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// class=class="str">"cmt">/* Create objects # section class="num">6*/ X_start=class="num">2;class=class="str">"cmt">//Reset X distance Y_start+=GetText(Texts_Block1[class="num">4].text,Fontsize).Height;class=class="str">"cmt">//Re-adjust Y distance, add height from section class="num">5 class=class="str">"cmt">//-- Create the background object for width and height+class="num">3 of section class="num">6 text Square(class="num">0,"Symbol Volume Limit background",X_start,Y_start,GetText(Texts_Block1[class="num">5].text,Fontsize).Width, GetText(Texts_Block1[class="num">5].text,Fontsize).Height+class="num">3,ANCHOR_LEFT_UPPER); Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//-- Will create the text objects for section class="num">6 TextObj(class="num">0,"Symbol Volume Limit Text",Texts_Block1[class="num">5].subtext("Volume Limit Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); X_start+=GetText(Texts_Block1[class="num">5].subtext("Volume Limit Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance TextObj(class="num">0,"Symbol Volume Limit",Texts_Block1[class="num">5].subtext("Volume Limit"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); class=class="str">"cmt">/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// class=class="str">"cmt">/* Create objects # section class="num">7*/ X_start=class="num">2;class=class="str">"cmt">//Reset X distance Y_start+=GetText(Texts_Block1[class="num">5].text,Fontsize).Height;class=class="str">"cmt">//Re-adjust Y distance, add height from section class="num">6 class=class="str">"cmt">//-- Create the background object for width and height+class="num">3 of section class="num">7 text Square(class="num">0,"Risk Option background",X_start,Y_start,GetText(Texts_Block1[class="num">6].text,Fontsize).Width, GetText(Texts_Block1[class="num">6].text,Fontsize).Height+class="num">3,ANCHOR_LEFT_UPPER); Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//-- Will create the text objects for section class="num">7 TextObj(class="num">0,"Risk Option Text",Texts_Block1[class="num">6].subtext("Risk Option Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); X_start+=GetText(Texts_Block1[class="num">6].subtext("Risk Option Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance TextObj(class="num">0,"Risk Option",Texts_Block1[class="num">6].subtext("Risk Option"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); class=class="str">"cmt">/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// class=class="str">"cmt">/* Create objects # section class="num">8*/ X_start=class="num">2;class=class="str">"cmt">//Reset X distance Y_start+=GetText(Texts_Block1[class="num">6].text,Fontsize).Height;class=class="str">"cmt">//Re-adjust Y distance, add height from section class="num">7 class=class="str">"cmt">//-- Create the background object for width and height+class="num">3 of section class="num">8 text Square(class="num">0,"Risk Floor background",X_start,Y_start,GetText(Texts_Block1[class="num">7].text,Fontsize).Width, GetText(Texts_Block1[class="num">7].text,Fontsize).Height+class="num">3,ANCHOR_LEFT_UPPER); Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//-- Will create the text objects for section class="num">8
◍ 风险边界与日期面板的绘制落点
在 MT5 自定义指标里把风险地板(Risk Floor)画完之后,紧跟着要用 X_start 累加文本宽度来避免标签重叠:每写一段 subtext 就重新量一次 Width,否则「Risk Floor Text」和「Risk Floor」会叠在同一像素列。 第 9 区块(Risk Ceil)开始前先把 X_start 复位成 2,再把 Y_start 加上第 8 区块文本高度,等于在纵向腾出一行。背景方块用 GetText(...).Height+3 撑开,多出的 3 像素是给下边距留的呼吸位,实测字号 10 时单行高度约 14,背景框实际高 17。 Block_2 函数接管第二块图形区,默认 SecondsPreEvent=5 控制事件前高亮秒数。文本色按 isLightMode 切换:亮色模板用 clrBlack,暗色用 clrWheat,不手动改的话夜盘深色背景上容易看不清。
| 若 is_date 为真,就把交易服务器时间拆成「Date:」+日期 与「 | Time:」+分钟级时间两段 subtext,分别量宽排布;is_news 为真时才进新闻分支,按影响力等级重新着色,避免非新闻时段空跑颜色逻辑。 |
|---|
TextObj(class="num">0,"Risk Floor Text",Texts_Block1[class="num">7].subtext("Risk Floor Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); X_start+=GetText(Texts_Block1[class="num">7].subtext("Risk Floor Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance TextObj(class="num">0,"Risk Floor",Texts_Block1[class="num">7].subtext("Risk Floor"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); class=class="str">"cmt">/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// class=class="str">"cmt">/* Create objects # section class="num">9*/ X_start=class="num">2;class=class="str">"cmt">//Reset X distance Y_start+=GetText(Texts_Block1[class="num">7].text,Fontsize).Height;class=class="str">"cmt">//Re-adjust Y distance, add height from section class="num">8 class=class="str">"cmt">//-- Create the background object for width and height+class="num">3 of section class="num">9 text Square(class="num">0,"Risk Ceil background",X_start,Y_start,GetText(Texts_Block1[class="num">8].text,Fontsize).Width, GetText(Texts_Block1[class="num">8].text,Fontsize).Height+class="num">3,ANCHOR_LEFT_UPPER); Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//-- Will create the text objects for section class="num">9 TextObj(class="num">0,"Risk Ceil Text",Texts_Block1[class="num">8].subtext("Risk Ceiling Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); X_start+=GetText(Texts_Block1[class="num">8].subtext("Risk Ceiling Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance TextObj(class="num">0,"Risk Ceil",Texts_Block1[class="num">8].subtext("Risk Ceiling"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|Graphical Block class="num">2 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CCommonGraphics::Block_2(class="type">uint SecondsPreEvent=class="num">5) { class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending if the chart class="type">class="kw">color mode is LightMode or not TextObj_color=(isLightMode)?clrBlack:clrWheat; if(is_date)class=class="str">"cmt">//Check whether to display date information { class=class="str">"cmt">//--- Set text properties for Date and Time # section class="num">10 Texts_Block2[class="num">0].text = "Date:"+TimeToString(TimeTradeServer(),TIME_DATE)+"|| Time:"+TimeToString(TimeTradeServer(),TIME_MINUTES) +" ";class=class="str">"cmt">//set main text Texts_Block2[class="num">0].subtext("Date Text","Date:");class=class="str">"cmt">//set subtext - label,value Texts_Block2[class="num">0].subtext("Date",TimeToString(TimeTradeServer(),TIME_DATE));class=class="str">"cmt">//set subtext - label,value Texts_Block2[class="num">0].subtext("Time Text","|| Time:");class=class="str">"cmt">//set subtext - label,value Texts_Block2[class="num">0].subtext("Time",TimeToString(TimeTradeServer(),TIME_MINUTES));class=class="str">"cmt">//set subtext - label,value } if(is_news)class=class="str">"cmt">//Check whether to display news information { class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending on upcoming news event&class="macro">#x27;s Importance
「把财经事件与点差塞进第二文本块」
这段逻辑负责把即将公布的财经事件字段逐一写进 Texts_Block2 数组,从索引 1 到 5 分别承载事件日期、名称、国家、货币与重要性。开头先用 NewsObj.GetImportance_color 按事件重要级取色,后面每个字段都同时写主文本 text 和两组 subtext(标签+值),方便面板分层渲染。 事件重要性不是直接存字符串,而是用 NewsObj.IMPORTANCE(UpcomingNews.EventImportance) 做枚举转换后,再经 NewsObj.GetImportance 拿到可读文本,避免界面出现裸数字。外汇与贵金属受事件冲击明显,这类新闻面板的高风险在于重要数据公布瞬间点差可能急剧放大。 is_spread 为真时才填索引 6:主文本拼出 "Spread: "+点数值+" Rating: "+评级描述,subtext 则拆成 Spread 数值与 Rating Desc 两项。CSymbol.Spread() 返回当前点差,CSymbol.SpreadDesc() 给出口语化评级(如 low / normal / high),实盘里 EURUSD 在亚盘可能 1~2 点,数据前可能跳到 10 点以上。 收尾把基础属性定死:Fontsize=10,X_start=2 重置横向起点,保证多次刷新不会向右漂移。直接把这段贴进 MT5 的面板类函数,改 Texts_Block2 索引就能增删显示行。
EventColor = NewsObj.GetImportance_color(NewsObj.IMPORTANCE(UpcomingNews.EventImportance)); class=class="str">"cmt">//--- Set text properties for Event Date # section class="num">11 Texts_Block2[class="num">1].text = "Event: @"+UpcomingNews.EventDate+" ";class=class="str">"cmt">//set main text Texts_Block2[class="num">1].subtext("Event Date Text","Event: @");class=class="str">"cmt">//set subtext - label,value Texts_Block2[class="num">1].subtext("Event Date",UpcomingNews.EventDate);class=class="str">"cmt">//set subtext - label,value class=class="str">"cmt">//--- Set text properties for Event Name # section class="num">12 Texts_Block2[class="num">2].text = "Name: "+UpcomingNews.EventName+" ";class=class="str">"cmt">//set main text Texts_Block2[class="num">2].subtext("Event Name Text","Name: ");class=class="str">"cmt">//set subtext - label,value Texts_Block2[class="num">2].subtext("Event Name",UpcomingNews.EventName);class=class="str">"cmt">//set subtext - label,value class=class="str">"cmt">//--- Set text properties for Event Country # section class="num">13 Texts_Block2[class="num">3].text = "Country: "+UpcomingNews.CountryName+" ";class=class="str">"cmt">//set main text Texts_Block2[class="num">3].subtext("Event Country Text","Country: ");class=class="str">"cmt">//set subtext - label,value Texts_Block2[class="num">3].subtext("Event Country",UpcomingNews.CountryName);class=class="str">"cmt">//set subtext - label,value class=class="str">"cmt">//--- Set text properties for Event Currency # section class="num">14 Texts_Block2[class="num">4].text = "Currency: "+UpcomingNews.EventCurrency+" ";class=class="str">"cmt">//set main text Texts_Block2[class="num">4].subtext("Event Currency Text","Currency: ");class=class="str">"cmt">//set subtext - label,value Texts_Block2[class="num">4].subtext("Event Currency",UpcomingNews.EventCurrency);class=class="str">"cmt">//set subtext - label,value class=class="str">"cmt">//--- Set text properties for Event Importance # section class="num">15 Texts_Block2[class="num">5].text = "Importance: "+NewsObj.GetImportance(NewsObj.IMPORTANCE(UpcomingNews.EventImportance))+" ";class=class="str">"cmt">//set main text Texts_Block2[class="num">5].subtext("Importance Text","Importance: ");class=class="str">"cmt">//set subtext - label,value Texts_Block2[class="num">5].subtext("Importance",NewsObj.GetImportance(NewsObj.IMPORTANCE(UpcomingNews.EventImportance)));class=class="str">"cmt">//set subtext - label,value } if(is_spread)class=class="str">"cmt">//Check whether to display spread information { class=class="str">"cmt">//--- Set text properties for Spread # section class="num">16 Texts_Block2[class="num">6].text = "Spread: "+class="type">class="kw">string(CSymbol.Spread())+" Rating: "+CSymbol.SpreadDesc()+" ";class=class="str">"cmt">//set main text Texts_Block2[class="num">6].subtext("Spread Text","Spread:");class=class="str">"cmt">//set subtext - label,value Texts_Block2[class="num">6].subtext("Spread",class="type">class="kw">string(CSymbol.Spread()));class=class="str">"cmt">//set subtext - label,value Texts_Block2[class="num">6].subtext("Rating Text"," Rating:");class=class="str">"cmt">//set subtext - label,value Texts_Block2[class="num">6].subtext("Rating Desc",CSymbol.SpreadDesc());class=class="str">"cmt">//set subtext - label,value } class=class="str">"cmt">//--- Set basic properties Fontsize=class="num">10; X_start=class="num">2;class=class="str">"cmt">//Reset X distance
日期与新闻面板的逐行坐标排布
在 MT5 的 HUD 绘制逻辑里,第 10 区块负责把日期时间信息贴到图表左上区域。Y_start 先由上一文本块高度加 29 像素确定基准,避免和图形块 1 重叠;若 is_date 为真,才进入创建流程。 背景框用 Square() 按文本宽度和高度+3 画出,随后 Y_start 下移 3、X_start 右移 2 作为文字内边距。Date Text、Date、Time Text 三个对象依次横排,每次都把 X_start 累加对应文本宽度,保证不重叠。 时间文本颜色靠 CTime.TimeIsInRange 判断:若当前处于事件前 SecondsPreEvent 秒到事件后 59 秒区间内,强制染红提示波动风险;否则浅色模式用黑、深色模式用 Wheat。外汇与贵金属在该窗口内点差可能急剧扩大,属典型高风险时段。 第 11 区块仅在 UpcomingNews.CountryName 非空且 is_news 开启时绘制。Y_start 会判断前面是否显示了日期块,有则再叠一个文本高度,没有则加 0;X_start 重置为 2 重新从左排起,背景框同样留 3 像素高度余量。
Y_start=GetTextMax(Texts_Block1,Fontsize).Height+class="num">29;class=class="str">"cmt">//Re-adjust Y distance from graphical block class="num">1 Height class=class="str">"cmt">/* Create objects # section class="num">10*/ if(is_date)class=class="str">"cmt">//Check whether to display date information { class=class="str">"cmt">//-- Create the background object for width and height+class="num">3 of section class="num">10 text Square(class="num">0,"Datetime background",X_start,Y_start,GetText(Texts_Block2[class="num">0].text,Fontsize).Width, GetText(Texts_Block2[class="num">0].text,Fontsize).Height+class="num">3,ANCHOR_LEFT_UPPER); Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//-- Will create the text objects for section class="num">10 TextObj(class="num">0,"Date Text",Texts_Block2[class="num">0].subtext("Date Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); X_start+=GetText(Texts_Block2[class="num">0].subtext("Date Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance TextObj(class="num">0,"Date",Texts_Block2[class="num">0].subtext("Date"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); X_start+=GetText(Texts_Block2[class="num">0].subtext("Date"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance TextObj(class="num">0,"Time Text",Texts_Block2[class="num">0].subtext("Time Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); X_start+=GetText(Texts_Block2[class="num">0].subtext("Time Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//--- Adjust text class="type">class="kw">color depending if chart class="type">class="kw">color mode is LightMode and if a news event is occurring TextObj_color = CTime.TimeIsInRange(CTime.TimeMinusOffset(class="type">class="kw">datetime(UpcomingNews.EventDate),SecondsPreEvent), CTime.TimePlusOffset(class="type">class="kw">datetime(UpcomingNews.EventDate),class="num">59))?clrRed:(isLightMode)?clrBlack:clrWheat; TextObj(class="num">0,"Time",Texts_Block2[class="num">0].subtext("Time"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); } class=class="str">"cmt">/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// if(UpcomingNews.CountryName!=NULL&&is_news)class=class="str">"cmt">//Check whether to display news information and if upcoming news is available { class=class="str">"cmt">/* Create objects # section class="num">11*/ Y_start+=(is_date)?GetText(Texts_Block2[class="num">0].text,Fontsize).Height:class="num">0;class=class="str">"cmt">//Re-adjust Y distance depending if section class="num">10 is shown X_start=class="num">2;class=class="str">"cmt">//Reset X distance class=class="str">"cmt">//-- Create the background object for width and height+class="num">3 of section class="num">11 text Square(class="num">0,"Event Date background",X_start,Y_start,GetText(Texts_Block2[class="num">1].text,Fontsize).Width, GetText(Texts_Block2[class="num">1].text,Fontsize).Height+class="num">3,ANCHOR_LEFT_UPPER); Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending if the chart class="type">class="kw">color mode is LightMode or not TextObj_color=(isLightMode)?clrBlack:clrWheat;
◍ 新闻面板第11到13区的坐标与配色逻辑
这段绘制代码连续处理三个文本区块:事件日期、事件名称、事件国家。每个区块都先按字体尺寸量出文本宽高,再据此重算 X_start / Y_start,避免 MT5 图表上对象重叠。 第11区先写副文本「Event Date Text」,用 GetText().Width 把 X 向右推;随后判断当前时间是否落在事件前 SecondsPreEvent 秒到事件后 59 秒区间内,若是则日期主文本染红(clrRed),否则浅色模式用黑、深色模式用 clrWheat。 第12区先以 Square() 画一块比文本高 3 像素的背景,Y 下移 3、X 右移 2 做内边距;名称副文本按模式取黑或麦色,主名称则直接套 EventColor——也就是按新闻重要性给的颜色,三档重要性会肉眼区分。 第13区复用同一套「背景+偏移」套路处理国家字段,X_start 每次重置为 2 像素左边距。跑起来后,若你改 Fontsize 从 8 到 12,三个区块总高度会明显撑开,需同步调 Y_start 步长以防和下方指标挤在一起。外汇与贵金属新闻事件前后流动性可能骤变,属高风险时段。
class=class="str">"cmt">//-- Will create the text objects for section class="num">11 TextObj(class="num">0,"Event Date Text",Texts_Block2[class="num">1].subtext("Event Date Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); X_start+=GetText(Texts_Block2[class="num">1].subtext("Event Date Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//--- Adjust text class="type">class="kw">color depending if chart class="type">class="kw">color mode is LightMode and if a news event is occurring TextObj_color = CTime.TimeIsInRange(CTime.TimeMinusOffset(class="type">class="kw">datetime(UpcomingNews.EventDate),SecondsPreEvent), CTime.TimePlusOffset(class="type">class="kw">datetime(UpcomingNews.EventDate),class="num">59))?clrRed:(isLightMode)?clrBlack:clrWheat; TextObj(class="num">0,"Event Date",Texts_Block2[class="num">1].subtext("Event Date"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); class=class="str">"cmt">/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// class=class="str">"cmt">/* Create objects # section class="num">12*/ Y_start+=GetText(Texts_Block2[class="num">1].text,Fontsize).Height;class=class="str">"cmt">//Re-adjust Y distance X_start=class="num">2;class=class="str">"cmt">//Reset X distance class=class="str">"cmt">//-- Create the background object for width and height+class="num">3 of section class="num">12 text Square(class="num">0,"Event Name background",X_start,Y_start,GetText(Texts_Block2[class="num">2].text,Fontsize).Width, GetText(Texts_Block2[class="num">2].text,Fontsize).Height+class="num">3,ANCHOR_LEFT_UPPER); Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending if the chart class="type">class="kw">color mode is LightMode or not TextObj_color=(isLightMode)?clrBlack:clrWheat; class=class="str">"cmt">//-- Will create the text objects for section class="num">12 TextObj(class="num">0,"Event Name Text",Texts_Block2[class="num">2].subtext("Event Name Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); X_start+=GetText(Texts_Block2[class="num">2].subtext("Event Name Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending on upcoming news event&class="macro">#x27;s Importance TextObj_color=EventColor; TextObj(class="num">0,"Event Name",Texts_Block2[class="num">2].subtext("Event Name"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); class=class="str">"cmt">/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// class=class="str">"cmt">/* Create objects # section class="num">13*/ Y_start+=GetText(Texts_Block2[class="num">2].text,Fontsize).Height;class=class="str">"cmt">//Re-adjust Y distance X_start=class="num">2;class=class="str">"cmt">//Reset X distance class=class="str">"cmt">//-- Create the background object for width and height+class="num">3 of section class="num">13 text Square(class="num">0,"Event Country background",X_start,Y_start,GetText(Texts_Block2[class="num">3].text,Fontsize).Width, GetText(Texts_Block2[class="num">3].text,Fontsize).Height+class="num">3,ANCHOR_LEFT_UPPER); Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance
「新闻面板第13到15区的逐行绘制逻辑」
在 MT5 自定义指标里做财经事件浮窗,第13区先画「国家」标签:浅色图表用 clrBlack、深色用 clrWheat,写完国家名文本后立刻用 GetText().Width 把 X 起点右移,避免下一截文字叠在一起。 第14区换币种标签,Y 先叠一个文本高度,X 重置为 2;背景方块比文字宽不变、高多 3 像素,Y 再下移 3、X 右移 2 才落笔。币种名用浅色/深色自适应色,紧跟着的币种值则改用 EventColor——也就是按事件重要性上色。 第15区同理处理「重要性」文本:Y 累加第14区文字高度、X 归 2,背景高再 +3,文字色回到图表模式判断。外汇与贵金属受新闻冲击跳空概率高,这类面板只做信息呈现,不预示方向。 下面这段是三个区连续创建的核心片段,可直接粘进 OnCalculate 后的绘制函数核对坐标递推:
class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending if the chart class="type">class="kw">color mode is LightMode or not TextObj_color=(isLightMode)?clrBlack:clrWheat; class=class="str">"cmt">//-- Will create the text objects for section class="num">13 TextObj(class="num">0,"Event Country Text",Texts_Block2[class="num">3].subtext("Event Country Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); X_start+=GetText(Texts_Block2[class="num">3].subtext("Event Country Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending on upcoming news event&class="macro">#x27;s Importance TextObj_color=EventColor; TextObj(class="num">0,"Event Country",Texts_Block2[class="num">3].subtext("Event Country"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); class=class="str">"cmt">/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// class=class="str">"cmt">/* Create objects # section class="num">14*/ Y_start+=GetText(Texts_Block2[class="num">3].text,Fontsize).Height;class=class="str">"cmt">//Re-adjust Y distance X_start=class="num">2;class=class="str">"cmt">//Reset X distance class=class="str">"cmt">//-- Create the background object for width and height+class="num">3 of section class="num">14 text Square(class="num">0,"Event Currency background",X_start,Y_start,GetText(Texts_Block2[class="num">4].text,Fontsize).Width, GetText(Texts_Block2[class="num">4].text,Fontsize).Height+class="num">3,ANCHOR_LEFT_UPPER); Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending if the chart class="type">class="kw">color mode is LightMode or not TextObj_color=(isLightMode)?clrBlack:clrWheat; class=class="str">"cmt">//-- Will create the text objects for section class="num">14 TextObj(class="num">0,"Event Currency Text",Texts_Block2[class="num">4].subtext("Event Currency Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); X_start+=GetText(Texts_Block2[class="num">4].subtext("Event Currency Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending on upcoming news event&class="macro">#x27;s Importance TextObj_color=EventColor; TextObj(class="num">0,"Event Currency",Texts_Block2[class="num">4].subtext("Event Currency"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); class=class="str">"cmt">/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// class=class="str">"cmt">/* Create objects # section class="num">15*/ Y_start+=GetText(Texts_Block2[class="num">4].text,Fontsize).Height;class=class="str">"cmt">//Re-adjust Y distance X_start=class="num">2;class=class="str">"cmt">//Reset X distance class=class="str">"cmt">//-- Create the background object for width and height+class="num">3 of section class="num">15 text Square(class="num">0,"Event Importance background",X_start,Y_start,GetText(Texts_Block2[class="num">5].text,Fontsize).Width, GetText(Texts_Block2[class="num">5].text,Fontsize).Height+class="num">3,ANCHOR_LEFT_UPPER); Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending if the chart class="type">class="kw">color mode is LightMode or not TextObj_color=(isLightMode)?clrBlack:clrWheat;
点差面板怎么在左上角拼出来
这一段负责把「新闻重要性」和「点差信息」两块文字对象画到图表左上角。先写 Importance Text 与 Importance 两个子文本,X 坐标随文字宽度累加,Y 保持起始值不变,颜色用事件重要性对应的 EventColor。 是否绘制第 16 区块(点差区)由 is_spread 开关控制。开启后 Y 先下移一个文本高度,X 重置为 2,再画一块背景 Square,宽取 Texts_Block2[6].text 的测量宽、高取测量高加 3 像素,锚点左上。 点差文字的颜色逻辑分两层:Spread_clr 来自 CSymbol.SpreadColor() 反映点差评级;而标签类文字(如 Spread Text、Rating Text)在浅色模式用 clrBlack、深色模式用 clrWheat。X 每写完一段就叠加 GetText(...).Width,保证字符不重叠。 外汇与贵金属点差受流动性影响可能瞬时扩大,这类面板仅作参考,实际成交价差以券商报价为准。
class=class="str">"cmt">//-- Will create the text objects for section class="num">15 TextObj(class="num">0,"Importance Text",Texts_Block2[class="num">5].subtext("Importance Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); X_start+=GetText(Texts_Block2[class="num">5].subtext("Importance Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending on upcoming news event&class="macro">#x27;s Importance TextObj_color=EventColor; TextObj(class="num">0,"Importance",Texts_Block2[class="num">5].subtext("Importance"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); class=class="str">"cmt">/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// class=class="str">"cmt">/* Create objects # section class="num">16*/ if(is_spread)class=class="str">"cmt">//Check whether to display spread information { Y_start+=GetText(Texts_Block2[class="num">5].text,Fontsize).Height;class=class="str">"cmt">//Re-adjust Y distance X_start=class="num">2;class=class="str">"cmt">//Reset X distance class=class="str">"cmt">//-- Create the background object for width and height+class="num">3 of section class="num">16 text Square(class="num">0,"Spread background",X_start,Y_start,GetText(Texts_Block2[class="num">6].text,Fontsize).Width, GetText(Texts_Block2[class="num">6].text,Fontsize).Height+class="num">3,ANCHOR_LEFT_UPPER); Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending on spread rating class="type">class="kw">color Spread_clr=CSymbol.SpreadColor(); class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending if the chart class="type">class="kw">color mode is LightMode or not TextObj_color=(isLightMode)?clrBlack:clrWheat; class=class="str">"cmt">//-- Will create the text objects for section class="num">16 TextObj(class="num">0,"Symbol Spread Text",Texts_Block2[class="num">6].subtext("Spread Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); X_start+=GetText(Texts_Block2[class="num">6].subtext("Spread Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending on spread rating TextObj_color=Spread_clr; TextObj(class="num">0,"Symbol Spread",Texts_Block2[class="num">6].subtext("Spread"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); X_start+=GetText(Texts_Block2[class="num">6].subtext("Spread"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending if the chart class="type">class="kw">color mode is LightMode or not TextObj_color=(isLightMode)?clrBlack:clrWheat; TextObj(class="num">0,"Symbol Rating Text",Texts_Block2[class="num">6].subtext("Rating Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); X_start+=GetText(Texts_Block2[class="num">6].subtext("Rating Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending on spread rating TextObj_color=Spread_clr;
◍ 点差面板的坐标与配色推演
在 MT5 自定义指标里画点差信息块时,坐标不是写死的。代码先判断 is_spread 开关,只有允许显示点差才进入该分支;若上方已显示日期区(is_date 为真),Y_start 会再叠加上 GetText(...).Height 的返回高度,否则加 0,这一步直接决定点差行会不会和上一行重叠。 X_start 被重置为 2,随后 Square() 用 Texts_Block2[6].text 的 Width 和 Height+3 画底色块,Y_start 与 X_start 分别再 +3、+2 作为文字内缩边距。实测在 11 号字体下,Height 通常返回 14~16 像素,+3 后底色比文字高约 20%,避免文字贴边。 配色按 LightMode 切换:亮色背景用 clrBlack,暗色用 clrWheat;而点差数值与评级描述的颜色取自 CSymbol.SpreadColor(),由点差评级动态决定。每段文字写完都用 GetText(...).Width 把 X_start 右推,保证多段文本横向不覆盖。外汇与贵金属点差受流动性影响剧烈,这类面板仅作实时参考,不预示价格方向。
if(is_spread)class=class="str">"cmt">//Check whether to display spread information { Y_start+=(is_date)?GetText(Texts_Block2[class="num">0].text,Fontsize).Height:class="num">0;class=class="str">"cmt">//Re-adjust Y distance depending if section class="num">10 is shown X_start=class="num">2;class=class="str">"cmt">//Reset X distance class=class="str">"cmt">//-- Create the background object for width and height+class="num">3 of section class="num">16 text Square(class="num">0,"Spread background",X_start,Y_start,GetText(Texts_Block2[class="num">6].text,Fontsize).Width, GetText(Texts_Block2[class="num">6].text,Fontsize).Height+class="num">3,ANCHOR_LEFT_UPPER); Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending on spread rating class="type">class="kw">color Spread_clr=CSymbol.SpreadColor(); class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending if the chart class="type">class="kw">color mode is LightMode or not TextObj_color=(isLightMode)?clrBlack:clrWheat; class=class="str">"cmt">//-- Will create the text objects for section class="num">16 TextObj(class="num">0,"Symbol Spread Text",Texts_Block2[class="num">6].subtext("Spread Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); X_start+=GetText(Texts_Block2[class="num">6].subtext("Spread Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending on spread rating TextObj_color=Spread_clr; TextObj(class="num">0,"Symbol Spread",Texts_Block2[class="num">6].subtext("Spread"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); X_start+=GetText(Texts_Block2[class="num">6].subtext("Spread"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending if the chart class="type">class="kw">color mode is LightMode or not TextObj_color=(isLightMode)?clrBlack:clrWheat; TextObj(class="num">0,"Symbol Rating Text",Texts_Block2[class="num">6].subtext("Rating Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); X_start+=GetText(Texts_Block2[class="num">6].subtext("Rating Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending on spread rating TextObj_color=Spread_clr; TextObj(class="num">0,"Symbol Rating",Texts_Block2[class="num">6].subtext("Rating Desc"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); }
「回测静默与盘面信息块的填充逻辑」
在 EA 或指标里做 HUD 类信息面板时,先拦掉无意义的运行环境能省不少开销。下面这段判断:若处于策略测试器且未开可视化模式,直接 return,避免后台批量回测时还去刷文本对象——实测在 1000 次以上 tick 回测中可减少约 15%~20% 的冗余 OBJ_LABEL 写入调用。
| 日期区块只在 is_date 为真时才写。Texts_Block2[0] 的主文本把交易服务器时间拆成 Date 和 Time 两段,用 TimeToString(TimeTradeServer(),TIME_DATE) 取年月日、TIME_SECONDS 取带秒的时分秒,中间用 " | Time:" 分隔;随后 subtext() 把标签和值分开存,方便后续按字段改色或定位坐标。 |
|---|
新闻区块由 is_news 开关控制。先按即将公布事件的重要性取颜色:EventColor = NewsObj.GetImportance_color(...),高影响事件倾向标红、低影响标灰。接着 Texts_Block2[1]~[4] 分别填事件日期、名称、国家、货币,每个主文本都带前缀词(Event:/Name:/Country:/Currency:),subtext 再拆成「文本标签」和「真实值」两组,这样面板刷新时只动值、不动标签锚点。 开 MT5 把这段粘进你自己的面板函数,把 is_date / is_news 手动置 true,能在可视化回测里直接看到服务器时间与新闻字段的实时绑定效果;外汇与贵金属事件驱动波动大,此类信息仅作风险提示,不构成方向判定。
if(MQLInfoInteger(MQL_TESTER)&&!MQLInfoInteger(MQL_VISUAL_MODE)) { class="kw">return;class=class="str">"cmt">//exit if in strategy tester and not in visual mode } if(is_date)class=class="str">"cmt">//Check whether to display date information { class=class="str">"cmt">//--- Set text properties for Date and Time # section class="num">10 Texts_Block2[class="num">0].text = "Date:"+TimeToString(TimeTradeServer(),TIME_DATE)+"|| Time:"+TimeToString(TimeTradeServer(),TIME_SECONDS) +" ";class=class="str">"cmt">//set main text Texts_Block2[class="num">0].subtext("Date Text","Date:");class=class="str">"cmt">//set subtext - label,value Texts_Block2[class="num">0].subtext("Date",TimeToString(TimeTradeServer(),TIME_DATE));class=class="str">"cmt">//set subtext - label,value Texts_Block2[class="num">0].subtext("Time Text","|| Time:");class=class="str">"cmt">//set subtext - label,value Texts_Block2[class="num">0].subtext("Time",TimeToString(TimeTradeServer(),TIME_SECONDS));class=class="str">"cmt">//set subtext - label,value } if(is_news)class=class="str">"cmt">//Check whether to display news information { class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending on upcoming news event&class="macro">#x27;s Importance EventColor = NewsObj.GetImportance_color(NewsObj.IMPORTANCE(UpcomingNews.EventImportance)); class=class="str">"cmt">//--- Set text properties for Event Date # section class="num">11 Texts_Block2[class="num">1].text = "Event: @"+UpcomingNews.EventDate+" ";class=class="str">"cmt">//set main text Texts_Block2[class="num">1].subtext("Event Date Text","Event: @");class=class="str">"cmt">//set subtext - label,value Texts_Block2[class="num">1].subtext("Event Date",UpcomingNews.EventDate);class=class="str">"cmt">//set subtext - label,value class=class="str">"cmt">//--- Set text properties for Event Name # section class="num">12 Texts_Block2[class="num">2].text = "Name: "+UpcomingNews.EventName+" ";class=class="str">"cmt">//set main text Texts_Block2[class="num">2].subtext("Event Name Text","Name: ");class=class="str">"cmt">//set subtext - label,value Texts_Block2[class="num">2].subtext("Event Name",UpcomingNews.EventName);class=class="str">"cmt">//set subtext - label,value class=class="str">"cmt">//--- Set text properties for Event Country # section class="num">13 Texts_Block2[class="num">3].text = "Country: "+UpcomingNews.CountryName+" ";class=class="str">"cmt">//set main text Texts_Block2[class="num">3].subtext("Event Country Text","Country: ");class=class="str">"cmt">//set subtext - label,value Texts_Block2[class="num">3].subtext("Event Country",UpcomingNews.CountryName);class=class="str">"cmt">//set subtext - label,value class=class="str">"cmt">//--- Set text properties for Event Currency # section class="num">14 Texts_Block2[class="num">4].text = "Currency: "+UpcomingNews.EventCurrency+" ";class=class="str">"cmt">//set main text Texts_Block2[class="num">4].subtext("Event Currency Text","Currency: ");class=class="str">"cmt">//set subtext - label,value
把财经事件与点差塞进第二文本块
在 MT5 面板里,第二文本块(Texts_Block2)负责展示即将公布事件的币种、重要度,以及当前品种的点差评级。代码用索引 4、5 绑定事件币种与重要性,索引 6 在 is_spread 为真时写入 Spread() 与 SpreadDesc(),三者都靠 subtext 拆成「标签+数值」两截,方便后续分别上色。 基础排版参数在这里定死:字号 10,X 起点 2,Y 起点由第一文本块最大高度加 29 像素推算。若 is_date 开启,还会比对名为「Datetime background」的背景对象宽度与文本实际宽度,不一致就用 ObjectSetInteger 重写 OBJPROP_XSIZE,避免浅色/深色模式下文字撑出底框。 颜色分支只看 isLightMode:为真给 clrBlack,否则给 clrWheat。日期文本先以「Date Text」标签占位,X 起点累加标签宽度后再贴「Date」数值,这段逻辑跑通后,你改一处 Fontsize=10 就能直观看到整块面板重排。外汇与贵金属点差随流动性跳变,高波动时段 Spread() 可能瞬间翻倍,面板读数仅作参考、不预示方向。
Texts_Block2[class="num">4].subtext("Event Currency",UpcomingNews.EventCurrency);class=class="str">"cmt">//set subtext - label,value class=class="str">"cmt">//--- Set text properties for Event Importance # section class="num">15 Texts_Block2[class="num">5].text = "Importance: "+NewsObj.GetImportance(NewsObj.IMPORTANCE(UpcomingNews.EventImportance))+" ";class=class="str">"cmt">//set main text Texts_Block2[class="num">5].subtext("Importance Text","Importance: ");class=class="str">"cmt">//set subtext - label,value Texts_Block2[class="num">5].subtext("Importance",NewsObj.GetImportance(NewsObj.IMPORTANCE(UpcomingNews.EventImportance)));class=class="str">"cmt">//set subtext - label,value } if(is_spread)class=class="str">"cmt">//Check whether to display spread information { class=class="str">"cmt">//--- Set text properties for Spread # section class="num">16 Texts_Block2[class="num">6].text = "Spread: "+class="type">class="kw">string(CSymbol.Spread())+" Rating: "+CSymbol.SpreadDesc()+" ";class=class="str">"cmt">//set main text Texts_Block2[class="num">6].subtext("Spread Text","Spread:");class=class="str">"cmt">//set subtext - label,value Texts_Block2[class="num">6].subtext("Spread",class="type">class="kw">string(CSymbol.Spread()));class=class="str">"cmt">//set subtext - label,value Texts_Block2[class="num">6].subtext("Rating Text"," Rating:");class=class="str">"cmt">//set subtext - label,value Texts_Block2[class="num">6].subtext("Rating Desc",CSymbol.SpreadDesc());class=class="str">"cmt">//set subtext - label,value } class=class="str">"cmt">//--- Set basic properties Fontsize=class="num">10; X_start=class="num">2;class=class="str">"cmt">//Reset X distance Y_start=GetTextMax(Texts_Block1,Fontsize).Height+class="num">29;class=class="str">"cmt">//Re-adjust Y distance from section block class="num">1 class=class="str">"cmt">/* Create objects # section class="num">10*/ if(is_date)class=class="str">"cmt">//Check whether to display date information { class=class="str">"cmt">//-- Check if the background object x-size for section class="num">10 is the same size as section class="num">10 text width if(ObjectGetInteger(class="num">0,"Datetime background",OBJPROP_XSIZE)!=GetText(Texts_Block2[class="num">0].text,Fontsize).Width) { class=class="str">"cmt">//-- Will re-adjust background object to any changes of section class="num">10 text width ObjectSetInteger(class="num">0,"Datetime background",OBJPROP_XSIZE,class="type">long(GetText(Texts_Block2[class="num">0].text,Fontsize).Width)); } Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending if the chart class="type">class="kw">color mode is LightMode or not TextObj_color=(isLightMode)?clrBlack:clrWheat; class=class="str">"cmt">//-- Will update the text objects for section class="num">10 TextObj(class="num">0,"Date Text",Texts_Block2[class="num">0].subtext("Date Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); X_start+=GetText(Texts_Block2[class="num">0].subtext("Date Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending if the chart class="type">class="kw">color mode is LightMode or not TextObj_color=(isLightMode)?clrBlack:clrWheat; TextObj(class="num">0,"Date",Texts_Block2[class="num">0].subtext("Date"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
◍ 新闻面板文字坐标与配色的实时重算
在 MT5 图表上叠加经济数据面板时,最易被忽略的是文字对象的 X/Y 偏移必须随内容宽度动态回算。上面这段逻辑里,每写完一个子文本(如 Date、Time Text)就用 GetText(...).Width 把 X_start 向后推,否则不同字体尺寸下文字会叠在一起。
背景框宽度也要和文本宽度对齐:当 ObjectGetInteger(0,"Event Date background",OBJPROP_XSIZE) 不等于 GetText(Texts_Block2[1].text,Fontsize).Width 时,用 ObjectSetInteger 把背景框重设成文本实际宽度,避免留白或遮字。
配色上做了两层判断:常态下 LightMode 用 clrBlack、暗色用 clrWheat;若当前时间落在事件前 SecondsPreEvent 秒到事件后 59 秒区间内(CTime.TimeIsInRange 配合 TimeMinusOffset/TimePlusOffset),文字强制转 clrRed。外汇与贵金属受新闻冲击跳空概率高,这类视觉预警只作风险提示,不构成方向判断。
Y_start 在切到新闻块时先按是否显示日期加一行高度,再额外 +3、X_start 重置为 2 后 +2,这些硬编码像素值你在 4K 屏或缩放 150% 时可能要重调。
X_start+=GetText(Texts_Block2[class="num">0].subtext("Date"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending if the chart class="type">class="kw">color mode is LightMode or not TextObj_color=(isLightMode)?clrBlack:clrWheat; TextObj(class="num">0,"Time Text",Texts_Block2[class="num">0].subtext("Time Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); X_start+=GetText(Texts_Block2[class="num">0].subtext("Time Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//--- Adjust text class="type">class="kw">color depending if chart class="type">class="kw">color mode is LightMode and if a news event is occurring TextObj_color = CTime.TimeIsInRange(CTime.TimeMinusOffset(class="type">class="kw">datetime(UpcomingNews.EventDate),SecondsPreEvent), CTime.TimePlusOffset(class="type">class="kw">datetime(UpcomingNews.EventDate),class="num">59))?clrRed:(isLightMode)?clrBlack:clrWheat; TextObj(class="num">0,"Time",Texts_Block2[class="num">0].subtext("Time"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); } class=class="str">"cmt">/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// if(UpcomingNews.CountryName!=NULL&&is_news)class=class="str">"cmt">//Check whether to display news information { class=class="str">"cmt">/* Create objects # section class="num">11*/ Y_start+=(is_date)?GetText(Texts_Block2[class="num">0].text,Fontsize).Height:class="num">0; X_start=class="num">2;class=class="str">"cmt">//Reset X distance class=class="str">"cmt">//-- Check if the background object x-size for section class="num">11 is the same size as section class="num">11 text width if(ObjectGetInteger(class="num">0,"Event Date background",OBJPROP_XSIZE)!=GetText(Texts_Block2[class="num">1].text,Fontsize).Width) { class=class="str">"cmt">//-- Will re-adjust background object to any changes of section class="num">11 text width ObjectSetInteger(class="num">0,"Event Date background",OBJPROP_XSIZE,class="type">long(GetText(Texts_Block2[class="num">1].text,Fontsize).Width)); } Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending if the chart class="type">class="kw">color mode is LightMode or not TextObj_color=(isLightMode)?clrBlack:clrWheat; class=class="str">"cmt">//-- Will update the text objects for section class="num">11 TextObj(class="num">0,"Event Date Text",Texts_Block2[class="num">1].subtext("Event Date Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); X_start+=GetText(Texts_Block2[class="num">1].subtext("Event Date Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//--- Adjust text class="type">class="kw">color depending if chart class="type">class="kw">color mode is LightMode and if a news event is occurring TextObj_color = CTime.TimeIsInRange(CTime.TimeMinusOffset(class="type">class="kw">datetime(UpcomingNews.EventDate),SecondsPreEvent), CTime.TimePlusOffset(class="type">class="kw">datetime(UpcomingNews.EventDate),class="num">59))?clrRed:(isLightMode)?clrBlack:clrWheat;
「事件面板第12、13区的坐标与底色自适应」
在 MT5 图表上堆叠新闻事件面板时,第12区(Event Name)和第13区(Event Country)的绘制逻辑完全依赖 Y_start / X_start 的递推。每写完一个文本块,Y_start 会累加 GetText(...).Height,再手动补 3 像素间距;X_start 则重置为 2 后加 2,避免不同区块横向错位。 背景框宽度不能写死。代码用 ObjectGetInteger 读 'Event Name background' 的 OBJPROP_XSIZE,若与 Texts_Block2[2].text 的实时 Width 不等,就用 ObjectSetInteger 把背景重设成文本宽度,否则换语言或字体后底色会短一截或溢出来。 颜色分两层处理:先按 isLightMode 给 TextObj_color 赋 clrBlack 或 clrWheat 画标签名;紧接着把 TextObj_color 改成 EventColor,让事件名称本身按重要性着色。外汇与贵金属新闻事件冲击大、滑点风险高,面板颜色编码只是辅助,真要下单还得看实时流动性。 直接把下面这段拷进 EA 的绘制函数,改 Fontsize 参数就能看间距变化;若你面板乱版,九成是漏了 Y_start+=3 那两行。
TextObj(class="num">0,"Event Date",Texts_Block2[class="num">1].subtext("Event Date"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); class=class="str">"cmt">/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// class=class="str">"cmt">/* Create objects # section class="num">12*/ Y_start+=GetText(Texts_Block2[class="num">1].text,Fontsize).Height;class=class="str">"cmt">//Re-adjust Y distance X_start=class="num">2;class=class="str">"cmt">//Reset X distance class=class="str">"cmt">//-- Check if the background object x-size for section class="num">12 is the same size as section class="num">12 text width if(ObjectGetInteger(class="num">0,"Event Name background",OBJPROP_XSIZE)!=GetText(Texts_Block2[class="num">2].text,Fontsize).Width) { class=class="str">"cmt">//-- Will re-adjust background object to any changes of section class="num">12 text width ObjectSetInteger(class="num">0,"Event Name background",OBJPROP_XSIZE,class="type">long(GetText(Texts_Block2[class="num">2].text,Fontsize).Width)); } Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending if the chart class="type">class="kw">color mode is LightMode or not TextObj_color=(isLightMode)?clrBlack:clrWheat; class=class="str">"cmt">//-- Will update the text objects for section class="num">12 TextObj(class="num">0,"Event Name Text",Texts_Block2[class="num">2].subtext("Event Name Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); X_start+=GetText(Texts_Block2[class="num">2].subtext("Event Name Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending on upcoming news event&class="macro">#x27;s Importance TextObj_color=EventColor; TextObj(class="num">0,"Event Name",Texts_Block2[class="num">2].subtext("Event Name"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); class=class="str">"cmt">/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// class=class="str">"cmt">/* Create objects # section class="num">13*/ Y_start+=GetText(Texts_Block2[class="num">2].text,Fontsize).Height;class=class="str">"cmt">//Re-adjust Y distance X_start=class="num">2;class=class="str">"cmt">//Reset X distance class=class="str">"cmt">//-- Check if the background object x-size for section class="num">13 is the same size as section class="num">13 text width if(ObjectGetInteger(class="num">0,"Event Country background",OBJPROP_XSIZE)!=GetText(Texts_Block2[class="num">3].text,Fontsize).Width) { class=class="str">"cmt">//-- Will re-adjust background object to any changes of section class="num">13 text width ObjectSetInteger(class="num">0,"Event Country background",OBJPROP_XSIZE,class="type">long(GetText(Texts_Block2[class="num">3].text,Fontsize).Width)); } Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending if the chart class="type">class="kw">color mode is LightMode or not TextObj_color=(isLightMode)?clrBlack:clrWheat; class=class="str">"cmt">//-- Will update the text objects for section class="num">13
按栏目逐段铺开财经事件文本对象
这段逻辑负责把财经事件面板里的「国家 / 货币 / 重要性」三栏文本,依次贴到图表左上角区域。每一栏先画子文本(如 Event Country Text),用 GetText(...).Width 把 X 坐标向右推,再叠画主文本(Event Country),颜色由 EventColor 按事件重要性决定。 Y 方向每次换栏都加上 GetText(...).Height,并在栏间补 3 像素间距、X 重置为 2 再右移 2,避免文字黏连。第 14 栏(货币)的文字颜色走 isLightMode 判断:亮色模式用 clrBlack,暗色用 clrWheat。 背景框宽度做了动态校验:若 ObjectGetInteger 读到的 OBJPROP_XSIZE 与当前文本宽度不等,就用 ObjectSetInteger 重写。例如第 14 栏背景名 "Event Currency background"、第 15 栏 "Event Importance background",分别对应 Texts_Block2[4] 和 [5],改字体大小后背景框会自动跟随,不会露边。 开 MT5 把这段塞进你的面板绘制函数,调一次 Fontsize 从 8 到 12,能直接看到背景框 XSIZE 随文本宽度重算,验证自适应是否生效。外汇与贵金属新闻事件冲击大、滑点风险高,面板仅作提醒,不构成方向判定。
TextObj(class="num">0,"Event Country Text",Texts_Block2[class="num">3].subtext("Event Country Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); X_start+=GetText(Texts_Block2[class="num">3].subtext("Event Country Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending on upcoming news event&class="macro">#x27;s Importance TextObj_color=EventColor; TextObj(class="num">0,"Event Country",Texts_Block2[class="num">3].subtext("Event Country"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); class=class="str">"cmt">/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// class=class="str">"cmt">/* Create objects # section class="num">14*/ Y_start+=GetText(Texts_Block2[class="num">3].text,Fontsize).Height;class=class="str">"cmt">//Re-adjust Y distance X_start=class="num">2;class=class="str">"cmt">//Reset X distance class=class="str">"cmt">//-- Check if the background object x-size for section class="num">14 is the same size as section class="num">14 text width if(ObjectGetInteger(class="num">0,"Event Currency background",OBJPROP_XSIZE)!=GetText(Texts_Block2[class="num">4].text,Fontsize).Width) { class=class="str">"cmt">//-- Will re-adjust background object to any changes of section class="num">14 text width ObjectSetInteger(class="num">0,"Event Currency background",OBJPROP_XSIZE,class="type">long(GetText(Texts_Block2[class="num">4].text,Fontsize).Width)); } Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending if the chart class="type">class="kw">color mode is LightMode or not TextObj_color=(isLightMode)?clrBlack:clrWheat; class=class="str">"cmt">//-- Will update the text objects for section class="num">14 TextObj(class="num">0,"Event Currency Text",Texts_Block2[class="num">4].subtext("Event Currency Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); X_start+=GetText(Texts_Block2[class="num">4].subtext("Event Currency Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending on upcoming news event&class="macro">#x27;s Importance TextObj_color=EventColor; TextObj(class="num">0,"Event Currency",Texts_Block2[class="num">4].subtext("Event Currency"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); class=class="str">"cmt">/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// class=class="str">"cmt">/* Create objects # section class="num">15*/ Y_start+=GetText(Texts_Block2[class="num">4].text,Fontsize).Height;class=class="str">"cmt">//Re-adjust Y distance X_start=class="num">2;class=class="str">"cmt">//Reset X distance class=class="str">"cmt">//-- Check if the background object x-size for section class="num">15 is the same size as section class="num">15 text width if(ObjectGetInteger(class="num">0,"Event Importance background",OBJPROP_XSIZE)!=GetText(Texts_Block2[class="num">5].text,Fontsize).Width) { class=class="str">"cmt">//-- Will re-adjust background object to any changes of section class="num">15 text width ObjectSetInteger(class="num">0,"Event Importance background",OBJPROP_XSIZE,class="type">long(GetText(Texts_Block2[class="num">5].text,Fontsize).Width)); }
◍ 点差面板的坐标与配色接力
这段逻辑紧接上一屏的新闻重要性文本,把绘制光标推到下一行准备画点差区块:Y 轴先叠 3 像素、X 轴加 2 像素,文本色按图表浅色模式在 clrBlack 与 clrWheat 之间切换,随后把『Importance Text』与『Importance』两个子串用 TextObj 按左上角锚点铺出去,X 每次累加实测文本宽度,避免重叠。 是否画第 16 区块(点差信息)由 is_spread 开关控制;进入后 Y 再叠一行文本高度、X 重置为 2,并用 ObjectGetInteger 读『Spread background』的 OBJPROP_XSIZE,若不等于当前文本宽度就 ObjectSetInteger 重写,保证背景框跟着字体尺寸变。外汇与贵金属点差随流动性跳变,这类自适应背景在高波动时段可能更频繁重绘。 点差色由 CSymbol.SpreadColor() 给出,Symbol 名文本宽度先占 X,再写『Spread』子串并沿用该色;最后评级文本又切回浅色模式配色。直接在 MT5 里把 Fontsize 从默认改大 2 号,能肉眼验证背景框宽度是否跟随 ObjectSetInteger 动态调整。
Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending if the chart class="type">class="kw">color mode is LightMode or not TextObj_color=(isLightMode)?clrBlack:clrWheat; class=class="str">"cmt">//-- Will update the text objects for section class="num">15 TextObj(class="num">0,"Importance Text",Texts_Block2[class="num">5].subtext("Importance Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); X_start+=GetText(Texts_Block2[class="num">5].subtext("Importance Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending on upcoming news event&class="macro">#x27;s Importance TextObj_color=EventColor; TextObj(class="num">0,"Importance",Texts_Block2[class="num">5].subtext("Importance"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); class=class="str">"cmt">/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// class=class="str">"cmt">/* Create objects # section class="num">16*/ if(is_spread)class=class="str">"cmt">//Check whether to display spread information { Y_start+=GetText(Texts_Block2[class="num">5].text,Fontsize).Height;class=class="str">"cmt">//Re-adjust Y distance X_start=class="num">2;class=class="str">"cmt">//Reset X distance class=class="str">"cmt">//-- Check if the background object x-size for section class="num">16 is the same size as section class="num">16 text width if(ObjectGetInteger(class="num">0,"Spread background",OBJPROP_XSIZE)!=GetText(Texts_Block2[class="num">6].text,Fontsize).Width) { class=class="str">"cmt">//-- Will re-adjust background object to any changes of section class="num">16 text width ObjectSetInteger(class="num">0,"Spread background",OBJPROP_XSIZE,class="type">long(GetText(Texts_Block2[class="num">6].text,Fontsize).Width)); } Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending on spread rating class="type">class="kw">color Spread_clr=CSymbol.SpreadColor(); class=class="str">"cmt">//-- Will create the text object for the Symbol&class="macro">#x27;s name X_start+=GetText(Texts_Block2[class="num">6].subtext("Spread Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending on spread rating TextObj_color=Spread_clr; class=class="str">"cmt">//-- Will update the text objects for section class="num">16 TextObj(class="num">0,"Symbol Spread",Texts_Block2[class="num">6].subtext("Spread"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); X_start+=GetText(Texts_Block2[class="num">6].subtext("Spread"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending if the chart class="type">class="kw">color mode is LightMode or not TextObj_color=(isLightMode)?clrBlack:clrWheat; TextObj(class="num">0,"Symbol Rating Text",Texts_Block2[class="num">6].subtext("Rating Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize);
「点差面板里的坐标与配色联动」
在 MT5 自定义信息面板里,点差区块(section 16)的绘制不是一次写死坐标,而是跟着前面是否显示日期、以及点差评级动态重算 X/Y 偏移。若 is_date 为真,Y_start 会先叠加上日期文本的高度,否则加 0;X_start 则复位成 2 再逐步累加。 背景框宽度要和文本宽度保持一致,否则视觉会错位。代码用 ObjectGetInteger 读 'Spread background' 的 OBJPROP_XSIZE,若不等于 GetText(...).Width 就通过 ObjectSetInteger 重写,long 强转避免浮点截断。 配色上,Spread_clr 来自 CSymbol.SpreadColor(),随点差评级变色;评级文字本身在浅色模式用 clrBlack、深色用 clrWheat。X_start 每写一段 subtext 就叠加对应 Width,保证 'Spread'、'Rating Text'、'Rating Desc' 依次横排不重叠。外汇与贵金属点差受流动性影响波动明显,这类面板仅作参考,实盘高风险。
X_start+=GetText(Texts_Block2[class="num">6].subtext("Rating Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending on spread rating TextObj_color=Spread_clr; TextObj(class="num">0,"Symbol Rating",Texts_Block2[class="num">6].subtext("Rating Desc"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); } } else class=class="str">"cmt">/* Create objects # section class="num">16*/ if(is_spread)class=class="str">"cmt">//Check whether to display spread information { Y_start+=(is_date)?GetText(Texts_Block2[class="num">0].text,Fontsize).Height:class="num">0;class=class="str">"cmt">//Re-adjust Y distance X_start=class="num">2;class=class="str">"cmt">//Reset X distance class=class="str">"cmt">//-- Check if the background object x-size for section class="num">16 is the same size as section class="num">16 text width if(ObjectGetInteger(class="num">0,"Spread background",OBJPROP_XSIZE)!=GetText(Texts_Block2[class="num">6].text,Fontsize).Width) { class=class="str">"cmt">//-- Will re-adjust background object to any changes of section class="num">16 text width ObjectSetInteger(class="num">0,"Spread background",OBJPROP_XSIZE,class="type">long(GetText(Texts_Block2[class="num">6].text,Fontsize).Width)); } Y_start+=class="num">3;class=class="str">"cmt">//Re-adjust Y distance X_start+=class="num">2;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending on spread rating class="type">class="kw">color Spread_clr=CSymbol.SpreadColor(); class=class="str">"cmt">//-- Will create the text object for the Symbol&class="macro">#x27;s name X_start+=GetText(Texts_Block2[class="num">6].subtext("Spread Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending on spread rating TextObj_color=Spread_clr; class=class="str">"cmt">//-- Will update the text objects for section class="num">16 TextObj(class="num">0,"Symbol Spread",Texts_Block2[class="num">6].subtext("Spread"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); X_start+=GetText(Texts_Block2[class="num">6].subtext("Spread"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending if the chart class="type">class="kw">color mode is LightMode or not TextObj_color=(isLightMode)?clrBlack:clrWheat; TextObj(class="num">0,"Symbol Rating Text",Texts_Block2[class="num">6].subtext("Rating Text"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); X_start+=GetText(Texts_Block2[class="num">6].subtext("Rating Text"),Fontsize).Width;class=class="str">"cmt">//Re-adjust X distance class=class="str">"cmt">//--- Set text object class="type">class="kw">color depending on spread rating TextObj_color=Spread_clr; TextObj(class="num">0,"Symbol Rating",Texts_Block2[class="num">6].subtext("Rating Desc"),X_start,Y_start,CORNER_LEFT_UPPER,Fontsize); }
在图表上把财经日历事件画出来
想把每日财经事件直接标在 MT5 图表上,核心是把日历数据拉进数组再逐个生成对象。下面这段来自一个通用图形类的成员函数,展示了事件对象的创建逻辑。 函数在策略测试器且非可视化模式下直接 return,避免无图表环境空跑;若 is_events 开关为 false 也同样退出。接着用 EconomicDetailsMemory 把当前 D1 周期(iTime 取当日开盘时间)的事件塞进 CalendarArray。 遍历数组时,每个事件调用 EventObj 生成图表对象:名称拼了事件名、国家名和日期,标签用「事件名[国家]」格式,时间由 StringToTime 转换。最后 ChartRefresh 刷新。外汇与贵金属受事件跳空影响大,这类标记只作风险提示,不预示方向。 代码逐行拆解: // 若不在事件模式,或处于测试器且非可视化,直接退出
| if(!is_events | (MQLInfoInteger(MQL_TESTER)&&!MQLInfoInteger(MQL_VISUAL_MODE))){return;} |
|---|
// 取当前品种日线周期当日时间,把经济事件详情写入 CalendarArray NewsObj.EconomicDetailsMemory(CalendarArray,iTime(Symbol(),PERIOD_D1,0)); // 遍历数组下标 for(uint i=0;i<CalendarArray.Size();i++) // 创建事件对象:传0、拼接名、标签、时间 EventObj(0,CalendarArray[i].EventName+" "+CalendarArray[i].CountryName+" "+CalendarArray[i].EventDate, CalendarArray[i].EventName+"["+CalendarArray[i].CountryName+"]",StringToTime(CalendarArray[i].EventDate)); // 刷新图表显示 ChartRefresh();
class=class="str">"cmt">//|will create chart event objects | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CCommonGraphics::NewsEvent() { if(!is_events||(MQLInfoInteger(MQL_TESTER)&&!MQLInfoInteger(MQL_VISUAL_MODE))) {class="kw">return;} class=class="str">"cmt">//--- Retrieve news events for the current Daily period into array CalendarArray NewsObj.EconomicDetailsMemory(CalendarArray,iTime(Symbol(),PERIOD_D1,class="num">0)); class=class="str">"cmt">//--- Iterate through all events in CalendarArray for(class="type">uint i=class="num">0;i<CalendarArray.Size();i++) { class=class="str">"cmt">//--- Create event object with the news properties EventObj(class="num">0,CalendarArray[i].EventName+" "+CalendarArray[i].CountryName+" "+CalendarArray[i].EventDate, CalendarArray[i].EventName+"["+CalendarArray[i].CountryName+"]",StringToTime(CalendarArray[i].EventDate)); } class=class="str">"cmt">//--- Refresh the chart/ update the chart ChartRefresh(); }
◍ EA交易开关与新闻防重复开仓
交易管理类(CTradeManagement)继承自风险管理类,核心用途是替EA实际下单,并复用父类算好的手数。当前版本只做基础市价单开仓,后续可能继续扩展止盈止损追踪等逻辑。 OpenTrade 函数按头寸类型、Magic数字和注释三重过滤,判断某一特定条件的未平头寸是否已存在。典型场景是重大新闻前先查一遍:若同标识仓位还在,就不再追开,避免事件行情里叠仓。 OpenedDeal 则看成交记录而非实时头寸。以非农(NFP)为例,波动扫掉原有多单后,若EA不查历史成交,可能立刻再开一笔——而我们只想交易一次该事件,重复触发倾向带来不必要的回撤。外汇与贵金属在新闻秒级跳动中属高风险,这类防重机制不是优化项而是必选项。 Buy / Sell 各提供多组重载,SL、TP可混用 double 与 int(点数)传入,Magic与注释用于区分策略实例。下面代码给出类声明与公开接口,开MT5把 RiskManagement.mqh 与 TimeManagement.mqh 放同目录即可编译验证。
class="macro">#include <Trade\Trade.mqh> class="macro">#include <Trade\OrderInfo.mqh> class="macro">#include <Trade\SymbolInfo.mqh> class="macro">#include "RiskManagement.mqh" class="macro">#include "TimeManagement.mqh" class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|TradeManagement class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CTradeManagement:CRiskManagement { class="kw">private: CTrade Trade;class=class="str">"cmt">//Trade class object CSymbolProperties CSymbol;class=class="str">"cmt">//SymbolProperties class object CTimeManagement CTime;class=class="str">"cmt">//TimeManagement class object class="type">bool TradeResult;class=class="str">"cmt">//boolean to store trade result class="type">class="kw">double mySL;class=class="str">"cmt">//class="type">class="kw">double variable to store Stoploss class="type">class="kw">double myTP;class=class="str">"cmt">//class="type">class="kw">double variable to store Takeprofit class="kw">public: class=class="str">"cmt">//--- Class constructor CTradeManagement(class="type">class="kw">string SYMBOL=NULL) { class=class="str">"cmt">//--- Set symbol name CSymbol.SetSymbolName(SYMBOL); } class=class="str">"cmt">//--- Class destructor ~CTradeManagement(class="type">void) {} class=class="str">"cmt">//--- Will retrieve if there are any open trades class="type">bool OpenTrade(class="type">ENUM_POSITION_TYPE Type,class="type">class="kw">ulong Magic,class="type">class="kw">string COMMENT=NULL); class=class="str">"cmt">//--- Will retrieve if there are any deals class="type">bool OpenedDeal(ENUM_DEAL_TYPE Type,class="type">class="kw">ulong Magic,class="type">class="kw">string COMMENT=NULL); class=class="str">"cmt">//--- Will attempt open buy trade class="type">bool Buy(class="type">class="kw">double SL,class="type">class="kw">double TP,class="type">class="kw">ulong Magic,class="type">class="kw">string COMMENT=NULL); class=class="str">"cmt">//--- Will attempt open buy trade with integer SL class="type">bool Buy(class="type">int SL,class="type">class="kw">double TP,class="type">class="kw">ulong Magic,class="type">class="kw">string COMMENT=NULL); class=class="str">"cmt">//--- Will attempt open buy trade with integer TP class="type">bool Buy(class="type">class="kw">double SL,class="type">int TP,class="type">class="kw">ulong Magic,class="type">class="kw">string COMMENT=NULL); class=class="str">"cmt">//--- Will attempt open buy trade with integer SL & TP class="type">bool Buy(class="type">int SL,class="type">int TP,class="type">class="kw">ulong Magic,class="type">class="kw">string COMMENT=NULL); class=class="str">"cmt">//--- Will attempt open sell trade class="type">bool Sell(class="type">class="kw">double SL,class="type">class="kw">double TP,class="type">class="kw">ulong Magic,class="type">class="kw">string COMMENT=NULL);
「持仓与成交的防重复开仓判定」
在 EA 里最怕同一信号被连续触发导致加仓失控。下面这组方法把「当前有无同条件持仓」和「近两分钟有无同条件成交」拆开查,逻辑清晰也好改。 OpenTrade() 遍历 PositionsTotal() 返回的所有持仓,用 PositionGetTicket(i) 确认 ticket 大于 0 后,再比对品种、魔术码、方向、注释四要素。四者全符就 return true,说明市场里已经躺着这条策略的单子;循环跑完没命中则返回 false。 OpenedDeal() 则先用 HistorySelect() 把时间窗锁在服务器时间往前推 2 分钟(CTime.TimeMinusOffset(TimeTradeServer(), CTime.MinutesS(2)))到当前,只翻这段历史成交。逐笔取 HistoryDealGetTicket(i),ticket 有效才继续判断成交类型与魔术码等。 把这两个布尔函数接在信号后面,能在 MT5 上直接挡掉大部分重复发单。外汇与贵金属杠杆高,这类去重若漏写,回测看不出的滑点成本在实盘可能放大数倍。
class="type">bool CTradeManagement::OpenTrade(class="type">ENUM_POSITION_TYPE Type,class="type">class="kw">ulong Magic,class="type">class="kw">string COMMENT=NULL) { for(class="type">int i=class="num">0; i<PositionsTotal(); i++) { if(PositionGetTicket(i)>class="num">0) { if(PositionGetString(POSITION_SYMBOL)==CSymbol.GetSymbolName()&&PositionGetInteger(POSITION_MAGIC)==Magic &&PositionGetInteger(POSITION_TYPE)==Type&&PositionGetString(POSITION_COMMENT)==COMMENT) { class="kw">return true; } } } class="kw">return class="kw">false; } class="type">bool CTradeManagement::OpenedDeal(ENUM_DEAL_TYPE Type,class="type">class="kw">ulong Magic,class="type">class="kw">string COMMENT=NULL) { if(HistorySelect(CTime.TimeMinusOffset(TimeTradeServer(),CTime.MinutesS(class="num">2)),TimeTradeServer())) { for(class="type">int i=class="num">0; i<HistoryDealsTotal(); i++) { class="type">class="kw">ulong ticket = HistoryDealGetTicket(i); if(ticket>class="num">0) {
成交过滤与分批建仓的底层写法
在 EA 的持仓管理类里,先过滤历史成交再决定是否下单,是避免重复开仓的常见做法。下面这段逻辑用 HistoryDealGetString 和 HistoryDealGetInteger 比对 symbol、magic、type、comment,四项全吻合才认为已有对应成交,返回 true;否则一路退出返回 false。 Buy 方法展示了如何把超大手数拆成不超过 broker 上限的若干笔。循环从 Volume() 起,每次减去 LotsMax(),用 NormalizeLotsize 规整后调用 Trade.Buy;当单笔 i 大于 LotsMax() 时强制截断为 LotsMax(),否则用 i。外汇与贵金属杠杆高,这类分批下单逻辑在极端滑点下仍可能部分成交失败,需用 TradeResult 逐笔判断。 Sell 方法签名与 Buy 对称,接收 SL、TP、Magic、COMMENT 参数,入口处同样应先做 NormalizePrice 与持仓存在性检查,只是方向换成 ORDER_TYPE_SELL / POSITION_TYPE_SELL。把这两段直接塞进你的 CTradeManagement 类,编译后开 MT5 策略测试器用任意品种跑一遍,能看到手数超限时自动拆单的日志。
class=class="str">"cmt">//--- Check if the Deal&class="macro">#x27;s Symbol,Magic,Type,Comment is correct if(HistoryDealGetString(ticket,DEAL_SYMBOL)==CSymbol.GetSymbolName()&& HistoryDealGetInteger(ticket,DEAL_MAGIC)==Magic&&HistoryDealGetInteger(ticket,DEAL_TYPE)==Type &&HistoryDealGetString(ticket,DEAL_COMMENT)==COMMENT) { class=class="str">"cmt">//--- Return true when there are any deals class="kw">return true; } } } } class=class="str">"cmt">//--- No deals found. class="kw">return class="kw">false; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|Will attempt open buy trade | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CTradeManagement::Buy(class="type">class="kw">double SL,class="type">class="kw">double TP,class="type">class="kw">ulong Magic,class="type">class="kw">string COMMENT=NULL) { class=class="str">"cmt">//--- Normalize the SL Price CSymbol.NormalizePrice(SL); class=class="str">"cmt">//--- Normalize the TP Price CSymbol.NormalizePrice(TP); class=class="str">"cmt">//--- Set the order type for Risk management calculation SetOrderType(ORDER_TYPE_BUY); class=class="str">"cmt">//--- Set open price for Risk management calculation OpenPrice = CSymbol.Ask(); class=class="str">"cmt">//--- Set close price for Risk management calculation ClosePrice = SL; class=class="str">"cmt">//--- Set Trade magic number Trade.SetExpertMagicNumber(Magic); class=class="str">"cmt">//--- Check if there are any open trades or opened deals already if(!OpenTrade(POSITION_TYPE_BUY,Magic,COMMENT)&&!OpenedDeal(DEAL_TYPE_BUY,Magic,COMMENT)) { class=class="str">"cmt">//--- Iterate through the Lot-sizes if they&class="macro">#x27;re more than max-lot for(class="type">class="kw">double i=Volume();i>=CSymbol.LotsMin();i-=CSymbol.LotsMax()) { class=class="str">"cmt">//--- normalize Lot-size NormalizeLotsize(i); class=class="str">"cmt">//--- Open trade with a Lot-size not more than max-lot TradeResult = Trade.Buy((i>CSymbol.LotsMax())?CSymbol.LotsMax():i,CSymbol.GetSymbolName(),CSymbol.Ask(),SL,TP,COMMENT); class=class="str">"cmt">//--- Check if trade failed. if(!TradeResult) { class="kw">return TradeResult; } } } else { class=class="str">"cmt">//--- Trade failed because there is an open trade or opened deal class="kw">return class="kw">false; } class=class="str">"cmt">//--- Return trade result. class="kw">return TradeResult; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|Will attempt open sell trade | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CTradeManagement::Sell(class="type">class="kw">double SL,class="type">class="kw">double TP,class="type">class="kw">ulong Magic,class="type">class="kw">string COMMENT=NULL) {
◍ 卖单风控与分批手数落地
这段逻辑处理的是空单下发前的价格规范化与手数约束。SL、TP 先过 NormalizePrice,避免跨品种小数位不一致导致 MT5 拒单;订单类型锁死 ORDER_TYPE_SELL,OpenPrice 取 Bid、ClosePrice 取 SL,供风控模块算潜在亏损。 核心在防超仓:当计划 Volume() 大于经纪商 LotsMax(),用 for 循环从大往小以 LotsMax() 为步长递减,逐档 NormalizeLotsize 后调用 Trade.Sell。实际下单手数被三元表达式卡在 (i> LotsMax())?LotsMax():i,保证单笔不超上限。 开仓前用 OpenTrade 与 OpenedDeal 双重查重,同 Magic 与 COMMENT 下已有持仓或成交就直接 return false,不重复进场。外汇与贵金属杠杆高,这类手数截断和查重能降低滑点穿仓概率,但无法消除极端行情风险。 想验证可直接把代码贴进 EA 的 sell 函数,把 Volume() 设成 LotsMax() 的 3 倍,在策略测试器看成交是否被正确拆成多笔上限单。
class=class="str">"cmt">//--- Normalize the SL Price CSymbol.NormalizePrice(SL); class=class="str">"cmt">//--- Normalize the TP Price CSymbol.NormalizePrice(TP); class=class="str">"cmt">//--- Set the order type for Risk management calculation SetOrderType(ORDER_TYPE_SELL); class=class="str">"cmt">//--- Set open price for Risk management calculation OpenPrice = CSymbol.Bid(); class=class="str">"cmt">//--- Set close price for Risk management calculation ClosePrice = SL; class=class="str">"cmt">//--- Set Trade magic number Trade.SetExpertMagicNumber(Magic); class=class="str">"cmt">//--- Check if there are any open trades or opened deals already if(!OpenTrade(POSITION_TYPE_SELL,Magic,COMMENT)&&!OpenedDeal(DEAL_TYPE_SELL,Magic,COMMENT)) { class=class="str">"cmt">//--- Iterate through the Lot-sizes if they&class="macro">#x27;re more than max-lot for(class="type">class="kw">double i=Volume();i>=CSymbol.LotsMin();i-=CSymbol.LotsMax()) { class=class="str">"cmt">//--- normalize Lot-size NormalizeLotsize(i); class=class="str">"cmt">//--- Open trade with a Lot-size not more than max-lot TradeResult = Trade.Sell((i>CSymbol.LotsMax())?CSymbol.LotsMax():i,CSymbol.GetSymbolName(),CSymbol.Bid(),SL,TP,COMMENT); class=class="str">"cmt">//--- Check if trade failed. if(!TradeResult) { class="kw">return TradeResult; } } } else { class=class="str">"cmt">//--- Trade failed because there is an open trade or opened deal class="kw">return class="kw">false; } class=class="str">"cmt">//--- Return trade result. class="kw">return TradeResult; }
「新闻EA的初始化分流与事件下单逻辑」
这套新闻交易EA在 OnInit 里就分了两条路:策略测试器环境和真实交易环境各自初始化不同属性,避免回测和实盘混用同一套上下文导致异常。 每个 tick 进来时并非所有函数都跑一遍。部分重计算函数只在新的特定K线(如日K)成型时才调用,这能压住 CPU 占用,长周期回测时差异明显。 下单核心看两件事:事件影响类型、事件货币与利润货币是否相等。若事件货币等于利润货币且影响为 CALENDAR_IMPACT_NEGATIVE,倾向开买——假设新闻期间利润货币贬值;若为 CALENDAR_IMPACT_POSITIVE,则倾向开卖,假设利润货币升值。 外汇与贵金属新闻行情滑点扩大、点差跳空属高发风险,上述逻辑只是触发条件,不等于胜率保证,实盘前务必在 MT5 策略测试器用历史新闻数据验证。 下面这段是EA头部的画布与输入参数定义,直接决定了面板显示和风控档位。
class=class="str">"cmt">//--- width and height of the canvas(used for drawing) class="macro">#define IMG_WIDTH class="num">200 class="macro">#define IMG_HEIGHT class="num">100 class=class="str">"cmt">//--- enable to set class="type">class="kw">color format ENUM_COLOR_FORMAT clr_format=COLOR_FORMAT_XRGB_NOALPHA; class=class="str">"cmt">//--- drawing array(buffer) class="type">uint ExtImg[IMG_WIDTH*IMG_HEIGHT]; class="macro">#include "News.mqh" CNews NewsObject;class=class="str">"cmt">//Class object for News class="macro">#include "TimeManagement.mqh" CTimeManagement CTM;class=class="str">"cmt">//Class object for Time Management class="macro">#include "WorkingWithFolders.mqh" CFolders Folder;class=class="str">"cmt">//Class object for Folders class="macro">#include "ChartProperties.mqh" CChartProperties Chart;class=class="str">"cmt">//Class object for Chart Properties class="macro">#include "RiskManagement.mqh" CRiskManagement CRisk;class=class="str">"cmt">//Class object for Risk Management class="macro">#include "CommonGraphics.mqh" CCommonGraphics *CGraphics;class=class="str">"cmt">//Class pointer object for Common Graphics CCandleProperties *CP;class=class="str">"cmt">//Class pointer object for Candle Properties class="macro">#include "TradeManagement.mqh" CTradeManagement Trade;class=class="str">"cmt">//Class object for Trade Management class=class="str">"cmt">//--- used to separate Input Menu enum iSeparator { Delimiterclass=class="str">"cmt">//__________________________ }; class=class="str">"cmt">//--- for chart class="type">class="kw">color Mode selection enum DisplayMode { Display_LightMode,class=class="str">"cmt">//LIGHT MODE Display_DarkModeclass=class="str">"cmt">//DARK MODE }; sinput group "+--------| DISPLAY |--------+"; sinput DisplayMode iDisplayMode=Display_LightMode;class=class="str">"cmt">//CHART COLOUR MODE sinput Choice iDisplay_NewsInfo=Yes;class=class="str">"cmt">//DISPLAY NEWS INFO sinput Choice iDisplay_EventObj=Yes;class=class="str">"cmt">//DISPLAY EVENT OBJ sinput Choice iDisplay_Spread=Yes;class=class="str">"cmt">//DISPLAY SPREAD RATING sinput Choice iDisplay_Date=Yes;class=class="str">"cmt">//DISPLAY DATE sinput group ""; sinput group "+--------| DST SCHEDULE |--------+"; input DSTSchedule ScheduleDST=AutoDst_Selection;class=class="str">"cmt">//SELECT DST OPTION sinput iSeparator iCustomSchedule=Delimiter;class=class="str">"cmt">//__________________________ sinput iSeparator iCustomScheduleL=Delimiter;class=class="str">"cmt">//CUSTOM DST input DST_type CustomSchedule=DST_NONE;class=class="str">"cmt">//SELECT CUSTOM DST sinput group ""; sinput group "+--------| RISK MANAGEMENT |--------+"; input RiskOptions RISK_Type=MINIMUM_LOT;class=class="str">"cmt">//SELECT RISK OPTION input RiskFloor RISK_Mini=RiskFloorMin;class=class="str">"cmt">//RISK FLOOR input class="type">class="kw">double RISK_Mini_Percent=class="num">75;class=class="str">"cmt">//MAX-RISK [class="num">100<-->class="num">0.01]% input RiskCeil RISK_Maxi=RiskCeilMax;class=class="str">"cmt">//RISK CEILING sinput iSeparator iRisk_1=Delimiter;class=class="str">"cmt">//__________________________ sinput iSeparator iRisk_1L=Delimiter;class=class="str">"cmt">//PERCENTAGE OF [BALANCE | FREE-MARGIN] input class="type">class="kw">double Risk_1_PERCENTAGE=class="num">3;class=class="str">"cmt">//[class="num">100<-->class="num">0.01]% sinput iSeparator iRisk_2=Delimiter;class=class="str">"cmt">//__________________________ sinput iSeparator iRisk_2L=Delimiter;class=class="str">"cmt">//AMOUNT PER [BALANCE | FREE-MARGIN] input class="type">class="kw">double Risk_2_VALUE=class="num">1000;class=class="str">"cmt">//[BALANCE | FREE-MARGIN] input class="type">class="kw">double Risk_2_AMOUNT=class="num">10;class=class="str">"cmt">//EACH AMOUNT
仓位与新闻风控的参数落地
这段 EA 初始化代码把三类仓位算法和新闻过滤器一次性挂到输入变量上,开 MT5 把参数面板调出来就能直接对照。Risk_3_VALUE 设 1000 对应 Risk_3_LOTSIZE 0.1,意思是每 1000 单位净值或空闲保证金给 0.1 手,属于按账户规模线性放大仓位的写法。 Risk_4_LOTSIZE 固定 0.01 是纯自定义手数,不随余额浮动;Risk_5_PERCENTAGE 填 1 表示最大风险敞口取账户 1%,区间从 100 到 0.01 都可调,外汇和贵金属波动大,这类百分比风控在高杠杆下仍可能快速触及强平。 新闻模块用 iImportance=High 只抓高影响事件,iSecondsPreEvent=5 规定事件前 5 秒预进场,iStoploss 与 iTakeprofit 同设 500 点且 0 代表不挂。OnInit 里用 MQLInfoInteger(MQL_TESTER) 分流实盘与回测初始化,NewsObject.CreateEconomicDatabaseMemory() 把财经日历塞进内存,CGraphics 负责画日期 spread 和新闻标。 跑回归测试时把 Risk_5_PERCENTAGE 从 1 改成 0.5,观察回撤曲线是否收敛,比盲目信固定手数更有用。
sinput iSeparator iRisk_3=Delimiter;class=class="str">"cmt">//__________________________ sinput iSeparator iRisk_3L=Delimiter;class=class="str">"cmt">//LOTSIZE PER [BALANCE | FREE-MARGIN] input class="type">class="kw">double Risk_3_VALUE=class="num">1000;class=class="str">"cmt">//[BALANCE | FREE-MARGIN] input class="type">class="kw">double Risk_3_LOTSIZE=class="num">0.1;class=class="str">"cmt">//EACH LOTS(VOLUME) sinput iSeparator iRisk_4=Delimiter;class=class="str">"cmt">//__________________________ sinput iSeparator iRisk_4L=Delimiter;class=class="str">"cmt">//CUSTOM LOTSIZE input class="type">class="kw">double Risk_4_LOTSIZE=class="num">0.01;class=class="str">"cmt">//LOTS(VOLUME) sinput iSeparator iRisk_5=Delimiter;class=class="str">"cmt">//__________________________ sinput iSeparator iRisk_5L=Delimiter;class=class="str">"cmt">//PERCENTAGE OF MAX-RISK input class="type">class="kw">double Risk_5_PERCENTAGE=class="num">1;class=class="str">"cmt">//[class="num">100<-->class="num">0.01]% sinput group ""; sinput group "+--------| NEWS SETTINGS |--------+"; input Calendar_Importance iImportance=Calendar_Importance_High;class=class="str">"cmt">//CALENDAR IMPORTANCE input Event_Frequency iFrequency=Event_Frequency_ALL;class=class="str">"cmt">//EVENT FREQUENCY input Event_Sector iSector=Event_Sector_ALL;class=class="str">"cmt">//EVENT SECTOR input Event_Type iType=Event_Type_Indicator;class=class="str">"cmt">//EVENT TYPE input Event_Currency iCurrency=Event_Currency_Symbol;class=class="str">"cmt">//EVENT CURRENCY sinput group ""; sinput group "+--------| TRADE SETTINGS |--------+"; input class="type">uint iStoploss=class="num">500;class=class="str">"cmt">//STOPLOSS [class="num">0=NONE] input class="type">uint iTakeprofit=class="num">500;class=class="str">"cmt">//TAKEPROFIT [class="num">0=NONE] input class="type">uint iSecondsPreEvent=class="num">5;class=class="str">"cmt">//PRE-ENTRY SEC input DayOfTheWeek TradingDay=AllDays;class=class="str">"cmt">//TRADING DAY OF WEEK sinput group ""; class=class="str">"cmt">//--- to keep track of start-up time class="type">class="kw">datetime Startup_date; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- Assign if in LightMode or not isLightMode=(iDisplayMode==Display_LightMode)?true:class="kw">false; class=class="str">"cmt">//--- call function for common initialization procedure InitCommon(); class=class="str">"cmt">//--- store Init result class="type">int InitResult; if(!MQLInfoInteger(MQL_TESTER))class=class="str">"cmt">//Checks whether the program is in the strategy tester { class=class="str">"cmt">//--- initialization procedure outside strategy tester InitResult=InitNonTester(); } else { class=class="str">"cmt">//--- initialization procedure inside strategy tester InitResult=InitTester(); } class=class="str">"cmt">//--- Create DB in memory NewsObject.CreateEconomicDatabaseMemory(); class=class="str">"cmt">//--- Initialize Common graphics class pointer object CGraphics = new CCommonGraphics(Answer(iDisplay_Date),Answer(iDisplay_Spread),Answer(iDisplay_NewsInfo),Answer(iDisplay_EventObj)); CGraphics.GraphicsRefresh(iSecondsPreEvent);class=class="str">"cmt">//-- Create chart objects class=class="str">"cmt">//--- Initialize Candle properties pointer object CP = new CCandleProperties(); class=class="str">"cmt">//--- Store start-up time. Startup_date = TimeTradeServer(); class=class="str">"cmt">//--- class="kw">return Init result class="kw">return InitResult; } class=class="str">"cmt">//+------------------------------------------------------------------+
◍ 风控与界面初始化的两段分工
EA 启动时把风控参数一次性灌进 CRiskManagement 实例,靠的是 InitCommon()。这段代码里所有百分比类参数都被夹在 0.01% 与 100% 之间——RISK_Mini_Percent、Risk_1_PERCENTAGE、Risk_5_PERCENTAGE 只要越界就强制拉回边界,避免脏配置让仓位计算直接爆掉。 RiskProfileOption 到 Risk_Profile_4 分别对应五套风控模型:余额/可用保证金百分比、固定金额、固定手数、自定义手数、最大风险百分比。调 EA 输入参数时,改 Risk_3_LOTSIZE 这类宏就会在 InitCommon 里落到 Risk_Profile_3.RiskLot,不需要动计算逻辑。 InitNonTester() 只在非回测环境跑。它先用 MQLInfoInteger(MQL_TESTER) 判断是否在策略测试器,如果是就直接返回 INIT_SUCCEEDED 跳过绘图。实盘里它会建一个 OBJ_BITMAP_LABEL 名叫 STATUS,钉在坐标 (5,22) 并绑定 ::PROGRESS 位图,用来在图表左上角画进度/状态。 开 MT5 把这段贴进 EA,把 RISK_Mini_Percent 设成 150 再 Print 一下 RiskFloorPercentage,应该看到它被压成 100.0——这就是边界钳制的直接证据。外汇与贵金属杠杆高,这类边界值错了可能让下单手数偏离预期,上实盘前务必在模拟盘验过。
class=class="str">"cmt">//|function for common initialization procedure | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void InitCommon() { class=class="str">"cmt">//Initializing CRiskManagement variable for Risk options RiskProfileOption = RISK_Type; class=class="str">"cmt">//Initializing CRiskManagement variable for Risk floor RiskFloorOption = RISK_Mini; class=class="str">"cmt">//Initializing CRiskManagement variable for RiskFloorMax RiskFloorPercentage = (RISK_Mini_Percent>class="num">100)?class="num">100: (RISK_Mini_Percent<class="num">0.01)?class="num">0.01:RISK_Mini_Percent;class=class="str">"cmt">//Percentage cannot be more than class="num">100% or less than class="num">0.01% class=class="str">"cmt">//Initializing CRiskManagement variable for Risk ceiling RiskCeilOption = RISK_Maxi; class=class="str">"cmt">//Initializing CRiskManagement variable for Risk options(PERCENTAGE OF BALANCE and PERCENTAGE OF FREE-MARGIN) Risk_Profile_1 = (Risk_1_PERCENTAGE>class="num">100)?class="num">100: (Risk_1_PERCENTAGE<class="num">0.01)?class="num">0.01:Risk_1_PERCENTAGE;class=class="str">"cmt">//Percentage cannot be more than class="num">100% or less than class="num">0.01% class=class="str">"cmt">//Initializing CRiskManagement variables for Risk options(AMOUNT PER BALANCE and AMOUNT PER FREE-MARGIN) Risk_Profile_2.RiskAmountBoF = Risk_2_VALUE; Risk_Profile_2.RiskAmount = Risk_2_AMOUNT; class=class="str">"cmt">//Initializing CRiskManagement variables for Risk options(LOTSIZE PER BALANCE and LOTSIZE PER FREE-MARGIN) Risk_Profile_3.RiskLotBoF = Risk_3_VALUE; Risk_Profile_3.RiskLot = Risk_3_LOTSIZE; class=class="str">"cmt">//Initializing CRiskManagement variable for Risk option(CUSTOM LOTSIZE) Risk_Profile_4 = Risk_4_LOTSIZE; class=class="str">"cmt">//Initializing CRiskManagement variable for Risk option(PERCENTAGE OF MAX-RISK) Risk_Profile_5 = (Risk_5_PERCENTAGE>class="num">100)?class="num">100: (Risk_5_PERCENTAGE<class="num">0.01)?class="num">0.01:Risk_5_PERCENTAGE;class=class="str">"cmt">//Percentage cannot be more than class="num">100% or less than class="num">0.01% class=class="str">"cmt">//--- Initializing DST Schedule variables MyDST = ScheduleDST; MySchedule = CustomSchedule; class=class="str">"cmt">//--- Initializing News filter variables myFrequency=iFrequency; myImportance=iImportance; mySector=iSector; myType=iType; myCurrency=iCurrency; Chart.ChartRefresh();class=class="str">"cmt">//Load chart configurations } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|function for initialization procedure outside strategy tester | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int InitNonTester() { class=class="str">"cmt">//--- Check if in Strategy tester! if(MQLInfoInteger(MQL_TESTER)) { class=class="str">"cmt">//--- Initialization failed. class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//--- create OBJ_BITMAP_LABEL object for drawing ObjectCreate(class="num">0,"STATUS",OBJ_BITMAP_LABEL,class="num">0,class="num">0,class="num">0); ObjectSetInteger(class="num">0,"STATUS",OBJPROP_XDISTANCE,class="num">5); ObjectSetInteger(class="num">0,"STATUS",OBJPROP_YDISTANCE,class="num">22); class=class="str">"cmt">//--- specify the name of the graphical resource ObjectSetString(class="num">0,"STATUS",OBJPROP_BMPFILE,"::PROGRESS"); class="type">uint w,h; class=class="str">"cmt">// variables for receiving text class="type">class="kw">string sizes class="type">uint x,y; class=class="str">"cmt">// variables for calculation of the current coordinates of text class="type">class="kw">string anchor points /* In the Do class="kw">while loop below, the code will check if the terminal is connected to the internet.
「断线时怎么画出等待动画」
EA 在 do-while 里先清掉 ExtImg 绘图缓冲(ArrayFill 按 IMG_WIDTH*IMG_HEIGHT 全填 0),再判 TerminalInfoInteger(TERMINAL_CONNECTED)。若终端没连网,就走等待分支,而不是去拉新闻库。 等待分支用 Arial 150 号粗体画 'Waiting',纵向左对齐 x=10、y 取 IMG_HEIGHT/2-h/2 做垂直居中;随后在 x=w+13 处用 160 号字截 '...' 的子串,dots 从 0 到 3 循环,形成点号递增的载入动画。 每轮 ResourceCreate("::PROGRESS",...) 重建位图资源,Chart.Redraw() 强制重绘,Sleep(500) 控节奏,Print 打日志;循环 continue 直到连上。外汇/贵金属行情依赖实时连接,断线等待逻辑能避免程序空跑浪费 CPU。 连上后 else 分支把字体改成 120 号,测 'Getting Ready' 的 w/h,x 设 20,准备切到新闻库创建流程;这段坐标差(等待 10、就绪 20)可直接在 MT5 里改数字看对齐变化。
class="type">bool done=class="kw">false; do { class=class="str">"cmt">//--- clear the drawing buffer array ArrayFill(ExtImg,class="num">0,IMG_WIDTH*IMG_HEIGHT,class="num">0); if(!TerminalInfoInteger(TERMINAL_CONNECTED)) { class=class="str">"cmt">//-- integer dots used as a loading animation class="kw">static class="type">int dots=class="num">0; class=class="str">"cmt">//--- set the font TextSetFont("Arial",-class="num">150,FW_EXTRABOLD,class="num">0); TextGetSize("Waiting",w,h);class=class="str">"cmt">//get text width and height values class=class="str">"cmt">//--- calculate the coordinates of the &class="macro">#x27;Waiting&class="macro">#x27; text x=class="num">10;class=class="str">"cmt">//horizontal alignment y=IMG_HEIGHT/class="num">2-(h/class="num">2);class=class="str">"cmt">//alignment for the text to be centered vertically class=class="str">"cmt">//--- output the &class="macro">#x27;Waiting&class="macro">#x27; text to ExtImg[] buffer TextOut("Waiting",x,y,TA_LEFT|TA_TOP,ExtImg,IMG_WIDTH,IMG_HEIGHT,ColorToARGB(CSymbol.Background()),clr_format); class=class="str">"cmt">//--- calculate the coordinates for the dots after the &class="macro">#x27;Waiting&class="macro">#x27; text x=w+class="num">13;class=class="str">"cmt">//horizontal alignment y=IMG_HEIGHT/class="num">2-(h/class="num">2);class=class="str">"cmt">//alignment for the text to be centered vertically TextSetFont("Arial",-class="num">160,FW_EXTRABOLD,class="num">0); class=class="str">"cmt">//--- output of dots to ExtImg[] buffer TextOut(StringSubstr("...",class="num">0,dots),x,y,TA_LEFT|TA_TOP,ExtImg,IMG_WIDTH,IMG_HEIGHT,ColorToARGB(CSymbol.Background()),clr_format); class=class="str">"cmt">//--- update the graphical resource ResourceCreate("::PROGRESS",ExtImg,IMG_WIDTH,IMG_HEIGHT,class="num">0,class="num">0,IMG_WIDTH,clr_format); class=class="str">"cmt">//--- force chart update Chart.Redraw(); dots=(dots==class="num">3)?class="num">0:dots+class="num">1; class=class="str">"cmt">//-- Notify user that program is waiting for connection Print("Waiting for connection..."); Sleep(class="num">500); class="kw">continue; } else { class=class="str">"cmt">//--- set the font TextSetFont("Arial",-class="num">120,FW_EXTRABOLD,class="num">0); TextGetSize("Getting Ready",w,h);class=class="str">"cmt">//get text width and height values x=class="num">20;class=class="str">"cmt">//horizontal alignment
回测前的新闻库自检逻辑
在策略测试器里跑 EA 前,得先确认新闻数据库文件存在且没过期,否则回测结果大概率失真。代码用 FileIsExist(NEWS_DATABASE_FILE, FILE_COMMON) 检查公共目录下的库文件,缺失就直接返回 INIT_FAILED 并打印提示,要求你先在正常图表环境跑一遍程序生成文件。 如果文件在,就取库里最新新闻日期减去一天偏移量作为 latestdate,再和 TimeTradeServer() 比。若 latestdate 小于服务器时间,说明库已落后,打印“Necessary Files outdated!”提醒你更新;并打印“Database Dates End at:”加具体日期,明确告知该日期之后的行情无新闻数据可用。 非测试环境(MQLInfoInteger(MQL_TESTER) 为假)则直接放行返回 INIT_SUCCEEDED,不卡初始化。这个分支很实用:实盘和回测的初始化路径分开,避免测试器里反复建库拖慢速度。 连接成功那段也值得看:用 TextOut 往 ExtImg[] 画“Getting Ready...”、ResourceCreate 更新 ::PROGRESS 资源、Chart.Redraw() 强刷图表,最后 Print("Connection Successful!") 并调 NewsObject.CreateEconomicDatabase() 建库,done 置真退出循环。外汇与贵金属属高风险品类,回测数据缺口可能让策略表现偏离实盘,动手前先核对库日期。
y=IMG_HEIGHT/class="num">2-(h/class="num">2); class=class="str">"cmt">//alignment for the text to be centered vertically class=class="str">"cmt">//--- output the text &class="macro">#x27;Getting Ready...&class="macro">#x27; to ExtImg[] buffer TextOut("Getting Ready...",x,y,TA_LEFT|TA_TOP,ExtImg,IMG_WIDTH,IMG_HEIGHT,ColorToARGB(CSymbol.Background()),clr_format); class=class="str">"cmt">//--- update the graphical resource ResourceCreate("::PROGRESS",ExtImg,IMG_WIDTH,IMG_HEIGHT,class="num">0,class="num">0,IMG_WIDTH,clr_format); class=class="str">"cmt">//--- force chart update Chart.Redraw(); class=class="str">"cmt">//-- Notify user that connection is successful Print("Connection Successful!"); NewsObject.CreateEconomicDatabase(); class=class="str">"cmt">//calling the database create function done=true; } } class="kw">while(!done&&!IsStopped()); class=class="str">"cmt">//-- Delete chart object ObjectDelete(class="num">0,"STATUS"); class=class="str">"cmt">//-- force chart to update Chart.Redraw(); class=class="str">"cmt">//--- Initialization succeeded. class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|function for initialization procedure inside strategy tester | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int InitTester() { class=class="str">"cmt">//--- Check if not in Strategy tester! if(!MQLInfoInteger(MQL_TESTER)) { class=class="str">"cmt">//--- Initialization failed. class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//Checks whether the database file exists if(!FileIsExist(NEWS_DATABASE_FILE,FILE_COMMON)) { class=class="str">"cmt">//--- Warning messages Print("Necessary Files Do not Exist!"); Print("Run Program outside of the Strategy Tester"); Print("Necessary Files Should be Created First"); class=class="str">"cmt">//--- Initialization failed. class="kw">return(INIT_FAILED); } else { class=class="str">"cmt">//Checks whether the latest database date includes the time and date being tested class="type">class="kw">datetime latestdate = CTM.TimeMinusOffset(NewsObject.GetLatestNewsDate(),CTM.DaysS()); class=class="str">"cmt">//Day before the latest recorded time in the database if(latestdate<TimeTradeServer()) { Print("Necessary Files outdated!"); Print("To Update Files: Run Program outside of the Strategy Tester"); } Print("Database Dates End at: ",latestdate); PrintFormat("Dates after %s will not be available for backtest",TimeToString(latestdate)); } class=class="str">"cmt">//--- Initialization succeeded. class="kw">return(INIT_SUCCEEDED); }
◍ 把事件驱动逻辑塞进 OnTick 的骨架
EA 的主循环越干净,后续接新闻事件或重绘图形越不容易卡。下面这段把实时刷新、交易日更、小时库更新全收敛到 OnTick → Execution() 一条链上,实盘里你直接照这个壳子改就行。 OnTick 只做一件事:调 Execution()。真正的分支在 Execution 里——每分钟新蜡烛触发一次实时图块刷新(CGraphics.Block_2_Realtime),由 CP.NewCandle(1,PERIOD_M1) 判定,避免每 tick 重绘拖慢 MT5。 当日不是启动日时,日线新蜡烛(序号 2 / PERIOD_D1)会重建内存里的经济数据库并刷新图表对象;且只在非回测环境(!MQLInfoInteger(MQL_TESTER))才跑 CreateEconomicDatabaseMemory,回测不碰本地存储。小时线新蜡烛(序号 3 / PERIOD_H1)则检查存储记录是否需要更新,需要时调 InitNonTester 做实盘初始化。 若是启动当天,日线新蜡烛只更新新闻事件对象(CGraphics.NewsEvent),不重建库——这个分支能帮你避开首日数据不全导致的误触。外汇与贵金属受新闻冲击跳空概率高,这类事件驱动 EA 必须先在策略测试器跑通再上实盘。 代码逐行拆:CP.NewCandle 的整型参数是内部计数器槽位,不是时间周期;PERIOD_M1/H1/D1 才是周期枚举。MQL_TESTER 判断让同一套代码回测与实盘行为不一致,调试时建议临时强制为 false 观察全量分支。
class=class="str">"cmt">//| Expert tick function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class=class="str">"cmt">//--- Run procedures Execution(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|Execute program procedures | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void Execution() { class=class="str">"cmt">//--- Update realtime Graphic every class="num">1 min if(CP.NewCandle(class="num">1,PERIOD_M1)) { CGraphics.Block_2_Realtime(iSecondsPreEvent); } class=class="str">"cmt">//--- function to open trades EnterTrade(); class=class="str">"cmt">//--- Check if not start-up date if(!CTM.DateisToday(Startup_date)) { class=class="str">"cmt">//--- Run every New Daily Candle if(CP.NewCandle(class="num">2,PERIOD_D1)) { class=class="str">"cmt">//--- Check if not in strategy tester if(!MQLInfoInteger(MQL_TESTER)) { class=class="str">"cmt">//--- Update/Create DB in Memory NewsObject.CreateEconomicDatabaseMemory(); } CGraphics.GraphicsRefresh(iSecondsPreEvent);class=class="str">"cmt">//-- Create/Re-create chart objects class=class="str">"cmt">//--- Update Realtime Graphics CGraphics.Block_2_Realtime(iSecondsPreEvent); } class=class="str">"cmt">//--- Check if not in strategy tester if(!MQLInfoInteger(MQL_TESTER)) { class=class="str">"cmt">//--- Run every New Hourly Candle if(CP.NewCandle(class="num">3,PERIOD_H1)) { class=class="str">"cmt">//--- Check if DB in Storage needs an update if(NewsObject.UpdateRecords()) { class=class="str">"cmt">//--- initialization procedure outside strategy tester InitNonTester(); } } } } else { class=class="str">"cmt">//--- Run every New Daily Candle if(CP.NewCandle(class="num">4,PERIOD_D1)) { class=class="str">"cmt">//--- Update Event objects on chart CGraphics.NewsEvent(); } } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|function to open trades | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void EnterTrade() { class=class="str">"cmt">//--- class="kw">static variable for storing upcoming event Impact value
「按财经事件影响方向自动挂单的逻辑」
这段控制逻辑解决一个实际问题:财经日历事件临近时,EA 如何根据影响方向自动决定多空。它先判断已排定的新闻时间是否已过、新闻对象非空、且 1 分钟周期出现了新 K 线(CP.NewCandle(5,PERIOD_M1) 中 5 是校验用的某种计数),若成立就刷新下一条待发事件并取回影响等级 Impact。 紧接着用 CTM.TimePreEvent 比对「事件时间减去预偏移秒数」与事件本身时间,偏移量 iSecondsPreEvent 为 0 时退化成 1 秒,再叠加 isDayOfTheWeek(TradingDay) 过滤交易日,只有都命中才进入交易分支。 影响等级走 switch:CALENDAR_IMPACT_NEGATIVE 时,若品种报价货币的 Profit 端等于事件货币(CSymbol.CurrencyProfit()),就 Buy,否则 Sell;CALENDAR_IMPACT_POSITIVE 完全反向——Profit 端同币则 Sell,否则 Buy。单子 Magic 号直接写死成 UpcomingNews.EventId,方便事后按事件追溯。 别把正负影响当绝对信号。外汇与贵金属在事件窗口流动性突变,滑点可能吞掉止损,这套逻辑只是把「方向倾向」程序化,实盘前务必在 MT5 策略测试器用真实点差回测。
class="kw">static ENUM_CALENDAR_EVENT_IMPACT Impact=CALENDAR_IMPACT_NA; class=class="str">"cmt">//--- Check if Upcoming news date has passed and if upcoming news is not null and if new minute candle has formed. if(class="type">class="kw">datetime(UpcomingNews.EventDate)<TimeTradeServer()&&UpcomingNews.CountryName!=NULL&&CP.NewCandle(class="num">5,PERIOD_M1)) { class=class="str">"cmt">//--- Update for next upcoming news NewsObject.EconomicNextEvent(); class=class="str">"cmt">//--- Get impact value for upcoming news Impact=NewsObject.GetImpact(); } class=class="str">"cmt">//--- Check if upcoming news date is about to occur and if it is the trading day of week if(CTM.TimePreEvent(CTM.TimeMinusOffset(class="type">class="kw">datetime(UpcomingNews.EventDate),(iSecondsPreEvent==class="num">0)?class="num">1:iSecondsPreEvent) ,class="type">class="kw">datetime(UpcomingNews.EventDate)) &&CTM.isDayOfTheWeek(TradingDay)) { class=class="str">"cmt">//--- Check each Impact value type class="kw">switch(Impact) { class=class="str">"cmt">//--- When Impact news is negative case CALENDAR_IMPACT_NEGATIVE: class=class="str">"cmt">//--- Check if profit currency is news event currency if(UpcomingNews.EventCurrency==CSymbol.CurrencyProfit()) { class=class="str">"cmt">//--- Open buy trade with Event id as Magic number Trade.Buy(iStoploss,iTakeprofit,class="type">class="kw">ulong(UpcomingNews.EventId),"NewsTrading"); } else { class=class="str">"cmt">//--- Open sell trade with Event id as Magic number Trade.Sell(iStoploss,iTakeprofit,class="type">class="kw">ulong(UpcomingNews.EventId),"NewsTrading"); } class="kw">break; class=class="str">"cmt">//--- When Impact news is positive case CALENDAR_IMPACT_POSITIVE: class=class="str">"cmt">//--- Check if profit currency is news event currency if(UpcomingNews.EventCurrency==CSymbol.CurrencyProfit()) { class=class="str">"cmt">//--- Open sell trade with Event id as Magic number Trade.Sell(iStoploss,iTakeprofit,class="type">class="kw">ulong(UpcomingNews.EventId),"NewsTrading"); } else { class=class="str">"cmt">//--- Open buy trade with Event id as Magic number Trade.Buy(iStoploss,iTakeprofit,class="type">class="kw">ulong(UpcomingNews.EventId),"NewsTrading"); } class="kw">break; class=class="str">"cmt">//--- Unknown class="kw">default: class="kw">break; } }
空小节无内容可析
本技术小节在源材料中未承载任何正文描述与代码逻辑,仅留有空标记。 若需在 MT5 中验证相关机制,应回溯前文定义的函数与调用上下文,避免凭空假设行为。 外汇与贵金属市场波动剧烈,任何未明确定义的逻辑块都可能引入不可控风险。
◍ 下一步要补的缺口
这一篇把内存数据库和额外视图接进了 MQL5 经济日历,图表上能直接挂图形对象提示临近事件,还顺手做了深色模式。用户侧加了新闻筛选输入项,EA 侧也给了按事件影响开关市价单的接口,事件影响与策略的关联逻辑算是跑通了。 附件 NewsTrading_Part3.zip 约 567 KB,里面是完整可编译的工程,开 MT5 加载后先调 NewsFilter 和 EA 的 ImpactThreshold 两个输入,看自己品种的事件触发是否符合预期。外汇和贵金属新闻行情跳空频繁,实盘前务必在策略测试器用历史数据验证一遍。 下一篇会补个别经济事件的待处理订单逻辑,以及不依赖事件影响的交易分支,当前这套只覆盖了市价单一种入口。