refactor: enhance Optuna backtesting framework, optimize orderbook filtering, and update database management utilities.
This commit is contained in:
@@ -26,6 +26,22 @@ from ..execution.order_manager import OrderManager
|
||||
from ..network.ws_manager import WSManager
|
||||
from ..utils.env import get_env_bool, get_env_from_db, get_env_int
|
||||
from ..utils.logger import get_logger
|
||||
import logging
|
||||
|
||||
class FeedPrefixLoggerAdapter(logging.LoggerAdapter):
|
||||
def process(self, msg, kwargs):
|
||||
if isinstance(msg, str) and "🔍 [" in msg and "시세:" not in msg and "LIVE_TICK_PROVIDER" in self.extra:
|
||||
try:
|
||||
db_obj = self.extra.get("db")
|
||||
if db_obj:
|
||||
tick_p = str(db_obj.get_merged_env_snapshot().get("LIVE_TICK_PROVIDER") or "kiwoom").strip().lower()
|
||||
ob_p = str(db_obj.get_merged_env_snapshot().get("LIVE_OB_PROVIDER") or "kiwoom").strip().lower()
|
||||
if tick_p or ob_p:
|
||||
msg = msg.replace("🔍 [", f"🔍 [T:{tick_p}|O:{ob_p}|", 1)
|
||||
except Exception:
|
||||
pass
|
||||
return msg, kwargs
|
||||
|
||||
|
||||
|
||||
# 비본주 판별 — kis_trader.utils.non_stock 공용 (KISClient 랭킹 필터와 동일)
|
||||
@@ -91,7 +107,8 @@ class BaseStrategy(ABC, threading.Thread):
|
||||
# LsConditionSearchManager (LS AFR 조건검색, 선택). 시세는 키움/한투 유지.
|
||||
self.ls_condition_mgr = ls_condition_mgr
|
||||
self.market_guard = market_guard # MarketGuard (선택, None 이면 가드 없음)
|
||||
self.logger = get_logger(f"kis_trader.strategy.{self.strategy_id}")
|
||||
base_logger = get_logger(f"kis_trader.strategy.{self.strategy_id}")
|
||||
self.logger = FeedPrefixLoggerAdapter(base_logger, {"db": self.db, "LIVE_TICK_PROVIDER": True})
|
||||
# MarketGuard PANIC 차단 로그 스팸 방지용 (분당 1회)
|
||||
self._panic_log_ts: float = 0.0
|
||||
|
||||
@@ -316,6 +333,20 @@ class BaseStrategy(ABC, threading.Thread):
|
||||
# 보유 목록 = DB 진실 + _runtime 오버레이 (poll 체결·재시작 정합)
|
||||
self._sync_holdings_from_db()
|
||||
|
||||
# 전략 ON/OFF 핫게이트 — WS 구독 해제 없음. 보유 청산만 유지.
|
||||
if not self._strategy_switch_enabled():
|
||||
if self.holdings:
|
||||
with self._sell_lock:
|
||||
sell_signals = self.check_sell_signals()
|
||||
if sell_signals and get_env_bool(
|
||||
"REAL_BALANCE_VERIFY_BEFORE_SELL", True
|
||||
):
|
||||
self.order_mgr.prefetch_broker_holdings()
|
||||
for sig in sell_signals:
|
||||
self._submit_sell(sig)
|
||||
time.sleep(self._scan_sleep("loop"))
|
||||
continue
|
||||
|
||||
# 일일익절 B안 — 매수루프 비어도 hit 후 보유 리스크 정리
|
||||
guard = getattr(self, "daily_profit_halt", None)
|
||||
if guard is not None:
|
||||
@@ -524,13 +555,17 @@ class BaseStrategy(ABC, threading.Thread):
|
||||
_codes = [c for c in _codes if c]
|
||||
if _codes and str(getattr(self, "strategy_id", "")).upper().startswith("US_"):
|
||||
self.logger.info(
|
||||
"🔍 [매수체크] 후보 %d (보유 %d/%d) codes=%s",
|
||||
"🔍 [매수체크/T:%s|O:%s] 후보 %d (보유 %d/%d) codes=%s",
|
||||
self.db.get_merged_env_snapshot().get("LIVE_TICK_PROVIDER", "kiwoom"),
|
||||
self.db.get_merged_env_snapshot().get("LIVE_OB_PROVIDER", "kiwoom"),
|
||||
len(candidates), active_cnt, max_stocks,
|
||||
",".join(_codes[:12]),
|
||||
)
|
||||
else:
|
||||
self.logger.info(
|
||||
"🔍 [매수체크] 후보 %d (보유 %d/%d)",
|
||||
"🔍 [매수체크/T:%s|O:%s] 후보 %d (보유 %d/%d)",
|
||||
self.db.get_merged_env_snapshot().get("LIVE_TICK_PROVIDER", "kiwoom"),
|
||||
self.db.get_merged_env_snapshot().get("LIVE_OB_PROVIDER", "kiwoom"),
|
||||
len(candidates), active_cnt, max_stocks,
|
||||
)
|
||||
for c in candidates:
|
||||
@@ -616,6 +651,25 @@ class BaseStrategy(ABC, threading.Thread):
|
||||
# ------------------------------------------------------------------
|
||||
def _submit_buy(self, signal: Dict):
|
||||
from ..execution.order_manager import OrderRequest
|
||||
import json
|
||||
|
||||
code = signal["code"]
|
||||
feats = signal.get("entry_features") or {}
|
||||
if "_env_snapshot" not in feats:
|
||||
snap = {}
|
||||
try:
|
||||
ob = self.ws.get_orderbook(code)
|
||||
if ob:
|
||||
snap = ob.copy()
|
||||
except Exception:
|
||||
pass
|
||||
if snap:
|
||||
# 불필요한 큰 필드 제거 (메모리/DB 최적화)
|
||||
snap.pop("raw", None)
|
||||
feats["_env_snapshot"] = json.dumps(snap, ensure_ascii=False)
|
||||
else:
|
||||
feats["_env_snapshot"] = "{}"
|
||||
signal["entry_features"] = feats
|
||||
|
||||
req = OrderRequest(
|
||||
strategy_id=self.strategy_id,
|
||||
@@ -1028,6 +1082,7 @@ class BaseStrategy(ABC, threading.Thread):
|
||||
``kis_trader.utils.non_stock.is_non_stock`` (코드 끝 5/7/9·알파벳 + 이름).
|
||||
- ``{STRATEGY_ID}_CAND_LIMIT`` (기본 0=무제한): 매니저 원본 순서 보존하며 상위 N개만 사용.
|
||||
WS 구독 41 한도 안전 + cond/ranking 폭주 시 매수 체크 분당 회전율 보장.
|
||||
(조건검색 매니저는 신규 ENTER·t1859 스냅을 앞으로 두어 캡에 신규가 안 잘리게 함)
|
||||
"""
|
||||
if not candidates:
|
||||
return []
|
||||
@@ -1087,6 +1142,18 @@ class BaseStrategy(ABC, threading.Thread):
|
||||
"(history=%s 교집합, 제외 %d)",
|
||||
sid, before, len(candidates), hist_src, dropped,
|
||||
)
|
||||
# 전량 탈락 = 운영 치명 (장중만 ops_alert 세션게이트)
|
||||
if before > 0 and len(candidates) == 0:
|
||||
try:
|
||||
from ..utils.ops_alert import ops_alert
|
||||
ops_alert(
|
||||
"universe_wipe",
|
||||
f"{sid} 유니버스 전량탈락 {before}→0",
|
||||
detail=f"history={hist_src} 교집합 제외 {dropped}",
|
||||
level="critical",
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return candidates
|
||||
|
||||
@@ -1108,6 +1175,28 @@ class BaseStrategy(ABC, threading.Thread):
|
||||
"""설정 리로드 훅 (서브클래스 오버라이드)."""
|
||||
pass
|
||||
|
||||
def _strategy_switch_enabled(self) -> bool:
|
||||
"""``STRATEGY_{SID}_ENABLED`` — 운영설정 ON/OFF 핫게이트.
|
||||
|
||||
False 여도 WS 구독은 유지(해제 안 함). 기동 시 OFF 전략은 쓰레드 자체가 없음.
|
||||
"""
|
||||
sid = str(getattr(self, "strategy_id", "") or "").strip().upper()
|
||||
if not sid:
|
||||
return True
|
||||
# main._register_strategies 기본값과 동기
|
||||
defaults = {
|
||||
"SCALP": True,
|
||||
"SHORT": True,
|
||||
"MOMENTUM": False,
|
||||
"US_MOMENTUM": True,
|
||||
"BREAKOUT": False,
|
||||
"RANGE_BREAK": False,
|
||||
"UPDOW": False,
|
||||
"DBBAND": False,
|
||||
"DART": False,
|
||||
}
|
||||
return bool(get_env_bool(f"STRATEGY_{sid}_ENABLED", defaults.get(sid, True)))
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# 서브클래스 구현 필수 메서드
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user