В статье рассматривается механика внешних (серверных) стоп-ордеров в OsEngine: как поддержка включается в коннекторе, какие коннекторы её имеют, как устроен жизненный цикл внешнего стопа и как он хранится внутри позиции.
До этого момента все стопы внутри платформы были внутренние. Теперь у нас появился механизм для выставления внешних. Пока в тестовом режиме и не для всех коннекторов. После бета-тестирования и как устоится – данный вид стопов будет проброшен по большинству популярных коннекторов в OsEngine.
1) Что такое внешний стоп?
Внутренний стоп-приказ хранится внутри OsEngine: движок сам отслеживает цену последней сделки и отправляет ордер на рынок только после достижения цены активации.
Внешний стоп-приказ сразу отправляется на биржу и хранится там. За ценой активации следит биржа и сама порождает заявку при касании уровня. Основные следствия:
стоп срабатывает, даже когда OsEngine выключен или потерял связь с сервером;
исключается задержка между касанием уровня и выставлением заявки — активацию выполняет биржа;
стоп виден в терминале брокера как обычный стоп-ордер.
2) Как поддержка включается в коннекторе?
Каждый коннектор в OsEngine декларирует свои возможности через интерфейс IServerPermission. За внешние стопы отвечает флаг StopOrdersIsSupport:
public bool StopOrdersIsSupport
{
get { return true; } // коннектор поддерживает внешние стопы
}
Если флаг включён, коннектор маршрутизирует ордера с типом StopLimit и StopMarket не в обычный торговый поток, а в специализированный API стоп-ордеров биржи. Если флаг выключен, все методы выставления внешних стопов автоматически откатываются на внутренние — код робота при этом не меняется.
На уровне ядра признаком внешнего стопа являются два значения перечисления OrderPriceType — StopLimit и StopMarket — в сочетании с полем StopPrice (цена активации) у ордера.
3) Какие коннекторы поддерживают внешние стопы?
Внешние стоп-ордера, на момент выхода поста, поддерживает коннектор Т-Инвестиции. Реализация построена на gRPC-сервисе StopOrdersService Tinkoff Invest API:
PostStopOrder — выставление стоп-ордера (stop-limit и stop-market);CancelStopOrder — отзыв стоп-ордера;
GetStopOrders — запрос активных стопов и истории (используется при восстановлении после переподключения);
OrderStateStream — поток событий: статусы стоп-ордеров и порождённых ими заявок приходят в коннектор в реальном времени.
На остальных коннекторах флаг StopOrdersIsSupport выключен, и внешние стопы прозрачно работают как внутренние. Архитектура открыта: чтобы подключить внешние стопы на новом коннекторе, достаточно включить флаг и обработать типы StopLimit / StopMarket при отправке, отзыве и приёме статусов ордеров.
4) Общая механика работы
Жизненный цикл внешнего стопа:
1) Робот или пользователь выставляет стоп-ордер (из кода — методы *OnServer, из интерфейса — чекбокс «Серверный стоп ордер» в окнах открытия, закрытия и доливки позиции).
2) Ордер уходит на биржу и переходит в статус Active. Он хранится на бирже, OsEngine следит за его статусом через поток событий.
3) Цена касается уровня активации — биржа активирует стоп и порождает дочернюю заявку: Limit для stop-limit, Market для stop-market.
4) Сам стоп-ордер завершает жизнь и приходит в робота со статусом Cancel — это штатное поведение, а не отмена пользователем.
5) Исполнение приходит на дочерней заявке: сделки (MyTrade) привязываются к ней.
Дополнительные свойства механики:
- Цена выставленного стопа не редактируется: изменение уровня выполняется парой операций «отмена + новый ордер».
- После перезапуска или переподключения активные стопы автоматически запрашиваются с сервера и восстанавливаются в журнале.
- Когда позиция полностью закрыта, оставшиеся активные стопы на её закрытие снимаются автоматически.
5) Архитектура внутри позиции
Позиция в OsEngine хранит два списка ордеров: OpenOrders (ордера открытия и доливки) и CloseOrders (ордера закрытия). Внешний стоп — обычный объект Order в одном из этих списков, с характерным набором полей:
- TypeOrder — StopLimit или StopMarket: признак внешнего стопа;
- StopPrice — цена активации (триггер);
- Price — лимитная цена дочерней заявки (только для stop-limit);
- IsStopOrProfit — ордер создан для закрытия по стопу или профиту;
- ChildOrderNumberMarket — биржевой номер дочерней заявки, порождённой при активации;
- ParentOrderNumberMarket — у дочерней заявки: биржевой номер стопа-родителя.
Ордера в списках лежат в хронологическом порядке: сначала стоп, после активации в конец списка дописывается дочерняя заявка. Типичное состояние позиции после срабатывания стопа на вход и выставления стопа на выход:
Типичное состояние позиции после срабатывания стопа на вход и выставления стопа на выход выглядит так. В списке OpenOrders лежат два ордера: первым идёт стоп-лимит на вход в статусе Cancel — биржа активировала его и погасила; следом — исполненная дочерняя лимитная заявка в статусе Done. Дочерняя заявка ссылается на стоп через поле ParentOrderNumberMarket, а стоп — на дочернюю заявку через поле ChildOrderNumberMarket. В списке CloseOrders в этот момент находится один активный стоп-лимит на закрытие позиции.
Связывание выполняет ядро журнала (PositionController): когда от сервера приходит ордер с заполненным ParentOrderNumberMarket, ядро находит стоп-родителя в позициях и прикрепляет дочерний ордер к той же позиции. Поэтому открытие и закрытие позиции внешними стопами корректно отражается в журнале, на графике и в расчёте прибыли без участия робота.
Поля StopPrice, ParentOrderNumberMarket и ChildOrderNumberMarket сериализуются вместе с ордером, поэтому связи «стоп — дочерняя заявка» переживают перезапуск приложения.
6) Программный интерфейс
Все методы относятся к торговой вкладке робота BotTabSimple.
Открытие позиции:
- BuyAtStopOnServer(decimal volume, decimal priceLimit, decimal priceActivation) и SellAtStopOnServer(...) — внешний стоп-лимит;
- BuyAtStopMarketOnServer(decimal volume, decimal priceActivation) и SellAtStopMarketOnServer(...) — внешний стоп-маркет.
Доливка к открытой позиции:
- BuyAtStopOnServerToPosition(Position position, decimal volume, decimal priceLimit, decimal priceActivation) и SellAtStopOnServerToPosition(...) — внешний стоп-лимит;
- BuyAtStopMarketOnServerToPosition(Position position, decimal volume, decimal priceActivation) и SellAtStopMarketOnServerToPosition(...) — внешний стоп-маркет.
Закрытие позиции:
- CloseAtStopOnServer(Position position, decimal priceActivation, decimal priceOrder) — внешний стоп-лимит на закрытие (есть перегрузки с объёмом и именем сигнала);
- CloseAtStopMarketOnServer(Position position, decimal priceActivation) — внешний стоп-маркет на закрытие.
Отмена ордеров:
- CloseAtStopOnServerCancel(Position position) — отзыв внешних стопов конкретной позиции;
- CloseAtStopOnServerCancel() — отзыв внешних стопов всех открытых позиций вкладки;
- CloseOrder(Order order) — отзыв произвольного ордера, включая внешний стоп.
На серверах без поддержки и в тестере все эти методы автоматически откатываются на внутренние стопы, поэтому один и тот же робот работает везде без изменений кода. Проверить поддержку можно свойством вкладки ServerIsSupportStopOrders.
Готовый пример — робот ServerStopOrdersSample (папка Robots/TechSamples): выставляет внешний стоп-лимит на покупку, ожидает активации заданное число свечей, перевыставляет ордер по актуальной цене и после открытия позиции сопровождает её внешним стопом на закрытие.
Удачных алгоритмов!
Комментарии открыты для друзей!

https://smart-lab.ru/company/os_engine/blog/1024149.php
Скачать OsEngine: https://github.com/AlexWan/OsEngine
Официальная поддержка OsEngine в MAX: https://max
Канал Научный трейдинг в MAX: https://max.ru/
Поддержка OsEngine в Телеграм: https://t.me/osengine_official_support
Канал Научный трейдинг (Bad Quant) в Телеграм: https://t.me/bad_quant
Подробнее https://smart-lab.ru/compan...


