This repository has been archived on 2026-10-22. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
anatoly c6671410dc research(5m): прогон 14 методов на 250 парах, сверка движка, издержки, графики
ЧТО: code/ (стенд и методы), results/ (сводки), charts/ (3 графика), reports/ (издержки, плацебо, по годам), docs/REJECTED.md.

ПОЧЕМУ: цель — найти кандидатов на 5м или доказать с цифрами их отсутствие.

КАК ПРОВЕРЕНО: движок сверен с прод-движком TON-бота 1:1; плацебо и рандом-контроль в отчёте; бутстрап по дням.
2026-09-29 02:46:08 +03:00

100 lines
6.1 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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())