이번 시간에는 매도 로직에 사용할 수 있는 주문가능 잔고를 조회하는 기능을 살펴 보겠습니다.
파이썬을 이용해서 비트코인 자동매매 프로그램을 만들때 공통으로 사용하는 기능은 공통 모듈에 작성하면 필요할 때 간편하게 사용할 수 있습니다. 공통 모듈을 만드는 방법은 아래 포스트를 참고 부탁 드립니다.
2021.06.06 - [프로젝트/비트코인 자동매매] - 비트코인 자동매매 - 프로젝트 구조 만들기
목차 - 클릭하면 이동합니다.
잔고 조회
프로그램을 이용해서 매도를 하려면 보유하고 있는 종목의 수량을 알고 있어야 합니다.
업비트에는 원화로 거래할 수 있는 KRW마켓, 비트코인(BTC)로 거래할 수 있는 BTC마켓 그리고 USDT로 거래할 수 있는 마켓이 있는데요. 잔고의 개념은 마켓과는 무관합니다.
예를들어 원화 마켓에서 도지코인(DOGE)을 구매하려면 KRW-DOGE라는 티커 코드를 사용하게 되고 BTC 마켓에서 구매하려면 BTC-DOGE라는 티커 코드를 사용합니다.
원화 마켓에서 100 DOGE를 구매하고 BTC마켓에서 100 DOGE를 구매했다고 가정하는 경우 잔고를 조회하면 마켓 구분없이 200 DOGE가 조회 됩니다. 잔고 조회 시 코드는 앞에 마켓이 없이 그냥 DOGE로 조회 됩니다.
그래서 잔고를 조회하려면 티커에서 마켓코드를 빼고 조회해야 합니다.
잔고 조회 로직
# -----------------------------------------------------------------------------
# - Name : get_balance
# - Desc : 주문가능 잔고 조회
# - Input
# 1) target_item : 대상 종목
# - Output
# 2) rtn_balance : 주문가능 잔고
# -----------------------------------------------------------------------------
def get_balance(target_item):
try:
# 주문가능 잔고 리턴용
rtn_balance = 0
# 최대 재시도 횟수
max_cnt = 0
# 잔고가 조회 될 때까지 반복
while True:
# 조회 회수 증가
max_cnt = max_cnt + 1
payload = {
'access_key': access_key,
'nonce': str(uuid.uuid4()),
}
jwt_token = jwt.encode(payload, secret_key)
authorize_token = 'Bearer {}'.format(jwt_token)
headers = {"Authorization": authorize_token}
res = send_request("GET", server_url + "/v1/accounts", "", headers)
my_asset = res.json()
# 해당 종목에 대한 잔고 조회
# 잔고는 마켓에 상관없이 전체 잔고가 조회됨
for myasset_for in my_asset:
if myasset_for['currency'] == target_item.split('-')[1]:
rtn_balance = myasset_for['balance']
# 잔고가 0 이상일때까지 반복
if Decimal(str(rtn_balance)) > Decimal(str(0)):
break
# 최대 100회 수행
if max_cnt > 100:
break
logging.info("[주문가능 잔고 리턴용] 요청 재처리중...")
return rtn_balance
# ----------------------------------------
# Exception Raise
# ----------------------------------------
except Exception:
raise
종목코드를 조회하거나 매수/매도를 하기 위해서는 KRW-DOGE와 같이 앞에 마켓 코드를 붙여야 하기 때문에 잔고조회 로직에도 마켓 코드를 붙인 종목코드를 입력 받도록 했습니다.
잔고를 조회한 후에는 target_item.split('-')[1]을 이용하여 마켓 코드를 제거하고 비교하여 원하는 종목의 잔고만 리턴하게 됩니다.
간혹 매수를 하자마자 잔고를 조회하면 반영 시간이 모자라 잔고가 조회되지 않는 경우가 있어 잔고가 조회 될 때까지 반복하는 로직이 추가되어 있습니다.
하지만 무한 루프에 빠질 수 있기 때문에 총 조회는 100번까지만 수행하도록 했습니다.
최종 코드
공통모듈(upbit.py)
import time
import logging
import requests
import jwt
import uuid
import hashlib
from urllib.parse import urlencode
from decimal import Decimal
# Keys
access_key = '업비트에서 발급받은 Access Key'
secret_key = '업비트에서 발급반은 Secret Key'
server_url = 'https://api.upbit.com'
# -----------------------------------------------------------------------------
# - Name : set_loglevel
# - Desc : 로그레벨 설정
# - Input
# 1) level : 로그레벨
# 1. D(d) : DEBUG
# 2. E(e) : ERROR
# 3. 그외(기본) : INFO
# - Output
# -----------------------------------------------------------------------------
def set_loglevel(level):
try:
# ---------------------------------------------------------------------
# 로그레벨 : DEBUG
# ---------------------------------------------------------------------
if level.upper() == "D":
logging.basicConfig(
format='[%(asctime)s][%(levelname)s][%(filename)s:%(lineno)d]:%(message)s',
datefmt='%Y/%m/%d %I:%M:%S %p',
level=logging.DEBUG
)
# ---------------------------------------------------------------------
# 로그레벨 : ERROR
# ---------------------------------------------------------------------
elif level.upper() == "E":
logging.basicConfig(
format='[%(asctime)s][%(levelname)s][%(filename)s:%(lineno)d]:%(message)s',
datefmt='%Y/%m/%d %I:%M:%S %p',
level=logging.ERROR
)
# ---------------------------------------------------------------------
# 로그레벨 : INFO
# ---------------------------------------------------------------------
else:
# -----------------------------------------------------------------------------
# 로깅 설정
# 로그레벨(DEBUG, INFO, WARNING, ERROR, CRITICAL)
# -----------------------------------------------------------------------------
logging.basicConfig(
format='[%(asctime)s][%(levelname)s][%(filename)s:%(lineno)d]:%(message)s',
datefmt='%Y/%m/%d %I:%M:%S %p',
level=logging.INFO
)
# ----------------------------------------
# Exception Raise
# ----------------------------------------
except Exception:
raise
# -----------------------------------------------------------------------------
# - Name : send_request
# - Desc : 리퀘스트 처리
# - Input
# 1) reqType : 요청 타입
# 2) reqUrl : 요청 URL
# 3) reqParam : 요청 파라메타
# 4) reqHeader : 요청 헤더
# - Output
# 4) reponse : 응답 데이터
# -----------------------------------------------------------------------------
def send_request(reqType, reqUrl, reqParam, reqHeader):
try:
# 요청 가능회수 확보를 위해 기다리는 시간(초)
err_sleep_time = 0.3
# 요청에 대한 응답을 받을 때까지 반복 수행
while True:
# 요청 처리
response = requests.request(reqType, reqUrl, params=reqParam, headers=reqHeader)
# 요청 가능회수 추출
if 'Remaining-Req' in response.headers:
hearder_info = response.headers['Remaining-Req']
start_idx = hearder_info.find("sec=")
end_idx = len(hearder_info)
remain_sec = hearder_info[int(start_idx):int(end_idx)].replace('sec=', '')
else:
logging.error("헤더 정보 이상")
logging.error(response.headers)
break
# 요청 가능회수가 3개 미만이면 요청 가능회수 확보를 위해 일정시간 대기
if int(remain_sec) < 3:
logging.debug("요청 가능회수 한도 도달! 남은횟수:" + str(remain_sec))
time.sleep(err_sleep_time)
# 정상 응답
if response.status_code == 200 or response.status_code == 201:
break
# 요청 가능회수 초과인 경우
elif response.status_code == 429:
logging.error("요청 가능회수 초과!:" + str(response.status_code))
time.sleep(err_sleep_time)
# 그 외 오류
else:
logging.error("기타 에러:" + str(response.status_code))
logging.error(response.status_code)
break
# 요청 가능회수 초과 에러 발생시에는 다시 요청
logging.info("[restRequest] 요청 재처리중...")
return response
# ----------------------------------------
# Exception Raise
# ----------------------------------------
except Exception:
raise
# -----------------------------------------------------------------------------
# - Name : get_balance
# - Desc : 주문가능 잔고 조회
# - Input
# 1) target_item : 대상 종목
# - Output
# 2) rtn_balance : 주문가능 잔고
# -----------------------------------------------------------------------------
def get_balance(target_item):
try:
# 주문가능 잔고 리턴용
rtn_balance = 0
# 최대 재시도 횟수
max_cnt = 0
# 잔고가 조회 될 때까지 반복
while True:
# 조회 회수 증가
max_cnt = max_cnt + 1
payload = {
'access_key': access_key,
'nonce': str(uuid.uuid4()),
}
jwt_token = jwt.encode(payload, secret_key)
authorize_token = 'Bearer {}'.format(jwt_token)
headers = {"Authorization": authorize_token}
res = send_request("GET", server_url + "/v1/accounts", "", headers)
my_asset = res.json()
# 해당 종목에 대한 잔고 조회
# 잔고는 마켓에 상관없이 전체 잔고가 조회됨
for myasset_for in my_asset:
if myasset_for['currency'] == target_item.split('-')[1]:
rtn_balance = myasset_for['balance']
# 잔고가 0 이상일때까지 반복
if Decimal(str(rtn_balance)) > Decimal(str(0)):
break
# 최대 100회 수행
if max_cnt > 100:
break
logging.info("[주문가능 잔고 리턴용] 요청 재처리중...")
return rtn_balance
# ----------------------------------------
# Exception Raise
# ----------------------------------------
except Exception:
raise
모듈 호출하기
import sys
import logging
import traceback
import time
from module import upbit
from decimal import Decimal
# -----------------------------------------------------------------------------
# - Name : main
# - Desc : 메인
# -----------------------------------------------------------------------------
if __name__ == '__main__':
# noinspection PyBroadException
try:
# 로그레벨 설정(DEBUG)
upbit.set_loglevel('I')
# 주문가능 잔고 조회
balance = upbit.get_balance('KRW-DOGE')
# 잔고 출력
logging.info(balance)
except KeyboardInterrupt:
logging.error("KeyboardInterrupt Exception 발생!")
logging.error(traceback.format_exc())
sys.exit(1)
except Exception:
logging.error("Exception 발생!")
logging.error(traceback.format_exc())
sys.exit(1)
실행 결과
프로그램을 수행하면 잔고가 조회 됩니다.
'프로젝트 > 비트코인 자동매매' 카테고리의 다른 글
잔고 정보 조회 - 파이썬 업비트 비트코인 자동매매 (2) | 2021.06.24 |
---|---|
호가 계산하기 - 파이썬 업비트 비트코인 자동매매 (3) | 2021.06.17 |
지정가 매도 로직 - 파이썬 업비트 비트코인 자동매매 (5) | 2021.06.17 |
파이썬 업비트 비트코인 자동매매 - 시장가 매도 로직 (0) | 2021.06.09 |
파이썬 업비트 비트코인 자동매매 - 시장가 매수 로직 (5) | 2021.06.09 |
업비트 자동매매 API KEY 발급받기 (1) | 2021.06.09 |