본문 바로가기
System Trading

바이낸스 페어 아비트라지 봇 빌딩하기 - 급락, 급등 회피 알고리즘 추가하기

by raacers 2022. 12. 22.

바이낸스 페어 아비트라지 봇 빌딩하기 - 급락, 급등 회피 알고리즘 추가하기

 

1. 문제 상황 분석하기

스몰사이즈 매매로 검증를 계속 진행하면서 1% 내외의 수익과 손실을 반복적으로 기록하였는데, 수익이 나는 거래가 더 많아서 계좌 잔고가 꾸준히 우상향하고 있었습니다. 다만, 이때까지는 모니터링을 주기적으로 하면서 수치가 안좋아지거나 하면 바로 손절하곤 했습니다. 문제 상황은 검증을 위해 거래를 많이 열어 놓고 자는 사이 발생했습니다.

 

두 종목의 산포도와 표준 점수 그래프인데 둘 다 급격히 상승하는 것을 볼 수 있습니다. 알트 종목 하나가 아비트라지 거래 방향 반대로 급격하게 움직인 것입니다. 시장 전체가 악재나 호재로 급격하게 움직일 때는 아비트라지 거래 종목 2개도 같은 방향으로 대부분 움직입니다. 시장 전체가 이벤트 없이 잔잔할 때 위와 같이 어느 한 종목이 급격하게 움직일 때가 가끔 있습니다.

 

어느 특정 종목의 호재나 악재가 발생하여 급격하게 움직이는 경우도 있고, 알트 종목 하나가 이유없이 급등하는 경우가 있습니다. 위 사례는 아비트라지 거래 중 Short 방향으로 거래를 시작한 종목이 이유없이 혼자서 50% 급등한 경우입니다. 반대로 이유없이 혼자서 급락하는 종목은 거의 못 봤습니다. 이유가 있거나 시장 악재로 다같이 급락하는 경우 둘 중에 하나라고 생각합니다. 이유없이 급락했다면 곧 제자리로 돌아올 것입니다.

 

만약 아비트라지 거래 Long 방향의 종목이 급등했다면 큰 수익을 얻었겠지만 Short 방향의 종목이 급등했고, 자느라 대응을 하지 못해 손실을 보게 되었습니다. 이번과 같은 상황을 방지하기 위해, 봇에 알고리즘을 추가할 필요가 있습니다.

 

 

2. 봇에 거래 방향과 반대 방향의 급등, 급락 회피 알고리즘 추가하기

우선 지금 봇이 가지고 있는 기능을 활용하는 방법으로 회피 알고리즘을 추가하려고 합니다. 그리고 혹시 작동하지 않을 상황을 대비해 예비 알고리즘을 더 추가하려고 합니다.

 

알고리즘으로 거래를 자동으로 시작, 종료하기 위해 기존에 배열만 사용하던 현재 거래 중 목록의 데이터 구조를 다음과 같이 변경하였습니다.

open_position_data = [
    {
        "sym_1": "HNTUSDT",
        "sym_2": "TOMOUSDT",
        "opening_z_score": 0,
        "current_z_score": 0,
        "closing_PNL": 0,
        "closing_ROE": 0,
        "opening_date": 0,
        "closing_date": 0,
    },
]

 

2.1 주 회피 알고리즘 작성하기

봇은 현재 모든 거래의 이익률(ROE, Return On Equity)들을 실시간으로 계산하고 있습니다. 이 이익률이 -1.1%를 넘어서면 거래를 자동으로 종료하는 알고리즘을 작성합니다. 현재 ROE를 계산하는 부분은 다음과 같습니다. 현재 거래 중 목록 전체를 받아서 모든 거래의 PNL을 바이낸스 API로 받아옵니다. API에 요청하려면 종목명이 필요합니다. 아래와 같이 for문으로 모든 거래의 종목명을 뽑아서 API에 요청을 보냅니다.

for k, v in self.open_position_data.items():
    response_sym_1 = self.um_futures_client.get_position_risk(symbol=v["sym_1"], recvWindow=5000)
    response_sym_2 = self.um_futures_client.get_position_risk(symbol=v["sym_2"], recvWindow=5000)

 

응답받은 데이터 안에 [0]["unRealizedProfit"]에 PNL 값이 저장되어 있습니다. PNL 값을 가져오기 전에 두 종목의 거래 수량이 0이 아닌지 확인하여 거래 중인 종목임을 한번 더 확인합니다. API에서 받는 모든 값은 문자열이기 때문에 float 함수로 실수로 변환해야 합니다.

if response_sym_1[0]["notional"] != "0" and response_sym_2[0]["notional"] != "0":
    pair_pnl = float(response_sym_1[0]["unRealizedProfit"]) + float(response_sym_2[0]["unRealizedProfit"])

 

두 종목의 PNL을 합치면 아비트라지 거래의 PNL을 구할 수 있고, 이를 활용하여 아래와 같이 ROE도 계산할 수 있고 결과를 배열에 차례대로 저장합니다.

pair_roe = (pair_pnl / (abs(float(response_sym_1[0]["notional"])) + abs(float(response_sym_1[0]["notional"])))) * 100
pair_pnls_roes.append({"pnl": pair_pnl, "roe": pair_roe})

 

저장이 끝나면 여기에 알고리즘을 추가하여 ROE가 -1.1% 이상이면 거래를 바로 종료하도록 작성해줍니다. 작성한 거래 종료 함수를 재사용하여 간단하게 작성할 수 있습니다. 1.1% 이상일 때도 마찬가지로 종료하도록 작성합니다.

# Roe -1.1% 이상 발생 시 자동으로 거래를 종료하고 거래 중 목록 파일에서도 삭제합니다.
if pair_roe < -1.1:
    close_order(v["sym_1"])
    close_order(v["sym_2"])
    
    delete_close_position_from_json(i)

 

그리고 아래처럼 함수를 하나 작성하여 거래 중 목록을 저장하여 실시간으로 관리하고 있는 json 파일에서 거래를 종료한 페어를 삭제합니다.

def delete_close_position_from_json(index):
    with open("open_position_data.json") as json_file:
        open_position_data = json.load(json_file)

    del open_position_data[index]

    with open("open_position_data.json", 'w') as json_file:
        json_file.write(json.dumps(open_position_data, indent=4))

 

2.2 보조 회피 알고리즘 작성하기

아래 명령어로 바이낸스 API에 원하는 종목의 24시간 동안의 가격 비율 변화를 요청하여 받을 수 있습니다. 인수로 종목명을 전달하지 않으면 바이낸스 거래소의 모든 종목에 대해 요청하게 되는데, 이는 요청 제한 주기가 굉장히 길어서 주의해야 합니다.

res = um_futures_client.ticker_24hr_price_change("HNTUSDT")

 

24시간 가격 비율 변화를 실시간으로 받는 쓰레드를 작성합니다. 아래처럼 거래 중 목록 데이터를 받아서 목록 안의 종목명들을 for문으로 하나씩 꺼낼 수 있도록 작성합니다.

class Get24hrPriceChange(QThread):
    dataSent = pyqtSignal(list)

    def __init__(self, open_position_data):
        super().__init__()
        self.open_position_data = open_position_data
        self.alive = True

    def run(self):
        while self.alive:
            price_changes_24hr = []

            for i, v in enumerate(self.open_position_data):
                sym_1 = v["sym_1"]
                sym_2 = v["sym_2"]
                
            self.dataSent.emit(price_changes_24hr)
            time.sleep(10)

    def close(self):
        self.alive = False

 

하나씩 꺼낸 종목명들의 24시간 가격 비율 변화를 아래와 같이 바이낸스 API로 요청하여 받습니다. 비교를 위해 비트코인의 가격 비율 변화도 함께 요청하여 받습니다.

res_1 = self.um_futures_client.ticker_24hr_price_change(sym_1)
res_2 = self.um_futures_client.ticker_24hr_price_change(sym_2)
res_BTC = self.um_futures_client.ticker_24hr_price_change("BTCUSDT")

price_change_24hr_sym_1 = float(res_1["priceChangePercent"])
price_change_24hr_sym_2 = float(res_2["priceChangePercent"])
price_change_24hr_sym_BTC = float(res_BTC["priceChangePercent"])

 

받아온 가격 비율 변화를 이용하여 한 종목 혼자서 급격히 움직였는지 여부를 확인합니다. 비트코인의 24시간 가격 비율 변화가 2.5% 이하인데, 한 종목의 가격 비율 변화가 10% 이상 넘어가는 상황이 발생했는지 확인합니다. 만약 발생했다면 발생한 거래 페어의 배열 인덱스 번호를 메인 쓰레드로 전달합니다.

if price_change_24hr_sym_1 > 10 and price_change_24hr_sym_BTC < 2.5:
    alert_signal_index = i
if price_change_24hr_sym_2 > 10 and price_change_24hr_sym_BTC < 2.5:
    alert_signal_index = i

 

메인 쓰레드에서 전달 받은 배열 인덱스 번호로 해당 거래를 아래와 같이 작성하여 종료할 수 있습니다.

def close_price_change_position(self, alert_position_index):
    print(f"{alert_position_index + 1} 번째 페어 급격한 가격 변화 원인으로 거래를 종료합니다")
    if alert_position_index >= 0:
        close_order(self.open_position_data[alert_position_index]["sym_1"])
        close_order(self.open_position_data[alert_position_index]["sym_2"])

 

댓글