280 lines
11 KiB
Python
280 lines
11 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_E_MIN_CHG_PCT",
|
|
"MOMENTUM_EXIT_OB_ENABLED",
|
|
"MOMENTUM_EXIT_OB_RATIO_MIN",
|
|
"MOMENTUM_EXIT_OB_MA_WINDOW",
|
|
"MOMENTUM_EXIT_OB_MIN_PROFIT_PCT",
|
|
"MOMENTUM_EXIT_OB_MIN_HOLD_BARS",
|
|
"MOMENTUM_STOP_OB_ENABLED",
|
|
"MOMENTUM_STOP_OB_RATIO_MIN",
|
|
"MOMENTUM_STOP_OB_MA_WINDOW",
|
|
"MOMENTUM_STOP_OB_MIN_LOSS_PCT",
|
|
"MOMENTUM_STOP_OB_MIN_HOLD_BARS",
|
|
"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 "e_min_chg_pct" in p:
|
|
_set("MOMENTUM_E_MIN_CHG_PCT", p["e_min_chg_pct"])
|
|
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"])))
|
|
if "exit_ob_enabled" in p:
|
|
_set("MOMENTUM_EXIT_OB_ENABLED", str(p["exit_ob_enabled"]).lower())
|
|
if "exit_ob_ratio_min" in p and p.get("exit_ob_ratio_min") not in (None, ""):
|
|
_set("MOMENTUM_EXIT_OB_RATIO_MIN", float(p["exit_ob_ratio_min"]))
|
|
if "exit_ob_ma_window" in p and p.get("exit_ob_ma_window") not in (None, ""):
|
|
_set("MOMENTUM_EXIT_OB_MA_WINDOW", int(float(p["exit_ob_ma_window"])))
|
|
if "exit_ob_min_profit_pct" in p and p.get("exit_ob_min_profit_pct") not in (None, ""):
|
|
_set("MOMENTUM_EXIT_OB_MIN_PROFIT_PCT", float(p["exit_ob_min_profit_pct"]))
|
|
if "exit_ob_min_hold_bars" in p and p.get("exit_ob_min_hold_bars") not in (None, ""):
|
|
_set("MOMENTUM_EXIT_OB_MIN_HOLD_BARS", int(float(p["exit_ob_min_hold_bars"])))
|
|
if "stop_ob_enabled" in p:
|
|
_set("MOMENTUM_STOP_OB_ENABLED", str(p["stop_ob_enabled"]).lower())
|
|
if "stop_ob_ratio_min" in p and p.get("stop_ob_ratio_min") not in (None, ""):
|
|
_set("MOMENTUM_STOP_OB_RATIO_MIN", float(p["stop_ob_ratio_min"]))
|
|
if "stop_ob_ma_window" in p and p.get("stop_ob_ma_window") not in (None, ""):
|
|
_set("MOMENTUM_STOP_OB_MA_WINDOW", int(float(p["stop_ob_ma_window"])))
|
|
if "stop_ob_min_loss_pct" in p and p.get("stop_ob_min_loss_pct") not in (None, ""):
|
|
_set("MOMENTUM_STOP_OB_MIN_LOSS_PCT", float(p["stop_ob_min_loss_pct"]))
|
|
if "stop_ob_min_hold_bars" in p and p.get("stop_ob_min_hold_bars") not in (None, ""):
|
|
_set("MOMENTUM_STOP_OB_MIN_HOLD_BARS", int(float(p["stop_ob_min_hold_bars"])))
|
|
return patch
|