Archived
research(5m): прогон 14 методов на 250 парах, сверка движка, издержки, графики
ЧТО: code/ (стенд и методы), results/ (сводки), charts/ (3 графика), reports/ (издержки, плацебо, по годам), docs/REJECTED.md. ПОЧЕМУ: цель — найти кандидатов на 5м или доказать с цифрами их отсутствие. КАК ПРОВЕРЕНО: движок сверен с прод-движком TON-бота 1:1; плацебо и рандом-контроль в отчёте; бутстрап по дням.
This commit is contained in:
@@ -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())
|
||||
Reference in New Issue
Block a user