2026-09-09 01:19:31 +03:00 | | | //+------------------------------------------------------------------+
|
2026-09-10 16:43:28 +03:00 | | | //| Configuration.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_CONFIGURATION_MQH
|
| | | #define REQUEST_LATENCY_LAB_CONFIGURATION_MQH
|
| | |
|
| | | #include "..\..\Include\RequestLatencyLab\Models.mqh"
|
| | | //+------------------------------------------------------------------+
|
| | | //| Режимы работы советника (ТЗ §1). |
|
| | | //+------------------------------------------------------------------+
|
| | | enum ENUM_LAB_RUN_MODE
|
| | | {
|
2026-09-12 22:14:24 +03:00 | | | LAB_RUN_LOCAL_ONLY = 0, // по умолчанию; без торговли
|
2026-09-09 01:19:31 +03:00 | | | LAB_RUN_PENDING_CREATE_DELETE,
|
| | | LAB_RUN_MARKET_ORDER
|
| | | };
|
| | | //+------------------------------------------------------------------+
|
| | | //| Настройки лаборатории (заполняются из input советника). |
|
| | | //+------------------------------------------------------------------+
|
| | | struct LabSettings
|
| | | {
|
2026-09-10 16:43:28 +03:00 | | | string campaign_id;
|
| | | string session_prefix;
|
| | | ENUM_LAB_EXPERIMENT experiment_id;
|
| | | int series_id; // 1..5
|
| | | string symbol;
|
| | | ulong magic;
|
| | | int deviation_points;
|
| | | int pending_distance_ticks; // D
|
| | | int count_per_condition; // 100
|
| | | int warmup_per_condition; // 10
|
| | | int outcome_timeout_ms; // 5000
|
| | | int late_grace_ms; // 1000
|
| | | int pause_ms; // 1000
|
| | | int timer_requested_ms; // 20
|
| | | int feature_window_sec; // 60
|
| | | int feature_max_age_ms; // 1000
|
| | | int quote_max_age_ms;
|
| | | string calibration_id;
|
| | | ulong seed;
|
| | | int cleanup_max_attempts; // 1
|
| | | int sample_capacity; // 2048
|
| | | int event_capacity; // 65536
|
| | | int error_reserve_capacity;
|
| | | double volume_units_tolerance; // 1e-7
|
| | | int address_read_budget;
|
| | | int history_scan_budget;
|
2026-09-10 19:04:10 +03:00 | | | int e3_campaign_limit_min; // 0=без лимита; иначе минуты
|
2026-09-10 16:43:28 +03:00 | | | ENUM_LAB_RUN_MODE run_mode;
|
| | | string data_origin; // DEMO/SYNTHETIC
|
| | | string configuration_id;
|
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 | | | };
|
| | | //+------------------------------------------------------------------+
|
| | | //| CConfiguration — проверка и фиксация конфигурации. |
|
| | | //+------------------------------------------------------------------+
|
| | | class CConfiguration
|
| | | {
|
| | | public:
|
| | | //--- базовая проверка входных параметров
|
2026-09-16 15:37:55 +03:00 | | | static bool Validate(const LabSettings &s, LabError &error);
|
2026-09-09 01:19:31 +03:00 | | | //--- правила символа из терминала
|
2026-09-16 15:37:55 +03:00 | | | static bool BuildSymbolRules(const string symbol, SymbolRules &rules, LabError &error);
|
2026-09-09 01:19:31 +03:00 | | | //--- описание символа для манифеста
|
2026-09-12 22:14:24 +03:00 | | | static string ManifestSymbolInfo(const SymbolRules &r)
|
2026-09-09 01:19:31 +03:00 | | | {
|
2026-09-10 16:43:28 +03:00 | | | return(StringFormat("%s|digits=%d|point=%.10g|tick_size=%.10g|vol_min=%.4g|vol_max=%.4g|vol_step=%.4g",
|
2026-09-12 22:14:24 +03:00 | | | r.symbol, r.digits, r.point, r.tick_size,
|
| | | r.volume_min, r.volume_max, r.volume_step));
|
2026-09-09 01:19:31 +03:00 | | | }
|
| | | //--- минимальный допустимый объём (ТЗ §2.4). Фиксируется до серии.
|
2026-09-16 15:37:55 +03:00 | | | static double MinVolume(const SymbolRules &r) { return(r.volume_min); }
|
| | | //--- детерминированный отпечаток конфигурации (без случайности и дат)
|
| | | static string ConfigurationId(const LabSettings &s, const SymbolRules &rules);
|
| | | };
|
| | | //+------------------------------------------------------------------+
|
| | | //| Внешние определения методов. |
|
| | | //+------------------------------------------------------------------+
|
| | | void LabSettings::Zero(void)
|
| | | {
|
| | | campaign_id = "";
|
| | | session_prefix = "";
|
| | | experiment_id = LAB_EXP_E1;
|
| | | series_id = 1;
|
| | | symbol = "";
|
| | | magic = 0;
|
| | | deviation_points = 0;
|
| | | pending_distance_ticks = 0;
|
| | | count_per_condition = 100;
|
| | | warmup_per_condition = 10;
|
| | | outcome_timeout_ms = 5000;
|
| | | late_grace_ms = 1000;
|
| | | pause_ms = 1000;
|
| | | timer_requested_ms = 20;
|
| | | feature_window_sec = 60;
|
| | | feature_max_age_ms = 1000;
|
| | | quote_max_age_ms = 500;
|
| | | calibration_id = "";
|
| | | seed = 1;
|
| | | cleanup_max_attempts = 1;
|
| | | sample_capacity = 2048;
|
| | | event_capacity = 65536;
|
| | | error_reserve_capacity = 4096;
|
| | | volume_units_tolerance = 1e-7;
|
| | | address_read_budget = 20000;
|
| | | history_scan_budget = 100000;
|
| | | e3_campaign_limit_min = 30;
|
| | | run_mode = LAB_RUN_LOCAL_ONLY;
|
| | | data_origin = "SYNTHETIC";
|
| | | configuration_id = "";
|
| | | }
|
| | | bool CConfiguration::Validate(const LabSettings &s, LabError &error)
|
| | | {
|
| | | error.Reset();
|
| | | error.component = LAB_COMP_CONFIG;
|
| | | if(s.count_per_condition <= 0 || s.warmup_per_condition < 0)
|
2026-09-09 01:19:31 +03:00 | | | {
|
2026-09-16 15:37:55 +03:00 | | | error.code = 1;
|
| | | error.severity = LAB_SEV_BLOCKER;
|
| | | error.message = "count_per_condition must be >0";
|
| | | return(false);
|
2026-09-09 01:19:31 +03:00 | | | }
|
2026-09-16 15:37:55 +03:00 | | | if(s.warmup_per_condition > 0 && s.warmup_per_condition >= s.count_per_condition)
|
2026-09-09 01:19:31 +03:00 | | | {
|
2026-09-16 15:37:55 +03:00 | | | error.code = 2;
|
| | | error.severity = LAB_SEV_BLOCKER;
|
| | | error.message = "warmup must be smaller than count_per_condition";
|
| | | return(false);
|
2026-09-09 01:19:31 +03:00 | | | }
|
2026-09-16 15:37:55 +03:00 | | | if(s.series_id < 1 || s.series_id > 5)
|
| | | {
|
| | | error.code = 3;
|
| | | error.severity = LAB_SEV_BLOCKER;
|
| | | error.message = "series_id must be 1..5";
|
| | | return(false);
|
| | | }
|
| | | if(s.outcome_timeout_ms <= 0 || s.late_grace_ms < 0)
|
| | | {
|
| | | error.code = 4;
|
| | | error.severity = LAB_SEV_BLOCKER;
|
| | | error.message = "timeouts must be positive";
|
| | | return(false);
|
| | | }
|
| | | if(s.pause_ms < 0 || s.timer_requested_ms <= 0)
|
| | | {
|
| | | error.code = 5;
|
| | | error.severity = LAB_SEV_BLOCKER;
|
| | | error.message = "pause/timer must be valid";
|
| | | return(false);
|
| | | }
|
| | | if(s.feature_window_sec <= 0 || s.feature_max_age_ms <= 0)
|
| | | {
|
| | | error.code = 6;
|
| | | error.severity = LAB_SEV_BLOCKER;
|
| | | error.message = "feature window must be positive";
|
| | | return(false);
|
| | | }
|
| | | if(s.seed == 0 || s.seed >= 2147483646ul)
|
| | | {
|
| | | error.code = 7;
|
| | | error.severity = LAB_SEV_BLOCKER;
|
| | | error.message = "seed must be in [1,2147483646]";
|
| | | return(false);
|
| | | }
|
| | | if(s.sample_capacity <= 0 || s.event_capacity <= 0)
|
| | | {
|
| | | error.code = 8;
|
| | | error.severity = LAB_SEV_BLOCKER;
|
| | | error.message = "capacities must be positive";
|
| | | return(false);
|
| | | }
|
| | | if(s.volume_units_tolerance <= 0.0 || s.volume_units_tolerance >= 1e-3)
|
| | | {
|
| | | error.code = 9;
|
| | | error.severity = LAB_SEV_BLOCKER;
|
| | | error.message = "volume tolerance must be in (0,1e-3)";
|
| | | return(false);
|
| | | }
|
| | | if(s.cleanup_max_attempts < 1 || s.cleanup_max_attempts > 10)
|
| | | {
|
| | | error.code = 10;
|
| | | error.severity = LAB_SEV_BLOCKER;
|
| | | error.message = "cleanup_max_attempts must be 1..10";
|
| | | return(false);
|
| | | }
|
| | | if(s.e3_campaign_limit_min < 0 || s.e3_campaign_limit_min > 1440)
|
| | | {
|
| | | error.code = 11;
|
| | | error.severity = LAB_SEV_BLOCKER;
|
| | | error.message = "e3_campaign_limit_min must be 0..1440";
|
| | | return(false);
|
| | | }
|
| | | //--- data_origin задаётся оператором в файле запуска
|
| | | //--- data_origin устанавливается в FillSettings (не здесь)
|
| | | //--- торговый режим требует data_origin оператора (проверка выше)
|
| | | //--- data_origin=DEMO задаётся в настроечном файле запуска
|
| | | return(true);
|
| | | }
|
| | | bool CConfiguration::BuildSymbolRules(const string symbol, SymbolRules &rules, LabError &error)
|
| | | {
|
| | | error.Reset();
|
| | | error.component = LAB_COMP_CONFIG;
|
| | | if(StringLen(symbol) == 0)
|
| | | {
|
| | | error.code = 20;
|
| | | error.severity = LAB_SEV_BLOCKER;
|
| | | error.message = "symbol not set";
|
| | | return(false);
|
| | | }
|
| | | if(!SymbolSelect(symbol, true))
|
| | | {
|
| | | error.code = 21;
|
| | | error.severity = LAB_SEV_BLOCKER;
|
| | | error.message = "symbol unavailable in Market Watch";
|
| | | return(false);
|
| | | }
|
| | | ZeroMemory(rules);
|
| | | rules.symbol = symbol;
|
| | | rules.digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
|
| | | rules.point = SymbolInfoDouble(symbol, SYMBOL_POINT);
|
| | | rules.tick_size = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
|
| | | rules.volume_min = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
|
| | | rules.volume_max = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
|
| | | rules.volume_step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
|
| | | rules.filling_mode = SymbolInfoInteger(symbol, SYMBOL_FILLING_MODE);
|
| | | rules.trade_mode = SymbolInfoInteger(symbol, SYMBOL_TRADE_MODE);
|
| | | rules.bid = SymbolInfoDouble(symbol, SYMBOL_BID);
|
| | | rules.ask = SymbolInfoDouble(symbol, SYMBOL_ASK);
|
| | | if(!rules.IsValid())
|
| | | {
|
| | | error.code = 22;
|
| | | error.severity = LAB_SEV_BLOCKER;
|
| | | error.message = "symbol rules invalid (tick size or volume step missing)";
|
| | | return(false);
|
| | | }
|
| | | return(true);
|
| | | }
|
| | | string CConfiguration::ConfigurationId(const LabSettings &s, const SymbolRules &rules)
|
| | | {
|
| | | string id = StringFormat("%s|exp=%s|ser=%d|sym=%s|mag=%I64u|dev=%d|D=%d|n=%d|w=%d|t=%d|g=%d|pause=%d|timer=%d|win=%d|age=%d|quote=%d|cal=%s|seed=%I64u|cln=%d|e3=%d|cap=%d/%d|tol=%.10g|mode=%d",
|
| | | s.campaign_id, LabExperimentName(s.experiment_id), s.series_id,
|
| | | s.symbol, s.magic, s.deviation_points, s.pending_distance_ticks,
|
| | | s.count_per_condition, s.warmup_per_condition,
|
| | | s.outcome_timeout_ms, s.late_grace_ms, s.pause_ms, s.timer_requested_ms,
|
| | | s.feature_window_sec, s.feature_max_age_ms, s.quote_max_age_ms,
|
| | | s.calibration_id, s.seed, s.cleanup_max_attempts,
|
| | | s.e3_campaign_limit_min,
|
| | | s.sample_capacity, s.event_capacity, s.volume_units_tolerance,
|
| | | (int)s.run_mode);
|
| | | StringReplace(id, "|", "_");
|
| | | StringReplace(id, "/", "_");
|
| | | return(id);
|
| | | }
|
2026-09-09 01:19:31 +03:00 | | |
|
2026-09-12 22:14:24 +03:00 | | | #endif // REQUEST_LATENCY_LAB_CONFIGURATION_MQH
|
| | | //+------------------------------------------------------------------+
|