Skip to content

Dynamic Stop Loss / Take Profit

Эта страница описывает общий контракт между MainApp, Backend и торговыми терминалами в части динамических Stop Loss и Take Profit.

Где разрешены динамические источники

Динамические источники доступны только для сигналов категории Price / Indicator. Для сигналов Screen / OCR они не применяются, и отправлять их не следует.

В качестве источника можно выбрать:

  • Open, High, Low или Close;
  • значение индикаторной колонки, например IndHigh или IndLow.

Volume и его краткое обозначение V запрещены в качестве источников динамической защиты. Поле DOHLCV.V может присутствовать в диагностическом OHLC-пакете, однако это не означает, что Volume разрешено использовать для Stop Loss или Take Profit.

Три варианта для каждой стороны защиты

Для Stop Loss и Take Profit доступны три взаимоисключающих варианта:

  • абсолютная цена: Stop Loss или Take Profit;
  • расстояние в тиках: Stop Loss Ticks или Take Profit Ticks;
  • динамический источник: Stop Loss Source или Take Profit Source.

Для каждой стороны допускается не более одного варианта. Stop Loss и Take Profit задаются независимо друг от друга: можно указать только Stop Loss, только Take Profit или обе стороны сразу.

Динамический источник допустим только для order.submit и не используется в position.protect: защита уже открытой позиции через position.protect должна передаваться в виде уже вычисленного числа или количества тиков.

Что делает MainApp

MainApp вычисляет значения источников на основе текущего снимка данных Price / Indicator. Терминальный плагин получает уже готовые числовые значения и не выполняет повторный расчет индикаторов.

Для динамического входа MainApp отправляет один первоначальный order.submit:

json
{
  "signalId": "delivery-entry-001",
  "type": "order",
  "cmd": "submit",
  "payload": {
    "instrument": "ES 09-26",
    "side": "buy",
    "executionType": "market",
    "quantity": 1,
    "stopLoss": 6280.25,
    "lifecycleKey": "life-001",
    "entrySignalId": "delivery-entry-001"
  }
}

Если изменился только источник Take Profit, MainApp отправляет отдельное обновление с новым signalId, но с сохранением тех же lifecycleKey и entrySignalId:

json
{
  "signalId": "delivery-protection-002",
  "type": "order",
  "cmd": "submit",
  "payload": {
    "instrument": "ES 09-26",
    "side": "buy",
    "executionType": "market",
    "quantity": 1,
    "takeProfit": 6310.25,
    "lifecycleKey": "life-001",
    "entrySignalId": "delivery-entry-001",
    "protectionUpdate": true
  }
}

Правила обновлений:

  • каждое новое обновление получает новый delivery signalId;
  • lifecycleKey остается неизменным на протяжении всей операции входа;
  • entrySignalId по-прежнему указывает на первоначальный вход;
  • передается только та сторона защиты, которая изменилась;
  • отсутствие Stop Loss или Take Profit в обновлении не означает удаления второй стороны;
  • после получения position.closed, order.cancelled или order.expired MainApp прекращает отправку обновлений по этой lifecycle-сессии.

Lifecycle-корреляция

Первоначальный динамический order.submit регистрирует в Backend корреляцию:

LifecycleKey + EntrySignalId

До ее регистрации терминал не должен отправлять lifecycle-события. Если Backend не находит соответствующую запись, он возвращает:

json
{
  "success": false,
  "status": "NotFound",
  "message": "No registered lifecycle was found."
}

Для передачи lifecycle-сообщений используется метод SignalR Hub — PublishPositionLifecycle. Метод возвращает ACK-объект с полями success, status, state и message.

Значение platform в payload должно соответствовать терминалу:

TerminalplatformHub client type
MetaTrader 5mt5MetaTrader
NinjaTrader 8ninjatraderNinjaTrader

terminalInstanceId должен точно совпадать с зарегистрированным Instance ID, а accountName и instrument — с account и symbol/instrument, для которых была создана корреляция.

Минимальный lifecycle payload:

json
{
  "eventId": "event-001",
  "eventType": "position.opened",
  "platform": "mt5",
  "terminalInstanceId": "MT5-DEMO-01",
  "accountName": "Demo",
  "instrument": "EURUSD",
  "positionRef": "position-001",
  "entrySignalId": "delivery-entry-001",
  "lifecycleKey": "life-001",
  "sequence": 1,
  "timestamp": 1785060000000
}

Backend дополнительно проверяет, что lifecycle принадлежит нужному пользователю, terminal instance, платформе, account и instrument.

Для события position.opened Backend возвращает state: "active" — это внутреннее состояние Backend, и терминалу достаточно проверять success и status. Для полного закрытия lifecycle используется событие position.closed.

Пример успешного ACK:

json
{
  "success": true,
  "status": "OK",
  "state": "active",
  "message": null
}

Правила обработки ACK:

  • OK — событие принято;
  • Duplicate, StaleSequence, TerminalState — безопасный результат для повторной или устаревшей доставки; повторно отправлять событие не нужно;
  • NotFound, EntrySignalMismatch, TerminalInstanceMismatch, PlatformMismatch, AccountMismatch, InstrumentMismatch, UnauthorizedTerminal, InvalidPayload, Disabled — свидетельствуют об ошибке контракта или регистрации, а не о поводе повторить тот же payload.

Для MT5-коннектора необходим экспорт DLL PublishPositionLifecycle и вызов Hub-метода с нормализованным lifecycle payload. Если экспорт отсутствует, outbox в MQL5 не сможет доставить событие в Backend.

Для NinjaTrader 8 текущий indicator package уже реализует lifecycle outbox: он обрабатывает PositionUpdate/ExecutionUpdate и вызывает PublishPositionLifecycle с capability position-lifecycle-v1. Об успешной доставке можно судить по записи feedback sent в логе. В payload используется каноническое значение platform: "ninjatrader"; Backend также принимает nt8.

Поведение при закрытии

MainApp получает от Backend событие PositionStateChanged и сопоставляет его сначала по lifecycleKey, а затем — как совместимый fallback — по entrySignalId или positionRef. При наступлении position.closed, order.cancelled или order.expired динамическая сессия удаляется, поэтому последующие изменения индикаторов больше не порождают команд.

Backend deployment

Production Backend работает из:

/var/www/signalr-app2

В этой же папке находятся:

  • SSNew.dll и runtime-файлы;
  • ssnew.db;
  • ssnew-backend.env;
  • GeoLite2-Country.mmdb.

Скрипт deploy.ps1 сохраняет файлы *.db, *.db-shm, *.db-wal и *.env при синхронизации. Режим -Fast передает только основные файлы приложения (SSNew.dll, apphost, deps/runtime JSON и production-конфигурацию), не копируя DLL-зависимости и не удаляя прочие файлы на сервере.

TradingMonitor.Pro documentation.