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

348 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# $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`:
```ts
{ 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`:
```ts
{ 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`):
`getCandles``summarizeTA``screen` (голоса + вердикт) → `price = mark || ta.price`
`markToMarket` (закрыть, если задело TP/SL, + уведомление) → `maybeOpen` (открыть, если
есть сигнал, + уведомление) → запись в лог сигналов **при смене решения** по монете;
3. `save()` — сохранение состояния на диск.
Параллельно: `commandLoop()` раз в 3 c (если задан Telegram-токен).
**Запуск:**
```bash
pnpm worker # бесконечный цикл
pnpm worker:once # один тик и выход (смоук-тест)
```
---
## 9. Хранение состояния (`worker/store.ts`)
Один JSON-файл `worker/data/state.json``.gitignore`), запись **атомарная**
(во временный файл + `rename`). Переживает перезапуск. Структура `State`:
```ts
{ 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).
```