Quant Agent — multi-agent quant terminal on Hyperliquid
All checks were successful
deploy / deploy (push) Successful in 7s

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>
This commit is contained in:
zefiroff 2026-06-09 19:00:13 +04:00
commit f3b533b032
110 changed files with 15045 additions and 0 deletions

348
docs/architecture.md Normal file
View file

@ -0,0 +1,348 @@
# $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).
```