서론: 왜 마이그레이션이 필요한가?
암호화폐 고빈도 트레이딩과 알고리즘 트레이딩을 수행하는 팀이라면, Binance L2 오더북 데이터를 안정적으로 확보하는 것이 필수입니다. 저는 과거 Tardis.dev를 포함한 여러 마켓데이터 공급자를 테스트해보며 지연 시간, 데이터 품질, 비용 문제로 상당한 어려움을 겪었습니다.
본 가이드는 Tardis.dev 및 기타 마켓데이터 공급자에서 HolySheep AI로 마이그레이션하는 방법과 함께, Binance L2 오더북 CSV 다운로드 및 Python 백테스팅 파이프라인을 구축하는 방법을 상세히 다룹니다. HolySheep AI는 AI/LLM API 게이트웨이이지만, 본 가이드에서는 백테스팅 로직에 AI를 활용하는 현대적 접근법을 함께 소개합니다.
핵심 메시지: HolySheep AI는 Binance 오더북 데이터를 직접 제공하지 않습니다. 그러나 AI 기반 백테스팅 분석, 전략 최적화, 자연어 기반 트레이딩 시그널 해석에 최적화된 LLM 통합을 제공하여 기존 마켓데이터 인프라와 시너지를 만들어냅니다.
마이그레이션 플레이북 개요
왜 타供应商에서 HolySheep로?
기존 마켓데이터 공급자의 한계:
- Tardis.dev: 실시간 데이터는 우수하나 월 $500+의 비용, CSV 내보내기 제한
- 공식 Binance API: 공개용 제한(1분당 1200リクエスト), 커넥션당 5最大
- 기타 제공자: 데이터 품질 편차, 레거시 REST 구조, 실시간 웹소켓 불안정
HolySheep AI 선택 이유:
- 해외 신용카드 불필요한 로컬 결제 지원
- 단일 API 키로 다중 AI 모델(GPT-4.1, Claude, Gemini, DeepSeek) 통합
- 백테스팅 결과 분석에 LLM 활용 시 비용 최적화(GPT-4.1 $8/MTok)
- 개발자 친화적 SDK 및 빠른 응답 시간
Binance L2 오더북 데이터 다운로드: 완전한 Python 가이드
1단계: 환경 설정
# requirements.txt
pandas>=2.0.0
numpy>=1.24.0
websocket-client>=1.6.0
requests>=2.31.0
aiohttp>=3.9.0
holysheep>=1.0.0 # HolySheep AI SDK
설치
pip install -r requirements.txt
2단계: Binance L2 오더북 REST API로 CSV 다운로드
import requests
import pandas as pd
from datetime import datetime
import time
class BinanceOrderbookDownloader:
"""Binance L2 오더북 CSV 다운로드 및 저장"""
BASE_URL = "https://api.binance.com/api/v3"
def __init__(self, symbol='btcusdt', depth=100):
self.symbol = symbol.lower()
self.depth = depth # 5, 10, 20, 50, 100, 500, 1000, 5000
def get_orderbook_snapshot(self, limit=100):
"""오더북 스냅샷 조회 (REST API)"""
endpoint = f"{self.BASE_URL}/depth"
params = {
'symbol': self.symbol.upper(),
'limit': limit
}
response = requests.get(endpoint, params=params)
response.raise_for_status()
data = response.json()
# DataFrame 변환
df_bids = pd.DataFrame(data['bids'],
columns=['price', 'quantity'],
dtype=float)
df_asks = pd.DataFrame(data['asks'],
columns=['price', 'quantity'],
dtype=float)
# 타임스탬프 추가
timestamp = pd.to_datetime(datetime.now())
return {
'timestamp': timestamp,
'bids': df_bids,
'asks': df_asks,
'lastUpdateId': data.get('lastUpdateId')
}
def save_to_csv(self, data, filepath=None):
"""오더북 데이터를 CSV로 저장"""
if filepath is None:
timestamp_str = data['timestamp'].strftime('%Y%m%d_%H%M%S')
filepath = f"orderbook_{self.symbol}_{timestamp_str}.csv"
# bids와 asks를 하나의 DataFrame으로 결합
bids_df = data['bids'].copy()
bids_df['side'] = 'bid'
asks_df = data['asks'].copy()
asks_df['side'] = 'ask'
combined_df = pd.concat([bids_df, asks_df], ignore_index=True)
combined_df['timestamp'] = data['timestamp']
combined_df['symbol'] = self.symbol
combined_df.to_csv(filepath, index=False)
print(f"저장 완료: {filepath}")
return filepath
사용 예시
downloader = BinanceOrderbookDownloader(symbol='ethusdt', depth=100)
orderbook_data = downloader.get_orderbook_snapshot()
csv_path = downloader.save_to_csv(orderbook_data)
print(f"최근 Bid 가격: {orderbook_data['bids']['price'].iloc[0]}")
print(f"최근 Ask 가격: {orderbook_data['asks']['price'].iloc[0]}")
print(f"스프레드: {float(orderbook_data['asks']['price'].iloc[0]) - float(orderbook_data['bids']['price'].iloc[0])}")
3단계: 실시간 웹소켓으로 L2 오더북 스트리밍
import websocket
import json
import pandas as pd
from datetime import datetime
import threading
class BinanceWebSocketOrderbook:
"""Binance 웹소켓을 통한 실시간 L2 오더북 수신"""
def __init__(self, symbol='btcusdt', on_update=None):
self.symbol = symbol.lower()
self.on_update = on_update
self.ws = None
self.orderbook_bids = {}
self.orderbook_asks = {}
self.running = False
self.thread = None
def on_message(self, ws, message):
data = json.loads(message)
if data.get('e') == 'depthUpdate':
self._process_update(data)
def _process_update(self, data):
"""오더북 업데이트 처리"""
# bids 업데이트
for price, qty in data.get('b', []):
price = float(price)
qty = float(qty)
if qty == 0:
self.orderbook_bids.pop(price, None)
else:
self.orderbook_bids[price] = qty
# asks 업데이트
for price, qty in data.get('a', []):
price = float(price)
qty = float(qty)
if qty == 0:
self.orderbook_asks.pop(price, None)
else:
self.orderbook_asks[price] = qty
# 콜백 실행
if self.on_update:
self.on_update({
'timestamp': datetime.now(),
'bids': self.orderbook_bids.copy(),
'asks': self.orderbook_asks.copy(),
'lastUpdateId': data.get('u')
})
def on_error(self, ws, error):
print(f"WebSocket 오류: {error}")
def on_close(self, ws, close_status_code, close_msg):
print(f"WebSocket 연결 종료: {close_status_code}")
self.running = False
def on_open(self, ws):
print(f"WebSocket 연결됨: {self.symbol}")
# 구독 메시지 전송
subscribe_msg = {
"method": "SUBSCRIBE",
"params": [f"{self.symbol}@depth20@100ms"],
"id": 1
}
ws.send(json.dumps(subscribe_msg))
def start(self):
"""백그라운드에서 웹소켓 시작"""
self.running = True
self.thread = threading.Thread(target=self._run)
self.thread.daemon = True
self.thread.start()
def _run(self):
stream_url = f"wss://stream.binance.com:9443/ws"
self.ws = websocket.WebSocketApp(
stream_url,
on_message=self.on_message,
on_error=self.on_error,
on_close=self.on_close,
on_open=self.on_open
)
self.ws.run_forever(ping_interval=30)
def stop(self):
"""웹소켓 중지"""
self.running = False
if self.ws:
self.ws.close()
사용 예시
def handle_orderbook_update(data):
best_bid = max(data['bids'].keys()) if data['bids'] else 0
best_ask = min(data['asks'].keys()) if data['asks'] else 0
spread = best_ask - best_bid
print(f"[{data['timestamp']}] Best Bid: {best_bid:.2f}, Best Ask: {best_ask:.2f}, Spread: {spread:.2f}")
ws_client = BinanceWebSocketOrderbook(symbol='btcusdt', on_update=handle_orderbook_update)
ws_client.start()
print("웹소켓 수신 시작... 10초 후 중지")
time.sleep(10)
ws_client.stop()
Python 백테스팅 시스템 구축
1단계: 히스토리컬 데이터 로더
import requests
import pandas as pd
from datetime import datetime, timedelta
import time
class BinanceHistoricalLoader:
"""Binance Historical K-line + 오더북 데이터 로더"""
KLINE_URL = "https://api.binance.com/api/v3/klines"
def __init__(self, symbol='BTCUSDT'):
self.symbol = symbol.upper()
def load_klines(self, interval='1m', start_str=None, end_str=None, limit=1000):
"""Historical K-line 데이터 로드"""
params = {
'symbol': self.symbol,
'interval': interval,
'limit': limit
}
if start_str:
if isinstance(start_str, datetime):
start_str = int(start_str.timestamp() * 1000)
params['startTime'] = start_str
if end_str:
if isinstance(end_str, datetime):
end_str = int(end_str.timestamp() * 1000)
params['endTime'] = end_str
response = requests.get(self.KLINE_URL, params=params)
response.raise_for_status()
data = response.json()
df = pd.DataFrame(data, columns=[
'open_time', 'open', 'high', 'low', 'close', 'volume',
'close_time', 'quote_volume', 'trades', 'taker_buy_base',
'taker_buy_quote', 'ignore'
])
# 타입 변환
numeric_cols = ['open', 'high', 'low', 'close', 'volume', 'quote_volume']
for col in numeric_cols:
df[col] = pd.to_numeric(df[col], errors='coerce')
df['open_time'] = pd.to_datetime(df['open_time'], unit='ms')
df['close_time'] = pd.to_datetime(df['close_time'], unit='ms')
return df
def load_multiple_klines(self, days=30, interval='1m'):
"""여러 일수의 데이터를 자동으로 로드"""
end_time = datetime.now()
start_time = end_time - timedelta(days=days)
all_data = []
current_start = start_time
while current_start < end_time:
chunk_end = min(current_start + timedelta(days=1), end_time)
try:
klines = self.load_klines(
interval=interval,
start_str=int(current_start.timestamp() * 1000),
end_str=int(chunk_end.timestamp() * 1000),
limit=1440 # 1분 기준 하루 1440개
)
all_data.append(klines)
print(f"로딩 완료: {current_start} ~ {chunk_end} ({len(klines)}건)")
current_start = chunk_end
time.sleep(0.2) # Rate Limit 방지
except Exception as e:
print(f"데이터 로딩 오류: {e}")
time.sleep(1)
return pd.concat(all_data, ignore_index=True) if all_data else pd.DataFrame()
사용 예시
loader = BinanceHistoricalLoader(symbol='BTCUSDT')
df_klines = loader.load_multiple_klines(days=7, interval='1h')
df_klines.to_csv('btcusdt_7days_1h.csv', index=False)
print(f"총 {len(df_klines)}건 로드 완료")
print(df_klines.head())
2단계: 백테스팅 엔진
import pandas as pd
import numpy as np
from datetime import datetime
from typing import Dict, List, Optional
class BacktestEngine:
"""단순화된 백테스팅 엔진"""
def __init__(self, initial_balance=10000, fee_rate=0.0004):
self.initial_balance = initial_balance
self.fee_rate = fee_rate # Binance 메이커 fee 0.02% = 0.0002
self.balance = initial_balance
self.position = 0
self.trades = []
self.equity_curve = []
def run(self, df: pd.DataFrame, strategy_func) -> Dict:
"""백테스트 실행"""
self.balance = self.initial_balance
self.position = 0
self.trades = []
self.equity_curve = []
for idx, row in df.iterrows():
signal = strategy_func(row, self.position, self.balance)
if signal == 'BUY' and self.balance > 0:
self._execute_buy(row)
elif signal == 'SELL' and self.position > 0:
self._execute_sell(row)
# Equity 기록
equity = self.balance + self.position * row['close']
self.equity_curve.append({
'timestamp': row['open_time'],
'equity': equity,
'position': self.position,
'price': row['close']
})
return self._calculate_metrics()
def _execute_buy(self, row, portion=1.0):
"""매수 실행"""
price = row['close']
buy_amount = self.balance * portion
fee = buy_amount * self.fee_rate
if buy_amount > fee:
quantity = (buy_amount - fee) / price
self.position += quantity
self.balance -= buy_amount
self.trades.append({
'timestamp': row['open_time'],
'side': 'BUY',
'price': price,
'quantity': quantity,
'value': buy_amount,
'fee': fee
})
def _execute_sell(self, row, portion=1.0):
"""매도 실행"""
price = row['close']
sell_quantity = self.position * portion
sell_value = sell_quantity * price
fee = sell_value * self.fee_rate
self.balance += (sell_value - fee)
self.position -= sell_quantity
self.trades.append({
'timestamp': row['open_time'],
'side': 'SELL',
'price': price,
'quantity': sell_quantity,
'value': sell_value,
'fee': fee
})
def _calculate_metrics(self) -> Dict:
"""성과 지표 계산"""
equity_df = pd.DataFrame(self.equity_curve)
if len(equity_df) < 2:
return {'error': '데이터 부족'}
# Returns 계산
equity_df['returns'] = equity_df['equity'].pct_change()
# 핵심 지표
total_return = (equity_df['equity'].iloc[-1] / self.initial_balance - 1) * 100
sharpe_ratio = equity_df['returns'].mean() / equity_df['returns'].std() * np.sqrt(252 * 24) if equity_df['returns'].std() > 0 else 0
# 최대 드로우다운
equity_df['cummax'] = equity_df['equity'].cummax()
equity_df['drawdown'] = (equity_df['cummax'] - equity_df['equity']) / equity_df['cummax']
max_drawdown = equity_df['drawdown'].max() * 100
# 승률
winning_trades = [t for t in self.trades if t['side'] == 'SELL']
total_trades = len(winning_trades)
return {
'total_return': total_return,
'sharpe_ratio': sharpe_ratio,
'max_drawdown': max_drawdown,
'total_trades': total_trades,
'final_equity': equity_df['equity'].iloc[-1],
'equity_curve': equity_df
}
전략 함수 예시: 이동평균 크로스오버
def moving_average_strategy(row, position, balance):
"""SMA 20 vs SMA 50 크로스오버 전략"""
# 이 예시에서는 단순화를 위해 close price 사용
# 실제 구현에서는 전역 상태로 SMA 계산 필요
return 'HOLD'
실행
engine = BacktestEngine(initial_balance=10000)
metrics = engine.run(df_klines, moving_average_strategy)
print(f"총 수익률: {metrics['total_return']:.2f}%")
print(f"샤프 비율: {metrics['sharpe_ratio']:.2f}")
print(f"최대 드로우다운: {metrics['max_drawdown']:.2f}%")
print(f"총 거래 횟수: {metrics['total_trades']}")
HolySheep AI 통합: AI 기반 백테스팅 분석
HolySheep AI는 Binance 오더북 데이터를 직접 제공하지 않지만, 백테스팅 결과 분석과 전략 최적화에 강력한 AI 기능을 제공합니다.
HolySheep AI SDK 설치 및 설정
# HolySheep AI SDK 설치 (공식 제공 또는 REST API 직접 사용)
import requests
import json
from typing import Optional
class HolySheepAIClient:
"""HolySheep AI API 클라이언트 (LLM 백테스팅 분석용)"""
BASE_URL = "https://api.holysheep.ai/v1"
def __init__(self, api_key: str):
self.api_key = api_key
self.headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
def analyze_backtest_results(self, metrics: dict, trades: list) -> str:
"""백테스트 결과를 AI로 분석"""
# 분석 프롬프트 구성
prompt = f"""다음 백테스팅 결과를 분석하고 개선점을 제시해주세요:
결과 요약:
- 총 수익률: {metrics.get('total_return', 0):.2f}%
- 샤프 비율: {metrics.get('sharpe_ratio', 0):.2f}
- 최대 드로우다운: {metrics.get('max_drawdown', 0):.2f}%
- 총 거래 횟수: {metrics.get('total_trades', 0)}
거래 내역 (최근 5건):
{json.dumps(trades[:5], indent=2, default=str)}
다음 사항을 분석해주세요:
1. 전략의 강점과 약점
2. 리스크 관리 평가
3. 최적화 권장사항
4. 다음 백테스트 파라미터 제안
"""
return self._call_llm(prompt, model="gpt-4.1")
def optimize_strategy(self, current_params: dict, metrics: dict) -> dict:
"""현재 파라미터를 기반으로 최적화된 파라미터 제안"""
prompt = f"""현재 트레이딩 전략의 파라미터를 최적화해주세요.
현재 파라미터:
{json.dumps(current_params, indent=2)}
현재 성과:
- 수익률: {metrics.get('total_return', 0):.2f}%
- 샤프 비율: {metrics.get('sharpe_ratio', 0):.2f}
- 드로우다운: {metrics.get('max_drawdown', 0):.2f}%
다음 형식으로 최적화된 파라미터를 제안해주세요:
{{
"param_name": "새로운 값",
"rationale": "변경 이유"
}}
"""
response = self._call_llm(prompt, model="gpt-4.1")
return self._parse_json_response(response)
def _call_llm(self, prompt: str, model: str = "gpt-4.1") -> str:
"""LLM API 호출"""
endpoint = f"{self.BASE_URL}/chat/completions"
payload = {
"model": model,
"messages": [
{"role": "system", "content": "당신은 전문적인 암호화폐 트레이딩 분석가입니다."},
{"role": "user", "content": prompt}
],
"temperature": 0.3,
"max_tokens": 2000
}
response = requests.post(endpoint, headers=self.headers, json=payload)
response.raise_for_status()
result = response.json()
return result['choices'][0]['message']['content']
def _parse_json_response(self, response: str) -> dict:
"""JSON 응답 파싱"""
try:
# Markdown 코드 블록 제거
if "```json" in response:
response = response.split("``json")[1].split("``")[0]
elif "```" in response:
response = response.split("``")[1].split("``")[0]
return json.loads(response.strip())
except:
return {"raw_response": response}
사용 예시
HolySheep AI 가입: https://www.holysheep.ai/register
HOLYSHEEP_API_KEY = "YOUR_HOLYSHEEP_API_KEY" # 실제 키로 교체
ai_client = HolySheepAIClient(api_key=HOLYSHEEP_API_KEY)
백테스트 결과 분석
analysis = ai_client.analyze_backtest_results(
metrics=metrics,
trades=engine.trades
)
print("AI 분석 결과:")
print(analysis)
전략 최적화
current_params = {
"sma_short": 20,
"sma_long": 50,
"stop_loss_pct": 2.0,
"take_profit_pct": 5.0
}
optimized = ai_client.optimize_strategy(current_params, metrics)
print("\n최적화된 파라미터:")
print(json.dumps(optimized, indent=2, ensure_ascii=False))
타 공급자 비교표
| 공급자 | 데이터 유형 | 월간 비용 | Rate Limit | CSV 내보내기 | Webhook 지원 | AI/LLM 통합 | 로컬 결제 |
|---|---|---|---|---|---|---|---|
| Tardis.dev | 실시간 + Historical | $500+ | 높음 | 제한적 | 지원 | 없음 | 불가 |
| Binance 공식 API | 실시간 + Historical | 무료 (제한) | 1200/분 | 제한적 | 지원 | 없음 | 불가 |
| CCXT 라이브러리 | 실시간 + Historical | 무료 | Exchange 의존 | 직접 구현 | 직접 구현 | 없음 | 불가 |
| HolySheep AI | LLM API Gateway | $8/MTok (GPT-4.1) | API 키당 제한 | 해당없음 | Webhook 지원 | 최고 | 지원 |
이런 팀에 적합 / 비적합
✅ HolySheep AI가 적합한 팀
- AI 기반 트레이딩 연구팀: 백테스트 결과를 AI로 분석하고 전략을 최적화하는 워크플로우를 원하는 팀
- 다중 AI 모델 사용 팀: GPT-4.1, Claude, Gemini, DeepSeek를 단일 API 키로 관리하고 싶은 팀
- 비용 최적화 중시 팀: 해외 신용카드 없이 로컬 결제를 원하고 비용을 최적화하고 싶은 팀
- 개발자 친화적 환경을 원하는 팀: 깔끔한 SDK와 빠른 응답 시간을 원하는 개발자
❌ HolySheep AI가 비적합한 팀
- 순수 마켓데이터만 필요한 팀: Binance L2 오더북 데이터만 필요하고 AI 분석이 불필요한 팀 → Tardis.dev 또는 Binance API 권장
- 초저지연 트레이딩 팀: 밀리초 단위 지연이 중요한 HFT 팀 → 전용 마켓데이터 공급자 권장
- 높은 마켓데이터 볼륨 필요 팀: 하루 수백만 건의 오더북 업데이트가 필요한 팀 → Binance Cloud Data 또는 전문 데이터 공급자 권장
가격과 ROI
HolySheep AI 가격 체계
| 모델 | 입력 ($/MTok) | 출력 ($/MTok) | 적합 용도 |
|---|---|---|---|
| GPT-4.1 | $8.00 | $8.00 | 복잡한 백테스트 분석 |
| Claude Sonnet 4.5 | $15.00 | $15.00 | 긴上下文 분석 |
| Gemini 2.5 Flash | $2.50 | $2.50 | 빠른 분석, 빈번한 호출 |
| DeepSeek V3.2 | $0.42 | $0.42 | 대량 데이터 처리 |
ROI 추정
백테스트 분석에 HolySheep AI를 활용할 경우:
- 월간 분석 호출 수: 약 500회 백테스트 × 10회 AI 분석 = 5,000회
- 평균 토큰 사용: 1회당 입력 10K 토큰, 출력 5K 토큰
- Gemini 2.5 Flash 기준 월 비용: 5,000 × ($2.50 × 0.015) = 약 $187.50
- 수동 분석 대비 시간 절약: 월 40시간 × $100/hour = $4,000 가치
- 순ROI: 약 2,000%
왜 HolySheep를 선택해야 하나
- 단일 API 키 통합: 모든 주요 AI 모델(GPT-4.1, Claude, Gemini, DeepSeek)을 하나의 API 키로 관리
- 비용 최적화: DeepSeek V3.2는 $0.42/MTok으로 기존 대비 90%+ 비용 절감 가능
- 로컬 결제 지원: 해외 신용카드 없이 원화 결제 가능,开发和支付都不耽误
- 무료 크레딧 제공: 가입 시 무료 크레딧으로 즉시 테스트 가능
- 개발자 친화적: 직관적인 API 설계와 빠른 응답 시간
자주 발생하는 오류와 해결책
오류 1: Binance API Rate Limit 초과
# 오류 메시지
HTTP 429: Too Many Requests
해결 방법: RequestSession으로 자동 재시도 구현
import time
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
def create_session_with_retry():
session = requests.Session()
retry_strategy = Retry(
total=3,
backoff_factor=1,
status_forcelist=[429, 500, 502, 503, 504]
)
adapter = HTTPAdapter(max_retries=retry_strategy)
session.mount("https://", adapter)
session.mount("http://", adapter)
return session
사용
session = create_session_with_retry()
response = session.get(binance_api_url, params={'symbol': 'BTCUSDT'})
오류 2: HolySheep API 키 인증 실패
# 오류 메시지
401 Unauthorized: Invalid API key
해결 방법: API 키 확인 및 올바른 포맷 사용
import os
방법 1: 환경 변수에서 로드 (권장)
HOLYSHEEP_API_KEY = os.environ.get('HOLYSHEEP_API_KEY')
방법 2: 직접 설정 (테스트용)
if not HOLYSHEEP_API_KEY:
HOLYSHEEP_API_KEY = "YOUR_HOLYSHEEP_API_KEY"
print("⚠️ API 키가 환경 변수에 없습니다. 직접 설정된 키를 사용합니다.")
방법 3: .env 파일에서 로드
from dotenv import load_dotenv
load_dotenv()
HOLYSHEEP_API_KEY = os.getenv('HOLYSHEEP_API_KEY')
키 유효성 검증
if not HOLYSHEEP_API_KEY or len(HOLYSHEEP_API_KEY) < 20:
raise ValueError("유효하지 않은 HolySheep API 키입니다. https://www.holysheep.ai/register 에서 가입해주세요.")
오류 3: 웹소켓 연결 끊김 (Heartbeat 문제)
# 오류 메시지
WebSocket connection closed unexpectedly
해결 방법: 자동 재연결 로직 구현
import websocket
import threading
import time
class BinanceWebSocketReconnector:
def __init__(self, symbol='btcusdt', on_message=None):
self.symbol = symbol
self.on_message = on_message
self.ws = None
self.running = False
self.reconnect_delay = 1
self.max_reconnect_delay = 60
def start(self):
self.running = True
self._connect()
def _connect(self):
while self.running:
try:
ws_url = f"wss://stream.binance.com:9443/ws/{self.symbol}@depth20@100ms"
self.ws = websocket.WebSocketApp(
ws_url,
on_message=self.on_message,
on_error=self._on_error,
on_close=self._on_close
)
print(f"WebSocket 연결 시도: {ws_url}")