돈벌고싶다

바이비트와 파이썬을 이용한 자동매매 프로그램 - 4. 변동성 돌파 전략 본문

자동매매-바이비트

바이비트와 파이썬을 이용한 자동매매 프로그램 - 4. 변동성 돌파 전략

coinwithpython 2022. 6. 24. 01:25
728x90
반응형

설명

이번 글에서는 자동매매 구현 문제 1번으로 나오는, 변동성 돌파 전략을 구현하겠습니다. 하지만 다른 블로그에서도 쉽게 볼 수 있는 기본적인 변동성 돌파 전략만을 구현하는 것은 아니고, 아래와 같이 몇 가지 개선점이라 생각되는 것을 추가하였습니다.

 

  1. 매 순간 최근 일주일 데이터를 통해 최적의 K값을 찾아 진입 가격을 설정
  2. 자산을 8분할 하여 0시에 한 번, 3시에 한번, 6시에 한번, ..... 이런식으로 분할 매수
  3. 5일, 15일, 30일 이동평균선에 따라 레버리지 비율 상승

 

위 기능들을 통해 다양한 이점을 가져올 수 있습니다. 일단 최적의 K값을 매 번 갱신하여 급변하는 시장에 대응이 좋으며, 분할 매수를 통해 특정 시간대에 대한 대형 사고를 피할 수 있고, 이동평균선보다 변동성 돌파 가격이 높고 낮음에 따라 레버리지를 다르게 두어 변동성 돌파 전략이 강세를 보이는 상승장에서는 더욱 과감한 투기를 할 수 있도록 하였습니다. 코드는 다음과 같은 흐름으로 진행되었습니다.

 

  1. 정의해야 할 라이브러리, 변수, 함수 등을 먼저 정의
  2. 반복문 시작
  3. 설정한 주문 시간이 아닐 경우 아무 행동 없이 1분 time sleep
  4. 설정한 주문 시간이 왔을 경우 한시간봉 데이터를 받아오기
  5. 데이터를 일봉 데이터로 통합
  6. 최적의 k값 및 변동성 돌파 가격 산출
  7. 이동평균선을 구해 레버리지 설정
  8. 기존 조건부주문 취소
  9. 기존 조건부주문이 취소되지 않을 경우 주문이 진행됐었다는 뜻으로 포지션 정리
  10. 새로운 조건부주문 실행

 

코드적으로 미숙한 부분이 많이 있기 때문에, 혹시 문제가 되는 부분이 있다면 댓글로 알려주세요.

 


코드

 

1. 라이브러리 호출

# 라이브러리
from pybit import usdt_perpetual
import numpy as np
import pandas as pd
import schedule
import time
import datetime
import calendar
import math

 

 

2. 필요 함수 호출

# 교차 격리 마진을 설정하는 함수
def set_margin_switch(symbol, is_isolated=True):
    try:
        session.cross_isolated_margin_switch(
            symbol=symbol,
            is_isolated=is_isolated,
        )
    except:
        pass


# 레버리지를 설정하는 함수
def set_leverage(data, target_price):
    # get leverage : 5일평균선, 15일평균선, 30일평균선
    data['ma5'] = data['close'].rolling(window=5).mean()
    data['ma15'] = data['close'].rolling(window=15).mean()
    data['ma30'] = data['close'].rolling(window=30).mean()
    leverage = 1
    if target_price > data['ma5'][len(data)-1]:
        leverage = 1
    if target_price > data['ma15'][len(data)-1]:
        leverage = 1.5
    if target_price > data['ma30'][len(data)-1]:
        leverage = 2
    try:
        session.set_leverage(
            symbol=symbol,
            buy_leverage=leverage,
            sell_leverage=leverage
        )
    except:
        pass


# 데이터를 불러오는 함수
def get_data(symbol, interval, limit):
    unixtime = calendar.timegm(now.utctimetuple())
    since = unixtime-interval*60*limit;
    response = session.query_kline(symbol=symbol,interval=str(interval), from_time=since, limit=limit)['result']
    return pd.DataFrame(response)


# 데이터를 재구성해주는 함수
def data_reconstruction(symbol, interval, limit):
    data = get_data(symbol, interval, limit)
    for n in range(3):
        unixtime = data['start_at'][0]
        since = unixtime-interval*60*limit;
        response = session.query_kline(symbol=symbol,interval=str(interval), from_time=since, limit=limit)['result']
        df = pd.DataFrame(response)
        data = pd.concat([df, data])
        time.sleep(0.2)
    data.sort_values(by='start_at', ascending=False, inplace=True)
    data.reset_index(drop=True, inplace=True)
    # 한시간봉 데이터를 일봉 데이터로 통합
    low = data['low'].rolling(window=24).min()
    high = data['high'].rolling(window=24).max()
    volume = data['volume'].rolling(window=24).sum()
    np_temp = np.zeros((round(len(data)/24), 6))
    for n in range(1, len(np_temp)):
        np_temp[n][0] = data['start_at'][(n-1)*24]      # 적용된 가장 최근 데이터에 대한 시각
        np_temp[n][1] = data['open'][n*24-1]            # 시가
        np_temp[n][2] = high[n*24-1]                    # 지난 24시간 중 최고가
        np_temp[n][3] = low[n*24-1]                     # 지난 24시간 중 최저가
        np_temp[n][4] = data['close'][(n-1)*24]         # 종가
        np_temp[n][5] = volume[n*24-1]                  # 지난 24시간동안 거래량
    data = pd.DataFrame(np_temp, columns=['start_at', 'open', 'high', 'low', 'close', 'volume'])
    data = data.iloc[1:]
    data.sort_values(by='start_at', ascending=True, inplace=True)
    data.reset_index(drop=True, inplace=True)
    return data


# 변동성 돌파 전략 구현을 위한 target값 계산 함수
class breaking_volatility:
    def __init__(self, df):
        self.df = df
        self.fee = 0.00048

    def get_crr(self, k):
        self.df['range'] = (self.df['high'] - self.df['low']) * k
        self.df['target'] = self.df['open'] + self.df['range'].shift(1)
        self.df['drr'] = np.where(self.df['high'] > self.df['target'], self.df['close'] / self.df['target'] - self.fee * 2, 1)
        return self.df['drr'][24:].cumprod().iloc[-1]
    
    def get_bestK(self):
        # k값을 계산
        best_K = 0.5
        max_crr = self.get_crr(best_K)
        for k in np.arange(0.1, 1.0, 0.1):
            crr = self.get_crr(k)
            if crr > max_crr :
                max_crr = crr
                best_K = k
        return best_K

    def get_target_price(self, k):
        yesterday = self.df.iloc[-1]
        today_open = yesterday['close']
        yesterday_high = yesterday['high']
        yesterday_low = yesterday['low']
        target = today_open + (yesterday_high - yesterday_low) * k
        return target

 

자주 쓰일 함수를 정의합니다.

  • set_margin_switch : 교차격리 마진 설정을 합니다.
  • set_leverage : 레버리지 설정을 합니다.
  • get_data : 데이터를 불러오기 위한 기본 함수입니다.
  • data_reconstruction : 슬리피지를 줄이기 위해 시간에 따라 분할매수를 하기로 하였습니다. 따라서 각 시간에 따라 기준이 바뀌는 데이터가 생성되어야 하는데, 기존의 데이터를 1시간 봉으로 가져올 경우 0시에 시작하여 24시에 끝나는 일봉으로 고정된 데이터만을 받아 올 수 있습니다. 해당 함수를 사용하여 데이터를 받아올 경우 3시에는 3시에 시작하여 3시에 끝나는 데이터를 생성할 수 있습니다.
  • breaking_volatility : 변동성 돌파 전략을 구현하기 위한 함수로, get_crr는 최근 일주일간의 수익률을, get_bestK는 계산되는 수익률을 통해 최적의 k값을, get_target_price는 최종 변동성 돌파에 사용되는 target price 값을 출력합니다.

 

 

3. 변수 및 마진 설정

# 필요 변수 정의.
symbol = "BTCUSDT"                              # 코인명
interval = 60                                   # 데이터 기준
limit = 200                                     # 데이터양
qty = 0.01                                      # 주문 수량
order_check = [0 for n in range(24)]            # 주문여부
order_id_list = [0 for n in range(24)]          # 조건부주문 id 리스트
hour_list = [1, 4, 7, 10, 13, 16, 19, 22]       # 실행 시간. 조건부주문일 경우 10개 이상 쌓일 수 없다. 따라서 하루에 8번까지만 쌓일수 있도록 설정

# api 정보 입력
session = usdt_perpetual.HTTP(
    endpoint="https://api-testnet.bybit.com", 
    api_key='my_api_key', 
    api_secret='my_secret_key',
)

# 격리 마진 설정
set_margin_switch(symbol, True)

 

필요한 변수들과, API 정보, 그리고 마진을 미리 설정합니다.

 

 

4. 자동매매 시작

# 거래 시작
while True:
    # 현재 시간을 확인
    now = datetime.datetime.utcnow()
    print(now.hour, now.minute)
    if (now.hour in hour_list) & (59 <= now.minute < 60):
        # 데이터를 불러온다.
        data = data_reconstruction(symbol, interval, limit)
        print('데이터를 문제 없이 받아왔는지 확인')
        print('데이터가 업로드된 가장 최근 시각(hour) :', datetime.datetime.fromtimestamp(data['start_at'][len(data)-1]).strftime("%Y-%m-%dT%H:%M:%S"))
        print('데이터로 받은 현재 코인 가격 :', data['close'][len(data)-1]) # 슬리피지로 인한 약간의 차이 존재

        bv = breaking_volatility(data)                # 위에서 생성한 클레스 호출
        best_k = bv.get_bestK()                       # 최적의 k값을 계산
        target_price = bv.get_target_price(best_k)    # 주문을 넣을 가격을 산출
    
        # 레버리지 설정
        set_leverage(data, target_price)
        
        # 기존 조건부주문 취소
        try:
            session.cancel_conditional_order(
                symbol=symbol,
                order_link_id=order_id_list[now.hour]
            )
            print(now.strftime("%H:%M:%S"), '에 시간조건 long 조건부주문 취소')
        except:
            # 조건부주문이 취소되지 않을 경우 매수가 진행된 상황으로, 매도를 진행
            try:
                session.place_active_order(
                    symbol=symbol,
                    side='Sell',
                    order_type="Market",
                    qty=qty,
                    time_in_force="GoodTillCancel",
                    reduce_only=True,
                    close_on_trigger=True,
                )
                print(now.strftime("%H:%M:%S"), '에 시간조건 long 청산')
            except:
                # 첫 주문일 경우 위 두가지 상황 모두 해당 되지 않아 pass로 마무리
                pass

        # 조건부주문 시작
        order_id_list[now.hour] = str(now)
        try:
            session.place_conditional_order(
                symbol=symbol,
                side='Buy',
                order_type="Market",
                qty=qty,
                base_price=target_price,
                stop_px=target_price+5,
                trigger_by='LastPrice',
                time_in_force="GoodTillCancel",
                reduce_only=False,
                close_on_trigger=False,
                order_link_id=order_id_list[now.hour],
            )
            print(now.strftime("%H:%M:%S"), '에 $', target_price, 'long 조건부주문')
        except:
            print(now.strftime("%H:%M:%S"), '에 $', target_price, 'long 조건부주문 과정에서 error 발생')
    
        print('='*30)
    time.sleep(10)

 

코드가 실행될 분은 반드시 59~60분으로 설정해야 합니다. 0분에서 1분 사이에 실행하고 싶을 경우 데이터 재구성 부분을 수정하면 가능합니다. 데이터셋에서 볼 수 있는 가장 최근 시각 데이터와 위 조건부주문이 실행되었다고 print 되는 부분에서 9시간 정도의 차이가 존재합니다. 이유는 파이썬 datetime 라이브러리에서 불러오는 시간은 한국 기준보다 9시간 차이나기 때문입니다. 데이터를 불러오는 것에 있어서는 최신 데이터를 받아오는 것이 맞기 때문에 걱정할 필요 없으며, 조건부주문이 들어간 순간의 print도 한국시간과 맞추고 싶을 경우 print 부분 코드를 아래와 같이 변경하면 됩니다. now 변수의 정의를 수정할 경우 예상치 못할 오류가 발생할 수 있습니다.

 

print((now+datetime.timedelta(hours=9)).strftime("%H:%M:%S"), '에 $', target_price, 'long 조건부주문')
728x90
반응형
Comments