2026-07-18 17:22:33 -04:00 | | | //+------------------------------------------------------------------+
|
| | | //| 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
|
2026-08-22 00:25:52 -04:00 | | | //--- 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.
|
2026-07-18 17:22:33 -04:00 | | | 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;
|
| | | }
|