한투 호가 = 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 키 정리도 같이 들어갔습니다. 파일 단위로 나눌 수 없어서입니다.
209 lines
8.1 KiB
Python
209 lines
8.1 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
optuna_tail_tpe_space.py — 꼬리 Optuna 연속(TPE) 탐색 공간
|
|
기존 ``_tail_grids()`` categorical 유지. ``skip_hts_scan_dupes=False`` 고정.
|
|
비율 축은 엔진과 동일(소수, UI% 아님) — evaluate/apply 경로와 맞춤.
|
|
|
|
multivariate TPE: 모든 축을 **먼저** suggest 한 뒤, 제약(ATR min/max·래칫 오름차순 등)만
|
|
맨 끝에서 TrialPruned. 중간 prune 시 뒤쪽 키가 trial마다 빠져 independent sampling 경고가 난다.
|
|
"""
|
|
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,
|
|
r3,
|
|
r4,
|
|
suggest_ratchet_tiers_pct,
|
|
)
|
|
|
|
# 래칫: 꼬리는 소폭 % (기존 메뉴 0.3~2.0 대역) — 숫자축 + 조립 문자열
|
|
# 휩쏘: 실매 DB 기본(enabled=false, subbar=30, lookback=90, dip=0.003, recovery=0.001) 그리드 포함
|
|
_TAIL_BASE_KEYS: List[str] = [
|
|
"entry_mode",
|
|
"cand_limit",
|
|
"max_daily_change",
|
|
"min_drop_rate",
|
|
"min_recovery_ratio",
|
|
"tail_ratio_min",
|
|
"tail_pct_min",
|
|
"max_rec_3m",
|
|
"shoulder_min_high",
|
|
"shoulder_cut_pct",
|
|
"stop_atr_mult",
|
|
"target_atr_mult",
|
|
"atr_sl_min_pct",
|
|
"atr_sl_max_pct",
|
|
"atr_tp_min_pct",
|
|
"atr_tp_max_pct",
|
|
"tail_vol_mult",
|
|
"tail_vol_win",
|
|
"symbol_daily_loss_limit_pct",
|
|
"symbol_daily_loss_limit_krw",
|
|
"reentry_min_edge_krw",
|
|
"reentry_require_nonneg",
|
|
"max_daily",
|
|
"cooldown_min",
|
|
"bar_chg_min_pct",
|
|
"bar_chg_max_pct",
|
|
"rsi_threshold",
|
|
"pattern_pin",
|
|
"pattern_engulfing",
|
|
"pattern_piercing",
|
|
"pattern_harami",
|
|
"pattern_doji",
|
|
"pattern_morning_star",
|
|
# 당일손익 다단트레일(trail_tiers/drop/arm) — 운영 리스크 손잡이. TPE·apply 탐색 제외(DB/UI 고정).
|
|
"max_loss_krw",
|
|
"whipsaw_enabled",
|
|
"whipsaw_subbar_sec",
|
|
"whipsaw_lookback_sec",
|
|
"whipsaw_dip_pct",
|
|
"whipsaw_recovery_tol_pct",
|
|
]
|
|
TAIL_TPE_AXIS_KEYS: List[str] = list(_TAIL_BASE_KEYS) + list(RATCHET_TPE_AXIS_KEYS)
|
|
|
|
|
|
def tail_tpe_axis_keys(entry_mode: Optional[str] = None) -> List[str]:
|
|
"""align 스터디에는 limit_atr_mult 없음(지정가 모드 전용)."""
|
|
keys = list(TAIL_TPE_AXIS_KEYS)
|
|
if normalize_tpe_tail_entry_mode(entry_mode) == "limit_atr":
|
|
if "limit_atr_mult" not in keys:
|
|
keys.append("limit_atr_mult")
|
|
return keys
|
|
|
|
|
|
def normalize_tpe_tail_entry_mode(raw: Optional[Any] = None) -> str:
|
|
"""TPE는 진입모드를 탐색하지 않고 스터디마다 고정. align | limit_atr."""
|
|
s = str(raw or "").strip().lower()
|
|
if s in ("limit_atr", "limit", "atr_limit"):
|
|
return "limit_atr"
|
|
return "align"
|
|
|
|
|
|
def suggest_tail_params_tpe(
|
|
trial: optuna.Trial,
|
|
entry_mode: Optional[str] = None,
|
|
) -> Dict[str, Any]:
|
|
combo: Dict[str, Any] = {}
|
|
# 한 스터디=한 모드. categorical 혼입 금지(웹 체크 2개면 잡 2개 순차).
|
|
combo["entry_mode"] = normalize_tpe_tail_entry_mode(entry_mode)
|
|
combo["cand_limit"] = trial.suggest_categorical("cand_limit", [0, 20])
|
|
combo["max_daily_change"] = r1(
|
|
trial.suggest_float("max_daily_change", 10.0, 60.0, step=1.0), # 확장: 20→10, 45→60
|
|
)
|
|
combo["min_drop_rate"] = r3(
|
|
trial.suggest_float("min_drop_rate", 0.005, 0.15, step=0.005), # 확장: 0.01→0.005, 0.08→0.15
|
|
)
|
|
combo["min_recovery_ratio"] = r2(
|
|
trial.suggest_float("min_recovery_ratio", 0.08, 0.35, step=0.01),
|
|
)
|
|
combo["tail_ratio_min"] = r2(trial.suggest_float("tail_ratio_min", 0.4, 1.5, step=0.1))
|
|
combo["tail_pct_min"] = r4(
|
|
trial.suggest_float("tail_pct_min", 0.0005, 0.01, step=0.0005),
|
|
)
|
|
combo["max_rec_3m"] = r2(trial.suggest_float("max_rec_3m", 0.7, 0.98, step=0.01))
|
|
combo["shoulder_min_high"] = r4(
|
|
trial.suggest_float("shoulder_min_high", 0.002, 0.015, step=0.001),
|
|
)
|
|
combo["shoulder_cut_pct"] = r4(
|
|
trial.suggest_float("shoulder_cut_pct", 0.0005, 0.005, step=0.0005),
|
|
)
|
|
combo["stop_atr_mult"] = r2(trial.suggest_float("stop_atr_mult", 0.8, 3.0, step=0.1))
|
|
combo["target_atr_mult"] = r2(
|
|
trial.suggest_float("target_atr_mult", 0.8, 3.5, step=0.1),
|
|
)
|
|
combo["atr_sl_min_pct"] = r2(trial.suggest_float("atr_sl_min_pct", 0.3, 1.0, step=0.1))
|
|
combo["atr_sl_max_pct"] = r1(trial.suggest_float("atr_sl_max_pct", 1.0, 12.0, step=0.5)) # 확장: 8.0→12.0
|
|
combo["atr_tp_min_pct"] = r2(trial.suggest_float("atr_tp_min_pct", 0.1, 2.0, step=0.1)) # 확장: 0.2→0.1
|
|
combo["atr_tp_max_pct"] = r1(trial.suggest_float("atr_tp_max_pct", 1.0, 10.0, step=0.5)) # 확장: 6.0→10.0
|
|
|
|
combo["tail_vol_mult"] = r2(trial.suggest_float("tail_vol_mult", 0.0, 4.0, step=0.1))
|
|
combo["tail_vol_win"] = trial.suggest_int("tail_vol_win", 2, 8)
|
|
# align 에서는 지정가 ATR 배수가 체결에 안 들어감 → 탐색 축에서 제외(스터디 공간 고정).
|
|
if combo["entry_mode"] == "limit_atr":
|
|
combo["limit_atr_mult"] = r2(trial.suggest_float("limit_atr_mult", 0.8, 2.5, step=0.1))
|
|
# 꼬리 래칫: OFF=\"off\" / gain·cut 소폭% (엔진 문자열과 동일)
|
|
combo.update(
|
|
suggest_ratchet_tiers_pct(
|
|
trial,
|
|
off_token="off",
|
|
n_max=3,
|
|
gain_low=0.3,
|
|
gain_high=3.0,
|
|
gain_step=0.1,
|
|
cut_low=0.15,
|
|
cut_high=0.5,
|
|
cut_step=0.05,
|
|
),
|
|
)
|
|
combo["symbol_daily_loss_limit_pct"] = r2(
|
|
trial.suggest_float("symbol_daily_loss_limit_pct", 0.0, 3.0, step=0.5),
|
|
)
|
|
combo["symbol_daily_loss_limit_krw"] = trial.suggest_int(
|
|
"symbol_daily_loss_limit_krw", 0, 80000, step=10000,
|
|
)
|
|
combo["reentry_min_edge_krw"] = trial.suggest_int(
|
|
"reentry_min_edge_krw", 0, 1000, step=100,
|
|
)
|
|
combo["reentry_require_nonneg"] = False
|
|
combo["max_daily"] = trial.suggest_int("max_daily", 3, 80, step=5) # 확장: 5→3, 60→80
|
|
combo["cooldown_min"] = r1(trial.suggest_float("cooldown_min", 0.0, 30.0, step=1.0)) # 확장: 15→30
|
|
# 3분 직전대비 등락. 구구간 max=-0.2 는 반등봉(+0.x%)을 한 번도 못 봄.
|
|
# 실매 DB -10.0 / -0.5 포함. min 상한 -3 < max 하한 -2 → 항상 min<max (불필요 prune 없음).
|
|
combo["bar_chg_min_pct"] = r1(
|
|
trial.suggest_float("bar_chg_min_pct", -25.0, -3.0, step=0.5), # 확장: -15→-25
|
|
)
|
|
combo["bar_chg_max_pct"] = r2(
|
|
trial.suggest_float("bar_chg_max_pct", -2.0, 10.0, step=0.2), # 확장: -0.2→+10 (0·반등 포함)
|
|
)
|
|
|
|
combo["rsi_threshold"] = r1(trial.suggest_float("rsi_threshold", 70.0, 95.0, step=1.0))
|
|
for pk in (
|
|
"pattern_pin",
|
|
"pattern_engulfing",
|
|
"pattern_piercing",
|
|
"pattern_harami",
|
|
"pattern_doji",
|
|
"pattern_morning_star",
|
|
):
|
|
combo[pk] = trial.suggest_categorical(pk, [False, True])
|
|
|
|
combo["max_loss_krw"] = trial.suggest_int("max_loss_krw", 50000, 300000, step=25000)
|
|
|
|
# 휩쏘 TRIGGER — 실매값(False/30/90/0.003/0.001) 포함. ON·OFF·초·% 축 분리.
|
|
combo["whipsaw_enabled"] = trial.suggest_categorical(
|
|
"whipsaw_enabled", [False, True],
|
|
)
|
|
combo["whipsaw_subbar_sec"] = trial.suggest_categorical(
|
|
"whipsaw_subbar_sec", [15, 30, 45, 60],
|
|
)
|
|
combo["whipsaw_lookback_sec"] = trial.suggest_categorical(
|
|
"whipsaw_lookback_sec", [60, 90, 120, 180],
|
|
)
|
|
combo["whipsaw_dip_pct"] = r4(
|
|
trial.suggest_float("whipsaw_dip_pct", 0.001, 0.01, step=0.001),
|
|
)
|
|
combo["whipsaw_recovery_tol_pct"] = r4(
|
|
trial.suggest_float("whipsaw_recovery_tol_pct", 0.0005, 0.003, step=0.0005),
|
|
)
|
|
|
|
combo["skip_hts_scan_dupes"] = False
|
|
|
|
# --- 제약 prune: 모든 suggest 이후에만 (키 공간 고정) ---
|
|
if combo["atr_sl_min_pct"] >= combo["atr_sl_max_pct"]:
|
|
raise optuna.TrialPruned("atr_sl min>=max")
|
|
if combo["atr_tp_min_pct"] >= combo["atr_tp_max_pct"]:
|
|
raise optuna.TrialPruned("atr_tp min>=max")
|
|
if combo["bar_chg_min_pct"] >= combo["bar_chg_max_pct"]:
|
|
raise optuna.TrialPruned("bar_chg invalid")
|
|
if combo.pop("_ratchet_ascending_ok", True) is False:
|
|
raise optuna.TrialPruned("ratchet gain not ascending")
|
|
|
|
return combo
|