2026-09-09 01:19:31 +03:00 | | | //+------------------------------------------------------------------+
|
2026-09-10 16:43:28 +03:00 | | | //| TradeRequestTools.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_LEGACY_TRADE_REQUEST_TOOLS_MQH
|
| | | #define REQUEST_LATENCY_LAB_LEGACY_TRADE_REQUEST_TOOLS_MQH
|
| | |
|
| | | #include "..\..\..\Include\RequestLatencyLab\Models.mqh"
|
| | | //+------------------------------------------------------------------+
|
| | | //| Нормализация цены по SYMBOL_DIGITS (алгоритм исходника). |
|
| | | //+------------------------------------------------------------------+
|
2026-09-12 22:14:24 +03:00 | | | bool LabNormalizePrice(const string symbol, const double price, double &result)
|
2026-09-09 01:19:31 +03:00 | | | {
|
2026-09-12 22:14:24 +03:00 | | | if(StringLen(symbol) == 0)
|
2026-09-10 16:43:28 +03:00 | | | return(false);
|
2026-09-12 22:14:24 +03:00 | | | const int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
|
| | | result = NormalizeDouble(price, digits);
|
2026-09-10 16:43:28 +03:00 | | | return(MathIsValidNumber(result));
|
2026-09-09 01:19:31 +03:00 | | | }
|
| | | //+------------------------------------------------------------------+
|
| | | //| Нормализация объёма: диапазон SYMBOL_VOLUME_MIN..MAX и шаг STEP, |
|
| | | //| с явной проверкой шага и диапазона. Тихо не меняет объём: |
|
2026-09-12 22:14:24 +03:00 | | | //| выход за диапазон даёт false (требование ТЗ §2.4). |
|
2026-09-09 01:19:31 +03:00 | | | //+------------------------------------------------------------------+
|
2026-09-12 22:14:24 +03:00 | | | bool LabNormalizeVolume(const string symbol, const double volume, double &result)
|
2026-09-09 01:19:31 +03:00 | | | {
|
2026-09-12 22:14:24 +03:00 | | | if(StringLen(symbol) == 0)
|
2026-09-10 16:43:28 +03:00 | | | return(false);
|
2026-09-12 22:14:24 +03:00 | | | const double min_v = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
|
| | | const double max_v = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
|
| | | const double step_v = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
|
| | | if(step_v <= 0.0)
|
2026-09-10 16:43:28 +03:00 | | | return(false);
|
2026-09-12 22:14:24 +03:00 | | | if(volume < min_v || volume > max_v)
|
2026-09-10 16:43:28 +03:00 | | | return(false);
|
2026-09-12 22:14:24 +03:00 | | | double v = MathRound(volume / step_v) * step_v;
|
| | | int vdigits = (int)MathCeil(-MathLog10(step_v));
|
| | | if(vdigits < 0)
|
| | | vdigits = 0;
|
| | | v = NormalizeDouble(v, vdigits);
|
| | | if(v < min_v || v > max_v)
|
2026-09-10 16:43:28 +03:00 | | | return(false);
|
2026-09-12 22:14:24 +03:00 | | | result = v;
|
2026-09-10 16:43:28 +03:00 | | | return(MathIsValidNumber(result));
|
2026-09-09 01:19:31 +03:00 | | | }
|
| | | //+------------------------------------------------------------------+
|
| | | //| Перевод объёма в целые шаги: q=round(volume/step), допуск |
|
| | | //| abs(volume/step-q)<=tolerance (ТЗ §6.4, предлагаемый 1e-7). |
|
| | | //+------------------------------------------------------------------+
|
2026-09-12 22:14:24 +03:00 | | | bool VolumeToUnits(const double volume, const double step, const double tolerance,
|
2026-09-09 01:19:31 +03:00 | | | double &units)
|
| | | {
|
2026-09-12 22:14:24 +03:00 | | | if(step <= 0.0)
|
2026-09-10 16:43:28 +03:00 | | | return(false);
|
2026-09-12 22:14:24 +03:00 | | | const double ratio = volume / step;
|
| | | const double q = MathRound(ratio);
|
| | | if(MathAbs(ratio - q) > tolerance)
|
2026-09-10 16:43:28 +03:00 | | | return(false);
|
2026-09-12 22:14:24 +03:00 | | | units = q;
|
2026-09-10 16:43:28 +03:00 | | | return(true);
|
2026-09-09 01:19:31 +03:00 | | | }
|
| | | //+------------------------------------------------------------------+
|
| | | //| Выбор режима исполнения (алгоритм исходника): для рыночной |
|
| | | //| заявки — первый допустимый FOK/IOC, иначе RETURN. Для отложенных |
|
2026-09-12 22:14:24 +03:00 | | | //| ордеров протокол закрепляет RETURN (ТЗ §2.4). |
|
2026-09-09 01:19:31 +03:00 | | | //+------------------------------------------------------------------+
|
2026-09-12 22:14:24 +03:00 | | | ENUM_ORDER_TYPE_FILLING SelectFilling(const string symbol, const bool is_pending)
|
2026-09-09 01:19:31 +03:00 | | | {
|
| | | if(is_pending)
|
2026-09-10 16:43:28 +03:00 | | | return(ORDER_FILLING_RETURN);
|
2026-09-12 22:14:24 +03:00 | | | const long mode = SymbolInfoInteger(symbol, SYMBOL_FILLING_MODE);
|
| | | if((mode & SYMBOL_FILLING_FOK) != 0)
|
2026-09-10 16:43:28 +03:00 | | | return(ORDER_FILLING_FOK);
|
2026-09-12 22:14:24 +03:00 | | | if((mode & SYMBOL_FILLING_IOC) != 0)
|
2026-09-10 16:43:28 +03:00 | | | return(ORDER_FILLING_IOC);
|
| | | return(ORDER_FILLING_RETURN);
|
2026-09-09 01:19:31 +03:00 | | | }
|
| | | //+------------------------------------------------------------------+
|
| | | //| Построение MqlTradeRequest из плана, правил символа и котировки. |
|
| | | //| Buy Limit — ниже Bid на D шагов (округление вниз), Sell Limit — |
|
| | | //| выше Ask на D шагов (округление вверх), ТЗ §7. |
|
| | | //+------------------------------------------------------------------+
|
2026-09-12 22:14:24 +03:00 | | | bool LabBuildRequest(const RequestPlan &plan, const SymbolRules &rules,
|
| | | const MqlTick "e, MqlTradeRequest &request,
|
2026-09-09 01:19:31 +03:00 | | | LabError &error)
|
| | | {
|
| | | error.Reset();
|
| | | ZeroMemory(request);
|
| | | if(!rules.IsValid())
|
| | | {
|
2026-09-12 22:14:24 +03:00 | | | error.component = LAB_COMP_TRADE_TOOLS;
|
| | | error.code = 1;
|
| | | error.severity = LAB_SEV_BLOCKER;
|
| | | error.message = "invalid symbol rules";
|
2026-09-10 16:43:28 +03:00 | | | return(false);
|
2026-09-09 01:19:31 +03:00 | | | }
|
2026-09-12 22:14:24 +03:00 | | | const bool is_market = (plan.operation == LAB_OP_MARKET_OPEN ||
|
| | | plan.operation == LAB_OP_POSITION_CLOSE);
|
| | | request.action = (is_market ? TRADE_ACTION_DEAL : TRADE_ACTION_PENDING);
|
| | | request.magic = (long)plan.magic;
|
| | | request.symbol = plan.symbol;
|
| | | request.volume = plan.volume;
|
| | | //--- S1: фактическое отклонение из плана (не хардкод 0)
|
| | | request.deviation = plan.deviation_points;
|
| | | request.type_time = ORDER_TIME_GTC;
|
| | | request.type_filling = SelectFilling(rules.symbol, !is_market);
|
| | | //--- S1: фактический comment (или дефолт)
|
| | | if(StringLen(plan.comment) > 0)
|
| | | request.comment = plan.comment;
|
 fix(audit3): блокеры B1-B10 и существенные S1-S6 этапа 3 аудита
Блокеры:
- B1: cleanup как полная запись tracker (role=CLEANUP, lifecycle, DriveCleanup,
нулевая экспозиция, однократный dispatch, отказ -> BLOCKED)
- B2: отложенная корреляция ранних ORDER_ADD/DEAL_ADD (PushDeferred/ReconcileDeferred)
- B3: collection не закрывается до ожидаемого REQUEST (T2) для market
- B4: 4754 = not found; 4756/FAILED не = absence; no_active без FAILED
- B5: событийная сверка light (без полного HistorySelect в callback)
- B6: проверка bool HistorySelect; history_scan_budget вместо 100000
- B7: финальный reject подключён к T6 (IsFinalRejection); неоднозначные — нет
- B8: E4 — reuse-анализ без торгового runtime; reuse=1
- B9: PLANNED->INTENT_SAVED->DISPATCHED; счётчик после OrderSend*; один PrecheckRejected
- B10: write/timer/journal errors блокирующие (export check, integrity), timer check
Существенные:
- S1: фактические deviation/comment из настроек в RequestPlan/запросе
- S2: bid/ask/spread/tick_time_msc + actual fields в samples.csv
- S3: пустые статистики summary при n<2
- S4: histogram_rebuilt.csv в LatencyReport; experiment/series из выборки
- S5: калибровка E3: символ + >=5 торговых сессий (trading_session_count)
- S6: market cleanup: type_filling/deviation + PreCheck
Тесты: DEFER-01, NACT-01, REJ-01, AMB-01.
Компиляция 4 exe: 0 errors / 0 warnings. EOL CRLF.
2026-09-10 21:54:49 +03:00 | | | else
|
2026-09-12 22:14:24 +03:00 | | | request.comment = StringFormat("RLL_%s_%I64u", plan.condition_id, plan.sequence);
|
| | | if(plan.operation == LAB_OP_PENDING_DELETE)
|
2026-09-09 01:19:31 +03:00 | | | {
|
2026-09-12 22:14:24 +03:00 | | | request.action = TRADE_ACTION_REMOVE;
|
| | | request.order = plan.request_price > 0.0 ? (ulong)MathRound(plan.request_price) : 0;
|
2026-09-10 16:43:28 +03:00 | | | return(true);
|
2026-09-09 01:19:31 +03:00 | | | }
|
| | | switch(plan.operation)
|
| | | {
|
| | | case LAB_OP_MARKET_OPEN:
|
| | | case LAB_OP_POSITION_CLOSE:
|
| | | {
|
2026-09-12 22:14:24 +03:00 | | | if(plan.side == LAB_SIDE_BUY)
|
2026-09-09 01:19:31 +03:00 | | | {
|
2026-09-12 22:14:24 +03:00 | | | request.type = ORDER_TYPE_BUY;
|
| | | request.price = quote.ask;
|
2026-09-09 01:19:31 +03:00 | | | }
|
| | | else
|
2026-09-12 22:14:24 +03:00 | | | if(plan.side == LAB_SIDE_SELL)
|
| | | {
|
| | | request.type = ORDER_TYPE_SELL;
|
| | | request.price = quote.bid;
|
| | | }
|
| | | else
|
| | | {
|
| | | error.component = LAB_COMP_TRADE_TOOLS;
|
| | | error.code = 2;
|
| | | error.message = "market operation requires side";
|
| | | return(false);
|
| | | }
|
2026-09-09 01:19:31 +03:00 | | | break;
|
| | | }
|
| | | case LAB_OP_PENDING_CREATE:
|
| | | {
|
2026-09-12 22:14:24 +03:00 | | | if(plan.side == LAB_SIDE_BUY)
|
2026-09-09 01:19:31 +03:00 | | | {
|
2026-09-12 22:14:24 +03:00 | | | request.type = ORDER_TYPE_BUY_LIMIT;
|
| | | request.price = quote.bid - plan.distance_ticks * rules.tick_size;
|
2026-09-09 01:19:31 +03:00 | | | }
|
| | | else
|
2026-09-12 22:14:24 +03:00 | | | if(plan.side == LAB_SIDE_SELL)
|
| | | {
|
| | | request.type = ORDER_TYPE_SELL_LIMIT;
|
| | | request.price = quote.ask + plan.distance_ticks * rules.tick_size;
|
| | | }
|
| | | else
|
| | | {
|
| | | error.component = LAB_COMP_TRADE_TOOLS;
|
| | | error.code = 3;
|
| | | error.message = "pending create requires side";
|
| | | return(false);
|
| | | }
|
2026-09-09 01:19:31 +03:00 | | | break;
|
| | | }
|
| | | default:
|
| | | {
|
2026-09-12 22:14:24 +03:00 | | | error.component = LAB_COMP_TRADE_TOOLS;
|
| | | error.code = 4;
|
| | | error.message = "unsupported operation";
|
2026-09-10 16:43:28 +03:00 | | | return(false);
|
2026-09-09 01:19:31 +03:00 | | | }
|
| | | }
|
2026-09-12 22:14:24 +03:00 | | | //--- нормализация цены; для лимитных ордеров округление в сторону от рынка
|
| | | double price = request.price;
|
| | | if(!LabNormalizePrice(rules.symbol, price, price))
|
2026-09-09 01:19:31 +03:00 | | | {
|
2026-09-12 22:14:24 +03:00 | | | error.component = LAB_COMP_TRADE_TOOLS;
|
| | | error.code = 5;
|
| | | error.message = "price normalization failed";
|
2026-09-10 16:43:28 +03:00 | | | return(false);
|
2026-09-09 01:19:31 +03:00 | | | }
|
2026-09-12 22:14:24 +03:00 | | | if(request.type == ORDER_TYPE_BUY_LIMIT)
|
| | | price = MathFloor(price / rules.tick_size + 0.00000001) * rules.tick_size;
|
| | | else
|
| | | if(request.type == ORDER_TYPE_SELL_LIMIT)
|
| | | price = MathCeil(price / rules.tick_size - 0.00000001) * rules.tick_size;
|
| | | request.price = NormalizeDouble(price, rules.digits);
|
2026-09-10 16:43:28 +03:00 | | | return(true);
|
2026-09-09 01:19:31 +03:00 | | | }
|
| | | //+------------------------------------------------------------------+
|
| | | //| PreCheck — обёртка OrderCheck без журналирования результата |
|
| | | //| (журналирование — на усмотрение вызывающего компонента). |
|
| | | //+------------------------------------------------------------------+
|
2026-09-12 22:14:24 +03:00 | | | bool LabPreCheck(const MqlTradeRequest &request, MqlTradeCheckResult &result,
|
2026-09-09 01:19:31 +03:00 | | | LabError &error)
|
| | | {
|
| | | error.Reset();
|
| | | ZeroMemory(result);
|
2026-09-12 22:14:24 +03:00 | | | if(!OrderCheck(request, result))
|
2026-09-09 01:19:31 +03:00 | | | {
|
2026-09-12 22:14:24 +03:00 | | | error.component = LAB_COMP_TRADE_TOOLS;
|
| | | error.code = 10;
|
| | | error.mql_error = GetLastError();
|
| | | error.severity = LAB_SEV_BLOCKER;
|
| | | error.message = StringFormat("OrderCheck refused retcode=%u", result.retcode);
|
2026-09-10 16:43:28 +03:00 | | | return(false);
|
2026-09-09 01:19:31 +03:00 | | | }
|
2026-09-12 22:14:24 +03:00 | | | result.retcode = TRADE_RETCODE_DONE;
|
2026-09-10 16:43:28 +03:00 | | | return(true);
|
2026-09-09 01:19:31 +03:00 | | | }
|
| | |
|
2026-09-12 22:14:24 +03:00 | | | #endif // REQUEST_LATENCY_LAB_LEGACY_TRADE_REQUEST_TOOLS_MQH
|
| | | //+------------------------------------------------------------------+
|