2026-09-09 01:19:31 +03:00 | | | //+------------------------------------------------------------------+
|
2026-09-10 16:43:28 +03:00 | | | //| MarketCalibration.mq5 |
|
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 | | | //+------------------------------------------------------------------+
|
2026-09-16 15:37:55 +03:00 | | | #property copyright "Copyright 2026, MetaQuotes Ltd."
|
2026-09-10 16:43:28 +03:00 | | | #property link "https://www.mql5.com"
|
| | | #property version "1.00"
|
| | | #property description "Калибровочный набор p50/p90 для QUIET/FAST"
|
| | | #property description "классификатора (ТЗ §6(E3)). Без торговых запросов."
|
| | | #property description "Не менее 1000 валидных минутных окон из 5 сессий."
|
2026-09-16 15:37:55 +03:00 | | | #property description "Результат — calibration.csv в Common\\Files\\RequestLatencyLab\\."
|
| | | #property script_show_inputs
|
2026-09-09 01:19:31 +03:00 | | |
|
| | | #include "..\Include\RequestLatencyLab\Models.mqh"
|
| | | #include "..\Include\RequestLatencyLab\Configuration.mqh"
|
| | | #include "..\Include\RequestLatencyLab\MarketRegime.mqh"
|
| | | #include "..\Include\RequestLatencyLab\CsvStorage.mqh"
|
| | | #include "..\Include\RequestLatencyLab\Legacy\BenchmarkStatistics.mqh"
|
| | |
|
2026-09-16 15:37:55 +03:00 | | | //--- символ калибровки
|
| | | input string InpSymbol="EURUSD";
|
| | | //--- 0=live: TimeCurrent()-InpDaysBack; тестер: начало периода
|
| | | input datetime InpCalibrationFrom=0;
|
| | | //--- 0=live: TimeCurrent(); тестер: конец периода
|
| | | input datetime InpCalibrationTo=0;
|
| | | //--- период калибровки назад от сегодня
|
| | | input int InpDaysBack=30;
|
| | | //--- идентификатор калибровки
|
| | | input string InpCalibrationId="cal-eur-usd-01";
|
2026-09-12 22:14:24 +03:00 | | | //+------------------------------------------------------------------+
|
| | | //| Построение калибровки рыночного режима |
|
2026-09-09 01:19:31 +03:00 | | | //+------------------------------------------------------------------+
|
2026-09-12 22:14:24 +03:00 | | | void OnStart(void)
|
2026-09-09 01:19:31 +03:00 | | | {
|
2026-09-10 16:43:28 +03:00 | | | LabError err;
|
2026-09-12 22:14:24 +03:00 | | | //--- R5-B6: ёмкость увеличена — для плотного потока 4096 окон = ~3 суток
|
| | | //--- (< 5 календарных сессий); теперь генератор добирает сессии.
|
| | | const int cap = 20000;
|
2026-09-10 16:43:28 +03:00 | | | double freq[];
|
| | | double rng[];
|
2026-09-11 04:09:32 +03:00 | | | long win_msc[];
|
2026-09-12 22:14:24 +03:00 | | | if(ArrayResize(freq, cap) != cap || ArrayResize(rng, cap) != cap ||
|
| | | ArrayResize(win_msc, cap) != cap)
|
2026-09-09 01:19:31 +03:00 | | | {
|
| | | Print("MarketCalibration: cannot allocate");
|
| | | return;
|
| | | }
|
2026-09-12 22:14:24 +03:00 | | | int count = 0;
|
| | | int invalid = 0;
|
| | | //--- R4-B4: отдельные торговые сессии = уникальные календарные даты,
|
| | | //--- в которых наблюдалось хотя бы одно валидное минутное окно
|
| | | //--- (дата берётся по серверному времени начала минуты).
|
2026-09-10 22:54:26 +03:00 | | | int session_dates[64];
|
2026-09-12 22:14:24 +03:00 | | | int session_count = 0;
|
| | | int cur_session_day = -1;
|
2026-09-16 15:37:55 +03:00 | | | const datetime now = TimeCurrent();
|
| | | const datetime from = (InpCalibrationFrom > 0 ? InpCalibrationFrom : now - InpDaysBack * 86400);
|
| | | const datetime to = (InpCalibrationTo > 0 ? InpCalibrationTo : now);
|
2026-09-12 22:14:24 +03:00 | | | //--- проход по минутам выбранного периода; R5-B6: цикл продолжается,
|
| | | //--- пока не набраны >=1000 окон И >=5 сессий (либо исчерпан период/ёмкость)
|
| | | datetime cursor = from;
|
| | | while(cursor < to && count < cap && !(session_count >= 5 && count >= 1000))
|
2026-09-09 01:19:31 +03:00 | | | {
|
| | | //--- границы минутного окна по календарной шкале
|
| | | MqlDateTime mdt;
|
2026-09-12 22:14:24 +03:00 | | | TimeToStruct(cursor, mdt);
|
| | | mdt.sec = 0;
|
| | | const datetime minute_start = StructToTime(mdt);
|
2026-09-10 16:43:28 +03:00 | | | MqlTick anchor;
|
2026-09-12 22:14:24 +03:00 | | | if(!SymbolInfoTick(InpSymbol, anchor))
|
2026-09-09 01:19:31 +03:00 | | | {
|
| | | //--- пропуск минут без котировки (не валидное окно)
|
| | | invalid++;
|
2026-09-12 22:14:24 +03:00 | | | cursor = minute_start + 60;
|
2026-09-09 01:19:31 +03:00 | | | continue;
|
| | | }
|
2026-09-12 22:14:24 +03:00 | | | const long b = (long)minute_start * 1000; // начало текущей минуты
|
2026-09-09 01:19:31 +03:00 | | | MarketWindow w;
|
2026-09-10 22:54:26 +03:00 | | | //--- R4-B5: единый контракт окна [b-60000, b-1] с anchor=b (как в runtime)
|
2026-09-12 22:14:24 +03:00 | | | if(CMarketRegime::ComputeWindow(InpSymbol, b, GetMicrosecondCount(), w, err))
|
2026-09-09 01:19:31 +03:00 | | | {
|
| | | if(w.valid)
|
| | | {
|
2026-09-12 22:14:24 +03:00 | | | freq[count] = w.quote_frequency_hz;
|
| | | rng[count] = w.mid_range_ticks;
|
| | | win_msc[count] = (long)minute_start * 1000; // R5-B6: исходное окно
|
2026-09-09 01:19:31 +03:00 | | | count++;
|
2026-09-10 22:54:26 +03:00 | | | //--- R4-B4: смена календарной даты валидного окна = новая сессия
|
| | | MqlDateTime sdt;
|
2026-09-12 22:14:24 +03:00 | | | TimeToStruct(minute_start, sdt);
|
| | | const int day = (int)sdt.year * 10000 + (int)sdt.mon * 100 + (int)sdt.day;
|
| | | if(cur_session_day != day)
|
2026-09-10 22:54:26 +03:00 | | | {
|
2026-09-12 22:14:24 +03:00 | | | cur_session_day = day;
|
| | | if(session_count < 64)
|
2026-09-10 22:54:26 +03:00 | | | {
|
2026-09-12 22:14:24 +03:00 | | | session_dates[session_count] = day;
|
2026-09-10 22:54:26 +03:00 | | | session_count++;
|
| | | }
|
| | | }
|
2026-09-09 01:19:31 +03:00 | | | }
|
| | | else
|
| | | invalid++;
|
| | | }
|
| | | else
|
| | | invalid++;
|
2026-09-12 22:14:24 +03:00 | | | cursor = minute_start + 60;
|
2026-09-09 01:19:31 +03:00 | | | }
|
| | | Calibration cal;
|
| | | cal.Zero();
|
2026-09-12 22:14:24 +03:00 | | | cal.calibration_id = InpCalibrationId;
|
| | | cal.symbol = InpSymbol;
|
| | | cal.calendar_time_scale = "SERVER";
|
| | | cal.period_from_msc = (long)from * 1000;
|
| | | cal.period_to_msc = (long)to * 1000;
|
| | | cal.invalid_window_count = invalid;
|
| | | cal.trading_session_count = session_count; // R4-B4: реальное число сессий
|
| | | //--- R5-B6: набор, который loader отвергнет, не сохраняется —
|
| | | //--- явный INSUFFICIENT_DATA вместо невалидной калибровки.
|
| | | if(session_count < 5 || count < 1000)
|
2026-09-11 04:09:32 +03:00 | | | {
|
| | | PrintFormat("MarketCalibration: INSUFFICIENT_DATA windows=%d sessions=%d",
|
2026-09-12 22:14:24 +03:00 | | | count, session_count);
|
2026-09-11 04:09:32 +03:00 | | | return;
|
| | | }
|
2026-09-12 22:14:24 +03:00 | | | if(CMarketRegime::ComputeThresholds(freq, rng, count, cal, err))
|
2026-09-09 01:19:31 +03:00 | | | {
|
| | | PrintFormat("MarketCalibration: windows=%d p50(f)=%.4g p90(f)=%.4g p50(r)=%.4g p90(r)=%.4g",
|
2026-09-12 22:14:24 +03:00 | | | count, cal.frequency_p50, cal.frequency_p90, cal.range_p50, cal.range_p90);
|
2026-09-09 01:19:31 +03:00 | | | //--- calibration.csv
|
2026-09-12 22:14:24 +03:00 | | | const string path = StringFormat("%s\\%s.csv", LAB_DATA_ROOT, InpCalibrationId);
|
| | | string out = CCsv::HeaderKeyValue();
|
| | | out += KeyValue(cal.calibration_id, cal.symbol, cal.frequency_p50, cal.frequency_p90,
|
| | | cal.range_p50, cal.range_p90, count, cal.invalid_window_count,
|
| | | cal.trading_session_count, cal.period_from_msc, cal.period_to_msc);
|
2026-09-16 15:37:55 +03:00 | | | if(CCsv::SaveUtf8(path, out, err, true))
|
| | | Print("MarketCalibration: saved Common\\Files\\" + path + " (read-back ok)");
|
2026-09-09 01:19:31 +03:00 | | | else
|
2026-09-12 22:14:24 +03:00 | | | Print("MarketCalibration: SAVE FAILED " + err.message);
|
2026-09-11 04:09:32 +03:00 | | | //--- R5-B6: исходные окна выгружаются для независимой проверки
|
| | | //--- (отсутствие пересечения калибровки и эксперимента, пересчёт порогов)
|
2026-09-12 22:14:24 +03:00 | | | string wh[] = {"minute_start_msc", "quote_frequency_hz", "mid_range_ticks"};
|
| | | string wout = CCsv::Header(wh);
|
2026-09-11 04:09:32 +03:00 | | | string wc[3];
|
2026-09-12 22:14:24 +03:00 | | | for(int i = 0; i < count; i++)
|
2026-09-11 04:09:32 +03:00 | | | {
|
2026-09-12 22:14:24 +03:00 | | | wc[0] = IntegerToString(win_msc[i]);
|
| | | wc[1] = DoubleToString(freq[i], 12);
|
| | | wc[2] = DoubleToString(rng[i], 12);
|
| | | wout += CCsv::BuildRow(wc, 3);
|
2026-09-11 04:09:32 +03:00 | | | }
|
2026-09-12 22:14:24 +03:00 | | | const string wpath = StringFormat("%s\\%s_windows.csv", LAB_DATA_ROOT, InpCalibrationId);
|
2026-09-16 15:37:55 +03:00 | | | if(CCsv::SaveUtf8(wpath, wout, err, true))
|
| | | Print("MarketCalibration: windows saved Common\\Files\\" + wpath);
|
2026-09-11 04:09:32 +03:00 | | | else
|
2026-09-12 22:14:24 +03:00 | | | Print("MarketCalibration: WINDOWS SAVE FAILED " + err.message);
|
2026-09-16 15:37:55 +03:00 | | | PrintFormat("MarketCalibration: FINISHED id=%s windows=%d sessions=%d",
|
| | | InpCalibrationId, count, session_count);
|
2026-09-09 01:19:31 +03:00 | | | }
|
| | | else
|
| | | PrintFormat("MarketCalibration: недобор валидных окон: %d (требуется >=1000): %s",
|
2026-09-12 22:14:24 +03:00 | | | count, err.message);
|
2026-09-09 01:19:31 +03:00 | | | }
|
| | | //+------------------------------------------------------------------+
|
| | | //| Вспомогательные функции записи calibration.csv |
|
| | | //+------------------------------------------------------------------+
|
2026-09-12 22:14:24 +03:00 | | | string KeyValue(const string id, const string symbol, const double fp50, const double fp90,
|
| | | const double rp50, const double rp90, const int valid_windows,
|
| | | const int invalid_windows, const int sessions, const long from_msc,
|
2026-09-11 04:09:32 +03:00 | | | const long to_msc)
|
2026-09-09 01:19:31 +03:00 | | | {
|
2026-09-12 22:14:24 +03:00 | | | string out = "";
|
2026-09-09 01:19:31 +03:00 | | | string cols[5];
|
2026-09-12 22:14:24 +03:00 | | | AddPair(out, cols, "calibration_id", id);
|
| | | AddPair(out, cols, "symbol", symbol);
|
| | | AddPair(out, cols, "frequency_p50", DoubleToString(fp50, 10));
|
| | | AddPair(out, cols, "frequency_p90", DoubleToString(fp90, 10));
|
| | | AddPair(out, cols, "range_p50", DoubleToString(rp50, 10));
|
| | | AddPair(out, cols, "range_p90", DoubleToString(rp90, 10));
|
| | | AddPair(out, cols, "valid_window_count", IntegerToString(valid_windows));
|
| | | AddPair(out, cols, "invalid_window_count", IntegerToString(invalid_windows));
|
| | | AddPair(out, cols, "trading_session_count", IntegerToString(sessions));
|
| | | //--- R5-B6: границы калибровочного периода (перекрытие с экспериментом)
|
| | | AddPair(out, cols, "period_from_msc", IntegerToString(from_msc));
|
| | | AddPair(out, cols, "period_to_msc", IntegerToString(to_msc));
|
2026-09-10 16:43:28 +03:00 | | | return(out);
|
2026-09-09 01:19:31 +03:00 | | | }
|
2026-09-12 22:14:24 +03:00 | | | //+------------------------------------------------------------------+
|
| | | //| Добавление пары ключ/значение в CSV-строку |
|
| | | //+------------------------------------------------------------------+
|
| | | void AddPair(string &out, string &cols[], const string key, const string value)
|
2026-09-09 01:19:31 +03:00 | | | {
|
2026-09-12 22:14:24 +03:00 | | | cols[0] = key;
|
| | | cols[1] = value;
|
| | | cols[2] = "string";
|
| | | cols[3] = "KNOWN";
|
| | | cols[4] = "DERIVED";
|
| | | out += CCsv::BuildRow(cols, 5);
|
| | | }
|
2026-09-16 15:37:55 +03:00 | | | //+------------------------------------------------------------------+
|