[파이썬] 코인 자동매매 프로그램 만들기 #6 - 오류 수정
개발/Python

[파이썬] 코인 자동매매 프로그램 만들기 #6 - 오류 수정

반응형


자동매매 프로그램을 실행하고 3일이 지나고 슬랙을 살펴보았는데

위와 같이 매일 자정마다 오류가 발생했습니다...

자정마다 발생한 오류라면 매도 시도나 데이터 갱신을 하는 함수에서 오류가 발생했을 가능성이 큽니다

while True :
            try :
                now = datetime.datetime.now()

                if openTime < now < openTime + datetime.timedelta(seconds=5) :
                    openTime = datetime.datetime(now.year, now.month, now.day) + datetime.timedelta(1)
                    self.sell_coin() # 매도 시도
                    self.get_today_data() # 데이터 갱신

                current_price = pyupbit.get_current_price(self.ticker)
                if((current_price >= self.target_price) and self.bull and not self.buy_yn) : # 매수 시도
                    self.buy_coin()
            except:
                slackBot.message("!!! 프로그램 오류 발생 !!!")

            time.sleep(1)

코드를 살펴보니 매도시도를 할 때에 전날 매수를 했을 때에만 매도시도를 해야하는데 그러한 조건이 없다보니 매일 매도시도를 하여 잔고가 0인 비트코인을 매도시도해서 발생한 오류가 아닌가 추정됩니다

실제로 지난 3일간 매수 시도는 없었습니다

따라서 다음과 같이 매수 여부를 조건으로 추가해 주었습니다

if openTime < now < openTime + datetime.timedelta(seconds=5) :
    openTime = datetime.datetime(now.year, now.month, now.day) + datetime.timedelta(1)
    if(self.buy_yn) :
        self.sell_coin() # 매도 시도
    self.get_today_data() # 데이터 갱신

 

추가적으로 오류가 발생했을 때 어떤 오류가 발생하였는지 구체적으로 파악하기위해 traceback 모듈을 추가해 주었습니다

import traceback

while True :
    try :
        ...
    except:
        slackBot.message("!!! 프로그램 오류 발생 !!!")
        traceback.print_exc()

    time.sleep(1)

다시 한번 프로그램을 실행하고 똑같이 오류가 발생하는지 확인해보도록 하겠습니다

※ 2022.01.20 확인 결과 자정에 발생하던 오류는 수정 후에는 발생하지 않았습니다

하지만 아직까지 매수가 이루어지지는 않았기 때문에 추후 거래내역이 발생할 때 다시 한번 오류발생 여부를 살펴보도록 하겠습니다

※ 2022.01.27

전날 충분히 목표가에 달성했을만큼 상승이 있어 보였는데 매수가 이루어 지지 않아 프로그램을 종료하고 다시 실행을 하니 그때서야 매수를 하고 자정에 매도시도를 한것으로 보이나 오류가 발생했습니다

매수, 매도 함수를 실행하는 대신 매수, 매도 시도 여부를 출력만 하도록 변경 후 프로그램이 제대로 동작하고 있는지 테스트를 해보도록 하겠습니다

※ 2022.02.03

며칠간 실제 매수, 매도 대신 메시지만 슬랙으로 전송하도록 수정하여 테스트를 해보았는데 두 번의 매수시도와 매도시도 메시지가 성공적으로 전송되었습니다. 다시 한번 실제 매수, 매도함수를 넣어 오류가 발생하는 부분을 확인할 예정입니다

※ 2022.02.07

계속해서 코드를 살펴보다보니 한 가지 문제점을 발견했습니다

데이터를 갱신하는 시점과 일봉데이터에서의 장시작시간의 차이에서 오는 문제점인데
pyupbit.get_ohlcv()함수를 통해 가져오는 데이터는 아래와 같이 장시작시간이 오전 9시로 되어있는데 반해서 현재 데이터 갱신은 자정으로 하고 있었습니다

2022-02-05 09:00:00  50613000.0  51488000.0  50216000.0  50788000.0   5618.422204  2.854418e+11  0.862421    0.619468  1272000.0  5.392282e+07  4.740220e+07   True
2022-02-06 09:00:00  50788000.0  52022000.0  50400000.0  51732000.0   4046.196215  2.066614e+11  0.418002    0.626583  1622000.0  5.158501e+07  4.810660e+07   True
2022-02-07 09:00:00  51678000.0  52506000.0  51084000.0  52372000.0   3600.997314  1.873336e+11  0.511955    0.622145  1422000.0  5.268712e+07  4.890100e+07   True

그렇기 때문에 2월7일에서 2월 8일로 넘어가는 자정에 일봉데이터를 새로 불러오더라도 2월7일까지의 데이터밖에 불러오지 못합니다

예를 들어 오늘 오후 3시에 목표가가 100만원이였고 현재가가 이를 돌파하여 매수 시도를 했다고 가정해보겠습니다.

자정이 되어 목표가를 갱신하는데 아직 오전 9시부터 새로운 데이터가 들어오기때문에 매매를 하는데 필요한 데이터는 전일 오후 3시경의 데이터와 동일할 것입니다. 그러나 제가 구하고자 하는 목표가는 ( 시가 + 변동폭 x K )이므로 시가데이터가 오늘의 것이 아니라 정확한 목표가가 아니라고 할 수 있습니다.
만약 정확하게 오늘의 새로운 목표가를 계산했을 때 120만원 이라고 한다면 자정이 지난 현재가가 110만원이였을 때 갱신된 데이터의 목표가는 어제 오후 3시와 동일한 100만원이므로 매수 시도를 하게 되어 불필요한 손실이 발생 할 수 있습니다

따라서 장 개시 시간을 구하는 코드를 자정에서 오전 9시로 변경해 주었습니다.

openTime = datetime.datetime(now.year, now.month, now.day) + datetime.timedelta(hours=9) # 09:00

 

※ 2022.03.19

현재 실행 중인 최종 코드입니다

매일 오전 9시에 데이터 갱신을 시도할 때 프로그램을 돌리는 서버와 업비트의 서버시간이 조금 차이가 있어서인지 9시에 차트데이터를 불러와도 당일의 데이터가 포함되어있지 않아 10초정도의 여유를 더 두었습니다

매수와 매도, 데이터 갱신 등 함수가 동작할때에 해당 정보를 슬랙으로 알림을 보내도록 추가하였으며 timer변수 관련하여 주석처리된 부분은 프로그램이 정상적으로 동작하는지 확인하기위해 1분마다 목표가, 현재가, 상승장 여부 등의 정보를 출력했던 부분이며 확인 및 오류 수정이 완료되어 주석처리한 부분입니다

※ 2022.03.19 전략수정

기존 상승장 여부를 판단할 때 데이터를 갱신할 때 5일 평균선을 시가와 비교하여 true, false로 판단을 하였는데, 이 경우 시가가 5일 이동평균보다 낮았다가 장중 가격이 많이 상승하여 5일 평균선과 목표가를 모두 넘었음에도 매수를 하지 않게 되어 5일 이동평균을 현재가와 비교하여 높은 경우 매수하도록 수정하였습니다

import pyupbit
import time
import datetime
import math
import requests
import traceback

class autoTrade :
    def __init__(self, start_cash, ticker) :
        self.fee = 0.05 # 수수료
        self.target_price = 0 # 목표 매수가
        # self.bull = False # 상승장 여부
        self.ma5 = 0 # 5일 이동평균
        self.ticker = ticker # 티커
        self.buy_yn = False # 매수 여부

        self.start_cash = start_cash # 시작 자산

        self.timer = 0
        self.get_today_data()
        

    def start(self) :
        now = datetime.datetime.now() # 현재 시간
        slackBot.message("자동 매매 프로그램이 시작되었습니다\n시작 시간 : " + str(now) + "\n매매 대상 : " + self.ticker + "\n시작 자산 : " + str(self.start_cash))
        openTime = datetime.datetime(now.year, now.month, now.day) + datetime.timedelta(days=1, hours=9, seconds=10) # 09:00:10

        while True :
            try :
                now = datetime.datetime.now()
                current_price = pyupbit.get_current_price(self.ticker)
               
                if(self.timer % 60 == 0) :
                    print(now, "\topenTime :", openTime, "\tTarget :", self.target_price, "\tCurrent :", current_price, "\tMA5 :", self.ma5, "\tBuy_yn :", self.buy_yn)

                if openTime < now < openTime + datetime.timedelta(seconds=5) :
                    openTime = datetime.datetime(now.year, now.month, now.day) + datetime.timedelta(days=1, hours=9, seconds=10)
                    if(self.buy_yn) :
                        print("==================== [ 매도 시도 ] ====================")
                        slackBot.message("매도 시도")
                        self.sell_coin()
                    self.get_today_data() # 데이터 갱신

                if((current_price >= self.target_price) and (current_price >= self.ma5) and not self.buy_yn) : # 매수 시도
                    print("==================== [ 매수 시도 ] ====================")
                    slackBot.message("매수 시도")
                    self.buy_coin()
            except Exception as err:
                slackBot.message("!!! 프로그램 오류 발생 !!!")
                slackBot.message(err)
                traceback.print_exc()
         
            self.timer += 1
            time.sleep(1)

    def get_today_data(self) :
        print("\n==================== [ 데이터 갱신 시도 ] ====================")
        daily_data = pyupbit.get_ohlcv(self.ticker, count=41)
        # 노이즈 계산 ( 1- 절대값(시가 - 종가) / (고가 - 저가) )
        daily_data['noise'] = 1 - abs(daily_data['open'] - daily_data['close']) / (daily_data['high'] - daily_data['low'])
        # 노이즈 20일 평균
        daily_data['noise_ma20'] = daily_data['noise'].rolling(window=20).mean().shift(1)
       
        # 변동폭 ( 고가 - 저가 )
        daily_data['range'] = daily_data['high'] - daily_data['low']
        # 목표매수가 ( 시가 + 변동폭 * K )
        daily_data['targetPrice'] = daily_data['open'] + daily_data['range'].shift(1) * daily_data['noise_ma20']

        # 5일 이동평균선
        daily_data['ma5'] = daily_data['close'].rolling(window=5, min_periods=1).mean().shift(1)
        # 상승장 여부
        # daily_data['bull'] = daily_data['open'] > daily_data['ma5']

        today = daily_data.iloc[-1]

        self.target_price = today.targetPrice
        # self.bull = today.bull
        self.ma5 = today.ma5
        print(daily_data.tail())
        print("==================== [ 데이터 갱신 완료 ] ====================\n")

    def buy_coin(self) :
        self.buy_yn = True
        balance = upbit.get_balance() # 잔고 조회
        
        if balance > 5000 : # 잔고 5000원 이상일 때
            upbit.buy_market_order(self.ticker, balance * 0.9995)

            buy_price = pyupbit.get_orderbook(self.ticker)['orderbook_units'][0]['ask_price'] # 최우선 매도 호가
            print('====================매수 시도====================')
            slackBot.message("#매수 주문\n매수 주문 가격 : " + str(buy_price) + "원")

    def sell_coin(self) :
        self.buy_yn = False
        balance = upbit.get_balance(self.ticker) # 잔고 조회

        upbit.sell_market_order(ticker, balance)

        sell_price = pyupbit.get_orderbook(self.ticker)['orderbook_units'][0]['bid_price'] # 최우선 매수 호가
        print('====================매도 시도====================')
        slackBot.message("#매도 주문\n매도 주문 가격 : " + str(sell_price) + "원")

class slack :
    def __init__(self, token, channel) :
        self.token = token
        self.channel = channel

    def message(self, message):
        response = requests.post("https://slack.com/api/chat.postMessage",
        headers={"Authorization": "Bearer " + self.token},
        data={"channel": self.channel,"text": message}
    )

with open("key_info.txt") as f :
    lines = f.readlines()
    acc_key = lines[0].strip()    # Access Key
    sec_key = lines[1].strip()    # Secret Key
    app_token = lines[2].strip()  # App Token
    channel = lines[3].strip()    # Slack Channel Name

upbit = pyupbit.Upbit(acc_key, sec_key)
slackBot = slack(app_token, channel)

start_cash = upbit.get_balance()
ticker = "KRW-BTC"
tradingBot = autoTrade(start_cash, ticker)
tradingBot.start()