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

260 lines
9.2 KiB
MQL5

//+------------------------------------------------------------------+
//| Statistics.mqh |
//| Copyright 2026, MetaQuotes Ltd. |
//| www.mql5.com |
//+------------------------------------------------------------------+
#ifndef REQUEST_LATENCY_LAB_STATISTICS_MQH
#define REQUEST_LATENCY_LAB_STATISTICS_MQH
#include "..\..\Include\RequestLatencyLab\Models.mqh"
#include "..\..\Include\RequestLatencyLab\Legacy\BenchmarkStatistics.mqh"
//+------------------------------------------------------------------+
//| Классификация одной попытки для метрики (ТЗ §9): применимость |
//| сначала, затем пригодность. |
//+------------------------------------------------------------------+
class CQuality
{
public:
static ENUM_LAB_FITNESS FinalCategory(const bool conflict,
const bool interrupted,
const bool late,
const bool missing);
};
//+------------------------------------------------------------------+
//| Строка summary (CSV schema summary.csv). |
//+------------------------------------------------------------------+
struct StatSummaryRow
{
2026-09-12 22:14:24 +03:00
string experiment_id;
string condition_id;
string series_id;
string metric_id;
string outcome_group;
string population; // PRIMARY / LATE_DIAGNOSTIC / CALLBACK_DIAGNOSTIC
string observation_unit; // REQUEST / CALLBACK
string segment_key;
string source_set_id;
string unit; // us
ulong n_attempted;
ulong n_applicable;
ulong n_not_applicable;
ulong n_applicability_unknown;
ulong n_valid;
ulong n_missing;
ulong n_late;
ulong n_conflict;
ulong n_interrupted;
ulong n_used;
double minimum;
double mean;
double median; // p50
double p90;
double p95;
double p99;
double p999; // p99.9
double maximum;
double stddev;
bool has_stats;
string percentile_method; // LINEAR_N_MINUS_ONE
double p95_tail_expected_n;
double p99_tail_expected_n;
double p999_tail_expected_n;
string tail_warning_codes;
ulong n_deadline_exceeded;
ulong deadline_denominator;
double deadline_rate;
ulong n_timestamp_missing_at_deadline;
double missing_timestamp_rate;
bool conditional_distribution;
void Zero(void);
};
//+------------------------------------------------------------------+
//| CStatistics — расчёт summary из значений пригодной выборки. |
//+------------------------------------------------------------------+
class CStatistics
{
public:
//--- расчёт статистик по непустой выборке; p99.9 отдельно
static bool Compute(double &row_values[], const int count, StatSummaryRow &row);
//--- балансы счётчиков (ТЗ §9)
2026-09-12 22:14:24 +03:00
static ulong SumApplicable(const StatSummaryRow &r)
{
2026-09-12 22:14:24 +03:00
return(r.n_conflict + r.n_interrupted + r.n_late + r.n_missing + r.n_valid);
}
};
//+------------------------------------------------------------------+
//| Внешние определения методов. |
//+------------------------------------------------------------------+
ENUM_LAB_FITNESS CQuality::FinalCategory(const bool conflict,
const bool interrupted, const bool late, const bool missing)
{
if(conflict)
return(LAB_FIT_CONFLICT);
if(interrupted)
return(LAB_FIT_INTERRUPTED);
if(late)
return(LAB_FIT_LATE);
if(missing)
return(LAB_FIT_MISSING);
return(LAB_FIT_VALID);
}
void StatSummaryRow::Zero(void)
{
experiment_id = "";
condition_id = "";
series_id = "";
metric_id = "";
outcome_group = "";
population = "PRIMARY";
observation_unit = "REQUEST";
segment_key = "{}";
source_set_id = "";
unit = "us";
n_attempted = 0;
n_applicable = 0;
n_not_applicable = 0;
n_applicability_unknown = 0;
n_valid = 0;
n_missing = 0;
n_late = 0;
n_conflict = 0;
n_interrupted = 0;
n_used = 0;
minimum = 0.0;
mean = 0.0;
median = 0.0;
p90 = 0.0;
p95 = 0.0;
p99 = 0.0;
p999 = 0.0;
maximum = 0.0;
stddev = 0.0;
has_stats = false;
percentile_method = "LINEAR_N_MINUS_ONE";
p95_tail_expected_n = 0.0;
p99_tail_expected_n = 0.0;
p999_tail_expected_n = 0.0;
tail_warning_codes = "[]";
n_deadline_exceeded = 0;
deadline_denominator = 0;
deadline_rate = 0.0;
n_timestamp_missing_at_deadline = 0;
missing_timestamp_rate = 0.0;
conditional_distribution = false;
}
bool CStatistics::Compute(double &row_values[], const int count, StatSummaryRow &row)
{
if(count <= 0)
return(false);
double tmp[];
if(ArrayResize(tmp, count) != count)
return(false);
for(int i = 0; i < count; i++)
tmp[i] = row_values[i];
row.n_used = (ulong)count;
row.has_stats = true;
if(!LabMinValue(tmp, count, row.minimum) ||
!LabMaxValue(tmp, count, row.maximum) ||
!LabAvgValue(tmp, count, row.mean))
return(false);
if(!LabPercentile(tmp, count, 50.0, row.median) ||
!LabPercentile(tmp, count, 90.0, row.p90) ||
!LabPercentile(tmp, count, 95.0, row.p95) ||
!LabPercentile(tmp, count, 99.0, row.p99) ||
!LabPercentile(tmp, count, 99.9, row.p999))
return(false);
LabStdDev(tmp, count, row.stddev);
row.p95_tail_expected_n = count * 0.05;
row.p99_tail_expected_n = count * 0.01;
row.p999_tail_expected_n = count * 0.001;
//--- предупреждение о редком хвосте: tail_expected_n < 10 (ТЗ §7)
string warns = "[";
bool first = true;
if(row.p95_tail_expected_n < 10.0)
{
warns += (first ? "" : ",") + "\"P95_TAIL_LT10\"";
first = false;
}
if(row.p99_tail_expected_n < 10.0)
{
warns += (first ? "" : ",") + "\"P99_TAIL_LT10\"";
first = false;
}
if(row.p999_tail_expected_n < 10.0)
{
warns += (first ? "" : ",") + "\"P999_TAIL_LT10\"";
first = false;
}
warns += "]";
row.tail_warning_codes = warns;
return(true);
}
//+------------------------------------------------------------------+
//| Гистограмма неотрицательных интервалов (мс), ТЗ §7. |
//+------------------------------------------------------------------+
#define LAB_HISTOGRAM_BINS 10
#define LAB_HIST_BIN_MS 256000ul
2026-09-12 22:14:24 +03:00
//+------------------------------------------------------------------+
//| Подсчёт значений по бинам гистограммы |
//+------------------------------------------------------------------+
bool HistogramCount(const long &values[], const int count, ulong &bins[])
{
2026-09-12 22:14:24 +03:00
ArrayInitialize(bins, 0);
if(count <= 0)
return(false);
2026-09-12 22:14:24 +03:00
const ulong bounds[LAB_HISTOGRAM_BINS] =
{1000, 2000, 4000, 8000, 16000, 32000, 64000, 128000, 256000, 0};
for(int i = 0; i < count; i++)
{
2026-09-12 22:14:24 +03:00
const long v = values[i];
if(v < 0)
continue; // только неотрицательные интервалы
2026-09-12 22:14:24 +03:00
int bin = -1;
for(int b = 0; b < LAB_HISTOGRAM_BINS - 1; b++)
{
2026-09-12 22:14:24 +03:00
if((ulong)v < bounds[b])
{
2026-09-12 22:14:24 +03:00
bin = b;
break;
}
}
2026-09-12 22:14:24 +03:00
if(bin == -1)
bin = LAB_HISTOGRAM_BINS - 1;
bins[bin]++;
}
return(true);
}
//+------------------------------------------------------------------+
//| Знаки знаковой разности (T2-T4): NEGATIVE/ZERO/POSITIVE. |
//+------------------------------------------------------------------+
2026-09-12 22:14:24 +03:00
void OffsetSignCounts(const long &values[], const int count,
ulong &n_neg, ulong &n_zero, ulong &n_pos)
{
2026-09-12 22:14:24 +03:00
n_neg = 0;
n_zero = 0;
n_pos = 0;
for(int i = 0; i < count; i++)
{
2026-09-12 22:14:24 +03:00
if(values[i] < 0)
n_neg++;
else
2026-09-12 22:14:24 +03:00
if(values[i] == 0)
n_zero++;
else
n_pos++;
}
}
//+------------------------------------------------------------------+
//| Вычисление знаковой разности без беззнакового вычитания с cast |
//| (ТЗ §9): signed = сравнение + модуль разности. |
//+------------------------------------------------------------------+
2026-09-12 22:14:24 +03:00
long SignedDelta(const ulong a, const ulong b)
{
2026-09-12 22:14:24 +03:00
if(a >= b)
return(long)(a - b);
return(-(long)(b - a));
}
2026-09-12 22:14:24 +03:00
#endif // REQUEST_LATENCY_LAB_STATISTICS_MQH
//+------------------------------------------------------------------+