feat: Binance WS, news client, and social client

Add ThreadedWebsocketManager-based BinanceWSClient for real-time price
streaming, NewsClient for CryptoPanic/NewsAPI fetching with coin filtering,
and SocialClient for Reddit post retrieval with keyword filtering and
simple keyword-based sentiment scoring. Includes unit tests for news and
social clients (4/4 passing).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-20 17:48:12 +09:00
parent ffada928f2
commit adad553a65
5 changed files with 252 additions and 0 deletions

51
data/binance_ws.py Normal file
View File

@@ -0,0 +1,51 @@
import threading
import logging
from binance import ThreadedWebsocketManager
logger = logging.getLogger(__name__)
class BinanceWSClient:
def __init__(self, api_key: str, api_secret: str):
self.api_key = api_key
self.api_secret = api_secret
self.twm = None
self.prices: dict[str, float] = {}
self._lock = threading.Lock()
def start(self, symbols: list[str]):
self.twm = ThreadedWebsocketManager(
api_key=self.api_key, api_secret=self.api_secret
)
self.twm.start()
streams = [s.lower() + "@miniTicker" for s in symbols]
self.twm.start_multiplex_socket(
callback=self._handle_message, streams=streams
)
logger.info(f"WebSocket started for {len(symbols)} symbols")
def _handle_message(self, msg):
if msg.get("e") == "error":
logger.error(f"WebSocket error: {msg}")
return
data = msg.get("data", msg)
if "s" in data and "c" in data:
with self._lock:
self.prices[data["s"]] = float(data["c"])
def get_price(self, symbol: str) -> float | None:
with self._lock:
return self.prices.get(symbol)
def get_all_prices(self) -> dict[str, float]:
with self._lock:
return dict(self.prices)
def update_symbols(self, symbols: list[str]):
if self.twm:
self.stop()
self.start(symbols)
def stop(self):
if self.twm:
self.twm.stop()
logger.info("WebSocket stopped")