import asyncio from pathlib import Path from typing import Optional import yaml from pydantic import BaseModel, Field from pydantic_settings import BaseSettings class FHobSettings(BaseModel): symbols: list[str] = Field( default_factory=list, description="Trading pairs to subscribe to. Empty = no subscriptions. oe_em adds pairs via REST.", ) log_level: str = Field(default="INFO", description="Logging level") log_file: Path = Field( default=Path("/tmp/fh_ob.log"), description="Path to log file. Logs are written here in addition to stdout.", ) socket_path: Path = Field( default=Path("/tmp/fh_ob.sock"), description="Unix domain socket path for OE+EM", ) rest_host: str = Field(default="0.0.0.0", description="FastAPI debug host") rest_port: int = Field(default=8000, description="FastAPI debug port") ws_url: str = Field( default="wss://ws-api-spot.kucoin.com", description="KuCoin WebSocket endpoint", ) token_url: str = Field( default="https://api.kucoin.com/api/v1/bullet-public", description="KuCoin public token endpoint", ) reconnect_base_delay: float = Field( default=1.0, description="Base delay for reconnect exponential backoff (seconds)", ) reconnect_max_delay: float = Field( default=60.0, description="Max delay for reconnect exponential backoff (seconds)", ) heartbeat_interval: float = Field( default=18.0, description="WS ping interval (seconds) - KuCoin uses 18s", ) class Settings(BaseSettings): fh_ob: FHobSettings = Field(default_factory=FHobSettings) @classmethod async def from_yaml(cls, path: Path) -> "Settings": loop = asyncio.get_running_loop() def _read() -> dict: with open(path) as f: return yaml.safe_load(f) or {} data = await loop.run_in_executor(None, _read) fh_ob_data = data.get("fh_ob", {}) if fh_ob_data.get("symbols") is None: fh_ob_data["symbols"] = [] return cls(**data) model_config = {"env_prefix": "TRIARB_", "extra": "ignore"}