ls증권 히스토리 구독 넣음
This commit is contained in:
@@ -81,12 +81,15 @@ class ScalpingStrategy(BaseStrategy):
|
||||
"require_reversal_candle": get_env_bool(
|
||||
"SCALP_REQUIRE_REVERSAL_CANDLE", True,
|
||||
),
|
||||
# True: 백테스트와 동일 — 신호봉(직전 확정봉) 조건 충족 시 현재봉에서 진입
|
||||
"live_backtest_align": get_env_bool(
|
||||
"SCALP_LIVE_BACKTEST_ALIGN", True,
|
||||
# True: 백테스트와 동일 — 신호봉(T−1) 충족 시 진입봉(T) 시가/첫 틱
|
||||
# DB 빈문자("") 는 get_env_bool 이 False 로 읽히므로 defaults 우선
|
||||
"live_backtest_align": bool(_d.get("live_backtest_align", True)),
|
||||
"live_signal_lookback_bars": int(
|
||||
_d.get("live_signal_lookback_bars", 1) or 1,
|
||||
),
|
||||
"live_signal_lookback_bars": get_env_int(
|
||||
"SCALP_LIVE_SIGNAL_LOOKBACK_BARS", 1,
|
||||
# 형성 중 봉(T)을 진입봉으로 — 확정봉만 쓰면 1봉 지연 (모멘텀과 동일)
|
||||
"live_align_use_forming_bar": bool(
|
||||
_d.get("live_align_use_forming_bar", True),
|
||||
),
|
||||
# 일일 진입 횟수 (reversal 기본 3회)
|
||||
"max_daily": get_env_int(
|
||||
@@ -127,12 +130,47 @@ class ScalpingStrategy(BaseStrategy):
|
||||
if get_env_bool("FORCE_BUY_TEST", False):
|
||||
return self._force_buy_test(code, name)
|
||||
|
||||
candles_raw = self.ws.get_candles(code, self.candle_tf, n=50)
|
||||
if len(candles_raw) < 5:
|
||||
candles_raw = list(self.ws.get_candles(code, self.candle_tf, n=50) or [])
|
||||
# ALIGN: 형성 중 봉(T)을 진입봉으로 붙여 BT(portfolio next open/첫 틱)와 동일 시점
|
||||
params = self._scan_engine_params or {}
|
||||
use_forming = bool(params.get("live_align_use_forming_bar", True))
|
||||
if params.get("live_backtest_align", True) and use_forming:
|
||||
try:
|
||||
self.ws.fill_gap([code], force=True)
|
||||
cur = self.ws.get_current_candle(code, self.candle_tf)
|
||||
except Exception:
|
||||
cur = None
|
||||
if cur and float(cur.get("open", 0) or 0) > 0:
|
||||
ct = str(cur.get("candle_time") or "")[:12]
|
||||
last_ct = ""
|
||||
if candles_raw:
|
||||
last_ct = str(candles_raw[-1].get("candle_time") or "")[:12]
|
||||
if ct and ct != last_ct:
|
||||
cur_d = dict(cur)
|
||||
cur_d["is_confirmed"] = 0
|
||||
candles_raw.append(cur_d)
|
||||
if len(candles_raw) < 5:
|
||||
force_sec = max(30, get_env_int("SCALP_CANDLE_GAP_FORCE_SEC", 120))
|
||||
now_g = time.time()
|
||||
if not hasattr(self, "_candle_gap_force_ts"):
|
||||
self._candle_gap_force_ts = {}
|
||||
last_g = float(self._candle_gap_force_ts.get(code, 0) or 0)
|
||||
do_force = (now_g - last_g) >= float(force_sec)
|
||||
if do_force:
|
||||
self._candle_gap_force_ts[code] = now_g
|
||||
try:
|
||||
self.ws.fill_gap([code], force=do_force)
|
||||
except Exception:
|
||||
pass
|
||||
log_sec = max(15, get_env_int("SCALP_CANDLE_SHORT_LOG_SEC", 60))
|
||||
if not hasattr(self, "_candle_short_log_ts"):
|
||||
self._candle_short_log_ts = {}
|
||||
last_l = float(self._candle_short_log_ts.get(code, 0) or 0)
|
||||
if now_g - last_l >= float(log_sec):
|
||||
self._candle_short_log_ts[code] = now_g
|
||||
self.logger.info(
|
||||
"🔍 [캔들부족] %s(%s) need>=5 have=%d",
|
||||
name, code, len(candles_raw),
|
||||
)
|
||||
return None
|
||||
candles = [self._norm_candle(c) for c in candles_raw]
|
||||
|
||||
@@ -156,7 +194,6 @@ class ScalpingStrategy(BaseStrategy):
|
||||
daily_cnt = 0
|
||||
state = {"last_exit_dt": last_exit_dt, "daily_cnt": daily_cnt}
|
||||
|
||||
params = self._scan_engine_params or {}
|
||||
# SCALP 는 reversal 단일 모드 (momentum 은 MomentumStrategy 로 분리됨)
|
||||
reject, msg, sig = se.check_buy_signal_live(candles, params, state)
|
||||
if reject:
|
||||
@@ -165,19 +202,39 @@ class ScalpingStrategy(BaseStrategy):
|
||||
if not sig:
|
||||
return None
|
||||
|
||||
latest = candles[-1]
|
||||
curr_price = float(latest["close"])
|
||||
if curr_price < self.min_price:
|
||||
# 중분 편입 → 같은 진입봉 시가 매수 보류 (다음 분부터)
|
||||
_defer = self._defer_mid_enroll_entry(
|
||||
code, sig.get("entry_bar_key"), int(self.candle_tf or 1), params,
|
||||
)
|
||||
if _defer:
|
||||
self.logger.info("🔍 [%s] %s(%s)", _defer, name, code)
|
||||
return None
|
||||
|
||||
# 현재가 보정 (WS → 없으면 REST)
|
||||
wsd = self.ws.get_price(code)
|
||||
if wsd:
|
||||
try:
|
||||
curr_price = abs(float(str(wsd.get("stck_prpr", curr_price)).replace(",", ""))) or curr_price
|
||||
except Exception:
|
||||
pass
|
||||
if curr_price <= 0:
|
||||
# 진입 계산가: align 시 T봉 첫 틱(RAM) → 없으면 시가. 폴백만 WS 현재가
|
||||
align_on = bool(params.get("live_backtest_align", True))
|
||||
entry_open = float(sig.get("entry_price", 0) or 0)
|
||||
entry_src = "ohlc_open"
|
||||
if align_on and entry_open > 0:
|
||||
from kis_trader.engine.tail_tick_replay import live_align_entry_price
|
||||
curr_price, entry_src = live_align_entry_price(
|
||||
self.ws,
|
||||
code,
|
||||
entry_open,
|
||||
entry_bar_key=str(sig.get("entry_bar_key") or "")[:12],
|
||||
tf_min=int(self.candle_tf or 1),
|
||||
)
|
||||
else:
|
||||
latest = candles[-1]
|
||||
curr_price = float(latest["close"])
|
||||
wsd = self.ws.get_price(code)
|
||||
if wsd:
|
||||
try:
|
||||
curr_price = abs(
|
||||
float(str(wsd.get("stck_prpr", curr_price)).replace(",", ""))
|
||||
) or curr_price
|
||||
except Exception:
|
||||
pass
|
||||
if curr_price <= 0 or curr_price < self.min_price:
|
||||
return None
|
||||
|
||||
hard_cap = get_env_int("SCALP_MAX_BUY_AMOUNT", 0) \
|
||||
@@ -199,8 +256,8 @@ class ScalpingStrategy(BaseStrategy):
|
||||
target_price = curr_price * (1 + eff_tp)
|
||||
|
||||
self.logger.info(
|
||||
"🎯 [SCALP-REVERSAL 시그널] %s(%s) price=%.0f qty=%d RSI=%.1f",
|
||||
name, code, curr_price, qty, sig.get("rsi", 0),
|
||||
"🎯 [SCALP-REVERSAL 시그널] %s(%s) price=%.0f qty=%d RSI=%.1f entry_src=%s",
|
||||
name, code, curr_price, qty, sig.get("rsi", 0), entry_src,
|
||||
)
|
||||
return {
|
||||
"code": code,
|
||||
|
||||
Reference in New Issue
Block a user