Appearance
Сигналы и статус исполнения
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 сигнала
| Поле | Тип | Обязательное | Ограничения и смысл |
|---|---|---|---|
v | integer | да | только 1 |
signalId | string | да | уникальный в аккаунте, 1–128 символов |
ts | integer | да | Unix milliseconds; не позже допустимого clock skew |
ttlMs | integer | да | 1..300000; ts + ttlMs должен быть в будущем |
platform | string | да | один или несколько comma-separated tokens |
instanceId | string | нет | точный terminal instance, 1–128 символов |
type | string | да | непустая строка до 64 символов |
cmd | string | да | непустая строка до 64 символов |
payload | object | да | параметры, которые проверяет целевой terminal/integration |
Поддерживаемые platform tokens:
| Token | Назначение |
|---|---|
ninjatrader | NinjaTrader tracked terminal |
mt5 | MetaTrader tracked terminal |
any | NinjaTrader и MetaTrader |
telegram | Telegram notification |
discord | Discord notification |
n8n | n8n 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: falseaccepted означает, что 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 | Значение |
|---|---|
processing | backend ещё не зафиксировал outcome |
accepted | backend принял запрос |
unknown | backend не смог надёжно подтвердить outcome; автоматически не повторять |
terminalRouting.status
| Status | Значение |
|---|---|
not_applicable | signal не содержит terminal route, например только messenger route |
no_compatible_terminal | нет активного terminal с необходимыми capabilities и подходящим instance |
dispatched | tracked 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
| Поле | Описание |
|---|---|
executionId | backend correlation конкретного terminal execution |
orderRef | opaque reference entry order; используется для exact cancel |
platform | ninjatrader или mt5 |
instanceId | terminal instance, которому отправлена команда |
filledQuantity | подтверждённое terminal количество исполнения |
averageFillPrice | подтверждённая средняя цена |
reasonCode | стабильный machine-readable reason, если доступен |
cancel | status последнего 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_limit | limit price |
stopPrice | для stop, stop_limit | stop 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
| type | cmd | payload |
|---|---|---|
system | pause | {} |
system | resume | {} |
pause блокирует новые live commands внутри данного indicator instance; resume снимает блокировку.
Position commands
| cmd | Требуемые payload fields | Действие |
|---|---|---|
close | instrument | закрыть открытую позицию инструмента |
reduce | instrument, quantity > 0 | уменьшить позицию |
protect | instrument и stopLoss и/или takeProfit | выставить/заменить protective orders |
close_all | нет | закрыть все позиции account |
flatten_now | instrument | отменить 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_instrument | instrument | локально запретить новые live commands по инструменту |
allow_instrument | instrument | снять локальный запрет |
flatten_now | нет | отменить working orders и закрыть позиции account |
Команды flatten
position.flatten_now действует на один инструмент. risk.flatten_now действует на account целиком. Проверяйте эту разницу на demo account.
Ошибки signal endpoints
Полный справочник находится на странице Ошибки, retries и лимиты.
Частые ответы:
| HTTP | Code |
|---|---|
400 | invalid_idempotency_key, malformed_json |
401 | invalid_api_key |
403 | insufficient_scope, account_frozen |
409 | idempotency_conflict, request_in_progress, idempotency_outcome_unknown, duplicate_signal_id |
413 | request_body_too_large |
415 | unsupported_media_type |
422 | invalid_signal |
429 | api_rate_limited, signal_rate_limit_exceeded, daily_signal_limit_exceeded |
500 | signal_processing_failed |