- Replaced standard library signal modules with custom implementations to allow for named patterns and improved voting. - Added new input parameters for module weights, allowing for optimization of individual signal contributions. - Enhanced the management of trades with new options for breakeven and management cut. - Introduced a mechanism for dynamic ranking of signal weights based on historical performance. - Improved initialization logic to ensure proper registration of filters and handling of trading conditions. - Added detailed logging for trading permissions and account status during initialization.
193 lines
7.9 KiB
MQL5
193 lines
7.9 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| AltDataFeed.mqh |
|
|
//| AnimateDread |
|
|
//| |
|
|
//| THE DAILY MACRO / POSITIONING BLOCK, read once at init. |
|
|
//| |
|
|
//| Common\Files\ADAltData\<symbol>_D1.csv - 6,096 rows, 2010-01-01 |
|
|
//| onward, semicolon separated. Sixteen usable columns: VIX and its |
|
|
//| 5-day change, the dollar's 5-day change, COT speculative net plus |
|
|
//| its 1y/3y percentiles and 4-week change, three EIA inventory |
|
|
//| series, and six macro series (10y, curve, breakeven, output gap, |
|
|
//| CPI, unemployment). |
|
|
//| |
|
|
//| ⚠ ivol AND ivol_chg5 ARE EMPTY IN EVERY ROW and are not loaded. |
|
|
//| Measured 2026-09-11: 0.0% filled on EURUSD, against ~100% for the |
|
|
//| other sixteen. Loading them would hand the network two constant |
|
|
//| zero columns, which is not neutral - it spends input width and |
|
|
//| lets the optimiser fit noise through the bias attached to them. |
|
|
//| |
|
|
//| WHY IT CANNOT LOOK AHEAD. A row dated D carries values known at |
|
|
//| D's close. Features are built from shift 1 - the last CLOSED bar |
|
|
//| - so the row matching that bar's date was fully observable before |
|
|
//| the bar being decided even opened. `Lookup` therefore takes the |
|
|
//| newest row whose date is <= the closed bar's date, never the row |
|
|
//| after it, and a missing date resolves BACKWARD (a holiday reads |
|
|
//| Friday's macro state, which is what a trader would have had). |
|
|
//| |
|
|
//| AND WHY IT IS NOT ZERO-FILLED. The series begin in 2010. A run |
|
|
//| starting earlier must SKIP those bars, not pad them: a column of |
|
|
//| zeros for 2005-2009 teaches the network the era rather than the |
|
|
//| market, and it would learn "alt data absent" as a tradeable |
|
|
//| state. Lookup() returns false and the caller drops the row. |
|
|
//+------------------------------------------------------------------+
|
|
#ifndef WARRIOR_ALTDATA_FEED_MQH
|
|
#define WARRIOR_ALTDATA_FEED_MQH
|
|
|
|
#define ALT_COLUMNS 16
|
|
|
|
class CAltDataFeed
|
|
{
|
|
private:
|
|
datetime m_date[]; // ascending, one per row
|
|
double m_val[]; // flat, ALT_COLUMNS per row
|
|
int m_rows;
|
|
string m_why;
|
|
string m_loadedFor;
|
|
|
|
//--- The CSV's own column order, minus the two empty ones. Index into the split line.
|
|
//--- 0=date 1=vix 2=vix_chg5 3=usd_chg5 [4=ivol 5=ivol_chg5 SKIPPED] 6=cot_spec_net
|
|
//--- 7=cot_idx_1y 8=cot_idx_3y 9=cot_chg_4w 10..12=eia 13..18=mac
|
|
static void SourceColumns(int &c[])
|
|
{
|
|
ArrayResize(c, ALT_COLUMNS);
|
|
int k = 0;
|
|
c[k++] = 1; c[k++] = 2; c[k++] = 3;
|
|
c[k++] = 6; c[k++] = 7; c[k++] = 8; c[k++] = 9;
|
|
c[k++] = 10; c[k++] = 11; c[k++] = 12;
|
|
c[k++] = 13; c[k++] = 14; c[k++] = 15; c[k++] = 16; c[k++] = 17; c[k++] = 18;
|
|
}
|
|
|
|
public:
|
|
CAltDataFeed(void) : m_rows(0), m_why("not loaded"), m_loadedFor("") {}
|
|
~CAltDataFeed(void) {}
|
|
|
|
bool Ok(void) const { return m_rows > 0; }
|
|
int Rows(void) const { return m_rows; }
|
|
string Why(void) const { return m_why; }
|
|
static string ColumnName(const int i);
|
|
|
|
bool Load(const string symbol);
|
|
//--- Values for the bar CLOSING at `when`. False when the date predates the series.
|
|
bool Lookup(const datetime when, double &out[]) const;
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
string CAltDataFeed::ColumnName(const int i)
|
|
{
|
|
switch(i)
|
|
{
|
|
case 0: return "vix";
|
|
case 1: return "vix_chg5";
|
|
case 2: return "usd_chg5";
|
|
case 3: return "cot_spec_net";
|
|
case 4: return "cot_idx_1y";
|
|
case 5: return "cot_idx_3y";
|
|
case 6: return "cot_chg_4w";
|
|
case 7: return "eia_stk_idx1y";
|
|
case 8: return "eia_stk_chg4";
|
|
case 9: return "eia_util";
|
|
case 10: return "mac_y10";
|
|
case 11: return "mac_curve";
|
|
case 12: return "mac_bei";
|
|
case 13: return "mac_gap";
|
|
case 14: return "mac_cpi";
|
|
case 15: return "mac_unemp";
|
|
}
|
|
return "?";
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
bool CAltDataFeed::Load(const string symbol)
|
|
{
|
|
if(m_loadedFor == symbol && m_rows > 0)
|
|
return true;
|
|
m_rows = 0;
|
|
ArrayResize(m_date, 0);
|
|
ArrayResize(m_val, 0);
|
|
|
|
const string path = "ADAltData\\" + symbol + "_D1.csv";
|
|
//--- FILE_COMMON: the corpus is shared by the tester and the live terminal, exactly like the
|
|
//--- journal database, so one file serves every agent rather than one copy per sandbox.
|
|
const int h = FileOpen(path, FILE_READ | FILE_TXT | FILE_ANSI | FILE_COMMON);
|
|
if(h == INVALID_HANDLE)
|
|
{
|
|
m_why = StringFormat("cannot open Common\\Files\\%s (%d)", path, GetLastError());
|
|
return false;
|
|
}
|
|
int src[];
|
|
SourceColumns(src);
|
|
string parts[];
|
|
bool header = true;
|
|
while(!FileIsEnding(h))
|
|
{
|
|
const string line = FileReadString(h);
|
|
if(header) // the column names, not data
|
|
{ header = false; continue; }
|
|
if(StringLen(line) < 10)
|
|
continue;
|
|
if(StringSplit(line, ';', parts) < 19)
|
|
continue;
|
|
//--- "2010-01-01" -> "2010.01.01"; StringToTime wants dots.
|
|
string d = parts[0];
|
|
StringReplace(d, "-", ".");
|
|
const datetime when = StringToTime(d);
|
|
if(when == 0)
|
|
continue;
|
|
const int r = m_rows;
|
|
ArrayResize(m_date, r + 1);
|
|
ArrayResize(m_val, (r + 1) * ALT_COLUMNS);
|
|
m_date[r] = when;
|
|
for(int c = 0; c < ALT_COLUMNS; c++)
|
|
{
|
|
const string cell = parts[src[c]];
|
|
//--- An empty cell carries the previous row's value forward - a macro series that has not
|
|
//--- printed yet has not CHANGED either, and the last print is what a trader would be
|
|
//--- acting on. Only the very first rows can have nothing to carry, and they read 0.
|
|
double v = 0.0;
|
|
if(StringLen(cell) > 0)
|
|
v = StringToDouble(cell);
|
|
else
|
|
if(r > 0)
|
|
v = m_val[(r - 1) * ALT_COLUMNS + c];
|
|
m_val[r * ALT_COLUMNS + c] = v;
|
|
}
|
|
m_rows++;
|
|
}
|
|
FileClose(h);
|
|
if(m_rows <= 0)
|
|
{
|
|
m_why = "file opened but no usable rows";
|
|
return false;
|
|
}
|
|
m_loadedFor = symbol;
|
|
m_why = "";
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
bool CAltDataFeed::Lookup(const datetime when, double &out[]) const
|
|
{
|
|
ArrayResize(out, ALT_COLUMNS);
|
|
ArrayInitialize(out, 0.0);
|
|
if(m_rows <= 0 || when < m_date[0])
|
|
return false; // predates the series - the caller drops the row
|
|
//--- Newest row at or before `when`. Binary search: this runs once per training row, and a
|
|
//--- linear scan over 6,000 rows x 4,000 rows of training is 24M comparisons per fit.
|
|
int lo = 0, hi = m_rows - 1, best = -1;
|
|
while(lo <= hi)
|
|
{
|
|
const int mid = (lo + hi) / 2;
|
|
if(m_date[mid] <= when)
|
|
{ best = mid; lo = mid + 1; }
|
|
else
|
|
hi = mid - 1;
|
|
}
|
|
if(best < 0)
|
|
return false;
|
|
for(int c = 0; c < ALT_COLUMNS; c++)
|
|
out[c] = m_val[best * ALT_COLUMNS + c];
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| ONE FEED PER CHART, like the Wyckoff one and for the same reason. |
|
|
//+------------------------------------------------------------------+
|
|
CAltDataFeed g_altData;
|
|
#endif // WARRIOR_ALTDATA_FEED_MQH
|