KIS API 선물옵션 주문 — TTTO1101U 함정 6가지
tr_id는 TTTO1101U(주간 실전)이고,
엔드포인트는 /uapi/domestic-futureoption/v1/trading/order입니다.
주식 주문 코드를 복사해 종목코드만 바꾸면 반드시 실패합니다.
계좌번호 뒤 두 자리 ACNT_PRDT_CD가 주식 계좌 값이 아니고,
종목코드는 PDNO가 아니라 SHTN_PDNO,
가격은 ORD_UNPR이 아니라 UNIT_PRICE이며,
주문 유형은 NMPR_TYPE_CD·ORD_DVSN_CD·KRX_NMPR_CNDT_CD
세 개를 함께 채워야 합니다.
한국투자증권 KIS API로 주식 자동매매를 만들어 본 사람이 코스피200 선물이나 옵션으로 넘어올 때
거의 같은 곳에서 막힙니다. "주식 주문이 잘 되니까 종목코드만 101W09로 바꾸면 되겠지"라고
생각하기 때문입니다. 그런데 KIS API에서 국내 선물옵션은 주식과 완전히 다른 API 묶음입니다.
경로도, 필드명도, 계좌 지정 방식도 겹치지 않습니다.
이 글은 한국투자증권 공식 저장소 koreainvestment/open-trading-api의
examples_user/domestic_futureoption/ 예제 코드를 기준으로,
실제로 사람이 걸려 넘어지는 지점 6개만 골라 정리한 것입니다.
KIS API 앱키 발급과 첫 주문까지는 끝냈다고 가정합니다.
이 글에서 다루는 것
- 주식 API와 선물옵션 API는 무엇이 다른가
- 함정 1 — 계좌상품코드가 주식과 다르다
- 함정 2 —
PDNO가 아니라SHTN_PDNO - 함정 3 — 주문 유형 필드가 하나가 아니라 셋이다
- 함정 4 — 야간 주문과 야간 정정취소의 접두어가 다르다
- 함정 5 — 모의투자에는 야간이 없다
- 함정 6 — 시세는
F와O로 갈리고output이 셋이다 - 주문부터 잔고까지 실행 코드
- 선물옵션 TR ID 전체 표 · 실시간 항목
주식 API와 선물옵션 API는 무엇이 다른가
가장 먼저 머릿속에서 지워야 할 것은 "같은 KIS API니까 비슷하겠지"라는 가정입니다. 아래 표가 두 묶음의 실제 차이입니다.
| 국내주식 | 국내 선물옵션 | |
|---|---|---|
| 주문 경로 | /uapi/domestic-stock/v1/trading/order-cash | /uapi/domestic-futureoption/v1/trading/order |
주문 tr_id | 매수·매도가 다른 TR | TTTO1101U 하나 (매수·매도는 바디로 구분) |
| 종목코드 필드 | PDNO | SHTN_PDNO |
| 가격 필드 | ORD_UNPR | UNIT_PRICE |
| 주문 유형 | ORD_DVSN 하나 | NMPR_TYPE_CD + ORD_DVSN_CD + KRX_NMPR_CNDT_CD |
| 매수·매도 | TR ID로 구분 | SLL_BUY_DVSN_CD (01 매도 / 02 매수) |
| 계좌 뒤 2자리 | 주식 계좌 값 | 선물옵션 계좌 값 (예제 설명은 03) |
겹치는 게 CANO(종합계좌번호)와 ORD_QTY(주문수량) 정도입니다.
그래서 "복사 후 수정"이 아니라 처음부터 새로 쓰는 게 빠릅니다.
함정 1 — 계좌상품코드가 주식과 다르다
KIS API는 계좌를 8자리 + 2자리로 쪼개서 받습니다. 앞 8자리가 CANO,
뒤 2자리가 ACNT_PRDT_CD입니다. 자세한 구조는
KIS API 계좌번호 CANO와 ACNT_PRDT_CD에 정리해 두었습니다.
문제는 이 뒤 2자리가 상품 종류를 가리키는 코드라는 점입니다.
주식 계좌에서 쓰던 값을 그대로 선물옵션 주문에 넣으면 계좌를 찾지 못합니다.
한국투자증권 공식 예제의 선물옵션 잔고 조회 함수는 acnt_prdt_cd 설명에 예시로
03을 적고 있습니다.
증상이 헷갈립니다. 계좌 코드가 틀리면 "계좌가 없다"는 명확한 문구가 아니라
권한·조회 실패 계열 메시지로 돌아오는 경우가 있어서, 앱키나 토큰을 의심하며 몇 시간을 태우기 쉽습니다.
선물옵션 API가 처음부터 안 될 때는 토큰보다 ACNT_PRDT_CD를 먼저 의심하십시오.
메시지별 대응은 KIS API 에러코드 대응에 모아 두었습니다.
내 계좌의 실제 뒤 2자리는 추측하지 말고 확인해야 합니다. 선물옵션 계좌를 따로 개설하지 않았다면 API 이전에 계좌 개설과 선물옵션 거래 자격 요건부터 필요합니다. 이 부분은 증권사 정책이라 코드로 우회되지 않습니다.
함정 2 — PDNO가 아니라 SHTN_PDNO
선물옵션 주문 바디에서 종목코드가 들어가는 자리는 SHTN_PDNO(단축상품번호)입니다.
주식에서 쓰던 PDNO를 넣으면 그 필드는 그냥 무시되고 종목이 비어 있는 주문이 됩니다.
자릿수도 상품마다 다릅니다. 공식 예제의 파라미터 설명은 이렇게 적고 있습니다.
| 상품 | 자릿수 | 예시 | 시세 FID_COND_MRKT_DIV_CODE |
|---|---|---|---|
| 지수선물 | 6자리 | 101W09 | F |
| 지수옵션 | 9자리 | 201S03370 | O |
앞자리 101이 코스피200 지수선물 계열, 201이 지수옵션 계열입니다.
실시간 예제에서는 선물이 101S12, 옵션이 201S11305 같은 형태로 등장합니다.
월물은 만기마다 바뀝니다. 종목코드를 소스에 상수로 박아 두면 만기가 지난 뒤 봇이 에러 없이 아무 일도 안 하는 상태가 됩니다. 이게 가장 위험한 실패입니다. 종목코드 마스터 파일을 매일 내려받아 최근월물을 계산하는 구조로 만드십시오.
함정 3 — 주문 유형 필드가 하나가 아니라 셋이다
주식은 ORD_DVSN 하나에 지정가나 시장가 코드를 넣으면 끝났습니다.
선물옵션은 세 개를 함께 채워야 합니다. 공식 예제의 코드값은 다음과 같습니다.
| 필드 | 뜻 | 값 |
|---|---|---|
NMPR_TYPE_CD | 호가유형코드 | 01 지정가 · 02 시장가 · 03 조건부 · 04 최유리 |
KRX_NMPR_CNDT_CD | 거래소 호가조건 | 0 없음 · 3 IOC · 4 FOK |
ORD_DVSN_CD | 주문구분코드 | 01~04 기본 · 10 지정가IOC · 11 지정가FOK · 12 시장가IOC · 13 시장가FOK · 14 최유리IOC · 15 최유리FOK |
ORD_PRCS_DVSN_CD | 주문처리구분 | 02 주문전송 |
ORD_DVSN_CD가 10~15에서 IOC·FOK를 다시 표현한다는 점에 주의하십시오.
같은 조건을 두 필드에 쓰게 되어 있어서 둘이 어긋나면 거절되거나 의도와 다른 주문이 나갑니다.
안전한 습관은 지정가·시장가·최유리 중 하나를 고르고, IOC·FOK를 쓸 때만
KRX_NMPR_CNDT_CD와 ORD_DVSN_CD를 짝으로 맞추는 것입니다.
그리고 시장가나 최유리 지정가일 때 UNIT_PRICE는 0으로 넣습니다.
공식 예제도 시장가나 최유리 지정가인 경우 0으로 입력하라고 적고 있습니다.
함정 4 — 야간 주문과 야간 정정취소의 접두어가 다르다
국내 선물옵션은 정규장 외에 야간 세션이 있습니다. KIS API는 이걸 별도 TR ID로 구분하는데, 여기서 사람들이 규칙을 잘못 외웁니다.
| 동작 | 주간 실전 | 야간 실전 | 모의투자 |
|---|---|---|---|
| 주문 | TTTO1101U | STTN1101U | VTTO1101U |
| 정정취소 | TTTO1103U | TTTN1103U | VTTO1103U |
| 체결내역 | TTTO5201R | STTN5201R | VTTO5201R |
| 주문가능 | TTTO5105R | STTN5105R | VTTO5105R |
| 잔고 | CTFO6118R | CTFN6118R | VTFO6118R |
야간 주문은 STTN인데 야간 정정취소는 TTTN입니다.
"야간이면 앞을 S로 바꾸면 된다"는 규칙으로 코드를 짜면 정정취소만 조용히 실패합니다.
그리고 이 실패는 주문이 이미 나간 뒤에 드러나기 때문에 손실로 직결됩니다.
TR ID는 규칙으로 생성하지 말고 매핑 표에 하드코딩하십시오.
정정취소 자체의 동작 원리는 주식과 같습니다. RVSE_CNCL_DVSN_CD에
01(정정) 또는 02(취소)를 넣고 ORGN_ODNO에 원주문번호를 넣습니다.
전량이면 RMN_QTY_YN을 Y로, ORD_QTY를 0으로 둡니다.
KIS API 주문 취소·정정에서 주식 쪽 흐름을 먼저 봐 두면 이해가 빠릅니다.
함정 5 — 모의투자에는 야간이 없다
한국투자증권 공식 예제 코드는 모의투자 환경일 때 주간만 허용하고
야간을 요청하면 오류를 냅니다. 즉 모의투자 선물옵션 주문 TR은 VTTO1101U 하나뿐이고
야간에 대응하는 모의 TR이 없습니다. 야간 잔고 CTFN6118R이나
야간 증거금 상세 CTFN7107R 같은 야간 전용 조회도 실전 계열입니다.
실무적으로 이게 의미하는 것: 야간 로직은 모의투자로 끝까지 검증할 수 없습니다. 모의에서 주간 경로를 다 검증한 다음, 야간은 최소 계약 수로 실계좌에서 한 번 통과시키는 단계를 반드시 넣으십시오. 모의에서 실전으로 넘어갈 때 무엇이 바뀌는지는 KIS API 모의투자에서 실전 전환에 정리해 두었습니다.
함정 6 — 시세는 F와 O로 갈리고 output이 셋이다
선물옵션 시세는 /uapi/domestic-futureoption/v1/quotations/inquire-price,
tr_id는 FHMIF10000000입니다. 파라미터는 두 개뿐입니다.
params = {
"FID_COND_MRKT_DIV_CODE": "F", # F: 지수선물, O: 지수옵션
"FID_INPUT_ISCD": "101W09",
}
주의할 점은 응답이 output 하나가 아니라 output1·output2·output3 세 덩어리로
온다는 것입니다. 주식 현재가 조회처럼 res["output"]으로 접근하면 KeyError가 납니다.
주식 쪽 KIS API 현재가 조회 함정과 응답 구조 자체가 다릅니다.
호가는 FHMIF10010000, 일봉은 FHKIF03020100, 분봉은 FHKIF03020200입니다.
시세 TR은 실전과 모의가 같은 값을 쓰지만 호출하는 도메인이 다르므로
base_url은 환경에 맞게 바꿔야 합니다.
주문부터 잔고까지 실행 코드
아래는 access_token이 이미 있다고 가정하고 시장가 1계약 매수 → 잔고 확인까지 가는
최소 코드입니다. 구조를 보여주기 위한 예시이며 그대로 실전에 쓰라는 뜻이 아닙니다.
import requests
BASE = "https://openapi.koreainvestment.com:9443" # 실전
CANO, ACNT = "12345678", "03" # 뒤 2자리는 반드시 본인 선물옵션 계좌 값
H = {
"content-type": "application/json; charset=utf-8",
"authorization": f"Bearer {ACCESS_TOKEN}",
"appkey": APP_KEY,
"appsecret": APP_SECRET,
"custtype": "P",
}
def order_futopt(code, qty, side="02"):
"""side 01=매도, 02=매수 / 시장가"""
url = BASE + "/uapi/domestic-futureoption/v1/trading/order"
body = {
"ORD_PRCS_DVSN_CD": "02", # 주문전송
"CANO": CANO,
"ACNT_PRDT_CD": ACNT,
"SLL_BUY_DVSN_CD": side,
"SHTN_PDNO": code, # PDNO 아님
"ORD_QTY": str(qty),
"UNIT_PRICE": "0", # 시장가는 0
"NMPR_TYPE_CD": "02", # 02 시장가
"KRX_NMPR_CNDT_CD": "0", # 0 없음
"ORD_DVSN_CD": "02", # 02 시장가
"CTAC_TLNO": "",
"FUOP_ITEM_DVSN_CD": "",
}
h = dict(H, tr_id="TTTO1101U") # 주간 실전
r = requests.post(url, headers=h, json=body, timeout=10).json()
if r.get("rt_cd") != "0":
raise RuntimeError(f'{r.get("msg_cd")} {r.get("msg1")}')
return r["output"]["ODNO"] # 주문번호
POST 바디의 키는 전부 대문자여야 합니다. 공식 예제도 이 점을 따로 명시하고 있습니다. 소문자로 보내면 필드가 통째로 무시되어 "수량 없음" 같은 엉뚱한 메시지를 받습니다.
잔고는 20건에서 끊기므로 연속조회를 처음부터 넣습니다.
def balance_futopt():
url = BASE + "/uapi/domestic-futureoption/v1/trading/inquire-balance"
p = {
"CANO": CANO, "ACNT_PRDT_CD": ACNT,
"MGNA_DVSN": "01", # 01 게시, 02 유지
"EXCC_STAT_CD": "1", # 1 정산, 2 본정산
"CTX_AREA_FK200": "", "CTX_AREA_NK200": "",
}
rows, cont = [], ""
while True:
h = dict(H, tr_id="CTFO6118R", tr_cont=cont)
r = requests.get(url, headers=h, params=p, timeout=10)
j = r.json()
rows += j.get("output1", [])
cont = r.headers.get("tr_cont", "")
if cont not in ("F", "M"): # 더 없음
break
cont = "N" # 다음 페이지 요청
p["CTX_AREA_FK200"] = j.get("ctx_area_fk200", "")
p["CTX_AREA_NK200"] = j.get("ctx_area_nk200", "")
return rows
실패했을 때 돌아오는 응답은 이런 모양입니다. rt_cd가 0이 아니면
msg_cd와 msg1을 그대로 로그에 남기십시오.
{
"rt_cd": "1",
"msg_cd": "40580000",
"msg1": "모의투자 장운영시간이 아닙니다."
}
운영시간 밖 호출은 에러입니다. 공식 예제도 선물옵션 운영시간 외 API 호출 시 에러가 발생하니 운영시간을 확인하라고 명시하고 있습니다. 봇이 24시간 돌면서 장 시작 전에 주문을 던지면 이 에러가 반복되고, 재시도 로직이 붙어 있으면 호출 제한까지 같이 맞습니다. 장 시작 전에는 주문 루프 자체를 돌리지 않는 스케줄러가 필요합니다.
선물옵션 TR ID 전체 표
공식 예제 domestic_futureoption_functions.py에 정의된 조회 계열입니다.
실전 기준이며 모의 지원 여부는 항목마다 다릅니다.
tr_id | 용도 |
|---|---|
FHMIF10000000 | 선물옵션 시세(현재가) |
FHMIF10010000 | 선물옵션 호가 |
FHKIF03020100 | 선물옵션 기간별 시세(일봉) |
FHKIF03020200 | 선물옵션 분봉 |
FHPIF05030100 | 선물옵션 콜풋 전광판 |
FHPIF05030200 | 선물 전광판 |
FHPIF05110100 | 선물옵션 일중 예상체결 추이 |
CTFO6118R / VTFO6118R | 선물옵션 잔고현황 (실전 / 모의) |
CTFO6117R | 선물옵션 잔고 정산손익내역 |
CTFO6159R | 선물옵션 잔고평가손익내역 |
CTFO6119R | 선물옵션 기간약정수수료 일별 |
CTRP6550R | 선물옵션 총자산현황(예수금) |
CTFO5139R | 선물옵션 기준일별 체결내역 |
CTFN7107R | (야간) 선물옵션 증거금 상세 |
실시간 WebSocket 항목
실시간은 domestic_futureoption_functions_ws.py에 정의돼 있고,
tr_key에 종목코드를 넣어 구독합니다.
접속·등록 절차 자체는
KIS API 웹소켓 실시간 시세와 같습니다.
tr_id | 내용 | tr_key 예시 |
|---|---|---|
H0IFCNT0 | 지수선물 실시간 체결 | 101S12 |
H0IFASP0 | 지수선물 실시간 호가 | 101S12 |
H0IOCNT0 | 지수옵션 실시간 체결 | 201S11305 |
H0IOASP0 | 지수옵션 실시간 호가 | 201S11305 |
H0IFCNI0 | 선물옵션 실시간 체결통보(내 주문) | HTS ID |
H0ZFANC0 | 선물 실시간 예상체결 | 종목코드 |
H0CFCNT0 / H0CFASP0 | 상품선물 실시간 체결 / 호가 | 종목코드 |
H0IFCNI0만 성격이 다릅니다. 종목이 아니라 내 계좌에서 벌어진 주문 사건을 받는 통로라
tr_key에 종목코드가 아니라 HTS ID를 넣습니다. 체결 확인을 폴링으로 돌리는 대신
이걸 구독하면 호출 수를 크게 줄일 수 있습니다.
선물옵션은 코드 문제가 아니라 리스크 문제다
여기까지가 API 문제입니다. 그런데 국내 선물옵션 자동매매에서 실제로 계좌를 위험하게 만드는 건 필드명이 아니라 레버리지입니다. 코스피200 선물은 계약당 명목 금액이 크고, 옵션 매도는 손실이 한쪽으로 열려 있습니다. 주식 봇에서 쓰던 수량 계산을 그대로 옮기면 같은 "1"이라는 숫자가 전혀 다른 크기의 베팅이 됩니다.
- 수량은 계약 수가 아니라 명목 금액으로 계산하십시오. 포지션 사이징 4가지에 계산 방식을 정리해 두었습니다.
- 증거금 부족은 주문 거절이 아니라 반대매매로 옵니다.
CTFO6118R의 증거금 관련 값을 주기적으로 감시하고, 임계치에서 신규 진입을 멈추십시오. - 킬 스위치를 먼저 만들고 전략을 나중에 붙이십시오. 일일 손실 한도에 닿으면 신규 주문을 막고 사람에게 알리는 장치가 전략보다 먼저입니다.
자주 묻는 것
선물옵션 API도 앱키를 새로 발급받아야 하나요?
appkey·appsecret은 계정 단위라서 새로 발급받을 필요가 없습니다.
같은 access_token으로 주식과 선물옵션을 모두 호출합니다. 바뀌는 것은
tr_id와 엔드포인트, 그리고 ACNT_PRDT_CD입니다.
다만 선물옵션 거래가 가능한 계좌가 있어야 하고, 이건 API가 아니라 증권사 계좌 요건입니다.
주문 결과에서 주문번호는 어디에 있나요?
응답 output의 ODNO입니다. 정정취소할 때 이 값을 ORGN_ODNO에 넣습니다.
주문번호를 저장하지 않으면 취소할 방법이 없으므로, 주문을 던지는 즉시 파일이나 DB에 남기십시오.
증권사 중 어디가 선물옵션 API를 지원하나요?
증권사마다 지원 범위와 방식이 다릅니다. 선물옵션 지원 여부와 조건은 공지로 바뀌므로 반드시 각 사 개발자 포털에서 현재 상태를 확인하십시오.
2026-08-30 기준으로 한국투자증권 공식 저장소
koreainvestment/open-trading-api의
examples_user/domestic_futureoption/domestic_futureoption_functions.py 및
domestic_futureoption_functions_ws.py에 정의된
엔드포인트·tr_id·파라미터 설명을 기준으로 작성했습니다.
본문의 코드는 구조를 보여주기 위한 예시이며 그대로 실전에 쓰라는 뜻이 아닙니다.
TR ID·필드 구성·모의투자 지원 범위·운영시간은 증권사 공지로 예고 없이 바뀝니다.
운영 전 KIS Developers 포털의 현재 명세로 대조하십시오.
이 글은 수익이나 시장 방향을 예측하지 않으며 투자 권유가 아닙니다.
선물옵션 봇, 안전장치까지 같이 만들어 드립니다
코스피200 선물·옵션 주문 연동부터 증거금 감시, 반대매매 방지 킬 스위치, 체결통보 알림까지 묶어서 실제로 돌아가는 자동매매 프로그램으로 만들어 드립니다. 24시간 빠른 답변 가능합니다.
무료 상담 시작하기