저는 호가창과 체결 내역을 주시하다가 호가를 강하게 돌파하는 거래 대금을 확인하고 매매하는 트레이딩을 선호합니다. 이 방법은 거래 대금이 흐르는 방향으로 형성된 단기적인 추세에서 수익을 내는 방법입니다. 체결 내역은 단기적인 추세를 확인하기 가장 좋은 수단이라 생각하여 먼저 다루겠습니다.
이 글에서는 파이썬에서 체결 내역을 호출하고, 웹 페이지에서 데이터를 받아오는 과정을 진행하겠습니다.
파이썬 웹 소켓 호출 테스트
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)
백엔드는 바이낸스 웹 소켓에서 데이터를 수신하는 동시에 프론트엔드에 데이터를 송신하는 웹 소켓 통신을 수행해야 합니다. 데이터를 수신하는 과정에서 무한 루프를 수행하기 때문에 새로운 스레드를 생성해줍니다.
프론트엔드
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)을 동시에 녹화한 영상입니다.
동영상을 보면 오른쪽의 체결 내역이 좀 더 느린 것을 확인할 수 있는데, 이는 바이낸스가 일정 기간 동안 집계된 스냅샷 데이터를 제공하기 때문입니다.
영상의 일부를 살펴보면, 빨간 박스의 ‘Amount’ 합이 오른쪽 바이낸스 Trades와 동일한 것을 확인할 수 있습니다. 이를 통해 바이낸스가 일정 기간 동안(빨간 박스)의 집계된 데이터를 제공한다는 것을 확인했습니다.
현재 코드는 지속적으로 데이터를 생성하기 때문에 렉을 유발하고, 원래 목적인 트레이딩 보조에는 적합하지 않습니다. 다음 글에선 이 부분을 수정하겠습니다.
작성된 코드는 깃 허브에서 확인할 수 있습니다.