AlgoLab Blog · 업비트 Open API · SMP · 2026

업비트 자전거래 방지 smp_type — taker 기준이다

업비트 · 주문/SMP 2026-09-18 · 약 6분 읽기 · 알고랩 AlgoLab
한 줄 요약 업비트 SMP(자전거래 체결 방지)는 taker 주문의 설정으로만 동작합니다. 공식 문서가 못 박는 문장이 이것입니다 — 호가창에 이미 남겨진 주문의 SMP 모드는 더 이상 유효하지 않다. 즉 먼저 걸어 둔 매수 주문에 smp_type을 넣어 두어도, 나중에 그 호가를 치는 매도 주문에 smp_type이 없으면 자전거래는 그대로 체결됩니다. 판정도 주문 시점이 아니라 체결 시점이라, 주문이 정상 접수된 것과 SMP는 아무 관계가 없습니다.

같은 종목에 매수와 매도를 동시에 걸어 두는 구조 — 그리드 봇, 마켓메이킹, 분할 매수와 익절이 겹치는 봇 — 에서는 내 주문끼리 만나는 일이 생깁니다. 체결되면 수수료만 양쪽으로 나가고 포지션은 그대로입니다. 업비트는 이걸 막는 옵션을 SMP(Self-Match Prevention)라는 이름으로 주문 파라미터에 열어 뒀는데, 기본값이 꺼짐이고 거는 위치가 직관과 반대라 넣어 놓고도 안 걸리는 경우가 생깁니다.

아래는 업비트 개발자 센터의 SMP 문서와 주문 생성 API의 OpenAPI 정의를 2026년 9월 18일 기준으로 읽고 정리한 것입니다.

1. 어디에 넣나 — 이름이 두 개다

엔드포인트파라미터 이름
POST /v1/orders (주문 생성)smp_type
POST /v1/orders/cancel_and_new (취소 후 재주문)new_smp_type

SMP를 지원하는 엔드포인트는 이 둘뿐이고, 이름이 다릅니다. 취소 후 재주문은 신규 주문 쪽 필드에 전부 new_가 붙는 규칙이라 new_volume·new_price와 마찬가지로 new_smp_type이 됩니다. 주문 생성 코드에서 쓰던 smp_type 키를 그대로 복사하면 에러 없이 무시되고 SMP만 빠진 주문이 나갑니다.

2. 세 가지 모드 — 누구를 살리나

동작남는 쪽
cancel_takermaker 주문을 유지하고 taker 주문을 취소호가창의 기존 주문
cancel_makertaker 주문을 유지하고 maker 주문을 취소방금 낸 주문
reduce겹치는 수량만큼 양쪽을 줄임둘 다 (수량이 남으면 유지)

고르는 기준은 "지금 이 주문이 반드시 체결돼야 하는가"입니다. 호가창에 깔아 둔 물량이 전략의 본체인 그리드 봇이라면 cancel_taker로 기존 주문을 지키고, 손절이나 청산처럼 지금 나가는 것이 목적인 주문이라면 cancel_maker가 맞습니다. reduce는 어느 쪽도 통째로 날리지 않고 겹친 만큼만 깎기 때문에 큰 물량을 쪼개 내는 분할 집행 구조에서 덜 파괴적입니다.

post_only와는 같이 쓸 수 없습니다. 공식 문서가 time_in_forcepost_only를 설정하면 SMP를 함께 쓸 수 없다고 명시합니다. 그런데 메이커 수수료를 받으려고 post_only를 쓰는 봇이 바로 자전거래가 가장 잘 나는 구조입니다. 둘 중 하나를 골라야 하고, 이건 수수료 설계와 직결되는 선택입니다. 나머지 옵션(limit·price·market·best, ioc·fok)과는 모두 병행 가능합니다.

3. 핵심 함정 — taker 쪽에 걸어야 한다

동작하지 않음 동작함 maker (호가창에 대기) smp_type: cancel_taker taker (지금 치는 주문) smp_type 없음 → 그대로 체결된다 maker (호가창에 대기) smp_type 없어도 무방 taker (지금 치는 주문) smp_type: cancel_taker → 방지된다 판정 시점 = 주문 시점이 아니라 체결 시점
SMP는 taker 주문의 설정만 본다 — 호가창에 남은 모드는 무효

많은 봇이 "방어용 주문"에만 옵션을 붙입니다. 호가창에 오래 걸어 두는 매수 주문에는 smp_type을 넣고, 신호가 떴을 때 급히 내는 시장가 매도에는 안 넣는 식입니다. 그런데 SMP는 정확히 그 반대로 동작합니다.

결론: 주문을 내는 모든 경로에 smp_type을 넣으십시오. 어느 주문이 taker가 될지는 호가 상황에 따라 바뀌므로, 코드에서 미리 가릴 수 없습니다. 주문 생성 함수의 기본 인자로 박아 두는 편이 안전합니다 — 주문 유형별로 분기해 두면 언젠가 빠진 경로가 생깁니다.

4. 취소된 걸 어떻게 아나 — 필드 3개

필드내용
smp_type실제 적용된 모드(reduce·cancel_maker·cancel_taker)
prevented_volumeSMP로 취소된 수량
prevented_locked매수: 취소된 금액(수수료 포함) / 매도: 취소된 수량
// REST 주문 응답 — null이면 필드 자체가 없다
{
  "uuid": "53afa136-8882-46e5-8119-614ae10e623b",
  "side": "bid",
  "smp_type": "cancel_maker",        // null이면 이 줄이 통째로 없음
  "prevented_volume": 1.174291929,
  "prevented_locked": 0.001706246173
}

⚠ REST와 WebSocket의 null 처리가 반대입니다. 주문 관련 REST 응답은 값이 null이면 해당 필드가 아예 내려오지 않습니다resp["smp_type"]으로 바로 접근하면 SMP가 적용되지 않은 평범한 주문에서 KeyError가 납니다. 반면 WebSocket의 MyOrder 스트림은 "smp_type": null로 키를 포함해서 보냅니다. 같은 파서를 두 경로에 재사용하면 여기서 갈립니다. resp.get("smp_type")으로 받으십시오. 단 prevented_volume·prevented_locked기본값이 0이라 항상 존재합니다.

5. MyOrder에 5번째 상태가 온다 — prevented

주문 상태(state)의 정규 값은 네 가지입니다 — wait(체결 대기) · watch(예약 주문 대기) · done(체결 완료) · cancel(주문 취소). 그런데 WebSocket MyOrder 스트림을 구독하면 여기 없는 값이 하나 더 옵니다.

// WebSocket MyOrder — null이어도 필드가 내려온다
{
  "type": "myOrder",
  "code": "KRW-BTC",
  "uuid": "ac2dc2a3-fce9-40a2-a4f6-5987c25c438f",
  "ask_bid": "BID",
  "order_type": "limit",
  "state": "prevented",              // ← 4종 목록에 없는 값
  "smp_type": null,
  "prevented_volume": "0",
  "prevented_locked": "0"
}

state: "prevented"는 자전거래 방지로 취소된 수량(또는 금액)을 알리는 이벤트입니다. 주문 전체가 취소된 것이 아닙니다 — 남은 수량이 더 이상 없어 주문 자체가 끝날 때만 cancel이 옵니다. wait·watch·done·cancel 네 가지만 알고 짠 상태 머신은 prevented모르는 값으로 흘려보내거나 else 분기에서 오분류합니다. reduce 모드를 쓰면 이 이벤트가 정상 동작 중에 반복적으로 발생합니다.

prevented_locked에는 수수료가 포함돼 있습니다(매수 한정). 공식 문서 표현대로 수수료가 0.25%면 방지된 주문 금액에 1.0025를 곱한 값입니다. 이 숫자를 그대로 "취소된 주문 금액"으로 장부에 적으면 수수료만큼 어긋납니다. 매도 주문일 때는 수수료와 무관하게 prevented_volume과 같은 값입니다.

수량 대사를 할 때도 이 필드가 끼어듭니다. 주문 조회 응답에는 volume(주문 요청 수량) · executed_volume(체결량) · remaining_volume(잔여량)이 같이 들어 있는데, SMP가 동작한 주문에서는 줄어든 몫이 executed_volume이 아니라 prevented_volume으로 빠집니다. "요청 수량 = 체결량 + 잔여량"을 가정하고 검증하는 대사 로직은 SMP가 걸린 주문에서 어긋나고, trades_count도 올라가지 않습니다. prevented_volume까지 항으로 넣어야 맞습니다.

6. 체크리스트

  1. 주문 생성 경로 전부smp_type이 들어가는가 (taker가 될 쪽을 미리 가릴 수 없다)
  2. cancel_and_new에는 new_smp_type으로 넣었는가
  3. post_only를 쓰고 있다면 SMP를 포기했다는 것을 알고 있는가
  4. 파서가 resp.get("smp_type")인가 (REST는 null이면 키가 없다)
  5. 상태 머신이 prevented를 알고 있는가
  6. 장부에서 prevented_locked수수료 포함을 보정하는가

SMP는 켜 둔다고 체결 품질이 좋아지는 기능이 아닙니다. 불필요한 수수료와 규제상 자전거래를 줄이는 안전장치이고, 대신 주문이 예고 없이 취소되거나 수량이 줄어드는 경우를 봇이 처리할 수 있어야 합니다. 자전거래가 거의 나지 않는 구조라면 굳이 켜지 않아도 되고, 그리드처럼 구조적으로 나는 구조라면 취소 처리 로직을 먼저 짜고 켜는 순서가 맞습니다.

자주 묻는 것

업비트 SMP는 어떻게 켜나요?

주문 파라미터로 넣습니다. POST /v1/orderssmp_type, POST /v1/orders/cancel_and_newnew_smp_type입니다. 값은 cancel_taker·cancel_maker·reduce 셋. 선택 사항이라 지정하지 않으면 적용되지 않고 동일 회원 주문끼리 그대로 체결될 수 있습니다.

먼저 건 주문에 넣어 두면 되나요?

안 됩니다. 공식 문서는 SMP가 taker 주문의 설정 기준으로 동작하며 호가창에 이미 남겨진 주문의 SMP 모드는 더 이상 유효하지 않다고 명시합니다. 판정도 주문 시점이 아니라 체결 시점입니다.

cancel_maker·cancel_taker·reduce는 무엇이 다른가요?

살리는 쪽이 다릅니다. cancel_taker는 호가창의 maker를 지키고 방금 낸 taker를 취소, cancel_maker는 반대, reduce겹치는 수량만큼만 양쪽을 줄이고 남은 수량이 있으면 주문을 유지합니다. 어느 모드가 유리한지는 전략 구조에 따라 다르며 수익과는 무관합니다.

취소된 것을 어떻게 알 수 있나요?

smp_type·prevented_volume·prevented_locked 세 필드입니다. REST는 null이면 필드가 아예 없고, WebSocket MyOrder는 null로 포함해 내려옵니다. MyOrder에서는 stateprevented로 오고, 남은 수량이 없어 주문이 끝날 때만 cancel이 옵니다.

그리드·마켓메이킹 봇을 맡기려면

자전거래 방지, 부분 취소 처리, 상태 머신 설계는 돌려 보기 전에는 잘 드러나지 않는 부분입니다.
어떤 구조가 맞는지부터 같이 정리해 드립니다. 24시간 빠른 답변 가능합니다.

무료로 상담하기
본 글은 업비트 개발자 센터(docs.upbit.com)의 자전거래 체결 방지(SMP) 문서와 주문 생성·취소 후 재주문 API의 OpenAPI 정의 원문을 2026년 9월 18일 기준으로 대조해 작성했습니다. 본문의 파라미터명·모드 값·응답 필드·상태 값은 전부 그 시점의 공식 문서 표기이며, 실계좌 주문으로 검증한 것이 아닙니다. 수수료율 0.25%는 공식 문서가 prevented_locked 계산을 설명하며 든 예시 값이고 실제 적용 요율과 다를 수 있습니다. 파라미터·필드·정책은 공지 후 변경될 수 있으므로 반드시 업비트 개발자 센터의 현재 문서로 대조하십시오. 이 글은 특정 종목이나 매매 시점에 대한 권유를 담고 있지 않으며, 수익률이나 시장 방향에 대한 어떠한 전망도 하지 않습니다. 알고랩(퀀트웍스)은 투자자문업·투자일임업을 영위하지 않으며, 고객이 정한 규칙을 코드로 구현하는 도구 제작 서비스를 제공합니다. 투자 판단과 그 결과의 책임은 전적으로 투자자 본인에게 있습니다.