바이낸스(Binance) 웹 소켓을 이용한 트레이딩 환경 구축(2) – 체결 내역

체결내역 확인
체결내역 확인

저는 호가창과 체결 내역을 주시하다가 호가를 강하게 돌파하는 거래 대금을 확인하고 매매하는 트레이딩을 선호합니다. 이 방법은 거래 대금이 흐르는 방향으로 형성된 단기적인 추세에서 수익을 내는 방법입니다. 체결 내역은 단기적인 추세를 확인하기 가장 좋은 수단이라 생각하여 먼저 다루겠습니다.

이 글에서는 파이썬에서 체결 내역을 호출하고, 웹 페이지에서 데이터를 받아오는 과정을 진행하겠습니다.


파이썬 웹 소켓 호출 테스트

API key 정보 불러오기

바이낸스 웹 소켓을 호출하기 위해 API 키 정보가 필요한데, 키 정보는 .env 파일에 저장되어 있습니다.
.env이 없으신 분은 바이낸스(Binance) 웹 소켓을 이용한 트레이딩 환경 구축(1) – API 발급 및 프로젝트 생성 글을 참고해주세요.

1. python-dotenv 설치

python-dotenv를 설치합니다. 이 라이브러리를 사용하면 .env 파일에 정의된 환경 변수를 쉽게 읽어올 수 있습니다.

pip install python-dotenv
2. key 정보 불러오기

이 코드는 키 정보를 올바르게 불러오는지 확인하는 과정입니다.

import os
import dotenv

# .env 파일 로드
dotenv.load_dotenv()

# 환경 변수 가져오기
API_KEY = os.getenv("API_KEY")
SECRET_KEY = os.getenv("SECRET_KEY")

print(API_KEY)
print(SECRET_KEY)
웹 소켓 호출 테스트

파이썬 CCXT 라이브러리를 사용하여 데이터가 호출되는지 확인합니다.

1. CCXT 설치

CCXT Pro는 웹 소켓 기능을 제공하는 유료 버전이었지만 지금은 무료입니다.

pip install ccxt
2. 웹 소켓 호출

웹 소켓은 비동기 프로그래밍으로 async와 await를 사용하므로 asyncio.run() 함수를 사용하여 실행해야 합니다. 다음은 BTC/USDT의 체결 내역을 호출하는 코드입니다.

import os
import pprint
import dotenv

...

# 추가 코드 #
import asyncio
import ccxt.pro as ccxtpro

symbol = "BTC/USDT"

async def future_trades_socket():
    bs = ccxtpro.binance({
        'apiKey': API_KEY,
        'secret': SECRET_KEY,
        'enableRateLimit': True,
        'options': {
            'defaultType': 'future'
        }
    })

    while True:
        trades = await bs.watch_trades(symbol=symbol)
        pprint.pprint(trades)

  
asyncio.run(future_trades_socket())

무한 루프를 사용하여 바이낸스 서버에서 전송하는 데이터를 지속적으로 수신합니다.

3. 호출 결과
[{
    'info': {
        'e': 'trade',
        'E': 1711382210692, 
        'T': 1711382210691, 
        's': 'BTCUSDT', 
        't': 4797629541, 
        'p': '69797.10', 
        'q': '0.027', 
        'X': 'MARKET', 
        'm': True
    },
    'timestamp': 1711382210691, 
    'datetime': '2024-03-25T15:56:50.691Z', 
    'symbol': 'BTC/USDT:USDT', 
    'id': '4797629541', 
    'order': None, 
    'type': None, 
    'side': 'sell', 
    'takerOrMaker': None, 
    'price': 69797.1, 
    'amount': 0.027, 
    'cost': 1884.5217, 
    'fee': None, 
    'fees': []
},
...
...
]

수신한 데이터는 리스트 타입이며, 각 리스트의 원소 개수는 동일한 시간에 체결된 거래 수에 따라 달라집니다.


백엔드와 프론트엔드 간의 웹 소켓 통신 구현

아래는 Flask와 Socket.IO를 사용한 백엔드 와 프론트엔드 웹 소켓 구현 코드입니다.

백엔드
Flask 설치
pip install Flask Flask-SocketIO
python 코드 작성
# app.py

import os
import dotenv
import asyncio
import threading
import ccxt.pro as ccxtpro

from flask import Flask, render_template
from flask_socketio import SocketIO

dotenv.load_dotenv()

API_KEY = os.getenv("API_KEY")
SECRET_KEY = os.getenv("SECRET_KEY")

app = Flask(__name__)
socketio = SocketIO(app)

symbol = "BTC/USDT"


async def future_trades_socket():
    bs = ccxtpro.binance({
        'apiKey': API_KEY,
        'secret': SECRET_KEY,
        'enableRateLimit': True,
        'options': {
            'defaultType': 'future'
        }
    })

    while True:
        trades = await bs.watch_trades(symbol=symbol)
        time = trades[-1]['datetime']
        count = len(trades)
        price = trades[-1]['price']
        amount = 0

        for trade in trades:
            amount += trade['cost']

        socketio.emit('trades', {
            'time': time,
            'count': count,
            'price': price,
            'amount': amount,
        })


@app.route('/')
def index():
    return render_template('index.html')


if __name__ == '__main__':
    websocket_thread = threading.Thread(
        target=lambda: asyncio.run(future_trades_socket()))
    websocket_thread.start()
    socketio.run(app)
Data Flow 1

백엔드는 바이낸스 웹 소켓에서 데이터를 수신하는 동시에 프론트엔드에 데이터를 송신하는 웹 소켓 통신을 수행해야 합니다. 데이터를 수신하는 과정에서 무한 루프를 수행하기 때문에 새로운 스레드를 생성해줍니다.

프론트엔드
html 작성
<!-- templates/index.html -->

<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>binance_practice</title>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script>
    <script src="{{ url_for('static', filename='socket.js') }}"></script>
  </head>
  <body>
    <div>
      <h1>체결 내역</h1>
      <table id="tradesTable">
        <tr>
          <th>Time</th>
          <th>Count</th>
          <th>Price</th>
          <th>Amount</th>
        </tr>
      </table>
    </div>
  </body>
</html>
javascript 작성
// static/socket.js

var socket = io();

socket.on("trades", function (data) {
  updateTrades(data);
});

function updateTrades(data) {
  var table = document.getElementById("tradesTable");

  var time = data["time"];
  var count = data["count"];
  var price = parseFloat(data["price"]);
  var amount = parseFloat(data["amount"]);

  var row = table.insertRow(1);
  row.insertCell(0).innerHTML = convertToKSTAndFormatTime(time);
  row.insertCell(1).innerHTML = count;
  row.insertCell(2).innerHTML = price.toFixed(1);
  row.insertCell(3).innerHTML = Math.round(amount).toLocaleString("en-US");
}

function convertToKSTAndFormatTime(timeString) {
  var time = new Date(timeString);
  var kstOffset = 9 * 60 * 60 * 1000; // 한국 표준시(KST)의 밀리초 단위 시차 (UTC+9)

  // UTC 시간에 시차를 더하고 KST로 변환
  var kstTime = new Date(time.getTime() + kstOffset);

  // 시, 분, 초, 밀리초 추출
  var hours = kstTime.getUTCHours().toString().padStart(2, "0");
  var minutes = kstTime.getUTCMinutes().toString().padStart(2, "0");
  var seconds = kstTime.getUTCSeconds().toString().padStart(2, "0");
  var milliseconds = kstTime.getUTCMilliseconds().toString().padStart(3, "0");

  // "HH:mm:ss.SSS" 형식으로 시간을 반환
  return hours + ":" + minutes + ":" + seconds + "." + milliseconds;
}
실행

app.py 파일을 실행하여 서버를 실행합니다.

python app.py

127.0.0.1:5000로 접속하면 실시간으로 업데이트 되는 체결 내역을 확인할 수 있습니다.
아래 동영상은 웹 소켓으로 수신한 체결 내역과 바이낸스 사이트의 체결 내역(Trades)을 동시에 녹화한 영상입니다.

동영상을 보면 오른쪽의 체결 내역이 좀 더 느린 것을 확인할 수 있는데, 이는 바이낸스가 일정 기간 동안 집계된 스냅샷 데이터를 제공하기 때문입니다.

영화 및 TV 2024 04 04 오후 8 51 21 1

영상의 일부를 살펴보면, 빨간 박스의 ‘Amount’ 합이 오른쪽 바이낸스 Trades와 동일한 것을 확인할 수 있습니다. 이를 통해 바이낸스가 일정 기간 동안(빨간 박스)의 집계된 데이터를 제공한다는 것을 확인했습니다.

현재 코드는 지속적으로 데이터를 생성하기 때문에 렉을 유발하고, 원래 목적인 트레이딩 보조에는 적합하지 않습니다. 다음 글에선 이 부분을 수정하겠습니다.

작성된 코드는 깃 허브에서 확인할 수 있습니다.