Торговые сигналыКо всем статьям

Основы

Публичный API: ключи, запросы, примеры

REST-доступ к своим сигналам, стратегиям и статистике по личному ключу.

Ключ

Ключ выдаётся в кабинете, в разделе «Интеграции». Он показывается один раз: в базе лежит только его хеш, и повторить показ невозможно даже администратору. Потеряли — отзовите и выпустите новый.

Ключ передаётся заголовком. В строке запроса он не принимается намеренно: адрес попадает в журналы прокси, в историю браузера и в чужие Referer.

curl -s https://123-opt.com/api/v1/me \
  -H "Authorization: Bearer sk_live_ВАШ_КЛЮЧ"

У каждого ключа свой потолок запросов в час. При его превышении приходит ответ 429 с заголовком Retry-After.

Что можно получить

# Свои сигналы: последние 50, можно сузить по инструменту и дате
curl -s "https://123-opt.com/api/v1/signals?limit=50&symbol=EURUSD" \
  -H "Authorization: Bearer $KEY"

# Только новые с прошлого раза
curl -s "https://123-opt.com/api/v1/signals?since=2026-09-01T00:00:00Z" \
  -H "Authorization: Bearer $KEY"

# Свои стратегии
curl -s https://123-opt.com/api/v1/strategies -H "Authorization: Bearer $KEY"

# Статистика за период
curl -s "https://123-opt.com/api/v1/stats?days=90" -H "Authorization: Bearer $KEY"

API отдаёт только ваши данные. Маршрута, показывающего чужое, здесь нет.

Что в сигнале

{
  "id": "…",
  "symbol": "EURUSD",
  "direction": "up",
  "timeframe": "1h",
  "horizonMinutes": 60,
  "score": 5.5,
  "generatedAt": "2026-09-10T12:00:03.120Z",
  "barOpenAt": "2026-09-10T11:00:00.000Z",
  "outcome": { "movePct": 0.42, "correct": true, "resolvedAt": "…" }
}

Цены входа в сигнале нет, и это не упущение: сигнал утверждает направление на горизонте, а цена зависит от того, когда вы нажмёте кнопку у брокера. outcome пуст, пока горизонт не вышел.

Webhook

Вместо опроса можно получать POST при каждом новом сигнале. Адрес задаётся в том же разделе кабинета. Требования к адресу: только https, порт 443, без логина и пароля в адресе, и адрес обязан быть публичным — внутренние сети отклоняются.

Тело запроса:

{
  "event": "signal.created",
  "signal": { "id": "…", "symbol": "EURUSD", "direction": "up" }
}

Заголовки:

Проверка подписи

Подписывается строка {момент}.{тело} вашим секретом. Момент входит в подпись намеренно: без него перехваченный запрос можно повторять сколько угодно.

import { createHmac, timingSafeEqual } from 'node:crypto'

function verify(secret, rawBody, headers) {
  const timestamp = Number(headers['x-signal-timestamp'])
  // Старше пяти минут — отвергаем: это защита от повтора.
  if (Math.abs(Date.now() / 1000 - timestamp) > 300) return false

  const expected = createHmac('sha256', secret)
    .update(timestamp + '.' + rawBody)
    .digest('hex')
  const got = headers['x-signal-signature']
  if (expected.length !== got.length) return false
  return timingSafeEqual(Buffer.from(expected), Buffer.from(got))
}
import hmac, hashlib, time

def verify(secret: str, raw_body: bytes, ts: str, sig: str) -> bool:
    if abs(time.time() - int(ts)) > 300:
        return False
    expected = hmac.new(
        secret.encode(), f"{ts}.".encode() + raw_body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, sig)

Подпись считается по сырому телу, до разбора JSON: пересобранный из объекта текст отличается пробелами и порядком полей, и подпись не сойдётся.

Повторы

Ответ 2xx означает «принято». Всё остальное — повтор: через 1, 5, 25, 120 и 360 минут. После пятой неудачи доставка помечается неуспешной, и её видно в журнале вызовов в кабинете.

Если адрес не отвечает подряд много раз, подписка выключается — долбиться в мёртвый адрес бессмысленно. Включается обратно там же, в кабинете.

Отвечайте быстро. Сервис ждёт ответа десять секунд; долгая обработка на вашей стороне должна идти после ответа, а не до него.

Обновлено: 2026-09-10