Files
kis_bot/kis_trader/engine/momentum_env_keys.py
2026-07-30 18:05:07 +09:00

247 lines
9.0 KiB
Python

"""
kis_trader/engine/momentum_env_keys.py — 모멘텀(MOMENTUM) env 키 단일 정의
==========================================================================
1분봉 단기 추세추격 — 스캘핑(SCALP reversal)과 분리.
config_momentum · momentum_engine · 웹 · 파라서치 공통.
"""
from __future__ import annotations
from typing import Any, Dict, Optional
MOMENTUM_CONFIG_KEYS = frozenset({
"MOMENTUM_RSI_MIN",
"MOMENTUM_RSI_MAX",
"MOMENTUM_VOL_MULT",
"MOMENTUM_VOL_WIN",
"MOMENTUM_TIME_END_HM",
"MOMENTUM_TIME_START",
"MOMENTUM_MAX_FROM_OPEN_PCT",
"MOMENTUM_MIN_FROM_OPEN_PCT",
"MOMENTUM_SKIP_HTS_SCAN_DUPES",
"MOMENTUM_STOP_LOSS_PCT",
"MOMENTUM_TAKE_PROFIT_PCT",
"MOMENTUM_TP_MAX_PCT",
"MOMENTUM_SHOULDER_MIN_HIGH_PCT",
"MOMENTUM_SHOULDER_CUT_PCT",
"MOMENTUM_RATCHET_TIERS",
"MOMENTUM_TRAIL_PCT",
"MOMENTUM_TRAIL_ARM_PCT",
"MOMENTUM_MAX_HOLD_BARS",
"MOMENTUM_MAX_DAILY",
"MOMENTUM_MIN_PRICE",
"MOMENTUM_MAX_DAILY_CHG",
"MOMENTUM_HIGH_CHASE_THR",
"MOMENTUM_USE_DEFENSE_FILTERS",
"MOMENTUM_USE_HIGH_CHASE_FILTER",
"MOMENTUM_USE_DAILY_RANGE_FILTER",
"MOMENTUM_USE_EMA_FILTER",
"MOMENTUM_EMA_FAST_PERIOD",
"MOMENTUM_EMA_SLOW_PERIOD",
"MOMENTUM_USE_RSI_MAX_FILTER",
"MOMENTUM_TRIGGER_E_CONFIRM",
"MOMENTUM_TRIGGER_REQUIRE_BULL_BAR",
"MOMENTUM_USE_VOL_TRIGGER",
"MOMENTUM_USE_RSI_FILTER",
"MOMENTUM_PATTERN_BREAKOUT",
"MOMENTUM_PATTERN_PULLBACK",
"MOMENTUM_CHASE_LOOKBACK_MIN",
"MOMENTUM_PULLBACK_LOOKBACK_MIN",
"MOMENTUM_PULLBACK_MIN_PCT",
"MOMENTUM_PULLBACK_MAX_PCT",
"MOMENTUM_SETUP_VOL_MAX_MULT",
"MOMENTUM_SETUP_BEAR_BARS_MIN",
"MOMENTUM_MAX_LOSS_PER_TRADE_KRW",
"MOMENTUM_MIN_DROP_PCT_FOR_LOSS_CUT",
"MOMENTUM_MIN_PROFIT_PCT",
"MOMENTUM_MIN_HOLD_SEC",
"MOMENTUM_SLOT_MONEY",
"MOMENTUM_MAX_STOCKS",
"MOMENTUM_TOTAL_BUDGET_KRW",
"MOMENTUM_MAX_BUY_AMOUNT",
"MOMENTUM_RSI_PERIOD",
"MOMENTUM_COOLDOWN_SEC",
"MOMENTUM_LIVE_BACKTEST_ALIGN",
"MOMENTUM_LIVE_ALIGN_USE_FORMING_BAR",
"MOMENTUM_COOLDOWN_USE_CANDLE_FLOOR",
"MOMENTUM_COOLDOWN_ENGINE_ONLY",
"MOMENTUM_BACKTEST_SKIP_PRE_SUBSCRIBE",
"MOMENTUM_BACKTEST_USE_TICK_EXIT",
"MOMENTUM_BACKTEST_USE_TICK_ENTRY",
"MOMENTUM_BACKTEST_TICK_FALLBACK_OHLC",
"MOMENTUM_BACKTEST_WALLCLOCK_LAST_PRICE",
"MOMENTUM_BACKTEST_POLL_MS",
"MOMENTUM_BACKTEST_SELL_SLIP_PCT",
"MOMENTUM_BACKTEST_BUY_SLIP_PCT",
"MOMENTUM_BACKTEST_UNIVERSE_STRICT",
"MOMENTUM_BACKTEST_UNIVERSE_STRICT_LAG_MIN",
"MOMENTUM_BACKTEST_LIVE_SCAN_QUEUE",
"MOMENTUM_BACKTEST_SCAN_SEC",
"MOMENTUM_BACKTEST_UNIVERSE_SCAN_AT",
"MOMENTUM_BACKTEST_CANDLE_WARMUP_BARS",
"MOMENTUM_BACKTEST_REST_WARMUP",
"MOMENTUM_BACKTEST_REST_WARMUP_BARS",
"MOMENTUM_BACKTEST_REST_WARMUP_BARS_RETRY",
"MOMENTUM_BACKTEST_REST_MAX_CODES",
"MOMENTUM_BACKTEST_REST_SLEEP_SEC",
"MOMENTUM_BACKTEST_REST_KIWOOM_MOCK",
"MOMENTUM_PREV_DAY_OPEN_HM_MAX",
"MOMENTUM_UNIVERSE_EXIT_DEBOUNCE_SEC",
"MOMENTUM_LIVE_SIGNAL_LOOKBACK_BARS",
"MOMENTUM_LIVE_MIN_CANDLES",
"MOMENTUM_GAP_FILL_LIMIT",
"MOMENTUM_EOD_ENABLED",
"MOMENTUM_EOD_HM",
"MOMENTUM_FORCE_EOD_EXIT",
})
def _row_val(row: Dict[str, Any], key: str, default: Any = None) -> Any:
v = row.get(key)
if v not in (None, "", "None"):
return v
return default
def momentum_env_float(row: Dict[str, Any], key: str, default: float) -> float:
v = _row_val(row, key)
if v is None:
return float(default)
try:
return float(v)
except (TypeError, ValueError):
return float(default)
def momentum_env_int(row: Dict[str, Any], key: str, default: int) -> int:
v = _row_val(row, key)
if v is None:
return int(default)
try:
return int(float(v))
except (TypeError, ValueError):
return int(default)
def momentum_env_bool(row: Dict[str, Any], key: str, default: bool) -> bool:
v = _row_val(row, key)
if v is None:
return default
s = str(v).strip().lower()
if s in ("1", "true", "t", "y", "yes", "on"):
return True
if s in ("0", "false", "f", "n", "no", "off", ""):
return False
return default
def _legacy_float(row: Dict[str, Any], primary: str, legacy_keys: tuple, default: float) -> float:
v = _row_val(row, primary)
if v is not None:
try:
return float(v)
except (TypeError, ValueError):
pass
for lk in legacy_keys:
v = _row_val(row, lk)
if v is not None:
try:
return float(v)
except (TypeError, ValueError):
continue
return float(default)
def params_to_momentum_env_patch(p: Dict[str, Any]) -> Dict[str, str]:
"""파라서치·웹 params → MOMENTUM env 패치 (문자열)."""
patch: Dict[str, str] = {}
if not p:
return patch
def _set(k: str, v: Any) -> None:
if v is not None and v != "":
patch[k] = str(v)
if "mom_rsi_min" in p:
_set("MOMENTUM_RSI_MIN", p["mom_rsi_min"])
if "mom_rsi_max" in p:
_set("MOMENTUM_RSI_MAX", p["mom_rsi_max"])
if "mom_vol_mult" in p:
_set("MOMENTUM_VOL_MULT", p["mom_vol_mult"])
if "mom_vol_win" in p:
_set("MOMENTUM_VOL_WIN", p["mom_vol_win"])
if "mom_time_end_hm" in p:
_set("MOMENTUM_TIME_END_HM", int(float(p["mom_time_end_hm"])))
if "mom_max_from_open_pct" in p:
_set("MOMENTUM_MAX_FROM_OPEN_PCT", p["mom_max_from_open_pct"])
if "mom_min_from_open_pct" in p:
_set("MOMENTUM_MIN_FROM_OPEN_PCT", p["mom_min_from_open_pct"])
if "sl_pct" in p:
_set("MOMENTUM_STOP_LOSS_PCT", -abs(float(p["sl_pct"])))
if "tp_pct" in p:
_set("MOMENTUM_TAKE_PROFIT_PCT", p["tp_pct"])
if "tp_max_pct" in p:
_set("MOMENTUM_TP_MAX_PCT", p["tp_max_pct"])
if "shoulder_min_high" in p:
_set("MOMENTUM_SHOULDER_MIN_HIGH_PCT", p["shoulder_min_high"])
if "shoulder_cut_pct" in p:
_set("MOMENTUM_SHOULDER_CUT_PCT", p["shoulder_cut_pct"])
if "ratchet_tiers" in p:
patch["MOMENTUM_RATCHET_TIERS"] = str(p.get("ratchet_tiers") or "").strip()
if "trail_pct" in p:
_set("MOMENTUM_TRAIL_PCT", abs(float(p["trail_pct"])))
if "trail_arm_pct" in p:
_set("MOMENTUM_TRAIL_ARM_PCT", abs(float(p["trail_arm_pct"])))
if "max_hold_bars" in p and p["max_hold_bars"] is not None:
_set("MOMENTUM_MAX_HOLD_BARS", int(float(p["max_hold_bars"])))
if "max_daily" in p:
_set("MOMENTUM_MAX_DAILY", int(float(p["max_daily"])))
if "min_price" in p:
_set("MOMENTUM_MIN_PRICE", p["min_price"])
if "max_daily_chg" in p:
_set("MOMENTUM_MAX_DAILY_CHG", p["max_daily_chg"])
if "high_chase_thr" in p and p["high_chase_thr"] is not None:
_set("MOMENTUM_HIGH_CHASE_THR", float(p["high_chase_thr"]))
if "max_loss_krw" in p:
_set("MOMENTUM_MAX_LOSS_PER_TRADE_KRW", int(float(p["max_loss_krw"])))
if "cooldown_min" in p and p["cooldown_min"] is not None:
_set("MOMENTUM_COOLDOWN_SEC", int(float(p["cooldown_min"]) * 60))
if "time_start_hm" in p:
_set("MOMENTUM_TIME_START", int(float(p["time_start_hm"])))
if "time_end_hm" in p:
_set("MOMENTUM_TIME_END", int(float(p["time_end_hm"])))
for js_k, env_k in (
("skip_hts_scan_dupes", "MOMENTUM_SKIP_HTS_SCAN_DUPES"),
("use_defense_filters", "MOMENTUM_USE_DEFENSE_FILTERS"),
("use_high_chase_filter", "MOMENTUM_USE_HIGH_CHASE_FILTER"),
("use_daily_range_filter", "MOMENTUM_USE_DAILY_RANGE_FILTER"),
("use_ema_filter", "MOMENTUM_USE_EMA_FILTER"),
("use_rsi_max_filter", "MOMENTUM_USE_RSI_MAX_FILTER"),
("pattern_breakout", "MOMENTUM_PATTERN_BREAKOUT"),
("pattern_pullback", "MOMENTUM_PATTERN_PULLBACK"),
):
if js_k in p:
_set(env_k, str(p[js_k]).lower())
if "chase_lookback_min" in p:
_set("MOMENTUM_CHASE_LOOKBACK_MIN", int(float(p["chase_lookback_min"])))
if "pullback_lookback_min" in p:
_set("MOMENTUM_PULLBACK_LOOKBACK_MIN", int(float(p["pullback_lookback_min"])))
if "pullback_min_pct" in p:
_set("MOMENTUM_PULLBACK_MIN_PCT", float(p["pullback_min_pct"]))
if "pullback_max_pct" in p:
_set("MOMENTUM_PULLBACK_MAX_PCT", float(p["pullback_max_pct"]))
if "setup_vol_max_mult" in p:
_set("MOMENTUM_SETUP_VOL_MAX_MULT", float(p["setup_vol_max_mult"]))
if "setup_bear_bars_min" in p:
_set("MOMENTUM_SETUP_BEAR_BARS_MIN", int(float(p["setup_bear_bars_min"])))
if "ema_fast_period" in p:
_set("MOMENTUM_EMA_FAST_PERIOD", int(float(p["ema_fast_period"])))
if "ema_slow_period" in p:
_set("MOMENTUM_EMA_SLOW_PERIOD", int(float(p["ema_slow_period"])))
if "slot_money" in p:
_set("MOMENTUM_SLOT_MONEY", int(float(p["slot_money"])))
if "max_stocks" in p:
_set("MOMENTUM_MAX_STOCKS", int(float(p["max_stocks"])))
if "total_budget_krw" in p:
_set("MOMENTUM_TOTAL_BUDGET_KRW", int(float(p["total_budget_krw"])))
return patch