프로젝트/비트코인 자동매매

올라운더를 위한 업비트 모든 종목 매도 프로그램 - 파이썬 업비트 비트코인 자동매매

Tech&Fin 2021. 7. 18. 07:50
반응형

지난 포스트에서 업비트에서 거래 가능한 전 종목을 매수하는 올라운더를 위한 업비트 모든 종목 매수 프로그램을 만들어 보았는데요.

 

이번 시간에는 시장이 심상치 않을때 보유하고 있는 모든 종목을 빠른 시간안에 정리하고 싶은 경우 사용할 수 있는 전체 보유종목을 시장가로 매도하는 프로그램을 만들어 보려고 합니다.

 

올라운더를 위한 모든 종목 매수 프로그램은 아래 포스트를 참고 부탁 드립니다.

 

2021.07.17 - [프로젝트/비트코인 자동매매] - 올라운더를 위한 업비트 모든 종목 매수 프로그램 - 파이썬 업비트 비트코인 자동매매

 

올라운더를 위한 업비트 모든 종목 매수 프로그램 - 파이썬 업비트 비트코인 자동매매

예전에 비트코인 관련 카페에서 업비트 원화마켓에서 거래되는 모든 종목을 매수하고 매일마다 수익률을 카페에 공유하던 분이 계셨는데요. 그 분은 이런 매수 방법을 올라운더라고 불렀던 것

technfin.tistory.com

 

자동매매 프로그램은 수익을 보장하는 프로그램이 아니며 시장 상황에 따라 상당히 큰 손실을 보게 되실 수도 있으니 가급적 처음에는 연습용으로 시도해 보시기 바랍니다.

본 블로그는 가상화폐 매수/매도에 관련하여 일체 관여하지 않으며 모든 책임은 본인에게 있음을 안내 드립니다.

 

 

목차 - 클릭하면 이동합니다.

     

    보유 종목 전체 매도 프로그램 작성 플랜

    보유 종목 조회

    보유 종목을 전부 매도하기 위해서는 먼저 매도를 하기 위한 보유 종목 리스트를 가져와야 합니다. 이전 포스트에서 다루었던 잔고 정보 조회 로직을 활용하면 보유 종목을 쉽게 가져올 수 있습니다.

     

    2021.06.24 - [프로젝트/비트코인 자동매매] - 잔고 정보 조회 - 파이썬 업비트 비트코인 자동매매

     

    잔고 정보 조회 - 파이썬 업비트 비트코인 자동매매

    자동 매도 로직을 구현하기 위해서는 현재 보유하고 있는 종목의 잔고 정보를 알아야 하는데요. 이번 시간에는 보유종목에 대해 잔고 정보를 조회하는 로직에 대해서 살펴 보겠습니다. 매도 로

    technfin.tistory.com

     

    시장가 매도

    매도할 종목 리스트를 가져왔다면 하나씩 반복하면서 시장가 매도를 진행하면 됩니다. 지정가 매도는 체결에 시간이 소요될 수 있기 때문에 빠르게 매도처리를 하기 위해서는 시장가 매도를 진행하는 것이 좋습니다.

     

    시장가 매도 로직은 아래 포스트를 참고하면 쉽게 적용할 수 있습니다.

     

    2021.06.09 - [프로젝트/비트코인 자동매매] - 파이썬 업비트 비트코인 자동매매 - 시장가 매도 로직

     

    파이썬 업비트 비트코인 자동매매 - 시장가 매도 로직

    지난 시간에 이어 이번에는 시장가 매도 로직에 대해서 살펴 보겠습니다. 공통모듈 만들기 및 시장가 매수 로직 관련은 아래 포스트를 참고 부탁 드립니다. 2021.06.06 - [프로젝트/비트코인 자동매

    technfin.tistory.com

     

    전체 코드

    공통코드

    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 발급받기

     

    업비트 자동매매 API KEY 발급받기

    이번 시간에는 업비트를 이용하여 코인 자동매매를 하기 위해 API KEY를 발급받는 방법에 대해서 살펴 보겠습니다. 앞으로 진행할 비트코인 자동매매 프로그램 관련에 대해서는 아래 포스트에서

    technfin.tistory.com

     

    공통모듈을 어떻게 어느 파일에 작성해야 하는지 모르는 분들은 아래의 '프로그램 구조 만들기' 포스트를 참고하시면 좋을 것 같습니다.

     

    2021.06.06 - [프로젝트/비트코인 자동매매] - 비트코인 자동매매 - 프로젝트 구조 만들기

     

    비트코인 자동매매 - 프로젝트 구조 만들기

    이번 시간에는 본격적으로 로직을 만들고 살을 붙이기 전에 프로젝트 구조를 만들어 보도록 하겠습니다. 비트코인 자동매매 프로그램을 만드는 것이 얼마나 간단한지는 아래 포

    technfin.tistory.com

     

    보유 종목 전체 매도 프로그램

    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분안에 모두 정리하는데 활용하실 수 있을 것 같습니다.

     

    자동매매 프로그램은 수익을 보장하는 프로그램이 아닙니다. 게다가 실수로 코딩을 하는 경우는 상당한 손실을 초래할 수 있으니 프로그램 실행전에 반드시 많은 검토를 진행하시기 바라며 중요 매수/매도 로직은 주석처리한 채로 이전 로직이 잘 작동하는지 확인 후 최종 실행하는 것이 좋습니다.

    본 블로그는 가상화폐 매매에 전혀 관여하지 않습니다. 모든 투자 및 실행의 책임은 본인 스스로에게 있습니다.
    반응형