//+------------------------------------------------------------------+ //| NewsRelevance.mqh | //| AnimateDread | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "AnimateDread" #property link "https://www.mql5.com" //--- Module-scope cache: a symbol's currency composition never changes at runtime, and //--- CalendarCountries() enumerating every country in the terminal's calendar database on //--- every call (both the live NewsFilter veto and, per-bar, the NN news feature) would be //--- pure waste. Single-symbol cache (this codebase runs one symbol per EA instance/chart) - //--- rebuilt if a different symbol is ever requested. string g_newsRelevanceCachedSymbol = ""; string g_newsRelevanceCountryCodes[]; //+------------------------------------------------------------------+ //| Builds the ISO country codes relevant to symbol's base+quote | //| currency (e.g. EURUSD -> every Eurozone country + the US) by | //| cross-referencing CalendarCountries() against | //| SYMBOL_CURRENCY_BASE/SYMBOL_CURRENCY_PROFIT. This is what | //| CalendarValueHistory()'s country_code filter needs to actually | //| scope news to the traded symbol, instead of vetoing/scoring on | //| ANY country's events regardless of relevance. | //+------------------------------------------------------------------+ void GetRelevantCountryCodes(string symbol, string &countryCodes[]) { if(symbol == g_newsRelevanceCachedSymbol) { ArrayCopy(countryCodes, g_newsRelevanceCountryCodes); return; } ArrayResize(countryCodes, 0); string baseCcy = SymbolInfoString(symbol, SYMBOL_CURRENCY_BASE); string profitCcy = SymbolInfoString(symbol, SYMBOL_CURRENCY_PROFIT); MqlCalendarCountry countries[]; int total = CalendarCountries(countries); for(int i = 0; i < total; i++) { if(countries[i].currency == baseCcy || countries[i].currency == profitCcy) { int n = ArraySize(countryCodes); ArrayResize(countryCodes, n + 1); countryCodes[n] = countries[i].code; } } g_newsRelevanceCachedSymbol = symbol; ArrayCopy(g_newsRelevanceCountryCodes, countryCodes); } //+------------------------------------------------------------------+ //| Impact-weighted proximity, in [0,1], to the CLOSEST relevant | //| (symbol-currency-matched) calendar event on one side of pivotTime: | //| searchForward=false looks backward (recency - "how close was the | //| last relevant release"), true looks forward (proximity to the | //| next SCHEDULED relevant release - legitimate to use even for a | //| historical training bar, since release times are publicly known | //| in advance; this is NOT the same as knowing the release's actual | //| outcome ahead of time, which would be real lookahead bias and is | //| deliberately not exposed anywhere here). | //| Returns 0.0 if no relevant event falls within windowMinutes on the | //| requested side, else (impact_type/3.0) * (1 - minutesAway/window) | //| for whichever qualifying event is closest to pivotTime. | //+------------------------------------------------------------------+ double ImpactWeightedProximity(string symbol, datetime pivotTime, int windowMinutes, bool searchForward) { if(windowMinutes <= 0) return 0.0; string countryCodes[]; GetRelevantCountryCodes(symbol, countryCodes); if(ArraySize(countryCodes) == 0) return 0.0; datetime windowStart = searchForward ? pivotTime : pivotTime - windowMinutes * 60; datetime windowEnd = searchForward ? pivotTime + windowMinutes * 60 : pivotTime; double best = 0.0; for(int c = 0; c < ArraySize(countryCodes); c++) { MqlCalendarValue values[]; if(!CalendarValueHistory(values, windowStart, windowEnd, countryCodes[c])) continue; for(int i = 0; i < ArraySize(values); i++) { double minutesAway = MathAbs((double)(values[i].time - pivotTime)) / 60.0; double weight = (values[i].impact_type / 3.0) * (1.0 - minutesAway / windowMinutes); if(weight > best) best = weight; } } return best; }