#!/usr/bin/env python3
"""Run reproducible black-swan window backtests for the volatility family.

Run from /data/freqtrade on the deployment host after downloading the stress
dataset documented in docs/strategy-iterations.md. Each window is tested with
and without Freqtrade protections; disabling protections does not alter the
strategy's entry, sizing, stoploss, or exposure-cap logic.
"""

from __future__ import annotations

import argparse
import json
import subprocess
import zipfile
from pathlib import Path


WINDOWS = {
    "covid_2020": "20200308-20200321",
    "may_deleveraging_2021": "20210510-20210525",
    "terra_2022": "20220505-20220517",
    "lenders_2022": "20220608-20220620",
    "ftx_2022": "20221102-20221116",
    "carry_unwind_2024": "20240801-20240811",
    "liquidation_2025": "20251006-20251018",
    "capitulation_2026": "20260201-20260211",
}
STRATEGIES = ["VolatilityBreakout", "VolatilityBreakoutD36"]
PAIRS = [
    "BTC/USDT:USDT",
    "ETH/USDT:USDT",
    "BNB/USDT:USDT",
    "SOL/USDT:USDT",
]
RESULT_DIR = Path("user_data/backtest_results/black-swan")


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--summary-only",
        action="store_true",
        help="Rebuild summary.json from existing event archives without rerunning.",
    )
    args = parser.parse_args()
    RESULT_DIR.mkdir(parents=True, exist_ok=True)
    rows: list[dict] = []

    if not args.summary_only:
        for old_result in RESULT_DIR.glob("*"):
            if old_result.is_file():
                old_result.unlink()

    for event, timerange in WINDOWS.items():
        for protected in (False, True):
            mode = "guard" if protected else "no_guard"
            archive = RESULT_DIR / f"{event}-{mode}.zip"
            if not args.summary_only:
                before = set(RESULT_DIR.glob("*.zip"))
                command = [
                    "docker", "compose", "run", "--rm", "--no-deps", "freqtrade",
                    "backtesting",
                    "--config", "user_data/config.json",
                    "--datadir", "user_data/data/binance_stress",
                    "--strategy-path", "user_data/strategies/volatility_breakout",
                    "--strategy-list", *STRATEGIES,
                    "--pairs", *PAIRS,
                    "--timerange", timerange,
                    "--cache", "none",
                    "--export", "trades",
                    "--backtest-directory", str(RESULT_DIR),
                    "--notes", f"black-swan:{event}:{mode}",
                ]
                if protected:
                    command.append("--enable-protections")
                subprocess.run(command, check=True)
                created = set(RESULT_DIR.glob("*.zip")) - before
                if len(created) != 1:
                    raise RuntimeError(f"Expected one new result archive, got: {created}")
                created.pop().replace(archive)
            rows.extend(read_results(archive, event, timerange, protected))

    summary = RESULT_DIR / "summary.json"
    summary.write_text(json.dumps(rows, indent=2) + "\n")
    print_table(rows)
    print(f"\nJSON: {summary}")


def read_results(
    archive: Path, event: str, timerange: str, protected: bool
) -> list[dict]:
    with zipfile.ZipFile(archive) as zipped:
        result_name = next(
            name
            for name in zipped.namelist()
            if name.endswith(".json") and not name.endswith("_config.json")
        )
        result = json.loads(zipped.read(result_name))["strategy"]

    rows = []
    for strategy_name in STRATEGIES:
        item = result[strategy_name]
        rows.append(
            {
                "event": event,
                "timerange": timerange,
                "protected": protected,
                "strategy": strategy_name,
                "trades": item["total_trades"],
                "longs": item["trade_count_long"],
                "shorts": item["trade_count_short"],
                "wins": item["wins"],
                "losses": item["losses"],
                "profit_pct": round(item["profit_total"] * 100, 3),
                "profit_factor": round(item["profit_factor"], 3),
                "max_drawdown_pct": round(item["max_drawdown_account"] * 100, 3),
                "max_consecutive_losses": item["max_consecutive_losses"],
                "market_change_pct": round(item["market_change"] * 100, 3),
            }
        )
    return rows


def print_table(rows: list[dict]) -> None:
    print(
        "| Event | Guard | Strategy | Trades L/S | Profit | PF | DD | "
        "Max losses | Market |"
    )
    print("|---|---:|---|---:|---:|---:|---:|---:|---:|")
    for row in rows:
        print(
            f"| {row['event']} | {'on' if row['protected'] else 'off'} | "
            f"{row['strategy']} | {row['trades']} {row['longs']}/{row['shorts']} | "
            f"{row['profit_pct']:+.2f}% | {row['profit_factor']:.2f} | "
            f"{row['max_drawdown_pct']:.2f}% | {row['max_consecutive_losses']} | "
            f"{row['market_change_pct']:+.2f}% |"
        )


if __name__ == "__main__":
    main()
