The operator checked the Toolbox: the Calendar tab is FULL. Calendar data comes
from MetaQuotes' servers; the News tab is the broker's own feed and is separately
empty. So my conclusion this morning - "this broker delivers no calendar" - was
wrong, and it was built on proxies: an empty news.dat, which is the unrelated
news-headline subsystem, and the absence of any file named *calendar* on disk,
which only means the store is not named what I guessed. One glance at the tab
settled what three filesystem checks could not.
The real fact is narrower and stranger: CalendarValueHistory over 2010..now with
NULL country and NULL currency returns zero rows and error 0 - success, no data -
while the terminal plainly holds the data. So the script now probes before it
exports, reporting CalendarCountries() and then trying the call four ways with n
and error printed for each: (NULL,NULL), ("",""), a narrow recent range, and a
per-country loop over CalendarCountries(). It exports the first variant that
returns rows, preferring the widest, and if all four are empty it says so rather
than offering a cause.
Compile-verified in _claude_stage: 0 errors, 0 warnings. Deployed to the fleet
terminal's MQL5\Scripts so it can be dragged onto a chart.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
249 lines
12 KiB
MQL5
249 lines
12 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| 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);
|
|
}
|
|
//+------------------------------------------------------------------+
|