# 키움 ↔ LS증권 웹소켓 및 조건검색 실시간 아키텍처 백서 이 문서는 키움증권(Kiwoom)과 LS증권(LS Open API)의 조건검색 및 시세 웹소켓 통신 메커니즘의 차이점, 과거 운영 중 발생한 꼬임(오류)의 근본원인과 금번 개편된 100점짜리 아키텍처 설계 지침을 총정리한 문서입니다. --- ## 🏛️ 1. 키움 ↔ LS증권 총괄 비교 및 초등학생 비유 요약 | 구분 | 🟣 **키움증권 (Kiwoom)** | 🔵 **LS증권 (LS Open API)** | |:---:|:---|:---| | **통신 메커니즘** | **100% 단일 WebSocket 일체형**
*(REST API 일절 사용하지 않음)* | **REST API (TR 조회) + WebSocket (AFR 알람)**
*(4단계 릴레이 분산형 하이브리드)* | | **국제표준 Ping (RFC 6455)** | ❌ **비호환** (`ping_interval=0`)
*(대신 서버가 30초마다 JSON `PING` 보냄)* | ❌ **비호환** (`ping OFF`)
*(표준 Ping 보내면 무시하고 Pong 안 줌)* | | **장중 생존 감시 방식** | **서버의 30초 심장박동 (JSON PING)**
👉 장중 워치독(의심병 타이머) 불필요! | **심장박동 아예 없음**
👉 15~30분마다 소켓 ID만 안전하게 리부팅 필요 | | **초기 종목 수집** | **`CNSRREQ` 소켓 응답에 포함**
*(구독 신청하면 현재 명단 같이 줌)* | **`t1859` (REST API 별도 호출 필수)**
*(소켓은 연결 이전 과거 멤버를 안 알려줌)* | | **초등학교 사장님 비유** | *"주문 접수했으니 수락 카톡(ACK) 줘요!
안 주면 3초마다 전화함!" (수락 즉시 타이머 삭제)
+ 사장님이 30초마다 켜져있다고 방송해 줌.* | *"사장님이 주문만 받고 장중 영업 고백을 안 함."
너무 조용하면 15분마다 "사장님 안 자죠?" 하고
알람 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)**하는 최상의 비동기 정제 엔진을 작동시킴!