# app/tools/dzengi_probe/ticker24hr_update_probe.py from __future__ import annotations import asyncio import csv import json import time from dataclasses import dataclass from decimal import Decimal from pathlib import Path from urllib.parse import urlencode from urllib.request import Request, urlopen from app.tools.dzengi_probe.config import DzengiProbeConfig @dataclass(frozen=True) class Ticker24hrSample: sample: int local_time_ms: int ask_price: str | None bid_price: str | None last_price: str | None close_time: int | None class Ticker24hrUpdateProbe: def __init__(self, config: DzengiProbeConfig) -> None: self.config = config async def run( self, *, samples: int = 300, interval_seconds: float = 0.2, ) -> Path: output_path = ( self.config.output_dir / "reports" / f"ticker24hr_update_{self.config.symbol_file_name}.csv" ) output_path.parent.mkdir(parents=True, exist_ok=True) rows: list[Ticker24hrSample] = [] for index in range(1, samples + 1): payload = self._get_ticker24hr() rows.append( Ticker24hrSample( sample=index, local_time_ms=int(time.time() * 1000), ask_price=self._decimal_str(payload.get("askPrice")), bid_price=self._decimal_str(payload.get("bidPrice")), last_price=self._decimal_str(payload.get("lastPrice")), close_time=self._int_or_none(payload.get("closeTime")), ) ) await asyncio.sleep(interval_seconds) self._save_csv(output_path, rows) return output_path def _get_ticker24hr(self) -> dict: query = urlencode({"symbol": self.config.symbol}) url = ( f"{self.config.base_url}" f"/api/{self.config.api_version}/ticker/24hr" f"?{query}" ) request = Request( url=url, method="GET", headers={ "Accept": "application/json", "User-Agent": "dzentra-dzengi-probe/1.0", }, ) with urlopen(request, timeout=self.config.timeout_seconds) as response: return json.loads(response.read().decode("utf-8")) def _decimal_str(self, value: object) -> str | None: if value is None: return None return str(Decimal(str(value))) def _int_or_none(self, value: object) -> int | None: if value is None: return None return int(str(value)) def _save_csv(self, path: Path, rows: list[Ticker24hrSample]) -> None: with path.open("w", encoding="utf-8", newline="") as file: writer = csv.writer(file) writer.writerow( [ "sample", "local_time_ms", "close_time", "ask_price", "bid_price", "last_price", "ask_changed", "bid_changed", "last_changed", "close_time_changed", "last_equals_bid", ] ) previous: Ticker24hrSample | None = None for row in rows: writer.writerow( [ row.sample, row.local_time_ms, row.close_time, row.ask_price, row.bid_price, row.last_price, previous is not None and row.ask_price != previous.ask_price, previous is not None and row.bid_price != previous.bid_price, previous is not None and row.last_price != previous.last_price, previous is not None and row.close_time != previous.close_time, row.last_price == row.bid_price, ] ) previous = row