Files
kis_bot/kis_trader/backtest/optuna_tail_tpe_space.py
Your Name 36a3e2b4a1 feat: Enhance trading system with new permanent subscription features and order book management
Changes:
- Added a new API endpoint for managing permanent subscriptions, allowing users to enable or disable subscriptions dynamically.
- Implemented a function to fill candle data from Kiwoom, ensuring that only relevant data is inserted into the database.
- Introduced a mechanism to handle master subscription states, improving the management of subscription statuses.
- Updated the database schema to include new fields for managing subscription states and order book filtering.

Impact:
- These enhancements improve the flexibility and reliability of the trading system, allowing for better management of subscriptions and order book data, while reducing the risk of data inconsistencies.

히스토리 align 제거 븅신같은 초기설계 아예 제거
진입모드에 구멍메움
호가진입을 켜도 호가가 안들어올때 호가 안보고 그냥 사버림
2026-08-15 23:01:14 +09:00

201 lines
7.5 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",
"limit_atr_mult",
"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() -> List[str]:
return list(TAIL_TPE_AXIS_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)
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
combo["bar_chg_min_pct"] = r1(
trial.suggest_float("bar_chg_min_pct", -15.0, -3.0, step=0.5),
)
combo["bar_chg_max_pct"] = r2(
trial.suggest_float("bar_chg_max_pct", -2.0, -0.2, step=0.1),
)
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