이번에 들어간 내용
한투 호가 = 2번째 앱키 전용 키 없거나 start 실패 시 메인에 H0STASP0 안 붙임. 운영설정 WS_ORDERBOOK_SAVE_KIS 빨간 danger. LS RAM 합집합 후보∪보유∪영구∪grace. sync_targets와 split reconcile 둘 다. 틱 DB 영구 게이트는 그대로. 분봉 쓰레기 → 다음 소스 봉 통째 그 분 틱 0건이거나 전부 봉끝 대비 LIVE_FEED_FALLBACK_MAX_AGE_SEC 초과면 구멍. 메인 WS → 2차 → LS → REST → rollup. CANDLE_GARBAGE_FALLBACK 기본 true. 파일: feed_fallback.py(신규), ws_manager.py, kis_ws.py, candle_series.py, bt_candle_source.py, live_config_schema.py, database.py, 스모크, MD 2개. 같은 ws_manager/database/kis_ws/live_config에는 직전 커밋 이후 쌓여 있던 시세 폴백·ENV 키 정리도 같이 들어갔습니다. 파일 단위로 나눌 수 없어서입니다.
This commit is contained in:
@@ -8,7 +8,7 @@ from collections import defaultdict
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any, Dict, Iterator, List, Optional, Set, Tuple
|
||||
|
||||
from ..utils.env import get_env_bool, get_env_from_db, get_env_int
|
||||
from ..utils.env import get_env_from_db, get_env_int
|
||||
from ..utils.logger import get_logger
|
||||
|
||||
logger = get_logger("kis_trader.breakout_tick_loader")
|
||||
@@ -121,7 +121,7 @@ def _fetch_ws_ticks_day_rows(
|
||||
params.append(tick_source)
|
||||
|
||||
sql = f"""
|
||||
SELECT code, tick_time, price, volume, source
|
||||
SELECT code, tick_time, price, volume, source, recv_ts, tick_time_raw
|
||||
FROM {table}
|
||||
WHERE market = %s
|
||||
AND tick_time >= %s
|
||||
@@ -155,13 +155,34 @@ def _fetch_ws_ticks_day_rows(
|
||||
return []
|
||||
|
||||
|
||||
def _tick_row_lag_seconds(row: Dict[str, Any]) -> Optional[int]:
|
||||
"""ws_ticks 1행 recv_ts vs 체결시각(raw 우선) 지연(초). 계산 불가 시 None."""
|
||||
from kis_trader.engine.feed_fallback import packet_lag_seconds
|
||||
|
||||
recv_ts = str(row.get("recv_ts") or "").strip()
|
||||
raw = str(row.get("tick_time_raw") or "").strip()
|
||||
tt = str(row.get("tick_time") or "").strip()
|
||||
try:
|
||||
recv_dt = datetime.strptime(recv_ts[:19], "%Y-%m-%d %H:%M:%S")
|
||||
except Exception:
|
||||
return None
|
||||
lag = packet_lag_seconds(raw or tt, now_dt=recv_dt)
|
||||
if lag is None:
|
||||
return None
|
||||
return int(lag)
|
||||
|
||||
|
||||
def _ingest_tick_rows(
|
||||
rows: List[Dict[str, Any]],
|
||||
out: Dict[str, Dict[str, List[Dict[str, Any]]]],
|
||||
) -> int:
|
||||
"""SELECT 행 → 종목·분봉 버킷. 반환=적재 건수."""
|
||||
"""SELECT 행 → 종목·분봉 버킷. 반환=적재 건수.
|
||||
|
||||
공책은 버리지 않음. lag 는 읽기 폴백(2초)용 메타만 붙인다.
|
||||
"""
|
||||
n = 0
|
||||
for r in rows:
|
||||
kw_lag = _tick_row_lag_seconds(r)
|
||||
code = str(r["code"]).strip()
|
||||
tt = str(r["tick_time"])[:14]
|
||||
if len(tt) < 12:
|
||||
@@ -173,6 +194,7 @@ def _ingest_tick_rows(
|
||||
"price": float(r["price"] or 0),
|
||||
"volume": int(r.get("volume") or 0),
|
||||
"source": r.get("source") or "",
|
||||
"_lag_sec": kw_lag,
|
||||
}
|
||||
bucket = out[code].setdefault(minute_key, [])
|
||||
bucket.append(tick)
|
||||
@@ -199,8 +221,7 @@ def load_breakout_ticks_by_code(
|
||||
"""
|
||||
mkt = (market or get_env_from_db("WS_TICK_DEFAULT_MARKET", "KR") or "KR").strip().upper()
|
||||
tt_start, tt_end = _candle_keys_to_tick_range(start_key, end_key)
|
||||
# 해외는 키움 체결 없음(kis_us) — 키움 우선 필터 끄기
|
||||
prefer_kw = bool(get_env_bool("WS_TICK_PREFER_KIWOOM", True)) and mkt != "US"
|
||||
# 분봉에 메인 1건이면 보조 전부 버리기는 하지 않음. 같은 초만 메인 우선.
|
||||
table = _ws_ticks_table(mkt)
|
||||
|
||||
try:
|
||||
@@ -258,24 +279,25 @@ def load_breakout_ticks_by_code(
|
||||
)
|
||||
|
||||
# 일별 SELECT 에 ORDER BY 없음 → 분봉 버킷 시간순 정렬 (틱 재생 정합)
|
||||
for _code, minutes in out.items():
|
||||
for _mk, ticks in minutes.items():
|
||||
ticks.sort(key=lambda t: str(t.get("tick_time") or ""))
|
||||
from kis_trader.engine.feed_fallback import live_tick_primary, merge_ticks_time_axis_fallback
|
||||
|
||||
# 실매(키움 구독) 정합: 분봉 단위 키움 우선 (모멘텀/꼬리 로더가 이 함수 재사용)
|
||||
if prefer_kw and out:
|
||||
kept = 0
|
||||
for code, minutes in out.items():
|
||||
for mk, ticks in list(minutes.items()):
|
||||
filtered = _prefer_kiwoom_minute_ticks(ticks)
|
||||
minutes[mk] = filtered
|
||||
main_src = live_tick_primary() if mkt != "US" else "kis"
|
||||
kept = 0
|
||||
for _code, minutes in out.items():
|
||||
for _mk, ticks in list(minutes.items()):
|
||||
ticks.sort(key=lambda t: str(t.get("tick_time") or ""))
|
||||
if mkt != "US":
|
||||
filtered = merge_ticks_time_axis_fallback(ticks, main_src=main_src)
|
||||
minutes[_mk] = filtered
|
||||
kept += len(filtered)
|
||||
if kept != total:
|
||||
logger.info(
|
||||
"📌 WS_TICK_PREFER_KIWOOM: raw=%d → kept=%d (분봉내 키움 우선)",
|
||||
total, kept,
|
||||
)
|
||||
total = kept
|
||||
else:
|
||||
kept += len(ticks)
|
||||
if kept != total:
|
||||
logger.info(
|
||||
"📌 틱 시간축 폴백: raw=%d → kept=%d (같은 초 메인 우선, 메인 lag>2s면 보조)",
|
||||
total, kept,
|
||||
)
|
||||
total = kept
|
||||
|
||||
return dict(out), total
|
||||
|
||||
|
||||
Reference in New Issue
Block a user