한투 호가 = 2번째 앱키 전용 키 없거나 start 실패 시 메인에 H0STASP0 안 붙임. 운영설정 WS_ORDERBOOK_SAVE_KIS 빨간 danger. LS RAM 합집합 후보∪보유∪영구∪grace. sync_targets와 split reconcile 둘 다. 틱 DB 영구 게이트는 그대로. 분봉 쓰레기 → 다음 소스 봉 통째 그 분 틱 0건이거나 전부 봉끝 대비 LIVE_FEED_FALLBACK_MAX_AGE_SEC 초과면 구멍. 메인 WS → 2차 → LS → REST → rollup. CANDLE_GARBAGE_FALLBACK 기본 true. 파일: feed_fallback.py(신규), ws_manager.py, kis_ws.py, candle_series.py, bt_candle_source.py, live_config_schema.py, database.py, 스모크, MD 2개. 같은 ws_manager/database/kis_ws/live_config에는 직전 커밋 이후 쌓여 있던 시세 폴백·ENV 키 정리도 같이 들어갔습니다. 파일 단위로 나눌 수 없어서입니다.
114 lines
4.1 KiB
Python
114 lines
4.1 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
optuna_breakout_tpe_space.py — 돌파 Optuna 연속(TPE) 탐색 공간
|
|
기존 ``_breakout_grids()`` categorical 경로는 유지. ``skip_hts`` 항상 False.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
import optuna
|
|
|
|
from kis_trader.backtest.optuna_tpe_common import (
|
|
RATCHET_TPE_AXIS_KEYS,
|
|
r1,
|
|
r2,
|
|
suggest_ratchet_tiers_pct,
|
|
)
|
|
|
|
# 매수 시간창(time_end_hm) 은 TPE 제외 — 운영 DB/골든타임 고정. 다단래칫·청산축만 탐색.
|
|
_BREAKOUT_BASE_KEYS: List[str] = [
|
|
"max_daily_chg",
|
|
"vol_mult",
|
|
"vol_window",
|
|
"min_turnover_1m_pct",
|
|
"prev_chg_min",
|
|
"prev_chg_max",
|
|
"min_price",
|
|
"tp_pct",
|
|
"sl_mode",
|
|
"trail_pct",
|
|
"trail_arm_pct",
|
|
"shoulder_min_high_pct",
|
|
"shoulder_cut_pct",
|
|
"lookback_min",
|
|
"confirm_margin_pct",
|
|
"body_min_pct",
|
|
"max_hold_bars",
|
|
]
|
|
BREAKOUT_TPE_AXIS_KEYS: List[str] = list(_BREAKOUT_BASE_KEYS) + list(RATCHET_TPE_AXIS_KEYS)
|
|
|
|
|
|
def normalize_tpe_breakout_sl_mode(raw: Optional[Any] = None) -> str:
|
|
"""TPE는 손절모드를 탐색하지 않고 스터디마다 고정. fixed | atr."""
|
|
s = str(raw or "").strip().lower()
|
|
if s in ("atr", "atr_sl", "dynamic"):
|
|
return "atr"
|
|
return "fixed"
|
|
|
|
|
|
def breakout_tpe_axis_keys(sl_mode: Optional[str] = None) -> List[str]:
|
|
"""fixed=sl_pct 만, atr=atr_sl_mult 만. 한 스터디에 둘 다 넣지 않음."""
|
|
keys = list(BREAKOUT_TPE_AXIS_KEYS)
|
|
if normalize_tpe_breakout_sl_mode(sl_mode) == "atr":
|
|
keys.append("atr_sl_mult")
|
|
else:
|
|
keys.append("sl_pct")
|
|
return keys
|
|
|
|
|
|
def suggest_breakout_params_tpe(
|
|
trial: optuna.Trial,
|
|
sl_mode: Optional[str] = None,
|
|
) -> Dict[str, Any]:
|
|
combo: Dict[str, Any] = {}
|
|
combo["max_daily_chg"] = r1(trial.suggest_float("max_daily_chg", 20.0, 55.0, step=1.0))
|
|
combo["vol_mult"] = r2(trial.suggest_float("vol_mult", 1.0, 5.0, step=0.1)) # 확장: 1.5→1.0, 4.0→5.0
|
|
combo["vol_window"] = trial.suggest_int("vol_window", 1, 15)
|
|
combo["min_turnover_1m_pct"] = r2(
|
|
trial.suggest_float("min_turnover_1m_pct", 0.05, 0.5, step=0.05),
|
|
)
|
|
combo["prev_chg_min"] = r2(trial.suggest_float("prev_chg_min", 0.2, 1.0, step=0.1))
|
|
combo["prev_chg_max"] = r1(trial.suggest_float("prev_chg_max", 5.0, 30.0, step=0.5))
|
|
if combo["prev_chg_min"] >= combo["prev_chg_max"]:
|
|
raise optuna.TrialPruned("prev_chg invalid")
|
|
|
|
combo["min_price"] = trial.suggest_int("min_price", 1000, 5000, step=500)
|
|
combo["tp_pct"] = r2(trial.suggest_float("tp_pct", 1.0, 25.0, step=0.5)) # 확장: 2.0→1.0, 18.0→25.0
|
|
# 한 스터디=한 손절모드. categorical 혼입 금지(웹 체크 2개면 잡 2개 순차).
|
|
combo["sl_mode"] = normalize_tpe_breakout_sl_mode(sl_mode)
|
|
if combo["sl_mode"] == "atr":
|
|
combo["atr_sl_mult"] = r2(trial.suggest_float("atr_sl_mult", 1.5, 3.5, step=0.1))
|
|
else:
|
|
combo["sl_pct"] = r2(trial.suggest_float("sl_pct", 1.0, 8.0, step=0.1)) # 확장: 2.0→1.0, 6.0→8.0
|
|
combo["trail_pct"] = r2(trial.suggest_float("trail_pct", 0.0, 6.0, step=0.1)) # 확장: 1.0→0.0, 4.0→6.0
|
|
combo["trail_arm_pct"] = r2(trial.suggest_float("trail_arm_pct", 0.0, 6.0, step=0.1)) # 확장: 1.0→0.0, 4.0→6.0
|
|
combo["shoulder_min_high_pct"] = r2(
|
|
trial.suggest_float("shoulder_min_high_pct", 1.0, 6.0, step=0.1),
|
|
)
|
|
combo["shoulder_cut_pct"] = r2(
|
|
trial.suggest_float("shoulder_cut_pct", 0.3, 1.5, step=0.1),
|
|
)
|
|
combo.update(
|
|
suggest_ratchet_tiers_pct(
|
|
trial,
|
|
off_token="",
|
|
n_max=3,
|
|
gain_low=2.0,
|
|
gain_high=15.0,
|
|
gain_step=0.5,
|
|
cut_low=0.5,
|
|
cut_high=3.0,
|
|
cut_step=0.1,
|
|
),
|
|
)
|
|
combo["lookback_min"] = trial.suggest_int("lookback_min", 1, 10)
|
|
combo["confirm_margin_pct"] = r2(
|
|
trial.suggest_float("confirm_margin_pct", 0.0, 1.0, step=0.1),
|
|
)
|
|
combo["body_min_pct"] = r2(trial.suggest_float("body_min_pct", 0.0, 0.5, step=0.1))
|
|
combo["max_hold_bars"] = trial.suggest_int("max_hold_bars", 0, 180, step=10)
|
|
|
|
combo["skip_hts_scan_dupes"] = False
|
|
return combo
|