이번에 들어간 내용

한투 호가 = 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:
Your Name
2026-08-19 22:11:31 +09:00
parent d1dc274f0e
commit 0ecac7cb95
229 changed files with 8487 additions and 1647 deletions

View File

@@ -5,6 +5,7 @@ kis_trader/execution/orphan_reconcile.py — 봇 고아 포지션 복구 (장마
「봇이 매수했는데 active_trades 미기록」 고아만 복구한다.
이어서 GHOST_PURGE_ON_RECONCILE 시 「DB有·브로커0」 유령 행을 삭제한다.
- 기동: 전략 쓰레드 전 1회 (ORPHAN_RECONCILE_ON_STARTUP, 기본 ON)
- Pre-EOD: 활성 전략 중 가장 이른 EOD 시각 N분 (기본 7분) 1회
- Post-EOD: 15:36~ 장마감 후 1회 (기존)
- 수동매수(orders BUY 없음) · MANUAL_HOLD_CODES · strategy=HOLDING 은 건드리지 않음.
@@ -20,6 +21,11 @@ from typing import Any, Dict, List, Optional, Set, Tuple
from ..engine.strategy_eod import _STRATEGY_EOD_SPEC, parse_eod_hm, resolve_strategy_eod_params
from ..utils.env import get_env_bool, get_env_float, get_env_from_db, get_env_int
from ..utils.strategy_ids import canonical_strategy_id
from .kis_client import (
broker_row_hldg_qty,
broker_row_sellable_qty,
cancelable_remainder_qty,
)
logger = logging.getLogger("kis_trader.orphan_reconcile")
@@ -167,13 +173,14 @@ def _num(v, default: float = 0.0) -> float:
def _broker_qty(broker: Dict[str, Any], code: str) -> int:
"""보유수량. 없으면 매도가능(구맵). 매도가능 0이어도 보유>0 이면 0이 아님."""
br = (broker or {}).get(code) or (broker or {}).get(str(code).strip())
if not br:
return 0
try:
return int((br or {}).get("qty") or 0)
except (TypeError, ValueError):
return 0
hldg = broker_row_hldg_qty(br)
if hldg > 0:
return hldg
return broker_row_sellable_qty(br)
def _orphan_skip_sell_reasons() -> Optional[List[str]]:
@@ -328,6 +335,18 @@ def purge_ghost_active_trades(order_mgr, broker: Optional[Dict[str, Any]] = None
out["error"] = "실계좌 잔고 조회 실패"
return out
cancelable = None
if get_env_bool("INQUIRE_PSBL_RVSECNCL_BEFORE_GHOST", True):
try:
cancelable = order_mgr.get_cancelable_sells(force=True)
except Exception as e:
logger.warning("유령정리 전 정정취소가능 조회 예외: %s", e)
cancelable = None
if cancelable is None:
out["error"] = "정정취소가능 조회 실패 — 유령삭제 스킵"
logger.warning("🧹 [유령잔고] 정정취소가능조회 실패 → 이번 회차 삭제 안 함")
return out
manual_hold = parse_manual_hold_codes()
try:
rows = db.conn.execute(
@@ -354,6 +373,22 @@ def purge_ghost_active_trades(order_mgr, broker: Optional[Dict[str, Any]] = None
continue
if _broker_qty(broker, code) > 0:
continue
if cancelable_remainder_qty(cancelable or {}, code) > 0:
logger.info(
"⏸ [유령스킵] [%s] %s %s — 정정취소가능 매도 잔량 있음",
strategy, name, code,
)
continue
try:
pending = db.get_pending_sell_order(strategy, code)
except Exception:
pending = None
if pending:
logger.info(
"⏸ [유령스킵] [%s] %s %s — DB 미체결 매도 ODNO=%s",
strategy, name, code, pending.get("ord_no"),
)
continue
try:
# OrderManager 와 동일: trade_history 에 ghost_purge 남긴 뒤 정리
recorded = False