2026-09-09 01:19:31 +03:00 | | | //+------------------------------------------------------------------+
|
2026-09-10 16:43:28 +03:00 | | | //| StateReader.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_STATE_READER_MQH
|
| | | #define REQUEST_LATENCY_LAB_STATE_READER_MQH
|
| | |
|
| | | #include "..\..\Include\RequestLatencyLab\Models.mqh"
|
2026-09-10 16:43:28 +03:00 | | | #include "..\..\Include\RequestLatencyLab\TimeSource.mqh"
|
2026-09-09 01:19:31 +03:00 | | |
|
| | | class CStateReader
|
| | | {
|
| | | public:
|
| | | //--- чтение активного ордера по тикету
|
2026-09-12 22:14:24 +03:00 | | | static bool ReadActiveOrder(const ulong ticket, long &state, double &volume_current,
|
| | | double &volume_initial, string &symbol, long &type,
|
2026-09-16 15:37:55 +03:00 | | | long &magic, ENUM_LAB_READ_STATUS &status);
|
2026-09-09 01:19:31 +03:00 | | | //--- чтение ордера из истории (требует HistorySelect с диапазоном)
|
2026-09-12 22:14:24 +03:00 | | | static bool ReadHistoryOrder(const ulong ticket, long &state, double &volume_current,
|
| | | double &volume_initial, string &symbol, long &type,
|
2026-09-16 15:37:55 +03:00 | | | ENUM_LAB_READ_STATUS &status);
|
2026-09-09 01:19:31 +03:00 | | | //--- сбор уникальных сделок ордера в пределах выбранной истории
|
2026-09-12 22:14:24 +03:00 | | | static int CollectDealsByOrder(const ulong order_ticket, const string symbol,
|
| | | ulong &tickets[], double &exec_units,
|
| | | const double volume_step, const double tolerance,
|
2026-09-16 15:37:55 +03:00 | | | int &api_errors, const int budget, bool &truncated);
|
| | | //--- позиция по тикету (именно тикет, не identifier)
|
| | | static bool ReadPositionFacts(const ulong ticket, PositionFacts &pf);
|
| | | //--- полный снимок для одной записи (T6 первый успешный)
|
| | | static void BuildSnapshot(const ulong sequence, const ENUM_LAB_OPERATION expected_op,
|
| | | const ulong order_ticket, const double requested_units,
|
| | | const double volume_step, const double tolerance,
|
| | | CTimeSource *clock, const ulong position_ticket_hint,
|
| | | const int history_scan_budget, ConfirmationSnapshot &snap,
|
| | | const bool light = false,
|
| | | const double callback_volume_units = 0.0,
|
| | | const int callback_deal_count = 0);
|
| | | //--- R4-B2 (audit-4): публичный перевод лотов в units (единая точка
|
| | | //--- конвертации для событийной сверки; аналог VolumeToUnitsSafe).
|
| | | static bool LotsToUnits(const double lots, const double step, const double tolerance,
|
| | | double &units);
|
| | |
|
| | | private:
|
| | | static bool VolumeToUnitsSafe(const double volume, const double step,
|
| | | const double tolerance, double &units);
|
| | | };
|
| | | //+------------------------------------------------------------------+
|
| | | //| Внешние определения методов. |
|
| | | //+------------------------------------------------------------------+
|
| | | bool CStateReader::ReadActiveOrder(const ulong ticket, long &state, double &volume_current,
|
| | | double &volume_initial, string &symbol, long &type,
|
| | | long &magic, ENUM_LAB_READ_STATUS &status)
|
| | | {
|
| | | status = LAB_READ_UNKNOWN;
|
| | | if(ticket == 0)
|
| | | return(false);
|
| | | ResetLastError();
|
| | | if(!OrderSelect(ticket))
|
2026-09-09 01:19:31 +03:00 | | | {
|
2026-09-16 15:37:55 +03:00 | | | const int e = GetLastError();
|
| | | //--- B4 (audit-3): 4754 ERR_TRADE_ORDER_NOT_FOUND — ожидаемый
|
| | | //--- «ордер не найден» -> ABSENT_CONFIRMED; прочие ошибки (в т.ч.
|
| | | //--- 4756 SEND_FAILED) остаются FAILED (не доказанное отсутствие).
|
| | | status = (e == 4754 ? LAB_READ_ABSENT_CONFIRMED : LAB_READ_FAILED);
|
| | | return(false);
|
| | | }
|
| | | state = OrderGetInteger(ORDER_STATE);
|
| | | volume_current = OrderGetDouble(ORDER_VOLUME_CURRENT);
|
| | | volume_initial = OrderGetDouble(ORDER_VOLUME_INITIAL);
|
| | | symbol = OrderGetString(ORDER_SYMBOL);
|
| | | type = OrderGetInteger(ORDER_TYPE);
|
| | | magic = OrderGetInteger(ORDER_MAGIC);
|
| | | status = LAB_READ_FOUND;
|
| | | return(true);
|
| | | }
|
| | | bool CStateReader::ReadHistoryOrder(const ulong ticket, long &state, double &volume_current,
|
| | | double &volume_initial, string &symbol, long &type,
|
| | | ENUM_LAB_READ_STATUS &status)
|
| | | {
|
| | | status = LAB_READ_UNKNOWN;
|
| | | if(ticket == 0)
|
| | | return(false);
|
| | | ResetLastError();
|
| | | if(!HistoryOrderSelect(ticket))
|
| | | {
|
| | | const int e = GetLastError();
|
| | | //--- B4 (audit-3): только 4754 считаем подтверждённым отсутствием.
|
| | | status = (e == 4754 ? LAB_READ_ABSENT_CONFIRMED : LAB_READ_FAILED);
|
| | | return(false);
|
| | | }
|
| | | state = HistoryOrderGetInteger(ticket, ORDER_STATE);
|
| | | volume_current = HistoryOrderGetDouble(ticket, ORDER_VOLUME_CURRENT);
|
| | | volume_initial = HistoryOrderGetDouble(ticket, ORDER_VOLUME_INITIAL);
|
| | | symbol = HistoryOrderGetString(ticket, ORDER_SYMBOL);
|
| | | type = HistoryOrderGetInteger(ticket, ORDER_TYPE);
|
| | | status = LAB_READ_FOUND;
|
| | | return(true);
|
| | | }
|
| | | int CStateReader::CollectDealsByOrder(const ulong order_ticket, const string symbol,
|
| | | ulong &tickets[], double &exec_units,
|
| | | const double volume_step, const double tolerance,
|
| | | int &api_errors, const int budget, bool &truncated)
|
| | | {
|
| | | int found = 0;
|
| | | exec_units = 0.0;
|
| | | truncated = false;
|
| | | //--- аудит-2 B1: детерминированная выборка истории сделок до перебора;
|
| | | //--- не зависит от ранее выбранного контекста (HistoryOrderSelect и пр.)
|
| | | //--- B6 (audit-3): проверяем bool HistorySelect; при неуспехе возвращаем
|
| | | //--- -1 (ошибка API), положительное завершение не допускается.
|
| | | ResetLastError();
|
| | | if(!HistorySelect((datetime)0, TimeCurrent()))
|
| | | {
|
| | | api_errors++;
|
| | | return(-1);
|
| | | }
|
| | | const int total = HistoryDealsTotal();
|
| | | if(total < 0)
|
| | | {
|
| | | api_errors++;
|
| | | return(-1);
|
| | | }
|
| | | int scanned = 0;
|
| | | for(int i = total - 1; i >= 0 && scanned < budget; i--, scanned++)
|
| | | {
|
| | | const ulong dt = HistoryDealGetTicket(i);
|
| | | if(dt == 0)
|
 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 | | | {
|
| | | api_errors++;
|
2026-09-16 15:37:55 +03:00 | | | continue;
|
 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 | | | }
|
2026-09-16 15:37:55 +03:00 | | | if((ulong)HistoryDealGetInteger(dt, DEAL_ORDER) != order_ticket)
|
| | | continue;
|
| | | if(StringLen(symbol) > 0 && HistoryDealGetString(dt, DEAL_SYMBOL) != symbol)
|
| | | continue;
|
| | | //--- уникальность тикета в собранном наборе
|
| | | bool dup = false;
|
| | | for(int k = 0; k < found; k++)
|
| | | if(tickets[k] == dt)
|
2026-09-09 01:19:31 +03:00 | | | {
|
2026-09-16 15:37:55 +03:00 | | | dup = true;
|
| | | break;
|
2026-09-09 01:19:31 +03:00 | | | }
|
2026-09-16 15:37:55 +03:00 | | | if(dup)
|
| | | continue;
|
| | | //--- R5-S4: буфер реестра исчерпан — явный неполный исход,
|
| | | //--- а не молчаливый обрыв (сигнал «не все данные» != «данных нет»)
|
| | | if(found >= ArraySize(tickets))
|
| | | {
|
2026-09-12 22:14:24 +03:00 | | | truncated = true;
|
2026-09-16 15:37:55 +03:00 | | | continue;
|
| | | }
|
| | | tickets[found] = dt;
|
| | | found++;
|
| | | const double vol = HistoryDealGetDouble(dt, DEAL_VOLUME);
|
| | | double units = 0;
|
| | | if(VolumeToUnitsSafe(vol, volume_step, tolerance, units))
|
| | | exec_units += units;
|
2026-09-09 01:19:31 +03:00 | | | }
|
2026-09-16 15:37:55 +03:00 | | | //--- R5-S4: достижение бюджета сканирования — история, возможно, неполна
|
| | | if(scanned >= budget && budget > 0)
|
| | | truncated = true;
|
| | | return(found);
|
| | | }
|
| | | bool CStateReader::ReadPositionFacts(const ulong ticket, PositionFacts &pf)
|
| | | {
|
| | | pf.Zero();
|
| | | if(ticket == 0)
|
| | | return(false);
|
| | | ResetLastError();
|
| | | if(!PositionSelectByTicket(ticket))
|
| | | return(false);
|
| | | pf.ticket = ticket;
|
| | | pf.identifier = (ulong)PositionGetInteger(POSITION_IDENTIFIER);
|
| | | pf.type = PositionGetInteger(POSITION_TYPE);
|
| | | pf.volume = PositionGetDouble(POSITION_VOLUME);
|
| | | pf.magic = (ulong)PositionGetInteger(POSITION_MAGIC);
|
| | | pf.symbol = PositionGetString(POSITION_SYMBOL);
|
| | | pf.read_status = true;
|
| | | return(true);
|
| | | }
|
| | | void CStateReader::BuildSnapshot(const ulong sequence, const ENUM_LAB_OPERATION expected_op,
|
| | | const ulong order_ticket, const double requested_units,
|
| | | const double volume_step, const double tolerance,
|
| | | CTimeSource *clock, const ulong position_ticket_hint,
|
| | | const int history_scan_budget, ConfirmationSnapshot &snap,
|
| | | const bool light, const double callback_volume_units,
|
| | | const int callback_deal_count)
|
| | | {
|
| | | snap.Zero();
|
| | | snap.sequence = sequence;
|
| | | snap.expected_operation = expected_op;
|
| | | snap.order_ticket = order_ticket;
|
| | | snap.requested_volume_units = requested_units;
|
| | | snap.light_snapshot = light; // R6-B1: вид сверки — явный признак (не deal_count)
|
| | | snap.check_start_us = clock.NowUs(); // начало построения снимка
|
| | | if(order_ticket == 0)
|
| | | return;
|
| | | long state = 0;
|
| | | double vol_cur = 0, vol_ini = 0;
|
| | | string sym = "";
|
| | | long type = 0, magic = 0;
|
| | | ENUM_LAB_READ_STATUS st = LAB_READ_UNKNOWN;
|
| | | if(ReadActiveOrder(order_ticket, state, vol_cur, vol_ini, sym, type, magic, st))
|
2026-09-09 01:19:31 +03:00 | | | {
|
2026-09-16 15:37:55 +03:00 | | | snap.active_select_status = LAB_READ_FOUND;
|
| | | snap.active_select_error = 0;
|
| | | snap.order_state_before = state;
|
| | | //--- R4-S3: поля *_volume_units всегда в units (лоты -> шаги объёма)
|
| | | double ini_u = 0.0, rem_u = 0.0;
|
| | | VolumeToUnitsSafe(vol_ini, volume_step, tolerance, ini_u);
|
| | | VolumeToUnitsSafe(vol_cur, volume_step, tolerance, rem_u);
|
| | | snap.initial_volume_units = ini_u;
|
| | | snap.remaining_volume_units = rem_u;
|
| | | //--- R4-S2: факты ордера для сверки ожидаемого контракта
|
| | | snap.order_symbol = OrderGetString(ORDER_SYMBOL);
|
| | | snap.order_type = OrderGetInteger(ORDER_TYPE);
|
| | | snap.order_magic = (ulong)OrderGetInteger(ORDER_MAGIC);
|
| | | snap.order_volume_lots = OrderGetDouble(ORDER_VOLUME_INITIAL);
|
| | | snap.order_price = OrderGetDouble(ORDER_PRICE_OPEN);
|
| | | snap.trading_state_known = true;
|
2026-09-09 01:19:31 +03:00 | | | }
|
2026-09-16 15:37:55 +03:00 | | | else
|
2026-09-09 01:19:31 +03:00 | | | {
|
2026-09-16 15:37:55 +03:00 | | | snap.active_select_status = st;
|
| | | snap.active_select_error = GetLastError();
|
| | | //--- проверка истории для терминальных состояний
|
| | | if(ReadHistoryOrder(order_ticket, state, vol_cur, vol_ini, sym, type, st))
|
2026-09-09 01:19:31 +03:00 | | | {
|
2026-09-16 15:37:55 +03:00 | | | snap.history_select_status = LAB_READ_FOUND;
|
| | | snap.history_order_error = 0;
|
2026-09-12 22:14:24 +03:00 | | | snap.order_state_before = state;
|
2026-09-16 15:37:55 +03:00 | | | //--- R4-S3: единицы объёма как units
|
2026-09-12 22:14:24 +03:00 | | | double ini_u = 0.0, rem_u = 0.0;
|
| | | VolumeToUnitsSafe(vol_ini, volume_step, tolerance, ini_u);
|
| | | VolumeToUnitsSafe(vol_cur, volume_step, tolerance, rem_u);
|
| | | snap.initial_volume_units = ini_u;
|
| | | snap.remaining_volume_units = rem_u;
|
2026-09-16 15:37:55 +03:00 | | | //--- R4-S2: факты ордера из истории
|
| | | snap.order_symbol = HistoryOrderGetString(order_ticket, ORDER_SYMBOL);
|
| | | snap.order_type = HistoryOrderGetInteger(order_ticket, ORDER_TYPE);
|
| | | snap.order_magic = (ulong)HistoryOrderGetInteger(order_ticket, ORDER_MAGIC);
|
| | | snap.order_volume_lots = HistoryOrderGetDouble(order_ticket, ORDER_VOLUME_INITIAL);
|
| | | snap.order_price = HistoryOrderGetDouble(order_ticket, ORDER_PRICE_OPEN);
|
2026-09-12 22:14:24 +03:00 | | | snap.trading_state_known = true;
|
2026-09-09 01:19:31 +03:00 | | | }
|
| | | else
|
| | | {
|
2026-09-16 15:37:55 +03:00 | | | snap.history_select_status = st;
|
| | | snap.history_order_error = GetLastError();
|
2026-09-09 01:19:31 +03:00 | | | }
|
2026-09-16 15:37:55 +03:00 | | | }
|
| | | //--- сделки ордера
|
| | | int api_errors = 0;
|
| | | if(light)
|
| | | {
|
| | | //--- B5 (audit-3): событийная сверка в callback выполняется адресно
|
| | | //--- и коротко: объём берётся из накопленных callback-сделок записи,
|
| | | //--- без полного прохода истории (запрещено в OnTradeTransaction).
|
| | | snap.deal_count = callback_deal_count;
|
| | | snap.executed_volume_units = callback_volume_units;
|
| | | snap.history_scan_failed = false;
|
| | | snap.api_errors = 0;
|
| | | }
|
| | | else
|
| | | {
|
| | | bool truncated = false;
|
| | | snap.deal_count = CollectDealsByOrder(order_ticket, sym, snap.deal_tickets,
|
| | | snap.executed_volume_units, volume_step,
|
| | | tolerance, api_errors, history_scan_budget,
|
| | | truncated);
|
| | | if(snap.deal_count < 0)
|
 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 | | | {
|
2026-09-16 15:37:55 +03:00 | | | snap.deal_count = 0;
|
| | | snap.history_scan_failed = true;
|
| | | snap.api_errors = api_errors;
|
 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-16 15:37:55 +03:00 | | | //--- R5-S4: прерванный скан (бюджет/ёмкость) = неполный исход
|
| | | snap.history_scan_failed = truncated;
|
| | | snap.api_errors = api_errors;
|
 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 | | | }
|
2026-09-16 15:37:55 +03:00 | | | }
|
| | | //--- покрытие объёма: полное исполнение (для B3/B4 и CompletionPolicy)
|
| | | snap.coverage_complete = (requested_units > 0.0 && snap.deal_count > 0 &&
|
| | | MathAbs(snap.executed_volume_units - requested_units) <= tolerance);
|
| | | //--- позиция (аудит-2 S7): адресная связь по переданному тикету
|
| | | //--- либо по DEAL_POSITION_ID сделок ордера; PositionGetTicket(0)
|
| | | //--- по символу не используется (ненадёжно на hedging-счёте)
|
| | | ulong pos_ticket = position_ticket_hint;
|
| | | ulong pos_identifier = 0;
|
| | | if(pos_ticket == 0)
|
| | | {
|
| | | for(int k = 0; k < snap.deal_count; k++)
|
2026-09-10 19:04:10 +03:00 | | | {
|
2026-09-16 15:37:55 +03:00 | | | const ulong dt = snap.deal_tickets[k];
|
| | | const ulong pid = (ulong)HistoryDealGetInteger(dt, DEAL_POSITION_ID);
|
| | | if(pid != 0)
|
| | | pos_identifier = pid;
|
| | | }
|
| | | if(pos_identifier != 0)
|
| | | {
|
| | | ResetLastError();
|
| | | for(int i = PositionsTotal() - 1; i >= 0; i--)
|
2026-09-10 19:04:10 +03:00 | | | {
|
2026-09-16 15:37:55 +03:00 | | | const ulong tt = PositionGetTicket(i);
|
| | | if(tt == 0)
|
| | | continue;
|
| | | if(PositionGetString(POSITION_SYMBOL) != sym)
|
| | | continue;
|
| | | if((ulong)PositionGetInteger(POSITION_IDENTIFIER) == pos_identifier)
|
2026-09-10 19:04:10 +03:00 | | | {
|
2026-09-16 15:37:55 +03:00 | | | pos_ticket = tt;
|
| | | break;
|
2026-09-10 19:04:10 +03:00 | | | }
|
| | | }
|
| | | }
|
2026-09-09 01:19:31 +03:00 | | | }
|
2026-09-16 15:37:55 +03:00 | | | PositionFacts pf;
|
| | | if(pos_ticket > 0 && ReadPositionFacts(pos_ticket, pf))
|
2026-09-09 01:19:31 +03:00 | | | {
|
2026-09-16 15:37:55 +03:00 | | | snap.position_found = true;
|
| | | snap.position_ticket = pos_ticket;
|
| | | snap.position_volume = pf.volume;
|
| | | snap.position_identifier = pf.identifier;
|
| | | snap.position_type = pf.type;
|
| | | snap.trading_state_known = true;
|
| | | snap.position_facts_last = pf;
|
2026-09-09 01:19:31 +03:00 | | | }
|
2026-09-16 15:37:55 +03:00 | | | snap.check_end_us = clock.NowUs(); // конец построения снимка; T6 ставит CompletionPolicy (п.7 аудита)
|
| | | }
|
| | | bool CStateReader::LotsToUnits(const double lots, const double step, const double tolerance,
|
| | | double &units)
|
| | | {
|
| | | if(step <= 0.0)
|
| | | return(false);
|
| | | const double ratio = lots / step;
|
| | | const double q = MathRound(ratio);
|
| | | if(MathAbs(ratio - q) > tolerance)
|
| | | return(false);
|
| | | units = q;
|
| | | return(true);
|
| | | }
|
| | | bool CStateReader::VolumeToUnitsSafe(const double volume, const double step,
|
| | | const double tolerance, double &units)
|
| | | {
|
| | | return(LotsToUnits(volume, step, tolerance, units));
|
| | | }
|
2026-09-09 01:19:31 +03:00 | | |
|
2026-09-12 22:14:24 +03:00 | | | #endif // REQUEST_LATENCY_LAB_STATE_READER_MQH
|
| | | //+------------------------------------------------------------------+
|