mql5-execution-microstructu.../Include/RequestLatencyLab/MarketRegime.mqh

238 lines
10 KiB
MQL5

//+------------------------------------------------------------------+
//| MarketRegime.mqh |
//| Copyright 2026, MetaQuotes Ltd. |
//| www.mql5.com |
//+------------------------------------------------------------------+
#ifndef REQUEST_LATENCY_LAB_MARKET_REGIME_MQH
#define REQUEST_LATENCY_LAB_MARKET_REGIME_MQH
#include "..\..\Include\RequestLatencyLab\Models.mqh"
#include "..\..\Include\RequestLatencyLab\Legacy\BenchmarkStatistics.mqh"
#define LAB_WINDOW_SECONDS 60
class CMarketRegime
{
public:
//--- R8-S2: FNV-1a 64 — лёгкая контрольная сумма набора тиков (целостность
//--- и однозначность диапазона [from_msc,to_msc] при повторном чтении).
2026-09-12 22:14:24 +03:00
static ulong LabFnv1a(ulong h, const ulong v)
{
2026-09-12 22:14:24 +03:00
h ^= v;
h *= 1099511628211UL;
return(h);
}
//--- R9-S3: вызовы, которым НЕ нужны сырые тики окна (калибратор):
//--- делегируют основной реализации с временным массивом.
2026-09-12 22:14:24 +03:00
static bool ComputeWindow(const string symbol, const long anchor_msc,
const ulong fetch_start_us, MarketWindow &w,
LabError &error)
{
MqlTick tmp[];
2026-09-12 22:14:24 +03:00
return(ComputeWindowRawTicks(symbol, anchor_msc, fetch_start_us, w, error, tmp));
}
//--- чтение причинного окна тиков до опорной котировки
2026-09-12 22:14:24 +03:00
static bool ComputeWindowRawTicks(const string symbol, const long anchor_msc,
const ulong fetch_start_us, MarketWindow &w,
LabError &error, MqlTick &raw_ticks[]);
//--- классификация по порогам калибровки (СП §8)
2026-09-12 22:14:24 +03:00
static void Classify(const MarketWindow &w, const Calibration &cal,
ENUM_LAB_MARKET_REGIME &regime, bool &valid);
//--- аккумулирование признаков минутных окон для калибровки
2026-09-12 22:14:24 +03:00
static bool AggregateWindow(const MarketWindow &w, double &freq[], double &rng[],
int &count, const int capacity);
//--- пороги по набору валидных окон (p50/p90 для f и r)
2026-09-12 22:14:24 +03:00
static bool ComputeThresholds(const double &freq[], const double &rng[],
const int count, Calibration &cal,
LabError &error);
};
//+------------------------------------------------------------------+
//| Внешние определения методов. |
//+------------------------------------------------------------------+
bool CMarketRegime::ComputeWindowRawTicks(const string symbol, const long anchor_msc,
const ulong fetch_start_us, MarketWindow &w, LabError &error, MqlTick &raw_ticks[])
{
error.Reset();
error.component = LAB_COMP_MARKET;
w.Zero();
w.symbol = symbol;
w.anchor_tick_msc = anchor_msc;
//--- R4-B5: единый контракт окна [anchor-60000, anchor-1] (60000 мс
//--- включительно); runtime и калибратор используют один и тот же anchor
//--- (time_msc опорной котировки) без смещений.
w.from_msc = anchor_msc - (long)LAB_WINDOW_SECONDS * 1000;
w.to_msc = anchor_msc - 1;
w.window_id = StringFormat("w%08X_%04X", (uint)(anchor_msc & 0xFFFFFFFF),
(uint)(fetch_start_us & 0xFFFF));
w.fetch_start_us = fetch_start_us;
MqlTick ticks[];
ResetLastError();
const int got = CopyTicksRange(symbol, ticks, COPY_TICKS_INFO, w.from_msc, w.to_msc);
w.copy_last_error = GetLastError();
w.fetch_end_us = GetMicrosecondCount();
if(got <= 0)
{
//--- неполный/пустой диапазон не считается полноценным окном
w.range_complete = false;
w.n_returned = 0;
w.valid = false;
w.quality_codes = "EMPTY_RANGE";
error.code = 1;
error.mql_error = w.copy_last_error;
error.message = "no ticks returned";
return(false);
}
w.n_returned = (ulong)got;
//--- R9-S3: сырые тики диапазона [from_msc,to_msc] целиком — для
//--- автономного offline-пересчёта признаков окна (boundaries+checksum
//--- служат проверкой, а не источником данных).
if(ArrayResize(raw_ticks, got) != got)
{
w.range_complete = false;
w.valid = false;
w.quality_codes = "RAW_TICKS_OOM";
error.code = 2;
error.message = "window raw ticks allocation failed";
return(false);
}
for(int i = 0; i < got; i++)
raw_ticks[i] = ticks[i];
//--- валидные двусторонние INFO-обновления Bid/Ask
ulong n_valid = 0;
double mid_min = DBL_MAX;
double mid_max = -DBL_MAX;
bool crossed = false;
long last_msc = 0;
ulong ticks_hash = 14695981039346656037UL; // R8-S2: FNV-1a offset basis
for(int i = 0; i < got; i++)
{
const MqlTick t = ticks[i];
if(((t.flags & TICK_FLAG_BID) == 0) && ((t.flags & TICK_FLAG_ASK) == 0))
continue;
//--- R4-B5: MqlTick содержит актуальные значения всех полей; flags
//--- сообщают только что изменилось. Полные t.bid/t.ask берутся напрямую.
const double bid = t.bid;
const double ask = t.ask;
if(bid <= 0.0 || ask <= 0.0 || ask < bid)
{
crossed = true;
continue;
}
const double mid = (bid + ask) * 0.5;
if(mid < mid_min)
mid_min = mid;
if(mid > mid_max)
mid_max = mid;
n_valid++;
//--- R8-S2: контрольная сумма валидного набора (детерминирована
//--- содержимым окна [from_msc,to_msc]; повторное CopyTicksRange
//--- того же диапазона даёт ту же сумму).
ticks_hash = LabFnv1a(ticks_hash, (ulong)t.time_msc);
ticks_hash = LabFnv1a(ticks_hash, (ulong)(bid * 1000000.0));
ticks_hash = LabFnv1a(ticks_hash, (ulong)(ask * 1000000.0));
if(t.time_msc < last_msc)
{
w.quality_codes = "UNORDERED_TICKS";
}
last_msc = t.time_msc;
}
w.n_valid_info = n_valid;
w.ticks_checksum = (n_valid > 0 ? ticks_hash : 0); // R8-S2
if(crossed)
w.quality_codes = (StringLen(w.quality_codes) > 0 ? w.quality_codes + "," : "") + "CROSSED_QUOTES";
w.tick_size = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
w.mid_min = (n_valid > 0 ? mid_min : 0.0);
w.mid_max = (n_valid > 0 ? mid_max : 0.0);
//--- N>=2, отсутствие ошибок загрузки даёт пригодность окна
if(w.n_valid_info >= 2 && w.copy_last_error == 0 && !crossed
&& w.tick_size > 0.0)
{
w.range_complete = true;
w.valid = true;
w.quote_frequency_hz = (double)n_valid / (double)LAB_WINDOW_SECONDS;
w.mid_range_ticks = (w.mid_max - w.mid_min) / w.tick_size;
}
else
{
if(w.copy_last_error != 0)
w.quality_codes = (StringLen(w.quality_codes) > 0 ? w.quality_codes + "," : "") + "COPY_ERROR";
if(got == 0)
w.quality_codes = (StringLen(w.quality_codes) > 0 ? w.quality_codes + "," : "") + "EMPTY_RANGE";
w.range_complete = false;
w.valid = false;
}
return(true);
}
void CMarketRegime::Classify(const MarketWindow &w, const Calibration &cal,
ENUM_LAB_MARKET_REGIME &regime, bool &valid)
{
regime = LAB_REGIME_UNKNOWN;
valid = false;
if(!w.valid || !cal.valid)
return;
//--- вырожденные пороги: два класса неразличимы
if(cal.frequency_p90 <= cal.frequency_p50 || cal.range_p90 <= cal.range_p50)
return;
valid = true;
const bool quiet_f = w.quote_frequency_hz <= cal.frequency_p50;
const bool quiet_r = w.mid_range_ticks <= cal.range_p50;
const bool fast_f = w.quote_frequency_hz >= cal.frequency_p90;
const bool fast_r = w.mid_range_ticks >= cal.range_p90;
if(quiet_f && quiet_r)
regime = LAB_REGIME_QUIET;
else
if(fast_f && fast_r)
regime = LAB_REGIME_FAST;
else
regime = LAB_REGIME_INTERMEDIATE;
}
bool CMarketRegime::AggregateWindow(const MarketWindow &w, double &freq[], double &rng[],
int &count, const int capacity)
{
if(!w.valid || count >= capacity)
return(false);
freq[count] = w.quote_frequency_hz;
rng[count] = w.mid_range_ticks;
count++;
return(true);
}
bool CMarketRegime::ComputeThresholds(const double &freq[], const double &rng[],
const int count, Calibration &cal, LabError &error)
{
error.Reset();
error.component = LAB_COMP_MARKET;
if(count < 1000)
{
//--- менее 1000 окон => калибровка непригодна (ТЗ §6(E3))
cal.valid = false;
cal.quality_codes = StringFormat("TOO_FEW_WINDOWS_%d", count);
error.code = 2;
error.message = cal.quality_codes;
return(false);
}
if(!LabPercentile(freq, count, 50.0, cal.frequency_p50) ||
!LabPercentile(freq, count, 90.0, cal.frequency_p90) ||
!LabPercentile(rng, count, 50.0, cal.range_p50) ||
!LabPercentile(rng, count, 90.0, cal.range_p90))
{
cal.valid = false;
cal.quality_codes = "PERCENTILE_FAILED";
error.code = 3;
return(false);
}
cal.valid_window_count = count;
//--- вырожденные пороги признаются невалидными
if(cal.frequency_p90 <= cal.frequency_p50 || cal.range_p90 <= cal.range_p50)
{
cal.valid = false;
cal.quality_codes = "DEGENERATE_THRESHOLDS";
error.code = 4;
return(false);
}
cal.valid = true;
cal.quality_codes = "OK";
return(true);
}
2026-09-12 22:14:24 +03:00
#endif // REQUEST_LATENCY_LAB_MARKET_REGIME_MQH
//+------------------------------------------------------------------+