ls증권 히스토리 구독 넣음

This commit is contained in:
Your Name
2026-07-30 18:05:07 +09:00
parent 61bec4bd1d
commit 67eab24603
1593 changed files with 135733 additions and 1232 deletions

View File

@@ -52,8 +52,10 @@ def resolve_prev_trading_day_open(
"""
from kis_trader.utils.env import get_env_int
# 전일 시가로 인정할 최대 HHMM (기본 09:10 — 그 이후만 있으면 장시작 시가 미확정)
open_hm_max = max(900, int(get_env_int("MOMENTUM_PREV_DAY_OPEN_HM_MAX", 910)))
# 전일 시가로 인정할 최대 HHMM.
# 기본 1000: 저유동 종목이 09:14 첫체결만 있어도 시가로 인정.
# (구 910은 월요 갭 후 전일 오전 봉이 얇으면 전부 '전일시가없음'으로 매수 불가)
open_hm_max = max(900, int(get_env_int("MOMENTUM_PREV_DAY_OPEN_HM_MAX", 1000)))
prev_day: Optional[str] = None
prev_open: Optional[float] = None
@@ -256,7 +258,18 @@ def eval_momentum_hts_buy_at_index(
cl = float(c.get("close", 0) or 0)
hi = float(c.get("high", cl) or cl)
if hm < time_start_hm or hm >= time_end_hm:
# 해외 US: params._session_wrap_midnight 로 자정 넘김 RTH 허용 (국내는 기존과 동일)
from kis_trader.utils.session_hm import hm_in_trading_window
_wrap = bool(params.get("_session_wrap_midnight"))
if not hm_in_trading_window(hm, time_start_hm, time_end_hm, wrap_midnight=_wrap):
# 국내는 장중 루프에서 흔한 silent skip. 해외는 세션 키 오설정 디버깅용으로 탈락 표기.
if str(params.get("market") or "").strip().upper() == "US":
return (
"탈락-시간외",
"hm=%04d not in %d~%d (wrap=%s)"
% (hm, time_start_hm, time_end_hm, _wrap),
None,
)
return (None, None, None)
if use_defense and cl < min_price:
@@ -275,8 +288,49 @@ def eval_momentum_hts_buy_at_index(
prev_open = resolve_prev_trading_day_open(candles, i, day)
# kiwoom momentum SCAN — E∧F∧H∧I 이미 통과, TRIGGER 는 타이밍·호가·휩쏘만
# ※ 해외(US)는 HTS SCAN 이 없으므로 skip_hts 숏컷으로 매 봉 시그널 내면 안 됨
# → V4 추격 패턴(돌파 OR 눌림재돌파) + RSI 하한.
if skip_hts:
sig_hts: Dict[str, Any] = {
is_us = str(params.get("market") or "").strip().upper() == "US"
if is_us:
from kis_trader.engine.momentum_chase_patterns import eval_momentum_chase_pattern
ok_pat, pat_name, metrics = eval_momentum_chase_pattern(candles, i, params)
if not ok_pat:
return (
"탈락-패턴",
"추격패턴 미충족 (%s) close=%.4f" % (pat_name or "?", cl),
None,
)
rsi_val: Optional[float] = None
if compute_rsi_series_fn is not None:
closes = [float(x["close"]) for x in candles]
ic = params.get("_indicator_cache")
if ic is not None and hasattr(ic, "rsi_at"):
rsi_val = ic.rsi_at(i, rsi_period)
else:
rsis = compute_rsi_series_fn(closes, rsi_period)
rsi_val = rsis[i] if i < len(rsis) else None
if rsi_val is None:
return ("탈락-RSI없음", "RSI 미계산", None)
if rsi_val < rsi_min:
return ("탈락-RSI약함", "RSI=%.1f < %.0f" % (rsi_val, rsi_min), None)
use_rsi_max = _to_bool(params.get("use_rsi_max_filter"), False)
if use_rsi_max and rsi_val > rsi_max:
return ("탈락-RSI과열", "RSI=%.1f > %.0f" % (rsi_val, rsi_max), None)
sig_hts: Dict[str, Any] = {
"signal": True,
"mode": "us_momentum_chase",
"pattern": pat_name or "chase",
"signal_candle_time": c.get("candle_time"),
"prev_day_open": prev_open,
}
if metrics:
sig_hts.update(metrics)
if rsi_val is not None:
sig_hts["rsi"] = rsi_val
return (None, None, sig_hts)
sig_hts = {
"signal": True,
"mode": "momentum_hts",
"pattern": "momentum_hts_scan",