본문 바로가기

Python-Autotrade (Binance)

파이썬(Python) - 바이낸스(Binance) 볼린저 밴드를 사용한 자동매매(Auto Trade)

안녕하세요 요번시간은 ccxt api를 사용하여 자동매매 프로그램을 만들어 보겠습니다.

 

거래소는 바이낸스 선물 거래를 사용하겠습니다.

 

1. 볼린저 밴드 (Bollinger Bands)

 볼린저 밴드는 아래와 같이 구성되어 있습니다.

 

1. Simple moving average(Sma) : n일간의 종가(close)의 평균

2. Upper Band (Ub) : sma + std * nstd (std : n일간의 표준편차, ntsd : 표준편차에 곱하는 상수)

3. Lower Band (Lb) : sma - std * nstd (std : n일간의 표준편차, ntsd : 표준편차에 곱하는 상수)

 

볼린저 밴드 (Bollinger Bands) 기법이란,  특정 코인 가격 변동의 90%는 볼린저 밴드 안에서 이루어진다고 가정을 하여, 만약 현재 가격이 Lb보다 작다면 매수를, 현재가격이 Ub보다 높아지면 매도를 하는 간단한 전략입니다. 

 

볼린저 밴드는 기본적으로 n = 20, nstd = 2를 사용하지만, 밴드의 폭을 넓혀 신중한 매매를 위해 ntsd를 3으로 설정하여 투자의 리스크를 줄이는 방향으로 만들어 보았습니다. 거래에 사용하는 코인은 비트코인(BTC/USDT), 이더리움(ETH/USDT)를 사용하였습니다.

 

2. 자동매매 코드
from datetime import datetime, timedelta
import time
import ccxt
import pandas as pd

with open('경로//binance_api_key.txt') as f:
    lines = f.readlines()
    api_key = lines[0].strip()
    secret = lines[1].strip()

binance = ccxt.binance(config={
    'apiKey': api_key, 
    'secret': secret,
    'enableRateLimit': True,
    'options': {
        'defaultType': 'future'
    }
})

def sma(data,window):
    return(data.rolling(window=window).mean())

def bollinger_bands(data,sma,window,ntsd):
   std = data.rolling(window=window).std()
   upper_band = sma + std * ntsd
   lower_band = sma - std * ntsd
   return upper_band, lower_band

def gather_data(symbols, n_hours):
    merge = False
    for symbol in symbols:
        ohlcv = binance.fetch_ohlcv(symbol=symbol, timeframe='1h', limit=n_hours)
        cols = ['datetime', f'{symbol}-open', f'{symbol}-high', f'{symbol}-low', f'{symbol}-close', f'{symbol}-volume'] 
        df = pd.DataFrame(ohlcv, columns=cols)
        if merge == True:
            dfs = pd.concat([df, dfs], join = 'outer', axis=1)
        else:
            dfs = df
            merge=True
    
    for symbol in symbols:
        dfs[f'{symbol}-sma'] = sma(dfs[f'{symbol}-close'],20)
        dfs[f'{symbol}-upper_band'] , dfs[f'{symbol}-lower_band'] = bollinger_bands(data = dfs[f'{symbol}-close'],
                                                                                    sma = dfs[f'{symbol}-sma'],
                                                                                    window = 20,
                                                                                    ntsd = 3)   
    dfs.dropna(inplace=True)
    return dfs

def get_states(df,symbol):
    states = {}
    for symbol in symbols:
        if df[f'{symbol}-close'].iloc[-1] < df[f'{symbol}-lower_band'].iloc[-1]:
            states[symbol] = 'below'
        elif df[f'{symbol}-close'].iloc[-1] > df[f'{symbol}-upper_band'].iloc[-1]:
            states[symbol] = 'above'
        else:
            states[symbol] = 'inside'

    return states

def set_leverage(symbol,leverage):
    global binance
    markets = binance.load_markets()
    symbol = symbol
    market = binance.market(symbol)
    leverage = leverage
    resp = binance.fapiPrivate_post_leverage({'symbol': market['id'],'leverage': leverage})

def buy(symbol,amount):
    order = binance.create_market_buy_order(symbol=symbol,amount=amount)

def sell(symbol,amount):
    order = binance.create_market_sell_order(symbol=symbol,amount=amount)

def get_current_fucture_cash():
    global binance
    balance = binance.fetch_balance()
    dic = balance['total']
    return dic['USDT']

symbols = ['BTC/USDT','ETH/USDT'] #'LTC/USDT'
balance_unit = 'USDT'
signal = True

while True:
    try:
        if (datetime.now().second % 10 == 0) or (signal == True):
            if ((datetime.now().minute % 5 == 0) and (datetime.now().second == 10)) or (signal == True):
                df = gather_data(symbols,22)
                states = get_states(df,symbols)
                print('Current state of market:')
                print(states)
                signal  = False
        if balance_unit == 'USDT': #looking to buy
            for symbol in symbols:
                ask_price = float(binance.fetch_order_book(symbol)['asks'][0][0])
                lower_band = df[f'{symbol}-lower_band'].iloc[-1]
                if ask_price < lower_band and states[symbol] == 'inside': # buy signal
                    set_leverage(symbol,5)
                    print(f'BUY {symbol}!\nTime : {datetime.now}\nbuy_price : {ask_price}')
                    balance_unit = symbol
                    if symbol == 'BTC/USDT':
                        buy(symbol,0.002)  
                    elif symbol == 'ETH/USDT':
                        buy(symbol,0.03)
                break

        if balance_unit != 'USDT': #looking to sell
            bid_price = float(binance.fetch_order_book(balance_unit)['bids'][0][0])
            upper_band = df['{symbol}-upper_band'].iloc[-1]
            if bid_price > upper_band and states[symbol] == 'inside': #sell signal
                print(f'SELL {balance_unit}!\nTime : {datetime.now}\nsell_price : {bid_price}\nRoe:{100*ask_price/bid_price}')
                if balance_unit == 'BTC/USDT':
                        sell(balance_unit,0.002)
                elif balance_unit == 'ETH/USDT':
                        sell(balance_unit,0.03)
                balance_unit = 'USDT'
        time.sleep(1)
    except Exception as e:
        print(e)
        time.sleep(1)

 코드의 로직은 유투버 thePrincipalcomponent 님을 사용하였습니다.

 

필자의 경우, Principalcomponent님(Binance-api)과 달리 ccxt-api를 사용하였습니다. 또한 레버리지 설정, 매수, 매도 등의 함수를 추가하여 자동매매 코드를 완성하였음을 알려드립니다.

 

 

-----------------  binance 객체 생성 및 함수  -----------------

 

- Binacne Api의 api_key와 secre_key는 txt파일로 관리를 하였습니다. (코드 6줄)

 

- Binance 선물(Future)거래를 위해  binance 객체를 만듭니다. (코드 11줄, 16줄)

 

- Sma, Lower band 및 Upper band를 계산하는 함수를 만듭니다. (코드 20줄, 23줄)

 

- 사용하는 코인은 비트코인 (BTC/USDT)과 이더리움 (ETH/USDT) 이므로, 관리의 편의성을 위해 한개의 DataFrame에 각 코인에 대한 ohlcv, sma, ub, lb 정보를 넣어주는 함수(gather data)를 만들어 줍니다.  (코드 29줄)

 

 각 코인의 DataFrame을 합하기 위해, 시그널을 주는 merge 객체를 사용합니다 .만약 더 많은 코인을 거래하고 싶다면, symbols list (코드 82줄) 에 특정 코인을 추가를 하여 함수 gather data에 인자(argument)로 넣어줍니다.

 

 - 타겟 코인의 상태(state)(코드 50줄)를 보여주는 함수를 만들어 줍니다. states라는 dictionary를 만들어 상태를 나타내는 key로 관리    하며, key의 value 값은 아래와 같습니다. 

 

  만약 현재 가격이 Ub를 넘어서면 value == 'above'

          현재 가격이 Lb보다 작아지면 value == 'below'

          현재 가격이 Ub,Lb사이에 있다면 value == 'inside'

 

- ccxt api를 사용하여 레버리지 설정 (코드 62줄), 매수 (코드 70줄), 매도(코드 73줄) 하는 함수를 만들어 줍니다.  

 

 

-----------------  while문을 사용한 자동매매  -----------------

 

- 코인의 DataFrame을 1시간 간격으로 가격을 불러오기 때문에, 매시 0분 gather data 함수를 실행시켜 각 함수의 상태(above, below, inside)를 불러와줍니다. (코드 85줄)

 

- 매수를 할때는 Balance unit이 'USDT', 매도를 할때는 != 'USDT' 를 신호로 사용합니다. (코드 95, 107 줄)

 

- 호가창(Orderbook)에서 첫번째 매수호가(코드  97줄)를 불러와 Lb보다 낮고 state 가 'inside' 일 때 (코드 99줄), 레버리지를 설정하여 매수 주문을 합니다.

 

-  매수와 마찬가지로 호가창에 첫번째 매도가(코드 108줄)를 불러와 Ub보다 높고 state가 'inside' 일때 (코드 110줄) 매도 주문을 합니다. 또한 다음 매수를 위하여 balance unit 을 'USDT'로 바꿔줍니다.

 

- 매수가와 매도가에 들어가는 코인 수량은 코인별 약 5만원의 비용으로 테스트하기 위해

 

 

볼린저 밴드를 사용한 자동매매는 여기까지입니다. 벡테스팅 결과 아무래도 롱 포지션이기 때문에 하락장에 불리하게 작용하다는 단점이 있지만, 단순 상승장 및 횡보장의 경우 좋은 결과를 보내는 것을 벡테스팅을 통해 확인하였습니다. 질문 있으시면 댓글 남겨주시면 감사하겠습니다.

 

3. 출처

Automated Cryptocurrency Trading Bot with Python - Pt. 5 Live Trading on the Binance API - YouTube

 

 

 

 

***현재 위의 코드에서 몇가지 매끄럽게 진행되지 않은 부분이 있으므로 아래의 최종본을 참고하시길 바랍니다.*** (2022.09.04)

 

<볼린저 밴드를 사용한 롱,숏 포지션 자동매매 (최종ver)>

https://dongkyue-python.tistory.com/10