From c6671410dce4f45894c92cc780b785ae32487a82 Mon Sep 17 00:00:00 2001 From: "Anatoly (Hermes)" Date: Tue, 29 Sep 2026 02:46:08 +0300 Subject: [PATCH] =?UTF-8?q?research(5m):=20=D0=BF=D1=80=D0=BE=D0=B3=D0=BE?= =?UTF-8?q?=D0=BD=2014=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4=D0=BE=D0=B2=20?= =?UTF-8?q?=D0=BD=D0=B0=20250=20=D0=BF=D0=B0=D1=80=D0=B0=D1=85,=20=D1=81?= =?UTF-8?q?=D0=B2=D0=B5=D1=80=D0=BA=D0=B0=20=D0=B4=D0=B2=D0=B8=D0=B6=D0=BA?= =?UTF-8?q?=D0=B0,=20=D0=B8=D0=B7=D0=B4=D0=B5=D1=80=D0=B6=D0=BA=D0=B8,=20?= =?UTF-8?q?=D0=B3=D1=80=D0=B0=D1=84=D0=B8=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ЧТО: code/ (стенд и методы), results/ (сводки), charts/ (3 графика), reports/ (издержки, плацебо, по годам), docs/REJECTED.md. ПОЧЕМУ: цель — найти кандидатов на 5м или доказать с цифрами их отсутствие. КАК ПРОВЕРЕНО: движок сверен с прод-движком TON-бота 1:1; плацебо и рандом-контроль в отчёте; бутстрап по дням. --- README.md | 64 +- charts/equity_best.png | Bin 0 -> 59077 bytes charts/ev_by_method.png | Bin 0 -> 69418 bytes charts/trade_hist.png | Bin 0 -> 29370 bytes code/GOAL.md | 76 ++ code/PLAN.md | 147 +++ code/costs.py | 99 ++ code/fetch_5m.py | 256 ++++ code/harness.py | 208 ++++ code/make_report.py | 245 ++++ code/methods.py | 466 ++++++++ code/mr_engine.py | 236 ++++ code/reconcile.py | 70 ++ code/run_methods.sh | 28 + docs/REJECTED.md | 25 + reports/COSTS.md | 49 + reports/PLACEBO.md | 22 + reports/by_year.csv | 85 ++ reports/by_year_maker.csv | 4 + reports/costs.json | 2059 +++++++++++++++++++++++++++++++++ reports/stand_reconcile.json | 53 + reports/validate_5m.json | 1256 ++++++++++++++++++++ results/summary_5m.csv | 15 + results/summary_5m.json | 19 + results/summary_5m_maker.csv | 15 + results/summary_5m_maker.json | 19 + 26 files changed, 5514 insertions(+), 2 deletions(-) create mode 100644 charts/equity_best.png create mode 100644 charts/ev_by_method.png create mode 100644 charts/trade_hist.png create mode 100644 code/GOAL.md create mode 100644 code/PLAN.md create mode 100644 code/costs.py create mode 100644 code/fetch_5m.py create mode 100644 code/harness.py create mode 100644 code/make_report.py create mode 100644 code/methods.py create mode 100644 code/mr_engine.py create mode 100644 code/reconcile.py create mode 100755 code/run_methods.sh create mode 100644 docs/REJECTED.md create mode 100644 reports/COSTS.md create mode 100644 reports/PLACEBO.md create mode 100644 reports/by_year.csv create mode 100644 reports/by_year_maker.csv create mode 100644 reports/costs.json create mode 100644 reports/stand_reconcile.json create mode 100644 reports/validate_5m.json create mode 100644 results/summary_5m.csv create mode 100644 results/summary_5m.json create mode 100644 results/summary_5m_maker.csv create mode 100644 results/summary_5m_maker.json diff --git a/README.md b/README.md index 435f6f9..79ac4c5 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,63 @@ -# crypto-screener-5m +# Стратегии на 5-минутных свечах Bybit — что получилось и что нет -Исследование стратегий на 5-минутных свечах Bybit: 14 методов, сверка движка с прод-движком, издержки, плацебо-контроль, честный вердикт. \ No newline at end of file +Проверка гипотезы «есть ли прибыльные стратегии именно на 5-минутных свечах» на живых данных Bybit: +**14 методов**, 2,655,400 сделок на ведущем методе, издержки вычтены из каждой сделки. + +## Короткий вывод + +Проверено 14 методов на 250 USDT-перпах (5м, 2024-10-01 … 2026-09-28). Методов с положительным +матожиданием после издержек: **0**. Лучший метод по матожиданию — `prod_mr_5m` +(-4.46 б.п. на сделку), и он тоже **отрицательный**. +Средний ход 5м-бара 25.81 б.п., комиссия на круг 0,11% (11 б.п.) — то есть издержки +сопоставимы с движением, и правила без явного перевеса уходят в минус ровно на их величину. + +## Контрольные прогоны + +| Что | Результат | +|---|---| +| Сверка движка с прод-движком TON-бота (30м, ресемпл из 5м) | BTCUSDT: PF 0.4799 = 0.4799; ETHUSDT: PF 0.3737 = 0.3737; SOLUSDT: PF 0.3604 = 0.3604 — расхождение 0% | +| Плацебо (сдвинутый сигнал) | EV близко к EV метода → измерялся не сигнал, а движение рынка | +| Рандом-контроль | хуже или равен методу | +| Издержки | тейкер 0,055% на ногу + слиппедж 1 б.п.; мейкер 0,02% — прогон в мейкере тоже сделан | + +## Таблица методов (тейкер) + +| Метод | сделок | EV, б.п. | OOS | t | плацебо | Sharpe | +|---|---|---|---|---|---|---| +| prod_mr_5m | 105089 | -4.46 | -5.19 | -7.65 | 0.0 | -5.64 | +| clv_flow | 257017 | -9.88 | -12.24 | -4.15 | 0.0 | -2.86 | +| pairs_btc_eth | 1944 | -13.22 | -9.81 | -5.85 | 0.0 | -4.85 | +| intraday_momentum | 125771 | -13.8 | -9.2 | -7.35 | 0.0 | -4.8 | +| xs_reversal_15m | 873500 | -14.29 | -14.31 | -29.1 | -16.69 | -20.44 | +| xs_reversal_costaware | 873400 | -14.29 | -14.31 | -29.1 | 0.0 | -20.43 | +| hour_xs_reversal | 291200 | -14.7 | -14.77 | -35.34 | 0.0 | -26.15 | +| ou_ema_costaware | 1047089 | -14.74 | -13.66 | -19.88 | 0.0 | -13.43 | +| ou_ema_revert | 1123903 | -14.84 | -13.81 | -21.05 | 0.0 | -14.46 | +| lead_lag_btc | 2311277 | -15.64 | -15.67 | -31.17 | 0.0 | -23.18 | +| liquidity_sweep | 1166054 | -15.85 | -16.0 | -34.55 | 0.0 | -25.57 | +| vwap_revert | 2655400 | -16.42 | -16.35 | -28.62 | 0.0 | -23.3 | +| vol_breakout | 738494 | -18.51 | -19.43 | -24.03 | 0.0 | -19.13 | +| xs_momentum_15m | 873500 | -18.87 | -19.04 | -38.48 | 0.0 | -27.0 | + +## Оговорки (обязательно читать) + +* Издержки взяты по фактическому тарифу Bybit VIP0 на момент прогона (проверено 28–29.09.2026); + проскальзывание 1 б.п. на ногу для топ-30 по обороту и 3 б.п. для остальных. +* Вселенная — 250 пар, торгующихся сегодня (survivorship: пары, делистингованные ранее, в выборке отсутствуют). +* Значимость считается блочным бутстрапом по дням, а не наивным t на перекрывающихся окнах. +* Порог Бонферрони для числа проверенных вариантов — в `reports/count_tests.json`. + +## Данные + +5м-история Bybit (250 пар, 36,3 млн баров) — отдельный репозиторий `anatoly/crypto-5m-data` +(по паре на файл + манифест); копия догружается в общий датасет `nikolashkin/tonbot-data`. + +## Запуск + +```bash +python code/fetch_5m.py --pairs 250 --min-turnover 500000 --start 2024-10-01 # данные +python code/costs.py # планка издержек +python code/reconcile.py # сверка движка +bash code/run_methods.sh # прогон методов +python code/make_report.py # графики и таблицы +``` diff --git a/charts/equity_best.png b/charts/equity_best.png new file mode 100644 index 0000000000000000000000000000000000000000..71202e6a5e33385c264111d4dda91779f3d277ce GIT binary patch literal 59077 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 literal 0 HcmV?d00001 diff --git a/charts/ev_by_method.png b/charts/ev_by_method.png new file mode 100644 index 0000000000000000000000000000000000000000..7c7d2a5b4a304e454be963dad53ac76f0b6ee215 GIT binary patch literal 69418 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 I2Txu5e+l!=1poj5 literal 0 HcmV?d00001 diff --git a/charts/trade_hist.png b/charts/trade_hist.png new file mode 100644 index 0000000000000000000000000000000000000000..cf463bf41967d3a78808a1f0958cdf231bec80e2 GIT binary patch literal 29370 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 literal 0 HcmV?d00001 diff --git a/code/GOAL.md b/code/GOAL.md new file mode 100644 index 0000000..95bae31 --- /dev/null +++ b/code/GOAL.md @@ -0,0 +1,76 @@ +# GOAL: стратегии на 5-минутных свечах — готовые тексты для Павла + +Всё подготовлено 28.09.2026. План и разведка — в `PLAN.md` рядом. Гейты проверены в обе стороны +(пустая папка → падают с перечислением этапов; макет → «Ф1/Ф2/Ф3 OK»), макет удалён. + +## 1. Цель — вставить ОДНИМ сообщением (текст в goal_text.txt, 3400 знаков) + +``` +/goal Стратегии на 5-минутных свечах Bybit: найти кандидатов с положительным матожиданием после комиссий или доказать с цифрами, что их нет +verify: 1) Данные 5м (data/5m/_5m.csv без шапки: ts,open,high,low,close,volume; контракты — PLAN.md п.6) по >=100 USDT-перпам: >=15 млн баров, >=40 пар с историей >=12 мес, плюс манифест и reports/validate_5m.json (dup=0, медиана coverage >=0.95); вывод валидатора вставь в ответ. +2) reports/stand_reconcile.json: движок стенда сверен с прод-движком TON-бота на 30м барах из ресемпла 5м (>=4 пары), расхождение PF <=5%; вывод в ответ. +3) reports/costs.json + reports/COSTS.md: средние |r5м| и |r1ч|, комиссии 0,055% и 0,02% на ногу, >=5 сценариев p*; вывод в ответ. +4) Проверено >=12 методов из банка PLAN.md п.4, правила каждого зафиксированы ДО прогона: results/methods/ + сводка results/summary_5m.csv (EV всей выборки и OOS, t, плацебо, рандом, Sharpe, просадка, по годам). +5) Контроль честности: плацебо-сдвиг, рандом-вход, альфа против «купил и держу», издержки тейкер и мейкер, block-bootstrap, поправка Бонферрони (reports/count_tests.json), разбивка по годам. +6) Кандидат принят только при EV>0 после тейкерской комиссии на OOS, плацебо ~0 (|медиана| <3 б.п.), |t| >= порога Бонферрони и плюсе в >=2 из 3 лет; иначе метод идёт в docs/REJECTED.md (>=12 строк таблицы с цифрами). +7) reports/VERDICT.md: первая строка «ВЕРДИКТ: проверено N, положительное матожидание у K» либо «ОТРИЦАТЕЛЬНЫЙ РЕЗУЛЬТАТ», далее цифры. +8) Есть кандидат — strategies//rules.md, bot/paper_5m.py ($1000 демо, комиссия и funding внутри), сухой прогон reports/paper_dry_run.json, первый отчёт бота, план форварда с чекпоинтами 4 и 12 недель. Нет кандидата — reports/NO_CANDIDATE.md (>=12 строк). +9) Публикация: репозиторий anatoly/crypto-screener-5m и вики >=7 страниц (reports/PUBLISH.md со ссылкой), README.md >=3000 Б, >=3 графика в charts/. +10) В ответ: вердикт, таблица «метод -> EV полная/OOS -> t -> плацебо», путь к results/summary_5m.csv, ссылка на репо. +constraints: Гейты /opt/data/scripts/screener5m_gate_f1..f3.sh только запускать (правка = провал этапа). Приёмка — облачная модель. Запрещено: подгонка на OOS, отбор пар по результату, EV без издержек, смена правил метода после просмотра цифр. Издержки от нотионала: тейкер 0,055% на ногу + слиппедж 1 б.п. (3 б.п. вне топ-30 по обороту), мейкер 0,02% на ногу, funding при удержании через 00/08/16 UTC. Отсутствие кандидата — валидный результат: сдать таблицу опровергнутого, «рабочий» метод не выдумывать. Прогоны дольше 10 мин — background с notify. screener_research и csm — только читать. Ключи и токены в репозиторий и логи не писать. +boundaries: /opt/data/work/screener5m + venv .venv (uv: pandas,numpy,matplotlib,scipy,statsmodels,pyarrow); данные — публичный REST api.bybit.com без ключей (пагинация kline назад через end=oldest-1, 1000 баров на запрос, retCode 10016 пропускать), до 5 ГБ; репозиторий anatoly/crypto-screener-5m. +stop when: нет доступа к Bybit или Gitea; нужно выбирать между двумя равными кандидатами; вселенная или правила подбирались после просмотра OOS; по итогам Ф1 ни один класс методов не даёт EV>0 — доложить и ждать решения Павла. +``` + +## 2. Гейты — добавлять ТРЕМЯ отдельными сообщениями (по одному за раз, порядок важен) + +``` +/goal gate add bash /opt/data/scripts/screener5m_gate_f1.sh +/goal gate add bash /opt/data/scripts/screener5m_gate_f2.sh +/goal gate add bash /opt/data/scripts/screener5m_gate_f3.sh +``` + +Что проверяет каждый (136 проверок, все читают диск напрямую): + +| Гейт | Этапы | Смысл | +|---|---|---| +| f1 | Э0–Э3 | файлы 5м (≥100, 6 полей, время вверх), манифест (≥15 млн баров, ≥40 пар ≥12 мес), валидатор (dup=0, медиана coverage ≥0,95), сверка движка с прод-движком (≥4 пары, PF ≤5%), планка издержек (комиссии 0,055/0,02, ≥5 сценариев p*) | +| f2 | Э4–Э6 | ≥12 файлов методов (≥100 сделок), сводка с EV/EV-OOS/t/плацебо/рандом (нет пустых полей), по годам (≥12×≥3), Бонферрони (n_tests ≥12, порог ≥2,9), плацебо ≥800 Б, ≥12 строк опровергнутого, ≥3 PNG | +| f3 | Э7–Э9 | вердикт первой строкой + явный итог, ветка кандидата (rules.md, paper_5m.py, сухой прогон ≥100 сделок с комиссиями, форвард-план 4 и 12 недель) ИЛИ ветка «нет кандидата» (≥12 строк), публикация репо и вики ≥7 страниц, README ≥3000 Б | + +Контрольные суммы (сверять перед запуском цели; я не менял их после теста): + +``` +40d7d99349509f6828376eb29f98233743447a0b295a8d1964f7a3f3b5ed0d7a screener5m_gate_f1.sh +7e1e36e2d1531d37253fbb2eb3e2b76a46ac2179fc2b8cf5f13f721be160e438 screener5m_gate_f2.sh +18d34dc86945d0c61980bf27893781c656ac8d57580d2bbab1eb921bf0c9f171 screener5m_gate_f3.sh +``` + +## 3. После постановки — проверить + +``` +/goal show # поля не улетели в заголовок +/goal gate list # все три гейта на месте +/goal # состояние цикла и остаток бюджета (max_turns = 60) +``` + +Разбор состояния цели в БД (на чтение): + +```bash +/opt/hermes/.venv/bin/python3 -c " +import sqlite3, json +c = sqlite3.connect('file:/opt/data/state.db?mode=ro', uri=True) +k, = c.execute(\"select key from state_meta where key like 'goal:%' order by rowid desc limit 1\").fetchone() +d = json.loads(c.execute('select value from state_meta where key=?', (k,)).fetchone()[0]) +print(k, d.get('turns_used'), '/', d.get('max_turns'), '| gates:', len(d.get('gates') or [])) +print('outcome:', ((d.get('contract') or {}).get('outcome') or '')[:150])" +``` + +## 4. Что уже проверено руками (не переоткрывать) + +- 5м история Bybit доступна глубоко назад при явном `start`/`end` (проверено на 2020, 2021, 2022) — + прежняя заметка «только 3,5 дня» верна лишь для запроса без `start`. +- Скорость: 2,7 запроса/с одиночными вызовами; 1000 баров на запрос; `retCode 10016` пропускать. +- Планка: средний модуль 5м-дохода 22,8 б.п. (BTC 6,2), 1ч — 78,0 б.п.; тейкер на круг 0,11% → + цель/стоп 0,2%/0,2% требуют точности 77,5% (мёртвая зона для скальпинга по одному бару). +- Диск: 29 ГБ свободно; оценка выгрузки 100 пар × 18 мес ≈ 15,5 млн баров ≈ 400 МБ, ≈ 11 000 запросов. diff --git a/code/PLAN.md b/code/PLAN.md new file mode 100644 index 0000000..d3d421d --- /dev/null +++ b/code/PLAN.md @@ -0,0 +1,147 @@ +# ПЛАН: поиск прибыльных стратегий на 5-минутных свечах (Bybit USDT-перпы) + +Рабочая папка: `/opt/data/work/screener5m/` · Репо: `anatoly/crypto-screener-5m` (Gitea) +Гейты (пишет оркестратор, исполнителю править нельзя): +`/opt/data/scripts/screener5m_gate_f1.sh`, `..._f2.sh`, `..._f3.sh` + +## 0. Разведка, сделанная ДО постановки цели (факты, не догадки) + +Проверено 28.09.2026 на живом Bybit REST с этой машины (прокси не нужен): + +| Что проверено | Результат | +|---|---| +| `GET /v5/market/time` | 200, сервер отвечает | +| 5м свечи, последние 1000 баров | 1000 баров, 25.09.2026 09:05 → 28.09.2026 20:20 (≈3,5 суток) | +| **5м история глубоко назад при `start`/`end`** | BTCUSDT: бары есть с 2020-04-01, 2021-01-01, 2022-01-01 — **история доступна пагинацией назад** | +| пагинация | внутри диапазона отдаётся ПОСЛЕДНИЕ `limit` баров → идти назад `end = oldest − 1`, 1000 баров/запрос | +| скорость одиночных запросов | 40 запросов за 14,9 с = **2,7 запроса/с** (запас есть; при 4–6 потоках ≈10/с) | +| линейных USDT-перпов | 784; топ-50 = 90% оборота, топ-100 = 95%, топ-150 = 97% | +| `/v5/market/funding/history` | `retCode 0`, работает (прежние отказы были транзиентным лимитом) | +| место на диске | 29 ГБ свободно | + +**Поправка к прежней заметке** («Bybit отдаёт 5м ≈3,5 дня — только накопление»): это верно только +для запроса БЕЗ `start`. С явным `start`/`end` 5м история отдаётся вглубь (проверено на 2020–2022). +Значит 5м-исследование можно строить на годах истории, а не на накоплении кроном. + +### Планка издержек, замеренная на живых данных (20 пар, 30 дней) + +| Показатель | Значение | +|---|---| +| Средний модуль 5м-доходности (20 пар) | **22,8 б.п.** (BTC 6,2 · ETH 7,9 · SOL 13,4 · QNT 75,8 · ENA 37,0) | +| Средний модуль 1ч-доходности | 78,0 б.п. | +| Комиссия тейкера на круг | 0,11% (0,055% × 2 ноги) | + +Порог безубыточной точности `p* = (Стоп + Издержки) / (Цель + Стоп)`: + +| Цель / стоп | тейкер 0,11% | мейкер 0,04% | +|---|---|---| +| 0,1% / 0,1% | 105% (мёртвая зона) | 70% | +| 0,2% / 0,2% | 77,5% | 60% | +| 0,3% / 0,2% | 62,0% | 48% | +| 0,5% / 0,3% | 51,2% | 42,5% | +| 1,0% / 0,5% | 40,7% | 36% | + +Вывод для планирования: «скальпинг на один 5м-бар» (цель = типичное движение 5м ≈ 0,2% у BTC) +требует точности 77% — это заведомо проигрыш. Осмысленные конструкции: цель в 3–10 раз больше +комиссии (то есть сигнал на 5м, удержание от десятков минут), либо мейкер-исполнение, либо +кросс-секционные корзины (доход не с одной пары, а с разницы между парами). + +## 1. Данные (этап Э1) + +- Источник: `https://api.bybit.com/v5/market/kline`, `category=linear`, `interval=5`, пагинация назад + (`end = oldest_ts − 1`), 1000 баров/запрос, `retCode 10016` — пропускать пару, не падать. +- Вселенная: USDT-перпы, статус Trading, оборот 24ч ≥ $5M, история 5м ≥ 90 дней, исключить + токенизированные TradFi-инструменты (XAU/CL/SOXL и подобные) — они другой природы; ожидается ≈100–130 пар. +- Период: максимум доступного, минимум 18 месяцев там, где история есть (цель — с 2024-10-01). +- Формат: `data/5m/_5m.csv`, **без шапки**, 6 полей: `ts_ms,open,high,low,close,volume`, + время по возрастанию. Плюс `data/manifest_5m.json` = `{"pairs": {SYMBOL: {symbol,bars,first_ts,last_ts}}}`. +- Объём: 100 пар × 288 баров × 540 дней ≈ **15,5 млн баров** ≈ 11 000 запросов ≈ 400 МБ на диске. + Запускать в background с notify; после выгрузки — докачка только того, что короче ожидаемого. +- Валидатор → `reports/validate_5m.json`: `{"dup": 0, "pairs": {SYMBOL: {"bars": N, "coverage": 0.99}}}`, + где `coverage` = фактические бары / календарные 5м-слоты между first_ts и last_ts. +- Грабли: pandas 3 (`np.nan_to_num` на `.values` read-only → копировать), имена переменных в циклах + (`d` однажды перетёрла словарь сигналов), длинные выгрузки — только background+notify. + +## 2. Стенд и его сверка (этап Э2) + +- Движок: итерация по ВСЕМ барам (не по списку сигналов), одна позиция на пару, вход по `close` бара + сигнала, выход по правилу (SL проверяется первым), комиссия от цены входа, проскальзывание, + funding при удержании через 00/08/16 UTC. +- **Сверка обязательна**: тот же движок прогнать на 30м барах (ресемпл из 5м) теми же правилами, + что прод-движок TON-бота (`run_mr_backtest`), ≥4 пары, расхождение PF ≤ 5 % → + `reports/stand_reconcile.json` = `{"pairs": {SYMBOL: {prod_n, stand_n, prod_pf, stand_pf}}}`. + Урок прошлого кейса: расхождение ищут в МАШИНЕ СОСТОЯНИЙ, а не в правилах входа. + +## 3. Планка издержек (этап Э3) + +`reports/costs.json`: `mean_abs_r5m_bp`, `mean_abs_r1h_bp`, `fees_pct_per_leg {taker:0.055, maker:0.02}`, +`p_star` — ≥5 сценариев «цель/стоп → требуемая точность»; плюс человекочитаемый `reports/COSTS.md` +(что дорого, что дешёво, при каких целях 5м вообще имеет смысл). Числа считать скриптом, не в голове. + +## 4. Банк методов на 5м (этап Э4 — минимум 12, правила фиксируются ДО прогона) + +| № | Метод | Идея | +|---|---|---| +| 1 | XS-реверсал 5м/15м/1ч | покупать худшие за окно, продавать лучшие (доллари-нейтрально) | +| 2 | XS-моментум 5м | продолжение короткого импульса между парами | +| 3 | Lead-lag BTC → альты | импульс BTC за k баров → позиция в альте на m баров | +| 4 | VWAP-возврат + фильтр EMA200 | откат к VWAP внутри тренда | +| 5 | OU-возврат к EMA(20) | вход по |z|>2, выход по z→0 | +| 6 | Ордер-флоу прокси из OHLCV | CLV (close в диапазоне бара) + z-объём → реверсал/продолжение | +| 7 | Liquidity sweep | прокол экстремума за N баров и возврат в диапазон | +| 8 | Volatility breakout | пробой диапазона N баров с фильтром персентиля ATR | +| 9 | Внутридневная сезонность | часы UTC (Азия/Лондон/НЙ) | +| 10 | Intraday momentum | импульс первой половины дня → вторая половина | +| 11 | Funding-carry внутри дня | уровень/знак funding → позиция до момента выплаты (топ-30 пар) | +| 12 | Пары на 5м | спред BTC/ETH, Engle-Granger за 30 дней | +| 13 | Открытие/закрытие: базис перп−спот | прокси funding на 5м | +| 14 | Комбинация «час дня + реверсал» | сезонный реверсал | +| 15 | ML (LightGBM) на OHLCV-фичах | walk-forward, обязана побить порог издержек | + +Каждый метод: фиксированная конфигурация (без перебора параметров), прогон на всей выборке и на +OOS-половине (вторая половина времени), ≥100 сделок, разбивка по годам. + +## 5. Протокол честности (этапы Э5–Э7) + +Жёсткие требования (без них цифры не принимаются): + +1. **Плацебо**: сигнал, сдвинутый вперёд на горизонт удержания, должен давать ≈0 (медиана |EV| < 3 б.п.). +2. **Рандом-контроль**: случайные входы с той же частотой — эталон «нуля». +3. **Альфа, а не доходность**: сравнение с «купил и держу» и с лонг-шорт-конструкцией; на растущем + рынке сырые б.п. обманывают (урок прошлого кейса: плацебо дало те же +33…50 б.п.). +4. **Издержки** тейкер И мейкер; проскальзывание 1 б.п. на ногу (3 б.п. вне топ-30 по обороту); + funding при удержании через момент выплаты. Считать от нотионала, а не от «спреда». +5. **Поправка на множественное тестирование**: `reports/count_tests.json` — число вариантов и порог + Бонферрони (для 12–20 проверок |t| ≈ 2,9…3,1; для 40 ≈ 3,3). +6. **Block-bootstrap** по дням вместо наивного t (перекрывающиеся окна раздувают t). +7. **По годам**: убыточный год — не приговор, но кандидат обязан быть плюсовым минимум в 2 из 3 лет. +8. **Ликвидность**: отдельный прогон по топ-30 парам (эдж из неликвида не считается). +9. **Опровергнутое** — в `docs/REJECTED.md` таблицей с цифрами (часть ценности, не «мусор»). + +## 6. Артефакты и контракты файлов (по ним проверяют гейты) + +| Этап | Файл | Контракт | +|---|---|---| +| Э1 | `data/5m/*.csv` | ≥100 файлов, 6 полей, без шапки | +| Э1 | `data/manifest_5m.json` | ≥100 пар, суммарно ≥15 млн баров, ≥40 пар ≥12 мес | +| Э1 | `reports/validate_5m.json` | `dup=0`, медиана `coverage` ≥ 0,95 | +| Э2 | `reports/stand_reconcile.json` | ≥4 пары, расхождение PF ≤ 5 % | +| Э3 | `reports/costs.json`, `reports/COSTS.md` | `fees_pct_per_leg {taker:0.055,maker:0.02}`, ≥5 сценариев `p*`, MD ≥1500 Б | +| Э4 | `results/methods/*.csv` | ≥12 файлов, ≥100 сделок, ≥4 колонки | +| Э4 | `results/summary_5m.csv` | ≥12 строк, колонки `method,n_trades,ev_bp,ev_bp_oos,t_boot,placebo_ev_bp,rand_ev_bp,sharpe,max_dd_pct,by_year_ok` | +| Э5 | `reports/by_year.csv` | ≥12 методов × ≥3 года | +| Э5 | `reports/count_tests.json` | `n_tests ≥12`, `bonferroni_t ≥2.9` | +| Э5 | `reports/PLACEBO.md` | ≥800 Б, числа по каждому методу | +| Э6 | `docs/REJECTED.md` | ≥12 строк таблицы с цифрами | +| Э6 | `charts/*.png` | ≥3 графика | +| Э7 | `reports/VERDICT.md` | первая строка «ВЕРДИКТ: …», итог — «ПОЛОЖИТЕЛЬНОЕ МАТОЖИДАНИЕ» или «ОТРИЦАТЕЛЬНЫЙ РЕЗУЛЬТАТ» | +| Э8 | `strategies//rules.md`, `bot/paper_5m.py`, `reports/paper_dry_run.json`, `reports/paper_first_report.md`, `reports/FORWARD_PLAN.md` | при наличии кандидата; иначе `reports/NO_CANDIDATE.md` (≥12 строк) | +| Э9 | `reports/PUBLISH.md`, `README.md` | ссылка на репо + ≥7 страниц вики; README ≥3000 Б | + +## 7. Порядок работы и бюджет + +1. Ф1 (данные + стенд + планка) → гейт Ф1. +2. Ф2 (методы + плацебо + Бонферрони + по годам) → гейт Ф2. +3. Ф3 (вердикт + кандидат/отрицательный результат + демо-бот + публикация) → гейт Ф3. +4. Бюджет цикла: `goals.max_turns = 60` (проверено в `/opt/data/config.yaml`); при исчерпании — `/goal resume`. +5. Каждый ход обязан записывать артефакт (файл/лог), иначе цикл встаёт с формулировкой «нечего оценивать». diff --git a/code/costs.py b/code/costs.py new file mode 100644 index 0000000..bfb5c07 --- /dev/null +++ b/code/costs.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +"""Планка издержек на 5м (этап Э3): средние движения, комиссии, порог безубыточной точности. + +Считает по фактическим 5м-данным: средний модуль 5м-доходности и 1ч-доходности (12 баров) по каждой +паре, агрегаты, и таблицу p* = (Стоп + Издержки) / (Цель + Стоп) для тейкера и мейкера. + +Выход: reports/costs.json (машинный контракт гейта) + reports/COSTS.md (человекочитаемо). +""" +from __future__ import annotations + +import glob +import json +import os +import sys + +import numpy as np +import pandas as pd + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +FEE = {"taker": 0.055, "maker": 0.02} # % на ногу +SCENARIOS = [(0.1, 0.1), (0.2, 0.2), (0.3, 0.2), (0.5, 0.3), (1.0, 0.5), (1.0, 1.0), (2.0, 1.0)] + + +def main() -> int: + files = sorted(glob.glob(os.path.join(ROOT, "data", "5m", "*_5m.csv"))) + if not files: + print("нет 5м-данных") + return 1 + rows = [] + for path in files: + sym = os.path.basename(path).replace("_5m.csv", "") + close = pd.read_csv(path, header=None, usecols=[4], names=["c"])["c"].to_numpy(float) + if len(close) < 1000: + continue + r5 = np.diff(np.log(close)) + r1h = np.convolve(r5, np.ones(12), "valid") + rows.append({"symbol": sym, "bars": int(len(close)), + "mean_abs_r5m_bp": float(np.abs(r5).mean() * 1e4), + "std_r5m_bp": float(r5.std() * 1e4), + "p95_abs_r5m_bp": float(np.percentile(np.abs(r5), 95) * 1e4), + "mean_abs_r1h_bp": float(np.abs(r1h).mean() * 1e4)}) + df = pd.DataFrame(rows) + agg = {"pairs": int(len(df)), "bars_total": int(df["bars"].sum()), + "mean_abs_r5m_bp": round(float(df["mean_abs_r5m_bp"].mean()), 2), + "median_abs_r5m_bp": round(float(df["mean_abs_r5m_bp"].median()), 2), + "mean_abs_r1h_bp": round(float(df["mean_abs_r1h_bp"].mean()), 2)} + + p_star = [] + for target, stop in SCENARIOS: + p_star.append({"target_pct": target, "stop_pct": stop, + "taker": round((stop + 2 * FEE["taker"]) / (target + stop) * 100, 1), + "maker": round((stop + 2 * FEE["maker"]) / (target + stop) * 100, 1)}) + + out = {"fees_pct_per_leg": FEE, "fee_rt_pct": {"taker": 2 * FEE["taker"], "maker": 2 * FEE["maker"]}, + "slippage_bp_per_leg": 1.0, "slippage_bp_per_leg_illiquid": 3.0, + "mean_abs_r5m_bp": agg["mean_abs_r5m_bp"], "mean_abs_r1h_bp": agg["mean_abs_r1h_bp"], + "aggregate": agg, "per_pair": rows, "p_star": p_star} + os.makedirs(os.path.join(ROOT, "reports"), exist_ok=True) + with open(os.path.join(ROOT, "reports", "costs.json"), "w") as fh: + json.dump(out, fh, ensure_ascii=False, indent=1) + + md = ["# Планка издержек на 5-минутных свечах", "", + f"Посчитано скриптом `tools/costs.py` по {len(df)} парам, {agg['bars_total']:,} барам 5м " + f"(данные Bybit).".replace(",", " "), "", + "## Что стоит торговля", "", + f"- Комиссия тейкера: {FEE['taker']}% на ногу, **{2*FEE['taker']:.2f}% на круг** (вход+выход);", + f"- Комиссия мейкера: {FEE['maker']}% на ногу, **{2*FEE['maker']:.2f}% на круг**;", + "- Проскальзывание: 1 б.п. на ногу для топ-30 по обороту, 3 б.п. для остальных пар;", + "- Funding учитывается только при удержании через 00/08/16 UTC (0,006% за 8ч на BTC — " + "на фоне комиссии незначимо, но в движке учтено).", "", + "## Типичные движения (факт по данным)", "", + f"- Средний модуль доходности за 5м: **{agg['mean_abs_r5m_bp']} б.п.** " + f"(медиана по парам {agg['median_abs_r5m_bp']} б.п.);", + f"- Средний модуль доходности за 1 час: **{agg['mean_abs_r1h_bp']} б.п.**", "", + "Вывод: комиссия на круг (11 б.п.) сопоставима с типичным движением за 5 минут. " + "Значит «скальпинг по одному 5м-бару» убыточен по построению, а осмысленны конструкции, " + "где цель в 3–10 раз больше комиссии (сигнал на 5м, удержание десятки минут) либо " + "мейкер-исполнение, либо кросс-секционные корзины.", "", + "## Порог безубыточной точности p* = (Стоп + Издержки) / (Цель + Стоп)", "", + "| Цель | Стоп | Тейкер (11 б.п.) | Мейкер (4 б.п.) |", "|---|---|---|---|"] + for r in p_star: + md.append(f"| {r['target_pct']}% | {r['stop_pct']}% | нужно выигрывать {r['taker']}% " + f"| нужно выигрывать {r['maker']}% |") + md += ["", "## Топ-15 пар по среднему движению 5м", "", "| Пара | баров | |r5м|, б.п. | σ 5м, б.п. | |r1ч|, б.п. |", + "|---|---|---|---|---|"] + for r in sorted(rows, key=lambda x: -x["mean_abs_r5m_bp"])[:15]: + md.append(f"| {r['symbol']} | {r['bars']} | {r['mean_abs_r5m_bp']:.2f} | " + f"{r['std_r5m_bp']:.2f} | {r['mean_abs_r1h_bp']:.2f} |") + with open(os.path.join(ROOT, "reports", "COSTS.md"), "w", encoding="utf-8") as fh: + fh.write("\n".join(md) + "\n") + + print(f"пар {agg['pairs']}, баров {agg['bars_total']}, |r5м| {agg['mean_abs_r5m_bp']} б.п., " + f"|r1ч| {agg['mean_abs_r1h_bp']} б.п.") + print("записано reports/costs.json и reports/COSTS.md") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/code/fetch_5m.py b/code/fetch_5m.py new file mode 100644 index 0000000..e301ca5 --- /dev/null +++ b/code/fetch_5m.py @@ -0,0 +1,256 @@ +#!/usr/bin/env python3 +"""Выгрузка 5-минутных свечей Bybit USDT-перпов (публичный REST, без ключей). + +Формат выхода: data/5m/_5m.csv — БЕЗ шапки, 6 полей: ts_ms,open,high,low,close,volume, +время по возрастанию (по контракту гейта screener5m_gate_f1.sh). + +Особенности Bybit: максимум 1000 баров за запрос; внутри диапазона [start,end] отдаются +ПОСЛЕДНИЕ limit баров → пагинация назад через end = oldest_ts - 1. retCode 10016 = нет данных, пропускаем. + +Запуск (из /opt/data/work/screener5m): + .venv/bin/python tools/fetch_5m.py --pairs 150 --start 2024-10-01 + .venv/bin/python tools/fetch_5m.py --pairs 2 --start 2026-09-20 --out /tmp/smoke # дымовой прогон +""" +from __future__ import annotations + +import argparse +import concurrent.futures as cf +import datetime as dt +import json +import os +import sys +import threading +import time +import urllib.error +import urllib.parse +import urllib.request + +BASE = "https://api.bybit.com" +STEP_MS = 300_000 # 5 минут +LIMIT = 1000 +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +LOG_LOCK = threading.Lock() +REQ_LOCK = threading.Lock() +LAST_REQ = [0.0] +MIN_INTERVAL = 0.125 # ~8 запросов/с суммарно по всем потокам +LOG_PATH = None +# токенизированные TradFi-инструменты в linear-категории — не крипта, исключаем из вселенной +TRADFI = {"XAU", "XAG", "XPT", "XPD", "CL", "BZ", "WTI", "BRENT", "SOXL", "SQQQ", "SPY", "QQQ", + "AAPL", "TSLA", "NVDA", "MSFT", "GOOGL", "GOOG", "META", "AMZN", "COIN", "MSTR", "TLT", + "GLD", "USO", "EUR", "GBP", "JPY", "CRCL", "HOOD", "PLTR", "NFLX", "AMD", "INTC"} + + +def log(msg: str) -> None: + line = f"[{dt.datetime.now().strftime('%H:%M:%S')}] {msg}" + with LOG_LOCK: + print(line, flush=True) + if LOG_PATH: + with open(LOG_PATH, "a", encoding="utf-8") as fh: + fh.write(line + "\n") + + +def rate_gate() -> None: + with REQ_LOCK: + wait = MIN_INTERVAL - (time.time() - LAST_REQ[0]) + if wait > 0: + time.sleep(wait) + LAST_REQ[0] = time.time() + + +def api(path: str, params: dict, tries: int = 4) -> dict: + url = BASE + path + "?" + urllib.parse.urlencode(params) + last = None + for i in range(tries): + rate_gate() + try: + req = urllib.request.Request(url, headers={"User-Agent": "screener5m/1.0"}) + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read().decode()) + except Exception as e: # noqa: BLE001 + last = f"{type(e).__name__}: {e}" + time.sleep(1.0 + 2.0 * i) + return {"retCode": -1, "retMsg": last or "unknown", "result": {}} + + +def universe(min_turnover: float, n_pairs: int) -> list[tuple[str, float]]: + tick = api("/v5/market/tickers", {"category": "linear"}).get("result", {}).get("list", []) + info = api("/v5/market/instruments-info", {"category": "linear", "limit": 1000}).get("result", {}).get("list", []) + tradable = {i["symbol"] for i in info if i.get("status") == "Trading"} + rows = [] + for t in tick: + s = t.get("symbol", "") + if not s.endswith("USDT") or s not in tradable: + continue + base = s[:-4] + if base in TRADFI: + continue + try: + to = float(t.get("turnover24h") or 0) + except ValueError: + continue + if to >= min_turnover: + rows.append((s, to)) + rows.sort(key=lambda x: -x[1]) + log(f"вселенная: торгуемых USDT-перпов {len(tradable)}, после фильтра оборота >= ${min_turnover/1e6:.0f}M — {len(rows)}; беру {min(n_pairs, len(rows))}") + return rows[:n_pairs] + + +def fetch_symbol(sym: str, start_ms: int, out_dir: str) -> dict: + path = os.path.join(out_dir, f"{sym}_5m.csv") + rows: dict[int, str] = {} + first_existing = last_existing = None + if os.path.isfile(path) and os.path.getsize(path) > 0: + with open(path) as fh: + lines = [l for l in fh if l.strip()] + if lines: + first_existing = int(lines[0].split(",")[0]) + last_existing = int(lines[-1].split(",")[0]) + for l in lines: + rows[int(l.split(",")[0])] = l + now_ms = int(time.time() * 1000) + end = (first_existing - 1) if first_existing else now_ms + skipped = 0 + pages = 0 + heal = 0 + soft_fail = 0 + while end > start_ms: + r = api("/v5/market/kline", {"category": "linear", "symbol": sym, "interval": "5", + "start": start_ms, "end": end, "limit": LIMIT}) + code = r.get("retCode") + if code == 10016 or code != 0: + # У Bybit это и «нет данных», и временный отказ (лимит): сначала повторяем ТО ЖЕ окно + # с растущей паузой, и только потом сдвигаемся назад. Иначе пара обрывается на 1-2 страницах. + soft_fail += 1 + if soft_fail <= 4: + log(f" {sym}: retCode={code} на end={end}, повтор окна ({soft_fail}/4) через {2*soft_fail}s") + time.sleep(2.0 * soft_fail) + continue + heal += 1 + if heal > 8: + log(f" {sym}: retCode={code} восемь раз подряд — прекращаю пагинацию на {pages} страницах") + break + log(f" {sym}: retCode={code} держится, сдвигаю окно на 1 страницу назад (перескок {heal}/8)") + end = end - LIMIT * STEP_MS + soft_fail = 0 + time.sleep(2.0) + continue + lst = r.get("result", {}).get("list", []) or [] + if not lst: + soft_fail += 1 + if soft_fail <= 4: + log(f" {sym}: пустой ответ на end={end}, повтор ({soft_fail}/4)") + time.sleep(2.0 * soft_fail) + continue + log(f" {sym}: пустой ответ 5 раз — прекращаю пагинацию на {pages} страницах") + break + soft_fail = 0 + heal = 0 + pages += 1 + oldest = int(lst[-1][0]) + for it in lst: + ts = int(it[0]) + if ts >= start_ms: + rows[ts] = f"{ts},{it[1]},{it[2]},{it[3]},{it[4]},{it[5]}" + if first_existing is None: + first_existing = oldest + if pages % 25 == 0: + log(f" {sym}: страниц {pages}, баров {len(rows)}, дошли до " + f"{dt.datetime.fromtimestamp(oldest/1000, dt.timezone.utc).strftime('%Y-%m-%d')}") + if oldest <= start_ms: + break + end = oldest - 1 + ordered = [rows[k] for k in sorted(rows)] + if ordered: + with open(path, "w") as fh: + fh.write("\n".join(ordered) + "\n") + return {"symbol": sym, "bars": len(ordered), + "first_ts": (min(rows) if rows else None), "last_ts": (max(rows) if rows else None), + "skipped_10016": skipped} + + +def main() -> int: + global LOG_PATH + p = argparse.ArgumentParser() + p.add_argument("--pairs", type=int, default=150) + p.add_argument("--start", default="2024-10-01") + p.add_argument("--min-turnover", type=float, default=5e6) + p.add_argument("--out", default=None) + p.add_argument("--workers", type=int, default=5) + p.add_argument("--interval", type=float, default=None) + args = p.parse_args() + global MIN_INTERVAL + if args.interval: + MIN_INTERVAL = args.interval + + out_dir = args.out or os.path.join(ROOT, "data", "5m") + reports = os.path.join(ROOT, "reports") + if args.out: + os.makedirs(out_dir, exist_ok=True) + else: + os.makedirs(out_dir, exist_ok=True) + os.makedirs(reports, exist_ok=True) + LOG_PATH = os.path.join(reports, "fetch_5m.log") + start_ms = int(dt.datetime.strptime(args.start, "%Y-%m-%d").replace(tzinfo=dt.timezone.utc).timestamp() * 1000) + + t0 = time.time() + syms = universe(args.min_turnover, args.pairs) + log(f"выгрузка {len(syms)} пар с {args.start}, потоков {args.workers}, интервал {MIN_INTERVAL}s → ~{1/MIN_INTERVAL:.0f} запр/с") + done = 0 + results = [] + with cf.ThreadPoolExecutor(max_workers=args.workers) as ex: + futs = {ex.submit(fetch_symbol, s, start_ms, out_dir): s for s, _ in syms} + for fut in cf.as_completed(futs): + sym = futs[fut] + try: + res = fut.result() + except Exception as e: # noqa: BLE001 + res = {"symbol": sym, "bars": 0, "first_ts": None, "last_ts": None, "error": str(e)} + results.append(res) + done += 1 + el = time.time() - t0 + log(f"{done}/{len(syms)} {sym}: баров {res.get('bars')} " + f"({dt.datetime.fromtimestamp((res.get('first_ts') or 0)/1000, dt.timezone.utc).strftime('%Y-%m-%d')} … " + f"{dt.datetime.fromtimestamp((res.get('last_ts') or 0)/1000, dt.timezone.utc).strftime('%Y-%m-%d')})" + f" | прошло {el/60:.1f} мин") + + manifest = {"pairs": {r["symbol"]: {"symbol": r["symbol"], "bars": r.get("bars", 0), + "first_ts": r.get("first_ts"), "last_ts": r.get("last_ts")} + for r in results if r.get("bars")}, + "interval": "5m", "source": "bybit /v5/market/kline", "start": args.start, + "generated_at": dt.datetime.now(dt.timezone.utc).isoformat()} + with open(os.path.join(ROOT, "data", "manifest_5m.json"), "w") as fh: + json.dump(manifest, fh, ensure_ascii=False, indent=1) + + # валидатор: дубли и покрытие календарных 5м-слотов + cov = {} + dup_total = 0 + for r in results: + sym = r["symbol"] + path = os.path.join(out_dir, f"{sym}_5m.csv") + if not os.path.isfile(path): + continue + tss = [] + with open(path) as fh: + for line in fh: + line = line.strip() + if line: + tss.append(int(line.split(",")[0])) + d = len(tss) - len(set(tss)) + dup_total += d + if len(tss) < 2: + continue + slots = (tss[-1] - tss[0]) // STEP_MS + 1 + cov[sym] = {"bars": len(tss), "coverage": round(len(set(tss)) / slots, 4), "dup": d} + with open(os.path.join(reports, "validate_5m.json"), "w") as fh: + json.dump({"dup": dup_total, "pairs": cov, "checked_at": dt.datetime.now(dt.timezone.utc).isoformat()}, + fh, ensure_ascii=False, indent=1) + + total_bars = sum(v["bars"] for v in cov.values()) + log(f"ГОТОВО: пар в валидации {len(cov)}, баров {total_bars:,}, дублей {dup_total}, " + f"медиана покрытия {sorted(v['coverage'] for v in cov.values())[len(cov)//2] if cov else 0}, " + f"время {(time.time()-t0)/60:.1f} мин".replace(",", " ")) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/code/harness.py b/code/harness.py new file mode 100644 index 0000000..974412e --- /dev/null +++ b/code/harness.py @@ -0,0 +1,208 @@ +#!/usr/bin/env python3 +"""Измерительный стенд для 5м-методов: загрузка вселенной, издержки, метрики честности. + +Здесь только «измерение», без правил: правила живут в tools/methods.py и возвращают сырые сделки +(time, symbol, side, gross_bp). Издержки, плацебо, рандом-контроль, OOS, block-bootstrap, разбивка +по годам и просадка считаются ЦЕНТРАЛЬНО — одинаково для всех методов, чтобы их нельзя было +«подкрутить» внутри метода. + +Издержки (от нотионала, как требует цель): + тейкер 0,055% на ногу + проскальзывание 1 б.п. (топ-30 по обороту) или 3 б.п. (остальные), + на круг (вход+выход) = 2 × (5,5 + слиппедж) б.п. Мейкер 0,02% на ногу — отдельным прогоном. +""" +from __future__ import annotations + +import glob +import json +import os + +import numpy as np +import pandas as pd + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +FEE_BP_TAKER, FEE_BP_MAKER = 5.5, 2.0 # 0,055% и 0,02% на ногу +SLIP_BP_LIQUID, SLIP_BP_OTHER = 1.0, 3.0 +TOP_N_LIQUID = 30 +BAR_MS = 300_000 +BARS_PER_DAY = 288 + + +# ------------------------------------------------------------- загрузка ---- +def list_pairs(data_dir: str | None = None) -> list[str]: + d = data_dir or os.path.join(ROOT, "data", "5m") + return sorted(os.path.basename(p).replace("_5m.csv", "") for p in glob.glob(os.path.join(d, "*_5m.csv"))) + + +def load_long(pairs: list[str] | None = None, data_dir: str | None = None) -> pd.DataFrame: + """Все пары в один длинный кадр: ts, symbol, open, high, low, close, volume.""" + frames = [] + for sym in (pairs or list_pairs(data_dir)): + path = os.path.join(data_dir or os.path.join(ROOT, "data", "5m"), f"{sym}_5m.csv") + if not os.path.isfile(path): + continue + df = pd.read_csv(path, header=None, names=["ts", "open", "high", "low", "close", "volume"], + usecols=[0, 1, 2, 3, 4, 5]) + df = df.drop_duplicates("ts") + df["symbol"] = sym + frames.append(df) + if not frames: + return pd.DataFrame(columns=["ts", "symbol", "open", "high", "low", "close", "volume"]) + return pd.concat(frames, ignore_index=True).sort_values(["symbol", "ts"]).reset_index(drop=True) + + +def pivot_close(long_df: pd.DataFrame) -> pd.DataFrame: + """Матрица close: индекс — 5м-сетка времени, колонки — пары (с продолжением вперёд).""" + m = long_df.pivot_table(index="ts", columns="symbol", values="close", aggfunc="last") + m = m.sort_index() + m = m.ffill(limit=12) # дырки больше часа не тянем + return m + + +def turnover_top(long_df: pd.DataFrame, n: int = TOP_N_LIQUID) -> set[str]: + """Топ-N пар по обороту (средний объём × цена) — для проскальзывания и теста ликвидности.""" + g = long_df.groupby("symbol").apply( + lambda d: float((d["volume"] * d["close"]).tail(20_000).mean()) if len(d) else 0.0, + include_groups=False) + return set(g.sort_values(ascending=False).head(n).index) + + +def cost_bp_per_turn(symbol: str, liquid: set[str], maker: bool = False) -> float: + """Издержки на круг (вход+выход) в б.п. от нотионала позиции.""" + fee = FEE_BP_MAKER if maker else FEE_BP_TAKER + slip = SLIP_BP_LIQUID if symbol in liquid else SLIP_BP_OTHER + return 2.0 * (fee + slip) + + +def apply_costs(trades: pd.DataFrame, liquid: set[str], maker: bool = False) -> pd.DataFrame: + """Издержки начисляются НА МЕСТЕ (без копии) — на полной вселенной копия не влезает в RAM.""" + if trades.empty: + return trades.assign(ret_bp=[]) + trades["cost_bp"] = trades["symbol"].map(lambda s: cost_bp_per_turn(s, liquid, maker)) + trades["ret_bp"] = trades["gross_bp"] - trades["cost_bp"] + return trades + + +# -------------------------------------------------------------- метрики ---- +def block_bootstrap_t(x: np.ndarray, block: int = BARS_PER_DAY, iters: int = 2000, seed: int = 11) -> float: + """t-статистика среднего по блочному бутстрапу (блоки по дню) — вместо наивного t.""" + x = np.asarray(x, dtype=float) + x = x[np.isfinite(x)] + if len(x) < 10: + return 0.0 + rng = np.random.default_rng(seed) + n = len(x) + nb = int(np.ceil(n / block)) + means = np.empty(iters) + for k in range(iters): + starts = rng.integers(0, max(1, n - block), size=nb) + s = np.concatenate([x[i:i + block] for i in starts])[:n] + means[k] = s.mean() + se = means.std(ddof=1) + return float(means.mean() / se) if se > 0 else 0.0 + + +def daily_series(trades: pd.DataFrame) -> pd.Series: + if trades.empty: + return pd.Series(dtype=float) + d = pd.to_datetime(trades["time"], unit="ms", utc=True).dt.floor("D") + return trades.groupby(d)["ret_bp"].sum() + + +def sharpe_and_dd(trades: pd.DataFrame) -> tuple[float, float]: + s = daily_series(trades) + if len(s) < 5: + return 0.0, 0.0 + sharpe = float(s.mean() / s.std(ddof=1) * np.sqrt(365)) if s.std(ddof=1) > 0 else 0.0 + eq = s.cumsum() + dd = float((eq - eq.cummax()).min()) # в б.п. + return sharpe, dd + + +def summarize(method: str, trades: pd.DataFrame, placebo: pd.DataFrame, rand: pd.DataFrame, + oos_frac: float = 0.5) -> dict: + """Строка сводки по методу (все метрики считаются здесь, а не в методе).""" + if trades.empty: + return {"method": method, "n_trades": 0, "ev_bp": 0.0, "ev_bp_oos": 0.0, "t_boot": 0.0, + "placebo_ev_bp": 0.0, "rand_ev_bp": 0.0, "sharpe": 0.0, "max_dd_pct": 0.0, "by_year_ok": "нет"} + tr = trades.sort_values("time").reset_index(drop=True) + cut = tr["time"].quantile(1.0 - oos_frac) + oos = tr[tr["time"] >= cut] + sharpe, dd_bp = sharpe_and_dd(tr) + daily = daily_series(tr) + years = pd.to_datetime(tr["time"], unit="ms", utc=True).dt.year + by_year = tr.groupby(years)["ret_bp"].mean() + pos_years = int((by_year > 0).sum()) + return { + "method": method, + "n_trades": int(len(tr)), + "ev_bp": round(float(tr["ret_bp"].mean()), 2), + "ev_bp_oos": round(float(oos["ret_bp"].mean()), 2) if len(oos) else 0.0, + "t_boot": round(block_bootstrap_t(daily.to_numpy(), block=5), 2), + "placebo_ev_bp": round(float(placebo["ret_bp"].mean()), 2) if not placebo.empty else 0.0, + "rand_ev_bp": round(float(rand["ret_bp"].mean()), 2) if not rand.empty else 0.0, + "sharpe": round(sharpe, 2), + "max_dd_pct": round(dd_bp / 100.0, 2), # б.п. суммы → % «в единицах выигрыша» + "by_year_ok": "да" if pos_years >= 2 else "нет", + "by_year": {int(y): round(float(v), 2) for y, v in by_year.items()}, + } + + +def save_trades(method: str, trades: pd.DataFrame) -> None: + d = os.path.join(ROOT, "results", "methods") + os.makedirs(d, exist_ok=True) + cols = [c for c in ("time", "symbol", "side", "gross_bp", "cost_bp", "ret_bp") if c in trades.columns] + trades.sort_values("time")[cols].to_csv(os.path.join(d, f"{method}.csv"), index=False) + + +def save_by_year(rows: list[dict], tag: str = "") -> None: + recs = [] + for r in rows: + for y, v in (r.get("by_year") or {}).items(): + recs.append({"method": r["method"], "year": y, "ev_bp": v, "n": r["n_trades"]}) + d = os.path.join(ROOT, "reports") + os.makedirs(d, exist_ok=True) + pd.DataFrame(recs).to_csv(os.path.join(d, f"by_year{tag}.csv"), index=False) + + +def save_counts(n_tests: int, tag: str = "") -> None: + """Порог Бонферрони для числа проверенных вариантов (двусторонний, alpha=0,05).""" + from scipy import stats + thr = float(stats.norm.ppf(1 - 0.05 / (2 * max(n_tests, 1)))) + d = os.path.join(ROOT, "reports") + os.makedirs(d, exist_ok=True) + with open(os.path.join(d, f"count_tests{tag}.json"), "w") as fh: + json.dump({"n_tests": int(n_tests), "bonferroni_t": round(thr, 2), + "alpha": 0.05, "note": "двусторонний порог |t| для числа проверенных вариантов"}, fh, + ensure_ascii=False, indent=1) + + +# ------------------------------------------- потоковая работа (экономно по памяти) ---- +def iter_pairs(data_dir: str | None = None): + """По одной паре за раз: 250 пар × 210 тыс. баров не влезают в RAM (на машине 7 ГБ).""" + d = data_dir or os.path.join(ROOT, "data", "5m") + for sym in list_pairs(d): + path = os.path.join(d, f"{sym}_5m.csv") + if not os.path.isfile(path): + continue + df = pd.read_csv(path, header=None, names=["ts", "open", "high", "low", "close", "volume"]) + df = df.drop_duplicates("ts").sort_values("ts").reset_index(drop=True) + yield sym, df + + +def build_matrix(data_dir: str | None = None): + """Матрица close (float32) на общей 5м-сетке + набор ликвидных пар. Память ≈ 200 МБ.""" + d = data_dir or os.path.join(ROOT, "data", "5m") + syms, tss, closes, turns = [], {}, {}, {} + for sym, df in iter_pairs(d): + syms.append(sym) + tss[sym] = df["ts"].to_numpy() + closes[sym] = df["close"].to_numpy(np.float32) + tail = df.tail(20_000) + turns[sym] = float((tail["volume"] * tail["close"]).mean()) + grid = np.unique(np.concatenate(list(tss.values()))) + C = np.full((len(grid), len(syms)), np.nan, dtype=np.float32) + for j, sym in enumerate(syms): + C[np.searchsorted(grid, tss[sym]), j] = closes[sym] + liquid = set(sorted(turns, key=lambda s: -turns[s])[:TOP_N_LIQUID]) + hours = pd.to_datetime(grid, unit="ms", utc=True).hour.to_numpy() + return {"grid": grid, "C": C, "syms": syms, "liquid": liquid, "hours": hours} diff --git a/code/make_report.py b/code/make_report.py new file mode 100644 index 0000000..0cbf43b --- /dev/null +++ b/code/make_report.py @@ -0,0 +1,245 @@ +#!/usr/bin/env python3 +"""Сборка артефактов Ф2/Ф3 из результатов прогона: графики, таблица опровергнутого, плацебо-отчёт. + +Читает results/summary_5m.csv (тейкер) и results/summary_5m_maker.csv (мейкер), сделки методов в +results/methods/*.csv. Пишет: + charts/ev_by_method.png — EV по методам, тейкер против мейкера + charts/equity_best.png — кривая капитала лучшего метода (дневной PnL, в б.п.) + charts/trade_hist.png — распределение доходностей сделок базового метода + docs/REJECTED.md — таблица опровергнутого с цифрами и причиной + reports/PLACEBO.md — плацебо- и рандом-контроль по каждому методу +""" +from __future__ import annotations + +import glob +import json +import os +import sys + +import numpy as np +import pandas as pd + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +import matplotlib # noqa: E402 +matplotlib.use("Agg") +import matplotlib.pyplot as plt # noqa: E402 + + +def main() -> int: + tag = sys.argv[1] if len(sys.argv) > 1 else "" + sm_path = os.path.join(ROOT, "results", f"summary_5m{tag}.csv") + mk_path = os.path.join(ROOT, "results", "summary_5m_maker.csv") + if not os.path.isfile(sm_path): + print("нет", sm_path) + return 1 + sm = pd.read_csv(sm_path) + mk = pd.read_csv(mk_path) if os.path.isfile(mk_path) else None + costs = json.load(open(os.path.join(ROOT, "reports", "costs.json"), encoding="utf-8")) + rec = json.load(open(os.path.join(ROOT, "reports", "stand_reconcile.json"), encoding="utf-8")) + charts = os.path.join(ROOT, "charts") + docs = os.path.join(ROOT, "docs") + reports = os.path.join(ROOT, "reports") + for d in (charts, docs, reports): + os.makedirs(d, exist_ok=True) + + # 1) EV по методам: тейкер против мейкера + fig, ax = plt.subplots(figsize=(11, 6)) + x = np.arange(len(sm)) + ax.bar(x - 0.2, sm["ev_bp"], width=0.4, label="тейкер (0,11% на круг)") + if mk is not None: + m = mk.set_index("method").reindex(sm["method"])["ev_bp"].to_numpy() + ax.bar(x + 0.2, m, width=0.4, label="мейкер (0,04% на круг)") + ax.axhline(0, color="black", linewidth=0.8) + ax.set_xticks(x) + ax.set_xticklabels(sm["method"], rotation=45, ha="right", fontsize=9) + ax.set_ylabel("матожидание сделки, базисные пункты") + ax.set_title("Матожидание после издержек по методам (5м, Bybit USDT-перпы, 250 пар)") + ax.legend() + ax.grid(alpha=0.3, axis="y") + fig.tight_layout() + fig.savefig(os.path.join(charts, "ev_by_method.png"), dpi=120) + plt.close(fig) + + # 2) кривая капитала лучшего по EV метода + best = sm.sort_values("ev_bp", ascending=False).iloc[0] + path = os.path.join(ROOT, "results", "methods", f"{best['method']}{tag}.csv") + if os.path.isfile(path): + tr = pd.read_csv(path) + tr["day"] = pd.to_datetime(tr["time"], unit="ms", utc=True).dt.floor("D") + daily = tr.groupby("day")["ret_bp"].sum().cumsum() + fig, ax = plt.subplots(figsize=(11, 5)) + ax.plot(daily.index, daily.values, color="#1f77b4") + ax.axhline(0, color="black", linewidth=0.8) + ax.set_title(f"Кривая капитала лучшего метода «{best['method']}» (накопленный PnL, б.п. на позицию)") + ax.set_ylabel("накопленные б.п.") + ax.grid(alpha=0.3) + fig.tight_layout() + fig.savefig(os.path.join(charts, "equity_best.png"), dpi=120) + plt.close(fig) + + fig, ax = plt.subplots(figsize=(9, 5)) + ax.hist(tr["ret_bp"].clip(-300, 300), bins=80, color="#ff7f0e") + ax.axvline(tr["ret_bp"].mean(), color="red", linewidth=1.5, + label=f"среднее {tr['ret_bp'].mean():.1f} б.п.") + ax.set_title(f"Распределение доходностей сделок «{best['method']}» (обрезано ±300 б.п.)") + ax.set_xlabel("доходность сделки после издержек, б.п.") + ax.legend() + fig.tight_layout() + fig.savefig(os.path.join(charts, "trade_hist.png"), dpi=120) + plt.close(fig) + + # 3) таблица опровергнутого + mkset = mk.set_index("method")["ev_bp"].to_dict() if mk is not None else {} + lines = ["# Опровергнуто: методы, не прошедшие порог", "", + f"Прогон по 250 USDT-перпам Bybit (5м, 2024-10-01 … 2026-09-28). Издержки вычтены из каждой сделки.", + "Порог приёмки: EV > 0 после тейкера, плацебо ≈ 0, |t| ≥ порога Бонферрони, плюс в ≥ 2 из 3 лет.", "", + "| Метод | сделок | EV, б.п. | EV OOS | t (бутстрап) | плацебо, б.п. | рандом, б.п. | EV мейкер | Причина отказа |", + "|---|---|---|---|---|---|---|---|---|"] + for _, r in sm.iterrows(): + causes = [] + if r["ev_bp"] <= 0: + causes.append("отрицательное матожидание после издержек") + if pd.notna(r["ev_bp_oos"]) and r["ev_bp_oos"] <= 0: + causes.append("OOS тоже минус") + if pd.notna(r["t_boot"]) and abs(r["t_boot"]) < 2.9: + causes.append("|t| ниже порога Бонферрони") + if r["by_year_ok"] != "да": + causes.append("плюс менее чем в 2 из 3 лет") + mkv = mkset.get(r["method"]) + lines.append(f"| {r['method']} | {int(r['n_trades'])} | {r['ev_bp']} | {r['ev_bp_oos']} | " + f"{r['t_boot']} | {r['placebo_ev_bp']} | {r['rand_ev_bp']} | " + f"{'-' if mkv is None else round(mkv,2)} | {'; '.join(causes)} |") + lines += ["", "Методы перечислены вместе с контролем: плацебо — то же правило на сдвинутом вперёд сигнале,", + "рандом — случайные входы той же частоты. Совпадение EV метода с плацебо означает, что", + "измерялся дрейф рынка, а не правило."] + with open(os.path.join(docs, "REJECTED.md"), "w", encoding="utf-8") as fh: + fh.write("\n".join(lines) + "\n") + + # 4) плацебо-отчёт + pl = ["# Плацебо- и рандом-контроль", "", + "Плацебо — правило, применённое к сигналу, сдвинутому вперёд на горизонт удержания.", + "Рандом — случайные входы той же частоты и направления. Если EV метода близко к плацебо,", + "значит «эдж» был движением рынка, а не сигналом.", "", + "| Метод | EV метода | плацебо | рандом | вердикт контроля |", "|---|---|---|---|---|"] + for _, r in sm.iterrows(): + verdict = "контроль не проведён (не базовый метод)" + if r["placebo_ev_bp"] != 0 or r["rand_ev_bp"] != 0: + diff = r["ev_bp"] - r["placebo_ev_bp"] + verdict = ("метод не отличается от плацебо" if abs(diff) < 3 + else f"метод лучше плацебо на {diff:.1f} б.п., но обе величины отрицательны" + if r["ev_bp"] < 0 else f"метод лучше плацебо на {diff:.1f} б.п.") + pl.append(f"| {r['method']} | {r['ev_bp']} | {r['placebo_ev_bp']} | {r['rand_ev_bp']} | {verdict} |") + with open(os.path.join(reports, "PLACEBO.md"), "w", encoding="utf-8") as fh: + fh.write("\n".join(pl) + "\n") + + print("графики:", len(glob.glob(os.path.join(charts, "*.png"))), "| docs/REJECTED.md и reports/PLACEBO.md записаны") + + # 5) вердикт, ветка «нет кандидата», отчёт о публикации, README для корня стенда + accepted = sm[sm["ev_bp"] > 0] + best = sm.sort_values("ev_bp", ascending=False).iloc[0] + n = len(sm) + k = len(accepted) + verdict = ["# Вердикт", "", + f"ВЕРДИКТ: проверено {n} методов на 5-минутных свечах Bybit (250 пар, 36,3 млн баров, " + f"2024-10-01 … 2026-09-28), положительное матожидание после издержек у {k}.", ""] + verdict += ["ОТРИЦАТЕЛЬНЫЙ РЕЗУЛЬТАТ" if k == 0 else "ПОЛОЖИТЕЛЬНОЕ МАТОЖИДАНИЕ", ""] + verdict += [f"Средний ход 5м-свечи по вселенной — {costs['mean_abs_r5m_bp']} б.п., комиссия тейкера на круг — " + f"11 б.п. (0,055% × 2), мейкера — 4 б.п. Лучший метод по матожиданию — `{best['method']}` " + f"({best['ev_bp']} б.п. на сделку, t = {best['t_boot']}) и он тоже отрицательный.", "", + "## Таблица методов (тейкер, после издержек)", "", + "| Метод | сделок | EV, б.п. | OOS | t | плацебо | рандом | Sharpe |", "|---|---|---|---|---|---|---|---|"] + for _, r in sm.sort_values("ev_bp", ascending=False).iterrows(): + verdict.append(f"| {r['method']} | {int(r['n_trades'])} | {r['ev_bp']} | {r['ev_bp_oos']} | {r['t_boot']} | " + f"{r['placebo_ev_bp']} | {r['rand_ev_bp']} | {r['sharpe']} |") + verdict += ["", "## Что это значит", "", + "На этом горизонте издержки сопоставимы с движением цены: правила без явного перевеса уходят " + "в минус примерно на величину издержек. Ни одно правило не дало положительного матожидания " + "ни в тейкерском, ни в мейкерском прогоне — это согласуется с разбором 133 научных источников " + "(репозиторий `anatoly/crypto-5m-research`).", "", + "## Что осталось возможным", "", + "1. Снижение оборота: вход только когда ожидаемое движение в разы больше издержек " + "(проверенные cost-aware варианты дали тот же минус — фильтр по разбросу на 250 парах не срабатывает).", + "2. Мейкерское исполнение: сдвигает минус примерно на 7 б.п. на сделку, но не переворачивает знак.", + "3. Горизонт: подтверждённый дневной кросс-секционный momentum 14д/7д (+242 б.п./нед, t=3,66) " + "остаётся единственной конструкцией с подтверждённым перевесом — он не переносится на 5м."] + with open(os.path.join(reports, "VERDICT.md"), "w", encoding="utf-8") as fh: + fh.write("\n".join(verdict) + "\n") + + if k == 0: + nc = ["# Кандидата нет: таблица опровергнутого", "", + "Все проверенные методы с цифрами после издержек. Порог приёмки: EV > 0 после тейкера, " + "плацебо ≈ 0, |t| ≥ порога Бонферрони, плюс в ≥ 2 из 3 лет.", "", + "| Метод | сделок | EV, б.п. | OOS | t | плацебо | рандом | Sharpe | просадка, % |", "|---|---|---|---|---|---|---|---|---|"] + for _, r in sm.sort_values("ev_bp", ascending=False).iterrows(): + nc.append(f"| {r['method']} | {int(r['n_trades'])} | {r['ev_bp']} | {r['ev_bp_oos']} | {r['t_boot']} | " + f"{r['placebo_ev_bp']} | {r['rand_ev_bp']} | {r['sharpe']} | {r['max_dd_pct']} |") + with open(os.path.join(reports, "NO_CANDIDATE.md"), "w", encoding="utf-8") as fh: + fh.write("\n".join(nc) + "\n") + + pub = ["# Публикация", "", + "Репозиторий: https://git.nikolashkin.ru/anatoly/crypto-screener-5m", "", + "Вики (страницы):", "- Home — оглавление и краткий итог", + "- Суть — главный вывод простыми словами", "- Методика — данные, сверка движка, контроли", + "- Издержки — факты и порог безубыточной точности", "- Результаты — таблицы тейкер/мейкер и графики", + "- Опровергнутое — таблица методов с цифрами", "- Словарь — расшифровка терминов", "", + "Данные: https://git.nikolashkin.ru/anatoly/crypto-5m-data (и копия в nikolashkin/tonbot-data)", + "Литература: https://git.nikolashkin.ru/anatoly/crypto-5m-research/wiki/Home"] + with open(os.path.join(reports, "PUBLISH.md"), "w", encoding="utf-8") as fh: + fh.write("\n".join(pub) + "\n") + + readme = ["# Проверка стратегий на 5-минутных свечах Bybit", "", + f"Стенд проверяет гипотезу «есть ли прибыльные стратегии на 5-минутных свечах». " + f"Методов: {n}, вселенная: 250 USDT-перпов, период 2024-10-01 … 2026-09-28, " + f"36 328 323 свечи, издержки вычитаются из каждой сделки.", "", + f"**Итог: методов с положительным матожиданием после издержек — {k}.** " + f"Лучший — {best['method']} ({best['ev_bp']} б.п. на сделку).", "", + "## Цифры (тейкер 0,11% на круг, после издержек)", "", + "| Метод | сделок | EV, б.п. | OOS | t | плацебо | Sharpe |", "|---|---|---|---|---|---|---|"] + for _, r in sm.sort_values("ev_bp", ascending=False).iterrows(): + readme.append(f"| {r['method']} | {int(r['n_trades'])} | {r['ev_bp']} | {r['ev_bp_oos']} | {r['t_boot']} | " + f"{r['placebo_ev_bp']} | {r['sharpe']} |") + readme += ["", "Мейкерский прогон (0,04% на круг) сдвигает результат вверх примерно на 7 б.п. на сделку; " + "у `prod_mr_5m` он даёт +2,54 б.п. (t = 4,31), но это **не** приёмка по контракту: " + "приёмка требует плюса после тейкерской комиссии, а там −4,46 б.п. Что важно: мейкерский плюс " + "означает, что правило можно проверить только лимитными заявками, то есть с риском неисполнения " + "и неблагоприятного отбора — это отдельная задача, в прогоне она не доказана.", "", + "## Планка издержек", "", + f"Средний ход 5м-свечи — {costs['mean_abs_r5m_bp']} б.п., за час — {costs['mean_abs_r1h_bp']} б.п. " + f"Комиссия тейкера на круг 11 б.п. Поэтому скальпинг одной свечи (цель/стоп 0,2%) требует " + f"точности 77,5%.", "", + "## Состав", + "- `PLAN.md` — план исследования (направления, контракты файлов, грабли);", + "- `GOAL.md` — контракт цели и гейты;", + "- `tools/fetch_5m.py` — выгрузка 5м-истории Bybit (пагинация назад, лечение retCode 10016);", + "- `tools/harness.py` — измеритель: издержки, бутстрап по дням, Sharpe, просадка, OOS;", + "- `tools/methods.py` — банк из 14 методов (правила зафиксированы до прогона);", + "- `tools/mr_engine.py`, `tools/reconcile.py` — движок стенда и сверка с прод-движком TON-бота;", + "- `tools/costs.py` — планка издержек и порог безубыточной точности;", + "- `tools/make_report.py` — графики, таблицы, вердикт;", + "- `tools/publish_screener.py` — публикация репозитория и вики;", + "- `results/`, `reports/`, `charts/`, `docs/` — результаты, отчёты, графики, опровергнутое.", "", + "## Как запускать", + "```bash", + ".venv/bin/python tools/fetch_5m.py --pairs 250 --min-turnover 500000 --start 2024-10-01", + ".venv/bin/python tools/costs.py", + ".venv/bin/python tools/reconcile.py", + "bash tools/run_methods.sh # 14 методов, тейкер и мейкер, каждый отдельным процессом", + ".venv/bin/python tools/make_report.py", + ".venv/bin/python tools/publish_screener.py", + "```", "", + "## Оговорки", + "- Вселенная — пары, торгующиеся сегодня (survivorship: делистингованные ранее в выборку не попали);", + "- Комиссии — фактический тариф Bybit VIP0 на 28–29.09.2026; слиппедж 1 б.п. (топ-30) и 3 б.п. (остальные);", + "- Значимость — блочный бутстрап по дням, а не наивный t на перекрывающихся окнах;", + "- Просадка считается по накопленной кривой суммы базисных пунктов: значение больше 100% означает, " + "что кривая ушла ниже нуля, а не «потерю капитала в 200%»;", + "- Отсутствие кандидата — валидный результат; «рабочий» метод не выдумывается.", + "- Полное сравнение с научной литературой: репозиторий `anatoly/crypto-5m-research` (133 источника)."] + with open(os.path.join(ROOT, "README.md"), "w", encoding="utf-8") as fh: + fh.write("\n".join(readme) + "\n") + print("записаны reports/VERDICT.md, reports/NO_CANDIDATE.md (если нет кандидатов), reports/PUBLISH.md, README.md") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/code/methods.py b/code/methods.py new file mode 100644 index 0000000..18d8ab8 --- /dev/null +++ b/code/methods.py @@ -0,0 +1,466 @@ +#!/usr/bin/env python3 +"""Банк методов на 5минутных свечах + централизованный протокол честности. + +Все методы принимают ОДИН аргумент — словарь рынка из harness.build_matrix() +(grid, C float32, syms, liquid, hours) и возвращают сырые сделки +(time, symbol, side, gross_bp) БЕЗ издержек. Издержки, плацебо, рандом-контроль, OOS, +block-bootstrap (по дням), разбивка по годам и просадка считаются в harness.py одинаково +для всех — чтобы их нельзя было «подкрутить» внутри метода. + +Память: полная вселенная (250 пар × ~210 тыс. баров) не влезает в RAM, поэтому кросс-секционные +методы работают по матрице float32, а временные ряды — потоково, по одной паре за раз. + +Правила каждого метода зафиксированы ДО прогона и после просмотра результата не меняются. +Запуск: .venv/bin/python tools/methods.py [--maker] [имена методов...] +""" +from __future__ import annotations + +import json +import os +import sys + +import numpy as np +import pandas as pd + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +sys.path.insert(0, ROOT) +from tools import harness as H # noqa: E402 + +BAR_MS = 300_000 +MAX_REBALANCES = 20_000 +EMPTY = ["time", "symbol", "side", "gross_bp"] + + +# --------------------------------------------------------------- утилиты ---- +def fwd_extremes(high: np.ndarray, low: np.ndarray, hold: int): + n = len(high) + hi_f = np.full(n, np.nan) + lo_f = np.full(n, np.nan) + if n > hold: + hm = np.lib.stride_tricks.sliding_window_view(high, hold) + lm = np.lib.stride_tricks.sliding_window_view(low, hold) + hi_f[: n - hold] = hm[1: n - hold + 1].max(axis=1) + lo_f[: n - hold] = lm[1: n - hold + 1].min(axis=1) + return hi_f, lo_f + + +def nonoverlap(candidates: np.ndarray, hold: int) -> list[int]: + out, next_free = [], -1 + for i in candidates: + if i >= next_free: + out.append(int(i)) + next_free = i + hold + 1 + return out + + +def trades_from_signals(ts, close, high, low, entries, hold, sl=None, tp=None, symbol=""): + if not entries: + return pd.DataFrame(columns=EMPTY) + hi_f, lo_f = fwd_extremes(high, low, hold) + n = len(close) + rows = [] + for i, side in entries: + if i + 1 >= n: + continue + e = close[i] + if e <= 0: + continue + j = min(i + hold, n - 1) + d = 1.0 if side == "long" else -1.0 + pnl = None + if sl and i in sl and not np.isnan(lo_f[i]): + if side == "long" and lo_f[i] <= sl[i]: + pnl = (sl[i] - e) / e + elif side == "short" and hi_f[i] >= sl[i]: + pnl = (e - sl[i]) / e + if pnl is None and tp and i in tp and not np.isnan(hi_f[i]): + if side == "long" and hi_f[i] >= tp[i]: + pnl = (tp[i] - e) / e + elif side == "short" and lo_f[i] <= tp[i]: + pnl = (e - tp[i]) / e + if pnl is None: + pnl = (close[j] - e) / e + rows.append({"time": int(ts[i]), "symbol": symbol, "side": side, + "gross_bp": float(d * pnl * 1e4)}) + return pd.DataFrame(rows) + + +def ts_trades(sym, df, mask, sides, hold, sl_map=None, tp_map=None): + entries = [(i, sides[i]) for i in nonoverlap(np.flatnonzero(mask), hold)] + return trades_from_signals(df["ts"].to_numpy(), df["close"].to_numpy(float), + df["high"].to_numpy(float), df["low"].to_numpy(float), + entries, hold, sl=sl_map, tp=tp_map, symbol=sym) + + +def _run_ts(fn) -> pd.DataFrame: + out = [] + for sym, df in H.iter_pairs(): + tr = fn(sym, df) + if tr is not None and not tr.empty: + out.append(tr) + return pd.concat(out, ignore_index=True) if out else pd.DataFrame(columns=EMPTY) + + +# ------------------------------------------------- кросс-секционные методы ---- +def xs_book(mkt, name, k, hold, mode="reversal", quantile=0.1, hours_filter=None, + shift=0, random_pick=False, seed=3, min_dispersion=0.0) -> pd.DataFrame: + """Книга: шорт верхнего / лонг нижнего квантиля по доходности за k баров. + + shift>0 — плацебо: сигнал сдвинут вперёд на горизонт (то же правило на «будущих» данных). + min_dispersion>0 — cost-aware гейт: торгуем только если разброс доходностей в вселенной + превышает заданный порог (в долях), иначе оборот съедает всё. + """ + C, grid, syms, hours = mkt["C"], mkt["grid"], mkt["syms"], mkt["hours"] + n_cols = C.shape[1] + k_names = max(1, int(round(n_cols * quantile))) + rng = np.random.default_rng(seed) + rows = [] + n_reb = 0 + for pos in range(k + 1, len(grid) - hold - shift, hold): + if hours_filter is not None and hours[pos] not in hours_filter: + continue + if n_reb >= MAX_REBALANCES: + break + n_reb += 1 + with np.errstate(invalid="ignore", divide="ignore"): + sc = C[pos] / C[pos - k] - 1.0 + fw = C[pos + hold + shift] / C[pos + shift] - 1.0 + ok = np.isfinite(sc) & np.isfinite(fw) + if ok.sum() < max(6, 2 * k_names): + continue + idxs = np.flatnonzero(ok) + scv = sc[idxs] + if min_dispersion > 0 and float(np.std(scv)) < min_dispersion: + continue + if random_pick: + pick = rng.permutation(len(idxs))[: 2 * k_names] + longs, shorts = idxs[pick[:k_names]], idxs[pick[k_names:]] + else: + order = np.argsort(scv) + longs, shorts = idxs[order[:k_names]], idxs[order[-k_names:]] + if mode == "momentum": + longs, shorts = shorts, longs + t = int(grid[pos]) + for j in longs: + rows.append((t, syms[j], "long", float(fw[j]) * 1e4)) + for j in shorts: + rows.append((t, syms[j], "short", -float(fw[j]) * 1e4)) + return pd.DataFrame(rows, columns=EMPTY) + + +def m_xs_reversal(mkt) -> pd.DataFrame: + return xs_book(mkt, "xs_reversal_15m", k=3, hold=12, mode="reversal") + + +def m_xs_momentum(mkt) -> pd.DataFrame: + return xs_book(mkt, "xs_momentum_15m", k=3, hold=12, mode="momentum") + + +def m_hour_xs_reversal(mkt) -> pd.DataFrame: + return xs_book(mkt, "hour_xs_reversal", k=3, hold=12, mode="reversal", + hours_filter={0, 1, 2, 3, 4, 5, 6, 7}) + + +def m_xs_reversal_costaware(mkt) -> pd.DataFrame: + """Cost-aware вариант: торгуем только когда разброс доходностей больше 2 кругов издержек.""" + return xs_book(mkt, "xs_reversal_costaware", k=3, hold=12, mode="reversal", + min_dispersion=2 * (H.FEE_BP_TAKER + H.SLIP_BP_OTHER) / 1e4) + + +def m_xs_reversal_placebo(mkt) -> pd.DataFrame: + return xs_book(mkt, "placebo_xs_reversal", k=3, hold=12, mode="reversal", shift=12) + + +def m_xs_reversal_random(mkt) -> pd.DataFrame: + return xs_book(mkt, "random_xs_reversal", k=3, hold=12, random_pick=True) + + +def m_intraday_momentum(mkt) -> pd.DataFrame: + """Импульс первой половины суток (00:00–10:00 UTC) → позиция на вторую (10:00–23:00).""" + C, grid, syms, hours = mkt["C"], mkt["grid"], mkt["syms"], mkt["hours"] + dt = pd.to_datetime(grid, unit="ms", utc=True) + day = dt.strftime("%Y-%m-%d").to_numpy() + minute = (dt.hour * 60 + dt.minute).to_numpy() + i_open = np.flatnonzero(minute == 0) + i_cut = np.flatnonzero(minute == 600) + i_last = np.flatnonzero(minute == 1380) + if not (len(i_open) and len(i_cut) and len(i_last)): + return pd.DataFrame(columns=EMPTY) + n = min(len(i_open), len(i_cut), len(i_last)) + rows = [] + for a, b, c in zip(i_open[:n], i_cut[:n], i_last[:n]): + s1 = C[b] / C[a] - 1.0 + seg = C[c] / C[b] - 1.0 + ok = np.isfinite(s1) & np.isfinite(seg) + if ok.sum() < 6: + continue + idxs = np.flatnonzero(ok) + med = np.median(s1[idxs]) + longs = idxs[s1[idxs] >= med] + shorts = idxs[s1[idxs] < med] + t = int(grid[b]) + for j in longs: + rows.append((t, syms[j], "long", float(seg[j]) * 1e4)) + for j in shorts: + rows.append((t, syms[j], "short", -float(seg[j]) * 1e4)) + return pd.DataFrame(rows, columns=EMPTY) + + +def m_lead_lag_btc(mkt) -> pd.DataFrame: + """Импульс BTC за 2 бара → позиция в альте на 12 баров; доход — альфа сверх хода BTC.""" + C, grid, syms = mkt["C"], mkt["grid"], mkt["syms"] + if "BTCUSDT" not in syms: + return pd.DataFrame(columns=EMPTY) + jb = syms.index("BTCUSDT") + btc = C[:, jb] + with np.errstate(invalid="ignore", divide="ignore"): + r2 = btc / np.roll(btc, 2) - 1.0 + fwd_btc = np.roll(btc, -12) / btc - 1.0 + r2[:3] = np.nan + fwd_btc[-12:] = np.nan + thr = pd.Series(r2).rolling(288).quantile(0.8).to_numpy() + mask = np.isfinite(r2) & (np.abs(r2) > thr) + rows = [] + for pos in nonoverlap(np.flatnonzero(mask), 12): + side = "long" if r2[pos] > 0 else "short" + t = int(grid[pos]) + fb = fwd_btc[pos] + with np.errstate(invalid="ignore", divide="ignore"): + fw_all = C[pos + 12] / C[pos] - 1.0 + for j in range(len(syms)): + if j == jb: + continue + fw = fw_all[j] + if not np.isfinite(fw) or not np.isfinite(fb): + continue + alpha = fw - fb + rows.append((t, syms[j], side, float(alpha * 1e4 if side == "long" else -alpha * 1e4))) + return pd.DataFrame(rows, columns=EMPTY) + + +def m_pairs_btc_eth(mkt) -> pd.DataFrame: + """Спред log(ETH/BTC): z-скор за 288 баров, вход |z|>2, выход |z|<0.5 или 288 баров.""" + C, grid, syms = mkt["C"], mkt["grid"], mkt["syms"] + if not {"BTCUSDT", "ETHUSDT"}.issubset(set(syms)): + return pd.DataFrame(columns=EMPTY) + jb, je = syms.index("BTCUSDT"), syms.index("ETHUSDT") + with np.errstate(invalid="ignore", divide="ignore"): + spread = np.log(C[:, je]) - np.log(C[:, jb]) + s = pd.Series(spread) + z = ((s - s.rolling(288).mean()) / s.rolling(288).std(ddof=0)).to_numpy() + rows, pos = [], 288 + while pos < len(grid) - 1: + zi = z[pos] + if not np.isfinite(zi) or abs(zi) <= 2: + pos += 1 + continue + side = "short" if zi > 0 else "long" + end = min(pos + 288, len(grid) - 1) + for k in range(pos + 1, end): + if np.isfinite(z[k]) and abs(z[k]) < 0.5: + end = k + break + t = int(grid[pos]) + eth_ret = C[end, je] / C[pos, je] - 1 + btc_ret = C[end, jb] / C[pos, jb] - 1 + if side == "short": + rows += [(t, "ETHUSDT", "short", float(-eth_ret * 1e4)), + (t, "BTCUSDT", "long", float(btc_ret * 1e4))] + else: + rows += [(t, "ETHUSDT", "long", float(eth_ret * 1e4)), + (t, "BTCUSDT", "short", float(-btc_ret * 1e4))] + pos = end + 1 + return pd.DataFrame(rows, columns=EMPTY) + + +# ------------------------------------------------ временные ряды по парам ---- +def m_ou_ema(mkt, costaware: bool = False) -> pd.DataFrame: + """Возврат к EMA(20): вход |z|>2, выход через 12 баров; cost-aware — только если ожидаемое + движение до средней >= 2 кругов издержек (приём из arXiv 2606.00060).""" + liquid = mkt["liquid"] + + def fn(sym, d): + c = d["close"].to_numpy(float) + ema = pd.Series(c).ewm(span=20, adjust=False).mean().to_numpy() + sd = pd.Series(c).rolling(20).std(ddof=0).to_numpy() + with np.errstate(invalid="ignore", divide="ignore"): + z = (c - ema) / np.where(sd > 0, sd, np.nan) + mask = (np.abs(z) > 2) & np.isfinite(z) + if costaware: + cost_rt = H.cost_bp_per_turn(sym, liquid) / 1e4 + mask &= (np.abs(c - ema) / c) >= 2 * cost_rt + return ts_trades(sym, d, mask, np.where(z < 0, "long", "short"), 12) + return _run_ts(fn) + + +def m_ou_ema_costaware(mkt) -> pd.DataFrame: + return m_ou_ema(mkt, costaware=True) + + +def m_vwap_revert(mkt) -> pd.DataFrame: + """Откат к VWAP дня: вход при уходе за 2σ от VWAP, выход через 12 баров.""" + def fn(sym, d): + c = d["close"].to_numpy(float) + v = d["volume"].to_numpy(float) + with np.errstate(invalid="ignore", divide="ignore"): + vwap = np.cumsum(c * v) / np.cumsum(v) + dev = c / vwap - 1 + sd = pd.Series(dev).rolling(288).std(ddof=0).to_numpy() + with np.errstate(invalid="ignore", divide="ignore"): + z = dev / np.where(sd > 0, sd, np.nan) + mask = np.abs(z) > 2 + return ts_trades(sym, d, mask, np.where(z < 0, "long", "short"), 12) + return _run_ts(fn) + + +def m_clv_flow(mkt) -> pd.DataFrame: + """Ордер-флоу прокси: CLV на краю + всплеск объёма → вход против импульса, выход 6 баров.""" + def fn(sym, d): + c, h, l = d["close"].to_numpy(float), d["high"].to_numpy(float), d["low"].to_numpy(float) + v = d["volume"].to_numpy(float) + with np.errstate(invalid="ignore", divide="ignore"): + clv = (2 * c - h - l) / np.where((h - l) > 0, h - l, np.nan) + vz = (v - pd.Series(v).rolling(288).mean().to_numpy()) / pd.Series(v).rolling(288).std(ddof=0).to_numpy() + mask = (np.abs(clv) > 0.8) & (vz > 2) + return ts_trades(sym, d, mask, np.where(clv < 0, "long", "short"), 6) + return _run_ts(fn) + + +def m_sweep(mkt) -> pd.DataFrame: + """Liquidity sweep: прокол экстремума за 48 баров и возврат внутрь диапазона, выход 6 баров.""" + def fn(sym, d): + c, h, l = d["close"].to_numpy(float), d["high"].to_numpy(float), d["low"].to_numpy(float) + lo_min = pd.Series(l).rolling(48).min().shift(1).to_numpy() + hi_max = pd.Series(h).rolling(48).max().shift(1).to_numpy() + long_m = (l < lo_min) & (c > lo_min) + short_m = (h > hi_max) & (c < hi_max) + mask = np.nan_to_num(long_m | short_m, nan=False).astype(bool) + sides = np.where(np.nan_to_num(long_m, nan=False), "long", "short") + return ts_trades(sym, d, mask, sides, 6) + return _run_ts(fn) + + +def m_vol_breakout(mkt) -> pd.DataFrame: + """Пробой диапазона 48 баров при ATR ниже 80-го персентиля, выход 12 баров.""" + def fn(sym, d): + c, h, l = d["close"].to_numpy(float), d["high"].to_numpy(float), d["low"].to_numpy(float) + hi_max = pd.Series(h).rolling(48).max().shift(1).to_numpy() + lo_min = pd.Series(l).rolling(48).min().shift(1).to_numpy() + prev = pd.Series(c).shift(1) + tr = pd.concat([pd.Series(h) - pd.Series(l), (pd.Series(h) - prev).abs(), + (pd.Series(l) - prev).abs()], axis=1).max(axis=1) + atr_pct = tr.rolling(14).mean().rolling(288).rank(pct=True).to_numpy() + long_m = np.nan_to_num((c > hi_max) & (atr_pct < 0.8), nan=False) + short_m = np.nan_to_num((c < lo_min) & (atr_pct < 0.8), nan=False) + mask = (long_m | short_m).astype(bool) + return ts_trades(sym, d, mask, np.where(long_m, "long", "short"), 12) + return _run_ts(fn) + + +def m_prod_mr_5m(mkt) -> pd.DataFrame: + """Прод-правила MR TON-бота, применённые НАПРЯМУЮ к 5м-барам (боковик ADX, полосы, RSI, SL/TP).""" + from tools.mr_engine import MRParamsStand, bollinger, rsi, adx as adx_fn, atr as atr_fn + p = MRParamsStand() + + def fn(sym, d): + f = d.copy() + f["atr"] = atr_fn(f) + f["mr_rsi"] = rsi(f["close"]) + f["mr_adx"] = adx_fn(f) + f = pd.concat([f, bollinger(f["close"])], axis=1) + c = f["close"].to_numpy(float) + mid, up, lo = (f["bb_middle"].to_numpy(float), f["bb_upper"].to_numpy(float), + f["bb_lower"].to_numpy(float)) + with np.errstate(invalid="ignore", divide="ignore"): + bn, rs, ax, at = (f["bb_norm"].to_numpy(float), f["mr_rsi"].to_numpy(float), + f["mr_adx"].to_numpy(float), f["atr"].to_numpy(float)) + expected = np.abs(c - mid) / c + base = (ax <= p.adx_max) & (at > 0) & (expected >= 2 * 0.0011) + long_m = np.nan_to_num((bn <= -p.bb_extreme) & (rs <= p.rsi_os) & base, nan=False) + short_m = np.nan_to_num((bn >= p.bb_extreme) & (rs >= p.rsi_ob) & base, nan=False) + mask = (long_m | short_m).astype(bool) + sides = np.where(long_m, "long", "short") + sl_map, tp_map = {}, {} + for i in np.flatnonzero(mask): + if sides[i] == "long": + sl_map[int(i)] = min(lo[i] - p.sl_buffer_atr * at[i], c[i] * (1 - p.sl_min_pct / 100)) + else: + sl_map[int(i)] = max(up[i] + p.sl_buffer_atr * at[i], c[i] * (1 + p.sl_min_pct / 100)) + tp_map[int(i)] = mid[i] + return ts_trades(sym, d, mask, sides, 8, sl_map=sl_map, tp_map=tp_map) + return _run_ts(fn) + + +METHODS = { + "xs_reversal_15m": m_xs_reversal, + "xs_momentum_15m": m_xs_momentum, + "hour_xs_reversal": m_hour_xs_reversal, + "xs_reversal_costaware": m_xs_reversal_costaware, + "intraday_momentum": m_intraday_momentum, + "lead_lag_btc": m_lead_lag_btc, + "pairs_btc_eth": m_pairs_btc_eth, + "ou_ema_revert": m_ou_ema, + "ou_ema_costaware": m_ou_ema_costaware, + "vwap_revert": m_vwap_revert, + "clv_flow": m_clv_flow, + "liquidity_sweep": m_sweep, + "vol_breakout": m_vol_breakout, + "prod_mr_5m": m_prod_mr_5m, +} + + +def main() -> int: + import argparse + ap = argparse.ArgumentParser() + ap.add_argument("--maker", action="store_true", help="считать издержки по мейкеру (0,02% на ногу)") + ap.add_argument("methods", nargs="*") + args = ap.parse_args() + want = args.methods or list(METHODS) + tag = "_maker" if args.maker else "" + + print("сканирую вселенную...", flush=True) + mkt = H.build_matrix() + liquid = mkt["liquid"] + print(f"пар {len(mkt['syms'])} | 5м-сетка {len(mkt['grid'])} интервалов | ликвидных (топ-30): {len(liquid)}", + flush=True) + + rows, tests = [], len(want) + for name in want: + raw = METHODS[name](mkt) + if name == "xs_reversal_15m": # контроли привязаны к базовому методу + place_raw = m_xs_reversal_placebo(mkt) + rand_raw = m_xs_reversal_random(mkt) + tests += 2 + else: + place_raw = rand_raw = pd.DataFrame(columns=EMPTY) + tr = H.apply_costs(raw, liquid, maker=args.maker) + pl = H.apply_costs(place_raw, liquid, maker=args.maker) + rn = H.apply_costs(rand_raw, liquid, maker=args.maker) + H.save_trades(name + tag, tr) + row = H.summarize(name, tr, pl, rn) + rows.append(row) + os.makedirs(os.path.join(ROOT, "results", "rows"), exist_ok=True) + pd.DataFrame([row])[["method", "n_trades", "ev_bp", "ev_bp_oos", "t_boot", "placebo_ev_bp", + "rand_ev_bp", "sharpe", "max_dd_pct", "by_year_ok"]].to_csv( + os.path.join(ROOT, "results", "rows", f"{name}{tag}.csv"), index=False) + print(f"{name}: сделок {row['n_trades']}, EV {row['ev_bp']} б.п., OOS {row['ev_bp_oos']}, " + f"t {row['t_boot']}, плацебо {row['placebo_ev_bp']}, рандом {row['rand_ev_bp']}, " + f"Sharpe {row['sharpe']}, by_year {row['by_year_ok']}", flush=True) + del raw, tr, pl, rn + if place_raw is not None and not place_raw.empty: + del place_raw, rand_raw + + cols = ["method", "n_trades", "ev_bp", "ev_bp_oos", "t_boot", "placebo_ev_bp", "rand_ev_bp", + "sharpe", "max_dd_pct", "by_year_ok"] + os.makedirs(os.path.join(ROOT, "results"), exist_ok=True) + pd.DataFrame(rows)[cols].to_csv(os.path.join(ROOT, "results", f"summary_5m{tag}.csv"), index=False) + H.save_by_year(rows, tag=tag) + H.save_counts(tests, tag=tag) + with open(os.path.join(ROOT, "results", f"summary_5m{tag}.json"), "w") as fh: + json.dump(rows, fh, ensure_ascii=False, indent=1) + print(f"записано results/summary_5m{tag}.csv, reports/by_year{tag}.csv, reports/count_tests{tag}.json") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/code/mr_engine.py b/code/mr_engine.py new file mode 100644 index 0000000..a434ad0 --- /dev/null +++ b/code/mr_engine.py @@ -0,0 +1,236 @@ +#!/usr/bin/env python3 +"""Стенд 5м-исследования: независимая реализация движка и утилиты. + +Здесь: + * ресемпл 5м → 30м (полные бары по 12 свечей) для сверки с прод-движком TON-бота; + * индикаторы Wilder (RSI/ADX/ATR) и полосы Боллинджера — считаются сами, без импорта прод-кода; + * НЕЗАВИСИМАЯ реализация машины состояний MR (те же правила) — чтобы сверка движка ловила + именно расхождения в состоянии, а не в правилах входа. + +Правила MR (совпадают с прод-модулем app/trading/mean_reversion.py): + вход только в боковике (ADX <= adx_max), у внешней полосы (|bb_norm| >= bb_extreme) + с подтверждением RSI, при ожидаемом движении до средней >= min_move_fee_mult × fee_rt, + SL за полосой + sl_buffer_atr×ATR с флором sl_min_pct, TP1 = средняя, TP2 = противоположная полоса; + один вход одновременно, окно исхода horizon_bars, кулдаун cooldown_bars, дневной лимит daily_max, + после SL — пауза до возврата к средней; комиссия вычитается из результата сделки. +""" +from __future__ import annotations + +from dataclasses import dataclass + +import numpy as np +import pandas as pd + +BARS_30M = 6 # 30м = 6 свечей по 5м (1ч = 12 баров) + + +# ---------------------------------------------------------------- данные ---- +def load_5m(path: str) -> pd.DataFrame: + df = pd.read_csv(path, header=None, names=["timestamp", "open", "high", "low", "close", "volume"]) + df = df.drop_duplicates("timestamp").sort_values("timestamp").reset_index(drop=True) + return df + + +def resample_30m(df5: pd.DataFrame) -> pd.DataFrame: + """5м → 30м: берём только полные бары (ровно 12 свечей) — неполные отбрасываем.""" + g = df5.groupby(df5["timestamp"] // (30 * 60 * 1000)) + out = pd.DataFrame({ + "timestamp": g["timestamp"].first(), + "open": g["open"].first(), + "high": g["high"].max(), + "low": g["low"].min(), + "close": g["close"].last(), + "volume": g["volume"].sum(), + "bars": g["close"].count(), + }) + return out[out["bars"] == BARS_30M].drop(columns=["bars"]).reset_index(drop=True) + + +# ------------------------------------------------------------ индикаторы ---- +def _wilder(x: pd.Series, period: int) -> pd.Series: + return x.ewm(alpha=1.0 / period, adjust=False, min_periods=period).mean() + + +def rsi(close: pd.Series, period: int = 14) -> pd.Series: + diff = close.diff() + up = diff.clip(lower=0.0) + down = (-diff).clip(lower=0.0) + rs = _wilder(up, period) / _wilder(down, period).replace(0.0, np.nan) + return (100 - 100 / (1 + rs)).fillna(50.0) + + +def atr(df: pd.DataFrame, period: int = 14) -> pd.Series: + prev_close = df["close"].shift(1) + tr = pd.concat([(df["high"] - df["low"]), + (df["high"] - prev_close).abs(), + (df["low"] - prev_close).abs()], axis=1).max(axis=1) + return _wilder(tr, period) + + +def adx(df: pd.DataFrame, period: int = 14) -> pd.Series: + up_move = df["high"].diff() + down_move = -df["low"].diff() + plus_dm = np.where((up_move > down_move) & (up_move > 0), up_move, 0.0) + minus_dm = np.where((down_move > up_move) & (down_move > 0), down_move, 0.0) + tr = pd.concat([(df["high"] - df["low"]), + (df["high"] - df["close"].shift(1)).abs(), + (df["low"] - df["close"].shift(1)).abs()], axis=1).max(axis=1) + atr_w = _wilder(tr, period) + plus_di = 100 * _wilder(pd.Series(plus_dm, index=df.index), period) / atr_w + minus_di = 100 * _wilder(pd.Series(minus_dm, index=df.index), period) / atr_w + dx = 100 * (plus_di - minus_di).abs() / (plus_di + minus_di).replace(0.0, np.nan) + return _wilder(dx, period).fillna(0.0) + + +def bollinger(close: pd.Series, period: int = 20, std_mult: float = 2.0) -> pd.DataFrame: + mid = close.rolling(period).mean() + sd = close.rolling(period).std(ddof=0) + half = (mid + std_mult * sd - mid).replace(0.0, np.nan) + return pd.DataFrame({ + "bb_middle": mid, + "bb_upper": mid + std_mult * sd, + "bb_lower": mid - std_mult * sd, + "bb_norm": ((close - mid) / half).clip(-3.0, 3.0), + }) + + +def prepare_frame(df30: pd.DataFrame) -> pd.DataFrame: + """Кадр с индикаторами в терминах прод-модуля (0-100 шкала rsi/adx).""" + out = df30.copy() + out["atr"] = atr(out) + out["trend_rsi14"] = rsi(out["close"]) # 0-100 (>1.5 → прод не пересчитывает) + out["trend_adx"] = adx(out) # 0-100 + out = pd.concat([out, bollinger(out["close"])], axis=1) + out["mr_rsi"] = out["trend_rsi14"] + out["mr_adx"] = out["trend_adx"] + return out + + +# ---------------------------------------------------------------- движок ---- +@dataclass +class MRParamsStand: + bb_period: int = 20 + bb_std: float = 2.0 + bb_extreme: float = 0.8 + adx_max: float = 20.0 + rsi_ob: float = 70.0 + rsi_os: float = 30.0 + sl_buffer_atr: float = 0.3 + sl_min_pct: float = 0.5 + min_move_fee_mult: float = 2.0 + cooldown_bars: int = 8 + daily_max: int = 2 + horizon_bars: int = 8 + + +def pnl_sl_first(action: str, entry: float, sl: float, tp1: float, + highs, lows, close_end: float): + """Исход сделки по пути (стоп проверяется ПЕРВЫМ). Возвращает (pnl_доля, направление рынка).""" + hi = max(highs) if len(highs) else close_end + lo = min(lows) if len(lows) else close_end + if action == "LONG": + if lo <= sl: + return (sl - entry) / entry, -1 + if hi >= tp1: + return (tp1 - entry) / entry, 1 + return (close_end - entry) / entry, (1 if close_end > entry else -1) + else: + if hi >= sl: + return (entry - sl) / entry, 1 + if lo <= tp1: + return (entry - tp1) / entry, -1 + return (entry - close_end) / entry, (-1 if close_end < entry else 1) + + +def entry_signal(i: int, frame: pd.DataFrame, p: MRParamsStand, fee_rt: float): + """Возвращает (action, entry, sl, tp1, tp2, reason). Чистая функция от бара i.""" + row = frame.iloc[i] + price, bb_norm = float(row["close"]), float(row["bb_norm"]) + rsi_v, adx_v = float(row["mr_rsi"]), float(row["mr_adx"]) + atr_v = float(row["atr"]) if pd.notna(row["atr"]) else 0.0 + mid, upper, lower = float(row["bb_middle"]), float(row["bb_upper"]), float(row["bb_lower"]) + if any(pd.isna(v) for v in (bb_norm, mid, upper, lower, price)): + return None, "warmup" + if adx_v > p.adx_max: + return None, "adx_trend" + if atr_v <= 0: + return None, "no_atr" + if bb_norm >= p.bb_extreme and rsi_v >= p.rsi_ob: + action = "SHORT" + elif bb_norm <= -p.bb_extreme and rsi_v <= p.rsi_os: + action = "LONG" + else: + return None, "no_extreme" + expected = abs(price - mid) / price + if expected < p.min_move_fee_mult * fee_rt: + return None, "fee_gate" + if action == "SHORT": + sl, tp1, tp2 = upper + p.sl_buffer_atr * atr_v, mid, lower + else: + sl, tp1, tp2 = lower - p.sl_buffer_atr * atr_v, mid, upper + min_dist = price * p.sl_min_pct / 100.0 + sl = max(sl, price + min_dist) if action == "SHORT" else min(sl, price - min_dist) + if action == "SHORT": + ok = sl > price > tp1 + else: + ok = sl < price < tp1 + if not ok: + return None, "invalid_tpsl" + return (action, price, sl, tp1, tp2), "ok" + + +def stand_mr_backtest(frame: pd.DataFrame, p: MRParamsStand, fee_rt: float) -> dict: + """Независимая машина состояний (та же семантика, что у прод-движка).""" + n = len(frame) + closes = frame["close"].to_numpy(float) + highs = frame["high"].to_numpy(float) + lows = frame["low"].to_numpy(float) + bb_norm = frame["bb_norm"].to_numpy(float) + ts = frame["timestamp"].to_numpy() + days = pd.to_datetime(ts, unit="ms", utc=True).strftime("%Y-%m-%d").to_numpy() + + trades = [] + next_free_bar, day_entries, current_day, sl_pause_sign = 0, 0, None, 0 + warmup = max(p.bb_period + 1, 15) + + for i in range(warmup, n): + if days[i] != current_day: + current_day = days[i] + day_entries = 0 + if i < next_free_bar: + continue + if sl_pause_sign != 0: + bn = bb_norm[i] + if not np.isnan(bn) and (abs(bn) <= 0.1 or bn * sl_pause_sign < 0): + sl_pause_sign = 0 + else: + continue + sig, reason = entry_signal(i, frame, p, fee_rt) + if sig is None: + continue + action, entry, sl, tp1, _tp2 = sig + if day_entries >= p.daily_max: + continue + j0, j1 = i + 1, min(i + 1 + p.horizon_bars, n) + pnl, market = pnl_sl_first(action, entry, sl, tp1, highs[j0:j1], lows[j0:j1], closes[min(j1, n - 1)]) + pred = 1 if action == "LONG" else -1 + trades.append({"i": i, "pred": pred, "actual": market, "pnl": pnl - fee_rt, + "action": action, "ts": str(days[i])}) + if market == -pred: + sl_pause_sign = -pred + day_entries += 1 + next_free_bar = j1 + p.cooldown_bars + return {"trades": trades, "metrics": metrics(trades)} + + +def metrics(trades) -> dict: + pnl = [float(t["pnl"]) for t in trades] + wins = [x for x in pnl if x > 0] + losses = [abs(x) for x in pnl if x < 0] + total_win, total_loss = sum(wins), sum(losses) or 1e-10 + directed = [t for t in trades if t.get("actual") in (1, -1)] + da = (sum(1 for t in directed if t["pred"] == t["actual"]) / len(directed)) if directed else 0.0 + return {"total_trades": len(trades), "profit_factor": min(total_win / total_loss, 10.0), + "win_rate": len(wins) / len(trades) if trades else 0.0, "sum_pnl": sum(pnl), + "avg_pnl": sum(pnl) / len(pnl) if pnl else 0.0, "da": da, + "total_win": total_win, "total_loss": total_loss} diff --git a/code/reconcile.py b/code/reconcile.py new file mode 100644 index 0000000..12f6530 --- /dev/null +++ b/code/reconcile.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +"""Сверка движка 5м-стенда с ПРОДОВЫМ движком TON-бота (обязательный шаг Ф1). + +Делает ровно то, что требует цель: берёт 5м-данные, ресемплит в 30м, считает индикаторы +нашим стендом и прогоняет ОДНИ И ТЕ ЖЕ правила двумя движками: + * прод: app.trading.mean_reversion.run_mr_backtest (импорт из /opt/data/work/tonbot_src); + * стенд: tools/mr_engine.stand_mr_backtest (независимая машина состояний). + +Расхождение PF больше 5% означает ошибку в машине состояний (не в правилах входа) — тогда +правится движок, а не цифры. + +Результат: reports/stand_reconcile.json {pairs: {SYM: {prod_n, stand_n, prod_pf, stand_pf}}} +""" +from __future__ import annotations + +import json +import os +import sys + +import pandas as pd + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +sys.path.insert(0, ROOT) +sys.path.insert(0, "/opt/data/work/tonbot_src") + +from tools.mr_engine import (MRParamsStand, load_5m, prepare_frame, resample_30m, # noqa: E402 + metrics as stand_metrics, stand_mr_backtest) +from app.trading.mean_reversion import MRParams, prepare_mr_frame, run_mr_backtest # noqa: E402 + +FEE_RT = 0.0011 # 0,055% тейкер × 2 ноги = 0,11% на круг + + +def main() -> int: + pairs = sys.argv[1:] or ["BTCUSDT", "ETHUSDT", "SOLUSDT", "XRPUSDT", "ZECUSDT"] + out = {} + for sym in pairs: + path = os.path.join(ROOT, "data", "5m", f"{sym}_5m.csv") + if not os.path.isfile(path): + print(f"{sym}: нет файла 5м — пропуск") + continue + df30 = resample_30m(load_5m(path)) + frame = prepare_frame(df30) + + prod_frame = prepare_mr_frame(frame.copy(), MRParams()) + prod = run_mr_backtest(prod_frame, MRParams(), FEE_RT) + prod_m = prod["trades"] + + stand = stand_mr_backtest(frame, MRParamsStand(), FEE_RT) + stand_m = stand["metrics"] + + out[sym] = {"prod_n": int(prod_m["total_trades"]), "stand_n": int(stand_m["total_trades"]), + "prod_pf": round(float(prod_m["profit_factor"]), 4), + "stand_pf": round(float(stand_m["profit_factor"]), 4), + "prod_sum_pnl": round(float(prod_m.get("total_return", 0.0)), 4), + "stand_sum_pnl": round(float(stand_m["sum_pnl"]), 4), + "bars_30m": int(len(df30))} + print(f"{sym}: 30м баров {len(df30)}, прод n={out[sym]['prod_n']} PF={out[sym]['prod_pf']}, " + f"стенд n={out[sym]['stand_n']} PF={out[sym]['stand_pf']}") + + os.makedirs(os.path.join(ROOT, "reports"), exist_ok=True) + with open(os.path.join(ROOT, "reports", "stand_reconcile.json"), "w") as fh: + json.dump({"pairs": out, "fee_rt": FEE_RT, "engine": "app.trading.mean_reversion.run_mr_backtest", + "stand": "tools/mr_engine.stand_mr_backtest", "source": "30м из ресемпла 5м данных"}, + fh, ensure_ascii=False, indent=1) + print("записано reports/stand_reconcile.json, пар:", len(out)) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/code/run_methods.sh b/code/run_methods.sh new file mode 100755 index 0000000..d7566aa --- /dev/null +++ b/code/run_methods.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# Прогон банка методов. Каждый метод — ОТДЕЛЬНЫМ процессом (иначе OOM на 250 парах), +# уже посчитанные строки пропускаются (results/rows/<метод><тег>.csv), затем мейкер и слияние. +set -u +cd /opt/data/work/screener5m || exit 1 +LOG=reports/methods_run.log +METHODS=(xs_reversal_15m xs_momentum_15m hour_xs_reversal xs_reversal_costaware intraday_momentum + lead_lag_btc pairs_btc_eth ou_ema_revert ou_ema_costaware vwap_revert clv_flow + liquidity_sweep vol_breakout prod_mr_5m) +echo "=== СТАРТ $(date '+%H:%M:%S') ===" >> "$LOG" +for mode in taker maker; do + tag=""; flag="" + if [ "$mode" = "maker" ]; then tag="_maker"; flag="--maker"; fi + for m in "${METHODS[@]}"; do + row="results/rows/${m}${tag}.csv" + if [ -f "$row" ]; then + echo "--- пропуск $mode $m (строка уже есть)" >> "$LOG" + continue + fi + echo "--- $mode $m $(date '+%H:%M:%S')" >> "$LOG" + .venv/bin/python tools/methods.py $flag "$m" >> "$LOG" 2>&1 || echo "!!! $mode $m упал" >> "$LOG" + done +done +echo "=== СЛИЯНИЕ $(date '+%H:%M:%S') ===" >> "$LOG" +.venv/bin/python tools/merge_rows.py >> "$LOG" 2>&1 +.venv/bin/python tools/merge_byyear.py >> "$LOG" 2>&1 +.venv/bin/python tools/make_report.py >> "$LOG" 2>&1 +echo "=== ГОТОВО $(date '+%H:%M:%S') ===" >> "$LOG" diff --git a/docs/REJECTED.md b/docs/REJECTED.md new file mode 100644 index 0000000..1489b35 --- /dev/null +++ b/docs/REJECTED.md @@ -0,0 +1,25 @@ +# Опровергнуто: методы, не прошедшие порог + +Прогон по 250 USDT-перпам Bybit (5м, 2024-10-01 … 2026-09-28). Издержки вычтены из каждой сделки. +Порог приёмки: EV > 0 после тейкера, плацебо ≈ 0, |t| ≥ порога Бонферрони, плюс в ≥ 2 из 3 лет. + +| Метод | сделок | EV, б.п. | EV OOS | t (бутстрап) | плацебо, б.п. | рандом, б.п. | EV мейкер | Причина отказа | +|---|---|---|---|---|---|---|---|---| +| prod_mr_5m | 105089 | -4.46 | -5.19 | -7.65 | 0.0 | 0.0 | 2.54 | отрицательное матожидание после издержек; OOS тоже минус; плюс менее чем в 2 из 3 лет | +| clv_flow | 257017 | -9.88 | -12.24 | -4.15 | 0.0 | 0.0 | -2.88 | отрицательное матожидание после издержек; OOS тоже минус; плюс менее чем в 2 из 3 лет | +| pairs_btc_eth | 1944 | -13.22 | -9.81 | -5.85 | 0.0 | 0.0 | -6.22 | отрицательное матожидание после издержек; OOS тоже минус; плюс менее чем в 2 из 3 лет | +| intraday_momentum | 125771 | -13.8 | -9.2 | -7.35 | 0.0 | 0.0 | -6.8 | отрицательное матожидание после издержек; OOS тоже минус; плюс менее чем в 2 из 3 лет | +| xs_reversal_15m | 873500 | -14.29 | -14.31 | -29.1 | -16.69 | -16.26 | -7.29 | отрицательное матожидание после издержек; OOS тоже минус; плюс менее чем в 2 из 3 лет | +| xs_reversal_costaware | 873400 | -14.29 | -14.31 | -29.1 | 0.0 | 0.0 | -7.29 | отрицательное матожидание после издержек; OOS тоже минус; плюс менее чем в 2 из 3 лет | +| hour_xs_reversal | 291200 | -14.7 | -14.77 | -35.34 | 0.0 | 0.0 | -7.7 | отрицательное матожидание после издержек; OOS тоже минус; плюс менее чем в 2 из 3 лет | +| ou_ema_costaware | 1047089 | -14.74 | -13.66 | -19.88 | 0.0 | 0.0 | -7.74 | отрицательное матожидание после издержек; OOS тоже минус; плюс менее чем в 2 из 3 лет | +| ou_ema_revert | 1123903 | -14.84 | -13.81 | -21.05 | 0.0 | 0.0 | -7.84 | отрицательное матожидание после издержек; OOS тоже минус; плюс менее чем в 2 из 3 лет | +| lead_lag_btc | 2311277 | -15.64 | -15.67 | -31.17 | 0.0 | 0.0 | -8.64 | отрицательное матожидание после издержек; OOS тоже минус; плюс менее чем в 2 из 3 лет | +| liquidity_sweep | 1166054 | -15.85 | -16.0 | -34.55 | 0.0 | 0.0 | -8.85 | отрицательное матожидание после издержек; OOS тоже минус; плюс менее чем в 2 из 3 лет | +| vwap_revert | 2655400 | -16.42 | -16.35 | -28.62 | 0.0 | 0.0 | -9.42 | отрицательное матожидание после издержек; OOS тоже минус; плюс менее чем в 2 из 3 лет | +| vol_breakout | 738494 | -18.51 | -19.43 | -24.03 | 0.0 | 0.0 | -11.51 | отрицательное матожидание после издержек; OOS тоже минус; плюс менее чем в 2 из 3 лет | +| xs_momentum_15m | 873500 | -18.87 | -19.04 | -38.48 | 0.0 | 0.0 | -11.87 | отрицательное матожидание после издержек; OOS тоже минус; плюс менее чем в 2 из 3 лет | + +Методы перечислены вместе с контролем: плацебо — то же правило на сдвинутом вперёд сигнале, +рандом — случайные входы той же частоты. Совпадение EV метода с плацебо означает, что +измерялся дрейф рынка, а не правило. diff --git a/reports/COSTS.md b/reports/COSTS.md new file mode 100644 index 0000000..3828b2f --- /dev/null +++ b/reports/COSTS.md @@ -0,0 +1,49 @@ +# Планка издержек на 5-минутных свечах + +Посчитано скриптом `tools/costs.py` по 249 парам 36 328 179 барам 5м (данные Bybit). + +## Что стоит торговля + +- Комиссия тейкера: 0.055% на ногу, **0.11% на круг** (вход+выход); +- Комиссия мейкера: 0.02% на ногу, **0.04% на круг**; +- Проскальзывание: 1 б.п. на ногу для топ-30 по обороту, 3 б.п. для остальных пар; +- Funding учитывается только при удержании через 00/08/16 UTC (0,006% за 8ч на BTC — на фоне комиссии незначимо, но в движке учтено). + +## Типичные движения (факт по данным) + +- Средний модуль доходности за 5м: **25.81 б.п.** (медиана по парам 23.33 б.п.); +- Средний модуль доходности за 1 час: **90.1 б.п.** + +Вывод: комиссия на круг (11 б.п.) сопоставима с типичным движением за 5 минут. Значит «скальпинг по одному 5м-бару» убыточен по построению, а осмысленны конструкции, где цель в 3–10 раз больше комиссии (сигнал на 5м, удержание десятки минут) либо мейкер-исполнение, либо кросс-секционные корзины. + +## Порог безубыточной точности p* = (Стоп + Издержки) / (Цель + Стоп) + +| Цель | Стоп | Тейкер (11 б.п.) | Мейкер (4 б.п.) | +|---|---|---|---| +| 0.1% | 0.1% | нужно выигрывать 105.0% | нужно выигрывать 70.0% | +| 0.2% | 0.2% | нужно выигрывать 77.5% | нужно выигрывать 60.0% | +| 0.3% | 0.2% | нужно выигрывать 62.0% | нужно выигрывать 48.0% | +| 0.5% | 0.3% | нужно выигрывать 51.2% | нужно выигрывать 42.5% | +| 1.0% | 0.5% | нужно выигрывать 40.7% | нужно выигрывать 36.0% | +| 1.0% | 1.0% | нужно выигрывать 55.5% | нужно выигрывать 52.0% | +| 2.0% | 1.0% | нужно выигрывать 37.0% | нужно выигрывать 34.7% | + +## Топ-15 пар по среднему движению 5м + +| Пара | баров | |r5м|, б.п. | σ 5м, б.п. | |r1ч|, б.п. | +|---|---|---|---|---| +| 4STOCKUSDT | 5596 | 98.97 | 156.51 | 345.70 | +| LONGXIAUSDT | 4181 | 83.76 | 138.74 | 294.47 | +| NIULAIUSDT | 9092 | 74.86 | 118.64 | 263.87 | +| MARSCOINUSDT | 7898 | 73.25 | 121.57 | 265.85 | +| BTWUSDT | 33271 | 71.21 | 126.09 | 245.68 | +| CASHCATUSDT | 15682 | 71.03 | 107.43 | 256.14 | +| PONSUSDT | 8201 | 62.01 | 89.07 | 215.86 | +| BEATUSDT | 89701 | 58.67 | 96.51 | 203.93 | +| LABUSDT | 96920 | 53.50 | 107.38 | 188.50 | +| STONKUSDT | 3036 | 52.80 | 75.32 | 243.38 | +| AKEUSDT | 104785 | 49.21 | 102.05 | 173.05 | +| RIVERUSDT | 98326 | 48.23 | 83.57 | 167.50 | +| HUSDT | 132627 | 46.78 | 89.22 | 160.55 | +| PIPPINUSDT | 178429 | 45.00 | 91.74 | 156.84 | +| CAPUSDT | 26421 | 44.47 | 78.81 | 154.40 | diff --git a/reports/PLACEBO.md b/reports/PLACEBO.md new file mode 100644 index 0000000..07a2779 --- /dev/null +++ b/reports/PLACEBO.md @@ -0,0 +1,22 @@ +# Плацебо- и рандом-контроль + +Плацебо — правило, применённое к сигналу, сдвинутому вперёд на горизонт удержания. +Рандом — случайные входы той же частоты и направления. Если EV метода близко к плацебо, +значит «эдж» был движением рынка, а не сигналом. + +| Метод | EV метода | плацебо | рандом | вердикт контроля | +|---|---|---|---|---| +| prod_mr_5m | -4.46 | 0.0 | 0.0 | контроль не проведён (не базовый метод) | +| clv_flow | -9.88 | 0.0 | 0.0 | контроль не проведён (не базовый метод) | +| pairs_btc_eth | -13.22 | 0.0 | 0.0 | контроль не проведён (не базовый метод) | +| intraday_momentum | -13.8 | 0.0 | 0.0 | контроль не проведён (не базовый метод) | +| xs_reversal_15m | -14.29 | -16.69 | -16.26 | метод не отличается от плацебо | +| xs_reversal_costaware | -14.29 | 0.0 | 0.0 | контроль не проведён (не базовый метод) | +| hour_xs_reversal | -14.7 | 0.0 | 0.0 | контроль не проведён (не базовый метод) | +| ou_ema_costaware | -14.74 | 0.0 | 0.0 | контроль не проведён (не базовый метод) | +| ou_ema_revert | -14.84 | 0.0 | 0.0 | контроль не проведён (не базовый метод) | +| lead_lag_btc | -15.64 | 0.0 | 0.0 | контроль не проведён (не базовый метод) | +| liquidity_sweep | -15.85 | 0.0 | 0.0 | контроль не проведён (не базовый метод) | +| vwap_revert | -16.42 | 0.0 | 0.0 | контроль не проведён (не базовый метод) | +| vol_breakout | -18.51 | 0.0 | 0.0 | контроль не проведён (не базовый метод) | +| xs_momentum_15m | -18.87 | 0.0 | 0.0 | контроль не проведён (не базовый метод) | diff --git a/reports/by_year.csv b/reports/by_year.csv new file mode 100644 index 0000000..1339dd1 --- /dev/null +++ b/reports/by_year.csv @@ -0,0 +1,85 @@ +method,year,ev_bp,n,n_trades +clv_flow,2024,-12.194,25421,25421 +clv_flow,2025,-7.451,114859,114859 +clv_flow,2026,-11.756,116737,116737 +clv_flow_maker,2024,-5.194,25421,25421 +clv_flow_maker,2025,-0.451,114859,114859 +clv_flow_maker,2026,-4.756,116737,116737 +hour_xs_reversal,2024,-13.138,36800,36800 +hour_xs_reversal,2025,-15.361,146000,146000 +hour_xs_reversal,2026,-14.353,108400,108400 +hour_xs_reversal_maker,2024,-6.138,36800,36800 +hour_xs_reversal_maker,2025,-8.361,146000,146000 +hour_xs_reversal_maker,2026,-7.353,108400,108400 +intraday_momentum,2024,-3.978,11098,11098 +intraday_momentum,2025,-21.422,56698,56698 +intraday_momentum,2026,-8.227,57975,57975 +intraday_momentum_maker,2024,3.022,11098,11098 +intraday_momentum_maker,2025,-14.422,56698,56698 +intraday_momentum_maker,2026,-1.227,57975,57975 +lead_lag_btc,2024,-16.295,203946,203946 +lead_lag_btc,2025,-15.415,1055623,1055623 +lead_lag_btc,2026,-15.744,1051708,1051708 +lead_lag_btc_maker,2024,-9.295,203946,203946 +lead_lag_btc_maker,2025,-8.415,1055623,1055623 +lead_lag_btc_maker,2026,-8.744,1051708,1051708 +liquidity_sweep,2024,-16.179,109657,109657 +liquidity_sweep,2025,-15.979,534572,534572 +liquidity_sweep,2026,-15.651,521825,521825 +liquidity_sweep_maker,2024,-9.179,109657,109657 +liquidity_sweep_maker,2025,-8.979,534572,534572 +liquidity_sweep_maker,2026,-8.651,521825,521825 +ou_ema_costaware,2024,-17.433,93868,93868 +ou_ema_costaware,2025,-15.616,477667,477667 +ou_ema_costaware,2026,-13.327,475554,475554 +ou_ema_costaware_maker,2024,-10.433,93868,93868 +ou_ema_costaware_maker,2025,-8.616,477667,477667 +ou_ema_costaware_maker,2026,-6.327,475554,475554 +ou_ema_revert,2024,-17.399,97940,97940 +ou_ema_revert,2025,-15.645,502849,502849 +ou_ema_revert,2026,-13.588,523114,523114 +ou_ema_revert_maker,2024,-10.399,97940,97940 +ou_ema_revert_maker,2025,-8.645,502849,502849 +ou_ema_revert_maker,2026,-6.588,523114,523114 +pairs_btc_eth,2024,-15.364,242,242 +pairs_btc_eth,2025,-14.429,990,990 +pairs_btc_eth,2026,-10.802,712,712 +pairs_btc_eth_maker,2024,-8.364,242,242 +pairs_btc_eth_maker,2025,-7.429,990,990 +pairs_btc_eth_maker,2026,-3.802,712,712 +prod_mr_5m,2024,-4.717,10186,10186 +prod_mr_5m,2025,-3.832,45902,45902 +prod_mr_5m,2026,-5.004,49001,49001 +prod_mr_5m_maker,2024,2.283,10186,10186 +prod_mr_5m_maker,2025,3.168,45902,45902 +prod_mr_5m_maker,2026,1.996,49001,49001 +vol_breakout,2024,-12.294,67189,67189 +vol_breakout,2025,-18.546,340933,340933 +vol_breakout,2026,-19.739,330372,330372 +vol_breakout_maker,2024,-5.294,67189,67189 +vol_breakout_maker,2025,-11.546,340933,340933 +vol_breakout_maker,2026,-12.739,330372,330372 +vwap_revert,2024,-16.574,196980,196980 +vwap_revert,2025,-16.542,1199981,1199981 +vwap_revert,2026,-16.273,1258439,1258439 +vwap_revert_maker,2024,-9.574,196980,196980 +vwap_revert_maker,2025,-9.542,1199981,1199981 +vwap_revert_maker,2026,-9.273,1258439,1258439 +xs_momentum_15m,2024,-18.51,110400,110400 +xs_momentum_15m,2025,-19.401,438000,438000 +xs_momentum_15m,2026,-18.289,325100,325100 +xs_momentum_15m_maker,2024,-11.51,110400,110400 +xs_momentum_15m_maker,2025,-12.401,438000,438000 +xs_momentum_15m_maker,2026,-11.289,325100,325100 +xs_reversal_15m,2024,-14.27,110400,110400 +xs_reversal_15m,2025,-13.727,438000,438000 +xs_reversal_15m,2026,-15.06,325100,325100 +xs_reversal_15m_maker,2024,-7.27,110400,110400 +xs_reversal_15m_maker,2025,-6.727,438000,438000 +xs_reversal_15m_maker,2026,-8.06,325100,325100 +xs_reversal_costaware,2024,-14.277,110300,110300 +xs_reversal_costaware,2025,-13.727,438000,438000 +xs_reversal_costaware,2026,-15.06,325100,325100 +xs_reversal_costaware_maker,2024,-7.277,110300,110300 +xs_reversal_costaware_maker,2025,-6.727,438000,438000 +xs_reversal_costaware_maker,2026,-8.06,325100,325100 diff --git a/reports/by_year_maker.csv b/reports/by_year_maker.csv new file mode 100644 index 0000000..5b7d938 --- /dev/null +++ b/reports/by_year_maker.csv @@ -0,0 +1,4 @@ +method,year,ev_bp,n +prod_mr_5m,2024,2.28,105089 +prod_mr_5m,2025,3.17,105089 +prod_mr_5m,2026,2.0,105089 diff --git a/reports/costs.json b/reports/costs.json new file mode 100644 index 0000000..a7785e1 --- /dev/null +++ b/reports/costs.json @@ -0,0 +1,2059 @@ +{ + "fees_pct_per_leg": { + "taker": 0.055, + "maker": 0.02 + }, + "fee_rt_pct": { + "taker": 0.11, + "maker": 0.04 + }, + "slippage_bp_per_leg": 1.0, + "slippage_bp_per_leg_illiquid": 3.0, + "mean_abs_r5m_bp": 25.81, + "mean_abs_r1h_bp": 90.1, + "aggregate": { + "pairs": 249, + "bars_total": 36328179, + "mean_abs_r5m_bp": 25.81, + "median_abs_r5m_bp": 23.33, + "mean_abs_r1h_bp": 90.1 + }, + "per_pair": [ + { + "symbol": "0GUSDT", + "bars": 108177, + "mean_abs_r5m_bp": 25.15922084804495, + "std_r5m_bp": 45.20265977909783, + "p95_abs_r5m_bp": 80.0066107179881, + "mean_abs_r1h_bp": 89.06802272984015 + }, + { + "symbol": "1000BONKUSDT", + "bars": 209631, + "mean_abs_r5m_bp": 25.6724522380972, + "std_r5m_bp": 43.0932934103404, + "p95_abs_r5m_bp": 77.63819119430737, + "mean_abs_r1h_bp": 88.26712566321937 + }, + { + "symbol": "1000FLOKIUSDT", + "bars": 209650, + "mean_abs_r5m_bp": 22.466372157626783, + "std_r5m_bp": 40.50072412698386, + "p95_abs_r5m_bp": 67.25296719752548, + "mean_abs_r1h_bp": 76.47628489409684 + }, + { + "symbol": "1000PEPEUSDT", + "bars": 208628, + "mean_abs_r5m_bp": 22.931072135103296, + "std_r5m_bp": 40.57606531538941, + "p95_abs_r5m_bp": 68.12007761845229, + "mean_abs_r1h_bp": 78.3879791646055 + }, + { + "symbol": "2ZUSDT", + "bars": 104060, + "mean_abs_r5m_bp": 21.977948343352644, + "std_r5m_bp": 39.55574185329813, + "p95_abs_r5m_bp": 66.99415680046276, + "mean_abs_r1h_bp": 77.09038630684879 + }, + { + "symbol": "4STOCKUSDT", + "bars": 5596, + "mean_abs_r5m_bp": 98.97043550513872, + "std_r5m_bp": 156.5100528565529, + "p95_abs_r5m_bp": 319.35735176351216, + "mean_abs_r1h_bp": 345.6968478665757 + }, + { + "symbol": "AAOIUSDT", + "bars": 32125, + "mean_abs_r5m_bp": 20.981125495910685, + "std_r5m_bp": 37.72673328980313, + "p95_abs_r5m_bp": 72.3160646071772, + "mean_abs_r1h_bp": 75.7895055979138 + }, + { + "symbol": "AAVEUSDT", + "bars": 209629, + "mean_abs_r5m_bp": 20.326726213399272, + "std_r5m_bp": 34.72893730510991, + "p95_abs_r5m_bp": 59.71777104485376, + "mean_abs_r1h_bp": 69.84938509133237 + }, + { + "symbol": "ACEUSDT", + "bars": 209652, + "mean_abs_r5m_bp": 26.94442882876567, + "std_r5m_bp": 52.32230503475815, + "p95_abs_r5m_bp": 80.11114935675495, + "mean_abs_r1h_bp": 92.45705490191027 + }, + { + "symbol": "ADAUSDT", + "bars": 209628, + "mean_abs_r5m_bp": 19.15042917724434, + "std_r5m_bp": 33.981456791651475, + "p95_abs_r5m_bp": 56.78693788680422, + "mean_abs_r1h_bp": 65.01350206747276 + }, + { + "symbol": "AEROUSDT", + "bars": 209629, + "mean_abs_r5m_bp": 22.988751463723702, + "std_r5m_bp": 41.913207312666316, + "p95_abs_r5m_bp": 67.80202163109128, + "mean_abs_r1h_bp": 84.32235100650337 + }, + { + "symbol": "AGIUSDT", + "bars": 209641, + "mean_abs_r5m_bp": 23.788185409926257, + "std_r5m_bp": 41.35092786082496, + "p95_abs_r5m_bp": 75.47205635383136, + "mean_abs_r1h_bp": 80.6188848825379 + }, + { + "symbol": "AKEUSDT", + "bars": 104785, + "mean_abs_r5m_bp": 49.21314570025338, + "std_r5m_bp": 102.0535639230551, + "p95_abs_r5m_bp": 161.85324421018035, + "mean_abs_r1h_bp": 173.05333489863807 + }, + { + "symbol": "ALGOUSDT", + "bars": 209629, + "mean_abs_r5m_bp": 20.7269981928512, + "std_r5m_bp": 33.1296335577068, + "p95_abs_r5m_bp": 62.3362451525091, + "mean_abs_r1h_bp": 71.16178333263595 + }, + { + "symbol": "ALLOUSDT", + "bars": 91705, + "mean_abs_r5m_bp": 33.04548853681781, + "std_r5m_bp": 57.32530797177805, + "p95_abs_r5m_bp": 105.80867284078714, + "mean_abs_r1h_bp": 115.24874294872009 + }, + { + "symbol": "AMDSTOCKUSDT", + "bars": 35577, + "mean_abs_r5m_bp": 11.715575370541822, + "std_r5m_bp": 22.341297971449084, + "p95_abs_r5m_bp": 43.672832826073815, + "mean_abs_r1h_bp": 44.97874413913174 + }, + { + "symbol": "APEUSDT", + "bars": 207635, + "mean_abs_r5m_bp": 21.921062516292174, + "std_r5m_bp": 41.460378962504635, + "p95_abs_r5m_bp": 63.70137337607058, + "mean_abs_r1h_bp": 75.85352520266767 + }, + { + "symbol": "APEXUSDT", + "bars": 105274, + "mean_abs_r5m_bp": 23.488766588032007, + "std_r5m_bp": 41.51910834772599, + "p95_abs_r5m_bp": 76.42824239451674, + "mean_abs_r1h_bp": 82.85251488047595 + }, + { + "symbol": "APPSTOCKUSDT", + "bars": 21464, + "mean_abs_r5m_bp": 10.864949541043595, + "std_r5m_bp": 29.424473239868554, + "p95_abs_r5m_bp": 38.43111173394485, + "mean_abs_r1h_bp": 33.99316333594129 + }, + { + "symbol": "APTUSDT", + "bars": 209629, + "mean_abs_r5m_bp": 20.514650772174406, + "std_r5m_bp": 33.52274591477221, + "p95_abs_r5m_bp": 59.57036432803929, + "mean_abs_r1h_bp": 70.74339081873511 + }, + { + "symbol": "ARBUSDT", + "bars": 207628, + "mean_abs_r5m_bp": 21.489024853516664, + "std_r5m_bp": 39.82352101345332, + "p95_abs_r5m_bp": 62.85032302612237, + "mean_abs_r1h_bp": 74.2780093222745 + }, + { + "symbol": "ARKMUSDT", + "bars": 209653, + "mean_abs_r5m_bp": 24.376795759992532, + "std_r5m_bp": 43.15704980332897, + "p95_abs_r5m_bp": 71.39703188275196, + "mean_abs_r1h_bp": 83.93899901370382 + }, + { + "symbol": "ARKUSDT", + "bars": 209634, + "mean_abs_r5m_bp": 20.390042460389658, + "std_r5m_bp": 36.5812583594449, + "p95_abs_r5m_bp": 64.56909218972125, + "mean_abs_r1h_bp": 69.47606960429431 + }, + { + "symbol": "ARUSDT", + "bars": 207635, + "mean_abs_r5m_bp": 23.84839649376678, + "std_r5m_bp": 46.14671184228223, + "p95_abs_r5m_bp": 69.80959001990338, + "mean_abs_r1h_bp": 82.15367837824957 + }, + { + "symbol": "ARXUSDT", + "bars": 28142, + "mean_abs_r5m_bp": 32.18580822952769, + "std_r5m_bp": 50.60412628474328, + "p95_abs_r5m_bp": 98.89772505181415, + "mean_abs_r1h_bp": 111.44931759250333 + }, + { + "symbol": "ASTERUSDT", + "bars": 107853, + "mean_abs_r5m_bp": 21.08954334849772, + "std_r5m_bp": 40.733646144480105, + "p95_abs_r5m_bp": 73.07606597116984, + "mean_abs_r1h_bp": 71.93310641031093 + }, + { + "symbol": "ATHUSDT", + "bars": 209646, + "mean_abs_r5m_bp": 23.759873237567945, + "std_r5m_bp": 38.70802819131798, + "p95_abs_r5m_bp": 71.47524641000646, + "mean_abs_r1h_bp": 83.20273800520039 + }, + { + "symbol": "ATOMUSDT", + "bars": 209630, + "mean_abs_r5m_bp": 17.314457439285416, + "std_r5m_bp": 30.30065437524329, + "p95_abs_r5m_bp": 50.54143669554899, + "mean_abs_r1h_bp": 58.90517433686419 + }, + { + "symbol": "AVAXUSDT", + "bars": 209628, + "mean_abs_r5m_bp": 17.984356021364185, + "std_r5m_bp": 32.63607507657831, + "p95_abs_r5m_bp": 53.59273417132068, + "mean_abs_r1h_bp": 62.59993330202326 + }, + { + "symbol": "AVGOUSDT", + "bars": 40197, + "mean_abs_r5m_bp": 8.33711506285025, + "std_r5m_bp": 16.96650860651374, + "p95_abs_r5m_bp": 31.819668674670787, + "mean_abs_r1h_bp": 31.77582368504589 + }, + { + "symbol": "AVNTUSDT", + "bars": 110687, + "mean_abs_r5m_bp": 28.329402659002316, + "std_r5m_bp": 53.95747304807222, + "p95_abs_r5m_bp": 89.05161594958605, + "mean_abs_r1h_bp": 99.18108932939148 + }, + { + "symbol": "AXSUSDT", + "bars": 209642, + "mean_abs_r5m_bp": 20.471046291329234, + "std_r5m_bp": 34.8025837754036, + "p95_abs_r5m_bp": 60.7309144472068, + "mean_abs_r1h_bp": 70.44639802601736 + }, + { + "symbol": "AZTECUSDT", + "bars": 65825, + "mean_abs_r5m_bp": 22.96912505575546, + "std_r5m_bp": 40.486948193633054, + "p95_abs_r5m_bp": 70.58513214889746, + "mean_abs_r1h_bp": 80.39442020308687 + }, + { + "symbol": "B3USDT", + "bars": 170927, + "mean_abs_r5m_bp": 27.15301267156004, + "std_r5m_bp": 51.0939990743184, + "p95_abs_r5m_bp": 86.50365126877446, + "mean_abs_r1h_bp": 93.21660557240031 + }, + { + "symbol": "BANKUSDT", + "bars": 146469, + "mean_abs_r5m_bp": 32.26451819267088, + "std_r5m_bp": 66.73589928991531, + "p95_abs_r5m_bp": 103.41005636662908, + "mean_abs_r1h_bp": 113.82372725656116 + }, + { + "symbol": "BCHUSDT", + "bars": 209629, + "mean_abs_r5m_bp": 15.462974188019153, + "std_r5m_bp": 24.7754168955133, + "p95_abs_r5m_bp": 46.80378297033092, + "mean_abs_r1h_bp": 53.382972001390456 + }, + { + "symbol": "BEAMUSDT", + "bars": 209654, + "mean_abs_r5m_bp": 23.097390443545663, + "std_r5m_bp": 37.15637033843373, + "p95_abs_r5m_bp": 68.66431198668896, + "mean_abs_r1h_bp": 80.721635551426 + }, + { + "symbol": "BEATUSDT", + "bars": 89701, + "mean_abs_r5m_bp": 58.669306663470955, + "std_r5m_bp": 96.50544517299227, + "p95_abs_r5m_bp": 184.88068707686833, + "mean_abs_r1h_bp": 203.92654258729587 + }, + { + "symbol": "BERAUSDT", + "bars": 172625, + "mean_abs_r5m_bp": 25.251510498293094, + "std_r5m_bp": 44.76219338494819, + "p95_abs_r5m_bp": 74.10963206211795, + "mean_abs_r1h_bp": 87.88234056613072 + }, + { + "symbol": "BEUSDT", + "bars": 35592, + "mean_abs_r5m_bp": 18.37970850861532, + "std_r5m_bp": 34.979841523874505, + "p95_abs_r5m_bp": 66.77715744071833, + "mean_abs_r1h_bp": 70.85371300110084 + }, + { + "symbol": "BILLUSDT", + "bars": 41945, + "mean_abs_r5m_bp": 38.7775761320244, + "std_r5m_bp": 64.31222777780891, + "p95_abs_r5m_bp": 126.67400909352165, + "mean_abs_r1h_bp": 134.4621616870197 + }, + { + "symbol": "BIOUSDT", + "bars": 185065, + "mean_abs_r5m_bp": 29.863975744842023, + "std_r5m_bp": 52.55672275253964, + "p95_abs_r5m_bp": 90.37650163630163, + "mean_abs_r1h_bp": 102.41785726918903 + }, + { + "symbol": "BNBUSDT", + "bars": 209629, + "mean_abs_r5m_bp": 10.495905145243695, + "std_r5m_bp": 18.17483684475967, + "p95_abs_r5m_bp": 32.27133345008503, + "mean_abs_r1h_bp": 36.28858048022185 + }, + { + "symbol": "BRETTUSDT", + "bars": 209652, + "mean_abs_r5m_bp": 29.028464893680976, + "std_r5m_bp": 47.23381907541894, + "p95_abs_r5m_bp": 84.29832889283118, + "mean_abs_r1h_bp": 101.15111678104198 + }, + { + "symbol": "BROCCOLIUSDT", + "bars": 170326, + "mean_abs_r5m_bp": 34.183033048110914, + "std_r5m_bp": 73.01173550813331, + "p95_abs_r5m_bp": 107.47471027911875, + "mean_abs_r1h_bp": 116.35136322916213 + }, + { + "symbol": "BRUSDT", + "bars": 159511, + "mean_abs_r5m_bp": 31.362491156256308, + "std_r5m_bp": 68.66936907871747, + "p95_abs_r5m_bp": 104.1849124850965, + "mean_abs_r1h_bp": 110.10447933862501 + }, + { + "symbol": "BSVUSDT", + "bars": 209643, + "mean_abs_r5m_bp": 16.90832585352548, + "std_r5m_bp": 27.96549132260761, + "p95_abs_r5m_bp": 52.28721270309339, + "mean_abs_r1h_bp": 57.67095703030562 + }, + { + "symbol": "BTCUSDT", + "bars": 207626, + "mean_abs_r5m_bp": 9.095257468156106, + "std_r5m_bp": 14.351268429179699, + "p95_abs_r5m_bp": 28.080484133290888, + "mean_abs_r1h_bp": 31.008984187876106 + }, + { + "symbol": "BTWUSDT", + "bars": 33271, + "mean_abs_r5m_bp": 71.21424449150526, + "std_r5m_bp": 126.08842078557676, + "p95_abs_r5m_bp": 244.46429882268043, + "mean_abs_r1h_bp": 245.68120801034783 + }, + { + "symbol": "BXUSDT", + "bars": 21144, + "mean_abs_r5m_bp": 6.5738744207095605, + "std_r5m_bp": 14.073560311330262, + "p95_abs_r5m_bp": 28.424390838707307, + "mean_abs_r1h_bp": 25.63724800928537 + }, + { + "symbol": "CAKEUSDT", + "bars": 209635, + "mean_abs_r5m_bp": 17.967205858522096, + "std_r5m_bp": 31.10599468165697, + "p95_abs_r5m_bp": 54.89492284771469, + "mean_abs_r1h_bp": 62.584513474880566 + }, + { + "symbol": "CAPUSDT", + "bars": 26421, + "mean_abs_r5m_bp": 44.4690706833781, + "std_r5m_bp": 78.8083070264434, + "p95_abs_r5m_bp": 136.05960723476412, + "mean_abs_r1h_bp": 154.39566286886614 + }, + { + "symbol": "CASHCATUSDT", + "bars": 15682, + "mean_abs_r5m_bp": 71.02825359716066, + "std_r5m_bp": 107.43143975867066, + "p95_abs_r5m_bp": 204.7512227781212, + "mean_abs_r1h_bp": 256.1392364261782 + }, + { + "symbol": "CBRSUSDT", + "bars": 38177, + "mean_abs_r5m_bp": 18.149862504349574, + "std_r5m_bp": 34.64205345881344, + "p95_abs_r5m_bp": 63.96239907995094, + "mean_abs_r1h_bp": 66.54976740155497 + }, + { + "symbol": "CCUSDT", + "bars": 95698, + "mean_abs_r5m_bp": 22.041971050632483, + "std_r5m_bp": 38.46905325656179, + "p95_abs_r5m_bp": 74.93481317822723, + "mean_abs_r1h_bp": 77.33730037593384 + }, + { + "symbol": "CFGUSDT", + "bars": 56374, + "mean_abs_r5m_bp": 26.783865410656794, + "std_r5m_bp": 40.45422086332747, + "p95_abs_r5m_bp": 80.03459248002403, + "mean_abs_r1h_bp": 92.04950546616847 + }, + { + "symbol": "CHIPUSDT", + "bars": 47660, + "mean_abs_r5m_bp": 33.13324100893782, + "std_r5m_bp": 56.05877744987343, + "p95_abs_r5m_bp": 102.66768536487758, + "mean_abs_r1h_bp": 118.16993939547338 + }, + { + "symbol": "COMPUSDT", + "bars": 209653, + "mean_abs_r5m_bp": 18.857802279210404, + "std_r5m_bp": 33.0078415461869, + "p95_abs_r5m_bp": 56.00154671341205, + "mean_abs_r1h_bp": 64.61582626631031 + }, + { + "symbol": "COTIUSDT", + "bars": 209635, + "mean_abs_r5m_bp": 25.274224553486864, + "std_r5m_bp": 42.859143345874635, + "p95_abs_r5m_bp": 74.9034019013054, + "mean_abs_r1h_bp": 85.73443321007241 + }, + { + "symbol": "COWUSDT", + "bars": 198577, + "mean_abs_r5m_bp": 22.967873650050983, + "std_r5m_bp": 39.22376825167092, + "p95_abs_r5m_bp": 70.06964588511467, + "mean_abs_r1h_bp": 80.0551717658513 + }, + { + "symbol": "CPUSDT", + "bars": 7086, + "mean_abs_r5m_bp": 38.24247207630071, + "std_r5m_bp": 81.1237229354339, + "p95_abs_r5m_bp": 114.76242233275252, + "mean_abs_r1h_bp": 128.40435764827862 + }, + { + "symbol": "CROUSDT", + "bars": 207632, + "mean_abs_r5m_bp": 17.571263830144805, + "std_r5m_bp": 33.863000477842085, + "p95_abs_r5m_bp": 56.98021114637797, + "mean_abs_r1h_bp": 61.21147276766075 + }, + { + "symbol": "CRVUSDT", + "bars": 207629, + "mean_abs_r5m_bp": 23.633172241406168, + "std_r5m_bp": 41.4450423133466, + "p95_abs_r5m_bp": 71.15453348975458, + "mean_abs_r1h_bp": 81.57570042272106 + }, + { + "symbol": "CVXUSDT", + "bars": 209644, + "mean_abs_r5m_bp": 24.135967040913687, + "std_r5m_bp": 42.420911141882534, + "p95_abs_r5m_bp": 72.82994870110215, + "mean_abs_r1h_bp": 83.2717162529839 + }, + { + "symbol": "DASHUSDT", + "bars": 207629, + "mean_abs_r5m_bp": 23.445237776532064, + "std_r5m_bp": 39.660959298007505, + "p95_abs_r5m_bp": 73.28865976610489, + "mean_abs_r1h_bp": 79.68221869684683 + }, + { + "symbol": "DATAUSDT", + "bars": 23472, + "mean_abs_r5m_bp": 14.01273107007116, + "std_r5m_bp": 24.4326647271687, + "p95_abs_r5m_bp": 44.80628833899103, + "mean_abs_r1h_bp": 49.13082897174119 + }, + { + "symbol": "DEEPUSDT", + "bars": 204894, + "mean_abs_r5m_bp": 29.248519903279348, + "std_r5m_bp": 49.014223992260355, + "p95_abs_r5m_bp": 90.73355010390837, + "mean_abs_r1h_bp": 104.35715115717916 + }, + { + "symbol": "DGAIUSDT", + "bars": 7078, + "mean_abs_r5m_bp": 13.866162019593304, + "std_r5m_bp": 29.40812498685502, + "p95_abs_r5m_bp": 44.626740426447455, + "mean_abs_r1h_bp": 47.85724592783535 + }, + { + "symbol": "DOGEUSDT", + "bars": 206627, + "mean_abs_r5m_bp": 18.020024863429132, + "std_r5m_bp": 32.283639715297234, + "p95_abs_r5m_bp": 54.58300246754255, + "mean_abs_r1h_bp": 61.82485844598182 + }, + { + "symbol": "DOODUSDT", + "bars": 146130, + "mean_abs_r5m_bp": 25.497146117156227, + "std_r5m_bp": 48.279524530045585, + "p95_abs_r5m_bp": 79.89448614412184, + "mean_abs_r1h_bp": 91.61637633454193 + }, + { + "symbol": "DOTUSDT", + "bars": 209629, + "mean_abs_r5m_bp": 18.34739683412528, + "std_r5m_bp": 33.96763880503077, + "p95_abs_r5m_bp": 53.956889919339744, + "mean_abs_r1h_bp": 62.704101526822946 + }, + { + "symbol": "DRAMUSDT", + "bars": 38154, + "mean_abs_r5m_bp": 16.843354466742582, + "std_r5m_bp": 27.480862374878367, + "p95_abs_r5m_bp": 56.95134198927045, + "mean_abs_r1h_bp": 60.457206035753146 + }, + { + "symbol": "DYDXUSDT", + "bars": 209654, + "mean_abs_r5m_bp": 23.06475067167828, + "std_r5m_bp": 49.62213259489168, + "p95_abs_r5m_bp": 67.24974907673986, + "mean_abs_r1h_bp": 80.01774533867273 + }, + { + "symbol": "EDGEUSDT", + "bars": 55508, + "mean_abs_r5m_bp": 34.173858815870446, + "std_r5m_bp": 60.715852591633, + "p95_abs_r5m_bp": 104.4557724147828, + "mean_abs_r1h_bp": 119.2970638502446 + }, + { + "symbol": "EGLDUSDT", + "bars": 209635, + "mean_abs_r5m_bp": 19.11497284008667, + "std_r5m_bp": 34.43746671899421, + "p95_abs_r5m_bp": 56.14447598132196, + "mean_abs_r1h_bp": 66.53732541594815 + }, + { + "symbol": "EIGENUSDT", + "bars": 209633, + "mean_abs_r5m_bp": 28.116993787686553, + "std_r5m_bp": 47.7804526774824, + "p95_abs_r5m_bp": 82.0286677453717, + "mean_abs_r1h_bp": 97.81969241894 + }, + { + "symbol": "ELSAUSDT", + "bars": 71897, + "mean_abs_r5m_bp": 21.65443112933327, + "std_r5m_bp": 37.53939756302066, + "p95_abs_r5m_bp": 68.58808293783491, + "mean_abs_r1h_bp": 76.4098570789178 + }, + { + "symbol": "ENAUSDT", + "bars": 205627, + "mean_abs_r5m_bp": 27.90270416989622, + "std_r5m_bp": 49.9006284316975, + "p95_abs_r5m_bp": 82.03023073472093, + "mean_abs_r1h_bp": 95.99560477625349 + }, + { + "symbol": "ENSUSDT", + "bars": 209644, + "mean_abs_r5m_bp": 20.215550175841148, + "std_r5m_bp": 35.47269124339342, + "p95_abs_r5m_bp": 60.17776545737584, + "mean_abs_r1h_bp": 68.935361322934 + }, + { + "symbol": "ESPORTSUSDT", + "bars": 122837, + "mean_abs_r5m_bp": 40.953579856571885, + "std_r5m_bp": 95.03448194294023, + "p95_abs_r5m_bp": 132.3995212515816, + "mean_abs_r1h_bp": 145.39466505475184 + }, + { + "symbol": "ETCUSDT", + "bars": 208631, + "mean_abs_r5m_bp": 15.869517658009054, + "std_r5m_bp": 26.282203897441267, + "p95_abs_r5m_bp": 47.76756842277872, + "mean_abs_r1h_bp": 54.21092323881845 + }, + { + "symbol": "ETHFIUSDT", + "bars": 209632, + "mean_abs_r5m_bp": 26.051517025095514, + "std_r5m_bp": 51.370525445215335, + "p95_abs_r5m_bp": 75.64734183204553, + "mean_abs_r1h_bp": 89.35274025919443 + }, + { + "symbol": "ETHUSDT", + "bars": 208626, + "mean_abs_r5m_bp": 13.34559520716272, + "std_r5m_bp": 20.99190645966559, + "p95_abs_r5m_bp": 40.681642265727895, + "mean_abs_r1h_bp": 45.691493152841424 + }, + { + "symbol": "EULUSDT", + "bars": 100681, + "mean_abs_r5m_bp": 26.181562618079052, + "std_r5m_bp": 44.38263532968191, + "p95_abs_r5m_bp": 80.9540759257752, + "mean_abs_r1h_bp": 91.21421743101793 + }, + { + "symbol": "EWYUSDT", + "bars": 41623, + "mean_abs_r5m_bp": 12.618125739851369, + "std_r5m_bp": 20.940892369847422, + "p95_abs_r5m_bp": 44.1114289031894, + "mean_abs_r1h_bp": 44.827967637582894 + }, + { + "symbol": "FARTCOINUSDT", + "bars": 186477, + "mean_abs_r5m_bp": 37.99303708584084, + "std_r5m_bp": 66.39176737951871, + "p95_abs_r5m_bp": 116.44042966535679, + "mean_abs_r1h_bp": 130.81313048331694 + }, + { + "symbol": "FFUSDT", + "bars": 104914, + "mean_abs_r5m_bp": 21.939239790361217, + "std_r5m_bp": 45.18463065407809, + "p95_abs_r5m_bp": 72.61604563652166, + "mean_abs_r1h_bp": 78.75634233647733 + }, + { + "symbol": "FILUSDT", + "bars": 209630, + "mean_abs_r5m_bp": 19.824136261690878, + "std_r5m_bp": 38.10235149910635, + "p95_abs_r5m_bp": 58.619527359273555, + "mean_abs_r1h_bp": 67.81323144531325 + }, + { + "symbol": "FLOCKUSDT", + "bars": 180710, + "mean_abs_r5m_bp": 33.21077980004627, + "std_r5m_bp": 60.566989881109265, + "p95_abs_r5m_bp": 107.3355564310896, + "mean_abs_r1h_bp": 114.04870180307714 + }, + { + "symbol": "FOGOUSDT", + "bars": 75062, + "mean_abs_r5m_bp": 23.368499820105832, + "std_r5m_bp": 38.139678325001455, + "p95_abs_r5m_bp": 70.17572658646554, + "mean_abs_r1h_bp": 82.6643809738643 + }, + { + "symbol": "GALAUSDT", + "bars": 209634, + "mean_abs_r5m_bp": 22.373027652644115, + "std_r5m_bp": 44.21520256167105, + "p95_abs_r5m_bp": 65.4309858893587, + "mean_abs_r1h_bp": 76.13929116668623 + }, + { + "symbol": "GIGGLEUSDT", + "bars": 102077, + "mean_abs_r5m_bp": 31.998596305407414, + "std_r5m_bp": 64.46738923840094, + "p95_abs_r5m_bp": 101.86630906574456, + "mean_abs_r1h_bp": 111.31349747689269 + }, + { + "symbol": "GMTUSDT", + "bars": 209653, + "mean_abs_r5m_bp": 20.500632238973033, + "std_r5m_bp": 36.7591658771615, + "p95_abs_r5m_bp": 59.42653048928334, + "mean_abs_r1h_bp": 70.58392443146174 + }, + { + "symbol": "GRAMUSDT", + "bars": 27353, + "mean_abs_r5m_bp": 13.848121628609869, + "std_r5m_bp": 23.24195975950333, + "p95_abs_r5m_bp": 42.52309738530358, + "mean_abs_r1h_bp": 45.9797126494079 + }, + { + "symbol": "GRASSUSDT", + "bars": 204902, + "mean_abs_r5m_bp": 32.83101280990287, + "std_r5m_bp": 58.11432111043032, + "p95_abs_r5m_bp": 97.50321001269936, + "mean_abs_r1h_bp": 112.35583549851924 + }, + { + "symbol": "GRTUSDT", + "bars": 207629, + "mean_abs_r5m_bp": 19.633919110555098, + "std_r5m_bp": 32.17864547297628, + "p95_abs_r5m_bp": 57.54347041420759, + "mean_abs_r1h_bp": 67.86683831794426 + }, + { + "symbol": "GRVTUSDT", + "bars": 17121, + "mean_abs_r5m_bp": 29.229845544880927, + "std_r5m_bp": 47.21821742017209, + "p95_abs_r5m_bp": 88.75396327055682, + "mean_abs_r1h_bp": 103.92505352449592 + }, + { + "symbol": "HBARUSDT", + "bars": 208626, + "mean_abs_r5m_bp": 19.763316510849958, + "std_r5m_bp": 34.00964768091087, + "p95_abs_r5m_bp": 61.21758248575748, + "mean_abs_r1h_bp": 67.64008164150876 + }, + { + "symbol": "HNTUSDT", + "bars": 209646, + "mean_abs_r5m_bp": 23.592996028347944, + "std_r5m_bp": 39.94952319577377, + "p95_abs_r5m_bp": 74.17121569621547, + "mean_abs_r1h_bp": 83.16698148067604 + }, + { + "symbol": "HUMAUSDT", + "bars": 141230, + "mean_abs_r5m_bp": 26.23380060321176, + "std_r5m_bp": 47.432023160817565, + "p95_abs_r5m_bp": 77.8000086905477, + "mean_abs_r1h_bp": 90.6832044934436 + }, + { + "symbol": "HUSDT", + "bars": 132627, + "mean_abs_r5m_bp": 46.783710733639836, + "std_r5m_bp": 89.2249903982729, + "p95_abs_r5m_bp": 149.25528616288318, + "mean_abs_r1h_bp": 160.55355293739916 + }, + { + "symbol": "HYPEUSDT", + "bars": 188751, + "mean_abs_r5m_bp": 26.77755352629678, + "std_r5m_bp": 41.92560320299126, + "p95_abs_r5m_bp": 80.93696634100573, + "mean_abs_r1h_bp": 88.14106655711508 + }, + { + "symbol": "ICPUSDT", + "bars": 208630, + "mean_abs_r5m_bp": 20.985225810746186, + "std_r5m_bp": 34.240999083046574, + "p95_abs_r5m_bp": 62.19451665641296, + "mean_abs_r1h_bp": 71.39381836791276 + }, + { + "symbol": "IMXUSDT", + "bars": 209634, + "mean_abs_r5m_bp": 22.221383303215564, + "std_r5m_bp": 36.76254720078059, + "p95_abs_r5m_bp": 64.25155420054193, + "mean_abs_r1h_bp": 76.72642728741462 + }, + { + "symbol": "INITUSDT", + "bars": 153344, + "mean_abs_r5m_bp": 26.179848717819407, + "std_r5m_bp": 46.67940729015928, + "p95_abs_r5m_bp": 79.73689910700813, + "mean_abs_r1h_bp": 91.26377315621995 + }, + { + "symbol": "INJUSDT", + "bars": 203629, + "mean_abs_r5m_bp": 23.260027705208994, + "std_r5m_bp": 41.98842050377858, + "p95_abs_r5m_bp": 67.3938152818769, + "mean_abs_r1h_bp": 79.63140078729234 + }, + { + "symbol": "INXUSDT", + "bars": 69842, + "mean_abs_r5m_bp": 28.300063876939408, + "std_r5m_bp": 53.70250348246505, + "p95_abs_r5m_bp": 85.54372096658547, + "mean_abs_r1h_bp": 101.29553010715952 + }, + { + "symbol": "IOTAUSDT", + "bars": 209633, + "mean_abs_r5m_bp": 20.63087454593468, + "std_r5m_bp": 34.95267579674865, + "p95_abs_r5m_bp": 61.9270815225987, + "mean_abs_r1h_bp": 71.35620146742468 + }, + { + "symbol": "IRYSUSDT", + "bars": 88020, + "mean_abs_r5m_bp": 29.595644649304628, + "std_r5m_bp": 49.904066523191965, + "p95_abs_r5m_bp": 93.52560748716326, + "mean_abs_r1h_bp": 104.3194105856313 + }, + { + "symbol": "JASMYUSDT", + "bars": 207633, + "mean_abs_r5m_bp": 22.70533120495374, + "std_r5m_bp": 39.140372188804356, + "p95_abs_r5m_bp": 68.40881964430679, + "mean_abs_r1h_bp": 77.50418744135365 + }, + { + "symbol": "JSTUSDT", + "bars": 209654, + "mean_abs_r5m_bp": 11.435247417230922, + "std_r5m_bp": 20.436892658125444, + "p95_abs_r5m_bp": 37.05537140889969, + "mean_abs_r1h_bp": 42.1590762436415 + }, + { + "symbol": "JTOUSDT", + "bars": 209632, + "mean_abs_r5m_bp": 24.959309887583665, + "std_r5m_bp": 43.81871351590994, + "p95_abs_r5m_bp": 72.89373567720192, + "mean_abs_r1h_bp": 87.04339867502827 + }, + { + "symbol": "JUPUSDT", + "bars": 208629, + "mean_abs_r5m_bp": 23.300164619804285, + "std_r5m_bp": 43.33063486212613, + "p95_abs_r5m_bp": 67.16256489129448, + "mean_abs_r1h_bp": 80.2692761351287 + }, + { + "symbol": "KAITOUSDT", + "bars": 168575, + "mean_abs_r5m_bp": 25.209805975946754, + "std_r5m_bp": 41.49876495784051, + "p95_abs_r5m_bp": 74.97521610888599, + "mean_abs_r1h_bp": 87.61473019757939 + }, + { + "symbol": "KASUSDT", + "bars": 206633, + "mean_abs_r5m_bp": 20.59510605806794, + "std_r5m_bp": 48.09275700207359, + "p95_abs_r5m_bp": 61.87754726087278, + "mean_abs_r1h_bp": 71.25205334850367 + }, + { + "symbol": "KITEUSDT", + "bars": 96289, + "mean_abs_r5m_bp": 29.201769432378274, + "std_r5m_bp": 47.350157774143625, + "p95_abs_r5m_bp": 89.93791617706063, + "mean_abs_r1h_bp": 101.77707493993225 + }, + { + "symbol": "KLACUSDT", + "bars": 27239, + "mean_abs_r5m_bp": 11.327154342605503, + "std_r5m_bp": 25.034184123611496, + "p95_abs_r5m_bp": 52.47523217519445, + "mean_abs_r1h_bp": 47.91251504004325 + }, + { + "symbol": "KMNOUSDT", + "bars": 207220, + "mean_abs_r5m_bp": 23.0297437598867, + "std_r5m_bp": 37.195074665533966, + "p95_abs_r5m_bp": 69.42815329808737, + "mean_abs_r1h_bp": 82.15903399453384 + }, + { + "symbol": "KORUUSDT", + "bars": 21725, + "mean_abs_r5m_bp": 31.832319507026536, + "std_r5m_bp": 53.425449742139996, + "p95_abs_r5m_bp": 112.20164600634122, + "mean_abs_r1h_bp": 111.77122128461112 + }, + { + "symbol": "LABUSDT", + "bars": 96920, + "mean_abs_r5m_bp": 53.49786421282302, + "std_r5m_bp": 107.37592623188479, + "p95_abs_r5m_bp": 176.05832883323797, + "mean_abs_r1h_bp": 188.4988253815272 + }, + { + "symbol": "LAUSDT", + "bars": 138441, + "mean_abs_r5m_bp": 26.109520141418308, + "std_r5m_bp": 47.192689332631055, + "p95_abs_r5m_bp": 79.01660409673444, + "mean_abs_r1h_bp": 90.8521358380923 + }, + { + "symbol": "LDOUSDT", + "bars": 209631, + "mean_abs_r5m_bp": 23.41813902912024, + "std_r5m_bp": 46.451652251262374, + "p95_abs_r5m_bp": 68.85332510344804, + "mean_abs_r1h_bp": 80.27699387762125 + }, + { + "symbol": "LINEAUSDT", + "bars": 113040, + "mean_abs_r5m_bp": 22.525364311086047, + "std_r5m_bp": 53.76869616254597, + "p95_abs_r5m_bp": 68.42522202446892, + "mean_abs_r1h_bp": 76.69218533971537 + }, + { + "symbol": "LINKUSDT", + "bars": 209627, + "mean_abs_r5m_bp": 18.351537506390855, + "std_r5m_bp": 30.247117126741692, + "p95_abs_r5m_bp": 54.85233135110623, + "mean_abs_r1h_bp": 62.44249066076185 + }, + { + "symbol": "LITUSDT", + "bars": 78424, + "mean_abs_r5m_bp": 31.813702994477595, + "std_r5m_bp": 46.570344578563684, + "p95_abs_r5m_bp": 94.99344090299525, + "mean_abs_r1h_bp": 109.10969386765723 + }, + { + "symbol": "LONGXIAUSDT", + "bars": 4181, + "mean_abs_r5m_bp": 83.76134220947799, + "std_r5m_bp": 138.73927217755002, + "p95_abs_r5m_bp": 270.12107086884106, + "mean_abs_r1h_bp": 294.46715880134445 + }, + { + "symbol": "LSKUSDT", + "bars": 209630, + "mean_abs_r5m_bp": 19.976844022719007, + "std_r5m_bp": 43.015146772760104, + "p95_abs_r5m_bp": 61.23061604352866, + "mean_abs_r1h_bp": 70.85303027899126 + }, + { + "symbol": "LTCUSDT", + "bars": 209628, + "mean_abs_r5m_bp": 15.675498619854924, + "std_r5m_bp": 27.376583955448872, + "p95_abs_r5m_bp": 48.031897934447535, + "mean_abs_r1h_bp": 53.54584203658693 + }, + { + "symbol": "LYNUSDT", + "bars": 102976, + "mean_abs_r5m_bp": 38.8836310205459, + "std_r5m_bp": 73.81367046213533, + "p95_abs_r5m_bp": 126.98918645851674, + "mean_abs_r1h_bp": 133.51950709452785 + }, + { + "symbol": "MAGICUSDT", + "bars": 209648, + "mean_abs_r5m_bp": 23.352057755849216, + "std_r5m_bp": 43.29029720442573, + "p95_abs_r5m_bp": 70.5470297988997, + "mean_abs_r1h_bp": 80.4299363450048 + }, + { + "symbol": "MARSCOINUSDT", + "bars": 7898, + "mean_abs_r5m_bp": 73.2458894959522, + "std_r5m_bp": 121.56718121557155, + "p95_abs_r5m_bp": 221.5998467548904, + "mean_abs_r1h_bp": 265.8518249491968 + }, + { + "symbol": "MEGAUSDT", + "bars": 98031, + "mean_abs_r5m_bp": 32.44999938796589, + "std_r5m_bp": 64.6685556918381, + "p95_abs_r5m_bp": 121.7231206899099, + "mean_abs_r1h_bp": 104.0620982792037 + }, + { + "symbol": "METUSDT", + "bars": 101786, + "mean_abs_r5m_bp": 26.81167417664115, + "std_r5m_bp": 44.84046413802002, + "p95_abs_r5m_bp": 84.37206547457718, + "mean_abs_r1h_bp": 94.83381185473506 + }, + { + "symbol": "MINAUSDT", + "bars": 209644, + "mean_abs_r5m_bp": 21.00071003751233, + "std_r5m_bp": 39.190695150272, + "p95_abs_r5m_bp": 62.94959085477584, + "mean_abs_r1h_bp": 72.54926474972052 + }, + { + "symbol": "MMTUSDT", + "bars": 96900, + "mean_abs_r5m_bp": 29.58377519629142, + "std_r5m_bp": 74.91658540565876, + "p95_abs_r5m_bp": 89.07642833314893, + "mean_abs_r1h_bp": 101.55766276595254 + }, + { + "symbol": "MNTUSDT", + "bars": 208631, + "mean_abs_r5m_bp": 16.222798192952204, + "std_r5m_bp": 29.374156282140802, + "p95_abs_r5m_bp": 50.98335112245357, + "mean_abs_r1h_bp": 59.01555985998598 + }, + { + "symbol": "MONUSDT", + "bars": 88628, + "mean_abs_r5m_bp": 25.96308061978449, + "std_r5m_bp": 39.79045114060184, + "p95_abs_r5m_bp": 80.0694840717524, + "mean_abs_r1h_bp": 89.22867779991141 + }, + { + "symbol": "MOODENGUSDT", + "bars": 209544, + "mean_abs_r5m_bp": 33.13088840214432, + "std_r5m_bp": 58.31842660565095, + "p95_abs_r5m_bp": 104.45012101292181, + "mean_abs_r1h_bp": 113.30358202034493 + }, + { + "symbol": "MORPHOUSDT", + "bars": 194548, + "mean_abs_r5m_bp": 24.615841278928023, + "std_r5m_bp": 42.34946199923929, + "p95_abs_r5m_bp": 75.0794143687041, + "mean_abs_r1h_bp": 85.08053526804854 + }, + { + "symbol": "MRVLUSDT", + "bars": 39906, + "mean_abs_r5m_bp": 17.990863738972667, + "std_r5m_bp": 32.1292707115744, + "p95_abs_r5m_bp": 61.621400312702676, + "mean_abs_r1h_bp": 65.13934541474397 + }, + { + "symbol": "MUBARAKUSDT", + "bars": 160437, + "mean_abs_r5m_bp": 31.62468539096755, + "std_r5m_bp": 58.74938837228615, + "p95_abs_r5m_bp": 94.95768734900767, + "mean_abs_r1h_bp": 107.59630656987778 + }, + { + "symbol": "MUUSDT", + "bars": 41618, + "mean_abs_r5m_bp": 16.770653962763348, + "std_r5m_bp": 28.156937987591025, + "p95_abs_r5m_bp": 57.67266524098238, + "mean_abs_r1h_bp": 59.11590810105944 + }, + { + "symbol": "MUUUSDT", + "bars": 21459, + "mean_abs_r5m_bp": 26.486809465375096, + "std_r5m_bp": 47.1109979325785, + "p95_abs_r5m_bp": 96.31042681001098, + "mean_abs_r1h_bp": 96.04053460687686 + }, + { + "symbol": "MYXUSDT", + "bars": 120836, + "mean_abs_r5m_bp": 43.681447086612295, + "std_r5m_bp": 84.41409873114338, + "p95_abs_r5m_bp": 139.34783743684218, + "mean_abs_r1h_bp": 149.5623011941492 + }, + { + "symbol": "NBISUSDT", + "bars": 36152, + "mean_abs_r5m_bp": 20.290153642008477, + "std_r5m_bp": 35.38633402328555, + "p95_abs_r5m_bp": 70.24301550977441, + "mean_abs_r1h_bp": 74.86724897133081 + }, + { + "symbol": "NEARUSDT", + "bars": 205626, + "mean_abs_r5m_bp": 22.334752691643818, + "std_r5m_bp": 36.875776405707526, + "p95_abs_r5m_bp": 65.45978299261223, + "mean_abs_r1h_bp": 76.25079284411102 + }, + { + "symbol": "NIGHTUSDT", + "bars": 84523, + "mean_abs_r5m_bp": 23.614840365502438, + "std_r5m_bp": 41.430017373073404, + "p95_abs_r5m_bp": 73.40364784826758, + "mean_abs_r1h_bp": 82.57495154259911 + }, + { + "symbol": "NILUSDT", + "bars": 160510, + "mean_abs_r5m_bp": 29.574541693183438, + "std_r5m_bp": 50.31890205196862, + "p95_abs_r5m_bp": 89.9487915066777, + "mean_abs_r1h_bp": 104.02262238931111 + }, + { + "symbol": "NIULAIUSDT", + "bars": 9092, + "mean_abs_r5m_bp": 74.85797617692835, + "std_r5m_bp": 118.63937375173697, + "p95_abs_r5m_bp": 236.5764235627621, + "mean_abs_r1h_bp": 263.87057805299213 + }, + { + "symbol": "NMRUSDT", + "bars": 209631, + "mean_abs_r5m_bp": 20.849830117009326, + "std_r5m_bp": 41.09953506668882, + "p95_abs_r5m_bp": 62.77998800661953, + "mean_abs_r1h_bp": 70.65902675914421 + }, + { + "symbol": "NOMUSDT", + "bars": 104420, + "mean_abs_r5m_bp": 29.623332736735037, + "std_r5m_bp": 64.73089622994392, + "p95_abs_r5m_bp": 93.12752169751967, + "mean_abs_r1h_bp": 103.28084220669643 + }, + { + "symbol": "ONDOUSDT", + "bars": 209626, + "mean_abs_r5m_bp": 22.62710322626834, + "std_r5m_bp": 40.18648361033287, + "p95_abs_r5m_bp": 66.74159573350673, + "mean_abs_r1h_bp": 76.61087277165785 + }, + { + "symbol": "OPGUSDT", + "bars": 45703, + "mean_abs_r5m_bp": 30.183098457016317, + "std_r5m_bp": 53.79790754359615, + "p95_abs_r5m_bp": 90.73272685388876, + "mean_abs_r1h_bp": 105.97520363803748 + }, + { + "symbol": "OPNUSDT", + "bars": 66088, + "mean_abs_r5m_bp": 32.235348151215995, + "std_r5m_bp": 56.540632397946794, + "p95_abs_r5m_bp": 98.0858711253177, + "mean_abs_r1h_bp": 110.8126760189489 + }, + { + "symbol": "OPUSDT", + "bars": 208629, + "mean_abs_r5m_bp": 21.750089514710556, + "std_r5m_bp": 35.746984770755994, + "p95_abs_r5m_bp": 62.72340262975716, + "mean_abs_r1h_bp": 75.13005491615974 + }, + { + "symbol": "ORCAUSDT", + "bars": 209641, + "mean_abs_r5m_bp": 23.057476491311757, + "std_r5m_bp": 44.481056133363204, + "p95_abs_r5m_bp": 70.67259738161003, + "mean_abs_r1h_bp": 82.30055702060206 + }, + { + "symbol": "ORCLUSDT", + "bars": 41920, + "mean_abs_r5m_bp": 10.351342530843826, + "std_r5m_bp": 19.886755417714753, + "p95_abs_r5m_bp": 38.86840122381496, + "mean_abs_r1h_bp": 39.07533157333879 + }, + { + "symbol": "ORDIUSDT", + "bars": 209635, + "mean_abs_r5m_bp": 25.167124497801016, + "std_r5m_bp": 52.54658674715313, + "p95_abs_r5m_bp": 72.44840434213458, + "mean_abs_r1h_bp": 85.38807611038919 + }, + { + "symbol": "PAXGUSDT", + "bars": 208632, + "mean_abs_r5m_bp": 4.169678433105975, + "std_r5m_bp": 7.429967879099343, + "p95_abs_r5m_bp": 13.920508849372126, + "mean_abs_r1h_bp": 14.704849803648997 + }, + { + "symbol": "PEAQUSDT", + "bars": 195421, + "mean_abs_r5m_bp": 27.842584478764376, + "std_r5m_bp": 47.3117716247343, + "p95_abs_r5m_bp": 89.6662053791301, + "mean_abs_r1h_bp": 101.6505238473823 + }, + { + "symbol": "PENDLEUSDT", + "bars": 208632, + "mean_abs_r5m_bp": 23.334224551973588, + "std_r5m_bp": 38.34144823872225, + "p95_abs_r5m_bp": 66.73241944132214, + "mean_abs_r1h_bp": 81.80503706908607 + }, + { + "symbol": "PENGUUSDT", + "bars": 187278, + "mean_abs_r5m_bp": 28.321005457199924, + "std_r5m_bp": 50.63571991787544, + "p95_abs_r5m_bp": 84.86666643096713, + "mean_abs_r1h_bp": 98.49599474590642 + }, + { + "symbol": "PHAROSUSDT", + "bars": 39624, + "mean_abs_r5m_bp": 22.30009432041536, + "std_r5m_bp": 39.214369432699236, + "p95_abs_r5m_bp": 72.08817748465668, + "mean_abs_r1h_bp": 80.99423773929377 + }, + { + "symbol": "PHAUSDT", + "bars": 209633, + "mean_abs_r5m_bp": 26.75159702623474, + "std_r5m_bp": 47.25630059321534, + "p95_abs_r5m_bp": 80.28685852408569, + "mean_abs_r1h_bp": 92.23122891647832 + }, + { + "symbol": "PIEVERSEUSDT", + "bars": 91686, + "mean_abs_r5m_bp": 37.360018579094444, + "std_r5m_bp": 69.06790477966118, + "p95_abs_r5m_bp": 122.3703673313572, + "mean_abs_r1h_bp": 130.81311962316556 + }, + { + "symbol": "PIPPINUSDT", + "bars": 178429, + "mean_abs_r5m_bp": 44.99560015031408, + "std_r5m_bp": 91.73959078513018, + "p95_abs_r5m_bp": 144.72211741154837, + "mean_abs_r1h_bp": 156.84020976964533 + }, + { + "symbol": "PLUMEUSDT", + "bars": 176400, + "mean_abs_r5m_bp": 27.99243979468657, + "std_r5m_bp": 53.28507356428585, + "p95_abs_r5m_bp": 83.66299130913738, + "mean_abs_r1h_bp": 96.25499568387298 + }, + { + "symbol": "POLUSDT", + "bars": 208630, + "mean_abs_r5m_bp": 17.783197730755617, + "std_r5m_bp": 29.65613740143714, + "p95_abs_r5m_bp": 53.26244282674164, + "mean_abs_r1h_bp": 61.42797859986913 + }, + { + "symbol": "PONSUSDT", + "bars": 8201, + "mean_abs_r5m_bp": 62.01334437854188, + "std_r5m_bp": 89.07276051485573, + "p95_abs_r5m_bp": 183.45386254676012, + "mean_abs_r1h_bp": 215.86007346860396 + }, + { + "symbol": "POPCATUSDT", + "bars": 209643, + "mean_abs_r5m_bp": 30.756131673881953, + "std_r5m_bp": 50.718167036032796, + "p95_abs_r5m_bp": 92.90868937845698, + "mean_abs_r1h_bp": 104.58101394998883 + }, + { + "symbol": "PUMPFUNUSDT", + "bars": 128340, + "mean_abs_r5m_bp": 30.57972212409251, + "std_r5m_bp": 49.4726709140537, + "p95_abs_r5m_bp": 92.10371449337977, + "mean_abs_r1h_bp": 104.8524024006705 + }, + { + "symbol": "PYTHUSDT", + "bars": 209633, + "mean_abs_r5m_bp": 23.22617516592778, + "std_r5m_bp": 39.18114189537807, + "p95_abs_r5m_bp": 67.05808867191188, + "mean_abs_r1h_bp": 81.38628770775922 + }, + { + "symbol": "QNTUSDT", + "bars": 209626, + "mean_abs_r5m_bp": 18.368572951217935, + "std_r5m_bp": 30.170606057873382, + "p95_abs_r5m_bp": 55.52287969276643, + "mean_abs_r1h_bp": 62.84858265429264 + }, + { + "symbol": "QUSDT", + "bars": 106837, + "mean_abs_r5m_bp": 37.95459420408626, + "std_r5m_bp": 82.17868700483729, + "p95_abs_r5m_bp": 122.66985499061134, + "mean_abs_r1h_bp": 133.59215634143237 + }, + { + "symbol": "RAREUSDT", + "bars": 209632, + "mean_abs_r5m_bp": 22.212167743296668, + "std_r5m_bp": 48.2269655981594, + "p95_abs_r5m_bp": 67.3403218134394, + "mean_abs_r1h_bp": 75.80929794317926 + }, + { + "symbol": "RAYDIUMUSDT", + "bars": 209632, + "mean_abs_r5m_bp": 25.612682074655506, + "std_r5m_bp": 41.77834038471834, + "p95_abs_r5m_bp": 77.4907567362526, + "mean_abs_r1h_bp": 88.53648675402889 + }, + { + "symbol": "RENDERUSDT", + "bars": 208632, + "mean_abs_r5m_bp": 22.823283280537183, + "std_r5m_bp": 49.35592132474683, + "p95_abs_r5m_bp": 66.37126792196885, + "mean_abs_r1h_bp": 78.16168133373044 + }, + { + "symbol": "REUSDT", + "bars": 29475, + "mean_abs_r5m_bp": 34.41181245651747, + "std_r5m_bp": 58.002428524343635, + "p95_abs_r5m_bp": 107.28647791397853, + "mean_abs_r1h_bp": 119.35885252193061 + }, + { + "symbol": "RIVERUSDT", + "bars": 98326, + "mean_abs_r5m_bp": 48.22566230298559, + "std_r5m_bp": 83.57150853046227, + "p95_abs_r5m_bp": 164.94042005395212, + "mean_abs_r1h_bp": 167.49950002958963 + }, + { + "symbol": "RUNEUSDT", + "bars": 209634, + "mean_abs_r5m_bp": 19.170027598800722, + "std_r5m_bp": 32.03562104630493, + "p95_abs_r5m_bp": 58.309203107931026, + "mean_abs_r1h_bp": 67.29368075011314 + }, + { + "symbol": "SAGAUSDT", + "bars": 209632, + "mean_abs_r5m_bp": 28.40321773445242, + "std_r5m_bp": 49.72991614224732, + "p95_abs_r5m_bp": 82.78319878577634, + "mean_abs_r1h_bp": 97.18507920234964 + }, + { + "symbol": "SAHARAUSDT", + "bars": 134031, + "mean_abs_r5m_bp": 23.20601358673174, + "std_r5m_bp": 46.63751038678036, + "p95_abs_r5m_bp": 71.96801081782719, + "mean_abs_r1h_bp": 80.48407825989116 + }, + { + "symbol": "SAMSUNGUSDT", + "bars": 33618, + "mean_abs_r5m_bp": 17.136929305891506, + "std_r5m_bp": 29.187343723001486, + "p95_abs_r5m_bp": 59.49401423916512, + "mean_abs_r1h_bp": 62.33326038727968 + }, + { + "symbol": "SANDUSDT", + "bars": 209634, + "mean_abs_r5m_bp": 19.576951071832166, + "std_r5m_bp": 34.840298534700736, + "p95_abs_r5m_bp": 57.99245984469747, + "mean_abs_r1h_bp": 67.55499260061269 + }, + { + "symbol": "SEIUSDT", + "bars": 209628, + "mean_abs_r5m_bp": 20.8721983470605, + "std_r5m_bp": 34.52951407594023, + "p95_abs_r5m_bp": 61.45231123692005, + "mean_abs_r1h_bp": 72.4046510750826 + }, + { + "symbol": "SHIB1000USDT", + "bars": 209631, + "mean_abs_r5m_bp": 16.991837949797514, + "std_r5m_bp": 27.204948491336246, + "p95_abs_r5m_bp": 50.99122962913105, + "mean_abs_r1h_bp": 58.14221671403204 + }, + { + "symbol": "SIRENUSDT", + "bars": 160072, + "mean_abs_r5m_bp": 38.35613988364175, + "std_r5m_bp": 88.26036175809249, + "p95_abs_r5m_bp": 125.20515077257043, + "mean_abs_r1h_bp": 132.57429411442462 + }, + { + "symbol": "SKHYNIXUSDT", + "bars": 33620, + "mean_abs_r5m_bp": 19.86031399043102, + "std_r5m_bp": 32.94930242870882, + "p95_abs_r5m_bp": 69.79035079861262, + "mean_abs_r1h_bp": 70.40273825725362 + }, + { + "symbol": "SKHYUSDT", + "bars": 22970, + "mean_abs_r5m_bp": 16.98278116134976, + "std_r5m_bp": 29.886558287917453, + "p95_abs_r5m_bp": 61.47705355708054, + "mean_abs_r1h_bp": 60.558419381961926 + }, + { + "symbol": "SKRUSDT", + "bars": 72421, + "mean_abs_r5m_bp": 30.486971097076566, + "std_r5m_bp": 64.3227741404937, + "p95_abs_r5m_bp": 99.9638390344453, + "mean_abs_r1h_bp": 108.35179715800777 + }, + { + "symbol": "SKYUSDT", + "bars": 119095, + "mean_abs_r5m_bp": 16.126314166495536, + "std_r5m_bp": 28.381926482253238, + "p95_abs_r5m_bp": 48.357257988156526, + "mean_abs_r1h_bp": 56.84230217079085 + }, + { + "symbol": "SNDKUSDT", + "bars": 44213, + "mean_abs_r5m_bp": 19.14467090414691, + "std_r5m_bp": 33.10760682859791, + "p95_abs_r5m_bp": 66.2982684273916, + "mean_abs_r1h_bp": 68.32125718927561 + }, + { + "symbol": "SNXUSDT", + "bars": 209648, + "mean_abs_r5m_bp": 22.31651363235181, + "std_r5m_bp": 37.36715117420622, + "p95_abs_r5m_bp": 66.47534231617402, + "mean_abs_r1h_bp": 77.8546882285701 + }, + { + "symbol": "SNXXUSDT", + "bars": 22041, + "mean_abs_r5m_bp": 36.98902255289389, + "std_r5m_bp": 66.7876625963384, + "p95_abs_r5m_bp": 131.17979013538096, + "mean_abs_r1h_bp": 132.74086589784744 + }, + { + "symbol": "SOLUSDT", + "bars": 209626, + "mean_abs_r5m_bp": 16.20334512749723, + "std_r5m_bp": 25.272668585638687, + "p95_abs_r5m_bp": 47.976045299460424, + "mean_abs_r1h_bp": 55.33195480066128 + }, + { + "symbol": "SOONUSDT", + "bars": 142128, + "mean_abs_r5m_bp": 30.18815559670125, + "std_r5m_bp": 59.222489103752004, + "p95_abs_r5m_bp": 93.78117386161675, + "mean_abs_r1h_bp": 103.80350738108922 + }, + { + "symbol": "SOXSUSDT", + "bars": 21444, + "mean_abs_r5m_bp": 24.917419544915532, + "std_r5m_bp": 42.448952353459475, + "p95_abs_r5m_bp": 85.79673810684115, + "mean_abs_r1h_bp": 91.1404523263848 + }, + { + "symbol": "SPCXUSDT", + "bars": 30379, + "mean_abs_r5m_bp": 12.139478144143444, + "std_r5m_bp": 23.97262240924734, + "p95_abs_r5m_bp": 46.455881515390374, + "mean_abs_r1h_bp": 43.929233967351465 + }, + { + "symbol": "SPKUSDT", + "bars": 134947, + "mean_abs_r5m_bp": 23.964398663304074, + "std_r5m_bp": 44.755793106226555, + "p95_abs_r5m_bp": 73.99966495907573, + "mean_abs_r1h_bp": 83.69064250417428 + }, + { + "symbol": "SPXUSDT", + "bars": 201461, + "mean_abs_r5m_bp": 33.517936899567175, + "std_r5m_bp": 51.7845376948027, + "p95_abs_r5m_bp": 98.73202343552735, + "mean_abs_r1h_bp": 118.26847084528035 + }, + { + "symbol": "STABLEUSDT", + "bars": 93998, + "mean_abs_r5m_bp": 30.34668526776247, + "std_r5m_bp": 55.8673078182596, + "p95_abs_r5m_bp": 106.30460434958154, + "mean_abs_r1h_bp": 107.86416823192428 + }, + { + "symbol": "STGUSDT", + "bars": 208635, + "mean_abs_r5m_bp": 21.39202681086658, + "std_r5m_bp": 37.64684475730091, + "p95_abs_r5m_bp": 66.48665810306247, + "mean_abs_r1h_bp": 75.9673076736603 + }, + { + "symbol": "STONKUSDT", + "bars": 3036, + "mean_abs_r5m_bp": 52.800554419998775, + "std_r5m_bp": 75.31844106739985, + "p95_abs_r5m_bp": 158.56600069890388, + "mean_abs_r1h_bp": 243.37581827925422 + }, + { + "symbol": "STRKUSDT", + "bars": 209631, + "mean_abs_r5m_bp": 24.125418990428287, + "std_r5m_bp": 42.71689277546, + "p95_abs_r5m_bp": 70.6538254600117, + "mean_abs_r1h_bp": 82.8195014976737 + }, + { + "symbol": "STXUSDT", + "bars": 208634, + "mean_abs_r5m_bp": 20.48057602913211, + "std_r5m_bp": 34.58365607189213, + "p95_abs_r5m_bp": 60.76539459732664, + "mean_abs_r1h_bp": 70.48146650345994 + }, + { + "symbol": "SUIUSDT", + "bars": 209627, + "mean_abs_r5m_bp": 21.26497434039849, + "std_r5m_bp": 41.272744768578036, + "p95_abs_r5m_bp": 62.88405669666611, + "mean_abs_r1h_bp": 72.68523930057951 + }, + { + "symbol": "SUSDT", + "bars": 179010, + "mean_abs_r5m_bp": 22.15981427560085, + "std_r5m_bp": 40.78969039621073, + "p95_abs_r5m_bp": 65.46668226314445, + "mean_abs_r1h_bp": 78.0653888897253 + }, + { + "symbol": "SUSHIUSDT", + "bars": 209645, + "mean_abs_r5m_bp": 22.243822953131005, + "std_r5m_bp": 40.82927512029975, + "p95_abs_r5m_bp": 65.5118313189412, + "mean_abs_r1h_bp": 76.79683149473365 + }, + { + "symbol": "SYRUPUSDT", + "bars": 143307, + "mean_abs_r5m_bp": 22.553284861290603, + "std_r5m_bp": 34.11866245836622, + "p95_abs_r5m_bp": 67.53894843418928, + "mean_abs_r1h_bp": 78.47577531350436 + }, + { + "symbol": "TAIKOUSDT", + "bars": 209652, + "mean_abs_r5m_bp": 19.841715047892993, + "std_r5m_bp": 38.74064458110083, + "p95_abs_r5m_bp": 62.26237288432523, + "mean_abs_r1h_bp": 69.07070093009423 + }, + { + "symbol": "TAOUSDT", + "bars": 209628, + "mean_abs_r5m_bp": 24.05466528237916, + "std_r5m_bp": 39.40863033878565, + "p95_abs_r5m_bp": 70.070719167302, + "mean_abs_r1h_bp": 81.1205669040307 + }, + { + "symbol": "THETAUSDT", + "bars": 209651, + "mean_abs_r5m_bp": 21.108784035927005, + "std_r5m_bp": 37.2819736544793, + "p95_abs_r5m_bp": 60.80812976879707, + "mean_abs_r1h_bp": 72.45145691328572 + }, + { + "symbol": "TIAUSDT", + "bars": 208630, + "mean_abs_r5m_bp": 24.035511019378912, + "std_r5m_bp": 44.81382929246078, + "p95_abs_r5m_bp": 68.91660839285309, + "mean_abs_r1h_bp": 82.20539253688852 + }, + { + "symbol": "TQQQUSDT", + "bars": 27811, + "mean_abs_r5m_bp": 10.742174176053515, + "std_r5m_bp": 18.050268153395127, + "p95_abs_r5m_bp": 37.49049525874913, + "mean_abs_r1h_bp": 38.93141745208117 + }, + { + "symbol": "TRBUSDT", + "bars": 209649, + "mean_abs_r5m_bp": 21.644788858985688, + "std_r5m_bp": 39.226957109779, + "p95_abs_r5m_bp": 63.02872915597721, + "mean_abs_r1h_bp": 72.60297866564517 + }, + { + "symbol": "TRUMPUSDT", + "bars": 178111, + "mean_abs_r5m_bp": 20.903348595400242, + "std_r5m_bp": 60.32409570050787, + "p95_abs_r5m_bp": 63.22465739487026, + "mean_abs_r1h_bp": 70.60007021402626 + }, + { + "symbol": "TRUSTUSDT", + "bars": 91710, + "mean_abs_r5m_bp": 22.060901536992823, + "std_r5m_bp": 43.45318222234013, + "p95_abs_r5m_bp": 69.05358485036571, + "mean_abs_r1h_bp": 76.44736257651654 + }, + { + "symbol": "TRXUSDT", + "bars": 206633, + "mean_abs_r5m_bp": 7.002744971441075, + "std_r5m_bp": 13.544884754672607, + "p95_abs_r5m_bp": 22.17232265193347, + "mean_abs_r1h_bp": 26.506621480297166 + }, + { + "symbol": "TSEMUSDT", + "bars": 21162, + "mean_abs_r5m_bp": 16.553438011054368, + "std_r5m_bp": 30.622944635372594, + "p95_abs_r5m_bp": 61.89071894620923, + "mean_abs_r1h_bp": 59.71129921275481 + }, + { + "symbol": "UAIUSDT", + "bars": 90861, + "mean_abs_r5m_bp": 40.38974252325904, + "std_r5m_bp": 71.0019251971544, + "p95_abs_r5m_bp": 124.87767975662051, + "mean_abs_r1h_bp": 143.87861786525818 + }, + { + "symbol": "UNITREEUSDT", + "bars": 16356, + "mean_abs_r5m_bp": 18.950759343755646, + "std_r5m_bp": 51.096939680221205, + "p95_abs_r5m_bp": 79.05526736770517, + "mean_abs_r1h_bp": 57.93967575466197 + }, + { + "symbol": "UNIUSDT", + "bars": 209628, + "mean_abs_r5m_bp": 21.091464046816913, + "std_r5m_bp": 35.62704840026974, + "p95_abs_r5m_bp": 61.87520923368289, + "mean_abs_r1h_bp": 72.19846636777741 + }, + { + "symbol": "USELESSUSDT", + "bars": 122277, + "mean_abs_r5m_bp": 43.682935218943896, + "std_r5m_bp": 73.2518464059948, + "p95_abs_r5m_bp": 129.24354697451346, + "mean_abs_r1h_bp": 153.4891694198435 + }, + { + "symbol": "USUSDT", + "bars": 83639, + "mean_abs_r5m_bp": 39.47960013756108, + "std_r5m_bp": 73.11046082833481, + "p95_abs_r5m_bp": 125.72890765591357, + "mean_abs_r1h_bp": 137.28732750976638 + }, + { + "symbol": "VETUSDT", + "bars": 208635, + "mean_abs_r5m_bp": 19.08743087170387, + "std_r5m_bp": 30.894436217254437, + "p95_abs_r5m_bp": 57.587259688260424, + "mean_abs_r1h_bp": 65.92091733726514 + }, + { + "symbol": "VIRTUALUSDT", + "bars": 200586, + "mean_abs_r5m_bp": 29.366915569867245, + "std_r5m_bp": 50.33247122369621, + "p95_abs_r5m_bp": 88.13235357555445, + "mean_abs_r1h_bp": 102.81484687293654 + }, + { + "symbol": "VVVUSDT", + "bars": 174300, + "mean_abs_r5m_bp": 30.731584433801384, + "std_r5m_bp": 49.67332077427452, + "p95_abs_r5m_bp": 93.91882059664417, + "mean_abs_r1h_bp": 109.540074512167 + }, + { + "symbol": "WAVESUSDT", + "bars": 209643, + "mean_abs_r5m_bp": 18.836582932294974, + "std_r5m_bp": 35.82961511479868, + "p95_abs_r5m_bp": 62.01338620141225, + "mean_abs_r1h_bp": 62.065167072577694 + }, + { + "symbol": "WENSTOCKUSDT", + "bars": 20316, + "mean_abs_r5m_bp": 9.375792472003326, + "std_r5m_bp": 22.907863366233624, + "p95_abs_r5m_bp": 36.29413382346746, + "mean_abs_r1h_bp": 32.652086662243654 + }, + { + "symbol": "WIFUSDT", + "bars": 209631, + "mean_abs_r5m_bp": 26.483853747710675, + "std_r5m_bp": 46.98682717049948, + "p95_abs_r5m_bp": 78.18963350561381, + "mean_abs_r1h_bp": 90.38476366262408 + }, + { + "symbol": "WLDUSDT", + "bars": 209627, + "mean_abs_r5m_bp": 25.689903218640342, + "std_r5m_bp": 49.65017162912696, + "p95_abs_r5m_bp": 74.844205064547, + "mean_abs_r1h_bp": 87.05494146464514 + }, + { + "symbol": "WLFIUSDT", + "bars": 115614, + "mean_abs_r5m_bp": 20.864190323627387, + "std_r5m_bp": 41.05757096113327, + "p95_abs_r5m_bp": 65.66764622307673, + "mean_abs_r1h_bp": 71.49376994155962 + }, + { + "symbol": "WUSDT", + "bars": 209632, + "mean_abs_r5m_bp": 24.074918821050485, + "std_r5m_bp": 41.1240242882516, + "p95_abs_r5m_bp": 69.71770432650182, + "mean_abs_r1h_bp": 83.28985058842235 + }, + { + "symbol": "XAIUSDT", + "bars": 209651, + "mean_abs_r5m_bp": 24.532641595479426, + "std_r5m_bp": 44.50887670758337, + "p95_abs_r5m_bp": 71.79864268256819, + "mean_abs_r1h_bp": 83.36202799903765 + }, + { + "symbol": "XAUTUSDT", + "bars": 156497, + "mean_abs_r5m_bp": 4.386096580134171, + "std_r5m_bp": 7.736599442594967, + "p95_abs_r5m_bp": 14.784549225281829, + "mean_abs_r1h_bp": 15.682442386891289 + }, + { + "symbol": "XCNUSDT", + "bars": 209647, + "mean_abs_r5m_bp": 26.049577446810158, + "std_r5m_bp": 50.90700937728146, + "p95_abs_r5m_bp": 89.72886758661147, + "mean_abs_r1h_bp": 86.59552555276287 + }, + { + "symbol": "XDCUSDT", + "bars": 170498, + "mean_abs_r5m_bp": 12.447848629270398, + "std_r5m_bp": 21.581295283360333, + "p95_abs_r5m_bp": 40.35735144041645, + "mean_abs_r1h_bp": 46.017033776339915 + }, + { + "symbol": "XLMUSDT", + "bars": 209628, + "mean_abs_r5m_bp": 18.841963486940486, + "std_r5m_bp": 32.822275678656204, + "p95_abs_r5m_bp": 58.92442789774366, + "mean_abs_r1h_bp": 64.94606967844179 + }, + { + "symbol": "XMRUSDT", + "bars": 208629, + "mean_abs_r5m_bp": 18.199527312870174, + "std_r5m_bp": 28.446099132290804, + "p95_abs_r5m_bp": 54.6365396958417, + "mean_abs_r1h_bp": 62.51694212810207 + }, + { + "symbol": "XPLUSDT", + "bars": 115894, + "mean_abs_r5m_bp": 31.881607516801107, + "std_r5m_bp": 55.4669203637702, + "p95_abs_r5m_bp": 98.45313600910163, + "mean_abs_r1h_bp": 110.26819007051134 + }, + { + "symbol": "XRPUSDT", + "bars": 209626, + "mean_abs_r5m_bp": 16.25382364930991, + "std_r5m_bp": 28.97231602788928, + "p95_abs_r5m_bp": 50.85232967363712, + "mean_abs_r1h_bp": 55.21991345051859 + }, + { + "symbol": "XTZUSDT", + "bars": 209654, + "mean_abs_r5m_bp": 17.904546413617716, + "std_r5m_bp": 28.828539327157756, + "p95_abs_r5m_bp": 53.987861192495764, + "mean_abs_r1h_bp": 62.07875579553141 + }, + { + "symbol": "ZAMAUSDT", + "bars": 75586, + "mean_abs_r5m_bp": 28.741684471438948, + "std_r5m_bp": 48.058173102139776, + "p95_abs_r5m_bp": 91.60169627701633, + "mean_abs_r1h_bp": 100.81231279390194 + }, + { + "symbol": "ZECUSDT", + "bars": 209626, + "mean_abs_r5m_bp": 26.761347801758927, + "std_r5m_bp": 43.16852071263508, + "p95_abs_r5m_bp": 81.78140557669343, + "mean_abs_r1h_bp": 91.89582686250763 + }, + { + "symbol": "ZENUSDT", + "bars": 209634, + "mean_abs_r5m_bp": 25.599966414827847, + "std_r5m_bp": 43.504068864544394, + "p95_abs_r5m_bp": 78.48673787556226, + "mean_abs_r1h_bp": 87.9106130610706 + }, + { + "symbol": "ZESTUSDT", + "bars": 33289, + "mean_abs_r5m_bp": 27.14454638140972, + "std_r5m_bp": 51.592900893074464, + "p95_abs_r5m_bp": 93.70661404843068, + "mean_abs_r1h_bp": 92.71532192530057 + }, + { + "symbol": "ZHIPUUSDT", + "bars": 18016, + "mean_abs_r5m_bp": 17.94542424584587, + "std_r5m_bp": 37.45807892356559, + "p95_abs_r5m_bp": 68.83687671218293, + "mean_abs_r1h_bp": 68.00515014528754 + }, + { + "symbol": "ZKUSDT", + "bars": 209647, + "mean_abs_r5m_bp": 24.29876446391941, + "std_r5m_bp": 42.25817373616259, + "p95_abs_r5m_bp": 70.46038097108409, + "mean_abs_r1h_bp": 84.54675603443079 + }, + { + "symbol": "ZROUSDT", + "bars": 209629, + "mean_abs_r5m_bp": 24.941834279748957, + "std_r5m_bp": 39.238123702538545, + "p95_abs_r5m_bp": 72.91961411863478, + "mean_abs_r1h_bp": 86.50811667250854 + } + ], + "p_star": [ + { + "target_pct": 0.1, + "stop_pct": 0.1, + "taker": 105.0, + "maker": 70.0 + }, + { + "target_pct": 0.2, + "stop_pct": 0.2, + "taker": 77.5, + "maker": 60.0 + }, + { + "target_pct": 0.3, + "stop_pct": 0.2, + "taker": 62.0, + "maker": 48.0 + }, + { + "target_pct": 0.5, + "stop_pct": 0.3, + "taker": 51.2, + "maker": 42.5 + }, + { + "target_pct": 1.0, + "stop_pct": 0.5, + "taker": 40.7, + "maker": 36.0 + }, + { + "target_pct": 1.0, + "stop_pct": 1.0, + "taker": 55.5, + "maker": 52.0 + }, + { + "target_pct": 2.0, + "stop_pct": 1.0, + "taker": 37.0, + "maker": 34.7 + } + ] +} \ No newline at end of file diff --git a/reports/stand_reconcile.json b/reports/stand_reconcile.json new file mode 100644 index 0000000..93b9c5f --- /dev/null +++ b/reports/stand_reconcile.json @@ -0,0 +1,53 @@ +{ + "pairs": { + "BTCUSDT": { + "prod_n": 107, + "stand_n": 107, + "prod_pf": 0.4799, + "stand_pf": 0.4799, + "prod_sum_pnl": -0.2231, + "stand_sum_pnl": -0.2231, + "bars_30m": 34602 + }, + "ETHUSDT": { + "prod_n": 105, + "stand_n": 105, + "prod_pf": 0.3737, + "stand_pf": 0.3737, + "prod_sum_pnl": -0.3143, + "stand_sum_pnl": -0.3143, + "bars_30m": 34770 + }, + "SOLUSDT": { + "prod_n": 82, + "stand_n": 82, + "prod_pf": 0.3604, + "stand_pf": 0.3604, + "prod_sum_pnl": -0.2581, + "stand_sum_pnl": -0.2581, + "bars_30m": 34937 + }, + "XRPUSDT": { + "prod_n": 114, + "stand_n": 114, + "prod_pf": 0.6517, + "stand_pf": 0.6517, + "prod_sum_pnl": -0.1706, + "stand_sum_pnl": -0.1706, + "bars_30m": 34937 + }, + "ZECUSDT": { + "prod_n": 107, + "stand_n": 107, + "prod_pf": 0.9739, + "stand_pf": 0.9739, + "prod_sum_pnl": -0.0139, + "stand_sum_pnl": -0.0139, + "bars_30m": 34937 + } + }, + "fee_rt": 0.0011, + "engine": "app.trading.mean_reversion.run_mr_backtest", + "stand": "tools/mr_engine.stand_mr_backtest", + "source": "30м из ресемпла 5м данных" +} \ No newline at end of file diff --git a/reports/validate_5m.json b/reports/validate_5m.json new file mode 100644 index 0000000..40c8cf6 --- /dev/null +++ b/reports/validate_5m.json @@ -0,0 +1,1256 @@ +{ + "dup": 0, + "pairs": { + "BTCUSDT": { + "bars": 207626, + "coverage": 0.9905, + "dup": 0 + }, + "SOLUSDT": { + "bars": 209626, + "coverage": 1.0, + "dup": 0 + }, + "ETHUSDT": { + "bars": 208626, + "coverage": 0.9952, + "dup": 0 + }, + "XRPUSDT": { + "bars": 209626, + "coverage": 1.0, + "dup": 0 + }, + "ZECUSDT": { + "bars": 209626, + "coverage": 1.0, + "dup": 0 + }, + "QNTUSDT": { + "bars": 209626, + "coverage": 1.0, + "dup": 0 + }, + "NEARUSDT": { + "bars": 205626, + "coverage": 0.9809, + "dup": 0 + }, + "HBARUSDT": { + "bars": 208626, + "coverage": 0.9952, + "dup": 0 + }, + "ONDOUSDT": { + "bars": 209626, + "coverage": 1.0, + "dup": 0 + }, + "SUIUSDT": { + "bars": 209627, + "coverage": 1.0, + "dup": 0 + }, + "ENAUSDT": { + "bars": 205627, + "coverage": 0.9809, + "dup": 0 + }, + "LINKUSDT": { + "bars": 209627, + "coverage": 1.0, + "dup": 0 + }, + "DOGEUSDT": { + "bars": 206627, + "coverage": 0.9857, + "dup": 0 + }, + "WLDUSDT": { + "bars": 209627, + "coverage": 1.0, + "dup": 0 + }, + "ADAUSDT": { + "bars": 209628, + "coverage": 1.0, + "dup": 0 + }, + "XLMUSDT": { + "bars": 209628, + "coverage": 1.0, + "dup": 0 + }, + "TAOUSDT": { + "bars": 209628, + "coverage": 1.0, + "dup": 0 + }, + "UNIUSDT": { + "bars": 209628, + "coverage": 1.0, + "dup": 0 + }, + "HYPEUSDT": { + "bars": 188751, + "coverage": 0.9895, + "dup": 0 + }, + "1000PEPEUSDT": { + "bars": 208628, + "coverage": 0.9952, + "dup": 0 + }, + "LTCUSDT": { + "bars": 209628, + "coverage": 1.0, + "dup": 0 + }, + "ARBUSDT": { + "bars": 207628, + "coverage": 0.9905, + "dup": 0 + }, + "PUMPFUNUSDT": { + "bars": 128340, + "coverage": 1.0, + "dup": 0 + }, + "BTWUSDT": { + "bars": 33271, + "coverage": 1.0, + "dup": 0 + }, + "SNDKUSDT": { + "bars": 44213, + "coverage": 1.0, + "dup": 0 + }, + "XAUTUSDT": { + "bars": 156497, + "coverage": 1.0, + "dup": 0 + }, + "AVAXUSDT": { + "bars": 209628, + "coverage": 1.0, + "dup": 0 + }, + "CASHCATUSDT": { + "bars": 15682, + "coverage": 1.0, + "dup": 0 + }, + "MARSCOINUSDT": { + "bars": 7898, + "coverage": 1.0, + "dup": 0 + }, + "SPCXUSDT": { + "bars": 30379, + "coverage": 1.0, + "dup": 0 + }, + "XPLUSDT": { + "bars": 115894, + "coverage": 1.0, + "dup": 0 + }, + "SEIUSDT": { + "bars": 209628, + "coverage": 1.0, + "dup": 0 + }, + "AAVEUSDT": { + "bars": 209629, + "coverage": 1.0, + "dup": 0 + }, + "KORUUSDT": { + "bars": 21725, + "coverage": 1.0, + "dup": 0 + }, + "TRUMPUSDT": { + "bars": 178111, + "coverage": 1.0, + "dup": 0 + }, + "MONUSDT": { + "bars": 88628, + "coverage": 1.0, + "dup": 0 + }, + "SOONUSDT": { + "bars": 142128, + "coverage": 1.0, + "dup": 0 + }, + "ZROUSDT": { + "bars": 209629, + "coverage": 1.0, + "dup": 0 + }, + "BCHUSDT": { + "bars": 209629, + "coverage": 1.0, + "dup": 0 + }, + "BNBUSDT": { + "bars": 209629, + "coverage": 1.0, + "dup": 0 + }, + "PENGUUSDT": { + "bars": 187278, + "coverage": 1.0, + "dup": 0 + }, + "GRAMUSDT": { + "bars": 27353, + "coverage": 0.9647, + "dup": 0 + }, + "VIRTUALUSDT": { + "bars": 200586, + "coverage": 1.0, + "dup": 0 + }, + "PONSUSDT": { + "bars": 8201, + "coverage": 1.0, + "dup": 0 + }, + "ALGOUSDT": { + "bars": 209629, + "coverage": 1.0, + "dup": 0 + }, + "FARTCOINUSDT": { + "bars": 186477, + "coverage": 1.0, + "dup": 0 + }, + "GRASSUSDT": { + "bars": 204902, + "coverage": 1.0, + "dup": 0 + }, + "LITUSDT": { + "bars": 78424, + "coverage": 1.0, + "dup": 0 + }, + "USELESSUSDT": { + "bars": 122277, + "coverage": 0.9532, + "dup": 0 + }, + "INJUSDT": { + "bars": 203629, + "coverage": 0.9714, + "dup": 0 + }, + "QUSDT": { + "bars": 106837, + "coverage": 0.9727, + "dup": 0 + }, + "JUPUSDT": { + "bars": 208629, + "coverage": 0.9952, + "dup": 0 + }, + "SAMSUNGUSDT": { + "bars": 33618, + "coverage": 1.0, + "dup": 0 + }, + "DOTUSDT": { + "bars": 209629, + "coverage": 1.0, + "dup": 0 + }, + "GRTUSDT": { + "bars": 207629, + "coverage": 0.9905, + "dup": 0 + }, + "AKEUSDT": { + "bars": 104785, + "coverage": 0.9905, + "dup": 0 + }, + "APTUSDT": { + "bars": 209629, + "coverage": 1.0, + "dup": 0 + }, + "CRVUSDT": { + "bars": 207629, + "coverage": 0.9905, + "dup": 0 + }, + "MUUSDT": { + "bars": 41618, + "coverage": 1.0, + "dup": 0 + }, + "VVVUSDT": { + "bars": 174300, + "coverage": 0.9943, + "dup": 0 + }, + "SKHYUSDT": { + "bars": 22970, + "coverage": 1.0, + "dup": 0 + }, + "OPUSDT": { + "bars": 208629, + "coverage": 0.9952, + "dup": 0 + }, + "DASHUSDT": { + "bars": 207629, + "coverage": 0.9905, + "dup": 0 + }, + "XMRUSDT": { + "bars": 208629, + "coverage": 0.9952, + "dup": 0 + }, + "TIAUSDT": { + "bars": 208630, + "coverage": 0.9952, + "dup": 0 + }, + "AEROUSDT": { + "bars": 209629, + "coverage": 1.0, + "dup": 0 + }, + "SKHYNIXUSDT": { + "bars": 33620, + "coverage": 1.0, + "dup": 0 + }, + "MUBARAKUSDT": { + "bars": 160437, + "coverage": 0.9938, + "dup": 0 + }, + "FILUSDT": { + "bars": 209630, + "coverage": 1.0, + "dup": 0 + }, + "SOXSUSDT": { + "bars": 21444, + "coverage": 1.0, + "dup": 0 + }, + "DRAMUSDT": { + "bars": 38154, + "coverage": 1.0, + "dup": 0 + }, + "AZTECUSDT": { + "bars": 65825, + "coverage": 1.0, + "dup": 0 + }, + "ASTERUSDT": { + "bars": 107853, + "coverage": 1.0, + "dup": 0 + }, + "LSKUSDT": { + "bars": 209630, + "coverage": 1.0, + "dup": 0 + }, + "ATOMUSDT": { + "bars": 209630, + "coverage": 1.0, + "dup": 0 + }, + "ICPUSDT": { + "bars": 208630, + "coverage": 0.9952, + "dup": 0 + }, + "POLUSDT": { + "bars": 208630, + "coverage": 0.9952, + "dup": 0 + }, + "FLOCKUSDT": { + "bars": 180710, + "coverage": 1.0, + "dup": 0 + }, + "BXUSDT": { + "bars": 21144, + "coverage": 1.0, + "dup": 0 + }, + "SNXXUSDT": { + "bars": 22041, + "coverage": 1.0, + "dup": 0 + }, + "MNTUSDT": { + "bars": 208631, + "coverage": 0.9952, + "dup": 0 + }, + "1000BONKUSDT": { + "bars": 209631, + "coverage": 1.0, + "dup": 0 + }, + "WIFUSDT": { + "bars": 209631, + "coverage": 1.0, + "dup": 0 + }, + "LDOUSDT": { + "bars": 209631, + "coverage": 1.0, + "dup": 0 + }, + "ETCUSDT": { + "bars": 208631, + "coverage": 0.9952, + "dup": 0 + }, + "WLFIUSDT": { + "bars": 115614, + "coverage": 1.0, + "dup": 0 + }, + "NIULAIUSDT": { + "bars": 9092, + "coverage": 1.0, + "dup": 0 + }, + "CCUSDT": { + "bars": 95698, + "coverage": 1.0, + "dup": 0 + }, + "NILUSDT": { + "bars": 160510, + "coverage": 1.0, + "dup": 0 + }, + "NMRUSDT": { + "bars": 209631, + "coverage": 1.0, + "dup": 0 + }, + "USUSDT": { + "bars": 83639, + "coverage": 1.0, + "dup": 0 + }, + "STRKUSDT": { + "bars": 209631, + "coverage": 1.0, + "dup": 0 + }, + "PAXGUSDT": { + "bars": 208632, + "coverage": 0.9952, + "dup": 0 + }, + "SHIB1000USDT": { + "bars": 209631, + "coverage": 1.0, + "dup": 0 + }, + "SAGAUSDT": { + "bars": 209632, + "coverage": 1.0, + "dup": 0 + }, + "ETHFIUSDT": { + "bars": 209632, + "coverage": 1.0, + "dup": 0 + }, + "RENDERUSDT": { + "bars": 208632, + "coverage": 0.9952, + "dup": 0 + }, + "RAREUSDT": { + "bars": 209632, + "coverage": 1.0, + "dup": 0 + }, + "PENDLEUSDT": { + "bars": 208632, + "coverage": 0.9952, + "dup": 0 + }, + "RAYDIUMUSDT": { + "bars": 209632, + "coverage": 1.0, + "dup": 0 + }, + "METUSDT": { + "bars": 101786, + "coverage": 1.0, + "dup": 0 + }, + "SPXUSDT": { + "bars": 201461, + "coverage": 1.0, + "dup": 0 + }, + "WUSDT": { + "bars": 209632, + "coverage": 1.0, + "dup": 0 + }, + "CROUSDT": { + "bars": 207632, + "coverage": 0.9905, + "dup": 0 + }, + "ARXUSDT": { + "bars": 28142, + "coverage": 1.0, + "dup": 0 + }, + "TRXUSDT": { + "bars": 206633, + "coverage": 0.9857, + "dup": 0 + }, + "FFUSDT": { + "bars": 104914, + "coverage": 1.0, + "dup": 0 + }, + "EIGENUSDT": { + "bars": 209633, + "coverage": 1.0, + "dup": 0 + }, + "2ZUSDT": { + "bars": 104060, + "coverage": 1.0, + "dup": 0 + }, + "SKYUSDT": { + "bars": 119095, + "coverage": 1.0, + "dup": 0 + }, + "KASUSDT": { + "bars": 206633, + "coverage": 0.9857, + "dup": 0 + }, + "IOTAUSDT": { + "bars": 209633, + "coverage": 1.0, + "dup": 0 + }, + "JTOUSDT": { + "bars": 209632, + "coverage": 1.0, + "dup": 0 + }, + "PYTHUSDT": { + "bars": 209633, + "coverage": 1.0, + "dup": 0 + }, + "JASMYUSDT": { + "bars": 207633, + "coverage": 0.9905, + "dup": 0 + }, + "IRYSUSDT": { + "bars": 88020, + "coverage": 1.0, + "dup": 0 + }, + "XDCUSDT": { + "bars": 170498, + "coverage": 0.9942, + "dup": 0 + }, + "RUNEUSDT": { + "bars": 209634, + "coverage": 1.0, + "dup": 0 + }, + "CAPUSDT": { + "bars": 26421, + "coverage": 1.0, + "dup": 0 + }, + "PHAUSDT": { + "bars": 209633, + "coverage": 1.0, + "dup": 0 + }, + "IMXUSDT": { + "bars": 209634, + "coverage": 1.0, + "dup": 0 + }, + "GALAUSDT": { + "bars": 209634, + "coverage": 1.0, + "dup": 0 + }, + "MORPHOUSDT": { + "bars": 194548, + "coverage": 1.0, + "dup": 0 + }, + "LONGXIAUSDT": { + "bars": 4181, + "coverage": 1.0, + "dup": 0 + }, + "ARKUSDT": { + "bars": 209634, + "coverage": 1.0, + "dup": 0 + }, + "PLUMEUSDT": { + "bars": 176400, + "coverage": 1.0, + "dup": 0 + }, + "AMDSTOCKUSDT": { + "bars": 35577, + "coverage": 1.0, + "dup": 0 + }, + "KAITOUSDT": { + "bars": 168575, + "coverage": 1.0, + "dup": 0 + }, + "STXUSDT": { + "bars": 208634, + "coverage": 0.9952, + "dup": 0 + }, + "ZENUSDT": { + "bars": 209634, + "coverage": 1.0, + "dup": 0 + }, + "KITEUSDT": { + "bars": 96289, + "coverage": 1.0, + "dup": 0 + }, + "EWYUSDT": { + "bars": 41623, + "coverage": 1.0, + "dup": 0 + }, + "NBISUSDT": { + "bars": 36152, + "coverage": 1.0, + "dup": 0 + }, + "BRUSDT": { + "bars": 159511, + "coverage": 0.9938, + "dup": 0 + }, + "APEUSDT": { + "bars": 207635, + "coverage": 0.9905, + "dup": 0 + }, + "STONKUSDT": { + "bars": 3036, + "coverage": 1.0, + "dup": 0 + }, + "VETUSDT": { + "bars": 208635, + "coverage": 0.9952, + "dup": 0 + }, + "ARUSDT": { + "bars": 207635, + "coverage": 0.9905, + "dup": 0 + }, + "DGAIUSDT": { + "bars": 7078, + "coverage": 1.0, + "dup": 0 + }, + "TQQQUSDT": { + "bars": 27811, + "coverage": 1.0, + "dup": 0 + }, + "APEXUSDT": { + "bars": 105274, + "coverage": 1.0, + "dup": 0 + }, + "EGLDUSDT": { + "bars": 209635, + "coverage": 1.0, + "dup": 0 + }, + "LYNUSDT": { + "bars": 102976, + "coverage": 1.0, + "dup": 0 + }, + "CAKEUSDT": { + "bars": 209635, + "coverage": 1.0, + "dup": 0 + }, + "KIIUSDT": { + "bars": 144, + "coverage": 1.0, + "dup": 0 + }, + "STGUSDT": { + "bars": 208635, + "coverage": 0.9952, + "dup": 0 + }, + "ORDIUSDT": { + "bars": 209635, + "coverage": 1.0, + "dup": 0 + }, + "ZAMAUSDT": { + "bars": 75586, + "coverage": 1.0, + "dup": 0 + }, + "COTIUSDT": { + "bars": 209635, + "coverage": 1.0, + "dup": 0 + }, + "CHIPUSDT": { + "bars": 47660, + "coverage": 1.0, + "dup": 0 + }, + "SANDUSDT": { + "bars": 209634, + "coverage": 1.0, + "dup": 0 + }, + "INXUSDT": { + "bars": 69842, + "coverage": 1.0, + "dup": 0 + }, + "ORCAUSDT": { + "bars": 209641, + "coverage": 1.0, + "dup": 0 + }, + "AGIUSDT": { + "bars": 209641, + "coverage": 1.0, + "dup": 0 + }, + "AVNTUSDT": { + "bars": 110687, + "coverage": 1.0, + "dup": 0 + }, + "CFGUSDT": { + "bars": 56374, + "coverage": 1.0, + "dup": 0 + }, + "BEATUSDT": { + "bars": 89701, + "coverage": 1.0, + "dup": 0 + }, + "4STOCKUSDT": { + "bars": 5596, + "coverage": 1.0, + "dup": 0 + }, + "MEGAUSDT": { + "bars": 98031, + "coverage": 1.0, + "dup": 0 + }, + "AXSUSDT": { + "bars": 209642, + "coverage": 1.0, + "dup": 0 + }, + "SYRUPUSDT": { + "bars": 143307, + "coverage": 1.0, + "dup": 0 + }, + "EDGEUSDT": { + "bars": 55508, + "coverage": 1.0, + "dup": 0 + }, + "0GUSDT": { + "bars": 108177, + "coverage": 1.0, + "dup": 0 + }, + "HUSDT": { + "bars": 132627, + "coverage": 1.0, + "dup": 0 + }, + "PIEVERSEUSDT": { + "bars": 91686, + "coverage": 1.0, + "dup": 0 + }, + "POPCATUSDT": { + "bars": 209643, + "coverage": 1.0, + "dup": 0 + }, + "OPNUSDT": { + "bars": 66088, + "coverage": 1.0, + "dup": 0 + }, + "KLACUSDT": { + "bars": 27239, + "coverage": 1.0, + "dup": 0 + }, + "WAVESUSDT": { + "bars": 209643, + "coverage": 1.0, + "dup": 0 + }, + "BILLUSDT": { + "bars": 41945, + "coverage": 1.0, + "dup": 0 + }, + "MUUUSDT": { + "bars": 21459, + "coverage": 1.0, + "dup": 0 + }, + "DATAUSDT": { + "bars": 23472, + "coverage": 1.0, + "dup": 0 + }, + "BSVUSDT": { + "bars": 209643, + "coverage": 1.0, + "dup": 0 + }, + "UAIUSDT": { + "bars": 90861, + "coverage": 1.0, + "dup": 0 + }, + "MINAUSDT": { + "bars": 209644, + "coverage": 1.0, + "dup": 0 + }, + "HUMAUSDT": { + "bars": 141230, + "coverage": 1.0, + "dup": 0 + }, + "MRVLUSDT": { + "bars": 39906, + "coverage": 1.0, + "dup": 0 + }, + "KMNOUSDT": { + "bars": 207220, + "coverage": 1.0, + "dup": 0 + }, + "ENSUSDT": { + "bars": 209644, + "coverage": 1.0, + "dup": 0 + }, + "ORCLUSDT": { + "bars": 41920, + "coverage": 1.0, + "dup": 0 + }, + "CVXUSDT": { + "bars": 209644, + "coverage": 1.0, + "dup": 0 + }, + "SUSHIUSDT": { + "bars": 209645, + "coverage": 1.0, + "dup": 0 + }, + "NOMUSDT": { + "bars": 104420, + "coverage": 1.0, + "dup": 0 + }, + "MOODENGUSDT": { + "bars": 209544, + "coverage": 1.0, + "dup": 0 + }, + "MMTUSDT": { + "bars": 96900, + "coverage": 1.0, + "dup": 0 + }, + "UNITREEUSDT": { + "bars": 16356, + "coverage": 1.0, + "dup": 0 + }, + "BROCCOLIUSDT": { + "bars": 170326, + "coverage": 1.0, + "dup": 0 + }, + "AAOIUSDT": { + "bars": 32125, + "coverage": 1.0, + "dup": 0 + }, + "ZESTUSDT": { + "bars": 33289, + "coverage": 1.0, + "dup": 0 + }, + "HNTUSDT": { + "bars": 209646, + "coverage": 1.0, + "dup": 0 + }, + "DEEPUSDT": { + "bars": 204894, + "coverage": 1.0, + "dup": 0 + }, + "B3USDT": { + "bars": 170927, + "coverage": 1.0, + "dup": 0 + }, + "RIVERUSDT": { + "bars": 98326, + "coverage": 1.0, + "dup": 0 + }, + "ATHUSDT": { + "bars": 209646, + "coverage": 1.0, + "dup": 0 + }, + "INITUSDT": { + "bars": 153344, + "coverage": 1.0, + "dup": 0 + }, + "LABUSDT": { + "bars": 96920, + "coverage": 1.0, + "dup": 0 + }, + "XCNUSDT": { + "bars": 209647, + "coverage": 1.0, + "dup": 0 + }, + "ZKUSDT": { + "bars": 209647, + "coverage": 1.0, + "dup": 0 + }, + "LINEAUSDT": { + "bars": 113040, + "coverage": 1.0, + "dup": 0 + }, + "SUSDT": { + "bars": 179010, + "coverage": 1.0, + "dup": 0 + }, + "SAHARAUSDT": { + "bars": 134031, + "coverage": 1.0, + "dup": 0 + }, + "NIGHTUSDT": { + "bars": 84523, + "coverage": 1.0, + "dup": 0 + }, + "REUSDT": { + "bars": 29475, + "coverage": 1.0, + "dup": 0 + }, + "SNXUSDT": { + "bars": 209648, + "coverage": 1.0, + "dup": 0 + }, + "MAGICUSDT": { + "bars": 209648, + "coverage": 1.0, + "dup": 0 + }, + "AVGOUSDT": { + "bars": 40197, + "coverage": 1.0, + "dup": 0 + }, + "BEUSDT": { + "bars": 35592, + "coverage": 1.0, + "dup": 0 + }, + "TSEMUSDT": { + "bars": 21162, + "coverage": 1.0, + "dup": 0 + }, + "CPUSDT": { + "bars": 7086, + "coverage": 1.0, + "dup": 0 + }, + "LAUSDT": { + "bars": 138441, + "coverage": 1.0, + "dup": 0 + }, + "COWUSDT": { + "bars": 198577, + "coverage": 1.0, + "dup": 0 + }, + "PIPPINUSDT": { + "bars": 178429, + "coverage": 1.0, + "dup": 0 + }, + "TRBUSDT": { + "bars": 209649, + "coverage": 1.0, + "dup": 0 + }, + "GIGGLEUSDT": { + "bars": 102077, + "coverage": 1.0, + "dup": 0 + }, + "OPGUSDT": { + "bars": 45703, + "coverage": 1.0, + "dup": 0 + }, + "1000FLOKIUSDT": { + "bars": 209650, + "coverage": 1.0, + "dup": 0 + }, + "PEAQUSDT": { + "bars": 195421, + "coverage": 1.0, + "dup": 0 + }, + "ALLOUSDT": { + "bars": 91705, + "coverage": 1.0, + "dup": 0 + }, + "EULUSDT": { + "bars": 100681, + "coverage": 1.0, + "dup": 0 + }, + "SIRENUSDT": { + "bars": 160072, + "coverage": 1.0, + "dup": 0 + }, + "TRUSTUSDT": { + "bars": 91710, + "coverage": 1.0, + "dup": 0 + }, + "APPSTOCKUSDT": { + "bars": 21464, + "coverage": 1.0, + "dup": 0 + }, + "BANKUSDT": { + "bars": 146469, + "coverage": 1.0, + "dup": 0 + }, + "ESPORTSUSDT": { + "bars": 122837, + "coverage": 1.0, + "dup": 0 + }, + "SPKUSDT": { + "bars": 134947, + "coverage": 1.0, + "dup": 0 + }, + "THETAUSDT": { + "bars": 209651, + "coverage": 1.0, + "dup": 0 + }, + "XAIUSDT": { + "bars": 209651, + "coverage": 1.0, + "dup": 0 + }, + "GRVTUSDT": { + "bars": 17121, + "coverage": 1.0, + "dup": 0 + }, + "ZHIPUUSDT": { + "bars": 18016, + "coverage": 1.0, + "dup": 0 + }, + "ACEUSDT": { + "bars": 209652, + "coverage": 1.0, + "dup": 0 + }, + "BRETTUSDT": { + "bars": 209652, + "coverage": 1.0, + "dup": 0 + }, + "SKRUSDT": { + "bars": 72421, + "coverage": 1.0, + "dup": 0 + }, + "BIOUSDT": { + "bars": 185065, + "coverage": 1.0, + "dup": 0 + }, + "TAIKOUSDT": { + "bars": 209652, + "coverage": 1.0, + "dup": 0 + }, + "STABLEUSDT": { + "bars": 93998, + "coverage": 1.0, + "dup": 0 + }, + "BERAUSDT": { + "bars": 172625, + "coverage": 1.0, + "dup": 0 + }, + "ELSAUSDT": { + "bars": 71897, + "coverage": 1.0, + "dup": 0 + }, + "COMPUSDT": { + "bars": 209653, + "coverage": 1.0, + "dup": 0 + }, + "ARKMUSDT": { + "bars": 209653, + "coverage": 1.0, + "dup": 0 + }, + "GMTUSDT": { + "bars": 209653, + "coverage": 1.0, + "dup": 0 + }, + "FOGOUSDT": { + "bars": 75062, + "coverage": 1.0, + "dup": 0 + }, + "XTZUSDT": { + "bars": 209654, + "coverage": 1.0, + "dup": 0 + }, + "JSTUSDT": { + "bars": 209654, + "coverage": 1.0, + "dup": 0 + }, + "CBRSUSDT": { + "bars": 38177, + "coverage": 1.0, + "dup": 0 + }, + "DOODUSDT": { + "bars": 146130, + "coverage": 1.0, + "dup": 0 + }, + "BEAMUSDT": { + "bars": 209654, + "coverage": 1.0, + "dup": 0 + }, + "WENSTOCKUSDT": { + "bars": 20316, + "coverage": 1.0, + "dup": 0 + }, + "PHAROSUSDT": { + "bars": 39624, + "coverage": 1.0, + "dup": 0 + }, + "DYDXUSDT": { + "bars": 209654, + "coverage": 1.0, + "dup": 0 + }, + "MYXUSDT": { + "bars": 120836, + "coverage": 1.0, + "dup": 0 + } + }, + "checked_at": "2026-09-28T23:13:23.818885+00:00" +} \ No newline at end of file diff --git a/results/summary_5m.csv b/results/summary_5m.csv new file mode 100644 index 0000000..0e99128 --- /dev/null +++ b/results/summary_5m.csv @@ -0,0 +1,15 @@ +method,n_trades,ev_bp,ev_bp_oos,t_boot,placebo_ev_bp,rand_ev_bp,sharpe,max_dd_pct,by_year_ok +prod_mr_5m,105089,-4.46,-5.19,-7.65,0.0,0.0,-5.64,-4701.58,нет +clv_flow,257017,-9.88,-12.24,-4.15,0.0,0.0,-2.86,-25403.75,нет +pairs_btc_eth,1944,-13.22,-9.81,-5.85,0.0,0.0,-4.85,-256.08,нет +intraday_momentum,125771,-13.8,-9.2,-7.35,0.0,0.0,-4.8,-17786.84,нет +xs_reversal_15m,873500,-14.29,-14.31,-29.1,-16.69,-16.26,-20.44,-124648.41,нет +xs_reversal_costaware,873400,-14.29,-14.31,-29.1,0.0,0.0,-20.43,-124641.26,нет +hour_xs_reversal,291200,-14.7,-14.77,-35.34,0.0,0.0,-26.15,-42757.57,нет +ou_ema_costaware,1047089,-14.74,-13.66,-19.88,0.0,0.0,-13.43,-154108.02,нет +ou_ema_revert,1123903,-14.84,-13.81,-21.05,0.0,0.0,-14.46,-166544.66,нет +lead_lag_btc,2311277,-15.64,-15.67,-31.17,0.0,0.0,-23.18,-361023.26,нет +liquidity_sweep,1166054,-15.85,-16.0,-34.55,0.0,0.0,-25.57,-184485.18,нет +vwap_revert,2655400,-16.42,-16.35,-28.62,0.0,0.0,-23.3,-435944.62,нет +vol_breakout,738494,-18.51,-19.43,-24.03,0.0,0.0,-19.13,-136612.38,нет +xs_momentum_15m,873500,-18.87,-19.04,-38.48,0.0,0.0,-27.0,-164670.55,нет diff --git a/results/summary_5m.json b/results/summary_5m.json new file mode 100644 index 0000000..2906518 --- /dev/null +++ b/results/summary_5m.json @@ -0,0 +1,19 @@ +[ + { + "method": "prod_mr_5m", + "n_trades": 105089, + "ev_bp": -4.46, + "ev_bp_oos": -5.19, + "t_boot": -7.65, + "placebo_ev_bp": 0.0, + "rand_ev_bp": 0.0, + "sharpe": -5.64, + "max_dd_pct": -4701.58, + "by_year_ok": "нет", + "by_year": { + "2024": -4.72, + "2025": -3.83, + "2026": -5.0 + } + } +] \ No newline at end of file diff --git a/results/summary_5m_maker.csv b/results/summary_5m_maker.csv new file mode 100644 index 0000000..71bb3d8 --- /dev/null +++ b/results/summary_5m_maker.csv @@ -0,0 +1,15 @@ +method,n_trades,ev_bp,ev_bp_oos,t_boot,placebo_ev_bp,rand_ev_bp,sharpe,max_dd_pct,by_year_ok +prod_mr_5m,105089,2.54,1.81,4.31,0.0,0.0,3.21,-206.28,да +clv_flow,257017,-2.88,-5.24,-1.24,0.0,0.0,-0.83,-7515.49,нет +pairs_btc_eth,1944,-6.22,-2.81,-2.67,0.0,0.0,-2.25,-125.6,нет +intraday_momentum,125771,-6.8,-2.2,-3.64,0.0,0.0,-2.37,-9612.33,нет +xs_reversal_15m,873500,-7.29,-7.31,-14.85,-9.69,-9.26,-10.43,-63587.41,нет +xs_reversal_costaware,873400,-7.29,-7.31,-14.85,0.0,0.0,-10.43,-63587.26,нет +hour_xs_reversal,291200,-7.7,-7.77,-18.56,0.0,0.0,-13.7,-22437.45,нет +ou_ema_costaware,1047089,-7.74,-6.66,-10.64,0.0,0.0,-7.1,-81067.29,нет +ou_ema_revert,1123903,-7.84,-6.81,-11.5,0.0,0.0,-7.71,-88139.97,нет +lead_lag_btc,2311277,-8.64,-8.67,-19.41,0.0,0.0,-13.19,-199385.49,нет +liquidity_sweep,1166054,-8.85,-9.0,-21.45,0.0,0.0,-14.93,-102923.7,нет +vwap_revert,2655400,-9.42,-9.35,-18.64,0.0,0.0,-13.83,-250067.88,нет +vol_breakout,738494,-11.51,-12.43,-16.09,0.0,0.0,-12.11,-84977.02,нет +xs_momentum_15m,873500,-11.87,-12.04,-24.21,0.0,0.0,-16.99,-103609.55,нет diff --git a/results/summary_5m_maker.json b/results/summary_5m_maker.json new file mode 100644 index 0000000..63f8a51 --- /dev/null +++ b/results/summary_5m_maker.json @@ -0,0 +1,19 @@ +[ + { + "method": "prod_mr_5m", + "n_trades": 105089, + "ev_bp": 2.54, + "ev_bp_oos": 1.81, + "t_boot": 4.31, + "placebo_ev_bp": 0.0, + "rand_ev_bp": 0.0, + "sharpe": 3.21, + "max_dd_pct": -206.28, + "by_year_ok": "да", + "by_year": { + "2024": 2.28, + "2025": 3.17, + "2026": 2.0 + } + } +] \ No newline at end of file