diff --git a/data/binance_rest.py b/data/binance_rest.py new file mode 100644 index 0000000..9d66ae1 --- /dev/null +++ b/data/binance_rest.py @@ -0,0 +1,42 @@ +import pandas as pd +from binance.client import Client +import logging + +logger = logging.getLogger(__name__) + +class BinanceRestClient: + def __init__(self, api_key: str, api_secret: str): + self.client = Client(api_key, api_secret) + + def get_top_coins(self, limit: int = 50) -> list[str]: + tickers = self.client.get_ticker() + usdt_pairs = [ + t for t in tickers + if t["symbol"].endswith("USDT") and not t["symbol"].startswith("USDT") + ] + usdt_pairs.sort(key=lambda x: float(x["quoteVolume"]), reverse=True) + return [t["symbol"] for t in usdt_pairs[:limit]] + + def get_ohlcv(self, symbol: str, interval: str = "1h", limit: int = 100) -> pd.DataFrame: + klines = self.client.get_klines(symbol=symbol, interval=interval, limit=limit) + df = pd.DataFrame(klines, columns=[ + "timestamp", "open", "high", "low", "close", "volume", + "close_time", "quote_volume", "trades", "taker_buy_base", + "taker_buy_quote", "ignore", + ]) + for col in ["open", "high", "low", "close", "volume", "quote_volume"]: + df[col] = df[col].astype(float) + df["timestamp"] = pd.to_datetime(df["timestamp"], unit="ms") + return df[["timestamp", "open", "high", "low", "close", "volume", "quote_volume"]] + + def get_all_prices(self) -> dict[str, float]: + tickers = self.client.get_all_tickers() + return {t["symbol"]: float(t["price"]) for t in tickers} + + def get_24h_volume(self, symbol: str) -> dict: + ticker = self.client.get_ticker(symbol=symbol) + return { + "volume": float(ticker["volume"]), + "quote_volume": float(ticker["quoteVolume"]), + "price_change_pct": float(ticker["priceChangePercent"]), + } diff --git a/tests/test_binance_rest.py b/tests/test_binance_rest.py new file mode 100644 index 0000000..1cdbbf1 --- /dev/null +++ b/tests/test_binance_rest.py @@ -0,0 +1,42 @@ +import pytest +from unittest.mock import patch, MagicMock +from data.binance_rest import BinanceRestClient + +@pytest.fixture +def client(): + with patch("data.binance_rest.Client") as mock_client_cls: + mock_client_cls.return_value = MagicMock() + c = BinanceRestClient(api_key="test", api_secret="test") + return c + +def test_get_top_coins_returns_symbols(client): + mock_tickers = [ + {"symbol": "BTCUSDT", "quoteVolume": "1000000"}, + {"symbol": "ETHUSDT", "quoteVolume": "500000"}, + {"symbol": "BTCETH", "quoteVolume": "200000"}, + ] + with patch.object(client.client, "get_ticker", return_value=mock_tickers): + result = client.get_top_coins(limit=2) + assert "BTCUSDT" in result + assert "ETHUSDT" in result + assert "BTCETH" not in result + +def test_get_ohlcv_returns_dataframe(client): + mock_klines = [ + [1700000000000, "40000", "41000", "39000", "40500", "100", + 1700003599999, "4050000", 500, "50", "2025000", "0"] + ] + with patch.object(client.client, "get_klines", return_value=mock_klines): + df = client.get_ohlcv("BTCUSDT", interval="1h", limit=1) + assert len(df) == 1 + assert "close" in df.columns + assert df.iloc[0]["close"] == 40500.0 + +def test_get_all_tickers(client): + mock_prices = [ + {"symbol": "BTCUSDT", "price": "40000.00"}, + {"symbol": "ETHUSDT", "price": "3500.00"}, + ] + with patch.object(client.client, "get_all_tickers", return_value=mock_prices): + result = client.get_all_prices() + assert result["BTCUSDT"] == 40000.0