//+------------------------------------------------------------------+ //| CalendarHistoryExport.mq5 | //| AnimateDread | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "AnimateDread" #property link "https://www.mql5.com" #property version "1.00" #property script_show_inputs #property description "Dumps the terminal's whole economic calendar to Common\\Files, once." #property description "Run it on ANY terminal that HAS a calendar - the folder is shared." //--- WHY THIS EXISTS AND WHEN TO REACH FOR IT //--- //--- Measured 2026-09-06 on the fleet terminal: CalendarValueHistory over 2010..now with a //--- NULL country and a NULL currency returns ZERO rows and error 0 - success, no data - //--- WHILE THE TOOLBOX CALENDAR TAB IS FULL. The data is present; the call is being refused. //--- (I first read that as "this broker has no calendar", from an empty news.dat - which is //--- the unrelated news-headline feed - and from no file named *calendar* on disk. The //--- operator corrected it by looking at the tab. Absence of a filename I guessed at is not //--- absence of data.) //--- //--- So this script PROBES before it exports. It reports what CalendarCountries() knows, then //--- tries the history call four ways and prints n and the error for each: //--- A (NULL, NULL) the documented "everything", and the one that returns nothing //--- B ("", "") in case this build wants empty strings rather than NULL //--- C narrow range last InpProbeDays days, to separate "range too wide" from "no data" //--- D per country CalendarCountries() then one call per country code, accumulated //--- It exports the first variant that returns rows, preferring the widest. If they all come //--- back empty the printout says which, and that is the fact to act on rather than a guess. //--- //--- It is ALSO the way to use a different terminal: FILE_COMMON resolves to the SAME //--- Common\Files folder for every standard MT5 install of one Windows user, so a terminal //--- that can read its calendar writes the file the research already reads, and nothing about //--- the fleet terminal has to change. A SCRIPT and not the EA because such a terminal is a //--- borrowed one: it should not have to host a trading system, its symbols and its models //--- just to hand over a CSV. //--- //--- WHAT IT IS NOT. It is not Scripts/CalendarRecorder.mq5 and does not replace it. That //--- one records releases AT release time, write-once, because actual_value is the //--- POST-REVISION figure and a surprise built from history is contaminated in the //--- flattering direction - strongest on exactly the events that were revised most. This //--- dumps history, which is honest for precisely the columns that are never restated: the //--- release TIME and the event's importance. Those are what the books' news blackout //--- (T-5/T+15) and the two news setups actually need. research/newsdata.py keeps the //--- surprise behind allow_surprise=False for the same reason. //--- //--- Deliberately NO includes from the EA tree, the same argument CalendarRecorder makes: //--- it has to compile on a machine that has none of it. input datetime InpFrom = D'2010.01.01 00:00'; // History from input int InpMinImportance = 0; // 0=all 1=low 2=moderate 3=high input int InpProbeDays = 30; // Narrow-range probe, in days back input string InpFileName = "Warrior_EA\\News\\calendar.csv"; #define CAL_SCALE 1000000.0 //--- Must match System/NewsExport.mqh column for column: research/newsdata.py reads whichever //--- of the two wrote the file and cannot be asked to tell them apart. #define NEWS_HEADER "time;country;currency;importance;event_id;value_id;name;actual;forecast;previous;revised;actual_raw;forecast_raw;previous_raw;impact;unit;multiplier;digits;sector;event_type" //--- ';' is the separator and event names contain them, so they are replaced rather than //--- quoted: a quoted field would need an escape convention the reader does not have. string Esc(const string s) { string o = s; StringReplace(o, ";", ","); StringReplace(o, "\n", " "); StringReplace(o, "\r", " "); return(o); } //--- LONG_MIN is the terminal's "no value" sentinel. It becomes EMPTY, not zero: a figure //--- reported as 0 is an impossible reading rather than a missing one, and the research has //--- to be able to tell the two apart. string Scaled(const long v, const int digits) { if(v == LONG_MIN) return(""); return(DoubleToString(v / CAL_SCALE, (digits >= 0 && digits <= 8) ? digits : 6)); } string Raw(const long v) { if(v == LONG_MIN) return(""); return(IntegerToString(v)); } //--- One attempt, reported. Returns the row count and leaves the rows in `out`. int Try(MqlCalendarValue &out[], const string label, const datetime from, const datetime to, const string country, const string ccy) { ArrayFree(out); ResetLastError(); const int n = CalendarValueHistory(out, from, to, country, ccy); const int err = GetLastError(); PrintFormat(" %-28s n=%d error=%d", label, n, err); return(n > 0 ? n : 0); } void OnStart() { if(MQLInfoInteger(MQL_TESTER)) { Print("CalendarHistoryExport: the calendar API returns nothing in the tester. " "Run this on a live chart."); return; } const datetime now = TimeCurrent(); //--- WHAT THE TERMINAL ADMITS TO KNOWING. If this is 0 the calendar base really is absent and //--- no variant below can help; if it is ~30 the base is loaded and the fault is in the query. MqlCalendarCountry countries[]; ResetLastError(); const int nc = CalendarCountries(countries); PrintFormat("CalendarHistoryExport: CalendarCountries() = %d (error %d). Probing the history " "call - the Toolbox Calendar tab being full means the data is here somewhere.", nc, GetLastError()); MqlCalendarValue values[]; int n = Try(values, "A (NULL, NULL) full range", InpFrom, now, NULL, NULL); if(n == 0) n = Try(values, "B (\"\", \"\") full range", InpFrom, now, "", ""); if(n == 0 && InpProbeDays > 0) n = Try(values, "C narrow range", now - (datetime)InpProbeDays * 86400, now, NULL, NULL); if(n == 0 && nc > 0) { //--- D: one call per country, accumulated. Slower, but it is the variant that does not //--- depend on NULL meaning "all" - and if THIS is the one that works, that is the answer. Print(" D per country - accumulating:"); MqlCalendarValue part[]; for(int i = 0; i < nc; i++) { ArrayFree(part); ResetLastError(); const int m = CalendarValueHistory(part, InpFrom, now, countries[i].code, NULL); if(m <= 0) continue; const int at = ArraySize(values); ArrayResize(values, at + m); for(int j = 0; j < m; j++) values[at + j] = part[j]; } n = ArraySize(values); PrintFormat(" %-28s n=%d", "D per country total", n); } if(n <= 0) { PrintFormat("CalendarHistoryExport: every variant returned nothing (countries=%d). The " "Calendar TAB reads the terminal's own store directly, so a full tab with an " "empty API is a terminal-side refusal, not a missing feed - report the four " "lines above rather than assuming which cause it is.", nc); return; } //--- Written under a temp name and moved into place, so no reader can ever see half a file. const string tmp = InpFileName + ".tmp"; const int h = FileOpen(tmp, FILE_WRITE | FILE_TXT | FILE_ANSI | FILE_COMMON | FILE_SHARE_READ | FILE_SHARE_WRITE); if(h == INVALID_HANDLE) { PrintFormat("CalendarHistoryExport: cannot open %s (%d)", tmp, GetLastError()); return; } FileWriteString(h, NEWS_HEADER + "\n"); //--- event and country lookups repeat heavily and are per-id, so they are cached by id ulong evIds[]; string evName[], evCountry[], evCurrency[]; int evImp[], evUnit[], evMult[], evDig[], evSector[], evType[]; int written = 0, skipped = 0; datetime lo = 0, hi = 0; for(int i = 0; i < n; i++) { MqlCalendarValue v = values[i]; int k = -1; for(int j = ArraySize(evIds) - 1; j >= 0; j--) if(evIds[j] == v.event_id) { k = j; break; } if(k < 0) { MqlCalendarEvent ev; if(!CalendarEventById(v.event_id, ev)) { skipped++; continue; } MqlCalendarCountry co; const bool okc = CalendarCountryById(ev.country_id, co); k = ArraySize(evIds); ArrayResize(evIds, k + 1); ArrayResize(evName, k + 1); ArrayResize(evImp, k + 1); ArrayResize(evUnit, k + 1); ArrayResize(evMult, k + 1); ArrayResize(evDig, k + 1); ArrayResize(evSector, k + 1); ArrayResize(evType, k + 1); ArrayResize(evCountry, k + 1); ArrayResize(evCurrency, k + 1); evIds[k] = v.event_id; evName[k] = Esc(ev.name); evImp[k] = (int)ev.importance; evUnit[k] = (int)ev.unit; evMult[k] = (int)ev.multiplier; evDig[k] = (int)ev.digits; evSector[k] = (int)ev.sector; evType[k] = (int)ev.type; evCountry[k] = okc ? Esc(co.code) : ""; evCurrency[k] = okc ? Esc(co.currency) : ""; } if(evImp[k] < InpMinImportance) { skipped++; continue; } const int dg = evDig[k]; FileWriteString(h, TimeToString(v.time, TIME_DATE | TIME_MINUTES) + ";" + evCountry[k] + ";" + evCurrency[k] + ";" + IntegerToString(evImp[k]) + ";" + IntegerToString((long)v.event_id) + ";" + IntegerToString((long)v.id) + ";" + evName[k] + ";" + Scaled(v.actual_value, dg) + ";" + Scaled(v.forecast_value, dg) + ";" + Scaled(v.prev_value, dg) + ";" + Scaled(v.revised_prev_value, dg) + ";" + Raw(v.actual_value) + ";" + Raw(v.forecast_value) + ";" + Raw(v.prev_value) + ";" + IntegerToString((int)v.impact_type) + ";" + IntegerToString(evUnit[k]) + ";" + IntegerToString(evMult[k]) + ";" + IntegerToString(dg) + ";" + IntegerToString(evSector[k]) + ";" + IntegerToString(evType[k]) + "\n"); if(lo == 0 || v.time < lo) lo = v.time; if(v.time > hi) hi = v.time; written++; } FileClose(h); if(!FileMove(tmp, FILE_COMMON, InpFileName, FILE_COMMON | FILE_REWRITE)) { PrintFormat("CalendarHistoryExport: wrote %s but could not move it into place (%d)", tmp, GetLastError()); return; } PrintFormat("CalendarHistoryExport: %s - %d releases of %d distinct events, %s to %s " "(%d skipped). If this is not the research machine, copy the file across.", InpFileName, written, ArraySize(evIds), TimeToString(lo, TIME_DATE), TimeToString(hi, TIME_DATE), skipped); } //+------------------------------------------------------------------+