#!/usr/bin/env python3 """모멘텀 청산 A/B — 어깨·래칫·트레일·시간컷 (실매 check_sell_signal_momentum_live 동일 우선순위).""" from __future__ import annotations import json from datetime import datetime from database import TradeDB from kis_trader.backtest import momentum_backtest_common as mbc from kis_trader.engine import momentum_engine as me START = "2026-05-01" END = "2026-05-31" SCENARIOS = [ ("baseline_DB(전용청산)", {}), ("래칫_2:1.5,5:1,8:0.7", { "ratchet_tiers": "2:1.5,5:1,8:0.7", }), ("트레일1.5%_무장1%", { "trail_pct": 0.015, "trail_arm_pct": 0.01, }), ("시간컷30분", { "max_hold_bars": 30, }), ("어깨0.5%_컷0.3%_래칫OFF", { "ratchet_tiers": "", "shoulder_min_high": 0.005, "shoulder_cut_pct": 0.003, }), ] def main() -> None: db = TradeDB() try: start_key, end_key, start_ymd, end_ymd = mbc.date_keys(START, END) candles_by_code, _ = mbc.load_momentum_candles_by_code(db, start_key, end_key) universe_by_slot, _, _, _, _ = mbc.resolve_momentum_universe( start_ymd, end_ymd, use_saved_history=True, ) snap = db.get_merged_env_snapshot() fee_rate, sell_tax, slot = mbc.fee_and_slot_from_env_row(snap) base = me.get_momentum_defaults_from_db(db) base["portfolio_mode"] = True base["force_eod_exit"] = False portfolio = mbc.resolve_momentum_portfolio_params(snap, base, slot_money=slot) slot_money = float(portfolio["slot_money"]) max_stocks = int(portfolio["max_stocks"]) total_budget = float(portfolio["total_budget_krw"]) period_days = max( 1, (datetime.strptime(END, "%Y-%m-%d") - datetime.strptime(START, "%Y-%m-%d")).days + 1, ) rows = [] for label, patch in SCENARIOS: params = dict(base) params.update(patch) meta: dict = {} trades = mbc.run_momentum_backtest_web_aligned( candles_by_code, params, universe_by_slot, slot_money=slot_money, fee_rate=fee_rate, sell_tax=sell_tax, max_stocks=max_stocks, total_budget_krw=total_budget, meta_out=meta, ) stats = mbc.summarize_momentum_trades( trades, total_budget_krw=total_budget, period_days=period_days, ) reasons = mbc.count_momentum_sell_reasons(trades) rows.append({ "label": label, "pnl": stats["total_pnl"], "win_rate": stats["win_rate"], "trades": stats["total_trades"], "pf": stats["pf"], "sell_reasons": reasons, }) print( f"{label:28s} | {stats['total_pnl']:>+10,.0f} | " f"승률 {stats['win_rate']:5.1f}% | {stats['total_trades']:3}건 | " f"PF {stats['pf']:.2f} | {reasons}" ) out = { "start": START, "end": END, "scenarios": rows, } ts = datetime.now().strftime("%Y%m%d_%H%M%S") path = f"kis_trader/backtest/results/momentum_exit_ab_{ts}.json" with open(path, "w", encoding="utf-8") as f: json.dump(out, f, indent=2, ensure_ascii=False) print(f"\n💾 저장: {path}") finally: db.close() if __name__ == "__main__": main()