Changes: - Introduced the `e_min_chg_pct` parameter to define the minimum price change percentage compared to the previous day's close, enhancing the momentum trading strategy. - Updated various functions and classes to incorporate this new parameter, ensuring it is utilized in both backtesting and live trading scenarios. - Improved documentation and comments to clarify the purpose and usage of the new parameter across the codebase. Impact: - This addition allows for more precise control over trading conditions, potentially increasing the effectiveness of the momentum strategy while maintaining system integrity and performance.
250 lines
9.1 KiB
Python
250 lines
9.1 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_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"])))
|
|
return patch
|