키움 0s 장운영구분 215 코드표 — s와 S가 다르다
키움 REST API 실시간 0s(장시작시간)의 FID 215는 값이 21개이고 대소문자를 구분합니다. 3이 장시작, 2가 장마감 알림, 4가 장마감, R이 NXT 메인마켓 시작입니다. 소문자 s는 선옵 동시호가 시작, 대문자 S는 NXT 메인마켓 종료라 .upper() 한 줄로 뜻이 바뀝니다. 공식 명세의 요청 예시는 "data": null로 깨져 있고 응답 예시는 b(16:00)에 170000이 붙어 있습니다. 0s는 이벤트라 장중에 재접속한 봇의 상태는 복원되지 않습니다. (키움 저장소 main 953e5db, 2026-11-05 확인. 파서는 명세 예시와 가짜 메시지로만 실행했고 실서버 수신은 하지 않았습니다.)
목차
- 215 코드표 21개 전체
- REG 패킷 — 명세 예시를 복사하면 안 된다
- 응답 예시의 b와 170000
- 대소문자 — s와 S는 다른 사건이다
- 파서와 실행 결과
- 재접속 — 3을 놓친 봇
- 자주 묻는 질문
1. 215 코드표 21개 전체
0s는 종목 시세가 아니라 장 운영 상태를 알리는 실시간 항목입니다. 응답 values에는 215 장운영구분, 20 체결시간(HHmmss), 214 장시작예상잔여시간(HHmmss)이 옵니다. 다음은 kiwoom_api_spec.json의 215 설명을 그대로 옮긴 표입니다. 0B·0D를 포함한 실시간 시세 전체는 키움 실시간 시세 가이드에 있습니다.
| 값 | 명세 설명 | 시장 |
|---|---|---|
0 | 장시작전 알림(8:40~) | KRX |
3 | 장시작(09:00) | KRX |
2 | 장마감 알림(15:20~) | KRX |
4 | 장마감(15:30) | KRX |
8 | 정규장마감(거래소 수신시 15:30 이후) | KRX |
9 | 전체장마감(거래소 수신시 18:00 이후) | KRX |
a / b | 시간외 종가매매 시작(15:40) / 종료(16:00) | KRX |
c / d | 시간외 단일가 시작(16:00) / 종료(18:00) | KRX |
e | 선옵 장마감전 동시호가 종료 | 선물옵션 |
f | 선물옵션 장운영시간 알림(조기개장 상품) | 선물옵션 |
o | 선옵 장시작 | 선물옵션 |
s | 선옵 장마감전 동시호가 시작 | 선물옵션 |
P / Q | NXT 프리마켓 시작 / 종료 알림 | NXT |
R / S | NXT 메인마켓 시작 / 종료 알림 | NXT |
T | NXT 에프터마켓 단일가 시작 알림 | NXT |
U / V | NXT 에프터마켓 시작 / 종료 알림 | NXT |
주의할 점이 둘 있습니다. 숫자 순서가 시간 순서가 아닙니다. 2(15:20~)가 3(09:00)보다 늦습니다. 그래서 if code >= "3" 같은 비교는 쓸 수 없습니다. 또 괄호 속 시각은 평소 기준입니다. 2026-11-19 수능일처럼 개장이 늦춰지는 날이 있으므로 수능날 10시 개장 글처럼 "09:00"을 시각으로 하드코딩하지 말고 3 수신으로 판정합니다. 수능일에 실제로 어떤 시각에 오는지는 명세에 없어 확인하지 못했습니다.
2. REG 패킷 — 명세 예시를 복사하면 안 된다
명세의 request_example은 표 변환 과정에서 깨진 것으로 보입니다. data가 null이고 - item, - type이라는 키가 따로 있습니다.
{
"trnm": "REG",
"grp_no": "1",
"refresh": "1",
"data": null,
"- item": "",
"- type": "0s"
}
공식 예제 examples/국내주식/실시간시세/subscribe_domestic_market_open_time_async.py는 올바른 구조를 씁니다. item이 빈 리스트이고 type이 리스트입니다.
body = {
"trnm": "REG", # 등록("0"이면 해제)
"grp_no": "1", # 그룹번호
"refresh": "1", # 기존 등록 유지 여부
"data": [{"item": [], "type": ['0s']}],
}
여기 주석에도 함정이 있습니다. "0"이면 해제라고 적혀 있지만 명세의 trnm 설명은 REG : 등록 , REMOVE : 해지입니다. 해지는 "trnm": "REMOVE"로 보내십시오.
refresh는 더 조심해야 합니다. 명세상 등록 시 0은 기존유지안함, 1은 기존유지입니다. 0B·0D를 등록해 둔 소켓에 0s를 "refresh": "0"으로 추가하면, 명세대로라면 앞서 등록한 항목이 해지됩니다. 시세가 조용히 끊기는 결과라 오류로 드러나지도 않습니다. 0s 등록에는 "1"을 쓰십시오. 공식 클라이언트 kiwoom/core/ws_client.py는 먼저 LOGIN을 보내고 PING을 처리하므로, 직접 짠다면 이 순서를 따라야 합니다. 키움 공지(2026-07-02 기준)는 계좌별(토큰별) 세션을 1개로 안내하니 소켓을 따로 열지 말고 한 세션에 함께 등록합니다.
3. 응답 예시의 b와 170000
명세의 수신 예시는 이렇습니다.
{"data": [{"values": {"215": "b", "20": "170000", "214": "000000"},
"type": "0s", "name": "장시작시간", "item": ""}],
"trnm": "REAL"}
코드표에서 b는 시간외 종가매매 종료(16:00)인데 예시의 20은 17시입니다. 예시 값을 실제 동작으로 읽지 말라는 뜻으로 보입니다. 시각 검증을 파서에 넣는다면 이 예시로 테스트하지 마십시오. "코드 b는 16시여야 한다"는 단정문을 만들면 공식 예시부터 실패합니다. 항목 이름도 장시작시간이지만 장마감까지 알리고, 개요는 "장 시작 전, 장 종료 후, 시간 외 등에 알림"입니다. item은 빈 문자열이라 item으로 종목별 분기를 하는 수신 코드는 이 메시지를 버리지 않게 확인해야 합니다.
4. 대소문자 — s와 S는 다른 사건이다
실제로 21개를 돌려 대문자·소문자로 변환했을 때 다른 코드와 겹치는 쌍을 찾으면 정확히 하나가 나옵니다.
print([k for k in ALL if k.upper() in ALL and k.upper() != k]) # ['s']
print([k for k in ALL if k.lower() in ALL and k.lower() != k]) # ['S']
s 선옵 마감전 동시호가 시작 | upper -> 메인마켓 종료
S 메인마켓 종료 | lower -> 선옵 마감전 동시호가 시작
코드를 정규화하는 습관이 있는 공통 함수를 지나면 s가 "NXT 메인마켓 종료"로 바뀝니다. trnm은 공식 클라이언트도 .upper()로 비교하기 때문에, 그 처리를 값까지 넓히면 이 사고가 납니다. 0~9만 있으리라 보고 int()를 씌우면 a에서 터집니다.
int("a")
# ValueError: invalid literal for int() with base 10: 'a'
판다스로 쌓는다면 pd.to_numeric(errors="coerce")에 넣지 마십시오. 위 오류 대신 문자 코드가 전부 NaN이 됩니다. 0s는 숫자 칼럼이 아니라 문자열입니다. 시세 필드의 부호 문제는 ka10061 이중 부호 글과 같은 종류의 함정입니다.
5. 파서와 실행 결과
import json
KRX = {"0": "장시작전 알림", "3": "장시작", "2": "장마감 알림", "4": "장마감",
"8": "정규장마감", "9": "전체장마감",
"a": "시간외 종가 시작", "b": "시간외 종가 종료",
"c": "시간외 단일가 시작", "d": "시간외 단일가 종료"}
FUT = {"e": "선옵 마감전 동시호가 종료", "f": "선옵 장운영시간 알림",
"o": "선옵 장시작", "s": "선옵 마감전 동시호가 시작"}
NXT = {"P": "프리마켓 시작", "Q": "프리마켓 종료", "R": "메인마켓 시작", "S": "메인마켓 종료",
"T": "애프터 단일가 시작", "U": "애프터마켓 시작", "V": "애프터마켓 종료"}
ALL = {**KRX, **FUT, **NXT} # 21개
def parse_0s(raw):
msg = json.loads(raw)
if msg.get("trnm") != "REAL": # REG 응답, PING은 건너뜀
return []
out = []
for d in msg.get("data", []):
if d.get("type") != "0s":
continue
v = d.get("values", {})
code = v.get("215", "") # 대소문자 변환 금지
out.append({"code": code, "name": ALL.get(code, "미등록:" + code),
"time": v.get("20"), "left": v.get("214")})
return out
모르는 코드는 예외를 내지 않고 미등록:x로 남겨 로그에 찍습니다. NXT 값들처럼 코드는 늘어날 수 있고, 장 상태 메시지 하나 때문에 봇이 죽을 이유는 없습니다. 실행 결과입니다.
[{'code': 'b', 'name': '시간외 종가 종료', 'time': '170000', 'left': '000000'}] # 명세 예시
[] # REG 응답
[] # PING
[{'code': 'x', 'name': '미등록:x', 'time': '090000', 'left': '000000'}] # 가짜 미등록 코드
같은 코드가 두 번 와도 주문이 두 번 나가지 않도록 상태 전이는 (날짜, 코드)로 한 번만 처리합니다. 명세는 중복 수신 여부를 말하지 않으므로 방어적으로 씁니다. 0, 3, 3, 2, 4, 8, 9 순서로 넣으면 두 번째 3만 False로 걸러집니다.
class Session:
def __init__(self):
self.open = False
self.seen = set()
def on(self, day, code):
key = (day, code)
if key in self.seen:
return False
self.seen.add(key)
if code == "3":
self.open = True
elif code in ("4", "8"):
self.open = False
return True
KRX 정규장 상태만 보는 코드입니다. NXT까지 주문하는 봇이라면 시장별 상태를 따로 두고 R·S를 별도로 처리하십시오.
6. 재접속 — 3을 놓친 봇
0s의 약점은 현재 상태 조회가 아니라 사건 알림이라는 점입니다. 명세에는 등록 직후 현재 상태를 보내 준다는 설명이 없습니다. 09:05에 소켓이 끊겼다 붙은 봇을 위 클래스로 새로 만들면 이렇게 됩니다.
n = Session()
print(n.open) # False 장은 열려 있는데 봇은 닫혔다고 믿는다
그래서 0s 단독으로 "지금 장이 열렸나"를 판단하지 않습니다. 시작할 때와 재접속할 때 상태를 다시 세웁니다.
- 당일 받은 코드를 파일에 남겨 재시작해도
seen을 복원합니다. - 기록이 없으면
exchange_calendars나 KISCTCA0903R같은 달력·시각표로 초기값을 정합니다(휴장일 판정 3가지 비교). - 구독 중인
0B체결이 들어오면 장이 열린 것으로 보조 판정합니다.
재접속 자체의 처리는 봇 장애 복구 플레이북에 있습니다. KIS 쪽에는 H0STMKO0이 있지만 공식 예제 설명이 VI 발동·해제 시 수신이라고 적고 있어, 개장 신호로 같게 다루기에는 근거가 약합니다. 키움 0s를 쓴 로직을 KIS로 그대로 옮기지 마십시오. 체결 통보가 필요하면 실시간 주문체결 00·잔고 04를 참고하십시오.
알고랩은 프로그램 제작 도구를 제공하며 투자자문업·투자일임업을 영위하지 않습니다. 명세는 바뀔 수 있으니 wss://api.kiwoom.com:10000 실서버 또는 wss://mockapi.kiwoom.com:10000 모의 서버에서 실제 수신 순서를 확인한 뒤 주문 로직에 연결하십시오.
자주 묻는 질문
장 시작을 판정하는 값은 무엇인가요?
KRX 정규장은 FID 215 값 3입니다. 명세에 장시작(09:00)으로 적혀 있습니다. NXT 메인마켓은 R입니다. 개장이 늦춰지는 날이 있으므로 09:00 시각을 고정하지 말고 코드 수신으로 판정하되, 재접속에 대비해 달력이나 시각표를 함께 쓰십시오.
0s를 등록할 때 종목코드가 필요한가요?
아닙니다. 공식 예제는 item을 빈 리스트로 보내고 type에 0s만 넣습니다. 수신 메시지의 item도 빈 문자열입니다.
refresh는 0과 1 중 무엇으로 보내야 하나요?
1입니다. 명세상 0은 기존 등록 항목을 해지하므로 0B나 0D를 쓰는 소켓에 0으로 보내면 시세가 끊길 수 있습니다.
모의투자 서버에서도 0s를 받을 수 있나요?
명세에 모의투자 도메인 wss://mockapi.kiwoom.com:10000이 적혀 있습니다. 다만 이 글에서는 서버에 접속해 수신을 확인하지 않았으므로 주문 로직에 연결하기 전에 직접 수신 순서를 확인하십시오.
장 상태 판정까지 들어간 봇이 필요하다면
키움 REST API 실시간 시세, 재접속 복원, KRX·NXT 시장별 상태 처리를 전략에 맞춰 맞춤 제작합니다. 24시간 빠른 답변 가능합니다.
무료 상담 시작하기