Files
microdao-daarion/services/market-data-service/app/config.py
Apple c50843933f feat: market-data-service for SenpAI trading agent
New service: real-time market data collection with unified event model.

Architecture:
- Domain events: TradeEvent, QuoteEvent, BookL2Event, HeartbeatEvent
- Provider interface: MarketDataProvider ABC with connect/subscribe/stream/close
- Async EventBus with fan-out to multiple consumers

Providers:
- BinanceProvider: public WebSocket (trades + bookTicker), no API key needed,
  auto-reconnect with exponential backoff, heartbeat timeout detection
- AlpacaProvider: IEX real-time data + paper trading auth,
  dry-run mode when no keys configured (heartbeats only)

Consumers:
- StorageConsumer: SQLite (via SQLAlchemy async) + JSONL append-only log
- MetricsConsumer: Prometheus counters, latency histograms, events/sec gauge
- PrintConsumer: sampled structured logging (1/100 events)

CLI: python -m app run --provider binance --symbols BTCUSDT,ETHUSDT
HTTP: /health, /metrics (Prometheus), /latest?symbol=XXX

Tests: 19/19 passed (Binance parse, Alpaca parse, bus smoke tests)

Config: pydantic-settings + .env, all secrets via environment variables.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 11:19:00 -08:00

54 lines
2.3 KiB
Python

"""
Configuration via pydantic-settings.
All secrets come from .env; no defaults for sensitive keys.
"""
from __future__ import annotations
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
extra="ignore",
)
# ── Binance (no key needed for public WS) ──────────────────────────
binance_ws_url: str = "wss://stream.binance.com:9443/ws"
binance_rest_url: str = "https://api.binance.com"
# ── Alpaca (paper trading — free tier) ─────────────────────────────
alpaca_key: str = ""
alpaca_secret: str = ""
alpaca_base_url: str = "https://paper-api.alpaca.markets"
alpaca_data_ws_url: str = "wss://stream.data.alpaca.markets/v2/iex"
alpaca_dry_run: bool = True # True = skip real API calls if no keys
# ── Storage ────────────────────────────────────────────────────────
sqlite_url: str = "sqlite+aiosqlite:///market_data.db"
jsonl_path: str = "events.jsonl"
# ── Reliability ────────────────────────────────────────────────────
reconnect_max_retries: int = 20
reconnect_base_delay: float = 1.0 # seconds, exponential backoff
reconnect_max_delay: float = 60.0
heartbeat_timeout: float = 30.0 # no-message timeout → reconnect
# ── Metrics / HTTP ─────────────────────────────────────────────────
http_host: str = "0.0.0.0"
http_port: int = 8891
metrics_enabled: bool = True
# ── Logging ────────────────────────────────────────────────────────
log_level: str = "INFO"
log_sample_rate: int = 100 # PrintConsumer: log 1 out of N events
@property
def alpaca_configured(self) -> bool:
return bool(self.alpaca_key and self.alpaca_secret)
settings = Settings()