파이썬으로 업비트 API 연동하기 — 시세 조회부터 주문까지 기초

🌐 English

코인 자동매매를 만들어 보려고 검색하면 대부분 "전략"부터 이야기합니다. 그런데 막상 코드를 짜려고 하면 거래소에 요청을 어떻게 보내는지에서 먼저 막힙니다. 이 글은 그 첫 관문만 다룹니다. 파이썬으로 업비트 API에 붙어서 시세를 읽고, 잔고를 확인하고, 주문을 넣는 데까지입니다.

0. 준비물

필요한 건 두 가지뿐입니다.

  • 파이썬 패키지: pip install requests pyjwt — requests는 HTTP 요청 라이브러리, PyJWT는 인증 토큰을 만드는 라이브러리입니다.
  • 업비트 API 키: 업비트 웹사이트 → 마이페이지 → Open API 관리에서 발급합니다. 발급할 때 허용 IP를 반드시 등록하고, 처음에는 자산조회·주문조회 권한만 켜세요. 주문 권한은 코드가 검증된 뒤에 켜도 늦지 않습니다.

발급받은 secret key는 발급 화면을 벗어나면 다시 볼 수 없습니다. 그리고 절대 소스코드에 직접 적지 마세요. 환경변수나 설정 파일로 분리합니다.

import os

ACCESS_KEY = os.environ["UPBIT_ACCESS_KEY"]   # 실제 키는 환경변수로

SECRET_KEY = os.environ["UPBIT_SECRET_KEY"]

SERVER_URL = "https://api.upbit.com"

1. 시세 조회 — 인증이 필요 없는 요청

가장 쉬운 것부터 합니다. 현재가 조회는 인증 없이 됩니다. 그냥 GET 요청 하나면 끝입니다.

import requests

def get_price(market="KRW-BTC"):

    url = f"{SERVER_URL}/v1/ticker"

    res = requests.get(url, params={"markets": market}, timeout=5)

    res.raise_for_status()          # 4xx/5xx면 예외 발생

    data = res.json()[0]

    return {

        "market":  data["market"],

        "price":   data["trade_price"],      # 현재가

        "change":  data["signed_change_rate"] * 100,  # 전일 대비 %

    }

print(get_price("KRW-BTC"))

# {'market': 'KRW-BTC', 'price': 95000000.0, 'change': 1.23}

여기서 market"결제통화-대상코인" 형식입니다. 원화로 비트코인을 사면 KRW-BTC, 원화로 이더리움이면 KRW-ETH입니다. 순서를 반대로 쓰는 실수가 은근히 잦습니다.

여러 종목을 한 번에 볼 수도 있습니다. markets 파라미터에 쉼표로 이어 붙이면 됩니다. 종목 20개를 20번 호출하지 말고 한 번에 묶으세요. 호출 횟수 제한을 아끼는 가장 쉬운 방법입니다.

2. 인증 요청 — JWT 토큰 만들기

잔고 조회부터는 "내가 누구인지" 증명해야 합니다. 업비트는 JWT라는 방식을 씁니다. 쉽게 말하면 내 access key와 일회용 번호를 secret key로 서명한 문자열을 헤더에 담아 보내는 것입니다.

규칙은 두 가지입니다.

  • 파라미터가 없는 요청: access_key와 nonce(중복 방지용 임의 문자열)만 서명합니다.
  • 파라미터가 있는 요청: 쿼리 문자열을 SHA512로 해시해서 query_hash로 함께 서명합니다.
import jwt, uuid, hashlib

from urllib.parse import urlencode

def make_token(params=None):

    payload = {

        "access_key": ACCESS_KEY,

        "nonce": str(uuid.uuid4()),   # 매 요청마다 새 값

    }

    if params:

        query = urlencode(params)

        payload["query_hash"] = hashlib.sha512(query.encode()).hexdigest()

        payload["query_hash_alg"] = "SHA512"

    token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")

    return {"Authorization": f"Bearer {token}"}

주의할 점 하나. query_hash를 만들 때 쓴 쿼리 문자열과 실제로 보내는 쿼리 문자열이 글자 하나까지 같아야 합니다. 순서가 바뀌거나 인코딩이 달라지면 서버는 서명이 틀렸다고 판단합니다. 그래서 위 코드처럼 urlencode(params)로 만든 값을 그대로 재사용하는 게 안전합니다.

3. 잔고 조회

이제 토큰이 있으니 내 계좌를 볼 수 있습니다. 파라미터가 없는 요청이라 가장 단순합니다.

def get_balances():

    res = requests.get(f"{SERVER_URL}/v1/accounts",

                       headers=make_token(), timeout=5)

    res.raise_for_status()

    return res.json()

for acc in get_balances():

    print(acc["currency"], acc["balance"], acc["avg_buy_price"])

# KRW 150000.0 0

# BTC 0.00123 94000000

여기까지 성공했다면 인증이 제대로 붙은 겁니다. 만약 401이 뜬다면 십중팔구 허용 IP 미등록이거나 키 앞뒤 공백입니다. 설정 파일에서 키를 읽을 때 .strip() 한 번 해주세요.

4. 주문 넣기

주문은 POST에 파라미터가 붙으므로 query_hash가 필요합니다. 업비트 주문은 세 가지 조합을 기억하면 됩니다.

  • 지정가: ord_type="limit" + price + volume
  • 시장가 매수: ord_type="price" + price(살 금액) — volume 없음
  • 시장가 매도: ord_type="market" + volume(팔 수량) — price 없음

시장가 매수에서 price가 "수량"이 아니라 "쓸 원화 금액"이라는 점이 가장 헷갈리는 부분입니다.

def buy_market(market, krw_amount):

    """시장가 매수 — krw_amount 만큼의 원화로 산다"""

    params = {

        "market":   market,

        "side":     "bid",        # bid=매수, ask=매도

        "ord_type": "price",

        "price":    str(krw_amount),

    }

    headers = make_token(params)

    headers["Content-Type"] = "application/json"

    res = requests.post(f"{SERVER_URL}/v1/orders",

                        json=params, headers=headers, timeout=5)

    if res.status_code >= 400:

        print("주문 실패:", res.status_code, res.text)

        return None

    return res.json()

# order = buy_market("KRW-BTC", 6000)   # 6,000원어치 매수

위 코드에서 query_hashurlencode(params)로 만들었는데 실제 전송은 JSON body입니다. 업비트는 이 조합을 허용합니다. 다만 params의 키·값이 완전히 동일해야 하므로, 딕셔너리를 만든 뒤에는 손대지 마세요.

5. 실전에서 걸리는 것들

  • 최소 주문 금액: 원화 마켓은 5,000원 이상이어야 주문이 들어갑니다. 테스트할 때 1,000원으로 넣고 "왜 안 되지" 하는 경우가 많습니다.
  • 호출 횟수 제한: 초당 요청 수에 제한이 있습니다. 넘기면 429가 돌아옵니다. 반복문에 time.sleep(0.1) 정도는 기본으로 넣고 시작하세요.
  • 수량 소수점: 코인 수량은 소수점 8자리까지입니다. 계산 결과를 그대로 넣으면 부동소수점 오차로 거부될 수 있어 round(qty, 8)로 다듬습니다.
  • 주문 즉시 체결이 아니다: 주문 API가 200을 돌려줘도 그건 "접수됨"입니다. 실제 체결은 /v1/order?uuid=...로 다시 확인해야 합니다.
  • 읽기 전용으로 먼저: 주문 함수를 처음 붙였을 때는 실제 호출 대신 print만 하는 모의 모드를 두는 걸 권합니다. 로직 버그는 대부분 첫 주 안에 드러납니다.

정리

순서만 다시 짚으면 키 발급 → 시세 조회(인증 없음) → JWT 토큰 → 잔고 조회 → 주문입니다. 각 단계가 확인되기 전에는 다음으로 넘어가지 마세요. 특히 잔고 조회가 되기 전에 주문 코드를 짜는 건 시간 낭비입니다. 인증이 안 되는 것인지 주문 파라미터가 틀린 것인지 구분이 안 되니까요.

여기까지가 자동매매의 "손발"입니다. 두뇌에 해당하는 전략은 그다음 문제고, 사실 손발이 튼튼한 쪽이 훨씬 오래 갑니다.

이 글은 교육·정보 목적이며 투자 권유가 아닙니다. 암호화폐 자동매매에는 원금 손실 위험이 있으며, API 키 관리 부주의로 인한 자산 손실 가능성도 있습니다. 모든 판단과 책임은 본인에게 있습니다.

댓글

이 블로그의 인기 게시물

한국투자증권 KIS API로 실시간 시세 받기 (WebSocket 실전)

Go로 자동매매 신호봇 프레임워크 설계하기