让新闻交易轻松上手(第二部分):风险管理·进阶篇
(2/3)· 接上篇数据库铺垫,本篇用 OOP 继承改写代码并给出多档风险画像
「用继承把央行事件拆成子类」
在 MT5 里做事件驱动型分析,最怕把所有国家的新闻塞进一个扁平数组。上面这段把 NewsData 作为基类,私有成员 Country 和结构体 EventDetails 锁在类内,子类只能碰 protected 的 News[] 数组和虚函数 SetNews(),封装边界很清楚。
美国子类 UnitedStates 的构造函数直接调 SetNews(),把 NFP 写死成 EventID=1、时间 D'2024.01.03 14:00:00';瑞士子类 Switzerland 构造函数里顺手调 SetCountry("Switerland") 再 SetNews(),事件 ID 置 0、时间 D'2024.01.06 10:00:00'。两个日期差 3 天,足够在 MT5 策略测试器里分窗验证事件触发的隔离性。
别把正态当圣经:基类的 SetNews() 是 virtual,子类不重写就调不到自己的事件数据;ArrayResize(News, News.Size()+1, News.Size()+2) 每次只扩 1~2 格,高频塞事件时内存碎片会偏多,真跑实盘前建议在 OnInit 预分配。
外汇与贵金属受这类事件冲击波动可能急剧放大,属高风险场景,上面代码仅作结构示范,未含任何风控与下单逻辑。
class="kw">virtual class="type">void SetNews()class=class="str">"cmt">//class="kw">private Function only Accessible in &class="macro">#x27;UnitedStates&class="macro">#x27; class { ArrayResize(News,News.Size()+class="num">1,News.Size()+class="num">2); News[News.Size()-class="num">1].EventID = class="num">1; News[News.Size()-class="num">1].EventName = "NFP(Non-Farm Payrolls)"; News[News.Size()-class="num">1].EventDate = D&class="macro">#x27;class="num">2024.01.class="num">03 class="num">14:class="num">00:class="num">00&class="macro">#x27;; } class="kw">public: class="type">void myNews()class=class="str">"cmt">//class="kw">public Function accessible via class&class="macro">#x27;s object { SetCountry("United States");class=class="str">"cmt">//Calling function from &class="macro">#x27;NewsData&class="macro">#x27; GetNews();class=class="str">"cmt">//Calling Function from class="kw">private inherited class &class="macro">#x27;NewsData&class="macro">#x27; } UnitedStates(class="type">void) {SetNews();}class=class="str">"cmt">//Class constructor }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|(Subclass/Child) for &class="macro">#x27;NewsData&class="macro">#x27; | class=class="str">"cmt">//+------------------------------------------------------------------+ class Switzerland: class="kw">public NewsData class=class="str">"cmt">//class="kw">public inheritance from NewsData { class="kw">public: class="kw">virtual class="type">void SetNews()class=class="str">"cmt">//Public Function to set News data { ArrayResize(News,News.Size()+class="num">1,News.Size()+class="num">2);class=class="str">"cmt">//Adjusting News structure array&class="macro">#x27;s size News[News.Size()-class="num">1].EventID = class="num">0;class=class="str">"cmt">//Setting event id to &class="macro">#x27;class="num">0&class="macro">#x27; News[News.Size()-class="num">1].EventName = "Interest Rate Decision";class=class="str">"cmt">//Assigning event name News[News.Size()-class="num">1].EventDate = D&class="macro">#x27;class="num">2024.01.class="num">06 class="num">10:class="num">00:class="num">00&class="macro">#x27;;class=class="str">"cmt">//Assigning event date } Switzerland(class="type">void) {SetCountry("Switerland"); SetNews();}class=class="str">"cmt">//Class construct }; class NewsData { class="kw">private:class=class="str">"cmt">//Properties are only accessible from this class class="type">class="kw">string Country;class=class="str">"cmt">//Private variable class="kw">struct EventDetailsclass=class="str">"cmt">//Private structure { class="type">int EventID; class="type">class="kw">string EventName; class="type">class="kw">datetime EventDate; }; class="kw">protected: class=class="str">"cmt">//-- Protected Array Only accessible from this class and its children EventDetails News[]; class=class="str">"cmt">//-- Proctected class="kw">virtual class="type">void Function(to be expanded on via child classes)
私有继承掐断基类接口,美国数据类这么写
用 MQL5 做财经事件封装时,继承方式直接决定外部能不能摸到基类方法。下面这段把 UnitedStates 设成 private 继承 NewsData,结果就是 UnitedStates 的对象和它的子类都看不到 NewsData 里的 GetNews、SetCountry 等 public 成员,只能靠自己类里转一道手。 对比 Switzerland 用的是 public 继承,它构造时直接 SetCountry("Switerland") 然后 SetNews(),外部拿到对象后理论上还能继续调基类公开函数。两种写法在 MT5 里编译都过,但权限边界完全不同,抄代码前先想清楚你要不要对外暴露事件打印。 UnitedStates 的 SetNews 里用 ArrayResize(News,News.Size()+1,News.Size()+2) 把动态数组扩 1 格并预留 2 格内存,再把 NFP 写进 EventID=1、EventDate=D'2024.01.03 14:00:00'。注意原代码 Country 拼写错误写成了 "Switerland",实盘跑前建议改掉。 别把私有继承当省事写法 私有继承不是「不能继承」而是「不传接口」,UnitedStates 里 myNews() 能调 GetNews() 只是类内可见,你在外面 new 一个 UnitedStates 对象直接点 GetNews() 会编译报错。要对外打印事件就用 public 继承,否则多包一层反而难调试。
class="kw">virtual class="type">void SetNews(); class=class="str">"cmt">//-- Protected Function Only accessible from this class and its children class="type">void SetCountry(class="type">class="kw">string myCountry) {Country=myCountry;} class="kw">public: class="type">void GetNews()class=class="str">"cmt">//Public function to display &class="macro">#x27;News&class="macro">#x27; array details { PrintFormat("+---------- %s ----------+",Country); for(class="type">uint i=class="num">0;i<News.Size();i++) { Print("ID: ",News[i].EventID," Name: ",News[i].EventName," Date: ",News[i].EventDate); } } NewsData(class="type">void) {}class=class="str">"cmt">//Class constructor ~NewsData(class="type">void) {ArrayFree(News);}class=class="str">"cmt">//Class destructor }; class UnitedStates:class="kw">private NewsData class=class="str">"cmt">//class="kw">private inheritance from NewsData, class=class="str">"cmt">//&class="macro">#x27;UnitedStates&class="macro">#x27; class&class="macro">#x27;s objects and children class=class="str">"cmt">//will not have access to &class="macro">#x27;NewsData&class="macro">#x27; class&class="macro">#x27;s properties { class="kw">private: class="kw">virtual class="type">void SetNews()class=class="str">"cmt">//class="kw">private Function only Accessible in &class="macro">#x27;UnitedStates&class="macro">#x27; class { ArrayResize(News,News.Size()+class="num">1,News.Size()+class="num">2); News[News.Size()-class="num">1].EventID = class="num">1; News[News.Size()-class="num">1].EventName = "NFP(Non-Farm Payrolls)"; News[News.Size()-class="num">1].EventDate = D&class="macro">#x27;class="num">2024.01.class="num">03 class="num">14:class="num">00:class="num">00&class="macro">#x27;; } class="kw">public: class="type">void myNews()class=class="str">"cmt">//class="kw">public Function accessible via class&class="macro">#x27;s object { SetCountry("United States");class=class="str">"cmt">//Calling function from &class="macro">#x27;NewsData&class="macro">#x27; GetNews();class=class="str">"cmt">//Calling Function from class="kw">private inherited class &class="macro">#x27;NewsData&class="macro">#x27; } UnitedStates(class="type">void) {SetNews();}class=class="str">"cmt">//Class constructor }; class Switzerland: class="kw">public NewsData class=class="str">"cmt">//class="kw">public inheritance from NewsData { class="kw">public: class="kw">virtual class="type">void SetNews()class=class="str">"cmt">//Public Function to set News data { ArrayResize(News,News.Size()+class="num">1,News.Size()+class="num">2);class=class="str">"cmt">//Adjusting News structure array&class="macro">#x27;s size News[News.Size()-class="num">1].EventID = class="num">0;class=class="str">"cmt">//Setting event id to &class="macro">#x27;class="num">0&class="macro">#x27; News[News.Size()-class="num">1].EventName = "Interest Rate Decision";class=class="str">"cmt">//Assigning event name News[News.Size()-class="num">1].EventDate = D&class="macro">#x27;class="num">2024.01.class="num">06 class="num">10:class="num">00:class="num">00&class="macro">#x27;;class=class="str">"cmt">//Assigning event date } Switzerland(class="type">void) {SetCountry("Switerland"); SetNews();}class=class="str">"cmt">//Class construct };
◍ 三个夏令时类的初始拆分
早期版本里,针对夏令时切换我们直接写了三个独立的类,分别覆盖澳洲、英国和美国市场。 具体命名是 CDaylightSavings_AU、CDaylightSavings_UK 和 CDaylightSavings_US,每一个都各自维护对应地区的时令偏移逻辑。 这种写法在只做新闻交易局部适配时尚能跑通,但每加一个地区就要复制一套近似结构,后续维护成本会随市场覆盖数线性上升。
「为夏令时拆出三层类」
处理跨时区交易时段,最麻烦的是夏令时切换规则各国不同。把逻辑按国家拆类,比在单个函数里写一堆 if 更利于维护。 原文给出的项目文件结构里,基类定为 CDaylightSavings,下面挂三个子类:CDaylightSavings_AU、CDaylightSavings_UK、CDaylightSavings_US。这样澳、英、美三地的偏移计算各自独立,改一国规则不影响其余。 在 MT5 里新建这些类后,调用方只需持有 CDaylightSavings 指针,运行时绑定具体子类即可拿到正确时区偏移。外汇与贵金属受夏令时跳变影响报价节奏,实盘前建议用历史 K 线验证偏移是否对齐。
把重复代码收进基类
早期实现里,每加一个地区的夏令时规则就重写一遍几乎一样的判断逻辑,类与类之间重复率极高。这种写法在维护时极易漏改,比如调了欧洲类的边界判断却忘了北美类,回测就会出现时区错位。 解决思路是把所有地区共有的字段和方法抽到一个基类,具体夏令时类只保留差异部分并继承基类。这样新增市场时只需写极少代码,MT5 里编译报错概率也会明显下降。 外汇与贵金属受时区切换影响大,时区处理出错可能让 EA 在冬夏令交接周错判开盘,属高风险环节,建议在策略测试器用 2023-03 与 2023-11 周边界周做验证。
◍ 用虚函数把接口和实现拆开
MQL5 里把基类成员函数标成 virtual,派生类就能重写同一签名。通过基类指针调方法时,实际跑的是对象真实类型里的版本,这就是运行期多态,而不是编译期绑死在指针类型上。 对 EA 和指标框架来说,好处是直接可验证的:写一套 CTradeBase 接口,下面挂 CTradeForex / CTradeMetal 各自实现下单逻辑,主循环只认基类指针。外汇和贵金属波动属性不同,这样改某一类执行细节不会动到调用层,解耦和复用都更稳。 高风险提醒: polymorphism 只解决结构灵活度,不替你过滤行情风险,MT5 上跑多品种前先单品种回测。