import logging from typing import Dict, List, Any, Optional try: import kis_rust_core except ImportError: kis_rust_core = None logger = logging.getLogger(__name__) def run_breakout_backtest_rust_experimental( codes_candles: Dict[str, List[Dict]], params: Dict[str, Any], ) -> List[Dict]: """ Rust 엔진을 이용한 Breakout (돌파매매) 고속 백테스트 브릿지. """ if kis_rust_core is None: logger.warning("kis_rust_core is not installed or imported. Falling back to empty trades.") return [] # 파라미터 파싱 lookback_min = int(params.get("lookback_min", 1)) vol_window = int(params.get("vol_window", 7)) vol_mult = float(params.get("vol_mult", 0.0) or 0.0) prev_chg_min = float(params.get("prev_chg_min", 1.0)) prev_chg_max = float(params.get("prev_chg_max", 10.0)) max_daily_chg = float(params.get("max_daily_chg", 15.0)) min_price = float(params.get("min_price", 1000.0)) min_bar_trade_value_krw = float(params.get("min_bar_trade_value_krw", 0.0) or 0.0) min_turnover_1m_pct = float(params.get("min_turnover_1m_pct", 0.0) or 0.0) share_denom = float(params.get("share_denom", 0.0) or 0.0) confirm_margin_pct = float(params.get("confirm_margin_pct", 0.0) or 0.0) body_min_pct = float(params.get("body_min_pct", 0.0) or 0.0) use_ema_filter = bool(params.get("use_ema_filter", False)) ema_fast_period = int(params.get("ema_fast_period", 9)) ema_slow_period = int(params.get("ema_slow_period", 21)) time_start_hm = int(params.get("time_start_hm", 900)) time_end_hm = int(params.get("time_end_hm", 1030)) sl_pct = abs(float(params.get("sl_pct", params.get("stop_loss_pct", -0.02)))) tp_pct = float(params.get("tp_pct", params.get("take_profit_pct", 0.05))) trail_pct = float(params.get("trail_pct", 0.015)) trail_arm_pct = float(params.get("trail_arm_pct", 0.0) or 0.0) shoulder_min_high = float(params.get("shoulder_min_high_pct", params.get("shoulder_min_high", 0.02))) shoulder_cut_pct = float(params.get("shoulder_cut_pct", 0.01)) max_hold_bars = int(params.get("max_hold_bars", 0) or 0) cooldown_min = float(params.get("cooldown_min", 30)) max_daily = int(params.get("max_daily", 1)) skip_hts_scan_dupes = bool(params.get("skip_hts_scan_dupes", False)) atr_period = int(params.get("atr_period", 14) or 14) # 파라미터 구조체 생성 rust_params = kis_rust_core.BreakoutParams( lookback_min, vol_window, vol_mult, prev_chg_min, prev_chg_max, max_daily_chg, min_price, min_bar_trade_value_krw, min_turnover_1m_pct, share_denom, confirm_margin_pct, body_min_pct, use_ema_filter, ema_fast_period, ema_slow_period, time_start_hm, time_end_hm, sl_pct, tp_pct, trail_pct, trail_arm_pct, shoulder_min_high, shoulder_cut_pct, max_hold_bars, cooldown_min, max_daily, skip_hts_scan_dupes, atr_period ) all_trades = [] for code, candles_dict in codes_candles.items(): if not candles_dict: continue rust_candles = [] for c in candles_dict: rc = kis_rust_core.CandleData( c.get("candle_time", ""), float(c.get("open", 0)), float(c.get("high", 0)), float(c.get("low", 0)), float(c.get("close", 0)), float(c.get("volume", 0)), float(c.get("rsi", 0.0)), ) rust_candles.append(rc) try: trades = kis_rust_core.run_breakout_backtest_fast(code, rust_candles, rust_params) for t in trades: all_trades.append({ "code": t.code, "buy_time": t.buy_time, "sell_time": t.sell_time, "buy_price": t.buy_price, "sell_price": t.sell_price, "profit_rate": t.pnl_pct, "sell_reason": t.reason, "qty": 1, # 단순화를 위해 1로 고정, 추후 예산 로직 반영 가능 "pnl": 0, # 단순화를 위해 0, # 부가 정보 기록 "entry_features": { "resistance": t.resistance, "vol_ratio": t.vol_ratio, "prev_chg": t.prev_chg, }, "atr_entry": t.atr_entry, "max_price": getattr(t, "max_price", t.buy_price), }) except Exception as e: logger.error(f"Rust breakout engine error for {code}: {e}") all_trades.sort(key=lambda x: x["sell_time"]) return all_trades