2026-09-09 01:19:31 +03:00 | | | //+------------------------------------------------------------------+
|
2026-09-10 16:43:28 +03:00 | | | //| Statistics.mqh |
|
2026-09-16 15:37:55 +03:00 | | | //| Copyright 2026, MetaQuotes Ltd. |
|
2026-09-10 16:43:28 +03:00 | | | //| www.mql5.com |
|
2026-09-09 01:19:31 +03:00 | | | //+------------------------------------------------------------------+
|
| | | #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,
|
2026-09-16 15:37:55 +03:00 | | | const bool missing);
|
2026-09-09 01:19:31 +03:00 | | | };
|
| | | //+------------------------------------------------------------------+
|
| | | //| Строка 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;
|
2026-09-09 01:19:31 +03:00 | | |
|
2026-09-16 15:37:55 +03:00 | | | void Zero(void);
|
2026-09-09 01:19:31 +03:00 | | | };
|
| | | //+------------------------------------------------------------------+
|
| | | //| CStatistics — расчёт summary из значений пригодной выборки. |
|
| | | //+------------------------------------------------------------------+
|
| | | class CStatistics
|
| | | {
|
| | | public:
|
| | | //--- расчёт статистик по непустой выборке; p99.9 отдельно
|
2026-09-16 15:37:55 +03:00 | | | static bool Compute(double &row_values[], const int count, StatSummaryRow &row);
|
2026-09-09 01:19:31 +03:00 | | | //--- балансы счётчиков (ТЗ §9)
|
2026-09-12 22:14:24 +03:00 | | | static ulong SumApplicable(const StatSummaryRow &r)
|
2026-09-09 01:19:31 +03:00 | | | {
|
2026-09-12 22:14:24 +03:00 | | | return(r.n_conflict + r.n_interrupted + r.n_late + r.n_missing + r.n_valid);
|
2026-09-09 01:19:31 +03:00 | | | }
|
| | | };
|
| | | //+------------------------------------------------------------------+
|
2026-09-16 15:37:55 +03:00 | | | //| Внешние определения методов. |
|
| | | //+------------------------------------------------------------------+
|
| | | 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);
|
| | | }
|
| | | //+------------------------------------------------------------------+
|
2026-09-09 01:19:31 +03:00 | | | //| Гистограмма неотрицательных интервалов (мс), ТЗ §7. |
|
| | | //+------------------------------------------------------------------+
|
| | | #define LAB_HISTOGRAM_BINS 10
|
2026-09-10 16:43:28 +03:00 | | | #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-09 01:19:31 +03:00 | | | {
|
2026-09-12 22:14:24 +03:00 | | | ArrayInitialize(bins, 0);
|
| | | if(count <= 0)
|
2026-09-10 16:43:28 +03:00 | | | 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-09 01:19:31 +03:00 | | | {
|
2026-09-12 22:14:24 +03:00 | | | const long v = values[i];
|
| | | if(v < 0)
|
2026-09-09 01:19:31 +03:00 | | | continue; // только неотрицательные интервалы
|
2026-09-12 22:14:24 +03:00 | | | int bin = -1;
|
| | | for(int b = 0; b < LAB_HISTOGRAM_BINS - 1; b++)
|
2026-09-09 01:19:31 +03:00 | | | {
|
2026-09-12 22:14:24 +03:00 | | | if((ulong)v < bounds[b])
|
2026-09-09 01:19:31 +03:00 | | | {
|
2026-09-12 22:14:24 +03:00 | | | bin = b;
|
2026-09-09 01:19:31 +03:00 | | | break;
|
| | | }
|
| | | }
|
2026-09-12 22:14:24 +03:00 | | | if(bin == -1)
|
| | | bin = LAB_HISTOGRAM_BINS - 1;
|
2026-09-09 01:19:31 +03:00 | | | bins[bin]++;
|
| | | }
|
2026-09-10 16:43:28 +03:00 | | | return(true);
|
2026-09-09 01:19:31 +03:00 | | | }
|
| | | //+------------------------------------------------------------------+
|
| | | //| Знаки знаковой разности (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-09 01:19:31 +03:00 | | | {
|
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-09 01:19:31 +03:00 | | | {
|
2026-09-12 22:14:24 +03:00 | | | if(values[i] < 0)
|
2026-09-09 01:19:31 +03:00 | | | n_neg++;
|
| | | else
|
2026-09-12 22:14:24 +03:00 | | | if(values[i] == 0)
|
| | | n_zero++;
|
| | | else
|
| | | n_pos++;
|
2026-09-09 01:19:31 +03:00 | | | }
|
| | | }
|
| | | //+------------------------------------------------------------------+
|
| | | //| Вычисление знаковой разности без беззнакового вычитания с cast |
|
| | | //| (ТЗ §9): signed = сравнение + модуль разности. |
|
| | | //+------------------------------------------------------------------+
|
2026-09-12 22:14:24 +03:00 | | | long SignedDelta(const ulong a, const ulong b)
|
2026-09-09 01:19:31 +03:00 | | | {
|
2026-09-12 22:14:24 +03:00 | | | if(a >= b)
|
| | | return(long)(a - b);
|
| | | return(-(long)(b - a));
|
2026-09-09 01:19:31 +03:00 | | | }
|
| | |
|
2026-09-12 22:14:24 +03:00 | | | #endif // REQUEST_LATENCY_LAB_STATISTICS_MQH
|
| | | //+------------------------------------------------------------------+
|