안녕하세요 요번시간은 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