Files
dzentra_bot/app/scripts/check_trades_unsubscribe.py

214 lines
5.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from __future__ import annotations
import asyncio
import json
import os
from typing import Any
from uuid import uuid4
import websockets
from websockets.typing import Subprotocol
SYMBOL = os.getenv(
"TRADE_TEST_SYMBOL",
"BTC/USD_LEVERAGE",
)
OBSERVE_BEFORE_UNSUBSCRIBE_SECONDS = 30
OBSERVE_AFTER_UNSUBSCRIBE_SECONDS = 60
RECEIVE_TIMEOUT_SECONDS = 10
def build_ws_url() -> str:
raw_url = os.getenv(
"EXCHANGE_WS_URL",
"wss://api-adapter.dzengi.com",
).rstrip("/")
if raw_url.startswith("https://"):
raw_url = raw_url.replace("https://", "wss://", 1)
elif raw_url.startswith("http://"):
raw_url = raw_url.replace("http://", "ws://", 1)
if raw_url.endswith("/connect"):
return raw_url
return f"{raw_url}/connect"
def build_headers() -> dict[str, str]:
base_url = os.getenv(
"EXCHANGE_BASE_URL",
"https://api-adapter.dzengi.com",
).rstrip("/")
headers = {
"Origin": base_url,
"Content-Type": "application/json",
}
api_key = os.getenv("EXCHANGE_API_KEY", "").strip()
if api_key:
headers["X-MBX-APIKEY"] = api_key
return headers
def build_request(destination: str) -> dict[str, Any]:
return {
"correlationId": str(uuid4()),
"destination": destination,
"payload": {
"symbols": [SYMBOL],
},
}
def decode_message(raw_message: str | bytes) -> Any:
if isinstance(raw_message, bytes):
raw_message = raw_message.decode("utf-8")
try:
return json.loads(raw_message)
except json.JSONDecodeError:
return raw_message
def print_message(label: str, message: Any) -> None:
print()
print("=" * 80)
print(label)
print("=" * 80)
if isinstance(message, (dict, list)):
print(json.dumps(message, indent=2, ensure_ascii=False))
else:
print(message)
def is_trade_event(message: Any) -> bool:
return (
isinstance(message, dict)
and message.get("destination") == "internal.trade"
)
async def receive_until(
websocket: Any,
*,
duration_seconds: int,
phase: str,
) -> int:
loop = asyncio.get_running_loop()
deadline = loop.time() + duration_seconds
trade_count = 0
while loop.time() < deadline:
remaining = deadline - loop.time()
timeout = min(RECEIVE_TIMEOUT_SECONDS, remaining)
try:
raw_message = await asyncio.wait_for(
websocket.recv(),
timeout=timeout,
)
except asyncio.TimeoutError:
print(
f"[{phase}] За последние {timeout:.0f} секунд "
"сообщений не получено; соединение остаётся открытым."
)
continue
message = decode_message(raw_message)
print_message(f"[{phase}] Получено сообщение", message)
if is_trade_event(message):
trade_count += 1
return trade_count
async def main() -> None:
ws_url = build_ws_url()
headers = build_headers()
subscribe_request = build_request("trades.subscribe")
unsubscribe_request = build_request("trades.unsubscribe")
print(f"WebSocket URL: {ws_url}")
print(f"Symbol: {SYMBOL}")
async with websockets.connect(
ws_url,
extra_headers=headers,
subprotocols=[Subprotocol("json")],
ping_interval=20,
ping_timeout=20,
open_timeout=20,
) as websocket:
print_message(
"Отправляется trades.subscribe",
subscribe_request,
)
await websocket.send(
json.dumps(subscribe_request),
)
before_count = await receive_until(
websocket,
duration_seconds=OBSERVE_BEFORE_UNSUBSCRIBE_SECONDS,
phase="BEFORE UNSUBSCRIBE",
)
print()
print(
"Количество Trade Event до unsubscribe: "
f"{before_count}"
)
print_message(
"Отправляется trades.unsubscribe",
unsubscribe_request,
)
await websocket.send(
json.dumps(unsubscribe_request),
)
after_count = await receive_until(
websocket,
duration_seconds=OBSERVE_AFTER_UNSUBSCRIBE_SECONDS,
phase="AFTER UNSUBSCRIBE",
)
print()
print("=" * 80)
print("РЕЗУЛЬТАТ НАБЛЮДЕНИЯ")
print("=" * 80)
print(f"Trade Event до unsubscribe: {before_count}")
print(f"Trade Event после unsubscribe: {after_count}")
if before_count == 0:
print(
"До unsubscribe не было получено ни одной сделки. "
"Эксперимент нельзя считать доказательным."
)
elif after_count == 0:
print(
"После trades.unsubscribe новые сделки не поступили. "
"Команда, вероятно, поддерживается, но необходимо "
"проверить ACK или повторить тест при высокой активности."
)
else:
print(
"После trades.unsubscribe сделки продолжили поступать. "
"Команда либо не поддерживается, либо была отклонена, "
"либо имеет другой формат."
)
if __name__ == "__main__":
asyncio.run(main())