Skip to content

Сигналы и статус исполнения

POST /api/v1/signals

Отправляет одну торговую или управляющую команду. Требует scope signals:write.

Обязательные заголовки

http
Authorization: Bearer tmpro_api_<secret>
Content-Type: application/json
Idempotency-Key: <unique-value>

Idempotency-Key должен иметь длину 1–128 символов и не содержать управляющих символов. Для новой логической операции используйте новый UUID.

Envelope сигнала

ПолеТипОбязательноеОграничения и смысл
vintegerдатолько 1
signalIdstringдауникальный в аккаунте, 1–128 символов
tsintegerдаUnix milliseconds; не позже допустимого clock skew
ttlMsintegerда1..300000; ts + ttlMs должен быть в будущем
platformstringдаодин или несколько comma-separated tokens
instanceIdstringнетточный terminal instance, 1–128 символов
typestringданепустая строка до 64 символов
cmdstringданепустая строка до 64 символов
payloadobjectдапараметры, которые проверяет целевой terminal/integration

Поддерживаемые platform tokens:

TokenНазначение
ninjatraderNinjaTrader tracked terminal
mt5MetaTrader tracked terminal
anyNinjaTrader и MetaTrader
telegramTelegram notification
discordDiscord notification
n8nn8n webhook

Tokens можно объединять, например "ninjatrader,telegram".

Если instanceId отсутствует, backend направляет tracked-команду всем совместимым активным terminals выбранного типа. Если instanceId указан, совпадение точное и чувствительное к регистру. Чтобы адресовать несколько компьютеров независимо, задайте уникальный InstanceId в каждом индикаторе.

Пустой instanceId

В signal body нельзя передавать "instanceId": "": validator отклонит его. Для маршрутизации без точного instance просто не добавляйте поле instanceId.

Пример market order

bash
export TMPRO_API_KEY='tmpro_api_REPLACE_ME'
TS=$(date +%s%3N)
IDEMPOTENCY_KEY=$(uuidgen)

curl --request POST 'https://tradingmonitor.pro/api/v1/signals' \
  --header "Authorization: Bearer $TMPRO_API_KEY" \
  --header 'Content-Type: application/json' \
  --header "Idempotency-Key: $IDEMPOTENCY_KEY" \
  --data "{
    \"v\": 1,
    \"signalId\": \"entry-$IDEMPOTENCY_KEY\",
    \"ts\": $TS,
    \"ttlMs\": 15000,
    \"platform\": \"ninjatrader\",
    \"instanceId\": \"VPS-NT8-01\",
    \"type\": \"order\",
    \"cmd\": \"submit\",
    \"payload\": {
      \"instrument\": \"ES 09-26\",
      \"side\": \"buy\",
      \"executionType\": \"market\",
      \"quantity\": 1,
      \"stopLoss\": 6280.25,
      \"takeProfit\": 6310.25
    }
  }"
csharp
using System.Net.Http.Headers;
using System.Net.Http.Json;

using var http = new HttpClient
{
    BaseAddress = new Uri("https://tradingmonitor.pro")
};
http.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", "tmpro_api_REPLACE_ME");

var signal = new
{
    v = 1,
    signalId = $"entry-{Guid.NewGuid():N}",
    ts = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
    ttlMs = 15_000,
    platform = "ninjatrader",
    instanceId = "VPS-NT8-01",
    type = "order",
    cmd = "submit",
    payload = new
    {
        instrument = "ES 09-26",
        side = "buy",
        executionType = "market",
        quantity = 1,
        stopLoss = 6280.25,
        takeProfit = 6310.25
    }
};

using var request = new HttpRequestMessage(HttpMethod.Post, "/api/v1/signals")
{
    Content = JsonContent.Create(signal)
};
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());

using var response = await http.SendAsync(request);
var body = await response.Content.ReadAsStringAsync();
Console.WriteLine($"{(int)response.StatusCode} {body}");
Console.WriteLine($"Location: {response.Headers.Location}");
Console.WriteLine(
    $"Replayed: {response.Headers.TryGetValues("Idempotency-Replayed", out var values) && values.Single() == "true"}");
python
import time
import uuid
import requests

api_key = "tmpro_api_REPLACE_ME"
signal = {
    "v": 1,
    "signalId": f"entry-{uuid.uuid4().hex}",
    "ts": int(time.time() * 1000),
    "ttlMs": 15_000,
    "platform": "ninjatrader",
    "instanceId": "VPS-NT8-01",
    "type": "order",
    "cmd": "submit",
    "payload": {
        "instrument": "ES 09-26",
        "side": "buy",
        "executionType": "market",
        "quantity": 1,
        "stopLoss": 6280.25,
        "takeProfit": 6310.25,
    },
}

response = requests.post(
    "https://tradingmonitor.pro/api/v1/signals",
    headers={
        "Authorization": f"Bearer {api_key}",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json=signal,
    timeout=30,
)
print(response.status_code, response.text)
print("Location:", response.headers.get("Location"))
print("Replayed:", response.headers.get("Idempotency-Replayed"))

Успешный ответ 202 Accepted:

json
{
  "requestId": "req_or_32_hex_characters",
  "signalId": "entry-0fc57b2cd72c4791a55c623c523c88c2",
  "status": "accepted",
  "acceptedAt": "2026-07-16T12:00:00Z"
}

Также возвращаются:

http
Location: /api/v1/signals/<requestId>
Idempotency-Replayed: false

accepted означает, что backend выполнил account/quota checks, создал запись запроса и передал сигнал в доступный pipeline. Проверяйте terminalRouting и executions через status endpoint.

Безопасный retry

При network timeout отправьте byte-identical JSON с тем же Idempotency-Key.

  • тот же key + то же тело: повторный 202, тот же requestId, Idempotency-Replayed: true;
  • тот же key + другое тело: 409 idempotency_conflict;
  • исходная операция ещё обрабатывается: 409 request_in_progress, обычно Retry-After: 2;
  • outcome исходной операции неизвестен: 409 idempotency_outcome_unknown;
  • новый idempotency key с уже использованным signalId: 409 duplicate_signal_id.

При idempotency_outcome_unknown не создавайте новый key для той же торговой команды. Сначала вручную проверьте terminal и broker account.

GET /api/v1/signals/{requestId}

Возвращает backend status, routing и executions. Требует scope signals:read.

bash
curl 'https://tradingmonitor.pro/api/v1/signals/REPLACE_WITH_REQUEST_ID' \
  --header 'Authorization: Bearer tmpro_api_REPLACE_ME'
csharp
using System.Net.Http.Headers;

var requestId = "REPLACE_WITH_REQUEST_ID";
using var http = new HttpClient
{
    BaseAddress = new Uri("https://tradingmonitor.pro")
};
http.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", "tmpro_api_REPLACE_ME");

using var response = await http.GetAsync($"/api/v1/signals/{requestId}");
Console.WriteLine($"{(int)response.StatusCode} {await response.Content.ReadAsStringAsync()}");
python
import requests

request_id = "REPLACE_WITH_REQUEST_ID"
response = requests.get(
    f"https://tradingmonitor.pro/api/v1/signals/{request_id}",
    headers={"Authorization": "Bearer tmpro_api_REPLACE_ME"},
    timeout=30,
)
print(response.status_code, response.text)

Пример ответа:

json
{
  "requestId": "3b080d01b59448d59c3e1d9b93725378",
  "signalId": "entry-f06d4948101a42df9a6e0555ad691810",
  "status": "accepted",
  "createdAt": "2026-07-16T06:39:57.0420248Z",
  "completedAt": "2026-07-16T06:39:57.0507129Z",
  "terminalRouting": {
    "status": "dispatched",
    "targetCount": 1
  },
  "executions": [
    {
      "executionId": "exec_3ef5c67ee80d4638b5139b83b331d5a3",
      "orderRef": "ord_c05a69ac94f948c0997c661d83d2a2b1",
      "platform": "ninjatrader",
      "instanceId": "VPS-NT8-01",
      "status": "broker_submitted",
      "filledQuantity": null,
      "averageFillPrice": null,
      "reasonCode": null,
      "createdAt": "2026-07-16T06:39:57.0465139Z",
      "updatedAt": "2026-07-16T06:39:57.3957111Z",
      "completedAt": null,
      "cancel": null
    }
  ]
}

Top-level status

StatusЗначение
processingbackend ещё не зафиксировал outcome
acceptedbackend принял запрос
unknownbackend не смог надёжно подтвердить outcome; автоматически не повторять

terminalRouting.status

StatusЗначение
not_applicablesignal не содержит terminal route, например только messenger route
no_compatible_terminalнет активного terminal с необходимыми capabilities и подходящим instance
dispatchedtracked envelope отправлен одному или нескольким terminals

targetCount — число созданных terminal executions.

executions[].status

Обычная последовательность entry order:

text
dispatched
  -> terminal_accepted
  -> broker_submitted
  -> partially_filled
  -> filled | rejected | cancelled

Все возможные execution statuses:

StatusФинальныйЗначение
dispatchedнетbackend отправил tracked command
terminal_acceptedнетterminal принял команду в обработку
broker_submittedнетордер отправлен broker/adapter
partially_filledнетчастичное исполнение
filledдаордер исполнен
rejectedдаterminal или broker отклонил команду
cancelledдаtracked order отменён
not_executedдакоманда не была выполнена
terminal_completedдаnon-order команда подтверждена terminal state updates
not_confirmedусловно финальныйподтверждение не получено до timeout

После reconnect terminal может восстановить correlation и заменить not_confirmed на достоверный broker_submitted, partially_filled или финальный status.

Значение полей execution

ПолеОписание
executionIdbackend correlation конкретного terminal execution
orderRefopaque reference entry order; используется для exact cancel
platformninjatrader или mt5
instanceIdterminal instance, которому отправлена команда
filledQuantityподтверждённое terminal количество исполнения
averageFillPriceподтверждённая средняя цена
reasonCodeстабильный machine-readable reason, если доступен
cancelstatus последнего cancel request для этого execution

orderRef создаётся только для type=order, cmd=submit. Не пытайтесь заменить его NinjaTrader OrderId.

Polling до финального результата

Не опрашивайте endpoint в tight loop. Практический интервал — 500–2000 ms, затем backoff.

python
import time
import requests

FINAL = {
    "filled",
    "rejected",
    "cancelled",
    "not_executed",
    "terminal_completed",
    "not_confirmed",
}

url = "https://tradingmonitor.pro/api/v1/signals/REPLACE_WITH_REQUEST_ID"
headers = {"Authorization": "Bearer tmpro_api_REPLACE_ME"}

for _ in range(60):
    response = requests.get(url, headers=headers, timeout=30)
    response.raise_for_status()
    body = response.json()
    executions = body.get("executions", [])
    if executions and all(item["status"] in FINAL for item in executions):
        print(body)
        break
    time.sleep(1)

Команды текущего NinjaTrader indicator

Backend проверяет envelope, но terminal проверяет фактический type, cmd и payload. Следующий каталог относится к текущему индикатору TMPro для NinjaTrader 8.

order.submit

Поле payloadОбязательноеОписание
instrumentдаточное имя инструмента NinjaTrader
sideдаbuy или sell
executionTypeдаmarket, limit, stop, stop_limit
quantityдачисло больше нуля; применяется QuantityMultiplier индикатора
limitPriceдля limit, stop_limitlimit price
stopPriceдля stop, stop_limitstop trigger
stopLossнетабсолютная цена protective stop
takeProfitнетабсолютная цена protective target

Limit order:

json
{
  "type": "order",
  "cmd": "submit",
  "payload": {
    "instrument": "NQ 09-26",
    "side": "buy",
    "executionType": "limit",
    "quantity": 1,
    "limitPrice": 23100.25
  }
}

Stop-limit order:

json
{
  "type": "order",
  "cmd": "submit",
  "payload": {
    "instrument": "NQ 09-26",
    "side": "sell",
    "executionType": "stop_limit",
    "quantity": 1,
    "stopPrice": 22990.00,
    "limitPrice": 22988.00
  }
}

System commands

typecmdpayload
systempause{}
systemresume{}

pause блокирует новые live commands внутри данного indicator instance; resume снимает блокировку.

Position commands

cmdТребуемые payload fieldsДействие
closeinstrumentзакрыть открытую позицию инструмента
reduceinstrument, quantity > 0уменьшить позицию
protectinstrument и stopLoss и/или takeProfitвыставить/заменить protective orders
close_allнетзакрыть все позиции account
flatten_nowinstrumentотменить working orders инструмента и закрыть его позицию

Пример reduce:

json
{
  "type": "position",
  "cmd": "reduce",
  "payload": {
    "instrument": "ES 09-26",
    "quantity": 1
  }
}

Пример protect:

json
{
  "type": "position",
  "cmd": "protect",
  "payload": {
    "instrument": "ES 09-26",
    "stopLoss": 6280.25,
    "takeProfit": 6310.25
  }
}

Risk commands

cmdТребуемые payload fieldsДействие
block_instrumentinstrumentлокально запретить новые live commands по инструменту
allow_instrumentinstrumentснять локальный запрет
flatten_nowнетотменить working orders и закрыть позиции account

Команды flatten

position.flatten_now действует на один инструмент. risk.flatten_now действует на account целиком. Проверяйте эту разницу на demo account.

Ошибки signal endpoints

Полный справочник находится на странице Ошибки, retries и лимиты.

Частые ответы:

HTTPCode
400invalid_idempotency_key, malformed_json
401invalid_api_key
403insufficient_scope, account_frozen
409idempotency_conflict, request_in_progress, idempotency_outcome_unknown, duplicate_signal_id
413request_body_too_large
415unsupported_media_type
422invalid_signal
429api_rate_limited, signal_rate_limit_exceeded, daily_signal_limit_exceeded
500signal_processing_failed

TradingMonitor.Pro documentation.