forked from mnbvc188199/Warrior_EA
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);
| |
}
| |
//+------------------------------------------------------------------+
|