지난 포스트에서 업비트에서 거래 가능한 전 종목을 매수하는 올라운더를 위한 업비트 모든 종목 매수 프로그램을 만들어 보았는데요.
이번 시간에는 시장이 심상치 않을때 보유하고 있는 모든 종목을 빠른 시간안에 정리하고 싶은 경우 사용할 수 있는 전체 보유종목을 시장가로 매도하는 프로그램을 만들어 보려고 합니다.
올라운더를 위한 모든 종목 매수 프로그램은 아래 포스트를 참고 부탁 드립니다.
2021.07.17 - [프로젝트/비트코인 자동매매] - 올라운더를 위한 업비트 모든 종목 매수 프로그램 - 파이썬 업비트 비트코인 자동매매
자동매매 프로그램은 수익을 보장하는 프로그램이 아니며 시장 상황에 따라 상당히 큰 손실을 보게 되실 수도 있으니 가급적 처음에는 연습용으로 시도해 보시기 바랍니다.
본 블로그는 가상화폐 매수/매도에 관련하여 일체 관여하지 않으며 모든 책임은 본인에게 있음을 안내 드립니다.
목차 - 클릭하면 이동합니다.
보유 종목 전체 매도 프로그램 작성 플랜
보유 종목 조회
보유 종목을 전부 매도하기 위해서는 먼저 매도를 하기 위한 보유 종목 리스트를 가져와야 합니다. 이전 포스트에서 다루었던 잔고 정보 조회 로직을 활용하면 보유 종목을 쉽게 가져올 수 있습니다.
2021.06.24 - [프로젝트/비트코인 자동매매] - 잔고 정보 조회 - 파이썬 업비트 비트코인 자동매매
시장가 매도
매도할 종목 리스트를 가져왔다면 하나씩 반복하면서 시장가 매도를 진행하면 됩니다. 지정가 매도는 체결에 시간이 소요될 수 있기 때문에 빠르게 매도처리를 하기 위해서는 시장가 매도를 진행하는 것이 좋습니다.
시장가 매도 로직은 아래 포스트를 참고하면 쉽게 적용할 수 있습니다.
2021.06.09 - [프로젝트/비트코인 자동매매] - 파이썬 업비트 비트코인 자동매매 - 시장가 매도 로직
전체 코드
공통코드
import time
import logging
import requests
import jwt
import uuid
import hashlib
import math
import os
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_accounts
# - Desc : 잔고정보 조회
# - Input
# 1) except_yn : KRW 및 소액 제외
# 2) market_code : 마켓코드 추가(매도시 필요)
# - Output
# 1) 잔고 정보
# -----------------------------------------------------------------------------
# 계좌 조회
def get_accounts(except_yn, market_code):
try:
rtn_data = []
# 소액 제외 기준
min_price = 5000
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)
account_data = res.json()
for account_data_for in account_data:
# KRW 및 소액 제외
if except_yn == "Y" or except_yn == "y":
if account_data_for['currency'] != "KRW" and Decimal(str(account_data_for['avg_buy_price'])) * (Decimal(str(account_data_for['balance'])) + Decimal(str(account_data_for['locked']))) >= Decimal(str(min_price)):
rtn_data.append(
{'market': market_code + '-' + account_data_for['currency'], 'balance': account_data_for['balance'],
'locked': account_data_for['locked'],
'avg_buy_price': account_data_for['avg_buy_price'],
'avg_buy_price_modified': account_data_for['avg_buy_price_modified']})
else:
rtn_data.append(
{'market': market_code + '-' + account_data_for['currency'], 'balance': account_data_for['balance'],
'locked': account_data_for['locked'],
'avg_buy_price': account_data_for['avg_buy_price'],
'avg_buy_price_modified': account_data_for['avg_buy_price_modified']})
return rtn_data
# ----------------------------------------
# Exception Raise
# ----------------------------------------
except Exception:
raise
# -----------------------------------------------------------------------------
# - Name : sellcoin_mp
# - Desc : 시장가 매도
# - Input
# 1) target_item : 대상종목
# 2) cancel_yn : 기존 주문 취소 여부
# - Output
# 1) rtn_data : 매도결과
# -----------------------------------------------------------------------------
# 시장가 매도
def sellcoin_mp(target_item, cancel_yn):
try:
if cancel_yn == 'Y':
# 기존 주문이 있으면 취소
cancel_order(target_item, "SELL")
# 잔고 조회
cur_balance = get_balance(target_item)
query = {
'market': target_item,
'side': 'ask',
'volume': cur_balance,
'ord_type': 'market',
}
query_string = urlencode(query).encode()
m = hashlib.sha512()
m.update(query_string)
query_hash = m.hexdigest()
payload = {
'access_key': access_key,
'nonce': str(uuid.uuid4()),
'query_hash': query_hash,
'query_hash_alg': 'SHA512',
}
jwt_token = jwt.encode(payload, secret_key)
authorize_token = 'Bearer {}'.format(jwt_token)
headers = {"Authorization": authorize_token}
res = send_request("POST", server_url + "/v1/orders", query, headers)
rtn_data = res.json()
logging.info("")
logging.info("----------------------------------------------")
logging.info("시장가 매도 완료!")
logging.info(rtn_data)
logging.info("----------------------------------------------")
return rtn_data
# ----------------------------------------
# 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
14~15 라인에 업비트에서 발급받은 Access Key와 Secret Key를 넣어 줍니다. 업비트 Key 발급 방법은 아래 포스트를 참고하시면 됩니다.
2021.06.09 - [프로젝트/비트코인 자동매매] - 업비트 자동매매 API KEY 발급받기
공통모듈을 어떻게 어느 파일에 작성해야 하는지 모르는 분들은 아래의 '프로그램 구조 만들기' 포스트를 참고하시면 좋을 것 같습니다.
2021.06.06 - [프로젝트/비트코인 자동매매] - 비트코인 자동매매 - 프로젝트 구조 만들기
보유 종목 전체 매도 프로그램
import os
import sys
import logging
import math
import traceback
# 공통 모듈 Import
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
from lib import upbit as upbit # noqa
# -----------------------------------------------------------------------------
# - Name : main
# - Desc : 메인
# -----------------------------------------------------------------------------
if __name__ == '__main__':
# noinspection PyBroadException
try:
print("***** USAGE ******")
print("[1] 로그레벨(D:DEBUG, E:ERROR, 그외:INFO)")
# 로그레벨(D:DEBUG, E:ERROR, 그외:INFO)
upbit.set_loglevel('I')
# ---------------------------------------------------------------------
# Logic Start!
# ---------------------------------------------------------------------
# 보유 종목 리스트 조회
item_list = upbit.get_accounts('Y', 'KRW')
logging.info(len(item_list))
logging.info(item_list)
# 종목별 처리
for item_list_for in item_list:
logging.info('종목코드:' + item_list_for['market'])
# 시장가 매도
# ★ 실제 매도가 될 수 있어 아래 주석 처리함.
# 실제 매도를 원하는 경우 아래 주석을 해제하면 됩니다.
#upbit.sellcoin_mp(item_list_for['market'], 'Y')
logging.info('전 종목 매도 완료')
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)
① 종목 리스트 조회
item_list = upbit.get_accounts('Y', 'KRW')
보유 종목 전체 리스트 조회 합니다.
첫번째 변수에는 원화 잔고 또는 에어드랍 등으로 받아서 5000원 미만의 소량 보유 종목을 제외하기 위해 Y를 입력하고 두번째 변수에는 마켓 정보를 입력합니다. 우리는 원화 마켓에서 매도를 할 예정이기 때문에 KRW를 입력했습니다.
② 종목별 시장가 매도 처리
for item_list_for in item_list:
logging.info('종목코드:' + item_list_for['market'])
# 시장가 매도
upbit.sellcoin_mp(item_list_for['market'], 'Y')
첫번째 변수에 매도할 종목을 입력하고 두번째 변수에는 혹시 미체결 주문이 있는 경우 먼저 미체결 주문을 취소하고 전량 매도처리 할 수 있도록 Y를 입력합니다.
지금까지 살펴본 로직을 통해 시장이 급락할 것 같은 경우 보유 종목을 1~2분안에 모두 정리하는데 활용하실 수 있을 것 같습니다.
자동매매 프로그램은 수익을 보장하는 프로그램이 아닙니다. 게다가 실수로 코딩을 하는 경우는 상당한 손실을 초래할 수 있으니 프로그램 실행전에 반드시 많은 검토를 진행하시기 바라며 중요 매수/매도 로직은 주석처리한 채로 이전 로직이 잘 작동하는지 확인 후 최종 실행하는 것이 좋습니다.
본 블로그는 가상화폐 매매에 전혀 관여하지 않습니다. 모든 투자 및 실행의 책임은 본인 스스로에게 있습니다.
'프로젝트 > 비트코인 자동매매' 카테고리의 다른 글
MACD 지표 구하기 - 파이썬 업비트 비트코인 자동매매 (4) | 2021.08.03 |
---|---|
MFI 자금흐름지수 구하기 - 파이썬 업비트 비트코인 자동매매 (7) | 2021.07.29 |
RSI 상대강도지수 구하기 - 파이썬 업비트 비트코인 자동매매 (17) | 2021.07.27 |
올라운더를 위한 업비트 모든 종목 매수 프로그램 - 파이썬 업비트 비트코인 자동매매 (7) | 2021.07.17 |
캔들 조회 로직 - 파이썬 업비트 비트코인 자동매매 (8) | 2021.07.15 |
지정가 매수 로직 - 파이썬 업비트 비트코인 자동매매 (2) | 2021.07.15 |