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.
Lifecycle correlation
Для lifecycle-коррелированного order.submit в payload передаются стабильные поля:
json
{
"lifecycleKey": "life-001",
"entrySignalId": "entry-001"
}Первоначальный order.submit регистрирует в Backend correlation record. Последующие обновления защиты могут использовать новый delivery signalId, но обязаны сохранять ту же пару lifecycleKey + entrySignalId; при обновлении также передаётся protectionUpdate: true.
После регистрации correlation terminal connector публикует lifecycle-события через SignalR Hub method PublishPositionLifecycle. Поддерживаются position.opened, position.closed, order.cancelled и order.expired. Для entry-команды с dynamic protection, если событие пришло до предварительного order.submit и регистрации ожидаемой корреляции, Backend вернёт success=false, status=NotFound. События position.opened для обычных ордеров без dynamic protection не образуют dynamic-сессию и игнорируются MainApp в контексте dynamic protection.
HTTP-ответ 202 Accepted означает только прием сигнала Public API. ACK lifecycle приходит отдельно от terminal connector и содержит success, status, state и message. При повторной доставке с тем же eventId Backend возвращает success=true, status=Duplicate.
Пример 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.
Противоположные сигналы и итоговая позиция
Public API и обычные сигналы являются независимыми командами. Backend и terminal plugins не выполняют общий автоматический арбитраж противоположных сигналов по одной паре account + instrument. Команды с разными signalId могут быть приняты и отправлены в терминал, даже если их направления конфликтуют.
В одном remote-плагине обычные и Public API-сигналы обрабатываются последовательно, но очередь не превращает несколько команд в атомарную операцию разворота. Ранее выставленный встречный pending-ордер автоматически не отменяется. Ручные заявки, TMProLocal, ATM, другие EA, стратегии и копировщики являются отдельными источниками и с этой очередью не координируются.
Статус execution filled подтверждает исполнение конкретного API-tracked ордера. Он не гарантирует, что текущая позиция счёта сохранилась в направлении этого ордера:
- на NinjaTrader или MT5 netting account встречный объём может уменьшить, закрыть или развернуть общую позицию;
- на MT5 hedging account встречный вход может создать отдельную противоположную позицию вместо закрытия существующей;
- partial fills, разные объёмы, pending-ордера, ручные операции и другие торговые системы могут изменить итоговое состояние.
Exact cancel отменяет только выбранный orderRef. Он не отменяет другие встречные, ручные, ATM/EA или защитные ордера и не закрывает уже открытую позицию. Перенос Stop Loss/Take Profit между пересекающимися противоположными сигналами не является атомарным или гарантированным.
Один управляющий источник
Для одной пары account + instrument рекомендуется использовать либо Public API, либо обычные сигналы, но не оба источника одновременно. Это относится также к TMProLocal, ручным заявкам, ATM, другим стратегиям, EA и копировщикам.
Использование только одного источника уменьшает межисточниковые гонки, но не защищает от двух перекрывающихся противоположных сигналов внутри самого этого источника.
Рекомендуемый порядок безопасного разворота:
- Не отправляйте встречную команду до окончательного terminal-confirmed результата предыдущей.
- Сверьте текущую позицию и все working entry/protection orders.
- Отмените каждый конфликтующий pending-ордер отдельно и дождитесь финального результата отмены.
- Закройте существующую позицию и подтвердите отсутствие позиции в фактическом состоянии терминала.
- Только после этого отправьте новый entry и установите новую защиту.
- После ручного вмешательства приостановите автоматические сигналы и полностью сверяйте состояние перед возобновлением.
Проверяйте этот сценарий, включая partial fills и защитные ордера, на отдельном demo/simulation account. Для MT5 netting и hedging accounts выполняйте отдельные проверки.
Polling до финального результата
Не опрашивайте endpoint в tight loop. Практический интервал — 500–2000 ms, затем backoff.
bash
REQUEST_ID='REPLACE_WITH_REQUEST_ID'
for attempt in $(seq 1 60); do
body=$(
curl --silent --show-error \
"https://tradingmonitor.pro/api/v1/signals/$REQUEST_ID" \
--header 'Authorization: Bearer tmpro_api_REPLACE_ME'
)
echo "$body" | jq .
routing=$(echo "$body" | jq -r '.terminalRouting.status')
execution_count=$(echo "$body" | jq '.executions | length')
pending_count=$(
echo "$body" |
jq '[.executions[] | select(
.status != "filled" and
.status != "rejected" and
.status != "cancelled" and
.status != "not_executed" and
.status != "terminal_completed" and
.status != "not_confirmed"
)] | length'
)
if [ "$routing" != 'dispatched' ] ||
{ [ "$execution_count" -gt 0 ] && [ "$pending_count" -eq 0 ]; }; then
break
fi
sleep 1
donecsharp
using System.Linq;
using System.Net.Http.Headers;
using System.Text.Json;
var finalStatuses = new HashSet<string>(StringComparer.Ordinal)
{
"filled",
"rejected",
"cancelled",
"not_executed",
"terminal_completed",
"not_confirmed"
};
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");
for (var attempt = 0; attempt < 60; attempt++)
{
using var response = await http.GetAsync($"/api/v1/signals/{requestId}");
response.EnsureSuccessStatusCode();
var text = await response.Content.ReadAsStringAsync();
using var json = JsonDocument.Parse(text);
var root = json.RootElement;
var routing = root.GetProperty("terminalRouting").GetProperty("status").GetString();
var executions = root.GetProperty("executions").EnumerateArray().ToArray();
Console.WriteLine(text);
if (routing != "dispatched" ||
(executions.Length > 0 &&
executions.All(item =>
finalStatuses.Contains(item.GetProperty("status").GetString() ?? ""))))
{
break;
}
await Task.Delay(TimeSpan.FromSeconds(1));
}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()
routing = body["terminalRouting"]["status"]
executions = body.get("executions", [])
print(body)
if routing != "dispatched" or (
executions and all(item["status"] in FINAL for item in executions)
):
break
time.sleep(1)terminalRouting.status=no_compatible_terminal является завершённым routing result: terminal execution не будет создан. not_applicable означает, что signal не содержал route в NinjaTrader/MT5, например был направлен только в messenger.
Каталог команд
Backend проверяет общий envelope, а фактические type, cmd и поля payload проверяет целевой terminal plugin. Поэтому HTTP 202 Accepted возможен и для команды, которую terminal затем завершит статусом rejected или not_executed.
Полный каталог всех поддерживаемых команд, матрица NinjaTrader 8/MetaTrader 5, обязательные поля и примеры curl, C# и Python для каждого варианта находятся на странице Справочник команд.
Кратко:
| Группа | Команды |
|---|---|
system | pause, resume, info |
order | submit с market, limit, stop; NT8 также stop_limit |
position | close, reduce, protect, close_all, flatten_now |
risk | block_instrument, allow_instrument, flatten_now |
Ошибки 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 |