API стратегий
Справочник событий и возможностей, доступных коду стратегии.
Жизненный цикл
Стратегия на C# реализует IStrategy. Метод OnInit(StrategyContext) получает сервисы окружения. OnTick обрабатывает тики, а OnBar(ISymbolKey, IKline) — закрытые бары подписки. OnFinish(StrategyFinishContext) вызывается при завершении теста.
Принимайте решения по закрытым барам, если алгоритм не должен читать будущие данные. Расчёт сигналов можно вынести в отдельный исходный файл, чтобы проверять его независимо.
public class UserStrategy : IStrategy
{
public void OnInit(StrategyContext context) { }
public void OnTick(decimal? price = null) { }
public void OnBar(ISymbolKey symbolKey, IKline kline) { }
public void OnFinish(StrategyFinishContext context) { }
}Параметры, рыночные данные и заявки
StrategyContext предоставляет Api, Indicators, Parameters, MarketData и Logger. Атрибуты [Parameter] задают параметры стратегии, например период индикатора или объём заявки. Проверяйте сигнатуры установленного API в редакторе: поддержка зависит от версии EngineHost.
Отправка заявки возвращает результат. Проверяйте Success, прежде чем считать заявку принятой; при ошибке изучайте ErrorCode и ErrorMessage. Явно задавайте предположения о счёте, количестве и инструменте.
События ордеров, сделок, позиции и баланса
Четыре необязательных колбэка сообщают стратегии, что произошло на её счёте. OnOrderUpdate(ISymbolKey, OrderUpdate): ордер принят, исполнен частично или полностью, снят, отклонён, истёк, изменён или сработал его триггер (update.Kind, update.Order, update.Reason, update.PreviousOrderId).
OnExecution(ISymbolKey, IExecution): сделка по ордеру стратегии — цена, количество, комиссия и её актив, maker или taker, реализованный PnL и признак ликвидации. OnPositionChanged(ISymbolKey, IPosition): изменилась фьючерсная позиция; Quantity = 0 — позиция закрыта. OnBalanceChanged(ISymbolKey, IBalance): изменился баланс актива; symbolKey.Symbol пуст, потому что баланс принадлежит счёту рынка.
Реализуйте только нужные; стратегия без них работает как раньше.
События приходят в потоке стратегии и никогда внутри другого колбэка: ордер, исполненный прямо в PlaceOrder из OnBar, придёт событием после выхода из OnBar. События одного момента идут так: принятые ордера, сделки, остальные изменения ордеров, позиции, балансы. Приходят только события ордеров самой стратегии; другой робот или ручной ордер на том же счёте её не касаются.
В симуляторе и в Paper события приходят в модельное время изменения счёта. В LIVE — из приватного вебсокета биржи, если у подключения он есть: HyperLiquid, Aster, Binance, Bybit, OKX, Bitget, Kraken, Kucoin, MEXC, BingX, Gate.io, Coinbase, Bitfinex, Crypto.com, HTX, BloFin, CoinW, DeepCoin, Weex, XT.
У фьючерсов BingX, у Coinbase, BloFin и CoinW он несёт только ордера, а сделки приходят опросом; у Bitstamp и инверсных фьючерсов MEXC и XT его нет. Иначе робот спрашивает биржу по REST, и только пока у стратегии есть открытые ордера, по умолчанию раз в 5 секунд, поэтому событие может прийти с опозданием до одного интервала.
Интервал задаётся полем «Опрос ордеров» в профиле запуска (Studio) или в профиле робота (лаунчер), а из кода — context.SetAccountPollInterval(TimeSpan.FromSeconds(1)), пока ждёте исполнения, и null, чтобы вернуть значение профиля; хост держит его от 1 секунды до 10 минут. Как приходят события, показывает context.Runtime.AccountEventDelivery: Simulated, Stream, Polling или None. Сверка счёта раз в 30 секунд остаётся страховкой.
Эту задержку можно воспроизвести в бэктесте: включите в профиле запуска «Эмулировать опрос ордеров в бэктесте».
Ответ на собственный вызов стратегии (PlaceOrder, CancelOrder, ModifyOrder, ClosePosition) по-прежнему приходит сразу после колбэка, а исполнение, срабатывание или истечение ордера, стоящего в рынке, — на ближайшем опросе: через «Опрос ордеров» после предыдущего, только пока у стратегии есть открытые ордера, и ещё один раз после того, как закрылся последний; context.SetAccountPollInterval сдвигает расписание сразу.
Позиции и балансы приходят ещё и при сверке раз в 30 секунд, поэтому OnPositionChanged может прийти раньше, чем Filled ордера. context.Runtime.AccountEventDelivery тогда — Polling. Шаг бэктеста крупнее интервала, поэтому событие приходит в первой точке выдачи после опроса: при 5 секундах и минутных барах задержки почти не видно, при длинном интервале или в тиковом режиме — видно.
Проверочный бэктест экспортированного бандла в лаунчере делает то же; Paper и LIVE не меняются.
Api.ModifyOrder(symbolKey, orderId, newPrice, newQuantity) меняет стоящий ордер одной командой. newPrice — лимитная цена или цена триггера у ордера без лимитной цены; newQuantity — новое полное количество, больше уже исполненного; null — поле не меняется. Ордер теряет место в очереди, а исполнимая новая цена исполняется сразу как taker.
Берите OrderId из возвращённого ордера: биржа может выдать ордеру новый id, и тогда OnOrderUpdate придёт с Kind = Modified и PreviousOrderId. На бирже, которая не умеет менять ордера, вызов вернёт ApiErrorCode.NotSupported; сам хост ордер не снимает и не ставит заново — сделайте это сами, если нужно. В LIVE новые условия проходят те же риск-лимиты, что и новый ордер.
Изменение работает на HyperLiquid, Aster, Binance (фьючерсы USD-M и COIN-M — лимитные ордера; спот — только уменьшение количества), Bybit, OKX (обычные ордера; у триггерных — только количество), Gate (спот и фьючерсы, обычные ордера), Coinbase, Crypto.com и XT (лимитные ордера), на споте Kraken, Kucoin и Bitstamp (лимитный ордер без исполненной части; биржа заменяет его ордером с новым id), на фьючерсах DeepCoin (лимитные ордера) и BingX (только количество); на остальных — NotSupported.
На инверсных (монетных) контрактах новый размер — целое число контрактов, как и любое количество на них.
В других языках те же события: Python — on_order_update, on_execution, on_position_changed, on_balance_changed и api.modify_order; JavaScript и TypeScript — onOrderUpdate, onExecution, onPositionChanged, onBalanceChanged и api.modifyOrder; Java, Go, C++ и R — те же имена в своих соглашениях. Стратегия на Rust дополнительно возвращает true из account_events(). В Pine таких обработчиков нет.
private StrategyContext _context = null!;
public void OnInit(StrategyContext context) => _context = context;
public void OnOrderUpdate(ISymbolKey symbolKey, OrderUpdate update)
{
if (update.Kind == OrderUpdateKind.Filled && update.Order.ClientOrderId == "entry")
_context.SetAccountPollInterval(null); // вернуть значение профиля
}
public void OnExecution(ISymbolKey symbolKey, IExecution fill) =>
_context.Logger.Info($"{fill.Quantity} @ {fill.Price}, fee {fill.Fee} {fill.FeeAsset}, maker {fill.IsMaker}");
// Ждём исполнения входа: спрашивать биржу чаще (только при опросе: LIVE или бэктест с его эмуляцией).
_context.SetAccountPollInterval(TimeSpan.FromSeconds(1));
// Сдвинуть лимитный ордер; биржа может выдать ему новый id.
var moved = _context.Api.ModifyOrder(symbolKey, orderId, newPrice: 101.5m, newQuantity: null);
if (moved.Success) orderId = moved.Data!.OrderId;
else if (moved.ErrorCode == ApiErrorCode.NotSupported) { /* снять и поставить новый ордер */ }Остановка и продолжение LIVE-робота
Когда трейдер нажимает «Стоп», OnFinish получает Reason = UserStopped и context.StopChoice — выбор из диалога: KeepOrdersAndPosition (оставить ордера и позицию), CancelOrders (снять ордера) или CancelOrdersAndClosePosition (снять и закрыть).
Если context.ExitOrdersAllowed истинно, в OnFinish (до 30 секунд) стратегия может снять свои ордера и отправлять только ордера ReduceOnly — уменьшить или закрыть позицию по-своему; открыть новое нельзя. При «оставить» ExitOrdersAllowed ложно, и любой ордер из OnFinish отклоняется. После OnFinish XTester сам выполняет выбор трейдера.
context.SetStopPolicy(StrategyStopChoice…) в OnInit сообщает, что стратегия рекомендует сделать при остановке. Выбирает всегда трейдер.
Диалог «Стоп» лаунчера предлагает четыре варианта и ни один не выбирает заранее: оставить ордера и позицию, снять ордера, снять ордера и закрыть позицию, удалить робота. Вариант, который рекомендует стратегия (context.SetStopPolicy), подписан «— рекомендует стратегия». «Удалить робота» вторым шагом спрашивает, что сделать с ордерами и позицией, и удаляет робота, только если биржа подтвердила остановку.
Падение робота (в том числе исключение в коде стратегии), потеря связи с XTester, остановка службы и перезагрузка сервера — не остановка: OnFinish не вызывается, ордера и позиция остаются на бирже. При следующем запуске OnInit получает context.Restart: IsContinuation, Position, OrderIds, ClientOrderIds и LastVerifiedUtc — что записано в журнале робота при последней сверке. Что на бирже сейчас, читайте через Api; сверку биржи с журналом XTester делает после OnInit, до первого торгового события.
После падения лаунчер сразу запускает робота снова в том же режиме — LIVE и Paper, без вопросов, не больше 3 раз за 10 минут. Если старту нужен человек (ключи, карантин, приёмка реальной заявки), робот остаётся остановленным. Если он падает снова и снова, перезапуски прекращаются; ордера и позиция при этом не трогаются. О падении сообщают уведомление лаунчера, Telegram и XTester (даже с закрытым видом «Роботы»); кнопка «Журнал» открывает журнал робота в лаунчере.
context.SetRestartMismatchPolicy(StrategyRestartMismatchPolicy…) задаёт строгость при перезапуске, если биржа не совпала с журналом (см. «Перезапуск LIVE-робота» в разделе о лаунчере). Настройка в профиле запуска, если задана, главнее. Неизвестный исход команды всегда решает человек.
В тестере и Paper StopChoice = None, Restart = StrategyRestartInfo.None, окна выхода нет.
Стратегии на других языках получают то же через контекст: context.restart в init и context.finish (причина, выбор трейдера, окно выхода) перед on_finish; рекомендацию и строгость задают set_stop_policy и set_restart_mismatch_policy (в JS/TS, Java, Go, C++ — setStopPolicy и setRestartMismatchPolicy, в R — context$set_stop_policy). В Pine обработчиков нет.
public void OnInit(StrategyContext context)
{
_api = context.Api;
_symbol = context.Parameters.SymbolKey;
context.SetStopPolicy(StrategyStopChoice.CancelOrdersAndClosePosition);
context.SetRestartMismatchPolicy(StrategyRestartMismatchPolicy.WarnAndContinue);
if (context.Restart.IsContinuation)
context.Logger.Info($"Continuing: position {context.Restart.Position}, orders {context.Restart.OrderIds.Count}");
}
public void OnFinish(StrategyFinishContext context)
{
if (context.ExitOrdersAllowed)
_api.CancelAllOrders(_symbol); // your own exit; to close use a ReduceOnly order
}Цена пары: OnTick(symbolKey, price)
Необязательный OnTick(ISymbolKey symbolKey, decimal price) приходит, когда меняется цена одной из пар стратегии, — отдельно для каждой пары, поэтому ветвитесь по полученному ключу. Он приходит в потоке стратегии и никогда внутри другого колбэка; пара, чья цена не изменилась, ничего не получает. Старый OnTick(price) не меняется и приходит как раньше; стратегия без нового метода работает как раньше.
В симуляторе цена — закрытие последней закрытой минуты, известной на Global.Now, поэтому пара получает не больше одного вызова в минуту. В Paper и LIVE цена приходит из публичного потока сделок биржи по мере изменения — на оживлённой паре несколько раз в секунду; где потока сделок нет или 90 секунд не было сделок — закрытие формирующейся минуты, её спрашивают раз в секунду.
Если цена меняется быстрее, чем стратегия успевает, приходит последняя цена этой пары; другие пары не теряются. Живая цена может быть новее последнего закрытого бара, а Global.Now остаётся на этом баре. С общим банком рыночных данных (XTESTER_MARKET_DATA_HUB=1) поток сделок пары читается один раз для всех роботов машины. Пока его получают только стратегии на C#.
public void OnTick(ISymbolKey symbolKey, decimal price)
{
if (symbolKey.Symbol == "ETHUSDT")
_lastEth = price;
}Уточнить Закрытый Интервал
В OnInit получите context.DataRefinement() рядом с context.MarketPhases(). Require(symbol, fromUtc, toUtc, options) сохраняет стек текущего callback во время ожидания работы хоста; OnInit и OnBar повторно не вызываются. UTC-интервал [fromUtc, toUtc) должен быть положительным, выровненным по минутам, уже закрытым по модельному времени и не длиннее 60 минут.
ResolutionSeconds по умолчанию равен 1; допустимы делители 60 от 1 до 30: 1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30. TimeoutSeconds по умолчанию 120, допустимо 1–600. RealOnly запрещает синтетику; AllowDownload=false оставляет только локальные реальные подробные данные. Пример останавливает расчёт, если их нет.
Каждое получение синтетических данных требует явного согласия, включая синтетику из кэша. Одновременные одинаковые запросы могут ожидать одну операцию. Остановить, закрыть вопрос, таймаут, отмена или смена запуска не означают согласие. Разрешение не переносится на будущие интервалы и запуски. Без пользователя/headless расчёт останавливается при отсутствии реальных данных; в Paper и Live интерактивного согласия пока нет.
DataRefinementPath.NearestExtremeFirst — модель по умолчанию: сначала ближайший к Open экстремум High/Low; при равном расстоянии — HighFirst. HighFirst и LowFirst принудительно выбирают первый экстремум. CandleColor идёт через High при Close >= Open (включая doji), иначе через Low. Это детерминированные возможные OHLC-траектории: синтетические цены, не восстановленные реальные сделки или исполнения. Seed записывается, но текущие модели не используют случайность.
DataRefinementResult содержит Candles, NoTrades, IsSynthetic, ProviderId, Model и ModelVersion. Подтверждённые интервалы без торгов остаются пустыми. Пропуск свечи сам по себе не доказывает отсутствие торгов. Для эмуляции нужны реальные канонические m1-свечи каждого родительского интервала; неизвестные пропуски не выдумываются. Ошибка обязательного уточнения останавливает расчёт даже после перехвата DataRefinementException стратегией.
Реализованы локальные проверенные подробные данные, детерминированная эмуляция и нативные/архивные маршруты загрузки. Разрешения зависят от источника: проверяйте history.refinementResolutionSeconds в get_market_data_capabilities. AllowDownload=true не гарантирует доступность истории или полное покрытие; неподдерживаемый маршрут приводит к остановке либо запросу явного согласия на эмуляцию. API доступен в C#; прямых Python/Node/Java bindings нет. Точные сигнатуры: get_strategy_api_doc(type="DataRefinementService", member="Require").
На графике истории раскройте «Локальная детализация», чтобы увидеть покрытие кэша для видимых закрытых минут (не более семи дней). Реальные, синтетические, подтверждённые интервалы без сделок и неизвестные интервалы отмечены отдельно. «Показать детализацию» открывает вкладку только для чтения на том же движке графика; возврат сохраняет основной график. Масштабирование и просмотр не загружают и не эмулируют данные.
// Поле: DataRefinementService? _detail;
// OnInit:
_detail = context.DataRefinement();
// OnBar: модельное время, округлённое вниз до закрытой минуты.
var toUtc = new DateTime(time.Ticks - time.Ticks % TimeSpan.TicksPerMinute, DateTimeKind.Utc);
var detail = _detail!.Require(symbolKey, toUtc.AddMinutes(-1), toUtc,
new DataRefinementOptions { ResolutionSeconds = 5, RealOnly = true, AllowDownload = false });
// Читайте detail.Candles, detail.NoTrades и detail.IsSynthetic.Ориентируйтесь на установленный API
Используйте автодополнение, подсказки при наведении, переход к определению и диагностику компиляции. Проверяйте сигнатуры API установленной версии EngineHost.
Эта справка не обещает наличие в Studio режимов Paper, Live, Research Lab или визуальной отправки заявок из старого настольного приложения. Возможности бэктеста следует проверять с подключённым движком.
Локальные ONNX-модели
Model SDK локально исполняет уже обученную ONNX-модель через host-managed API Models. Он не обучает модели и не вызывает AI-провайдера. Откройте нужный проект стратегии, затем выберите «ONNX-модели» в палитре команд или меню проекта.
«Импорт ONNX» сначала проверяет локальный файл. Просмотрите форму входа и выходы, укажите логический ID модели и UTC-границу данных, использованных при обучении. Необязательные имена признаков должны описывать весь вход в точном порядке; векторы нормализации должны иметь ту же длину. «Импорт .xtmodel» сохраняет запечатанный контракт пакета. Импортируйте только доверенные модели: проверки формата и хэшей не изолируют враждебный нативный граф.
Импорт повторно проверяет хэш исходного файла и точную ревизию проекта. Замена требует подтверждения текущей идентичности модели. Отмена выбора файла или просмотра не меняет проект; после подтверждения импорта дождитесь нативной проверки и сохранения. Удаление отсоединяет модель от проекта и сохраняет исходный файл и неизменяемые файлы пакетов.
Откройте модель один раз в OnInit через Models.Open("your-id"), затем вызывайте Predict с точным порядком признаков или словарём имён. Модель с границей обучения позже первого возможного использования кодом стратегии отклоняется, включая объявленный прогрев. Замена требует новой подготовки расчёта или экспорта bundle и перезапуска worker.
Portable-импорт использует onnx-cpu-v1; старые пакеты windows-ml-onnx читаются без изменения хэшей. Tester и Launcher используют один CPU/sequential контракт с одним потоком. Отсутствующий native runtime или несовместимые тензоры приводят к отказу. Эти настройки не делают любой ONNX-граф детерминированным: повторяйте характерные входные векторы и проверяйте конкретную стратегию в обоих хостах. Автоматической загрузки GPU/NPU providers нет.