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

비트코인 자동매매 프로그램 자주 발생하는 오류 및 대처 방법

Tech&Fin 2022. 3. 12. 15:09
반응형

처음 Tech&Fin 블로그를 시작했을 때보다 비교적 많은 분들이 블로그를 찾아주시고 올려드린 비트코인 자동매매 프로그램을 설치하여 사용중인 것 같습니다.

 

사용하시는 분이 많아질 수록 예상치 못한 오류가 발생하는 케이스도 많아지고 있는데요. 이번 시간에는 지금까지 댓글로 문의를 받았던 오류들을 해결하면서 알게된 내용을 공유드리는 시간을 가져 보려고 합니다.

 

 

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

     

     

    일반적인 오류 해결 방법

    어떤 프로그램이나 마찬가지겠지만 오류가 발생하는 원인은 한 가지가 아니라 매우 다양합니다. 또한 같은 메세지를 내 뱉는 오류라 할 지라도 오류의 원인은 다를 수 있습니다.

     

    그렇기 때문에 오류를 해결하는 방법이 한 가지일 수는 없으며 매우 다양한 케이스가 존재할 수 있습니다. 이럴 때 오류를 해결할 수 있는 가장 좋은 방법은 의심이 가는 곳에서 로그를 발생시켜 원인을 차근차근 분석 해 찾아내는 방법입니다.

     

    [2022/03/12 02:00:31 PM][ERROR][test_module.py:31]:Traceback (most recent call last):
      File "C:\Python-Project\trade_bot\test_module.py", line 21, in <module>
        accounts = upbit.get_accounts('Y', 'KRW')
      File "C:\Python-Project\trade_bot\module\upbit.py", line 785, in get_accounts
        if market_code + '-' + account_data_for['currency'] == market_item_list_for['market']:
    TypeError: string indices must be integers

    예를 들어 위와 같은 오류가 발생한 경우 실제 에러가 발생한 내용은 [TypeError: string indices must be integers] 입니다. 스트링의 인덱스가 정수가 아닌 경우에 발생하는 에러인데요. 그럼 왜 이런 에러가 발생했을까요?

     

    앞서 말씀 드렸듯이 원인은 한가지가 아닐 수 있습니다. 그렇기 때문에 가능한 의심이 가는 곳에 로그를 발생 시켜 원인을 차근차근 찾아 보아야 합니다.

     

    File "C:\Python-Project\trade_bot\module\upbit.py", line 785, in get_accounts
        if market_code + '-' + account_data_for['currency'] == market_item_list_for['market']:

    먼저 바로 위에 줄에 trace가 찍힌 곳부터 차근차근 살펴보면 되는데요. upbit.py의 785 번째 라인에서 에러가 발생했음을 알수 있습니다.

     

    res = send_request("GET", server_url + "/v1/accounts", "", headers)
    account_data = res.json()
    
    for account_data_for in account_data:
        for market_item_list_for in market_item_list:
    
            # 해당 마켓에 있는 종목만 조합
            if market_code + '-' + account_data_for['currency'] == market_item_list_for['market']:
    
                # KRW 및 소액 제외
                if except_yn == "Y" or except_yn == "y":

    해당 위치로 가서 살펴보니 해당 마켓에 있는 종목만 걸러내기 위한 로직에서 에러가 발생했음을 알 수 있으며 해당 if문 내의 어디선가 오류가 발생했음을 짐작할 수 있습니다.

     

    ① market_code

    ② account_data_for

    ③ market_item_list_for

     

    우선 세 가지 오브젝트의 내용이 정상적으로 들어오지 않았는지 로그를 발생시켜 확인해 보는 것이 좋습니다.

     

    res = send_request("GET", server_url + "/v1/accounts", "", headers)
    account_data = res.json()
    
    for account_data_for in account_data:
        for market_item_list_for in market_item_list:
    
            logging.info(market_code)
            logging.info(account_data_for)
            logging.info(market_item_list_for)
    
            # 해당 마켓에 있는 종목만 조합
            if market_code + '-' + account_data_for['currency'] == market_item_list_for['market']:
    
                # KRW 및 소액 제외
                if except_yn == "Y" or except_yn == "y":

    7~9번 라인과 같이 에러가 발생한 곳 직전에 의심이 가는 세 가지 오브젝트를 로그로 출력해 보았습니다.

     

    [2022/03/12 02:32:18 PM][INFO][upbit.py:784]:KRW
    [2022/03/12 02:32:18 PM][INFO][upbit.py:785]:error
    [2022/03/12 02:32:18 PM][INFO][upbit.py:786]:{'market': 'KRW-BTC', 'korean_name': '비트코인', 'english_name': 'Bitcoin'}
    [2022/03/12 02:32:18 PM][ERROR][test_module.py:30]:Exception 발생!
    [2022/03/12 02:32:18 PM][ERROR][test_module.py:31]:Traceback (most recent call last):
      File "C:\Python-Project\trade_bot\test_module.py", line 21, in <module>
        accounts = upbit.get_accounts('Y', 'KRW')
      File "C:\Python-Project\trade_bot\module\upbit.py", line 789, in get_accounts
        if market_code + '-' + account_data_for['currency'] == market_item_list_for['market']:
    TypeError: string indices must be integers

    로그 내용을 살펴보니 2번째 줄에서 account_data_for의 내용이 정상적이지 않고 'error'라는 텍스트 스트링이 들어 있음을 확인할 수 있습니다. 그로 인해 딕셔너리 타입으로 예상하여 account_data_for['currency']라는 키를 가져오려고 한 부분에서 에러가 발생하는 것이 었습니다.

     

    그럼 실질적인 에러의 원인은 다른 곳에 있다는 건데요. 왜 account_data_for에 잔고 데이터가 아닌 error가 입력되어 있는지를 찾아야 합니다.

     

    res = send_request("GET", server_url + "/v1/accounts", "", headers)
    account_data = res.json()

    account_data_for는 잔고를 조회하는 API를 호출한 결과에서 받아오는 내용인데 결국 업비트 API를 호출한 후 정상 결과가 아닌 error를 받았음을 짐작할 수 있습니다.

     

    # -----------------------------------------------------------------------------
    # - 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("에러 코드:" + str(response.status_code))
                    logging.error("에러 내용:" + str(response.text))
                    break
    
                # 요청 가능회수 초과 에러 발생시에는 다시 요청
                logging.info("[restRequest] 요청 재처리중...")
    
            return response
    
        # ----------------------------------------
        # Exception Raise
        # ----------------------------------------
        except Exception:
            raise

    업비트 API를 호출하는 공통 모듈 부분입니다. 여기서 에러가 발생한건데요. 에러의 원인을 찾기 위해 50~52 라인에 오류의 내용을 로그로 출력하도록 했습니다.

     

    기존에는 에러코드까지만 로그를 출력해서 원인 파악이 조금 힘들었는데 에러의 상세 내역(response.text)까지 출력하도록 하면 에러의 내용을 파악하는데 훨씬 도움이 됩니다.

     

    C:\Python-Project\trade_bot\venv\Scripts\python.exe C:/Python-Project/trade_bot/test_module.py
    [2022/03/12 02:32:18 PM][ERROR][upbit.py:172]:기타 에러:401
    [2022/03/12 02:32:18 PM][ERROR][upbit.py:173]:에러 코드:401
    [2022/03/12 02:32:18 PM][ERROR][upbit.py:174]:에러 내용:{"error":{"name":"invalid_access_key","message":"잘못된 엑세스 키입니다."}}
    [2022/03/12 02:32:18 PM][INFO][upbit.py:784]:KRW
    [2022/03/12 02:32:18 PM][INFO][upbit.py:785]:error
    [2022/03/12 02:32:18 PM][INFO][upbit.py:786]:{'market': 'KRW-BTC', 'korean_name': '비트코인', 'english_name': 'Bitcoin'}
    [2022/03/12 02:32:18 PM][ERROR][test_module.py:30]:Exception 발생!
    [2022/03/12 02:32:18 PM][ERROR][test_module.py:31]:Traceback (most recent call last):
      File "C:\Python-Project\trade_bot\test_module.py", line 21, in <module>
        accounts = upbit.get_accounts('Y', 'KRW')
      File "C:\Python-Project\trade_bot\module\upbit.py", line 789, in get_accounts
        if market_code + '-' + account_data_for['currency'] == market_item_list_for['market']:
    TypeError: string indices must be integers

    다시 오류를 재현해보니 오류의 내용을 명확하게 알 수 있게 되었습니다. 바로 액세스키가 잘못되어서 발생한 오류였는데요. 참고로 이 오류는 제가 일부러 발생시킨 오류이며 업비트에서 오류로 넘어올 수 있는 내용은 아래와 같으니 참고하시면 좋을 것 같습니다.

     

     

    에러 코드 관련한 업비트 API 공식 문서는 아래 링크를 통해 확인하실 수 있습니다.

     

    https://docs.upbit.com/docs/api-%EC%A3%BC%EC%9A%94-%EC%97%90%EB%9F%AC-%EC%BD%94%EB%93%9C-%EB%AA%A9%EB%A1%9D

    반응형
     

    업비트 개발자 센터

    업비트 Open API 사용을 위한 개발 문서를 제공 합니다.업비트 Open API 사용하여 다양한 앱과 프로그램을 제작해보세요.

    docs.upbit.com

     

    자주 발생하는 오류 해결 방법

    그럼 지금부터 그 동안 댓글로 문의 주신 내용들 중 빈번하게 발생하는 내용들에 대한 해결 방법을 알아보도록 하겠습니다. 파이썬의 버전 및 사용하는 환경이 달라 오류의 해결 방법이 다소 다를 수 있는 점은 미리 양해 부탁 드리겠습니다.

     

    JWT 모듈 관련 오류

    C:\Python-Project\trade_bot\venv\Scripts\python.exe C:/Python-Project/trade_bot/test_module.py
    [2022/03/12 02:50:27 PM][ERROR][test_module.py:30]:Exception 발생!
    [2022/03/12 02:50:27 PM][ERROR][test_module.py:31]:Traceback (most recent call last):
      File "C:\Python-Project\trade_bot\test_module.py", line 21, in <module>
        accounts = upbit.get_accounts('Y', 'KRW')
      File "C:\Python-Project\trade_bot\module\upbit.py", line 774, in get_accounts
        jwt_token = jwt.encode(payload, get_env_keyvalue('secret_key'))
    AttributeError: module 'jwt' has no attribute 'encode'

    JWT는 Json Web Token의 약자로 인증을 위한 모듈입니다. 파이썬에서 설치할 수 있는 JWT 모듈은 총 2가지로 그냥 JWT와 PyJWT가 있습니다. 이 중에 PyJWT를 설치하는 것이 가장 좋으며 JWT를 설치하셨다면 공통모듈의 encode와 decode 부분을 조금 수정해 주어야 합니다.

     

    그렇기 때문에 현재 설치되어 있는 JWT 모듈을 확인하고 JWT가 설치되어 있다면 삭제 후 PyJWT를 설치하여 오류를 해결 할 수 있습니다.

     

    python -m pip list

    먼저 파이참 하단의 터미널을 클릭하여 명령어 창을 연 후에 설치되어 있는 모듈을 검색하는 명령어를 수행합니다.

     

    현재 설치되어 있는 모듈을 확인할 수 있으며 현재 pyJWT가 아닌 jwt 1.3.1이 설치되어 있음을 알 수 있습니다.

     

    python -m pip uninstall jwt

    먼저 jwt 모듈을 삭제합니다.

     

    python -m pip install pyjwt

    pyjwt 모듈을 설치해 줍니다.

     

    인증키 관련 오류

    그 외에 자주 발생하는 오류는 인증키 관련 오류인데요. 원인을 알기 힘든 오류가 발생 한다면 아래 인증키 관련 내용을 살펴 보시는 것이 도움이 될 수 있을것 같습니다.

     

    ① 허용된 IP 확인 : 업비트에서 API Key를 생성할 때 허용된 IP를 등록했다면 해당 IP에서 프로그램을 수행해야만 정상적으로 작동합니다. 업비트에 등록한 IP와 현재 프로그램을 수행하는 PC 또는 서버의 공인 IP를 확인합니다.

     

    ② 인증키 만료 확인 : 업비트에서 발급받은 API Key는 무기한 인증키가 아닙니다. 만료기간이 있기 때문에 이 부분을 잘 확인할 필요가 있습니다. 특히 잘 되던 프로그램이 갑자기 안되는 것은 인증키 만료의 경우일 가능성이 높습니다.

     

    파이썬 버전 및 PIP 버전 오류

    파이썬이 현재 시점으로 3.9 버전보다 낮거나 PIP의 버전이 최신이 아닌경우 모듈 설치 및 구동 시 런타임 에러가 발생할 수 있으니 가능한 최신 버전으로 유지하는 것이 좋습니다.

     

    python --version

    설치된 파이썬 버전은 위의 명령어로 확인할 수 있습니다. 참고로 리눅스에서 파이썬 3.9를 설치하는 방법은 아래 포스팅을 참고 부탁 드립니다.

     

    2022.01.13 - [코딩스토리/리눅스] - 리눅스 서버에 파이썬 3.9 설치하기

     

    리눅스 서버에 파이썬 3.9 설치하기

    Tech&Fin 블로그에서 사용중인 서버는 오라클 클라우드에서 무료로 제공되는 프리티어 서버이며 RedHat 그리고 CentOS와 같은 계열인 오라클 리눅스 8 버전을 기준으로 블로그를 진행하고 있습니다.

    technfin.tistory.com

     

    python -m pip --version

    PIP 버전은 위의 명령어로 확인할 수 있습니다.

     

    python.exe -m pip install --upgrade pip

    PIP는 위의 명령어로 업그레이드 할 수 있습니다.

     

    마치며

    이번 포스팅에서는 로그를 이용하여 에러를 찾는 기본적인 방법과 자주 발생하는 문제 해결 방법에 대해서 살펴 보았습니다.

     

    하지만 앞서 말씀 드린바와 같이 에러가 발생하는 경우는 매우 다양할 수 있기 때문에 단순히 에러 내용으로 판단하기 보다는 앞뒤로 로그를 출력해가며 실제 원인을 파악하여 해결하는 방법에 익숙해 지시는 것을 추천 드립니다.

     

    블로그를 구독하면 소식을 조금 더 빨리 받아 보실 수 있습니다. 감사합니다.

    반응형