AlgoLab Blog · 경제 일정 × 자동매매 · 2026

수능날 자동매매 — 10시 개장에 봇이 깨지는 5곳

경제 일정 × 자동매매 2026-11-02 · 약 7분 읽기 · 알고랩 AlgoLab
한 줄 요약

2027학년도 수능은 2026년 11월 19일(목)이고, 한국거래소는 2022~2025년 수능일마다 KOSPI·KOSDAQ·KONEX 정규장을 10:00~16:30으로 1시간씩 늦췄습니다. 휴장이 아니라 '시각이 바뀌는 개장일'이라 휴장일 체크만 넣은 봇은 그대로 09:00에 움직입니다. 실측해 보니 파이썬 exchange_calendars 최신판(4.13.2)의 XKRX 달력은 수능일 목록이 2020-12-03에서 끝나 2026-11-19를 09:00~15:30으로 돌려주고, 한국투자증권 KIS 국내휴장일조회(CTCA0903R) 응답에는 시각 필드가 아예 없습니다. 날짜→시각 표를 한 곳에 두고 모든 작업 시각을 개장·마감에서 계산하거나, 키움 실시간 0s(장시작시간) 이벤트로 개장을 판정하면 막을 수 있습니다. (올해 KRX 공지는 2026-11-02 현재 미확인 — 예년 패턴 기준, 공지 확인 필수)

목차

  1. 수능날 시간표 — 무엇이 몇 시로 밀리나
  2. 휴장일 판정이 수능날을 못 잡는 이유 (실측)
  3. 봇이 깨지는 5곳
  4. 고치는 법 ① 날짜→시각 표 한 곳에
  5. 고치는 법 ② 시계 대신 장운영 이벤트
  6. 11월 19일 전 점검표
  7. 자주 묻는 질문

1. 수능날 시간표 — 무엇이 몇 시로 밀리나

올해(2026) 수능일 KRX 공지는 이 글을 쓰는 11월 2일 아침까지 찾지 못했습니다. 대신 지난해 공지가 기준이 됩니다. 한국거래소는 2025년 10월 30일, 수능일(2025-11-13)에 유가증권시장·코스닥시장·코넥스시장의 거래 시작과 종료를 1시간씩 늦춘다고 밝혔고, 2022·2023·2024년 수능일도 같은 방식이었습니다. 발표 시점은 2022년 11월 3일, 2024년 10월 31일, 2025년 10월 30일로 매년 수능 2주 전쯤입니다.

구분평소2025 수능일 (보도 기준)
KRX 정규장 (KOSPI·KOSDAQ·KONEX)09:00~15:3010:00~16:30
KRX 장전·장후 시간외—개시 1시간 늦춤, 장후 시간외 마감 18:00 유지
NXT 프리마켓08:00~08:50미운영
NXT 메인마켓09:00~15:2010:00~16:20
NXT 애프터마켓15:30~20:0016:30~20:00 (마감 동일)
KRX 파생상품상품별상품에 따라 09:45 또는 10:00 개장 (2024 공지)

봇 입장에서 핵심은 두 가지입니다. 개장과 마감이 같이 밀린다(정규장 길이 6시간 30분은 그대로)는 것, 그리고 장후 시간외·NXT 애프터마켓은 마감이 그대로라 구간이 1시간 짧아진다는 것입니다. 시간외 세부 시각은 해마다 공지 문구가 조금씩 달랐으니 올해 공지 원문으로 다시 맞추세요. 평소 장 운영 시간과 NXT 라우팅은 NXT 넥스트레이드 자동매매 — KRX·SOR 주문 라우팅에 정리돼 있습니다.

평소 vs 수능날 (KRX 정규장) 080910111213141516 평소 09:00 ─ 정규장 ─ 15:30 수능날 10:00 ─ 정규장 ─ 16:30 09:00 봇 기상 15:15 청산·15:40 저장
09:00에 깨어나는 봇은 1시간 동안 체결 없는 시장을 보고, 15시대 작업은 장중에 돈다.

2. 휴장일 판정이 수능날을 못 잡는 이유 (실측)

대부분의 봇은 휴장일 판정까지는 갖추고 있습니다. 문제는 그 도구들이 답하는 질문이 "오늘 장이 열리나"뿐이라는 점입니다. 수능날은 열리는 날이므로 세 도구 모두 '정상 거래일'이라고 답합니다.

KIS 국내휴장일조회 CTCA0903R — 시각 필드가 없다

한국투자증권 공식 저장소 open-trading-api(main, 2026-10-27 커밋 3a9be59)의 예제 chk_holiday.py가 정의한 응답 컬럼 전체입니다.

# GET /uapi/domestic-stock/v1/quotations/chk-holiday   tr_id = "CTCA0903R"
COLUMN_MAPPING = {
    'bass_dt': '기준일자',
    'wday_dvsn_cd': '요일구분코드',
    'bzdy_yn': '영업일여부',
    'tr_day_yn': '거래일여부',
    'opnd_yn': '개장일여부',
    'sttl_day_yn': '결제일여부'
}

날짜 여부 플래그 4개가 전부이고 개장·마감 시각은 없습니다. 예제 주석은 "주문을 넣을 수 있는지 확인하고자 하실 경우 개장일여부(opnd_yn)을 사용"하라고 안내하고, 원장 서비스와 연관돼 있어 가급적 1일 1회 호출을 부탁합니다. 수능날 opnd_yn은 개장일이므로 Y가 나오는 것이 정상이고, 그 Y만 보고 09:00 스케줄을 그대로 돌리는 게 사고의 시작입니다.

exchange_calendars XKRX — 규칙은 있는데 날짜가 2020년에서 멈췄다

파이썬에서 거래소 달력으로 가장 많이 쓰는 exchange_calendars는 사정이 더 미묘합니다. 소스 exchange_calendar_xkrx.py에는 "Every year's CSAT day, all schedules are delayed by … 1 hour"라는 주석과 함께 수능일 1시간 지연 규칙(special_offsets_adhoc)이 실제로 구현돼 있습니다. 그런데 그 규칙이 적용될 날짜 목록 precomputed_csat_days가 30개뿐이고 마지막이 2020-12-03입니다. PyPI 최신판 4.13.2(2026-03-10 배포)로 직접 돌려 봤습니다.

import exchange_calendars as xc, pandas as pd   # exchange_calendars 4.13.2, pandas 2.2.3
cal = xc.get_calendar("XKRX")
for d in ["2020-12-03", "2021-11-18", "2025-11-13", "2026-11-19"]:
    t = pd.Timestamp(d)
    print(d, cal.session_open(t).tz_convert("Asia/Seoul").time(),
             cal.session_close(t).tz_convert("Asia/Seoul").time())

# 실행 결과
2020-12-03 10:00:00 16:30:00   ← 목록에 있음
2021-11-18 09:00:00 15:30:00   ← 실제는 10:00~16:30
2025-11-13 09:00:00 15:30:00   ← 실제는 10:00~16:30
2026-11-19 09:00:00 15:30:00   ← 올해 수능일

규칙이 있다는 걸 알고 쓰는 사람일수록 "라이브러리가 알아서 처리하겠지" 하고 넘어가기 쉬운 지점입니다. 2021년부터 다섯 번의 수능일이 전부 평소 시각으로 나옵니다. pykrx는 영업일·일자 데이터만 주고 시각 개념이 없으니 마찬가지로 이 문제를 잡지 못합니다. 세 도구의 차이는 주식 휴장일 조회 — 봇 개장일 판정 3가지 비교에서 자세히 다뤘습니다.

3. 봇이 깨지는 5곳

수능날 장애는 에러 메시지로 드러나지 않는 경우가 많습니다. 주문이 거부되거나 아무 일도 안 일어나거나, 더 나쁘게는 틀린 데이터가 조용히 저장됩니다.

  1. 09:00 시가 진입·감시 루프. 장 시작 직후 시장가로 들어가는 전략이나 09:00부터 websocket 체결(H0STCNT0, 키움 0B) 수신을 기다리는 루프는 1시간 동안 체결이 없는 시장을 봅니다. 그 사이 현재가 조회(FHKST01010100, 키움 ka10001)로 받은 값을 '시가'로 저장하면 그날 계산 전체가 어긋납니다. 그 시간에 주문을 넣으면 장 시작 전 접수 주문이 되어 10:00 시가 결정 때 의도와 다른 시점·가격에 체결될 수 있습니다.
  2. '개장 후 N분' 창. 변동성 돌파의 목표가 계산, 오프닝 레인지(첫 30분 고가·저가), 09:00~09:05 첫 5분봉 같은 로직은 시각을 하드코딩해 두면 수능날 엉뚱한 구간을 잡습니다. 변동성 돌파 전략 가이드의 시가 기준 목표가가 대표적입니다.
  3. 당일 청산·장마감 동시호가 판단. 15:15에 보유 종목을 정리하는 봇은 수능날 장이 1시간 15분 남은 시점에 다 팔아 버립니다. 15:20을 '동시호가 시작'으로 보고 신규 주문을 막는 로직도 1시간 일찍 문을 닫습니다. 에러는 없지만 전략이 하루 동안 다른 전략이 됩니다.
  4. 15:40 종가·일봉 저장 배치. 가장 조용하고 가장 오래 남는 사고입니다. 장중(15:40은 수능날 장 한가운데) 가격을 '종가'로 DB에 넣으면, 그 일봉은 이후 백테스트와 지표 계산에 계속 쓰입니다. ka10081·FHKST03010100으로 일봉을 받는 배치라면 다음 날 재수집으로 덮어쓰는 구조인지 확인해야 합니다.
  5. NXT 프리마켓 주문. 08:00에 NXT 프리마켓(_NX 종목코드, SOR _AL)으로 주문을 내는 봇은 2025년 수능일처럼 프리마켓이 운영되지 않으면 주문이 거부됩니다. 거부 응답을 '일시 오류'로 보고 재시도 루프를 돌면 EGW00201 초당 거래건수 초과까지 겹칩니다.

선물·옵션을 같이 돌린다면 여섯 번째가 있습니다. 2024년 공지 기준 파생상품은 상품에 따라 09:45 또는 10:00에 열렸습니다. 주식과 같은 10:00로 일괄 처리하면 안 됩니다.

4. 고치는 법 ① 날짜→시각 표 한 곳에

원칙은 하나입니다. 코드 어디에도 "09:00"·"15:30"을 직접 쓰지 않고, 모든 작업 시각을 그날의 개장·마감에서 계산합니다. 그러면 공지가 나왔을 때 표 한 줄만 고치면 됩니다. 아래는 표준 라이브러리만으로 만든 최소 구현이고, 실행 결과까지 그대로 붙입니다.

from datetime import date, time, datetime, timedelta

# KRX 정규장 기본값과 '시각이 바뀌는 날' 목록 — 공지가 나오면 여기만 고친다
REGULAR = (time(9, 0), time(15, 30))
SPECIAL = {
    date(2025, 11, 13): (time(10, 0), time(16, 30)),  # 2026학년도 수능 (KRX 공지 확인)
    date(2026, 11, 19): (time(10, 0), time(16, 30)),  # 2027학년도 수능 (KRX 공지 나오면 재확인)
}

def session(d: date):
    o, c = SPECIAL.get(d, REGULAR)
    return datetime.combine(d, o), datetime.combine(d, c)

def jobs(d: date):
    o, c = session(d)
    return {
        "watch_start": o,                          # 감시 시작 = 개장
        "breakout_calc": o + timedelta(minutes=1), # 시가 확정 후 목표가 계산
        "flatten": c - timedelta(minutes=15),      # 당일 청산 = 마감 15분 전
        "daily_save": c + timedelta(minutes=10),   # 종가 저장 = 마감 10분 뒤
    }

# 실행 결과
2026-11-18 {'watch_start': '09:00', 'breakout_calc': '09:01', 'flatten': '15:15', 'daily_save': '15:40'}
2026-11-19 {'watch_start': '10:00', 'breakout_calc': '10:01', 'flatten': '16:15', 'daily_save': '16:40'}

APScheduler를 쓴다면 cron 트리거에 시각을 박지 말고, 매일 아침 한 번 jobs(오늘)을 계산해 date 트리거로 그날 작업을 등록하는 구조가 맞습니다. 휴장일이면 아무것도 등록하지 않으면 되니 휴장일 판정과도 자연스럽게 합쳐집니다.

exchange_calendars를 이미 쓰고 있다면 — 서브클래스로 날짜만 추가

라이브러리의 규칙(1시간 지연)은 맞고 날짜 목록만 낡았으니, 목록을 합쳐 주는 서브클래스면 충분합니다. 4.13.2에서 실행해 확인한 코드입니다.

import pandas as pd
import exchange_calendars as xc
from exchange_calendars.exchange_calendar_xkrx import XKRXExchangeCalendar
from exchange_calendars.xkrx_holidays import precomputed_csat_days

EXTRA_CSAT = pd.DatetimeIndex(["2021-11-18", "2022-11-17", "2023-11-16",
                               "2024-11-14", "2025-11-13", "2026-11-19"])

class XKRXPatched(XKRXExchangeCalendar):
    name = "XKRX_CSAT"

    @property
    def special_offsets_adhoc(self):
        days = precomputed_csat_days.union(EXTRA_CSAT)
        h = pd.Timedelta(1, unit="h")
        return [(h, h, h, h, days[days.slice_indexer("1998-11-18", None)])]

xc.register_calendar_type("XKRX_CSAT", XKRXPatched)
cal = xc.get_calendar("XKRX_CSAT")

# 실행 결과 (session_open / session_close, KST)
2025-11-13 10:00 16:30
2026-11-18 09:00 15:30
2026-11-19 10:00 16:30

register_calendar가 아니라 register_calendar_type에 클래스를 넘겨야 합니다. 처음에 register_calendar("XKRX_CSAT", XKRXPatched)로 클래스를 넘겼더니 get_calendar가 인스턴스 대신 클래스를 돌려줘 TypeError: ExchangeCalendar.session_open() missing 1 required positional argument: 'session'이 났습니다. 원래 튜플의 1998년 이전 30분 지연 구간은 2026년 봇과 무관해서 뺐습니다.

5. 고치는 법 ② 시계 대신 장운영 이벤트

표는 공지를 사람이 옮겨 적어야 한다는 약점이 있습니다. 보조 장치로 "09:00이 됐다"가 아니라 "장이 시작됐다는 신호를 받았다"로 판정하면 표를 빠뜨려도 버팁니다. 키움 REST의 실시간 0s(장시작시간)가 바로 그 신호입니다. 공식 명세 kiwoom_api_spec.json(키움 저장소 main 953e5db)의 등록 요청과 수신 예시입니다.

# wss://api.kiwoom.com:10000/api/dostk/websocket  (모의: wss://mockapi.kiwoom.com:10000)
{"trnm": "REG", "grp_no": "1", "refresh": "1",
 "data": [{"item": [""], "type": ["0s"]}]}

# 실시간 수신 (명세 예시)
{"data": [{"values": {"215": "b", "20": "170000", "214": "000000"},
           "type": "0s", "name": "장시작시간", "item": ""}],
 "trnm": "REAL"}

FID 215(장운영구분)의 값이 0 장시작전 알림, 3 장시작, 2 장마감 알림, 4 장마감, 8 정규장마감입니다. NXT도 P·Q(프리마켓 시작·종료), R·S(메인마켓 시작·종료), U·V(애프터마켓 시작·종료)로 따로 옵니다. 214는 장시작예상잔여시간(HHmmss)입니다. 명세 설명에는 "3 : 장시작(09:00)"처럼 평소 시각이 괄호로 박혀 있지만, 봇은 괄호 속 시각이 아니라 215 == "3"이 실제로 도착한 순간을 개장으로 보면 됩니다. 수능날 프리마켓이 열리지 않으면 P가 오지 않는다는 것도 판정 근거가 됩니다(이 부분은 명세상 근거가 아니라 동작 추론이니 실제 수신 로그로 확인하세요). REG 등록 형식과 FID 함정은 키움 REST API 웹소켓 — REG 등록·FID 함정 5가지에 있습니다.

코드 21개 전체와 재접속 처리는 키움 0s 장운영구분 코드표에 있습니다.

한국투자증권 KIS에도 실시간 장운영정보 H0STMKO0(컬럼 mkop_cls_code 장운영구분코드, antc_mkop_cls_code 예상장운영구분코드 등)가 있지만, 공식 예제 설명이 "연결된 종목의 VI 발동 시와 VI 해제 시에 데이터가 수신"된다고 적고 있어 개장 신호로 쓰기엔 근거가 약합니다. KIS 봇이라면 ① 표 방식을 기본으로 두고, 개장 판정은 "H0STCNT0 체결이 처음 들어온 시각"으로 보조하는 편이 안전합니다. VI 쪽 동작은 VI 발동 때 봇이 할 일을 참고하세요.

6. 11월 19일 전 점검표

7. 자주 묻는 질문

2026년 수능날 주식시장은 몇 시에 열리나요?

2027학년도 수능은 2026년 11월 19일(목)입니다. 한국거래소는 2022~2025년 수능일 모두 유가증권·코스닥·코넥스 정규장을 10시~16시 30분으로 1시간씩 늦췄습니다. 올해 공지는 2026-11-02 현재 확인되지 않았으니, 예년처럼 10월 말~11월 초에 나오는 KRX 공지로 최종 확인하세요.

휴장일 API로 확인하면 수능날도 걸러지나요?

아닙니다. 수능날은 휴장이 아니라 개장일이라 휴장일 API는 정상 거래일로 답합니다. 한국투자증권 국내휴장일조회(CTCA0903R)의 응답 필드는 bass_dt, wday_dvsn_cd, bzdy_yn, tr_day_yn, opnd_yn, sttl_day_yn뿐이고 개장·마감 시각 필드가 없습니다.

exchange_calendars XKRX는 수능일을 알지 않나요?

코드에 수능일 1시간 지연 규칙은 있지만 날짜 목록(precomputed_csat_days)이 2020-12-03에서 끝납니다. 2026-11-02 기준 최신판 4.13.2로 실측하면 2021~2025년 수능일과 2026-11-19 모두 09:00~15:30으로 나옵니다. 서브클래스로 날짜를 추가하면 10:00~16:30이 나옵니다.

넥스트레이드(NXT)도 수능날 시간이 바뀌나요?

2025년 수능일에는 NXT 프리마켓(평소 08:00~08:50)을 운영하지 않았고, 메인마켓은 10:00~16:20, 애프터마켓은 16:30 시작·20:00 마감이었습니다. 올해 운영 시간은 넥스트레이드 공지로 확인하세요.

달력 이벤트에 안 깨지는 봇, 만들어 드립니다

휴장일·수능일·연초 개장 지연을 표 한 곳에서 관리하고, 모든 작업 시각을 개장·마감에서 계산하는 KIS·키움 자동매매 봇을 맞춤 제작합니다. 기존 봇 점검도 가능합니다. 24시간 빠른 답변 가능합니다.

무료 상담 시작하기

이 글은 발행 시점(2026-11-02) 기준 공개 보도와 공식 저장소 코드를 바탕으로 한 기술 정보이며, 거래 시간은 한국거래소·넥스트레이드 공지가 최종 기준입니다. 특정 종목이나 매매 시점을 권하지 않습니다.