- 프론트엔드 UI 업데이트 (backtest.html, backtest.js) 엔진 라디오 버튼 통합 관련 반영 - Rust 플러그인(kis_rust_core) 및 컴파일 소스코드 추가 - CLI 백테스트 스크립트 수정 및 최신화 - 기타 스크래치 테스트 스크립트, 로그 요약 마크다운(.md) 등 누락 파일 일괄 반영 - 추가적으로 아직 발견되지 않은 엣지 케이스나 렌더링 오류가 포함되어 있을 가능성이 있음
114 lines
4.9 KiB
Python
114 lines
4.9 KiB
Python
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,
|
|
"entry_time": t.buy_time,
|
|
"exit_time": t.sell_time,
|
|
"entry": t.buy_price,
|
|
"exit": t.sell_price,
|
|
"exit_reason": t.reason,
|
|
"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
|