Files
kis_bot/kis_trader/engine/dbband_env_keys.py
Hwang 61c72a8a4c feat(tests): 신규 키움 웹소켓 조건검색 및 실시간 조건검색 테스트 추가
변경 사항
----
- _test_kiwoom_condition_list.py: 키움 웹소켓 조건검색 '목록조회' 기능을 단독으로 테스트하는 스크립트 추가
- _test_kiwoom_condition_realtime.py: 'momentum' 조건식을 실시간으로 등록하고 초기 매칭 종목 리스트 및 실시간 편입/이탈을 수신하는 테스트 스크립트 추가
- _verify_columnar_bitid.py, _verify_shared_e2e_breakout.py, _verify_shared_e2e.py: 공유 메모리 및 dict 간의 데이터 일관성을 검증하는 테스트 추가

영향
----
- 신규 테스트 스크립트 추가로 키움 웹소켓 API의 기능 검증 및 안정성을 높임
- 기존 기능에 대한 영향 없음

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-06 01:27:00 +09:00

213 lines
6.8 KiB
Python

"""
kis_trader/engine/dbband_env_keys.py — 더블 볼린저(DBBAND) env 키 단일 정의
=============================================================================
캐시 리엔 더블 BB(20/2 + 20/3) + 추세 MA 필터 — config_dbband · dbband_engine · 웹 · 파라서치 공통.
"""
from __future__ import annotations
from typing import Any, Dict, Optional
# config_dbband 전용 키
DBBAND_CONFIG_KEYS = frozenset({
"DBBAND_BB_PERIOD",
"DBBAND_BB_INNER_STD",
"DBBAND_BB_OUTER_STD",
"DBBAND_TREND_MA_PERIOD",
"DBBAND_USE_TREND_FILTER",
"DBBAND_SIDE_MODE",
"DBBAND_ENTRY_VALID_BARS",
"DBBAND_ENTRY_MODE",
"DBBAND_STOP_MODE",
"DBBAND_STOP_BUFFER_PCT",
"DBBAND_STOP_LOSS_PCT",
"DBBAND_TP_MODE",
"DBBAND_TAKE_PROFIT_PCT",
"DBBAND_RR_RATIO",
"DBBAND_EXIT_MODE",
"DBBAND_SHOULDER_MIN_HIGH_PCT",
"DBBAND_SHOULDER_CUT_PCT",
"DBBAND_TRAIL_PCT",
"DBBAND_TRAIL_ARM_PCT",
"DBBAND_TIME_START",
"DBBAND_TIME_END",
"DBBAND_COOLDOWN_SEC",
"DBBAND_MAX_DAILY",
"DBBAND_MIN_PRICE",
"DBBAND_SLOT_MONEY",
"DBBAND_MAX_STOCKS",
"DBBAND_TOTAL_BUDGET_KRW",
"DBBAND_MAX_BUY_AMOUNT",
"DBBAND_MAX_HOLD_BARS",
"DBBAND_TIMEFRAME",
"DBBAND_MIN_INVEST_RATIO_OF_SLOT",
"DBBAND_FORCE_EOD_EXIT",
"DBBAND_LIVE_MIN_CANDLES",
"DBBAND_LIVE_SIGNAL_LOOKBACK_BARS",
"DBBAND_UNIVERSE_SOURCE",
"DBBAND_GRID_BB_PERIOD0",
"DBBAND_GRID_BB_PERIOD1",
"DBBAND_GRID_INNER_STD0",
"DBBAND_GRID_INNER_STD1",
"DBBAND_GRID_OUTER_STD0",
"DBBAND_GRID_OUTER_STD1",
"DBBAND_GRID_TREND_MA0",
"DBBAND_GRID_TREND_MA1",
"DBBAND_GRID_SL0",
"DBBAND_GRID_SL1",
"DBBAND_GRID_TP0",
"DBBAND_GRID_TP1",
"DBBAND_GRID_RR0",
"DBBAND_GRID_RR1",
})
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 dbband_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 dbband_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 dbband_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 params_to_dbband_env_patch(p: Dict[str, Any]) -> Dict[str, str]:
"""파라서치·웹 JSON params → DBBAND 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)
mapping = (
("bb_period", "DBBAND_BB_PERIOD"),
("bb_inner_std", "DBBAND_BB_INNER_STD"),
("bb_outer_std", "DBBAND_BB_OUTER_STD"),
("trend_ma_period", "DBBAND_TREND_MA_PERIOD"),
("use_trend_filter", "DBBAND_USE_TREND_FILTER"),
("side_mode", "DBBAND_SIDE_MODE"),
("entry_valid_bars", "DBBAND_ENTRY_VALID_BARS"),
("entry_mode", "DBBAND_ENTRY_MODE"),
("stop_mode", "DBBAND_STOP_MODE"),
("stop_buffer_pct", "DBBAND_STOP_BUFFER_PCT"),
("sl_pct", "DBBAND_STOP_LOSS_PCT"),
("tp_mode", "DBBAND_TP_MODE"),
("tp_pct", "DBBAND_TAKE_PROFIT_PCT"),
("rr_ratio", "DBBAND_RR_RATIO"),
("exit_mode", "DBBAND_EXIT_MODE"),
("shoulder_min_high", "DBBAND_SHOULDER_MIN_HIGH_PCT"),
("shoulder_cut_pct", "DBBAND_SHOULDER_CUT_PCT"),
("trail_pct", "DBBAND_TRAIL_PCT"),
("trail_arm_pct", "DBBAND_TRAIL_ARM_PCT"),
("time_start_hm", "DBBAND_TIME_START"),
("time_end_hm", "DBBAND_TIME_END"),
("max_daily", "DBBAND_MAX_DAILY"),
("min_price", "DBBAND_MIN_PRICE"),
("slot_money", "DBBAND_SLOT_MONEY"),
("max_stocks", "DBBAND_MAX_STOCKS"),
("total_budget_krw", "DBBAND_TOTAL_BUDGET_KRW"),
("max_hold_bars", "DBBAND_MAX_HOLD_BARS"),
("timeframe", "DBBAND_TIMEFRAME"),
("min_invest_ratio_of_slot", "DBBAND_MIN_INVEST_RATIO_OF_SLOT"),
)
for js_k, env_k in mapping:
if js_k in p and p[js_k] is not None:
_set(env_k, p[js_k])
if "use_trend_filter" in p:
_set("DBBAND_USE_TREND_FILTER", str(p["use_trend_filter"]).lower())
if "cooldown_min" in p and p["cooldown_min"] is not None:
_set("DBBAND_COOLDOWN_SEC", int(float(p["cooldown_min"]) * 60))
if "sl_pct" in p:
_set("DBBAND_STOP_LOSS_PCT", abs(float(p["sl_pct"])))
if "force_eod_exit" in p:
_set("DBBAND_FORCE_EOD_EXIT", str(p["force_eod_exit"]).lower())
return patch
def web_body_to_dbband_env_patch(body: Dict[str, Any]) -> Dict[str, str]:
"""웹 saveDbBandConfig POST → DBBAND env 패치."""
if not isinstance(body, dict):
return {}
def _get(key: str) -> Any:
v = body.get(key)
if v is None or v == "":
return None
return v
p: Dict[str, Any] = {}
for k in (
"bb_period", "bb_inner_std", "bb_outer_std", "trend_ma_period",
"side_mode", "entry_valid_bars", "entry_mode", "stop_mode",
"stop_buffer_pct", "tp_mode", "rr_ratio",
"cooldown_min", "max_daily", "min_price", "slot_money",
"max_stocks", "total_budget_krw", "max_hold_bars", "timeframe",
"min_invest_ratio_of_slot",
):
v = _get(k)
if v is not None:
p[k] = v
sl = _get("sl_pct")
if sl is not None:
p["sl_pct"] = float(sl)
tp = _get("tp_pct")
if tp is not None:
p["tp_pct"] = float(tp)
smh = _get("shoulder_min_high")
if smh is not None:
p["shoulder_min_high"] = float(smh)
sc = _get("shoulder_cut_pct")
if sc is not None:
p["shoulder_cut_pct"] = float(sc)
tr = _get("trail_pct")
if tr is not None:
p["trail_pct"] = float(tr)
tra = _get("trail_arm_pct")
if tra is not None:
p["trail_arm_pct"] = float(tra)
utf = _get("use_trend_filter")
if utf is not None:
p["use_trend_filter"] = utf
if "time_start" in body and body["time_start"] not in (None, ""):
p["time_start_hm"] = int(float(body["time_start"]))
if "time_end" in body and body["time_end"] not in (None, ""):
p["time_end_hm"] = int(float(body["time_end"]))
if "force_eod_exit" in body:
p["force_eod_exit"] = body.get("force_eod_exit")
return params_to_dbband_env_patch(p)