#!/usr/bin/env python3 """7/9 SHORT 종목일일손익게이트 백테 검증 — Case A(edge=2000) vs B(edge=0).""" from __future__ import annotations import sys import traceback from collections import defaultdict from pathlib import Path ROOT = Path(__file__).resolve().parents[1] if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT)) from database import TradeDB from kis_trader.engine import tail_engine as te from kis_trader.backtest import tail_backtest_common as tbc from kis_trader.engine.tail_tick_replay import tail_backtest_wants_tick_replay def _summarize(trades, label: str) -> dict: total = len(trades) wins = [t for t in trades if float(t.get("pnl") or 0) > 0] pnl = sum(float(t.get("pnl") or 0) for t in trades) by_code: dict = defaultdict(list) for t in trades: by_code[t.get("code")].append(t) multi = sum(1 for v in by_code.values() if len(v) > 1) wr = (len(wins) / total * 100) if total else 0.0 print( f" trades={total} wins={len(wins)} WR={wr:.1f}% " f"pnl={pnl:+,.0f} multi_codes={multi}", flush=True, ) for code, ts in sorted(by_code.items()): cum = 0.0 parts = [] for t in ts: cum += float(t.get("pnl") or 0) hm = str(t.get("entry_time") or t.get("candle_time") or "")[8:12] parts.append( f"{hm}:{t.get('pnl', 0):+.0f}({t.get('exit_reason', '')})→cum{cum:+.0f}" ) if len(ts) > 1: print(f" [{code}] " + " | ".join(parts), flush=True) return {"label": label, "trades": total, "pnl": pnl, "multi": multi} def main() -> int: print("=== 7/9 SHORT 게이트 백테 검증 (skip_hts=DB) ===", flush=True) db = TradeDB() start, end = "2026-07-09", "2026-07-09" start_key = start.replace("-", "") + "0000" end_key = end.replace("-", "") + "2359" start_ymd, end_ymd = start_key[:8], end_key[:8] try: base = te.get_tail_defaults_from_db(db) skip_hts = bool(base.get("skip_hts_scan_dupes")) print(f"skip_hts_scan_dupes(DB)={skip_hts}", flush=True) universe, src, n_slots, _ = tbc.resolve_tail_universe( start_ymd, end_ymd, use_saved_history=True, strategy_id="SHORT", ) tf = int(base.get("timeframe") or 3) rsi = int(base.get("rsi_period") or 14) candles_by_code, _, _ = tbc.load_tail_candles_by_code( db, start_key, end_key, tf, rsi_period=rsi, ) codes = len(candles_by_code) bars = sum(len(v) for v in candles_by_code.values()) slot = float(base.get("slot_money") or 300000) budget = float(base.get("total_budget_krw") or slot * int(base.get("max_stocks") or 4)) print( f"[로드] universe={src} slots={n_slots} codes={codes} bars={bars} " f"slot={slot} budget={budget}", flush=True, ) fee = float(base.get("fee_rate") or 0.00015) tax = float(base.get("sell_tax") or 0.0023) meta = {"db": db, "start_key": start_key, "end_key": end_key} use_tick = tail_backtest_wants_tick_replay(base) print(f"[로드] tick_replay={use_tick}", flush=True) cases = [ ("A GATE_ON edge=2000", 30000.0, 1.5, 2000.0), ("B GATE_ON edge=0", 30000.0, 1.5, 0.0), ] results = [] for label, krw, pct, edge in cases: params = dict(base) params["symbol_daily_loss_limit_krw"] = krw params["symbol_daily_loss_limit_pct"] = pct params["reentry_min_edge_krw"] = edge params["skip_hts_scan_dupes"] = skip_hts print( f"\n--- {label}: krw={krw} pct={pct} edge={edge} ---", flush=True, ) trades = tbc.run_tail_backtest_web_aligned( candles_by_code, params, universe, slot_money=slot, fee_rate=fee, sell_tax=tax, total_budget_krw=budget, meta_out=meta, ) results.append(_summarize(trades, label)) print("\n======== SUMMARY ========", flush=True) for r in results: print(f" {r['label']}: trades={r['trades']} pnl={r['pnl']:+,.0f} multi={r['multi']}", flush=True) print("\n[LIVE 7/9] trades=3(신규2) pnl=-7,053 (376980 전일포지션 포함)", flush=True) print("✅ VERIFY DONE", flush=True) return 0 except Exception: traceback.print_exc() return 1 finally: db.close() if __name__ == "__main__": raise SystemExit(main())