//+------------------------------------------------------------------+ //| 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 { 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) static ulong SumApplicable(const StatSummaryRow &r) { 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 //+------------------------------------------------------------------+ //| Подсчёт значений по бинам гистограммы | //+------------------------------------------------------------------+ bool HistogramCount(const long &values[], const int count, ulong &bins[]) { ArrayInitialize(bins, 0); if(count <= 0) return(false); const ulong bounds[LAB_HISTOGRAM_BINS] = {1000, 2000, 4000, 8000, 16000, 32000, 64000, 128000, 256000, 0}; for(int i = 0; i < count; i++) { const long v = values[i]; if(v < 0) continue; // только неотрицательные интервалы int bin = -1; for(int b = 0; b < LAB_HISTOGRAM_BINS - 1; b++) { if((ulong)v < bounds[b]) { bin = b; break; } } if(bin == -1) bin = LAB_HISTOGRAM_BINS - 1; bins[bin]++; } return(true); } //+------------------------------------------------------------------+ //| Знаки знаковой разности (T2-T4): NEGATIVE/ZERO/POSITIVE. | //+------------------------------------------------------------------+ void OffsetSignCounts(const long &values[], const int count, ulong &n_neg, ulong &n_zero, ulong &n_pos) { n_neg = 0; n_zero = 0; n_pos = 0; for(int i = 0; i < count; i++) { if(values[i] < 0) n_neg++; else if(values[i] == 0) n_zero++; else n_pos++; } } //+------------------------------------------------------------------+ //| Вычисление знаковой разности без беззнакового вычитания с cast | //| (ТЗ §9): signed = сравнение + модуль разности. | //+------------------------------------------------------------------+ long SignedDelta(const ulong a, const ulong b) { if(a >= b) return(long)(a - b); return(-(long)(b - a)); } #endif // REQUEST_LATENCY_LAB_STATISTICS_MQH //+------------------------------------------------------------------+