AlgoLab Blog · 국내주식 자동매매 실무 · 2026

NXT 넥스트레이드 자동매매 — KRX·SOR 주문 라우팅

국내주식 · 주문 실무 2026-08-05 · 약 9분 읽기 · 알고랩 AlgoLab
한 줄 요약 국내주식 거래소가 KRX 하나에서 KRX·NXT 둘로 늘면서, 자동매매 봇의 주문 요청에 거래소를 고르는 값이 하나 더 생겼습니다. 키움 REST API 기준으로는 주문 파라미터 dmst_stex_tpKRX·NXT·SOR 중 하나를 넣습니다. 실무에서 걸리는 건 이 값 자체가 아니라 시간대·주문유형·종목 커버리지가 값마다 다르다는 점입니다. 정규장은 SOR로 맡기고, 프리마켓·애프터마켓은 NXT를 직접 지정하되 시장가가 아닌 지정가로 보내야 하며, 종목이 NXT 거래 대상인지 먼저 확인해야 합니다.

2025년 3월 4일 국내 첫 대체거래소(ATS) 넥스트레이드(NXT)가 문을 열면서, 국내주식 자동매매의 전제 하나가 조용히 바뀌었습니다. 예전에는 "국내주식 주문 = 한국거래소(KRX)로 보낸다"가 당연했지만, 지금은 같은 종목을 두 시장 중 어디로 보낼지 봇이 고르는 항목이 생겼습니다.

문제는 이 변화가 화면에서는 거의 안 보인다는 것입니다. HTS·MTS를 쓰는 사람은 증권사가 알아서 라우팅해 주니 체감이 없습니다. 반면 키움 REST APIKIS API로 직접 주문을 조립하는 봇은 파라미터를 명시적으로 넣지 않으면 예전 그대로 KRX로만 동작합니다. 밤 8시까지 열리는 시장을 옆에 두고 3시 30분에 문을 닫는 봇이 되는 셈입니다.

이 글은 "NXT가 무엇인가"를 설명하는 글이 아닙니다. 그건 증권사 안내 페이지가 이미 잘 정리해 두었습니다. 여기서는 이미 돌아가는 봇의 코드에서 정확히 어디를 손대야 하는지만 봅니다.

이 글에서 다루는 것

  1. 거래소가 둘이 되면 시간표가 어떻게 되나
  2. 주문 파라미터 한 줄 — dmst_stex_tp
  3. SOR에 맡기면 끝나지 않는 4가지
  4. 시간대별 라우팅을 코드로 분리하기
  5. 백테스트 데이터와 실체결 시장이 어긋나는 문제

1. 거래소가 둘이 되면 시간표가 어떻게 되나

봇 입장에서 가장 먼저 바뀌는 건 "장이 열려 있는 시간"의 정의입니다. KRX 정규장은 09:00~15:30이지만, NXT는 프리마켓·메인마켓·애프터마켓으로 나뉘어 아침 8시부터 저녁 8시까지 운영됩니다.

구간시간(증권사 안내 기준)KRXNXT
프리마켓08:00 ~ 08:50운영
정규장 / 메인마켓09:00 ~ 15:20운영운영
종가 단일가 구간15:20 ~ 15:30운영
애프터마켓15:40 ~ 20:00운영
08:00 09:00 15:30 20:00 KRX 정규장 09:00 - 15:30 NXT 프리 메인마켓 애프터마켓 - 20:00 봇이 자주 틀리는 구간: 15:20 - 15:40
KRX·NXT 운영 시간 비교 — 겹치는 구간과 한쪽만 열리는 구간이 나뉜다

표에서 봇에게 실제로 위험한 건 15:20~15:40 사이입니다. NXT 메인마켓이 끝난 뒤 애프터마켓이 시작되기 전이고, KRX는 종가 단일가만 받는 구간입니다. 여기서 NXT로 고정된 주문을 계속 밀어 넣는 봇은 거부 응답만 쌓다가 재시도 루프에 갇힙니다. 재시도 설계는 호출 제한 설계 쪽 문제와도 곧장 이어집니다.

시간은 반드시 공식 안내로 확인하세요. 위 시간표는 증권사 안내 기준으로 정리한 것이며, 시장 제도와 운영 시간은 변경될 수 있습니다. 봇에 하드코딩하기 전에 거래 증권사의 대체거래소 안내 페이지와 넥스트레이드 공지를 확인하고, 값은 코드가 아니라 설정 파일로 빼 두는 것을 권합니다.

2. 주문 파라미터 한 줄 — dmst_stex_tp

키움증권 REST API의 국내주식 주문 요청에는 국내거래소구분 항목인 dmst_stex_tp가 있습니다. 여기에 들어가는 값이 KRX·NXT·SOR입니다. 실제 매수 주문 요청은 이런 모양입니다.

POST https://api.kiwoom.com/api/dostk/ordr
Content-Type: application/json;charset=UTF-8
authorization: Bearer {access_token}
api-id: kt10000

{
  "dmst_stex_tp": "SOR",     <-- KRX | NXT | SOR
  "stk_cd": "005930",
  "ord_qty": "10",
  "ord_uv": "",              <-- 시장가면 공란
  "trde_tp": "3"             <-- 주문 유형(시장가/지정가 등)
}

한국투자증권 KIS API 역시 국내주식 주문에 거래소를 구분하는 항목을 두고 있습니다. 다만 필드명과 허용값은 증권사마다 다르고 개정도 잦으므로, KIS 쪽은 KIS Developers의 국내주식 주문 API 문서에서 현재 규격을 확인한 뒤 적용하시기 바랍니다. 이 글에서 dmst_stex_tp만 구체적으로 적는 이유는 그 값이 키움 공식 가이드와 다수의 래퍼 라이브러리에서 일관되게 확인되기 때문입니다.

의미언제 쓰나주의
KRX한국거래소로 직접 전송종가 단일가 구간, KRX에서만 되는 종목연장시간에는 주문 불가
NXT넥스트레이드로 직접 전송프리마켓·애프터마켓거래 가능 종목이 한정됨
SOR증권사가 유리한 쪽 선택두 시장이 함께 열린 정규장연장시간은 대상이 아님

SOR은 Smart Order Routing의 약자로, 증권사가 KRX와 NXT의 호가를 실시간으로 비교해 투자자에게 더 유리한 거래조건 쪽으로 주문을 보내 주는 기능입니다. 봇 개발자 입장에서는 "정규장에는 그냥 SOR을 넣으면 된다"로 이해해도 대체로 무리가 없습니다. 문제는 그 대체로가 커버하지 못하는 구간입니다.

3. SOR에 맡기면 끝나지 않는 4가지

① 프리·애프터마켓에는 시장가가 없다

연장 시간대에는 시장가 주문이 지원되지 않습니다. 지정가로 내야 합니다. 이게 왜 문제냐면, 대부분의 자동매매 봇이 진입은 지정가, 청산·손절은 시장가로 짜여 있기 때문입니다. 손절 로직만 시장가로 남겨 둔 봇을 애프터마켓에 그대로 돌리면 손절 주문만 조용히 거부됩니다. 주문 유형별 성격은 주문 유형 정리에서 따로 다뤘습니다.

가장 위험한 조합입니다. 진입은 성공하고 손절만 실패하면, 봇은 "포지션 진입 완료" 상태로 밤새 방치됩니다. 연장시간 매매를 넣을 거라면 손절을 지정가로 대체하는 경로를 먼저 만들고, 그것이 어려우면 애당초 연장시간에는 신규 진입을 막는 편이 안전합니다.

② NXT 거래 가능 종목이 전 종목이 아니다

NXT에서 거래되는 종목은 KRX 전 종목이 아니라 별도로 정해진 목록이고, 이 목록은 고정이 아닙니다. 키움증권 REST API 공지사항에도 「넥스트레이드(NXT) 거래가능종목 축소 안내」가 올라온 적이 있습니다. 즉 어제까지 되던 종목이 오늘 안 될 수 있습니다.

조건검색식으로 종목을 뽑아 자동 주문하는 구조라면 특히 조심해야 합니다. 검색식이 뱉은 종목이 NXT 대상인지 검사하지 않고 애프터마켓에 던지면, 종목마다 성공·실패가 갈리는 산발적 장애가 됩니다. 로그만 보면 원인을 찾기 매우 어려운 유형입니다.

③ 중간가호가 같은 NXT 전용 호가가 있다

NXT에는 중간가호가처럼 KRX에 없는 주문 유형이 있습니다. KRX와 NXT의 최우선 매수호가·매도호가의 산술평균 가격으로 주문이 걸리는 방식입니다. 스프레드가 벌어진 종목에서는 유용할 수 있지만, 봇이 지정가 가격을 스스로 계산하는 로직과 충돌할 수 있습니다. 쓸 거라면 가격 계산 책임이 봇에 있는지 시장에 있는지부터 정리하고 들어가야 합니다.

④ 체결 로그에 어느 시장인지 안 남는다

이건 사고가 난 뒤에야 알게 되는 항목입니다. 주문은 SOR로 보냈는데 실제 체결은 NXT에서 났다면, 체결 로그에 그 사실이 남아 있어야 나중에 슬리피지를 분석할 수 있습니다. 남기지 않으면 "왜 예상 가격과 다르게 체결됐지"를 영원히 못 풉니다. 체결 로그에 dmst_stex_tp와 응답의 체결 시장 정보를 필드로 하나 늘리는 것으로 충분합니다.

4. 시간대별 라우팅을 코드로 분리하기

위 조건들을 주문 함수 안에 if로 흩어 놓으면 반드시 샙니다. "지금 몇 시인가 → 어느 거래소로, 어떤 주문 유형으로"를 한 곳에서 결정하는 함수로 빼는 편이 낫습니다.

from datetime import time as t

# 값은 증권사 공식 안내로 확인 후 설정 파일에서 주입할 것
PRE   = (t(8, 0),  t(8, 50))
MAIN  = (t(9, 0),  t(15, 20))
CLOSE = (t(15, 20), t(15, 30))   # KRX 종가 단일가
AFTER = (t(15, 40), t(20, 0))

def route(now, stk_cd, nxt_universe):
    """(dmst_stex_tp, 지정가만 허용 여부) 를 돌려준다."""
    if PRE[0] <= now < PRE[1] or AFTER[0] <= now < AFTER[1]:
        if stk_cd not in nxt_universe:
            return None, None          # NXT 미지원 종목 -> 주문 보류
        return "NXT", True             # 연장시간: 지정가만
    if MAIN[0] <= now < MAIN[1]:
        return "SOR", False            # 정규장: 증권사에 맡김
    if CLOSE[0] <= now < CLOSE[1]:
        return "KRX", False            # 종가 단일가는 KRX
    return None, None                  # 그 외 시간: 주문 금지

def build_order(now, stk_cd, qty, price, side, nxt_universe):
    stex, limit_only = route(now, stk_cd, nxt_universe)
    if stex is None:
        raise MarketClosed(f"{now} 주문 불가 구간 · {stk_cd}")
    if limit_only and price is None:
        raise OrderTypeError("연장시간에는 시장가 주문이 불가 — 지정가 필요")
    return {
        "dmst_stex_tp": stex,
        "stk_cd": stk_cd,
        "ord_qty": str(qty),
        "ord_uv": "" if price is None else str(price),
        "trde_tp": "3" if price is None else "0",
    }

포인트는 세 가지입니다. 첫째, 시간 경계값을 상수로 뺐습니다 — 제도가 바뀌면 여기만 고칩니다. 둘째, 주문 불가 구간을 예외로 명시했습니다. 조용히 KRX로 폴백시키면 15:20~15:40 사이에 의도치 않은 주문이 나갑니다. 셋째, 시장가 금지 조건을 주문 조립 단계에서 잡았습니다 — 증권사 응답을 받고 나서 알게 되면 이미 늦습니다.

nxt_universe는 기동 시 한 번 만들어 두고 하루 한 번 갱신하는 집합입니다. 종목 유니버스를 구성하는 방법은 국내주식 데이터 수집에서 다룬 방식을 그대로 쓰되, NXT 거래 가능 여부 필드를 하나 추가하면 됩니다.

💬
이미 돌고 있는 봇에 연장시간을 붙이는 게 맞을까요?

전략에 따라 답이 갈립니다. 일봉 종가 기준 전략이라면 연장시간이 큰 의미가 없고, 갭·뉴스 대응형이라면 오히려 핵심 구간입니다. 어느 쪽인지 판단이 서지 않으면 현재 코드 구조를 놓고 상담해 주세요.

→ 무료 상담으로 구조 점검받기

5. 백테스트 데이터와 실체결 시장이 어긋난다

마지막이 가장 조용한 함정입니다. 흔히 쓰는 국내주식 일봉·분봉 데이터는 KRX 기준으로 정리된 것이 많습니다. 그런데 실제 체결은 SOR을 통해 NXT에서 이뤄질 수 있고, 프리마켓·애프터마켓 구간은 백테스트 데이터에 아예 없는 시간대입니다.

즉 연장시간 매매를 전략에 넣는 순간, 그 부분은 검증된 적이 없는 로직이 됩니다. 백테스트와 실거래의 괴리에서 다룬 문제가 여기서 한 층 더해집니다. 데이터에 없는 구간을 실계좌로 시험하는 셈이기 때문입니다.

과거 데이터로 확인된 결과가 미래 성과를 보장하지 않습니다. 특히 연장시간은 정규장보다 참여자가 적어 호가가 얇을 수 있고, 같은 전략이라도 체결 품질이 달라질 수 있습니다. 수익률을 추정하기보다 "이 시간대에 내 주문이 규격대로 접수·체결되는가"부터 소액으로 확인하는 순서를 권합니다. 시장 제도·수수료·세제 관련 사항은 반드시 증권사와 거래소의 공식 자료로 확인하시기 바랍니다.

정리 — 오늘 코드에서 확인할 5줄

확인할 것안 고치면 생기는 일
주문 요청에 dmst_stex_tp가 있는가정규장에만 동작 · 연장시간 미사용
15:20~15:40 구간을 막았는가거부 응답 + 재시도 루프
연장시간 손절이 지정가인가진입만 되고 청산이 실패
종목의 NXT 거래 가능 여부를 보는가종목별 산발적 주문 실패
체결 로그에 체결 시장을 남기는가슬리피지 원인 추적 불가

거래소가 둘이 된 것은 봇에게 기회이자 새로운 실패 지점입니다. 증권사별 규격 차이는 증권사 API 비교에 정리해 두었습니다.

자주 묻는 질문

SOR로만 보내면 거래소를 신경 쓰지 않아도 되나요?

정규장 시간대에는 대체로 그렇지만 전체 시간대를 SOR로 덮을 수는 없습니다. SOR은 두 시장이 동시에 열려 있을 때 호가를 비교하는 기능이라, KRX가 닫혀 있는 프리마켓·애프터마켓 구간에서는 NXT를 직접 지정해야 합니다. 이 시간대에는 시장가가 지원되지 않으므로 지정가로 보내야 한다는 조건도 함께 붙습니다. 세부 지원 범위는 증권사마다 다르므로 각 사 공식 안내를 확인하세요.

기존 봇을 안 고치면 어떻게 되나요?

망가지지는 않습니다. 거래소 값을 KRX로 고정해 둔 봇은 정규장에서 그대로 동작합니다. 다만 연장시간을 쓸 수 없고, 정규장에서도 SOR이 잡아 줬을 더 유리한 호가를 놓칠 수 있습니다. 반대로 값만 바꾸고 주문 유형·시간대 조건을 손보지 않으면 특정 구간에서만 거부가 나므로, 고칠 거면 라우팅 함수까지 함께 고치는 편이 낫습니다.

KIS API에서도 같은 방식으로 하면 되나요?

개념은 같지만 필드명과 허용값이 다릅니다. 이 글의 dmst_stex_tp는 키움 REST API 기준이고, 한국투자증권은 국내주식 주문 API에 별도의 거래소 구분 항목을 둡니다. tr_id·appkey 헤더 체계 자체가 다르기도 하고, 규격 개정도 잦습니다. 적용 전 KIS Developers 문서에서 현재 규격을 확인하시기 바랍니다.

연장시간에 봇을 돌리면 호출량이 늘어나 문제가 되지 않나요?

될 수 있습니다. 운영 시간이 6시간 30분에서 12시간으로 늘면 같은 폴링 주기라도 하루 총 호출량이 거의 두 배가 됩니다. 증권사 API는 초당 호출 한도가 있고 초과하면 오류가 떨어집니다. KIS를 쓴다면 EGW00201 초당 거래건수 초과를 함께 보시길 권합니다.

국내주식 자동매매 봇 맞춤 제작

KRX·NXT 시간대별 주문 라우팅, 연장시간 지정가 처리, 종목 유니버스 관리까지 — 알고랩이 통합 패키지로 제작합니다.
24시간 빠른 답변 가능합니다.

무료 상담 시작하기