Appearance
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.expiredMainApp прекращает отправку обновлений по этой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 должно соответствовать терминалу:
| Terminal | platform | Hub client type |
|---|---|---|
| MetaTrader 5 | mt5 | MetaTrader |
| NinjaTrader 8 | ninjatrader | NinjaTrader |
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-зависимости и не удаляя прочие файлы на сервере.