#!/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())