fix: 한투 호가 2키 전용, LS RAM 합집합, 분봉 쓰레기면 다음 소스 통째

메인 시세 41에 H0STASP0를 붙이는 폴백을 없앤다. MINIMAL과 무관하게
후보∪보유∪영구를 LS RAM에 붙인다. 그 분 틱이 없거나 봉끝 대비 늦으면
그 WS 봉을 버리고 2차·LS·REST 봉을 통째로 쓴다.

같은 파일에 직전 커밋 이후 쌓인 시세 폴백/ENV 키 정리도 포함된다.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Your Name
2026-08-19 22:09:03 +09:00
parent 6d2a706a48
commit d1dc274f0e
10 changed files with 2146 additions and 459 deletions

View File

@@ -190,30 +190,68 @@ def build_live_config_groups() -> List[GroupDef]:
"fields": [
_f("LIVE_TICK_PROVIDER", "📊 실매매 틱(시세) 소스 (재시작 필요)", "text", default="kiwoom",
hint=(
"kiwoom (권장) = 키움증권 실시간 체결가 사용 | "
"kis = 한국투자증권 실시간 체결가 사용"
"get_price 가 어느 RAM 을 먼저 볼지(1차). 폴백 나이 넘으면 실패로 보고 "
"2차(상대 체결소켓)→3차(LS)→매도는 4차 키움 REST(ka10007). "
"한투 60초 캐시는 안 씀. MINIMAL 이면 한투 RAM 은 보유만"
)),
_f("LIVE_OB_PROVIDER", "📊 실매매 호가(OB) 소스 (재시작 필요)", "text", default="kiwoom",
hint=(
"kiwoom (권장) = 키움증권 10단계 호가 사용 | "
"kis = 한국투자증권 10단계 호가 사용 (kis 선택 시 아래 'KIS 실시간 호가 수집' 스위치도 ON이어야 합니다)"
"호가 1차. 폴백 나이 넘으면 그 벤더는 없는 것과 같고 2차→3차(LS UH1). "
"만료로 None 만 만들어서 필터 ON 통과 구멍을 만들지 않음. "
"kis 선택 시 아래 'KIS 실시간 호가 수집' 도 ON"
)),
_f("LIVE_FEED_FALLBACK_MAX_AGE_SEC", "⏱ 시세·호가 읽기 폴백 나이(초)", "float", default=3.0,
hint=(
"비교 = 그 틱의 체결시각(키움 FID20 / 한투 chetime) vs 이 서버 지금. "
"1차·2차·3차 모두 같음. 보조가 늦으면 3차, 매도는 REST. "
"키움 시각에서 한투 시각을 빼서 늦다고 하지 않음. "
"넘기면 그 벤더 실패. 적재(공책)와 분리. 기본 3. "
"분봉 쓰레기 판정 나이도 이 값(봉 끝시각 대비)."
)),
_f("CANDLE_GARBAGE_FALLBACK", "🕯 분봉 쓰레기→2차·3차 봉 통째", "bool", default=True,
hint=(
"ON(기본)=그 분 메인 틱이 0건이거나 전부 봉끝 대비 읽기나이 초과면 "
"그 봉을 구멍으로 보고 2차 WS→LS 봉 통째. 한 봉 안 혼합 롤업 없음. "
"실매 링에 그 분이 안 들어오면 판정 불가→기존 봉 유지"
)),
_f("WS_TICK_SAVE_KIWOOM", "📈 키움 체결 틱 수집", "bool", default=True,
hint="ON = 키움 체결 틱을 ws_ticks 테이블에 저장 (기본값: ON)"),
_f("KIWOOM_TICK_TIME_MAX_LAG_SEC", "⏱ 키움 체결시간(FID20) 동결 한도(초)", "int", default=120,
hint=(
"같은 FID20 가 이 초 이상 안 움직일 때만 아침 시계멈춤으로 보고 "
"RAM 유지·tick_time 을 수신시각으로 보정. "
"밀린 줄의 같은 초 연속 체결(수십 ms)은 동결이 아님"
)),
_f("KIWOOM_TICK_LIVE_MAX_LAG_SEC", "⏱ 키움 DB 적재 최대 지연(초)", "int", default=0,
hint=(
"0(기본)=ws_ticks 전부 적재(밀린 줄도 공책에 남김). "
"양수=FID20 vs 지금 이 초 초과만 틱 미저장. "
"매매 RAM 은 LIVE_FEED_FALLBACK(기본 3초) — 적재와 분리"
)),
_f("KIWOOM_TICK_STALE_LOG_GAP_SEC", "⏱ 키움 stale skip 로그 간격(초)", "float", default=30.0,
hint="종목당 stale skip 경고 최소 간격"),
_f("KIWOOM_WS_REG_REFRESH", "🧪 키움 REG refresh (테스트)", "int", default=1,
hint=(
"1(기본)=기존 구독 유지. 0=LOGIN 직후 일괄 REG 첫 청크만 그룹 구독 해지 후 재등록. "
"장중 종목 추가 REG는 항상 1. 장중 상시 0 비추(catch-up 재점화). "
"실매 반영은 kis_trader_main 재시작"
)),
_f("WS_ORDERBOOK_SAVE_KIWOOM", "📋 키움 실시간 호가 수집", "bool", default=True,
hint="ON = 키움 호가를 ws_orderbook 테이블에 저장 (기본값: ON)"),
_f("WS_TICK_SAVE_KIS", "📈 KIS 체결 틱 수집", "bool", default=True,
hint="ON = KIS 체결 틱을 ws_ticks 테이블에 저장 (기본값: ON)"),
_f("WS_ORDERBOOK_SAVE_KIS", "📋 KIS 실시간 호가 수집", "bool", default=False,
danger=True,
hint=(
"OFF(기본) = KIS 호가는 수집 안 함 (한도 40개 보호) | "
"ON = KIS 호가도 kis_ws_orderbook 테이블에 저장. "
"단, KIS는 시세+호가 합산 40개 한도라서 ON 시 최대 20종목만 추적 가능"
"한투 호가는 2번째 앱키(KIS_APP_KEY_OB_REAL) 전용 세션만. "
"메인 시세 41과 합산 금지. 2키 없으면 ON 해도 호가 구독·적재 안 함. "
"2키 세션 한도는 호가 전용 41."
)),
_f("WS_SUBSCRIBE_KIS_MINIMAL", "📉 KIS 최소 구독 모드", "bool", default=False,
hint=(
"OFF(기본) = KIS가 키움과 동일한 후보 40종목을 전부 수신하여 DB에 저장 | "
"ON = KIS가 현재 내가 사고 있는 종목만 수신 (KIS ID 한도 아낄 때)"
"한투 소켓에 누구를 넣을지. LIVE_TICK_PROVIDER 와 무관. "
"OFF(기본)=한투도 후보·보유를 키움과 같이 구독(세션 41). "
"ON=한투는 보유만, 후보는 키움. 영구구독 KR은 LS"
)),
_f("WS_SUBSCRIBE_SPILL", "📡 구독초과/실패 즉시 벤더 spill", "bool", default=True,
hint=(
@@ -227,10 +265,39 @@ def build_live_config_groups() -> List[GroupDef]:
hint="비우면 LIVE_OB_PROVIDER→나머지→ls(3차 RAM). 예: kis,kiwoom,ls"),
_f("LS_WS_ENABLED", "📡 LS WS 기동 (재시작 필요)", "bool", default=False,
hint=(
"ON = LS증권 실시간 WS 기동 — 구독 spill 3차·영구구독 RAM 백업용. "
"ON = LS증권 실시간 WS 기동 — 구독 spill 3차·영구구독·읽기 폴백 RAM. "
"OFF(기본)=VALIDATION/조건이력/ls_condition 전략이 있을 때만 기동. "
"키: env_config LS_APP_KEY_REAL / LS_APP_SECRET_REAL 필요."
)),
_f("LS_FEED_FALLBACK_SUBSCRIBE", "📡 LS 3차 폴백 구독 (메인 유니버스)", "bool", default=True,
hint=(
"ON(기본)=영구 외에 메인 틱/호가 구독 집합(후보보유grace)을 LS에 시세+UH1 붙임. "
"3차가 실제로 있게 함. 후보 갭 REST 와 별개"
)),
_f("LS_WS_TICK_SAVE", "📈 LS 체결 틱 DB 적재", "bool", default=False,
hint=(
"OFF(기본)=후보 틱은 ls_ws_ticks 에 안 쌓음(옵투나 3차 틱 없음). RAM 은 구독 ON. "
"영구 ETF 틱은 persist 게이트(영구만) + 이 스위치. "
"OFF 여도 아래 LS 호가 DB 적재는 별개로 동작"
)),
_f("LS_WS_ALSO_HOGA", "📋 LS 호가(UH1) 구독 (재시작 필요)", "bool", default=True,
hint=(
"ON(기본)=LS 종목 구독 시 체결과 같이 UH1 호가도 받음. "
"OFF면 호가 RAM·ls_ws_orderbook 이 비어 3차 호가가 없음. "
"적재 스위치와 별개 — 둘 다 ON"
)),
_f("LS_WS_ORDERBOOK_SAVE", "📋 LS 호가 DB 적재", "bool", default=True,
hint=(
"ON(기본)=LS 구독 종목 호가를 ls_ws_orderbook 에 쌓음(영구만이 아님). "
"옵투나 호가 3차. tick 모드=체결 1건당 1장. "
"LS_WS_TICK_SAVE OFF 여도 이 스위치만 ON 이면 호가는 쌓임. "
"위 UH1 구독이 OFF 면 쌓을 사진이 없음"
)),
_f("LS_GAP_FILL_CANDIDATES", "⛔ 후보 LS 갭 REST (t8412)", "bool", default=False,
hint=(
"OFF(기본)=폴백 구독만, 후보 전 종목 t8412 금지. "
"영구 갭은 LS_GAP_FILL_ENABLED"
)),
_f("PERMANENT_SUBSCRIBE_ENABLED", "📡 영구구독 마스터 (재시작 필요)", "bool", default=True,
hint=(
"ON(기본)=테이블 enabled 종목을 KR=LS WS / US=해외 WS 로 구독. "
@@ -246,7 +313,7 @@ def build_live_config_groups() -> List[GroupDef]:
_f("PERM_LS_FILL_SLEEP_MIN", "확정봉 가져오기 종목간 sleep 최소(초)", "float", default=1.0),
_f("PERM_LS_FILL_SLEEP_MAX", "확정봉 가져오기 종목간 sleep 최대(초)", "float", default=3.0),
_f("KIS_APP_KEY_OB_REAL", "🔑 KIS 실전 호가 전용 앱키 (선택)", "text",
hint="호가 전용 2번째 실전 앱키(시세 키와 역할 분리). 미입력 시 메인 세션+SAVE 플래그 현행. spill 틱용 아님."),
hint="호가 전용 2번째 실전 앱키. 미입력 시 메인에 호가 TR 안 붙임(합산 금지). spill 틱용 아님."),
_f("KIS_APP_SECRET_OB_REAL", "🔑 KIS 실전 호가 전용 시크릿 (선택)", "text",
hint="호가 전용 2번째 실전 시크릿키."),
_f("KIS_APP_KEY_OB_MOCK", "🔑 KIS 모의 호가 전용 앱키 (선택)", "text",
@@ -303,10 +370,39 @@ def build_live_config_groups() -> List[GroupDef]:
"float",
default=0.0,
hint=(
"0(기본)=마지막 RAM 호가로 검사(만료로 건너뛰지 않음) | "
"양수=그 초보다 오래면 스냅 없음으로 취급. REST 없음"
"0(기본)=마지막 호가로 검사하되, 벤더가 폴백 나이 넘으면 그 사진만 버리고 2차·3차를 봄. "
"만료=None 으로 필터 ON 통과 구멍을 만들지 않음. REST 없음"
),
),
_f(
"WS_PRICE_MAX_AGE_SEC",
"⏱ WS 현재가 조회 나이(초)",
"float",
default=0.0,
hint=(
"0(기본)=읽기 폴백 나이(LIVE_FEED_FALLBACK, 기본 3초)로 메인→2차→3차. "
"양수=그 초와 폴백 나이 중 짧은 쪽. None 인자만 마지막 RAM. "
"매도 4차 REST 는 1·2·3 실패 시에만(키움 ka10007, 쿨다운)"
),
),
_f(
"SELL_WS_STALE_REST_SEC",
"⏱ 매도 죽은선 REST(초)",
"float",
default=1.0,
hint=(
"1·2·3차 WS 가 모두 없거나 2초 넘을 때 키움 ka10007 1회. "
"한투 inquire_price(60초 캐시) 안 씀. 0=REST 안 함. "
"종목당 쿨다운은 아래 간격. 매수 루프 전 종목 REST 금지"
),
),
_f(
"SELL_WS_STALE_REST_COOLDOWN_SEC",
"⏱ 매도 REST 확인 간격(초)",
"float",
default=30.0,
hint="죽은선 REST를 종목당 이 초마다 1회. 유량 보호. 코드 기본 30",
),
_f(
"WS_ORDERBOOK_FILTER_REJECT_IF_EMPTY",
"⛔ 호가 사진 없으면 매수 안 함",
@@ -888,8 +984,8 @@ def build_live_config_groups() -> List[GroupDef]:
"bool",
default=True,
hint=(
"ON(권장) = 취소 요청이 실패해도 실제 잔고를 보니 0주면 지정가에 팔린 걸로 확정 | "
"→ 유령 포지션(DB엔 있는데 실제론 없는 것) 방지"
"ON(권장) = 취소 실패해도 보유=0·매도가능=0·시장 미체결 매도=0 일 때만 지정가 체결로 확정 | "
"매도가능만 0인 경우는 아직 안 팔린 것 → 확정 안 함"
),
),
_f(
@@ -902,6 +998,32 @@ def build_live_config_groups() -> List[GroupDef]:
"'팔리는 중인데 유령이라고 지워버리는' 사고 방지"
),
),
_f(
"INQUIRE_PSBL_RVSECNCL_BEFORE_GHOST",
"📋 유령정리 전 정정취소가능 조회",
"bool",
default=True,
hint=(
"ON(권장) = 실전은 한투 정정취소가능주문조회, 모의는 당일체결 미체결로 "
"시장에 남은 매도를 본 뒤에만 유령 정리 | "
"매도주문 나가면 매도가능=0 이 정상. 그걸 0주=없다 로 지우지 않음 | "
"OFF = 예전처럼 잔고 맵만 봄 (비권장)"
),
),
_f(
"PSBL_RVSECNCL_CACHE_TTL_SEC",
"📋 정정취소가능 조회 캐시(초)",
"int",
default=5,
hint="같은 초 안에 종목마다 REST 연타 방지. 잔고 캐시 TTL 과 비슷하게.",
),
_f(
"PSBL_RVSECNCL_MAX_PAGES",
"📋 정정취소가능 최대 페이지",
"int",
default=5,
hint="한 페이지 최대 50건. 5페이지면 250건. REST 유량 보호 상한.",
),
_f(
"BACKTEST_SLIP_PCT",
"📊 백테스트 슬리피지 (백테 전용)",
@@ -1098,9 +1220,13 @@ def build_live_config_groups() -> List[GroupDef]:
"유니버스/봉 분슬롯과 무관(임계값만)"),
_f("TICK_SELL_ENABLED", "틱매도(공통)", "bool", default=False,
hint="ON=WS 현재가 갱신마다 매도검사(백테 틱청산에 근접) · OFF=루프(~0.1초)만 · "
"전략별 *_TICK_SELL_ENABLED 가 비어있지 않으면 그쪽 우선 · 기본 OFF"),
"전략별 *_TICK_SELL_ENABLED 가 비어있지 않으면 그쪽 우선 · 기본 OFF. "
"주문은 TICK_SELL_ASYNC_PLACE(기본 ON) 이면 워커"),
_f("TICK_SELL_MIN_INTERVAL_MS", "틱매도 최소간격(ms)", "int", default=50,
hint="같은 종목 연속 틱 폭주 시 매도검사 하한 · 백테 poll≈50~100ms 정합"),
_f("TICK_SELL_ASYNC_PLACE", "틱매도 주문 비동기", "bool", default=True,
hint="ON(기본)=틱 수신 스레드에서는 신호만 큐. 잔고·주문 REST는 워커. "
"OFF=예전처럼 틱 콜백에서 place()까지 동기(시세 일시정지)"),
],
})
@@ -1136,7 +1262,8 @@ def build_live_config_groups() -> List[GroupDef]:
"title": "전략별 후보 상한 (WS 100 구독 분배)",
"hint": (
"env_config · 키움 WS 실시간 구독은 계좌당 100종목이 한도. "
"전략별 후보 상한 합 + 영구구독 + 보유 여유 ≤ 100 이 되도록 분배. "
"전략별 후보 상한 합 + 보유 + grace ≤ 100. "
"영구구독 KR은 LS(키움 100·한투 41에 안 넣음). "
"UPDOW 는 추가로 watchlist(UPDOWN_WATCH_MAX) 안에서만 유지됨."
),
"fields": cand_fields,
@@ -1363,6 +1490,8 @@ def build_live_config_groups() -> List[GroupDef]:
hint="미등록 봇고아 일괄 시장가매도 시 REST 429 방지 · 종목마다 sleep"),
_f("ORPHAN_RECONCILE_ENABLED", "장마감 고아복구", "bool", default=True,
hint="매일 15:36~16:00 1회 · 실계좌 잔고↔active_trades 대조 · orders BUY 있는 미기록분만 복구 · 수동·MANUAL_HOLD 제외"),
_f("ORPHAN_RECONCILE_ON_STARTUP", "기동 시 잔고=DB 맞춤", "bool", default=True,
hint="봇 시작 직후 1회 · 매도 없음 · 고아복구+유령삭제+수량/평단 sync · 전략 쓰레드보다 먼저 · OFF 시 웹 「잔고=DB 맞추기」만"),
_f("ORPHAN_RECONCILE_PRE_EOD_ENABLED", "Pre-EOD 고아복구", "bool", default=True,
hint="활성 전략 중 가장 이른 EOD N분 1회 · EOD 청산 전 active_trades 반영"),
_f("ORPHAN_RECONCILE_PRE_EOD_LEAD_MIN", "Pre-EOD 선행(분)", "int", default=7,
@@ -1374,7 +1503,7 @@ def build_live_config_groups() -> List[GroupDef]:
_f("ORPHAN_PURGE_ACTIVE_AFTER_SELL_HISTORY", "청산후 유령 active 정리", "bool", default=True,
hint="ON=active에 남았는데 매수 이후 청산 히스토리면 DB 행 삭제"),
_f("GHOST_PURGE_ON_RECONCILE", "고아복구 시 유령잔고 삭제", "bool", default=True,
hint="ON=브로커 0주인데 active_trades 남은 종목 삭제(수동보호 제외) · Pre/Post EOD 동일 잔고조회에서 처리"),
hint="ON=브로커 보유 0·매도가능 0·정정취소가능 매도 없을 때만 active_trades 삭제(수동보호 제외) · Pre/Post EOD"),
_f(
"GHOST_PURGE_RECORD_HISTORY",
"유령정리 시 거래내역 기록",