feat: 새로운 안전 규칙 및 최적화 적용을 통한 트레이딩 시스템 개선
변경 사항 (Changes): 구문 오류(Syntax error) 및 토큰 낭비를 방지하기 위해 에이전트 쉘(Agent shell)과 파이썬 코드 스니펫에 다수의 신규 안전 규칙(Safety rules)을 추가함. 스키마 검증 및 적절한 SQL 포맷팅을 보장하기 위해 임시(Ad-hoc) 데이터베이스 쿼리 작성 가이드라인을 도입함. 코드 수정 후 UI 기능이 정상 작동하는지 확인하기 위해, 백테스트 웹 서비스 재시작 및 브라우저 검증에 대한 새로운 규칙을 구현함. 시스템 전반의 무결성(Integrity)을 유지하기 위해 실전 매매(Live trading), 웹 백테스팅, 파라미터 탐색(Parameter searches) 간의 일관성 검사(Consistency checks) 체계를 확립함. 기대 효과 (Impact): 이러한 개선 사항들은 트레이딩 시스템의 견고성(Robustness)과 신뢰성을 향상시키며, 에러 발생을 최소화하고 다양한 시스템 컴포넌트 간의 원활한 상호작용을 보장함.
This commit is contained in:
187
kis_trader/engine/orderbook_env.py
Normal file
187
kis_trader/engine/orderbook_env.py
Normal file
@@ -0,0 +1,187 @@
|
||||
"""
|
||||
kis_trader/engine/orderbook_env.py — 호가필터 env 키 (전략 전용)
|
||||
================================================================
|
||||
글로벌 ``ORDERBOOK_*`` 임계값은 폐기. 실매·그리드·apply-best 는
|
||||
``{SCALP|TAIL|MOMENTUM|BREAKOUT}_ORDERBOOK_*`` 만 사용.
|
||||
|
||||
코드 기본값 = DB 시드·웹 스키마 default 와 동일해야 함.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
from kis_trader.utils.env import get_env_bool, get_env_float, get_env_from_db, get_env_int
|
||||
|
||||
# 임계값 기본 (하드코딩 금지 규칙: get_env_* 기본 인자로만 사용 · DB 시드와 동기)
|
||||
OB_DEFAULT_FILTER_ENABLED = False # 현행 실매 OFF 유지
|
||||
OB_DEFAULT_MAX_SPREAD_PCT = 0.45
|
||||
OB_DEFAULT_ENTRY_BID_LEVELS = 3
|
||||
OB_DEFAULT_ENTRY_BID_DEPTH_MULT = 1.2
|
||||
OB_DEFAULT_ENTRY_ASK_MAX_MULT = 3.0
|
||||
OB_DEFAULT_MIN_BID_ASK_RATIO = 0.85
|
||||
OB_DEFAULT_ASK_WALL_MAX_QTY = 5000
|
||||
|
||||
# 시드·UI 대상 전략 prefix (SHORT 오케스트레이터 → TAIL_* canonical)
|
||||
OB_STRATEGY_PREFIXES: Tuple[str, ...] = ("SCALP", "TAIL", "MOMENTUM", "BREAKOUT")
|
||||
|
||||
|
||||
def orderbook_strategy_prefix(strategy: str) -> str:
|
||||
s = (strategy or "").strip().upper()
|
||||
if s in ("BREAKOUT", "BO"):
|
||||
return "BREAKOUT"
|
||||
if s in ("MOMENTUM", "MOM"):
|
||||
return "MOMENTUM"
|
||||
if s in ("TAIL", "SHORT", "TAIL_CATCH"):
|
||||
return "TAIL"
|
||||
if s in ("SCALP", "SCALPING", "REVERSAL"):
|
||||
return "SCALP"
|
||||
return s
|
||||
|
||||
|
||||
def _key(pfx: str, suffix: str) -> str:
|
||||
return f"{pfx}_ORDERBOOK_{suffix}"
|
||||
|
||||
|
||||
def orderbook_filter_enabled(strategy: str) -> bool:
|
||||
"""전략별 ON/OFF. 글로벌 ORDERBOOK_FILTER_ENABLED 폐기 → 기본 OFF."""
|
||||
pfx = orderbook_strategy_prefix(strategy)
|
||||
if not pfx or pfx == "ORDERBOOK":
|
||||
return False
|
||||
return get_env_bool(_key(pfx, "FILTER_ENABLED"), OB_DEFAULT_FILTER_ENABLED)
|
||||
|
||||
|
||||
def get_ob_float(strategy: str, suffix: str, default: float) -> float:
|
||||
pfx = orderbook_strategy_prefix(strategy)
|
||||
if not pfx:
|
||||
return float(default)
|
||||
return float(get_env_float(_key(pfx, suffix), default))
|
||||
|
||||
|
||||
def get_ob_int(strategy: str, suffix: str, default: int) -> int:
|
||||
pfx = orderbook_strategy_prefix(strategy)
|
||||
if not pfx:
|
||||
return int(default)
|
||||
return int(get_env_int(_key(pfx, suffix), default))
|
||||
|
||||
|
||||
def load_orderbook_threshold_cfg(
|
||||
strategy: str,
|
||||
params: Optional[Dict[str, Any]] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""실매/백테 공통 임계값. params 의 ``_ob_*`` 가 있으면 덮어씀(파람서치)."""
|
||||
pfx = orderbook_strategy_prefix(strategy)
|
||||
cfg = {
|
||||
"max_spread_pct": get_ob_float(pfx, "MAX_SPREAD_PCT", OB_DEFAULT_MAX_SPREAD_PCT),
|
||||
"entry_bid_levels": get_ob_int(pfx, "ENTRY_BID_LEVELS", OB_DEFAULT_ENTRY_BID_LEVELS),
|
||||
"entry_bid_depth_mult": get_ob_float(
|
||||
pfx, "ENTRY_BID_DEPTH_MULT", OB_DEFAULT_ENTRY_BID_DEPTH_MULT,
|
||||
),
|
||||
"entry_ask_max_mult": get_ob_float(
|
||||
pfx, "ENTRY_ASK_MAX_MULT", OB_DEFAULT_ENTRY_ASK_MAX_MULT,
|
||||
),
|
||||
"min_bid_ask_ratio": get_ob_float(
|
||||
pfx, "MIN_BID_ASK_RATIO", OB_DEFAULT_MIN_BID_ASK_RATIO,
|
||||
),
|
||||
"breakout_ask_wall_max_qty": get_ob_int(
|
||||
pfx, "ASK_WALL_MAX_QTY", OB_DEFAULT_ASK_WALL_MAX_QTY,
|
||||
),
|
||||
}
|
||||
if params:
|
||||
_overrides = (
|
||||
("_ob_max_spread_pct", "max_spread_pct"),
|
||||
("_ob_min_bid_ask_ratio", "min_bid_ask_ratio"),
|
||||
("_ob_ask_max_mult", "entry_ask_max_mult"),
|
||||
("_ob_ask_wall_max_qty", "breakout_ask_wall_max_qty"),
|
||||
("_ob_bid_depth_mult", "entry_bid_depth_mult"),
|
||||
("_ob_bid_levels", "entry_bid_levels"),
|
||||
)
|
||||
for src_key, cfg_key in _overrides:
|
||||
ov = params.get(src_key)
|
||||
if ov is not None and str(ov).strip() != "":
|
||||
try:
|
||||
cfg[cfg_key] = float(ov)
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
return cfg
|
||||
|
||||
|
||||
def orderbook_params_to_env_patch(strategy: str, params: Dict[str, Any]) -> Dict[str, str]:
|
||||
"""그리드/Optuna merged → 전략별 ORDERBOOK_* 패치 (값 있을 때만)."""
|
||||
pfx = orderbook_strategy_prefix(strategy)
|
||||
if not pfx:
|
||||
return {}
|
||||
out: Dict[str, str] = {}
|
||||
if "max_spread_pct" in params and params.get("max_spread_pct") not in (None, ""):
|
||||
out[_key(pfx, "MAX_SPREAD_PCT")] = str(float(params["max_spread_pct"]))
|
||||
if "min_bid_ask_ratio" in params and params.get("min_bid_ask_ratio") not in (None, ""):
|
||||
out[_key(pfx, "MIN_BID_ASK_RATIO")] = str(float(params["min_bid_ask_ratio"]))
|
||||
if "ask_max_mult" in params and params.get("ask_max_mult") not in (None, ""):
|
||||
out[_key(pfx, "ENTRY_ASK_MAX_MULT")] = str(float(params["ask_max_mult"]))
|
||||
if "ask_wall_max_qty" in params and params.get("ask_wall_max_qty") not in (None, ""):
|
||||
out[_key(pfx, "ASK_WALL_MAX_QTY")] = str(int(float(params["ask_wall_max_qty"])))
|
||||
if "bid_depth_mult" in params and params.get("bid_depth_mult") not in (None, ""):
|
||||
out[_key(pfx, "ENTRY_BID_DEPTH_MULT")] = str(float(params["bid_depth_mult"]))
|
||||
if "bid_levels" in params and params.get("bid_levels") not in (None, ""):
|
||||
out[_key(pfx, "ENTRY_BID_LEVELS")] = str(int(float(params["bid_levels"])))
|
||||
# 호가축이 섞인 apply 면 필터 ON (탐색 결과를 실매에 쓰려면 필요)
|
||||
if out and params.get("ob_filter_enabled") not in (None, ""):
|
||||
out[_key(pfx, "FILTER_ENABLED")] = (
|
||||
"true" if str(params.get("ob_filter_enabled")).lower() in ("1", "true", "yes", "on")
|
||||
else "false"
|
||||
)
|
||||
return out
|
||||
|
||||
|
||||
def build_orderbook_seed_patch(
|
||||
*,
|
||||
filter_enabled: Optional[bool] = None,
|
||||
copy_global_if_empty: bool = True,
|
||||
) -> Dict[str, str]:
|
||||
"""전략별 기본값 시드. 빈 칸만 채움(이미 값 있으면 유지).
|
||||
|
||||
copy_global_if_empty: 구 글로벌 ORDERBOOK_* 가 있으면 1회 이관.
|
||||
"""
|
||||
fe = OB_DEFAULT_FILTER_ENABLED if filter_enabled is None else bool(filter_enabled)
|
||||
# 글로벌 → 전략 이관용 (폐기 전 잔존값)
|
||||
g_spread = str(get_env_from_db("ORDERBOOK_MAX_SPREAD_PCT", "") or "").strip()
|
||||
g_levels = str(get_env_from_db("ORDERBOOK_ENTRY_BID_LEVELS", "") or "").strip()
|
||||
g_depth = str(get_env_from_db("ORDERBOOK_ENTRY_BID_DEPTH_MULT", "") or "").strip()
|
||||
g_ask = str(get_env_from_db("ORDERBOOK_ENTRY_ASK_MAX_MULT", "") or "").strip()
|
||||
g_ratio = str(get_env_from_db("ORDERBOOK_MIN_BID_ASK_RATIO", "") or "").strip()
|
||||
g_wall = str(get_env_from_db("ORDERBOOK_BREAKOUT_ASK_WALL_MAX_QTY", "") or "").strip()
|
||||
g_filt = str(get_env_from_db("ORDERBOOK_FILTER_ENABLED", "") or "").strip()
|
||||
|
||||
patch: Dict[str, str] = {}
|
||||
for pfx in OB_STRATEGY_PREFIXES:
|
||||
pairs: List[Tuple[str, str, str]] = [
|
||||
("FILTER_ENABLED", "false" if not fe else "true", g_filt),
|
||||
("MAX_SPREAD_PCT", str(OB_DEFAULT_MAX_SPREAD_PCT), g_spread),
|
||||
("ENTRY_BID_LEVELS", str(OB_DEFAULT_ENTRY_BID_LEVELS), g_levels),
|
||||
("ENTRY_BID_DEPTH_MULT", str(OB_DEFAULT_ENTRY_BID_DEPTH_MULT), g_depth),
|
||||
("ENTRY_ASK_MAX_MULT", str(OB_DEFAULT_ENTRY_ASK_MAX_MULT), g_ask),
|
||||
("MIN_BID_ASK_RATIO", str(OB_DEFAULT_MIN_BID_ASK_RATIO), g_ratio),
|
||||
]
|
||||
if pfx == "BREAKOUT":
|
||||
pairs.append(("ASK_WALL_MAX_QTY", str(OB_DEFAULT_ASK_WALL_MAX_QTY), g_wall))
|
||||
for suf, default_s, glob_s in pairs:
|
||||
k = _key(pfx, suf)
|
||||
cur = str(get_env_from_db(k, "") or "").strip()
|
||||
if cur:
|
||||
continue
|
||||
if suf == "FILTER_ENABLED":
|
||||
# 시드 우선순위: 호출자 fe → (이관 시) 글로벌 → 코드 기본
|
||||
if filter_enabled is not None:
|
||||
patch[k] = "true" if fe else "false"
|
||||
elif copy_global_if_empty and glob_s:
|
||||
patch[k] = (
|
||||
"true"
|
||||
if glob_s.lower() in ("1", "true", "yes", "on")
|
||||
else "false"
|
||||
)
|
||||
else:
|
||||
patch[k] = default_s
|
||||
elif copy_global_if_empty and glob_s:
|
||||
patch[k] = glob_s
|
||||
else:
|
||||
patch[k] = default_s
|
||||
return patch
|
||||
Reference in New Issue
Block a user