fix(시세): LS spill-only·토큰 파일캐시·호가 snap_time 3초컷
- _feed_fallback 미러 OFF, LS cap/grace/hold RAM을 KIS·키움 spill과 정합 - LS 접근토큰 .ls_token_cache_*.json (재시작 재사용, revoke 루프 없음) - 호가 RAM을 틱과 동일 LIVE_FEED_FALLBACK(snap_time)로 컷, 필터 max_age=0은 유지 - 익절 지정가 로그에 실제 호가 벤더(kis/kiwoom/ls 1·2·3차) 표기 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -51,6 +51,21 @@ def is_feed_read_stale(
|
||||
return False
|
||||
|
||||
|
||||
def is_orderbook_snap_time_stale(
|
||||
snap_time: Any,
|
||||
*,
|
||||
max_age_sec: Optional[float] = None,
|
||||
) -> bool:
|
||||
"""호가 거래소 시각(snap_time) vs 지금 — 틱 ``packet_lag`` + ``is_feed_read_stale`` 와 동일.
|
||||
|
||||
snap_time 없거나 파싱 실패 → False(버리지 않음). LIVE_FEED_FALLBACK 0 이하면 OFF.
|
||||
"""
|
||||
return is_feed_read_stale(
|
||||
packet_lag_seconds(str(snap_time or "").strip()),
|
||||
max_age_sec=max_age_sec,
|
||||
)
|
||||
|
||||
|
||||
def live_feed_fallback_max_age_sec() -> float:
|
||||
"""읽기 유효 나이(초). 기본 3. 0 이하면 나이로 벤더를 버리지 않음(레거시)."""
|
||||
try:
|
||||
@@ -361,6 +376,59 @@ def merge_ticks_time_axis_fallback(
|
||||
return out
|
||||
|
||||
|
||||
def merge_ls_ticks_third_fallback(
|
||||
ticks_by_code: Dict[str, Dict[str, List[Dict[str, Any]]]],
|
||||
ls_ticks_by_code: Dict[str, Dict[str, List[Dict[str, Any]]]],
|
||||
*,
|
||||
max_lag_sec: Optional[float] = None,
|
||||
) -> int:
|
||||
"""실매 틱 3차: 같은 초에 kis/kiwoom 이 이미 있으면 LS 안 넣음.
|
||||
|
||||
메인·2차가 비었거나(또는 나이컷으로 비움) 그 초만 ``ls_ws_ticks`` 로 채움.
|
||||
``LIVE_FEED_FALLBACK_MAX_AGE_SEC``(기본 3) — lag 모르면 버리지 않음(실매와 동일).
|
||||
반환=추가한 틱 수.
|
||||
"""
|
||||
if not ls_ticks_by_code:
|
||||
return 0
|
||||
age = live_feed_fallback_max_age_sec() if max_lag_sec is None else float(max_lag_sec)
|
||||
added = 0
|
||||
for code, ls_minutes in ls_ticks_by_code.items():
|
||||
code_s = str(code or "").strip()
|
||||
if not code_s or not isinstance(ls_minutes, dict):
|
||||
continue
|
||||
main_minutes = ticks_by_code.setdefault(code_s, {})
|
||||
occupied: set = set()
|
||||
for _mk, ticks in main_minutes.items():
|
||||
for t in ticks or []:
|
||||
if not isinstance(t, dict):
|
||||
continue
|
||||
sk = _tick_second_key(t)
|
||||
if sk:
|
||||
occupied.add(sk)
|
||||
for mk, ls_ticks in ls_minutes.items():
|
||||
mk_s = str(mk or "").strip()
|
||||
if not mk_s:
|
||||
continue
|
||||
bucket = main_minutes.setdefault(mk_s, [])
|
||||
for t in ls_ticks or []:
|
||||
if not isinstance(t, dict):
|
||||
continue
|
||||
sk = _tick_second_key(t)
|
||||
if not sk or sk in occupied:
|
||||
continue
|
||||
lag = t.get("_lag_sec")
|
||||
if is_feed_read_stale(lag, age):
|
||||
continue
|
||||
row = dict(t)
|
||||
row["source"] = "ls"
|
||||
bucket.append(row)
|
||||
occupied.add(sk)
|
||||
added += 1
|
||||
if bucket:
|
||||
bucket.sort(key=lambda x: str(x.get("tick_time") or ""))
|
||||
return added
|
||||
|
||||
|
||||
def orderbook_row_lag_seconds(row: Dict[str, Any]) -> Optional[int]:
|
||||
"""recv_ts vs snap_time 초 차이. 파싱 실패면 None(유효로 봄)."""
|
||||
recv_ts = str(row.get("recv_ts") or "").strip()
|
||||
|
||||
@@ -123,10 +123,13 @@ def orderbook_params_to_env_patch(strategy: str, params: Dict[str, Any]) -> Dict
|
||||
out[_key(pfx, "ENTRY_BID_DEPTH_MULT")] = str(float(params["bid_depth_mult"]))
|
||||
if "bid_levels" in params and params.get("bid_levels") not in (None, ""):
|
||||
out[_key(pfx, "ENTRY_BID_LEVELS")] = str(int(float(params["bid_levels"])))
|
||||
# 호가축이 섞인 apply 면 필터 ON (탐색 결과를 실매에 쓰려면 필요)
|
||||
if out and params.get("ob_filter_enabled") not in (None, ""):
|
||||
# 호가축 apply → FILTER_ENABLED (_orderbook_filter_enabled / ob_filter_enabled)
|
||||
fe = params.get("ob_filter_enabled")
|
||||
if fe is None:
|
||||
fe = params.get("_orderbook_filter_enabled")
|
||||
if fe is not None and str(fe).strip() != "":
|
||||
out[_key(pfx, "FILTER_ENABLED")] = (
|
||||
"true" if str(params.get("ob_filter_enabled")).lower() in ("1", "true", "yes", "on")
|
||||
"true" if str(fe).lower() in ("1", "true", "yes", "on")
|
||||
else "false"
|
||||
)
|
||||
return out
|
||||
|
||||
Reference in New Issue
Block a user