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

12 KiB
Raw Blame History

키움 ↔ 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)**하는 최상의 비동기 정제 엔진을 작동시킴!