//+------------------------------------------------------------------+ //| Configuration.mqh | //| Copyright 2026, MetaQuotes Ltd. | //| www.mql5.com | //+------------------------------------------------------------------+ #ifndef REQUEST_LATENCY_LAB_CONFIGURATION_MQH #define REQUEST_LATENCY_LAB_CONFIGURATION_MQH #include "..\..\Include\RequestLatencyLab\Models.mqh" //+------------------------------------------------------------------+ //| Режимы работы советника (ТЗ §1). | //+------------------------------------------------------------------+ enum ENUM_LAB_RUN_MODE { LAB_RUN_LOCAL_ONLY = 0, // по умолчанию; без торговли LAB_RUN_PENDING_CREATE_DELETE, LAB_RUN_MARKET_ORDER }; //+------------------------------------------------------------------+ //| Настройки лаборатории (заполняются из input советника). | //+------------------------------------------------------------------+ struct LabSettings { 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; int e3_campaign_limit_min; // 0=без лимита; иначе минуты ENUM_LAB_RUN_MODE run_mode; string data_origin; // DEMO/SYNTHETIC string configuration_id; void Zero(void); }; //+------------------------------------------------------------------+ //| CConfiguration — проверка и фиксация конфигурации. | //+------------------------------------------------------------------+ class CConfiguration { public: //--- базовая проверка входных параметров static bool Validate(const LabSettings &s, LabError &error); //--- правила символа из терминала static bool BuildSymbolRules(const string symbol, SymbolRules &rules, LabError &error); //--- описание символа для манифеста static string ManifestSymbolInfo(const SymbolRules &r) { return(StringFormat("%s|digits=%d|point=%.10g|tick_size=%.10g|vol_min=%.4g|vol_max=%.4g|vol_step=%.4g", r.symbol, r.digits, r.point, r.tick_size, r.volume_min, r.volume_max, r.volume_step)); } //--- минимальный допустимый объём (ТЗ §2.4). Фиксируется до серии. 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) { error.code = 1; error.severity = LAB_SEV_BLOCKER; error.message = "count_per_condition must be >0"; return(false); } if(s.warmup_per_condition > 0 && s.warmup_per_condition >= s.count_per_condition) { error.code = 2; error.severity = LAB_SEV_BLOCKER; error.message = "warmup must be smaller than count_per_condition"; return(false); } 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); } #endif // REQUEST_LATENCY_LAB_CONFIGURATION_MQH //+------------------------------------------------------------------+