name: futures-screener
description: Use when the user asks about the Futures Screener, crypto futures analytics, Binance futures data, signal detection, glassmorphism UI, screen.clkway.online deployment, Malaysia VPS, rsync deploy, rate limiter, mini-charts, treemap, or anything related to the futures-screener project at /home/app/futures-screener/.
Futures Screener Skill
Quick Access
- Прод URL: https://screen.clkway.online
- Прод SSH:
ssh root@72.62.247.119
- Дев код:
/home/app/futures-screener/
- PM2 name:
futures-screener (на обоих серверах)
- Port: 3200
- Детальная архитектура:
brain/projects/futures-screener.md
Servers
🟢 PROD — Malaysia VPS (5 May 2026)
- IP: 72.62.247.119
- SSH:
ssh root@72.62.247.119
- Host: Hostinger KVM 2 (Malaysia) — 2 vCPU, 8GB RAM, 100GB NVMe
- Domain:
screen.clkway.online (DNS Hostinger, SSL certbot)
- OS: Ubuntu 24.04 LTS | Node.js 18.20.8 | PM2 7.0.1 | Nginx 1.24
- Code path:
/home/app/futures-screener/
- Latency to Binance: ~30-50ms (Tokyo ap-northeast-1)
🔵 DEV — Main Server (Бендер)
- IP: 76.13.138.220
- Code path:
/home/app/futures-screener/
- Domain:
futures-screener.szhub.space (fallback)
- PM2: stopped (5 May), код остаётся как master copy + git
- Роль: разработка, git, rsync source
Environment Variables
JWT_SECRET=<in ecosystem.config.js>
VAPID_PUBLIC_KEY=<in ecosystem.config.js>
VAPID_PRIVATE_KEY=<in ecosystem.config.js>
VAPID_SUBJECT=mailto:admin@szhub.space
PORT=3200
Deploy (Dev → Prod)
# 1. Sync code
rsync -avz --exclude='node_modules' --exclude='.git' --exclude='data/*.db' --exclude='data/*.db-*' /home/app/futures-screener/ root@72.62.247.119:/home/app/futures-screener/
# 2a. Frontend ONLY (JS/CSS/HTML) — NO PM2 restart!
ssh root@72.62.247.119 "curl -s -X POST http://localhost:3200/api/reload-static"
# 2b. Server code changes — PM2 restart (expect 1-2 min Binance ban)
ssh root@72.62.247.119 "pm2 restart futures-screener"
# 3. Check logs
ssh root@72.62.247.119 "pm2 logs futures-screener --lines 20 --nostream"
⚠️ Грабли (Lessons Learned)
- НИКОГДА PM2 restart для фронтенда — каждый restart = Binance IP ban 20-45 мин
- Resync queue (8 May fix): WS gap → resync батчами (5 sym, 3s pause), не все 200+ разом
- Rate limiter: 3 уровня (soft 1800, hard 2200, pause on 429/418). Endpoint:
/api/rate-limiter
- Frontend hot-reload:
POST /api/reload-static (перечитывает файлы из RAM без restart)
Prod Admin
# Status
ssh root@72.62.247.119 "pm2 ls"
# Logs
ssh root@72.62.247.119 "pm2 logs futures-screener --lines 50 --nostream"
# Restart
ssh root@72.62.247.119 "pm2 restart futures-screener"
# Nginx
ssh root@72.62.247.119 "nginx -t && systemctl reload nginx"
# SSL renew (auto via certbot timer)
ssh root@72.62.247.119 "certbot renew --dry-run"
Tech Stack
- Backend: Node.js 18+, Fastify 5, better-sqlite3, ws, web-push
- Frontend: Vanilla JS, TailwindCSS, lightweight-charts v5
- Data: Binance Futures REST + WebSocket (FAPI)
- Deploy: PM2, Nginx reverse proxy, Let's Encrypt SSL
⚠️ SSH к Malaysia VPS (2 Jun 2026)
- Текущий
~/.ssh/id_ed25519 НЕ авторизован на проде (ротировали 23 May, прод не обновили).
- Рабочий ключ:
~/.ssh/id_ed25519.bak. Команда:
ssh -o IdentitiesOnly=yes -i ~/.ssh/id_ed25519.bak root@72.62.247.119
- node/pm2 на проде в
/usr/bin. sqlite3 CLI НЕТ — юзать python3 + sqlite3 модуль.
🐕 Watchdog сигналов (2 Jun 2026)
- Что было: процесс online 7 дней, HTTP 200, но сканеры залипли в resync-шторме (сиквенс-трекер state.js даёт вечные ложные gap'ы → бесконечный resync → event loop голодает → 0 сигналов). Сигналы не писались 27 May–1 Jun (~6 дней потеряно, невосстановимо).
health-check.sh это НЕ ловит (PM2=online, HTTP=200).
- Детектор:
/api/rate-limiter → weightAge. В норме <60с (скан каждые 60с), при залипе растёт до дней.
- Фикс симптома:
pm2 restart futures-screener (память 2.1gb→88mb). Корневой баг state.js НЕ чинен.
- Watchdog:
scripts/watchdog.sh (commit cd8a86b) в prod cron каждые 5 мин. Триггер: weightAge>15мин ИЛИ HTTP down → restart (кулдаун 15мин). Лог: logs/watchdog.log. Telegram-алерт опц. через env FS_WATCHDOG_TG_TOKEN+FS_WATCHDOG_TG_CHAT (выключен).
- Диагностика «пустые сигналы»:
curl /api/signals/live (count), /api/signals/summary (last_1h), /api/rate-limiter (weightAge). Сигналы в БД server/data/users.db таблица signal_log. Live — в памяти (сброс на рестарте).
📊 Стандарт отчётов Rick'у = PNG в Telegram (2 Jun 2026)
- Rick читает с телефона. KB (kb.szhub.space) показывает .html как КОД, не рендерит; PNG не отображает вообще.
- Решение: HTML-отчёт → рендер в PNG (headless chromium, JS выполняется) → отправка фоткой в чат.
- Хелпер:
/home/app/scripts/send-report.sh <report.html> [caption] [width] [height] (дефолт 1120×2400, retina 2x). Читает токен бота read-only из claude-code-telegram/.env (файлы бота НЕ трогать), шлёт на chat_id Rick'а 191142060.
- ImageMagick на сервере НЕТ (обрезка пустоты недоступна — играть высотой).
📖 Order-Flow Imbalance — новый тип сигнала (3 Jun 2026, Phase 0 live)
- Что: скальп на дисбалансе стакана.
signals.js scanOrderflow() каждые 10с по ликвидным альтам (vol≥$30M, искл BTC/ETH), эмит orderflow_imbalance при |imbalance|≥0.35. depth-store.js getImbalance(symbol) = Σbid/Σask из последнего снапшота. metadata несёт paper-тикет (entry/TP+1.5%/SL−0.5%/qty для $50×5=$250).
- Confidence (1 фактор!):
50+((|imb|−0.35)/0.65)×45, макс 95. ТОЛЬКО сила дисбаланса (50≈±35%, 71≈±55%, 95≈±100%). Обогащение факторами — после Фазы 1.
- Кулдаун: emitSignal получил опц.
cooldownMs (дефолт=глоб 60мин); OF юзает 5мин. НЕ менять глобальный COOLDOWN_MS — сломает 6 др. сканеров.
- Фронт: тип в фильтре/карточке + секция 📖 Order-Flow в settings.js + слайдер
signalOfMinConf (дефолт 50) = свой min-confidence фильтр ленты (независим от глоб MIN CONFIDENCE). Фильтр в signals.js renderSignals.
- Phase 0 = детекция+лог в signal_log (НЕТ реальных ордеров). checkOutcomes сам трекает MFE/MAE. ~2500 сигналов/день при 0.35.
- Спека лайва: Bybit, банк $50, плечо 5x, брекет 1.5%/0.5% (R:R 3:1), hold 5-10мин, MAKER-вход (taker убивает эдж). Эдж тонкий ~+0.10-0.15%/сделку. Фазы: 0 лог→1 форвард-тест→2 бумага→3 микро-лайв.
- 🔴 ГРАБЛЯ ДЕПЛОЯ: nginx проксирует ВСЮ статику (.js тоже) в node:3200 → node отдаёт из in-memory staticCache.
reload-static требует admin-JWT (не подписался, 403). Фронт-изменения на FS-проде = pm2 restart (НЕ reload-static), вопреки общему правилу. Cache-bust: sw CACHE_NAME + ?v= в index.html.
- 📊 Симулятор реальных сделок (3 Jun): orderflow в signal_log × путь цены из depth.db (10с) → path-resolved TP/SL + тайм-стоп. Скрипт-паттерн: открыть обе БД (
server/data/users.db + data/depth.db), to_ms(created_at) UTC→epoch, walk снапшоты символа в окне HOLD, TP/SL/время. ⚠️ depth.db rolling 4ч — анализ старше 4ч невозможен (путь цены ушёл); для дней нужен архив depth ИЛИ грубые spot_after_* из signal_log. Первый прогон (~1.5ч, ~110 сделок): ШУМ ±0.07%/сделку, TP1.5/SL0.5≈+0.05, TP1/SL0.5≈−0.01, короткий тайм-стоп 3-5мин чуть лучше, TP 1-1.5% редко достаётся за скальп (10-18/110). Эдж из бэктеста вживую пока не воспроизвёлся — ждём накопления (Фаза 1, недели). NEXT: повторить sweep когда будет ≥3-4к сделок.
🎯 Order-Flow v2 — окно 0.5% + антиспуфер (4 Jun 2026, live на проде)
- Бэктест (4 Jun, 147 path-сделок, depth 4ч): на ВСЕЙ выборке (метод mfe/mae) эдж только у сильнейшей четверти (conf 80+); 94% слабых сигналов = шум/минус. Перебор форм веса по дистанции → узкое окно ≤0.5-1% бьёт плоское ±3%. Но главный рычаг — АНТИСПУФЕР: Q4 net flat ±3% +0.16% → окно ≤1% +0.19% → окно ≤0.5%+антиспуф +0.33%, WR 51%. Само сужение окна даёт мало — скачок даёт выброс флеш-стен. Оптимум TP/SL/hold: TP 2% / SL 0.5% / тайм-стоп 3-30м (в пределах шума, 10м ок), R:R 4:1; SL 0.3% номинально лучше, но нереален (спред/слип).
- Внедрено (2 коммита):
depth-store.js getImbalance переписан — окно 0.5% (только стены в пределах стопа) + антиспуфер: читает последние 4 снапшота (~30-40с), стену засчитывает только если жила во ВСЕХ предыдущих (≥50% объёма); бакеты по 0.1% дистанции (устойчиво к дрожанию цены); возвращает null пока нет истории (после рестарта). Константы вверху depth-store.js: OF_WINDOW_PCT=0.5, OF_PERSIST_SNAPS=4, OF_PERSIST_FRAC=0.5. signals.js: OF_THRESHOLD 0.35→0.60→0.80 (0.60 давал ~130/час, 46% в полке 0.60-0.70 ниже эдж-квартиля 0.79 → подняли до 0.80, коммит 0081c0b), OF_TP_PCT 1.5→2.0, тикет += exchange:'binance'. Confidence-формула не трогалась (привязана к OF_THRESHOLD, пересчиталась сама).
- Порог: 0.60 был мягким (~130/час, 46% в полке 0.60-0.70 ниже эдж-квартиля 0.79; на узком окне перекос часто насыщается до ±1 → conf 95) → поднят до 0.80 (4 Jun 07:09 UTC). БД orderflow очищена полностью при смене порога — чистый старт с 0.80. NEXT: утром/через 1-2 дня пересчитать стату → подтвердить эдж, при необходимости докрутить порог.
- Биржа торговли = БИНАНС (не Bybit!): сигнал считаем по стакану Бинанса → торговать там же = та же стена под ордером, список монет 1:1, нет cross-exchange риска. $50 на Бинансе. Тикет помечен exchange:'binance' (метка, исполнения нет — Phase 0).
- Бэктест-скрипт:
reports/of_analyze.py (на проде через ssh ... 'python3 -' < of_analyze.py, абсолютные пути к БД). Окно+антиспуф+перебор TP×SL×hold по квартилям |imbalance|. Отчёты-картинки: reports/of-deep.html (+inject JSON → send-report.sh).
- 🔴 ГРАБЛЯ:
depth-store.init() при старте СРАЗУ чистит снапшоты >4ч (rolling). Тест getImbalance через require+init на dev стёр замороженный depth.db (был мёртв с 23 мая — безвредно). Тестировать getImbalance изолированно нельзя без живого свежего depth.
- DEV-СКРИНЕР ОСТАНОВЛЕН (4 Jun):
pm2 stop futures-screener на главном (76.13.138.220) + save. Depth на dev был заморожен с 23 мая (не копился) → стейджинг мёртв. Прод (Malaysia VPS) = единственная живая среда. Каталог /home/app/futures-screener НЕ удалять — он deploy-источник (rsync на прод). Деплой OF v2: rsync 2 файлов ключом ~/.ssh/id_ed25519.bak + pm2 restart на VPS.