파이썬/바이낸스 선물 웹소켓

[선물 WS] 주문 체결, 지갑 정보 전달받기 (User Data Stream)

Eluv 2023. 6. 12. 07:55

API나 웹소켓을 다룰 때 주문, 계좌 정보와 같이

개인정보에 해당하는 내용을 다룰땐 API키가 필요합니다.

API키 발급 방법은 바이낸스 API키 발급 참조

 

import requests
import asyncio
import websockets
import json

with open("D:/코인/binance key.txt") as f:
    lines = f.readlines()
    api_key = lines[0].strip()
    secret_key = lines[1].strip()

headers = {
    "X-MBX-APIKEY": api_key,
}

FUTURES_STREAM_END_POINT_1 = "wss://fstream.binance.com"
BINANCE_FUTURES_END_POINT = "https://fapi.binance.com/fapi/v1/listenKey"

def create_futures_listen_key():
    response = requests.post(url=BINANCE_FUTURES_END_POINT, headers=headers)
    return response.json()['listenKey']

listen_key = create_futures_listen_key()

async def UserDataStream():
    uri = f"{FUTURES_STREAM_END_POINT_1}/ws/{listen_key}"
    async with websockets.connect(uri) as websocket:
        try:
            while True:
                data = json.loads(await websocket.recv())
                print(data)
        except asyncio.CancelledError:
            print("UserDataStream 작업이 취소되었습니다.")
        except Exception as e:
            print(f"UserDataStream 예외가 발생했습니다: {e}")
        finally:
            print("UserDataStream 웹소켓 연결을 종료합니다.")
            await websocket.close()

async def ping_listen_key():
    while True:
        await asyncio.sleep(1800)  # 30분 간격으로 ping 전송
        response = requests.put(url=BINANCE_FUTURES_END_POINT, headers=headers)
        if response.status_code == 200:
            print('Ping sent successfully')
        else:
            print('Failed to send ping')

async def main():
    tasks = [
        asyncio.ensure_future(UserDataStream()),
        asyncio.ensure_future(ping_listen_key())
    ]
    try:
        await asyncio.gather(*tasks)
    except asyncio.CancelledError:
        print("메인 작업이 취소되었습니다.")
        for task in tasks:
            task.cancel()
        await asyncio.gather(*tasks, return_exceptions=True)
    except Exception as e:
        print(f"예외가 발생했습니다: {e}")
    finally:
        print("메인 작업이 종료되었습니다.")

if __name__ == '__main__':
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        print("프로그램을 종료합니다.")

 

참조.

Python을 사용한 스트리밍 바이낸스 주문 업데이트 | by 쿠쿠 카밀 | 코인몽크스 | 보통 (medium.com)

User Data Streams – Binance API Documentation (binance-docs.github.io)

 

공식 설명에도 나와있듯이 60분이 지나면 연결이 자동 종료됩니다.

그래서 ping_listen_ket()함수에서 1800초(30분)에 한번씩

재연결을 하도록 설계되어 있습니다.

 

 

주문을 넣을 때 해당 웹소켓으로 전달받는 데이터입니다.

{"e":"ORDER_TRADE_UPDATE", #이벤트 타입
"T":1686517295922, # 시간
"E":1686517295926, # 이벤트 시간
"o":{"s":"ETHUSDT", # 종목
       "S":"BUY",  # 주문 타입 
       "o":"LIMIT", # 지정가. 시장가는 MARKET
       "q":"0.020", # 주문 수량
       "p":"1700", # 주문 가격
       "ap":"0", # 해당 주문이 체결된 평균 가격. 전체 포지션의 평단가가 아님
       "x":"NEW", # 주문 유형, NEW는 새 주문 입력, TRADE는 주문 체결 
       "X":"NEW", # 주문 상태, NEW는 새 주문 입력, FILLED는 체결 완료
       "i":8389765602604928560, # 주문 id
       "ps":"LONG", # 포지션 방향
       
       ...
      'rp': '-0.05000000'  # 실현손익. 포지션 정리 주문 외엔 기본적으로 0
}}

 

위의 데이터에서 주로 사용하는 것은 's', 'q', 'p' 입니다.

어떤 코인을 몇개씩, 얼마에 주문 넣었는지 알 수 있습니다.

( data['o']['s'],  data['o']['q'],  data['o']['p'] )

 

포지션 정리 주문을 넣을 때 실현손익은 'rp' 입니다.

( data['o']['rp'] )

 

선물은 롱, 숏 양방향입니다.

Long, Buy는 롱 포지션 진입.

Long, Sell은 롱 포지션 정리.

Short, Sell은 숏 포지션 진입.

Short, buy는 숏 포지션 정리.

 

데이터들은 전부 문자열 형태로 되어 있기 때문에

숫자로 사용하기 위해선 형변환을 해야합니다.

float( data['o']['q'] )

 

data['o']['s']는 코인 이름이기 때문에 숫자로 형변환을 하면 안됩니다.

 

 

주문이 체결되는 등의 이벤트로 계좌에 변동에 생기면

다음의 데이터를 전달받게 됩니다.

{"e":"ACCOUNT_UPDATE",
"T":1686518836619,
"E":1686518836631,
"a":{
      "B" : [ { "a":"USDT",  # 현금. 정확히는 USDT 스테이블 코인
                  "wb":"146.84106420", # 지갑 잔액
                  "cw":"146.84106420", # 교차 지갑 잔액
                  "bc":"0"}],
      "P" : [ { "s":"ETHUSDT", # 보유 코인
                  "pa":"0.010", # 보유 수량. short일 경우 "-0.010"으로 표기됨
                  "ep":"1769.00000000", # 평단가
                  "cr":"28.50606903", # 누적 실현 손익
                  "up":"0.00190000", # 미실현 손익
                  ...
                  "ps":"LONG", # 포지션 방향
                  "ma":"USDT" } ],
      "m":"ORDER"}}

 

여기에서 가장 많이 쓰이는 것은 지갑 잔액, 보유 코인, 수량, 평단가 네가지입니다.

( data['a']['B'][0]['wb'],  data['a']['P'][0]['s'], data['a']['P'][0]['pa'], data['a']['P'][0]['ep'] )

 

마찬가지로 잔고, 수량, 평단가는 float으로 형변환을 해줘야 합니다.