#!/usr/bin/env python3
"""
BREAKOUT 공유메모리 틱 배선 end-to-end 검증 — 동일 데이터로 dict vs SharedTicksMapping 백테 2회 대조.

거래가 확인된 실 파라미터(merged_params)로 주간 백테를 두 번 돌린다:
  A) ticks_by_code = 원본 dict (기존 경로)
  B) ticks_by_code = SharedTicksMapping (공유메모리 컬럼 경로)
두 결과의 trades 리스트와 요약통계가 완전히 동일해야 한다(bit-identical).
"""
import json
import os
import sys

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))

from database import TradeDB
from kis_trader.backtest import breakout_backtest_common as bbc
from kis_trader.backtest.param_search_breakout import _load_candles_for_search
from kis_trader.backtest.breakout_backtest_common import resolve_breakout_universe
from kis_trader.backtest.breakout_tick_loader import load_breakout_ticks_by_code
from kis_trader.backtest.shared_ticks import build_shared_ticks

START = "2026-06-28"
END = "2026-07-05"
JSON = "./kis_trader/backtest/results/search_breakout_fast_20260705_031411.json"


def run_backtest(codes_candles, universe, ticks_by_code, engine_params, slot_money,
                 max_stocks, total_budget, start_key, end_key):
    p = dict(engine_params)
    meta = {"start_key": start_key, "end_key": end_key}
    trades = bbc.run_breakout_backtest_web_aligned(
        codes_candles, p, universe,
        slot_money=slot_money, fee_rate=0.00015, sell_tax=0.0018,
        max_stocks=max_stocks, total_budget_krw=total_budget,
        ticks_by_code=ticks_by_code,
        orderbook_by_code={}, program_by_code={},
        meta_out=meta,
    )
    stats = bbc.summarize_breakout_trades(trades, total_budget_krw=total_budget, period_days=1)
    return trades, stats


def norm_trades(trades):
    out = []
    for t in trades:
        out.append(tuple(sorted(
            (k, (round(v, 6) if isinstance(v, float) else v))
            for k, v in t.items()
        )))
    return out


def main():
    meta_j = json.load(open(JSON))
    r0 = meta_j["top"][0]
    engine_params = dict(r0["merged_params"])
    slot_money = float(meta_j.get("slot_money") or 300_000)
    max_stocks = int(meta_j.get("max_stocks") or 20)
    total_budget = float(meta_j.get("total_budget_krw") or 6_000_000)
    lookback_min = int(engine_params.get("lookback_min", 1))
    vol_window = int(engine_params.get("vol_window", 7))

    start_key = START.replace("-", "") + "0000"
    end_key = END.replace("-", "") + "2359"

    print("⏳ 캔들 로드...")
    codes_candles = _load_candles_for_search(
        START, END, lookback_min,
        fixed={"lookback_min": lookback_min, "vol_window": vol_window},
    )
    print(f"   종목 {len(codes_candles):,}")

    print("⏳ 유니버스 로드...")
    universe, src, n_bins, _iv = resolve_breakout_universe(
        START.replace("-", ""), END.replace("-", ""),
        use_saved_history=True, strategy_id="BREAKOUT",
    )
    print(f"   유니버스 슬롯 {len(universe or {}):,} (src={src})")

    print("⏳ ws_ticks 로드...")
    db = TradeDB()
    try:
        ticks_dict, tick_rows = load_breakout_ticks_by_code(
            db, start_key, end_key, set(codes_candles.keys()),
        )
    finally:
        db.close()
    print(f"   틱 {tick_rows:,}건 / 종목 {len(ticks_dict):,}")

    # 거래를 넓게 커버하는 변형들 (진입/체결/휩쏘 경로 커버).
    _base_loose = {
        "vol_mult": 1.0, "min_turnover_1m_pct": 0.0, "cooldown_min": 0,
        "max_daily": 100, "prev_chg_min": -50.0, "prev_chg_max": 50.0,
        "time_start_hm": 900, "time_end_hm": 1520, "min_price": 0,
    }
    variants = [
        ("baseline(top)", {}),
        ("loose", {"vol_mult": 1.0, "min_turnover_1m_pct": 0.0, "cooldown_min": 0,
                   "max_daily": 50}),
        ("loose_wide", dict(_base_loose)),
        ("loose_slip", {**_base_loose, "intrabar_slippage_pct": 0.3}),
        ("loose_lb3", {**_base_loose, "lookback_min": 3}),
        ("tight_sl", {"vol_mult": 1.0, "sl_mode": "atr", "atr_sl_mult": 1.0,
                      "trail_pct": 0.5}),
        ("wide", {"vol_mult": 1.0, "shoulder_cut_pct": 0.5, "trail_pct": 2.0}),
    ]

    print("▶ 공유메모리 store 빌드...")
    store = build_shared_ticks(ticks_dict)
    if store is None:
        print("❌ build_shared_ticks None — 검증 불가")
        return 1

    all_ok = True
    total_trades_checked = 0
    try:
        for name, override in variants:
            ep = dict(engine_params)
            ep.update(override)
            trades_a, stats_a = run_backtest(codes_candles, universe, ticks_dict, ep,
                                             slot_money, max_stocks, total_budget, start_key, end_key)
            shared_map = store.attach_mapping()
            trades_b, stats_b = run_backtest(codes_candles, universe, shared_map, ep,
                                             slot_money, max_stocks, total_budget, start_key, end_key)
            na, nb = norm_trades(trades_a), norm_trades(trades_b)
            ok = True
            if len(na) != len(nb):
                ok = False
                detail = f"거래수 {len(na)} vs {len(nb)}"
            else:
                diff = [i for i in range(len(na)) if na[i] != nb[i]]
                if diff:
                    ok = False
                    detail = f"내용불일치 {len(diff)}건 (idx {diff[0]})"
                    print("    dict  :", dict(na[diff[0]]))
                    print("    shared:", dict(nb[diff[0]]))
                else:
                    detail = "동일"
            for k in ("total_trades", "total_pnl", "win_rate", "pf", "mdd"):
                if stats_a.get(k) != stats_b.get(k):
                    ok = False
                    detail += f" | {k} {stats_a.get(k)}≠{stats_b.get(k)}"
            total_trades_checked += len(na)
            all_ok = all_ok and ok
            print(f"  {'✅' if ok else '❌'} {name:16s} 거래 {len(na):3d} · 손익 {stats_a['total_pnl']:+,.0f} · {detail}")
    finally:
        store.unlink()

    print(f"\n대조한 총 거래 {total_trades_checked}건 (변형 {len(variants)}개)")
    print("🎉 bit-identical 통과" if all_ok else "🚨 불일치 발생")
    return 0 if all_ok else 2


if __name__ == "__main__":
    sys.exit(main())
