ls증권 히스토리 구독 넣음
This commit is contained in:
@@ -5,6 +5,8 @@ kis_trader/ws/tick_recorder.py — 실시간 체결 틱 RAM 링버퍼 + ws_ticks
|
||||
[트랙 2] 제한 큐 → 백그라운드 스레드 배치 INSERT (journalctl 파싱 아님)
|
||||
|
||||
해외 확장: market / exchange / currency / session 컬럼 (국내 기본 KR/KRX/KRW).
|
||||
- 국내 → ``ws_ticks`` (메인 TradeDB 커넥션)
|
||||
- 해외 US → ``ws_ticks_us`` (전용 raw 커넥션 — 앱 TradeDB 락과 분리)
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -27,7 +29,8 @@ class TickRecorder:
|
||||
|
||||
- ``set_record_codes()`` 로 후보(또는 구독) 종목만 필터.
|
||||
- RAM: 종목당 최대 N틱 (링버퍼).
|
||||
- DB: BATCH_SIZE / FLUSH_SEC 마다 ws_ticks append INSERT.
|
||||
- DB: BATCH_SIZE / FLUSH_SEC 마다 append INSERT
|
||||
(KR→ws_ticks, US→ws_ticks_us).
|
||||
"""
|
||||
|
||||
def __init__(self, db=None) -> None:
|
||||
@@ -49,12 +52,14 @@ class TickRecorder:
|
||||
self._writer_thread: Optional[threading.Thread] = None
|
||||
self._last_cleanup_ts = 0.0
|
||||
self._drops = 0
|
||||
# 해외 틱 전용 DB 커넥션 (메인 TradeDB._lock 과 분리)
|
||||
self._us_write_conn: Any = None
|
||||
|
||||
if self._enabled and self.db is not None:
|
||||
self._ensure_table()
|
||||
self._start_writer()
|
||||
logger.info(
|
||||
"✅ TickRecorder 활성 (buf=%d/종목, batch=%d, flush=%.1fs, keep=%dd)",
|
||||
"✅ TickRecorder 활성 (buf=%d/종목, batch=%d, flush=%.1fs, keep=%dd, us→ws_ticks_us)",
|
||||
self._buffer_max, self._batch_size, self._flush_sec, self._keep_days,
|
||||
)
|
||||
elif self._enabled and self.db is None:
|
||||
@@ -94,15 +99,21 @@ class TickRecorder:
|
||||
code = (code or "").strip()
|
||||
if not code:
|
||||
return
|
||||
with self._lock:
|
||||
filt = self._record_codes
|
||||
if filt is not None and code not in filt:
|
||||
return
|
||||
mkt = (market or self._default_market).upper()[:8]
|
||||
# 국내: WS_TICK_RECORD_SCOPE(candidates 등) 필터 적용.
|
||||
# 해외(US): 영구구독이 국내 후보 집합에 없어 필터에 걸리면
|
||||
# 봉(ws_candles)은 쌓이는데 틱(ws_ticks_us)만 끊기는 사고가 남.
|
||||
# → US 는 필터 우회 (저장 ON/OFF 는 해외 WS 의 WS_TICK_SAVE_US_ENABLED).
|
||||
if mkt != "US":
|
||||
with self._lock:
|
||||
filt = self._record_codes
|
||||
if filt is not None and code not in filt:
|
||||
return
|
||||
|
||||
tick_time_full = self._normalize_tick_time(tick_time)
|
||||
recv_ts = dt.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
item = {
|
||||
"market": (market or self._default_market).upper()[:8],
|
||||
"market": mkt,
|
||||
"exchange": (exchange or self._default_exchange)[:16] if (exchange or self._default_exchange) else None,
|
||||
"code": code[:32],
|
||||
"tick_time": tick_time_full,
|
||||
@@ -181,8 +192,18 @@ class TickRecorder:
|
||||
try:
|
||||
if hasattr(self.db, "ensure_ws_ticks_table"):
|
||||
self.db.ensure_ws_ticks_table()
|
||||
if hasattr(self.db, "ensure_ws_ticks_us_table"):
|
||||
self.db.ensure_ws_ticks_us_table()
|
||||
except Exception as e:
|
||||
logger.warning("ws_ticks 테이블 확인 실패: %s", e)
|
||||
logger.warning("ws_ticks/_us 테이블 확인 실패: %s", e)
|
||||
# 해외 INSERT 전용 커넥션 (메인 전략 SELECT 와 파이썬 락 비공유)
|
||||
try:
|
||||
if hasattr(self.db, "new_raw_connection"):
|
||||
self._us_write_conn = self.db.new_raw_connection()
|
||||
logger.info("✅ TickRecorder US writer 전용 커넥션 (ws_ticks_us)")
|
||||
except Exception as e:
|
||||
self._us_write_conn = None
|
||||
logger.warning("US 전용 커넥션 실패 — 메인 DB로 폴백: %s", e)
|
||||
|
||||
def _start_writer(self) -> None:
|
||||
self._writer_thread = threading.Thread(
|
||||
@@ -224,12 +245,26 @@ class TickRecorder:
|
||||
def _flush_batch(self, batch: List[dict]) -> None:
|
||||
if not batch or not self.db:
|
||||
return
|
||||
us_batch = [x for x in batch if str(x.get("market") or "").upper() == "US"]
|
||||
kr_batch = [x for x in batch if str(x.get("market") or "").upper() != "US"]
|
||||
try:
|
||||
if hasattr(self.db, "insert_ws_ticks_batch"):
|
||||
self.db.insert_ws_ticks_batch(batch)
|
||||
logger.debug("💾 [틱배치] %d건 → ws_ticks", len(batch))
|
||||
if kr_batch and hasattr(self.db, "insert_ws_ticks_batch"):
|
||||
self.db.insert_ws_ticks_batch(kr_batch)
|
||||
logger.debug("💾 [틱배치] %d건 → ws_ticks", len(kr_batch))
|
||||
except Exception as e:
|
||||
logger.warning("ws_ticks 배치 INSERT 실패 (%d건): %s", len(batch), e)
|
||||
logger.warning("ws_ticks 배치 INSERT 실패 (%d건): %s", len(kr_batch), e)
|
||||
try:
|
||||
if us_batch and hasattr(self.db, "insert_ws_ticks_us_batch"):
|
||||
self.db.insert_ws_ticks_us_batch(
|
||||
us_batch, conn=self._us_write_conn,
|
||||
)
|
||||
logger.debug("💾 [틱배치] %d건 → ws_ticks_us", len(us_batch))
|
||||
elif us_batch and hasattr(self.db, "insert_ws_ticks_batch"):
|
||||
# 구버전 DB 헬퍼 폴백 (분리 전)
|
||||
self.db.insert_ws_ticks_batch(us_batch)
|
||||
logger.debug("💾 [틱배치] %d건 → ws_ticks(폴백 US)", len(us_batch))
|
||||
except Exception as e:
|
||||
logger.warning("ws_ticks_us 배치 INSERT 실패 (%d건): %s", len(us_batch), e)
|
||||
|
||||
def _maybe_cleanup_old(self) -> None:
|
||||
if self._keep_days <= 0:
|
||||
@@ -239,4 +274,8 @@ class TickRecorder:
|
||||
self.db.cleanup_old_ws_ticks(keep_days=self._keep_days)
|
||||
except Exception as e:
|
||||
logger.debug("ws_ticks 정리 실패: %s", e)
|
||||
|
||||
try:
|
||||
if hasattr(self.db, "cleanup_old_ws_ticks_us"):
|
||||
self.db.cleanup_old_ws_ticks_us(keep_days=self._keep_days)
|
||||
except Exception as e:
|
||||
logger.debug("ws_ticks_us 정리 실패: %s", e)
|
||||
|
||||
Reference in New Issue
Block a user