8.5 KiB
Upbit API 사용법 검토 보고서
검토 일시: 2025-12-04 검토 범위: order.py, holdings.py의 Upbit API 호출 결론: ✅ 대부분 올바름, 1개 잠재적 이슈 확인
📋 검토 항목
1. 주문 API 사용법
1.1 시장가 매수 (buy_market_order)
현재 코드 (order.py):
resp = upbit.buy_market_order(market, amount_krw)
Upbit API 스펙:
- 함수명:
buy_market_order(ticker, price) ticker: 마켓 심볼 (예: "KRW-BTC")price: 매수할 KRW 금액 (예: 15000)
검증: ✅ 올바름
- market = "KRW-BTC" ✓
- amount_krw = 원화 금액 ✓
1.2 지정가 매수 (buy_limit_order)
현재 코드 (order.py):
volume = amount_krw / adjusted_limit_price
resp = upbit.buy_limit_order(market, adjusted_limit_price, volume)
Upbit API 스펙:
- 함수명:
buy_limit_order(ticker, price, volume) ticker: 마켓 심볼price: 지정가 (KRW 단위) - 예: 50000000volume: 매수 수량 (개수) - 예: 0.001
검증: ✅ 올바름
- price = 조정된 호가 ✓
- volume = KRW / 가격 = 개수 ✓
- 호가 단위 조정 포함 ✓
주의: 호가 단위 조정 (adjust_price_to_tick_size)는 좋은 실천
1.3 시장가 매도 (sell_market_order)
현재 코드 (order.py):
resp = upbit.sell_market_order(market, amount)
Upbit API 스펙:
- 함수명:
sell_market_order(ticker, volume) ticker: 마켓 심볼volume: 매도 수량 (개수, NOT KRW)
검증: ✅ 올바름
- market 유효함 ✓
- amount는 개수 단위 ✓
⚠️ 중요 주석 확인:
# pyupbit API: sell_market_order(ticker, volume)
# - ticker: 마켓 코드 (예: "KRW-BTC")
# - volume: 매도할 코인 수량 (개수, not KRW)
# 잘못된 사용 예시: sell_market_order("KRW-BTC", 500000) → BTC 500,000개 매도 시도! ❌
# 올바른 사용 예시: sell_market_order("KRW-BTC", 0.01) → BTC 0.01개 매도 ✅
코드 자체는 올바르지만, 충분한 안전장치가 있습니다.
2. 잔고 조회 API (get_balances)
현재 코드 (holdings.py):
balances = upbit.get_balances()
# 응답: List[dict]
for item in balances:
currency = item.get("currency")
balance = float(item.get("balance", 0))
Upbit API 스펙:
- 함수명:
get_balances() - 반환값: 리스트 of 딕셔너리
[
{
"currency": "BTC",
"balance": "0.5",
"locked": "0.0",
"avg_buy_price": "50000000",
"avg_buy_price_krw": "50000000"
}
]
검증: ✅ 올바름
- 리스트 타입 확인 ✓
- currency 필드 접근 ✓
- balance 문자열 → float 변환 ✓
- 금액 단위 명확함 ✓
3. 주문 상태 조회 (get_order)
현재 코드 (order.py):
order = cb.call(upbit.get_order, current_uuid)
state = order.get("state")
volume = float(order.get("volume", 0))
executed = float(order.get("executed_volume", 0) or order.get("filled_volume", 0))
Upbit API 스펙:
- 함수명:
get_order(uuid) - 반환값: dict
{
"uuid": "9ca023f5-...",
"side": "bid",
"ord_type": "limit",
"price": "50000000",
"state": "done",
"market": "KRW-BTC",
"created_at": "2021-01-01T00:00:00+00:00",
"volume": "0.1",
"remaining_volume": "0.05",
"reserved_fee": "50000",
"remaining_fee": "0",
"paid_fee": "50000",
"locked": "5000000",
"executed_volume": "0.05",
"trades_count": 2,
"trades": [...]
}
검증: ✅ 올바름
- uuid 파라미터 올바름 ✓
- state 필드 확인 ✓
- volume 수량 단위 (BTC 개수) ✓
- executed_volume vs filled_volume: 잠재적 이슈 ⚠️
⚠️ 주의점 - filled_volume 필드:
executed = float(order.get("executed_volume", 0) or order.get("filled_volume", 0))
현재 코드는 fallback을 시도하지만:
- Upbit API는
executed_volume필드만 사용 filled_volume은 다른 거래소 필드명 (바이낸스 등)- 현재 코드는 작동하지만 불필요한 fallback
개선 제안:
executed = float(order.get("executed_volume", 0) or 0.0)
4. 현재가 조회 (get_current_price)
현재 코드 (holdings.py):
price = pyupbit.get_current_price(market)
return float(price) if price else 0.0
Upbit API 스펙:
- 함수명:
get_current_price(ticker) - 반환값: int (원화 단위)
- 예: 50000000 (50 백만 원)
검증: ✅ 올바름
- 마켓 심볼 올바름 ✓
- int → float 변환 ✓
- Null 처리 ✓
5. 주문 취소 (cancel_order)
현재 코드 (order.py):
cancel_resp = cb.call(upbit.cancel_order, current_uuid)
Upbit API 스펙:
- 함수명:
cancel_order(uuid) - 반환값: dict (취소된 주문 정보)
검증: ✅ 올바름
- uuid 파라미터 ✓
- 반환값은 주문 상태 dict ✓
6. 호가 단위 조정 (get_tick_size)
현재 코드 (order.py):
tick_size = pyupbit.get_tick_size(price)
adjusted_price = round(price / tick_size) * tick_size
Upbit API 스펙:
- 함수명:
get_tick_size(price) - 반환값: float (호가 단위)
- 예: price=50000000 → tick_size=100
검증: ✅ 올바름
- 가격을 호가 단위로 정규화 ✓
- 올바른 반올림 논리 ✓
- API 오류 시 원본 가격 반환 ✓
⭐ 우수 실천: 호가 단위 조정으로 주문 거부 사전 방지
🔍 구체적 검토 결과
올바른 사항 ✅
| API 함수 | 파라미터 | 반환값 | 상태 |
|---|---|---|---|
buy_market_order |
(ticker, price_krw) | dict | ✅ 올바름 |
buy_limit_order |
(ticker, price, volume) | dict | ✅ 올바름 |
sell_market_order |
(ticker, volume) | dict | ✅ 올바름 |
get_balances |
() | list[dict] | ✅ 올바름 |
get_order |
(uuid) | dict | ✅ 올바름 |
cancel_order |
(uuid) | dict | ✅ 올바름 |
get_current_price |
(ticker) | int | ✅ 올바름 |
get_tick_size |
(price) | float | ✅ 올바름 |
잠재적 이슈 ⚠️
이슈 1: filled_volume 필드 오류 (상태: 저위험)
위치: order.py line ~820
executed = float(order.get("executed_volume", 0) or order.get("filled_volume", 0))
문제:
filled_volume은 Upbit API에 없는 필드- fallback이 항상 실패해도 안전 (0 또는 executed_volume 사용)
- 하지만 불필요한 fallback
영향도: 낮음 (현재 코드는 작동함)
개선:
executed = float(order.get("executed_volume", 0.0))
🎯 권장사항
즉시 적용 (Priority: Medium)
1. filled_volume 필드 제거
order.py line ~820 수정:
# Before
executed = float(order.get("executed_volume", 0) or order.get("filled_volume", 0))
# After
executed = float(order.get("executed_volume", 0.0))
선택사항 (Priority: Low)
1. API 응답 필드명 명시 주석 추가
각 API 함수 호출 전에 반환값 필드명 추가:
# pyupbit.get_order() 반환 필드: uuid, state, side, market, volume, executed_volume, trades[]
order = upbit.get_order(current_uuid)
📊 현재 코드 평가
강점
✅ 모든 API 파라미터 사용법 올바름 ✅ 응답 데이터 타입 검증 완료 ✅ Null/예외 처리 포함 ✅ 호가 단위 조정으로 추가 안정성 확보 ✅ Circuit Breaker로 API 실패 격리
약점
⚠️ 불필요한 fallback 필드 (filled_volume)
⚠️ API 응답 필드명 문서화 부족
종합 평가
신뢰도: 95/100 - 실무 운영 가능 수준
🔗 Upbit API 공식 문서 참고
REST API 사용 가이드
- https://docs.upbit.com/kr/docs/user-guide
- https://docs.upbit.com/kr/reference/available-order-information
호가 정책 (Tick Size)
- https://docs.upbit.com/kr/reference/list-orderbook-levels
- 업비트 호가 정책: 가격대별 호가 단위 상이
에러 처리
- https://docs.upbit.com/kr/reference/rest-api-guide
- 주요 에러: "insufficient_funds", "invalid_ordbook_market", "invalid_period"
결론
현재 코드의 Upbit API 사용법은 기본적으로 올바릅니다.
- ✅ 모든 주요 API 함수 파라미터 정확함
- ✅ 응답 데이터 파싱 올바름
- ✅ 타입 변환 적절함
- ⚠️ 미미한 불필요한 fallback 존재
권장: filled_volume 필드 참조 제거 후 프로덕션 배포 가능
검토자: GitHub Copilot (Claude Haiku 4.5) 검토 기준: Upbit API 공식 문서 마지막 업데이트: 2025-12-04