Files
kis_trader/docs/like_mcp.md/소켓.md

107 lines
12 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 키움 ↔ LS증권 웹소켓 및 조건검색 실시간 아키텍처 백서
이 문서는 키움증권(Kiwoom)과 LS증권(LS Open API)의 조건검색 및 시세 웹소켓 통신 메커니즘의 차이점, 과거 운영 중 발생한 꼬임(오류)의 근본원인과 금번 개편된 100점짜리 아키텍처 설계 지침을 총정리한 문서입니다.
---
## 🏛️ 1. 키움 ↔ LS증권 총괄 비교 및 초등학생 비유 요약
| 구분 | 🟣 **키움증권 (Kiwoom)** | 🔵 **LS증권 (LS Open API)** |
|:---:|:---|:---|
| **통신 메커니즘** | **100% 단일 WebSocket 일체형**<br>*(REST API 일절 사용하지 않음)* | **REST API (TR 조회) + WebSocket (AFR 알람)**<br>*(4단계 릴레이 분산형 하이브리드)* |
| **국제표준 Ping (RFC 6455)** | ❌ **비호환** (`ping_interval=0`)<br>*(대신 서버가 30초마다 JSON `PING` 보냄)* | ❌ **비호환** (`ping OFF`)<br>*(표준 Ping 보내면 무시하고 Pong 안 줌)* |
| **장중 생존 감시 방식** | **서버의 30초 심장박동 (JSON PING)**<br>👉 장중 워치독(의심병 타이머) 불필요! | **심장박동 아예 없음**<br>👉 15~30분마다 소켓 ID만 안전하게 리부팅 필요 |
| **초기 종목 수집** | **`CNSRREQ` 소켓 응답에 포함**<br>*(구독 신청하면 현재 명단 같이 줌)* | **`t1859` (REST API 별도 호출 필수)**<br>*(소켓은 연결 이전 과거 멤버를 안 알려줌)* |
| **초등학교 사장님 비유** | *"주문 접수했으니 수락 카톡(ACK) 줘요!<br>안 주면 3초마다 전화함!" (수락 즉시 타이머 삭제)<br>+ 사장님이 30초마다 켜져있다고 방송해 줌.* | *"사장님이 주문만 받고 장중 영업 고백을 안 함."<br>너무 조용하면 15분마다 "사장님 안 자죠?" 하고<br>알람 ID만 새것으로 교체해 소켓을 새로 열어봄.* |
---
## 🟣 2. 키움증권 (Kiwoom) 조건검색 및 소켓 동작 원리
### (1) 작동 순서
1. **`CNSRLST` (WS)**: 웹소켓 접속 후 계정에 저장된 조건식 명단과 일련번호(`seq`) 조회.
2. **`CNSRCLR` (WS)**: 충돌 방지를 위해 기존 등록을 선행 청산하고 완료 응답(ACK) 대기.
3. **`CNSRREQ` (WS)**: 실시간 감시 구독 요청. **응답 데이터에 현재 조건에 걸린 초기 종목 스냅샷이 동봉**됨.
4. **`REAL` (WS)**: 같은 소켓을 통해 신규 편입/이탈 시 실시간 푸시 수신.
### (2) 아까 발생했던 오류와 금번 수정(해결)의 본질
* **과거 치명적 오류 (왜 터졌었는가?)**:
- 새벽 `Bye` 메시지 후 소켓 재접속 시, 키움 서버에 이전 등록이 남아있는 경우 `CNSRREQ`를 보내면 **`900003` (이미 등록된 조건식) 충돌 에러**가 발생.
- 구형 로직은 이 900003 충돌이 두 번 나면 **즉시 해당 조건식을 영구 폐기(discard)**하여 유니버스가 평생 0종목이 되는 자살골을 범함. 또한 재시도 간격이 지수 백오프(5초 → 15초 → 30초 → 300초)로 멍들어 있어 장중 5분 이상 매매 감시가 비어있는 대참사 유발.
* **금번 혁신적 개편 (100% ACK 기반 고정 회복)**:
- **영구 폐기(discard) 퇴출**: 어떤 충돌이 나도 절대 조건식을 포기하거나 유니버스를 0개로 버리지 않음.
- **3초 고정 주기 초재기 (ACK 획득 전용)**: `CNSRCLR` 후 서버 완료 확인증(ACK)을 인지하여 2초 settle 후 safe 등록 수행. 만약 응답이 누락되거나 거절되면 **3.0초 고정 주기 / 최대 30회(약 90초)** 동안 완료 확인증(ACK)을 받아낼 때까지 끝까지 전화해서 살려냄.
- 💡 **핵심**: 서버가 "수락 완료(ACK)"를 쏘는 즉시 이 3초짜리 초재기 타이머는 100% 삭제(소멸)되며, 장중 내내 드르륵 도는 것이 아님!
### (3) 키움은 왜 장중 유지보수(15~30분 주기 재기동)가 안 필요한가?
* 키움 웹소켓은 장중에 아무 거래가 없어도, 서버가 **30초마다 0.1초도 틀리지 않고 `{"trnm": "PING"}` JSON 메시지를 내려줌**.
* 봇은 이를 그대로 echo하여 소켓이 100% 팽그르르 돌고 있음을 지속 확인.
* 통신선이 끊어지면 30초마다 오던 PING이 실패하므로 OS의 비동기 `_on_close` / `_on_error`가 즉각 펑 터져 자동 재접속됨. 따라서 LS처럼 "조용히 죽었나?" 의심해서 주기적으로 소켓을 건드릴 필요가 전혀 없음!
---
## 🔵 3. LS증권 (LS Open API) 조건검색 및 소켓 동작 원리
### (1) 국제표준 PING(20초)을 못 쓰고 `ping OFF`를 한 이유
* **LS 서버 사양의 한계**: LS Open API 웹소켓(`wss://openapi.ls-sec.co.kr:9443`)은 클라이언트가 국제 표준 RFC 6455 PING 프레임을 20초마다 보내면, **이를 무시하고 PONG 응답을 돌려주지 않는 서버 비호환 상태**임.
* 만약 표준 PING을 켜두면, Python 라이브러리가 "10초째 PONG 대답이 없다!"며 멀쩡한 소켓을 에러 쳐서 끊고 재접속을 연타하게 됨. 이 때문에 `AfrHistoryWatcher`에서 강제로 `ping OFF`로 설정할 수밖에 없었음.
* 또한 키움과 달리 30초 주기로 내려오는 사제 JSON PING조차 없음 (심장박동 제로).
### (2) `t1866`, `t1859`, `t1860`, `AFR` — 4대 TR의 100% 정확한 역할
LS는 REST API와 WebSocket이 물리적으로 찢어져 있어 4단계를 조립해야 합니다. (**`t1866`, `t1859`, `t1860`은 100% REST API이며, `AFR`만 웹소켓 소켓입니다!**)
1. **`t1866` (REST API) - [조건식 명단 장부]**:
- 내 계정에 저장된 조건식들의 **이름(title)과 고유 인덱스(`query_index`)** 목록을 조회하는 TR. (종목 명단은 절대 주지 않음).
2. **`t1859` (REST API) - [현재 종목 풀덤프 장부 / 🚨장중 위험 인물]**:
- 현재 시간 기준 해당 조건식(`query_index`)에 충족해 있는 **전체 종목 명단을 한꺼번에 조회**하는 TR.
- *왜 필요한가?* AFR 웹소켓은 소켓 연결 **이후** 발생하는 일만 말해주므로, **봇을 맨 처음 켤 때(기동 시) 또는 재시작할 때 최초 1번은 RAM 장부를 채우기 위해 100% 필수**로 불러야 함!
3. **`t1860` (REST API) - [실시간 알람 신청 및 ID 발급기]**:
- 옵션 `'E'` (등록): "이 조건식 실시간 감시 시작하게 소켓 전용 **알람 ID(`sAlertNum`, 11자리)** 하나 발급해주세요!" 하고 ID를 따내는 TR.
- 옵션 `'D'` (해제): "이전 알람 ID 이제 필요 없으니 감시 멈춰주세요." 하고 닫는 TR. (역시 종목 명단은 안 줌).
4. **`AFR` (WebSocket) - [실시간 편입/이탈 스피커]**:
- 위 3번에서 받은 알람 ID(`sAlertNum`)를 들고 접속하는 실시간 시세 소켓. 접속 시점 이후 발생한 **`N` (진입), `R` (재진입), `O` (이탈)** 이벤트만 날려줌.
---
## 💥 4. 과거 LS 로직의 근본 원인과 "859 연타 땜질"이 부른 참사
### (1) 진짜 근본 원인 (Root Cause: 꿀 먹은 AFR 소켓)
* 장중 LS 서버 부하 또는 사용자의 HTS 조건식 변경 등으로 인해 **AFR 실시간 소켓이 에러 신호도 띄우지 않고 조용히 굳어버리거나 푸시를 멈추는 침묵 현상**이 일어남.
* 소켓이 조용해지면 이탈(`O`) 메시지를 끝내 못 받아, HTS 화면에는 종목이 탈락해 사라졌는데 봇 RAM 장부에는 끝까지 살아있어 썩어가는 **죽은 유령 종목(Sticky) 잔존 사고** 발생.
### (2) 과거 개발자의 어리석은 땜장이 처방 (60초 규칙)
* LS는 PING도 안 주고 소켓 침묵이 감지 안 되니까 극도의 공포감을 느낀 과거 개발자가 **"에이! 소켓으로 편입/이탈 신호가 60초(1분) 동안 안 오면, 무조건 소켓이 먹통 되었거나 HTS 조건을 수정한 걸로 칠래!"** 하고 간주.
* 그래서 60초간 이벤트가 없으면 **`t1859`(REST 조회)를 무조건 연타해서 RAM 장부를 통째로 강제 덮어쓰기(교차보정) 시키는 기형 로직**을 탑재함.
### (3) 859 땜장이 처방이 부른 2차 대참사 (자살골)
* 평화로운 점심시간이나 장이 한산할 땐 멀쩡한 소켓도 5~30분간 조용한 것이 완벽한 정상임!
* 그런데 구형 봇이 "60초 동안 조용하네? 찔러야지!" 하고 1분마다 `t1859` REST를 난타하다가...
* LS 서버가 장중 순간 지연으로 엉뚱한 **"조회결과 0종목(Empty)"** 응답을 툭 반환하면, **"헐! 0개네! 다 나갔나 보다!" 하고 RAM 속 우량 감시 종목 20개를 싹뚝 쳐내서 살해(Clear)해버리는 대참사** 발발.
* 이를 가리겠다고 "축소 방지(`ALLOW_SHRINK=false`)", "레이스 컨디션 방지" 등 누더기 코드가 기하급수적으로 증식했던 것.
---
## 🎯 5. 금번 개편된 LS 조건검색 실시간 아키텍처 (최종 합의 마스터 로직)
### 1⃣ 왜 Pong을 안 주거나 줘도 문제인가? (국내 증권사 좀비 소켓의 비밀)
* **프록시(문지기) ↔ 백엔드 불일치 현상**: 아침 9시 부하 시, 앞단 웹소켓 프록시(Gateway)는 멀쩡해 Ping/Pong 통신이나 TCP 연결이 녹색불(정상)을 띄우고 있어도, 뒷단 실시간 조건검색 방송 엔진이 혼자 부하로 먹통이 되어 알람을 안 쏴주는 **"꿀 먹은 좀비 소켓"** 사태가 발생함.
* 일반 주식 시세(`ls_ws.py`)는 거래 틱이 45초간 안 오면 100% 좀비로 판단해 차단하고 재연결(Watchdog)할 수 있지만, **조건검색은 평안한 장세에서 2시간 동안 이벤트가 없는 게 정상이므로 짧은 워치독 타이머를 둬서는 안 됨!**
### 2⃣ 시세·조건 전용 이점 극대화 ➡️ 60초(1분) 주기 "소켓 심폐소생술 & Delta Diff"
* **매매 전용이 아닌 조건·시세 전용 (Scouter) 구조의 위대한 판정**:
- 당사의 봇은 실제 주식 매수/매도 주문(TR)을 LS 증권으로 쏘지 않고, 오직 **조건검색 감시와 호가/시세 전용 스카우터 기지**로만 운용함.
- 따라서 REST API를 매분 찔러도 **정작 제일 중요한 실매매 주문이 429 에러(유량 상한 초과)로 막힐 위험이 100% 영구 소멸**함!
* **하루 총 눈감는 시간 58초 (수학적 극대화 효율)**:
- 60초(`LS_T1859_STALE_RESYNC_SEC=60`) 주기로 국장 정규시간 390분(09:00~15:30) 내내 하루 390회 소켓 ID를 재발급(`t1860 D ➡️ E ➡️ AFR 재구독`)하더라도, 1회당 발생하는 마의 암흑기 틈새 0.15초를 곱하면 **하루 누적 눈감는 총 시간이 고작 '58.5초'(1분 미만)**에 불과함!
- 60초마다 HTS 조건 수동 변경 사항을 즉각 반입하고, 멍든 소켓을 단 1분 만에 깨우는 극강의 실시간 스카우터를 완주함.
### 3⃣ 신의 한 수: "Delta Diff (1:1 비교 보정) & 0종목 보호 방패"
60초마다 소켓이 재개통되는 0.15초 틈새(Gap) 동안에 HTS에서 몰래 탈퇴한 **유령 종목(Sticky Ghost)**이 RAM에 남는 사태(100개 퇴적 악몽)와 서버 순간 지연을 막기 위해, 60초 주기 `t1859` 대조 시 **아래 2중 철옹성 방어막을 영구 적용**함!
* 🔴 **0종목 보호 방패 (자살골 절대 차단)**:
- 기존 RAM에 종목이 있는데 `t1859` 응답이 0종목(Empty)으로 내려오면, 이는 99.9% LS 서버 지연이나 통신 장애로 인한 가짓말 응답임.
- 즉시 **"⚠️ 0종목 오류 응답 감지 → 자살골 청산 없이 기존 RAM 장부 100% 보존!"** 판정으로 서버 응답을 무시하여 우량 감시 주식을 수호함!
* 🟢 **Delta Diff 정교 소결 (빼고 넣고 1:1 대조)**:
- 유효한 응답(예: RAM 20종 ➡️ t1859 15종)이 오면, 무지성 전체 삭제 후 덮어쓰지 않음.
- 기존 RAM 명단과 최신 명단을 대조하여 **틈새 동안 사라진 유령 종목 5개만 정확히 솎아내어(Pop) 내버리고, 신규 편입 종목만 상단에 반영(Push)**하는 최상의 비동기 정제 엔진을 작동시킴!