본문 바로가기

코딩

코인 자동 매매 - 업비트, 바이낸스

unrelated picture

https://suganglive.tistory.com/12

 

반자동 코인투자 - 가상화폐 투자 마법공식 전략 백테스트

가상화폐 투자 마법공식 투자전략(책) 중 12번째 전략을 엑셀로 백테스트하고 실전 실행해보겠다. 준비물 : 코인 데이터 코인 데이터는 달러 기준으로 coinmarketcap에서 구할 수 있고, 원화(업비트)

suganglive.tistory.com

저번 반자동 코인투자를 약 한 달 정도 하다보니 내가 백테스트를 뭘 잘못했는지 알게 되었고 CAGR 몇 백%에 MDD 한 자리 수는 지금으로써는 불가능하다는 것을 깨달았다. 결국에 20%정도 되는 연복리수익률에 20%정도되는 MDD를 가지고 수동으로 실전투자를 해보았는데, 후기를 짧게 남겨보겠다.

Good

  1. 실전 반자동 투자도 데이터랑 매우 유사하게 수익이 난다

빨강이 데이터 상 수익률이고 초록이 실전 수익률이다. 슬리피지를 보수적으로 0.2%로 잡았기 때문에 실전 수익률이 조금 더 나은 모습이다.

 

Bad

  1. 아침 9시에 일어나기 힘들다.
  2. 매일 9시에 데이터 얻어와서 엑셀에 복붙하고 업비트에 예약-지정가 주문하기 귀찮다.(약 10분 소요된다.)
  3. 예약-지정가 주문 수수료가 그냥 주문보다 약 3배 비싸다.(0.139%, 0.05%)

 

이것 외에도 실수 가능성, 시간 소모 등의 여러가지 요인을 고려해 봤을 때 자동 매매 프로그램이 필요했다. 시중에 나와있는 자동 매매 프로그램들이 있지만 다음과 같은 이유로 내가 만들었다.

  1. 신용할 수 없다.
  2. 수수료가 비싸다.
  3. 자동 매매 프로그램 만들기가 어렵지 않다.(추측이었지만 실제로 어렵지 않았다.)
  4. 능동적인 주체로서 투자하고 싶었다.

 

등등의 이유로 wikidocs 자동매매 만들기, 조코딩님의 유튜브 등을 참고해서 개인 자동 매매 프로그램을 완성했다. 그것을 공유하고자 한다.

 

주의 - 이하 게시글 내용, 코드 사용에 대한 책임은 본인에게 있음

Upbit

임포트 라이브러리

# 업비트, 상승장, 변동성 돌파, 변동성 조절 Coins : [BTC, ETH, XRP, LTC], 9시 초기화, 로그파일생성, 9시 체결 여부 확인, 모든 함수 재사용 가능
# nohup python3 mark2-3.py > output.log &
import pyupbit
import time
import datetime
import math
import logging

logging.basicConfig(filename='mark2-3.log', level=logging.INFO, format='%(asctime)s:%(message)s')

여기서 기본으로 파이썬에 없는 것은 pyupbit뿐이다. logging은 서버에서 돌릴 때 얘가 잘 돌아가나 확인하려고 추가했다.

 

계정, 티커

access_key = "a"
secret_key = "b"
upbit = pyupbit.Upbit(access_key, secret_key)

tickers = ["KRW-BTC", "KRW-ETH", "KRW-XRP", "KRW-LTC"]

업비트 계정 연결에 필요하다. tickers는 전략에 사용할 코인 4가지이다. 선정 이유는 친숙해서이다.

 

레인지 함수

def get_range(ticker):
    df = pyupbit.get_ohlcv(ticker, count=3)
    y_day = df.iloc[-2]

    y_high = y_day['high']
    y_low = y_day['low']
    range = y_high - y_low
    return range

변동성 돌파 전략의 매수 기준 가격을 선정하는 데 필요한 요소인 레인지를 얻는 함수이다. 업비트에서 ohlcv를 데이터프레임(?) 형식으로 가져오기 때문에 iloc을 썼다. y_day는 yesterday의 줄임이다.

 

투자비중 함수

def get_percentage(ticker):
    range = get_range(ticker)
    y_open = get_y_open(ticker)
    target_v = 0.05
    range_ratio = range/y_open
    if target_v > range_ratio:
        percentage = 1/4
    else:
        percentage = (1/4)*(target_v/range_ratio)
    return percentage

변동성 조절을 위한 투자비중을 구하는 함수이다. 가상화폐투자마법공식의 투자전략 8을 활용할 것이기 때문에 타겟 변동성은 0.05로 설정했다. 만약 전일 레인지 비율이 타겟 변동성보다 작을 경우에는 투자비중으로 0.25를 선정하고 전일 레인지 비율이 타겟 변동성보다 커서 변동성 조절이 필요한 경우에는 그에 맞게 조치했다.

 

목표 매수 가격 함수

def get_target_price(ticker):
    df = pyupbit.get_ohlcv(ticker)
    today = df.iloc[-1]

    t_open = today['open']
    range = get_range(ticker)
    k = 0.5
    target_price = t_open + range * k
    return target_price

투자전략대로 목표 매수 가격을 구한다.

 

시가 함수

def get_open(ticker):
    df = pyupbit.get_ohlcv(ticker)
    today = df.iloc[-1]
    open = today['open']
    return open

시가를 구한다.

 

어제 시가 함수

def get_y_open(ticker):
    df = pyupbit.get_ohlcv(ticker)
    today = df.iloc[-2]
    open = today['open']
    return open

레인지 비율을 구할 때 분모에 나는 어제 시가를 넣기 때문에(특별한 이유는 없다.) 어제 시가를 구하는 함수를 만들었다.

 

5일 이동평균

def get_ma5(ticker):
    df = pyupbit.get_ohlcv(ticker)
    open = df['open']
    ma = open.rolling(window=5).mean()
    return ma[-1]

책에 나온대로 만들었다.

 

매수 가격 별 호가 맞추기

def buyable(ticker):
    target = get_target_price(ticker)
    target = target * 1.002
    if target - 1 < 0:
        if target * 10 - 1 < 0:
            target = target * 10000
            target = math.floor(target)
            target = target + 1
            target = target / 10000
        else:
            target = target * 1000
            target = math.floor(target)
            target = target + 1
            target = target / 1000
    elif len(str(math.floor(target))) == 1:
        target = target * 100
        target = math.floor(target)
        target = target + 1
        target = target / 100
    elif len(str(math.floor(target))) == 2:
        target = target * 10
        target = math.floor(target)
        target = target + 1
        target = target / 10
    elif len(str(math.floor(target))) == 3:
        target = math.floor(target)
        target = target + 1
    elif len(str(math.floor(target))) == 4:
        if target % 10 <= 5: 
            target = target / 10
            target = math.floor(target)
            target = target * 10
            target = target + 5
        else:
            target = target / 10
            target = math.floor(target)
            target = target * 10
            target = target + 10
    elif len(str(math.floor(target))) == 5:
        target = math.floor(target)
        target = target + 10
    elif len(str(math.floor(target))) == 6:
        if (target/10) % 10 <= 5: 
            target = target / 100
            target = math.floor(target)
            target = target * 100
            target = target + 50
        else:
            target = target / 100
            target = math.floor(target)
            target = target * 100
            target = target + 100
    elif len(str(math.floor(target))) >=7:
        target = target / 1000
        target = math.floor(target)
        target = target * 1000
        target = target + 1000
    return target

그냥 시장가에 거래를 하면 호가를 따로 필요로 하지 않지만 유동성이 많이 떨어질 때 매수 호가 창을 타고 엄청나게 큰 거래비용을 내고 싶지 않았기 때문에 목표 매수 가격에 1.002를 곱한 가격을 지정가 매수로 하고싶었지만 업비트 주문 가능 금액에 맞춰서 주문을 내지 않으면 오류가 떴기 때문에 업비트 주문 가능 금액 기준에 맞게 변환시켜주는 함수를 썼다. 매수 목표 가격 * 1.002의 주문 가능한 바로 위 호가를 리턴한다. 특정 가격에서 오류가 있을 수 있지만 대세에는 지장이 없다.

매수 주문 함수

def buy_limit(ticker):
    percentage = get_percentage(ticker)
    krw = krw * percentage
    price = buyable(ticker)
    unit = krw/float(price)
    upbit.buy_limit_order(ticker, price, unit)

krw은 내 계정 현재 현금 보유량이고, 일전의 투자비중 함수에서 구한 퍼센트를 활용해서 주문을 낸다.

 

매도 가격 함수

def sell_price(ticker):
    price = pyupbit.get_current_price(ticker)
    price = price * 0.998
    return price

매도 가격을 설정한다. 슬리피지 0.2% 매수 가격 별 호가 맞추기 함수와 다르게 매도 가격 별 호가 맞추기 함수에서 가격과 맞추기 함수를 따로 둔 것은 내가 체계적으로 코드를 쓰지 않았기 때문이다.

 

매도 가격 별 호가 맞추기 함수

def sellable(ticker):
    price = sell_price(ticker)
    if price - 1 < 0:
        if price * 10 - 1 < 0:
            price = price * 10000
            price = math.floor(price)
            price = price - 1
            price = price / 10000
        else:
            price = price * 10000
            price = math.floor(price)
            price = price - 1
            price = price / 10000
    elif len(str(math.floor(price))) == 1:
        price = price * 100
        price = math.floor(price)
        price = price - 1
        price = price / 100
    elif len(str(math.floor(price))) == 2:
        price = price * 10
        price = math.floor(price)
        price = price / 10
    elif len(str(math.floor(price))) == 3:
        price = math.floor(price)
        price = price - 1
    elif len(str(math.floor(price))) == 4:
        if price % 10 <= 5: 
            price = price / 10
            price = math.floor(price)
            price = price * 10
        else:
            price = price / 10
            price = math.floor(price)
            price = price * 10
            price = price + 5
    elif len(str(math.floor(price))) == 5:
        price = price / 10
        price = math.floor(price)
        price = price * 10
    elif len(str(math.floor(price))) == 6:
        if (price/10) % 10 <= 5: 
            price = price / 100
            price = math.floor(price)
            price = price * 100
        else:
            price = price / 100
            price = math.floor(price)
            price = price * 100
            price = price + 50
    elif len(str(math.floor(price))) >=7:
        price = price / 1000
        price = math.floor(price)
        price = price * 1000
    return price

매수 호가 맞추기 함수와 유사하다.

매도 함수, 시장가 매도 함수

def sell_limit(ticker):
    unit = upbit.get_balance(ticker[4:])
    price = sellable(ticker)
    upbit.sell_limit_order(ticker, price, unit)

def sell_market(ticker):
    unit = upbit.get_balance(ticker[4:])
    upbit.sell_market_order(ticker, unit)

인덱싱을 쓴 이유는 다른 티커들이 모두 “KRW-BTC”이런 형태이기 때문이다. 매도는 시장가도 적용하여 혹시 0.998 가격에 체결되지 않았을 경우 시장가로 체결되도록 뒤에 짜뒀다.

 

현재 시간, 매도 시간

now = datetime.datetime.now()
mid = datetime.datetime(now.year, now.month, now.day) + datetime.timedelta(hours=8, minutes=59)

if now > mid:
    mid = mid + datetime.timedelta(1)

현재 시간과 매도 시점 시간(8시 59분)을 설정한다. 만약 현재 시간이 9시를 넘었다면 다음날 9시로 설정한다.

 

기본 값 구하기

a = 0
while a == 0:
    try:
        btc_open = get_open("KRW-BTC")
        btc_ma5 = get_ma5("KRW-BTC")
        btc_target = get_target_price("KRW-BTC")
        btc_status = 0

        eth_open = get_open("KRW-ETH")
        eth_ma5 = get_ma5("KRW-ETH")
        eth_target = get_target_price("KRW-ETH")
        eth_status = 0

        xrp_open = get_open("KRW-XRP")
        xrp_ma5 = get_ma5("KRW-XRP")
        xrp_target = get_target_price("KRW-XRP")
        xrp_status = 0

        ltc_open = get_open("KRW-LTC")
        ltc_ma5 = get_ma5("KRW-LTC")
        ltc_target = get_target_price("KRW-LTC")
        ltc_status = 0

        krw = upbit.get_balance("KRW")
        a = 1
    except Exception as e:
        logging.info("default value error : " + str(e))
        time.sleep(5)

logging.info("program started")

함수들을 사용해서 본격적인 반복문에 들어가기에 앞서 필요한 자료들을 설정한다.

 

매수, 매도 반복문

while True:
    try:
        now = datetime.datetime.now()
        if mid < now:
            for ticker in tickers:
                if upbit.get_balance(ticker) != 0:
                    sell_limit(ticker)
                    logging.info(ticker, "sell order submitted")
            time.sleep(60)
            for ticker in tickers:
                if upbit.get_order(ticker):
                    upbit.cancel_order(ticker)
                    sell_market(ticker)
                    logging.info(ticker, "sold at market price")      
                
            mid = datetime.datetime(now.year, now.month, now.day) + datetime.timedelta(days=1, hours=8, minutes=59)
            btc_target = get_target_price("KRW-BTC")
            eth_target = get_target_price("KRW-ETH")
            xrp_target = get_target_price("KRW-XRP")
            ltc_target = get_target_price("KRW-LTC")
            btc_ma5 = get_ma5("KRW-BTC")
            eth_ma5 = get_ma5("KRW-ETH")
            xrp_ma5 = get_ma5("KRW-XRP")
            ltc_ma5 = get_ma5("KRW-LTC")
            btc_open = get_open("KRW-BTC")
            eth_open = get_open("KRW-ETH")
            xrp_open = get_open("KRW-XRP")
            ltc_open = get_open("KRW-LTC")
            btc_status = 0
            eth_status = 0
            xrp_status = 0
            ltc_status = 0
            krw = upbit.get_balance("KRW")
            logging.info("btc_target : ", btc_target)
            logging.info("eth_target : ", eth_target)
            logging.info("xrp_target : ", xrp_target)
            logging.info("ltc_target : ", btc_target)
            logging.info("krw_balance : ", krw)

        btc_current_price = pyupbit.get_current_price("KRW-BTC")
        eth_current_price = pyupbit.get_current_price("KRW-ETH")
        xrp_current_price = pyupbit.get_current_price("KRW-XRP")
        ltc_current_price = pyupbit.get_current_price("KRW-LTC")
        if btc_current_price > btc_target and btc_status == 0 and btc_open > btc_ma5:
            buy_limit("KRW-BTC")
            btc_status = 1
            logging.info("btc get")

        if eth_current_price > eth_target and eth_status == 0 and eth_open > eth_ma5:
            buy_limit("KRW-ETH")
            eth_status = 1
            logging.info("eth get")

        if xrp_current_price > xrp_target and xrp_status == 0 and xrp_open > xrp_ma5:
            buy_limit("KRW-XRP")
            xrp_status = 1
            logging.info("xrp get")

        if ltc_current_price > ltc_target and ltc_status == 0 and ltc_open > ltc_ma5:
            buy_limit("KRW-LTC")
            ltc_status = 1
            logging.info("ltc get")
    except Exception as e:
        logging.info("programm error : " + str(e))
    time.sleep(1)

미안하다 이거 보여주려고 어그로 끌었다. 뭐 나름대로 예외처리를 한다고 했는데 잘 돌아갈지는 미지수이다.

전체 코드는 https://github.com/suganglive/coinauto/blob/main/mark2-3.py에서 확인할 수 있다.

Binance

기본적으로 업비트와 같다. 하지만 라이브러리가 pyupbit → ccxt로 바뀌었다.

다른 점이 있다면 업비트와 다르게 바이낸스는 호가 가능 금액 기준이 금액이 기준이 아니라 코인마다 제각각이라서 그냥 지금 당장 필요한 코인들 가격만 구해주는 함수를 만들었다. 그리고 업비트 판 자동 매매 코드와는 다르게 반복문에서가 아니라 함수 내에서 매도 불가능 할 때 예외처리를 했는데 뭐가 더 나은지는 모르겠다. 다시 말하지만 체계적으로 만들지 않았다. https://github.com/suganglive/coinauto/blob/main/mark3.py에서 확인할 수 있다.

 

다음 - AWS를 사용해서 내 컴퓨터 안 켜놓고 24시간 자동매매 프로그램 돌리기 후기 글

 

가상화폐투자마법공식

파이썬을 이용한 비트코인 자동매매

조코딩

 

스승님들 잘 배웠습니다. 고맙습니다. ㅎ.ㅎ

binance 링크