본문 바로가기
AutoBot

업비트 자동매매 - Upbit Auto Trading No.10 (Trading Code 2)

by zemba 2022. 2. 6.
반응형
SMALL

안녕하세요~ Zemba입니다. 드디어 핵심코드 구현 부분을 포스팅하게 되었습니다! ( 핵심이라고 했지만 누구나 다 아는... 그런 ㅋ)
그래도 모르시는 분들이 있다는 가정하에 작성하는 포스팅이기 때문에 아시는 분들은 너그러이 한번 그냥 이놈이 아주 초보구나 하면서 봐주시면 될 것 같습니다 ㅎㅎ
지난 포스팅에서 예고한 바와 같이 오늘은 RSI1428_trader의 코드에 대해서 설명드릴까 합니다. 그전에 자동매매의 코드에 대한 1편의 내용을 보시려면 이전 포스팅을 참고 부탁드립니다 ( 참고 : https://zemba.tistory.com/38)

 

업비트 자동매매 - Upbit Auto Trading No.9 (Trading Code 1)

안녕하세요~ Zemba입니다~ 😁 지난번 포스팅에서는 전체적인 구조와 설정을 Load 하여 사용하는 구조를 작성하였습니다. 이 구조를 구성하지 않으신분은 지난 포스팅을 참고하시길 바랍니다 (참

zemba.tistory.com

자 그럼 지난 포스팅의 내용에서 잠깐 기억을 되살려보자면 아래 코드를 보시면 됩니다!

for ticker in target_ticker:
    ticker_msg.append(f"# {ticker} process start !\n")
    rsi1428_trader(upbit=upbit, ticker=ticker, ticker_map=ticker_map, period='minute60',
                   minimum_buy_price=minimum_buy_price, ticker_msg=ticker_msg)
    ticker_msg.append(f"# {ticker} process End !\n")

rsi1428_trader의 함수를 살펴보겠습니다. 누가 봐도 rsi1428 전략을 사용하여 트레이딩을 할 것 같은 함수명입니다. ㅎㅎ
그럼 이제 내부를 살펴보도록 하겠습니다. 그전에!!!!! 우선 준비해야 할 항목이 있습니다. (????? 시간 끄는 거 아닙니다 ㅋㅋㅋㅋ )
저희가 RSI의 보조지표를 사용하여 자동 매매의 기준을 잡아야 하기 때문에 RSI값을 구할 수 있는 코드가 있어야 합니다. 그렇기 때문에 RSI지표를 구하는 코드를 구현하고 이를 검증하는 단계가 먼저 필요합니다. ( RSI코드는 구글링 하면 많이 나옵니다 ㅎㅎ 저도 그것을 참조...)

import pandas as pd


def rsi(ohlc: pd.DataFrame, period: int = 14):
    delta = ohlc["close"].diff(1)

    up, down = delta.copy(), delta.copy()
    up[up < 0] = 0
    down[down > 0] = 0

    _gain = up.ewm(com=(period - 1), min_periods=period).mean()
    _loss = down.abs().ewm(com=(period - 1), min_periods=period).mean()

    RS = _gain / _loss
    return pd.Series(100 - (100 / (1 + RS)), name="RSI")

넵 RSI에 대한 코드를 작성했습니다. RSI를 계산하는 공식이 gain과 loss를 구해서 공식을 사용하여 RSI계산을 하여 수치를 구합니다.
마지막의 return부분을 보시면 100에서 빼주는 것을 보면 RSI는 음수의 값은 절대로 나올 수가 없습니다. ( 이미 전문가들은 다아는 내용 ㅋ) 그래서 저희가 이 부분에서 30과 70의 기준을 추출하려고 합니다. 하지만 이 코드가 정상적으로 지표를 그릴 수 있는지를 먼저 검증해야 합니다. 그래서 테스트 코드를 통하여 해당 지표를 한번 확인해 보도록 합니다.

matplotlib의 plot을 사용하여 차트를 그려보았습니다. 위의 이미지를 보시면 240분 봉 기준으로 rsi14,28의 지표를 비교해보면 어느 정도 비슷한 모양의 차트를 구성하고 있는 것을 확인할 수 있습니다 ( 살짝 달라 보이는 건 인덱스와 화면 크기에 따라서 약간 달라 보입니다 ㅎㅎ;; )
이 정도면 rsi의 코드를 그대로 사용할 수 있을 것입니다.


이제 사전 확인할 내용도 검증이 완료되었으니 본격적으로 함수 내용의 코드를 설명드리겠습니다.

import time

from indicator.rsi import rsi


def rsi1428_trader(upbit, ticker, ticker_map, period, minimum_buy_price, ticker_msg):
    ticker_msg.append(f"- RSI1428_TR! [{period}]\n")
    if upbit.get_balances_ticker(ticker) == 0:
        result = upbit.buy_market_order(ticker, minimum_buy_price)
        print(f"FIRST BUY - {result}")
        ticker_msg.append(f"- FIRST BUY!\n")
        time.sleep(1)

        return ticker_msg

    df = upbit.get_ohlcv(ticker, period)
    rsi14 = rsi(df, 14)
    rsi28 = rsi(df, 28)

    current_price = upbit.get_current_price(ticker)
    ticker_info = ticker_map.get(ticker)

    if (float(ticker_info['avg_price']) - (float(ticker_info['avg_price']) * 0.05)) >= current_price:
        if upbit.get_balances_krw() <= 5000:
            ticker_msg.append("## 구매할 금액이 부족합니다.\n")
        else:
            result = upbit.buy_market_order(ticker, minimum_buy_price)
            print(f"AVG UNDER BUY - {result}")
            ticker_msg.append(f"- AVG UNDER BUY!\n")

    if 30 >= rsi14[-1] and 30 >= rsi28[-1]:
        if upbit.get_balances_krw() <= 5000:
            ticker_msg.append("## 구매할 금액이 부족합니다.\n")
        else:
            result = upbit.buy_market_order(ticker, minimum_buy_price)
            print(f"RSI30 UNDER BUY - {result}")
            ticker_msg.append(f"- RSI30 UNDER BUY!\n")

    if rsi14[-1] >= 70 and rsi28[-1] >= 70:
        rate = round(((float(current_price) / float(ticker_info['avg_price'])) * 100) - 100, 3)
        if rate >= 0:
            ticker_count = float(ticker_info['count']) * 0.1

        # 판매금액 최소 5000이상일경우만 처리함.
        if (current_price * ticker_count) <= 5000:
            return ticker_msg

        result = upbit.sell_market_order(ticker=ticker, count=ticker_count)
        print(f"RSI70 OVER 10% SELL - {result}")
        ticker_msg.append(f"- RSI70 OVER 10% SELL!\n")

    return ticker_msg

드디어 rsi1428에 대한 거래기준에 대한 코드를 공유드렸습니다!!( 빠밤~ ) 뭐 거창 한 건 아니고 다들 예상하셨겠지만 ㅎㅎㅎ 앞선 포스팅에서 저의 최종 전략을 정리하면서 이미 나열된 항목들입니다.

나눠서 설명을 자세하게 드리겠습니다. 
오늘은 좀 포스팅이 길어도 끝까지 작성하겠습니다 ㅎㅎ 내용이 끊어지면 안 되니까요....!!

    if upbit.get_balances_ticker(ticker) == 0:
        result = upbit.buy_market_order(ticker, minimum_buy_price)
        print(f"FIRST BUY - {result}")
        ticker_msg.append(f"- FIRST BUY!\n")
        time.sleep(1)

        return ticker_msg

    df = upbit.get_ohlcv(ticker, period)
    rsi14 = rsi(df, 14)
    rsi28 = rsi(df, 28)

우선 저희가 Ticker(코인)을 거래할 종류를 Config에 지정하였기 때문에 최초에 1번은 구매를 해야 보유한 코인의 매수/매도의 판단을 진행할 수 있기 때문에 Ticker의 보유 수량을 검사합니다. 만약에 보유 수량이 없으면 시장가로 최초로 1번 매수합니다. 매수금액은 Config에 설정된 최고 구매가로 매수합니다. 처음에 구매한 금액이 너무 높다고 생각하실 수도 있습니다. 하지만 자동거래를 위해서는 자동이든 수동이든 해당 티커를 언젠가는 보유해야 하며 보유하고 나서 평단가를 확인하여 추가 매수를 하는 코드도 작성할 것이기 때문에 너무 걱정하지 않아도 될 것 같습니다. 결국 평단이 높으면 낮출 것이고 높을 경우는 기다리면 되죠 ㅎㅎ

그다음으로는 해당 Ticker의 ohlcv를 가져와야 합니다. 여기서 ohlcv란? 해당 코인의 차트(캔들) 정보를 불러오는 것입니다. 그런데 이름이 왜 ohlcv인지 궁금해하시는 분들이 많으실 텐데 단순합니다 ㅎㅎ 해당 정보를 불러오면 DataFrame형태의 데이터로 반환되는데 이를 print로 출력해보면 다음과 같습니다.

ohlcv DataFrame

눈치가 빠르신 분들은 이미 파악하셨겠지만 결과의 컬럼 값을 보시면 open, high, low, close, volume, value 이렇게 컬럼으로 구성이 되어있습니다. 여기서 앞 단어만 추출하여 만들어진 이름입니다.( 제가 정한 게 아니라 그렇게들 불리고 있더라고요 ㅎㅎㅎ )
아무튼 ohlcv의 정보를 받는다는 것은 과거 캔들의 시작가, 최고가, 최저가, 종가, 거래량 등을 불러와서 확인할 수 있는 정보로 보시면 됩니다.

df = upbit.get_ohlcv(ticker, period)
rsi14 = rsi(df, 14)
rsi28 = rsi(df, 28)

그럼 캔들 정보를 불러왔으니까 이제 rsi14, rsi28의 지표를 각각 구합니다. 위에서 rsi에 대한 코드를 작성한 것을 보여드렸기 때문에 rsi함수의 내부 코드의 설명은 생략합니다. 결국 우리가 구하려고 하는 것은 몇 분 봉 기준으로 rsi지표를 추출할 것인지에 대한 코드로 봐주시면 됩니다.

current_price = upbit.get_current_price(ticker)
ticker_info = ticker_map.get(ticker)

if (float(ticker_info['avg_price']) - (float(ticker_info['avg_price']) * 0.05)) >= current_price:
    if upbit.get_balances_krw() <= 5000:
        ticker_msg.append("## 구매할 금액이 부족합니다.\n")
    else:
        result = upbit.buy_market_order(ticker, minimum_buy_price)
        print(f"AVG UNDER BUY - {result}")
        ticker_msg.append(f"- AVG UNDER BUY!\n")

이제 rsi 지표를 추출했으니 자동매수/매도를 위한 기준을 설정해야 합니다. 첫 번째로 계속 등장한 평단가를 확인하여 추가 매수를 하는 코드입니다. Ticker의 현재가를 조회합니다. 그리고 ticker_map에서 이미 현재 내계좌의 해당 ticker의 평단가를 계산해둔 Map형태의 데이터에서 해당 Ticker의 정보를 불러옵니다. 

첫번째 if문을 봅니다. ticker_info에는 내 계좌에 해당하는 정보가 담겨있기 때문에 평단가 정보를 조회합니다. 
그러고는 평단가의 5% 금액을 계산하여 빼줍니다. 그리고 현재가와 비교합니다.
정리하자면 현재가보다 -5% 이하 인지를 체크하는 조건문이라 보시면 됩니다.
기존에 포스팅을 보신 분들이 라면 전략이 바뀐 것을 알 수 있을 텐데요 ㅎㅎ 원래는 무조건 평단가보다 낮은 경우에 구매를 하도록 했었습니다.
그러나 지금 제가 이미(?) 프로그램을 돌리고 있으면서 경과를 확인해봤는데 시드가 너무 빨리 말라버리더라고요 ㅎㅎ 그래서 몰래 바꿨습니다 ㅎㅎㅎ;;;;; ( 저도 아직 계속 보완 중입니다!!! ) 그다음은 간략히 설명만 하자면 원화가 최소금액이 없으면 거래하지 않고 원화가 있으면 시장가로 매수합니다.

if 30 >= rsi14[-1] and 30 >= rsi28[-1]:
    if upbit.get_balances_krw() <= 5000:
        ticker_msg.append("## 구매할 금액이 부족합니다.\n")
    else:
        result = upbit.buy_market_order(ticker, minimum_buy_price)
        print(f"RSI30 UNDER BUY - {result}")
        ticker_msg.append(f"- RSI30 UNDER BUY!\n")

if rsi14[-1] >= 70 and rsi28[-1] >= 70:
    rate = round(((float(current_price) / float(ticker_info['avg_price'])) * 100) - 100, 3)
    if rate >= 0:
        ticker_count = float(ticker_info['count']) * 0.1

    # 판매금액 최소 5000이상일경우만 처리함.
    if (current_price * ticker_count) <= 5000:
        return ticker_msg

    result = upbit.sell_market_order(ticker=ticker, count=ticker_count)
    print(f"RSI70 OVER 10% SELL - {result}")
    ticker_msg.append(f"- RSI70 OVER 10% SELL!\n")

그다음 조건으로는 RSI의 30, 70에 따른 처리입니다. 우선 첫 번째 조건문을 보면 rsi14 [-1]이 30 이하고 rsi28 [-1]이 30 이하일 경우에 조건에 해당할 경우 시장가로 매수합니다. 여기서 rsi14,28의 뒤에 [-1]로 값을 찾는 것은 ohlcv의 정보를 조회했을 때 현재 가장 마지막 캔들의 정보를 조회한다는 의미입니다. ( java나 다른 언어에서는 마지막 size를 가져오지만 python에서는 -1로 찾아오면 맨 마지막 정보를 찾더라고요 ㅎㅎ) 그래서 현재 마지막 캔들의 RSI정보를 조회하여 판단하기 위함입니다. 마찬가지로 그럼 70의 조건도 동일하게 적용하면 되겠죠? 하지만 70의 조건에서는 매수가 아닌 매도를 진행합니다. 또한 최근에 수정한 내용을 또 공유드리자면 ㅋㅋ;;

rate = round(((float(current_price) / float(ticker_info['avg_price'])) * 100) - 100, 3)
if rate >= 0:

바로 이 부분입니다 ㅎㅎ RSI1428이 70선을 넘겼는데 현재의 수익률을 계산하는 코드입니다. 이것이 추가된 이유는 최근에 시장이 전체적으로 폭락하면서 비트코인이 4천만 원까지 하락하는 시장이 회복되면서 RSI 70을 넘기게 되는 조건을 맞이하게 되었는데요... 모니터링하다 보니까 지금 현재 수익률이 마이너스인데도 불구하고 RSI 70을 넘겼기 때문에 매도를 하는 것을 보았습니다. 
아니...... 수익을 보면 팔아야지라는 생각이 들어서 우선은 70의 조건은 마이너스가 아닐 경우로 한정하였습니다.( 이건 변동될 가능성이 있습니다. ㅎㅎㅎ) 결국 저도 조금씩 강화해나가고 있다고 보시면 될 것 같습니다.
나머지는 동일합니다. 백테스팅 때 RSI 70 이상일 경우에 전체다 매도하지 않고 보유 수량의 10%만 매도하기로 했기 때문에 10%의 수량을 계산하여 시장가로 매도합니다. 이제 나머지 코드의 설명이 끝났습니다.

그 이외의 코드는 로그 및 텔레그램에 메시지를 전달할 코드들이라 별도로 설명을 드리지는 않겠습니다.
이제 전체적인 매매의 코드도 설명이 다 된 것 같습니다. 코드는 완성이 되었으니 이제 서버에 올려서 24시간 운영을 해야 하겠죠?
다음 포스팅에서는 서버 세팅과 실행 등에 대해서 한번 정리하는 포스팅을 작성하고 그다음에 최종적인 저의 견해와 느낀 점 등을 좀 공유드리도록 하겠습니다. 최종적으로 12편까지 나오고 오토봇은 종료될 것 같네요 ㅎㅎ

저는 전문 투자자도 아니고 그냥 일개 개미에 불과하면 그냥 개발자이기 때문에 전문적인 지식이 없어 실제 투자의 결정은 본인에게 있습니다.

반응형
LIST

댓글