StopFirst 1.00, the read-only core
Fourteen headers, a header-only MQL5 library: lot size from risk, pips on
forex, gold and indices, broker checks, bars and candle patterns, cached ATR,
sessions in GMT, account risk, symbol suffixes, a calendar filter. Sends no
trade requests. Plus the LotByRisk example script.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 21:40:34 +03:00 | | | //+------------------------------------------------------------------+
|
| | | //| News.mqh|
|
| | | //| Copyright 2026, bandersoft |
|
| | | //| https://www.mql5.com/en/users/fats |
|
| | | //+------------------------------------------------------------------+
|
| | | //| |
|
2026-10-16 21:48:01 +03:00 | | | //| Economic calendar filter. |
|
StopFirst 1.00, the read-only core
Fourteen headers, a header-only MQL5 library: lot size from risk, pips on
forex, gold and indices, broker checks, bars and candle patterns, cached ATR,
sessions in GMT, account risk, symbol suffixes, a calendar filter. Sends no
trade requests. Plus the LotByRisk example script.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 21:40:34 +03:00 | | | //| |
|
2026-10-16 21:48:01 +03:00 | | | //| Read-only: sends no trade requests. |
|
StopFirst 1.00, the read-only core
Fourteen headers, a header-only MQL5 library: lot size from risk, pips on
forex, gold and indices, broker checks, bars and candle patterns, cached ATR,
sessions in GMT, account risk, symbol suffixes, a calendar filter. Sends no
trade requests. Plus the LotByRisk example script.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 21:40:34 +03:00 | | | //| |
|
| | | //| Uses the terminal's built-in calendar - CalendarValueHistory, |
|
| | | //| CalendarEventById, CalendarCountryById. No WebRequest, no DLL, |
|
| | | //| nothing that would block a Market submission. |
|
| | | //| |
|
| | | //| NOT AVAILABLE IN THE STRATEGY TESTER. The calendar functions |
|
| | | //| return nothing there - the tester has no calendar database. |
|
| | | //| Every query below detects that and returns a DOCUMENTED NEUTRAL |
|
| | | //| result: "no news found", with IsCalendarAvailable() false so a |
|
| | | //| caller can tell "nothing scheduled" from "cannot see". |
|
| | | //| |
|
| | | //| That choice matters. A news filter that fails closed would stop |
|
| | | //| every backtest dead; one that fails open silently makes |
|
| | | //| backtests disagree with live for reasons nobody can find. The |
|
| | | //| neutral result plus an explicit flag is the only honest option. |
|
| | | //| |
|
| | | //+------------------------------------------------------------------+
|
| | | #ifndef STOPFIRST_NEWS_MQH
|
| | | #define STOPFIRST_NEWS_MQH
|
| | |
|
| | | #include "Defs.mqh"
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| One scheduled event |
|
| | | //+------------------------------------------------------------------+
|
| | | struct SNewsEvent
|
| | | {
|
| | | ulong eventId;
|
| | | datetime time; // server time
|
| | | string name;
|
| | | string currency;
|
| | | ENUM_CALENDAR_EVENT_IMPORTANCE importance;
|
| | | int minutesAway; // negative once past
|
| | | };
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| Can the calendar be read at all? |
|
| | | //| |
|
| | | //| False in the Strategy Tester, and on a terminal whose calendar |
|
| | | //| has never synchronised. Check this before believing an empty |
|
| | | //| result. |
|
| | | //| |
|
| | | //| Returns: true if the calendar answered a probe query. |
|
| | | //+------------------------------------------------------------------+
|
| | | bool IsCalendarAvailable(void)
|
| | | {
|
| | | //--- the tester has no calendar database at all
|
| | | if(MQLInfoInteger(MQL_TESTER)) return false;
|
| | |
|
| | | MqlCalendarValue values[];
|
| | | datetime from = TimeTradeServer() - 7 * 24 * 3600;
|
| | | datetime to = TimeTradeServer() + 7 * 24 * 3600;
|
| | |
|
| | | //--- a fortnight around now contains events for any live calendar;
|
| | | //--- an empty answer here means the database is absent, not quiet
|
| | | int count = CalendarValueHistory(values, from, to, NULL, NULL);
|
| | |
|
| | | return count > 0;
|
| | | }
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| The two currencies a symbol depends on |
|
| | | //| |
|
| | | //| Reads SYMBOL_CURRENCY_BASE and SYMBOL_CURRENCY_PROFIT, so it is |
|
| | | //| right for XAUUSD (XAU/USD) and for an index priced in a currency |
|
| | | //| as well as for a plain pair. |
|
| | | //| |
|
| | | //| Returns: true if at least one currency was found. |
|
| | | //+------------------------------------------------------------------+
|
| | | bool SymbolCurrencies(const string symbol, string &base, string &profit)
|
| | | {
|
| | | string resolved = (symbol == "") ? _Symbol : symbol;
|
| | |
|
| | | base = SymbolInfoString(resolved, SYMBOL_CURRENCY_BASE);
|
| | | profit = SymbolInfoString(resolved, SYMBOL_CURRENCY_PROFIT);
|
| | |
|
| | | return (base != "" || profit != "");
|
| | | }
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| Events for one currency within a time window |
|
| | | //| |
|
| | | //| Parameters: |
|
| | | //| currency - ISO code, e.g. "USD" |
|
| | | //| from, to - server time bounds |
|
| | | //| minImportance - events below this are dropped |
|
| | | //| events - receives the matches, soonest first |
|
| | | //| |
|
| | | //| Returns: how many were written. 0 in the tester - check |
|
| | | //| IsCalendarAvailable() before reading that as "quiet". |
|
| | | //+------------------------------------------------------------------+
|
| | | int CalendarEventsFor(const string currency, const datetime from, const datetime to,
|
| | | const ENUM_CALENDAR_EVENT_IMPORTANCE minImportance,
|
| | | SNewsEvent &events[])
|
| | | {
|
| | | ArrayResize(events, 0);
|
| | |
|
| | | if(currency == "") return 0;
|
| | | if(MQLInfoInteger(MQL_TESTER)) return 0;
|
| | |
|
| | | MqlCalendarValue values[];
|
| | | int total = CalendarValueHistory(values, from, to, NULL, currency);
|
| | | if(total <= 0) return 0;
|
| | |
|
| | | int written = 0;
|
| | | datetime now = TimeTradeServer();
|
| | |
|
| | | for(int i = 0; i < total; i++)
|
| | | {
|
| | | MqlCalendarEvent event;
|
| | | if(!CalendarEventById(values[i].event_id, event)) continue;
|
| | |
|
| | | if(event.importance < minImportance) continue;
|
| | |
|
| | | ArrayResize(events, written + 1);
|
| | | events[written].eventId = values[i].event_id;
|
| | | events[written].time = values[i].time;
|
| | | events[written].name = event.name;
|
| | | events[written].currency = currency;
|
| | | events[written].importance = event.importance;
|
| | | events[written].minutesAway = (int)((values[i].time - now) / 60);
|
| | | written++;
|
| | | }
|
| | |
|
| | | //--- soonest first; CalendarValueHistory does not promise an order
|
| | | for(int i = 0; i < written - 1; i++)
|
| | | for(int j = i + 1; j < written; j++)
|
| | | if(events[j].time < events[i].time)
|
| | | {
|
| | | SNewsEvent swap = events[i];
|
| | | events[i] = events[j];
|
| | | events[j] = swap;
|
| | | }
|
| | |
|
| | | return written;
|
| | | }
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| Is an event due near a given time, for a symbol's currencies? |
|
| | | //| |
|
| | | //| This is the question a trading filter actually asks. |
|
| | | //| |
|
| | | //| Parameters: |
|
| | | //| symbol - "" for the current chart symbol |
|
| | | //| minImportance - usually CALENDAR_IMPORTANCE_HIGH |
|
| | | //| minutesBefore, minutesAfter - the window around `when` |
|
| | | //| when - 0 for now |
|
| | | //| |
|
| | | //| Returns: true if a qualifying event falls inside the window. |
|
| | | //| ALWAYS false in the Strategy Tester, where the calendar |
|
| | | //| cannot be read - so a backtest behaves as though no news |
|
| | | //| ever happens. Gate on IsCalendarAvailable() if that |
|
| | | //| difference matters to the strategy. |
|
| | | //+------------------------------------------------------------------+
|
| | | bool IsNewsNear(const string symbol = "",
|
| | | const ENUM_CALENDAR_EVENT_IMPORTANCE minImportance = CALENDAR_IMPORTANCE_HIGH,
|
| | | const int minutesBefore = 30, const int minutesAfter = 30,
|
| | | const datetime when = 0)
|
| | | {
|
| | | if(MQLInfoInteger(MQL_TESTER)) return false;
|
| | |
|
| | | string base, profit;
|
| | | if(!SymbolCurrencies(symbol, base, profit)) return false;
|
| | |
|
| | | datetime centre = (when == 0) ? TimeTradeServer() : when;
|
| | | datetime from = centre - minutesBefore * 60;
|
| | | datetime to = centre + minutesAfter * 60;
|
| | |
|
| | | SNewsEvent events[];
|
| | |
|
| | | if(base != "" && CalendarEventsFor(base, from, to, minImportance, events) > 0)
|
| | | return true;
|
| | |
|
| | | //--- a pair depends on both sides; USD news moves EURUSD as much
|
| | | //--- as EUR news does
|
| | | if(profit != "" && profit != base &&
|
| | | CalendarEventsFor(profit, from, to, minImportance, events) > 0)
|
| | | return true;
|
| | |
|
| | | return false;
|
| | | }
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| Upcoming events for a symbol's currencies |
|
| | | //| |
|
| | | //| Parameters: |
|
| | | //| hoursAhead - how far forward to look |
|
| | | //| events - receives the matches, soonest first |
|
| | | //| |
|
| | | //| Returns: how many were written. 0 in the tester. |
|
| | | //+------------------------------------------------------------------+
|
| | | int UpcomingNews(SNewsEvent &events[], const string symbol = "",
|
| | | const ENUM_CALENDAR_EVENT_IMPORTANCE minImportance = CALENDAR_IMPORTANCE_HIGH,
|
| | | const int hoursAhead = 24)
|
| | | {
|
| | | ArrayResize(events, 0);
|
| | |
|
| | | if(MQLInfoInteger(MQL_TESTER)) return 0;
|
| | |
|
| | | string base, profit;
|
| | | if(!SymbolCurrencies(symbol, base, profit)) return 0;
|
| | |
|
| | | datetime now = TimeTradeServer();
|
| | | datetime to = now + hoursAhead * 3600;
|
| | |
|
| | | SNewsEvent baseEvents[], profitEvents[];
|
| | | int baseCount = 0, profitCount = 0;
|
| | |
|
| | | if(base != "")
|
| | | baseCount = CalendarEventsFor(base, now, to, minImportance, baseEvents);
|
| | |
|
| | | if(profit != "" && profit != base)
|
| | | profitCount = CalendarEventsFor(profit, now, to, minImportance, profitEvents);
|
| | |
|
| | | ArrayResize(events, baseCount + profitCount);
|
| | |
|
| | | for(int i = 0; i < baseCount; i++) events[i] = baseEvents[i];
|
| | | for(int i = 0; i < profitCount; i++) events[baseCount + i] = profitEvents[i];
|
| | |
|
| | | int total = baseCount + profitCount;
|
| | |
|
| | | for(int i = 0; i < total - 1; i++)
|
| | | for(int j = i + 1; j < total; j++)
|
| | | if(events[j].time < events[i].time)
|
| | | {
|
| | | SNewsEvent swap = events[i];
|
| | | events[i] = events[j];
|
| | | events[j] = swap;
|
| | | }
|
| | |
|
| | | return total;
|
| | | }
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| Minutes until the next qualifying event |
|
| | | //| Returns: minutes, or -1 if nothing is scheduled in the window or |
|
| | | //| the calendar cannot be read. |
|
| | | //+------------------------------------------------------------------+
|
| | | int MinutesToNextNews(const string symbol = "",
|
| | | const ENUM_CALENDAR_EVENT_IMPORTANCE minImportance = CALENDAR_IMPORTANCE_HIGH,
|
| | | const int hoursAhead = 24)
|
| | | {
|
| | | SNewsEvent events[];
|
| | | int count = UpcomingNews(events, symbol, minImportance, hoursAhead);
|
| | |
|
| | | if(count <= 0) return -1;
|
| | |
|
| | | return events[0].minutesAway;
|
| | | }
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| Importance as text |
|
| | | //| Returns: "none", "low", "moderate" or "high". |
|
| | | //+------------------------------------------------------------------+
|
| | | string ImportanceToString(const ENUM_CALENDAR_EVENT_IMPORTANCE importance)
|
| | | {
|
| | | switch(importance)
|
| | | {
|
| | | case CALENDAR_IMPORTANCE_NONE: return "none";
|
| | | case CALENDAR_IMPORTANCE_LOW: return "low";
|
| | | case CALENDAR_IMPORTANCE_MODERATE: return "moderate";
|
| | | case CALENDAR_IMPORTANCE_HIGH: return "high";
|
| | | }
|
| | |
|
| | | return "unknown";
|
| | | }
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| Multi-line upcoming-news summary |
|
| | | //| |
|
| | | //| States explicitly when the calendar is unreadable, so an empty |
|
| | | //| report is never mistaken for a quiet day. |
|
| | | //| |
|
| | | //| Returns: the report. Never empty. |
|
| | | //+------------------------------------------------------------------+
|
| | | string NewsReport(const string symbol = "",
|
| | | const ENUM_CALENDAR_EVENT_IMPORTANCE minImportance = CALENDAR_IMPORTANCE_HIGH,
|
| | | const int hoursAhead = 24)
|
| | | {
|
| | | string resolved = (symbol == "") ? _Symbol : symbol;
|
| | | string report = "=== News for " + resolved + " ===\n";
|
| | |
|
| | | if(MQLInfoInteger(MQL_TESTER))
|
| | | {
|
| | | report += "Calendar UNAVAILABLE: the Strategy Tester has no calendar\n";
|
| | | report += "database. Every query returns 'no news', which is a neutral\n";
|
| | | report += "default and not an observation.\n";
|
| | | return report;
|
| | | }
|
| | |
|
| | | if(!IsCalendarAvailable())
|
| | | {
|
| | | report += "Calendar UNAVAILABLE: the terminal returned no events at all.\n";
|
| | | report += "It may never have synchronised. Treat 'no news' as unknown.\n";
|
| | | return report;
|
| | | }
|
| | |
|
| | | string base, profit;
|
| | | SymbolCurrencies(resolved, base, profit);
|
| | | report += "Currencies: " + base + (profit != base ? " / " + profit : "") + "\n";
|
| | |
|
| | | SNewsEvent events[];
|
| | | int count = UpcomingNews(events, resolved, minImportance, hoursAhead);
|
| | |
|
| | | report += "Importance: " + ImportanceToString(minImportance) + " or above\n";
|
| | | report += "Window: next " + IntegerToString(hoursAhead) + " hours\n";
|
| | |
|
| | | if(count == 0)
|
| | | {
|
| | | report += "Nothing scheduled.\n";
|
| | | return report;
|
| | | }
|
| | |
|
| | | for(int i = 0; i < count; i++)
|
| | | report += StringFormat(" %s %-4s %-6s in %4d min %s\n",
|
| | | TimeToString(events[i].time, TIME_DATE | TIME_MINUTES),
|
| | | events[i].currency,
|
| | | ImportanceToString(events[i].importance),
|
| | | events[i].minutesAway, events[i].name);
|
| | |
|
| | | return report;
|
| | | }
|
| | |
|
| | | #endif // STOPFIRST_NEWS_MQH
|
| | | //+------------------------------------------------------------------+
|