Article-22063-Alternative-B.../Article-22063-Alternative-Bars-For-Market-Intent.mq5

345 lines
12 KiB
MQL5
Raw Permalink Normal View History

2026-06-09 22:25:49 +03:00
//+------------------------------------------------------------------+
//| Article-22063-Alternative-Bars-For-Market-Intent.mq5 |
//| Patrick M. Njoroge |
//| https://www.mql5.com/en/users/patricknjoroge743 |
//+------------------------------------------------------------------+
//| Example EA that constructs alternative bars from the live tick |
//| stream and writes them to CSV for downstream feature pipelines |
//| or parity testing against the Python afml.data_structures.bars |
//| implementation. |
//| |
//| Usage: |
//| 1. Drop onto a chart, set InpBarType and InpBarSize. |
//| 2. For imbalance bars, ensure the JSON config file exists |
//| in MQL5/Files/Common or supply InpExpTicksInit directly. |
//| 3. Run in Strategy Tester for parity verification, or on a |
//| live chart for deployment. |
//| |
//| NOTE: All dollar-based bar types now use the mid-price |
//| ((bid + ask)/2) as the valuation basis. This reduces |
//| noise from bid-ask bounce and gives a more representative |
//| trade value. |
//+------------------------------------------------------------------+
#property copyright "Patrick M. Njoroge"
#property link "https://www.mql5.com/en/users/patricknjoroge743"
#property version "1.00"
#include <AlternativeBars\CBarConstructor.mqh>
#include <AlternativeBars\CStandardBars.mqh>
#include <AlternativeBars\CImbalanceBars.mqh>
#include <AlternativeBars\CRunsBar.mqh>
//--- Bar type selector
enum ENUM_BAR_TYPE
{
BAR_TIME = 0,
BAR_TICK = 1,
BAR_VOLUME = 2,
BAR_DOLLAR = 3,
BAR_TICK_IMB = 4,
BAR_VOLUME_IMB = 5,
BAR_DOLLAR_IMB = 6,
BAR_TICK_RUNS = 7,
BAR_VOLUME_RUNS = 8,
BAR_DOLLAR_RUNS = 9
};
//--- Inputs
input ENUM_BAR_TYPE InpBarType = BAR_DOLLAR; // Bar type
input double InpBarSize = 10000000.0; // Threshold (seconds for time; count for tick; volume/dollar units otherwise)
input double InpExpTicksInit = 300.0; // Initial E_0[T] for imbalance/run bars
input double InpExpImbInit = 0.1; // Initial E_0[|imbalance|] for imbalance bars
input double InpExpRunBuyInit = 0.1; // Initial E_0[theta_buy/T] for run bars
input double InpExpRunSellInit = 0.1; // Initial E_0[theta_sell/T] for run bars
input int InpEwmSpan = 20; // EWM span for adaptation
input bool InpUseStateFile = true; // Persist EWM state across EA restarts
input string InpStateFile = "bar_state.bin"; // State file (MQL5/Files/Common)
input int InpStateMaxAgeMin = 1440; // Max staleness (minutes); older state is discarded
//--- Globals
CBarConstructor *g_bar = NULL;
long g_tick_num = 0;
int g_csv_handle = INVALID_HANDLE;
datetime g_state_saved = 0;
//+------------------------------------------------------------------+
//| Factory to instantiate the bar constructor for the given type |
//+------------------------------------------------------------------+
CBarConstructor *CreateBarConstructor(void)
{
switch(InpBarType)
{
case BAR_TIME:
return new CTimeBar((int)InpBarSize);
case BAR_TICK:
return new CTickBar((int)InpBarSize);
case BAR_VOLUME:
return new CVolumeBar(InpBarSize);
case BAR_DOLLAR:
return new CDollarBar(InpBarSize);
case BAR_TICK_IMB:
return new CImbalanceBar(IMB_METRIC_TICK,
InpExpTicksInit,
InpExpImbInit,
InpEwmSpan);
case BAR_VOLUME_IMB:
return new CImbalanceBar(IMB_METRIC_VOLUME,
InpExpTicksInit,
InpExpImbInit,
InpEwmSpan);
case BAR_DOLLAR_IMB:
return new CImbalanceBar(IMB_METRIC_DOLLAR,
InpExpTicksInit,
InpExpImbInit,
InpEwmSpan);
case BAR_TICK_RUNS:
return new CRunsBar(IMB_METRIC_TICK,
InpExpTicksInit,
InpExpRunBuyInit,
InpExpRunSellInit,
InpEwmSpan);
case BAR_VOLUME_RUNS:
return new CRunsBar(IMB_METRIC_VOLUME,
InpExpTicksInit,
InpExpRunBuyInit,
InpExpRunSellInit,
InpEwmSpan);
case BAR_DOLLAR_RUNS:
return new CRunsBar(IMB_METRIC_DOLLAR,
InpExpTicksInit,
InpExpRunBuyInit,
InpExpRunSellInit,
InpEwmSpan);
}
return NULL;
}
//+------------------------------------------------------------------+
//| State file helpers |
//+------------------------------------------------------------------+
bool RestoreState(void)
{
if(!InpUseStateFile)
return false;
if(!FileIsExist(InpStateFile, FILE_COMMON))
return false;
int fh = FileOpen(InpStateFile, FILE_READ | FILE_BIN | FILE_COMMON);
if(fh == INVALID_HANDLE)
{
PrintFormat("RestoreState: FileOpen failed, err=%d", GetLastError());
return false;
}
//--- Check staleness
datetime saved = (datetime)FileReadLong(fh);
if((TimeCurrent() - saved) > (InpStateMaxAgeMin * 60))
{
FileClose(fh);
PrintFormat("State file is stale (saved at %s); discarding.",
TimeToString(saved));
return false;
}
g_tick_num = FileReadLong(fh);
bool ok = g_bar.LoadState(fh);
FileClose(fh);
if(ok)
PrintFormat("State restored: saved=%s, tick_num=%d, type=%s",
TimeToString(saved), g_tick_num, g_bar.BarType());
return ok;
}
//+------------------------------------------------------------------+
//| PersistState |
//+------------------------------------------------------------------+
bool PersistState(void)
{
if(!InpUseStateFile || g_bar == NULL)
return false;
int fh = FileOpen(InpStateFile, FILE_WRITE | FILE_BIN | FILE_COMMON);
if(fh == INVALID_HANDLE)
{
PrintFormat("PersistState: FileOpen failed, err=%d", GetLastError());
return false;
}
FileWriteLong(fh, (long)TimeCurrent());
FileWriteLong(fh, g_tick_num);
bool ok = g_bar.SaveState(fh);
FileClose(fh);
if(ok)
PrintFormat("State saved at tick_num=%d", g_tick_num);
return ok;
}
//+------------------------------------------------------------------+
//| CSV output helpers |
//+------------------------------------------------------------------+
bool OpenCsvOutput(void)
{
//--- Build a unique suffix from the parameters that distinguish runs
string suffix;
switch(InpBarType)
{
case BAR_TIME:
case BAR_TICK:
case BAR_VOLUME:
case BAR_DOLLAR:
suffix = StringFormat("%s_size%g",
g_bar.BarType(),
InpBarSize);
break;
default: // all information bars (imbalance & run)
suffix = StringFormat("%s_span%d_size%g",
g_bar.BarType(),
InpEwmSpan,
InpExpTicksInit);
//--- For run bars, also append the buy/sell initial values
if(InpBarType == BAR_TICK_RUNS ||
InpBarType == BAR_VOLUME_RUNS ||
InpBarType == BAR_DOLLAR_RUNS)
{
suffix += StringFormat("_buy%g_sell%g",
InpExpRunBuyInit,
InpExpRunSellInit);
}
break;
}
string fname = _Symbol + "_" + suffix + ".csv";
bool existed = FileIsExist(fname);
g_csv_handle = FileOpen(fname,
FILE_WRITE | FILE_READ | FILE_CSV | FILE_ANSI,
',');
if(g_csv_handle == INVALID_HANDLE)
{
PrintFormat("OpenCsvOutput: FileOpen('%s') failed, err=%d",
fname, GetLastError());
return false;
}
FileSeek(g_csv_handle, 0, SEEK_END);
if(!existed || FileTell(g_csv_handle) == 0)
{
FileWrite(g_csv_handle,
"time", "open", "high", "low", "close",
"mid_open", "mid_close",
"tick_volume", "volume", "spread",
"tick_num", "bar_type");
}
return true;
}
//+------------------------------------------------------------------+
//| WriteBarToCsv |
//+------------------------------------------------------------------+
void WriteBarToCsv(const SBar &bar)
{
if(g_csv_handle == INVALID_HANDLE)
return;
FileWrite(g_csv_handle,
TimeToString(bar.time, TIME_DATE | TIME_SECONDS),
DoubleToString(bar.open, _Digits),
DoubleToString(bar.high, _Digits),
DoubleToString(bar.low, _Digits),
DoubleToString(bar.close, _Digits),
DoubleToString(bar.mid_open, _Digits),
DoubleToString(bar.mid_close, _Digits),
IntegerToString(bar.tick_volume),
DoubleToString(bar.volume, 2),
DoubleToString(bar.spread, 5),
IntegerToString(bar.tick_num),
bar.bar_type);
FileFlush(g_csv_handle);
}
//+------------------------------------------------------------------+
//| OnInit |
//+------------------------------------------------------------------+
int OnInit()
{
g_bar = CreateBarConstructor();
if(g_bar == NULL)
{
Print("OnInit: unknown bar type");
return INIT_FAILED;
}
RestoreState();
if(!OpenCsvOutput())
{
delete g_bar;
g_bar = NULL;
return INIT_FAILED;
}
PrintFormat("BarBuilderEA: type=%s, bar_size=%g, state=%s, output=%s",
g_bar.BarType(),
InpBarSize,
InpUseStateFile ? "persisted" : "ephemeral",
_Symbol + "_" + g_bar.BarType() + ".csv");
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
//| OnTick |
//+------------------------------------------------------------------+
void OnTick()
{
MqlTick tick;
if(!SymbolInfoTick(_Symbol, tick))
return;
g_tick_num += 1;
SBar bar;
if(g_bar.ProcessTick(tick, g_tick_num, bar))
WriteBarToCsv(bar);
}
//+------------------------------------------------------------------+
//| OnDeinit |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
if(g_csv_handle != INVALID_HANDLE)
{
FileClose(g_csv_handle);
g_csv_handle = INVALID_HANDLE;
}
//--- Persist state unless the EA is being removed by the user
if(reason == REASON_REMOVE)
{
if(InpUseStateFile && FileIsExist(InpStateFile, FILE_COMMON))
FileDelete(InpStateFile, FILE_COMMON);
}
else
{
PersistState();
}
if(g_bar != NULL)
{
delete g_bar;
g_bar = NULL;
}
}
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+