larp-quant-agent/docs/architecture.md
zefiroff f3b533b032
All checks were successful
deploy / deploy (push) Successful in 7s
Quant Agent — multi-agent quant terminal on Hyperliquid
Live HL data → in-browser TA → 4-agent committee → LONG/SHORT/SKIP verdict.
Terminal grid command bar + interactive agent-workflow graph. Paper-trading
worker reuses the browser brain (dry-run + Telegram). Forgejo static deploy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 19:00:13 +04:00

21 KiB
Raw Blame History

$QUANT — техническая документация (как это реализовано)

Описание реальной реализации системы: стек, поток данных, модули, алгоритмы, формулы. Документ описывает то, что есть в коде. Термины называются как есть (rule-based, детерминированно, paper-trading) — на этой фактуре можно строить тексты без додумывания.


1. Обзор

$QUANT — мульти-агентная система анализа бессрочных фьючерсов (перпов) на бирже Hyperliquid. Поток обработки:

рыночные данные (Hyperliquid API)
        │
        ▼
теханализ (RSI, MACD, EMA, ATR, Bollinger)
        │
        ▼
4 стратегии-агента голосуют  LONG / SHORT / SKIP
        │
        ▼
агрегация → вердикт (направление + уверенность + размер + TP/SL)
        │
        ├──► веб-терминал (живая отрисовка, опрос раз в ~12 c)
        └──► воркер: paper-trading на живых ценах + Telegram-уведомления

Стек. Фронтенд: React 19, Vite 8, TypeScript, Tailwind v4, Recharts, React Router 7, anime.js, Lenis. Воркер: Node.js + tsx (TypeScript-рантайм). Менеджер пакетов: pnpm.

Ключевое свойство — детерминированность. Одно и то же состояние рынка даёт один и тот же вердикт. В торговом контуре нет генератора случайных чисел и нет LLM/ML: решение рассчитывается формулами по индикаторам.


2. Слой L1 — данные (src/lib/hyperliquid.ts)

Источник — публичный info-API Hyperliquid: POST https://api.hyperliquid.xyz/info, без авторизации, CORS открыт (можно дёргать из браузера). Все ответы — JSON.

Три запроса:

Функция Тело запроса Что возвращает
getMarketStats(coins) { "type": "metaAndAssetCtxs" } mark/mid цена, ставка фандинга, OI, объём по каждой монете
getCandles(coin, interval, lookback) { "type": "candleSnapshot", "req": { coin, interval, startTime, endTime } } массив свечей OHLCV
getAllMids() { "type": "allMids" } средние цены по всем тикерам

Что извлекается в MarketStat: mark (маркировочная цена), mid, prevDayPx → из неё changePct (изменение за 24 ч), funding (часовая ставка фандинга), openInterest (в базовом активе) и oiUsd = openInterest × mark, dayVolumeUsd.

Свечи (Candle = { t, o, h, l, c, v }): по умолчанию интервал 1h, глубина lookback = 120 свечей. Поддерживаемые интервалы: 1m / 5m / 15m / 1h / 4h / 1d.

Торгуемая вселенная по умолчанию: BTC, ETH, SOL (константа SYMBOLS). Веб-терминал дополнительно показывает вкладки BNB, AVAX.

Сетевой слой: таймаут 12 c через AbortController, при ошибке — throw, который выше по стеку обрабатывается (показывается последнее удачное значение / seed).


3. Слой L2 — технический анализ (src/lib/ta.ts)

Считается из реальных свечей. Реализованные индикаторы:

Индикатор Параметры Реализация
EMA периоды 9, 21, 50 экспоненциальное сглаживание, k = 2/(period+1)
RSI период 14 по Уайлдеру (сглаженные средние gain/loss)
MACD 12 / 26 / 9 гистограмма = MACD-линия сигнальная
ATR период 14 EMA от True Range (волатильность)
Bollinger %B 20, 2σ позиция цены в полосах: 0 = низ, 1 = верх
Funding-Z нормировка ставки фандинга: clamp(funding / 0.00005, 4, 4) (иллюстративная шкала, не статистический z-score по истории)

Функция summarizeTA(candles, funding) сворачивает серию в снимок TASnapshot:

{ price, rsi, ema9, ema21, ema50,
  emaDiffPct,   // (ema9  ema21) / price × 100 — наклон тренда
  macdHist, atr, atrPct,
  pctB,         // Bollinger %B
  changePct,    // изменение от первой свечи окна
  fundingZ, closes }

Этот снимок — единственный вход для слоя сигналов. Один и тот же TASnapshot всегда даёт один и тот же набор голосов.


4. Слой L3 — сигнальные агенты (src/lib/signals.ts)

Четыре независимых rule-based стратегии. Каждая возвращает голос Vote = { agent, layer, direction, score, reason }, где direction ∈ {LONG, SHORT, SKIP}, score ∈ 0..1 — «уверенность» (конвикция), reason — человекочитаемое объяснение.

TrendAgent — следование за трендом

  • LONG, если ema9 > ema21 и rsi > 50; SHORT, если ema9 < ema21 и rsi < 50; иначе SKIP.
  • score = clamp(|emaDiffPct| × 14 + |rsi 50| / 60, 0, 1).
  • Пример reason: «EMA9 above EMA21, RSI 56».

BreakoutAgent — пробой полос Боллинджера

  • LONG, если %B > 0.92; SHORT, если %B < 0.08; иначе SKIP.
  • score = clamp(|%B 0.5| × 1.7, 0, 1).

MeanReversionAgent — возврат к среднему (фейд крайностей)

  • LONG, если rsi < 32 и %B < 0.15; SHORT, если rsi > 68 и %B > 0.85; иначе SKIP.
  • score растёт по мере удаления RSI от порога.

FundingAgent — фейд перекошенного фандинга

  • SHORT, если funding-Z > 1.5 (лонги перегреты); LONG, если funding-Z < 1.5; иначе SKIP.
  • score = clamp(|funding-Z| / 3, 0, 1).

Для голосов SKIP score дополнительно занижается (×0.30.4) — пропуск тоже взвешивается.


5. Слой L4 — агрегация и вердикт (src/lib/signals.ts, aggregate())

Голоса сводятся в итог Verdict:

{ decision,     // LONG / SHORT / SKIP
  confidence,   // 0..1
  sizePct,      // рекомендуемый размер позиции, %
  tpPct, slPct, // take-profit / stop-loss, %
  reasoning,    // текстовое объяснение
  net }         // чистый счёт голосов

Алгоритм:

  1. net = Σ (±1 × score) по всем не-SKIP голосам (LONG = +, SHORT = ).
  2. agreement = доля голосов, совпавших со знаком net.
  3. Порог thr = 0.5: net > 0.5 → LONG, net < 0.5 → SHORT, иначе SKIP.
  4. Уверенность:
    • для сделки: confidence = clamp(0.45 + |net|×0.18 + agreement×0.18, 0, 0.97);
    • для SKIP: clamp(0.3 + |net|×0.2, 0.18, 0.5).
  5. Параметры риска (масштабируются от волатильности vol = clamp(atrPct, 0.4, 6)):
    • tpPct = clamp(vol × 1.6, 0.8, 6);
    • slPct = clamp(vol × 0.9, 0.6, 3.5);
    • sizePct = clamp(confidence × 3.2, 0.5, 3.2).
  6. reasoning собирается из числа согласных агентов и причины лидирующего голоса, например: «1/1 agents align LONG. EMA9 above EMA21, RSI 56. RR 1.9» (RR = TP/SL).

Примечание по терминологии: в UI этот слой назван «LLMScreener / ConfidenceGate», но в текущей реализации это детерминированная агрегация формулами, а не вызов LLM. Порог уверенности (роль «ConfidenceGate») в воркере вынесен в confFloor (см. §6).


6. Слой L5 — paper-trading движок (worker/paper.ts)

Превращает вердикт в бумажную позицию и ведёт её по реальной цене Hyperliquid. Режим жёстко dry-run: ордера на биржу не подписываются и не отправляются — это симуляция исполнения на живых рыночных данных, считающая реальный PnL.

Открытие — maybeOpen(coin, price, verdict):

  • пропуск, если decision = SKIP, или confidence < confFloor (по умолчанию 0.55), или по монете уже есть открытая позиция (maxOpenPerCoin = 1);
  • entry = price (текущая mark-цена);
  • tp = entry × (1 ± tpPct/100), sl = entry × (1 ∓ slPct/100) (знак по направлению);
  • notional = equity × sizePct/100 × leverage (по умолчанию leverage = 5);
  • сохраняется Position { id, coin, dir, entry, notional, tp, sl, tpPct, slPct, confidence, reason, openedAt }.

Ведение — markToMarket(coin, price): на каждом тике, если цена пересекла TP или SL, позиция закрывается по уровню (исполнение по TP/SL предполагается точным).

Закрытие — расчёт PnL:

move   = (exit  entry) / entry
pnlPct = (dir = LONG ? move : move) × 100
pnlUsd = notional × pnlPct / 100
equity = equity + pnlUsd            // капитал компаундируется

Закрытая сделка пишется в Trade (добавляются exit, closedAt, pnlPct, pnlUsd, outcome ('tp'|'sl'), holdMin).

Статистика — stats() (источник команды /pnl): число сделок, открытые позиции, винрейт, суммарный PnL ($), ROI (%), profit factor, средний выигрыш/проигрыш, лучшая и худшая сделка, текущий капитал.


7. Слой L6 — Telegram (worker/telegram.ts, worker/loop.ts)

Реализован на «голом» Bot API (без библиотек), через fetch.

Исходящие — tgSend(): POST .../sendMessage с parse_mode: HTML. Сообщения шлются при открытии и закрытии позиции. Если токен/чат не заданы — текст печатается в stdout (воркер работает и без Telegram).

Пример сообщения об открытии (реальный формат):

🟢 LONG BTC-PERP @ $62,135
size 2.6% · ×5 → $1,300 notional
TP +1.30% ($62,943) · SL -0.70% ($61,700)
confidence 81% · 1/1 agents align LONG. EMA9 above EMA21, RSI 56. RR 1.9.

Пример закрытия:

✅ TP BTC-PERP LONG
$62,135 → $62,943  (+1.30%, +$16.90)
held 2.4h · equity $10,017

Входящие — tgPoll(): long-polling getUpdates (offset-based), раз в ~3 c. Поддерживаемые команды: /status (капитал, открытые позиции, винрейт), /positions (детально по открытым), /pnl (он же /learn — сводная статистика по закрытым сделкам), /help, /start.

Терминология: команда /learn в текущей реализации возвращает статистическую сводку по закрытым сделкам, а не дообучение модели.


8. Воркер: рантайм (worker/loop.ts)

Постоянный фоновый цикл — работает независимо от того, открыт сайт или нет.

Тик (tick()) — по умолчанию раз в pollMs = 60 000 мс:

  1. один запрос getMarketStats(coins) на все монеты;
  2. по каждой монете (tickCoin): getCandlessummarizeTAscreen (голоса + вердикт) → price = mark || ta.pricemarkToMarket (закрыть, если задело TP/SL, + уведомление) → maybeOpen (открыть, если есть сигнал, + уведомление) → запись в лог сигналов при смене решения по монете;
  3. save() — сохранение состояния на диск.

Параллельно: commandLoop() раз в 3 c (если задан Telegram-токен).

Запуск:

pnpm worker        # бесконечный цикл
pnpm worker:once   # один тик и выход (смоук-тест)

9. Хранение состояния (worker/store.ts)

Один JSON-файл worker/data/state.json.gitignore), запись атомарная (во временный файл + rename). Переживает перезапуск. Структура State:

{ startedAt, mode,
  equity,                        // текущий капитал
  positions: Position[],         // открытые
  closed:    Trade[],            // закрытые (последние 500)
  signals:   SignalLog[],        // лог решений при смене (последние 200)
  equityCurve: { t, equity }[] } // кривая капитала (последние 1000 точек)

Это ровно тот формат, который будущий HTTP-эндпоинт (/api/equity, /api/signals) сможет отдавать на сайт. (SQLite заменяема без изменения вызывающего кода — сейчас используется JSON.)


10. Фронтенд (src/)

Две страницы (React Router, src/App.tsx):

  • / — лендинг (src/pages/Landing.tsx): секции Hero, Thesis, Layers (6-слойный конвейер), SignalDesk, AIBrief (ресёрч-агент), AgentRoster, Performance, Tokenomics, TelegramModes, Roadmap, FAQ, CTA.
  • /terminal — живой терминал (src/pages/Terminal.tsx): опрашивает Hyperliquid раз в ~12 c (useScreen), показывает: цену/изменение/фандинг/OI/объём/ATR, сетку индикаторов, карточку вердикта, голоса агентов с барами уверенности, граф «оркестра» агентов.

Seed-фолбэк (src/data/seedMarket.ts): пока грузятся живые данные, мгновенно показываются детерминированные значения (помечены как не-live), чтобы UI не «висел»; затем заменяются реальными.

Ресёрч-агент (FactsAI) (src/lib/factsai.ts, AIBrief.tsx): интеграция с deep-research API (AI-бриф рынка с цитатами источников). Требует прокси (CORS + ключ на сервере); секция сама скрывается, если фид недоступен.


11. Статус реализации (по слоям)

Технический срез: что считается на живых данных, что симулируется, чего ещё нет.

Компонент Статус
L1: свечи, mark/mid, фандинг, OI, объём Реально (Hyperliquid live)
L1: стакан, лента ликвидаций Не реализовано (только в описании ростера)
L2: RSI / MACD / EMA / ATR / Bollinger / funding-Z Реально
L3: 4 агента (Trend / Breakout / MeanReversion / Funding) Реально
L4: агрегация → вердикт + риск-параметры Реально (формулы, не LLM)
L5: исполнение 🟡 Paper / dry-run (реальный PnL, без ордеров на бирже)
L6: Telegram (уведомления + команды) Реально
Персистентность Реально (JSON; в копирайте сайта упомянут SQLite)
Ростер «18 агентов» Метаданные UI; реально голосуют 4 стратегии
Кривая капитала / «последние сделки» на сайте 🟡 Seed-заглушка (live-данные даёт воркер, на сайт ещё не выведены)
FactsAI ресёрч-бриф 🟡 Код есть, выключен (нужен прокси; upstream отдаёт ошибку)
Коннект кошелька / hold-to-access Косметика (после TGE)
Токен $QUANT (цена/контракт/холдеры) pre-TGE (ещё не выпущен)

В разработке (roadmap): реальное исполнение ордеров на Hyperliquid (live-режим за флагом); LLM-слой (обоснование вердиктов и осмысленный /learn через Claude); вывод реального трек-рекорда воркера на сайт через /api/*.


12. Конфигурация (env, всё опционально — есть дефолты)

Читается воркером из .env.local (worker/config.ts):

Переменная Дефолт Назначение
QUANT_COINS BTC,ETH,SOL торгуемая вселенная
QUANT_INTERVAL 1h таймфрейм свечей
QUANT_POLL_MS 60000 период тика, мс
QUANT_EQUITY_START 10000 стартовый бумажный капитал, $
QUANT_LEVERAGE 5 плечо для расчёта notional
QUANT_CONF_FLOOR 0.55 минимальная уверенность для входа
QUANT_MAX_OPEN_PER_COIN 1 одновременных позиций на монету
TELEGRAM_BOT_TOKEN / TELEGRAM_CHAT_ID Telegram (без них — вывод в stdout)

13. Глоссарий

  • Perp (перп) — бессрочный фьючерс, контракт без даты экспирации.
  • Hyperliquid — децентрализованная биржа перпов с открытым публичным API данных.
  • LONG / SHORT / SKIP — ставка на рост / на падение / нет сигнала.
  • Funding (фандинг) — периодический платёж между лонгами и шортами; перекос показывает «перегретость» толпы. Положительная ставка → лонги платят шортам.
  • OI (Open Interest) — открытый интерес, сумма открытых контрактов.
  • Mark price — справедливая цена для расчётов (а не цена последней сделки).
  • RSI — осциллятор 0100: >70 перекуплен, <30 перепродан.
  • MACD — индикатор моментума по разнице скользящих средних.
  • EMA — экспоненциальная скользящая средняя (тренд).
  • ATR — мера волатильности (средний диапазон свечи).
  • Bollinger %B — положение цены внутри полос Боллинджера (0 = низ, 1 = верх).
  • TP / SL — take-profit / stop-loss.
  • RR (risk/reward) — отношение потенциальной прибыли к риску (TP/SL).
  • Notional — номинальный размер позиции в $ (капитал × размер% × плечо).
  • PnL — прибыль/убыток. Equity — капитал. Profit factor — сумма прибылей / сумма убытков.
  • Paper-trading / dry-run — «бумажная» торговля: расчёт как в реале, но без ордеров и денег.
  • Детерминированный — при одинаковом входе всегда одинаковый результат (нет случайности).
  • pre-TGE — до выпуска токена (Token Generation Event).