""" Executor configuration loaded from config.yaml. Defines settings for the triangular arbitrage executor including Unix socket path, concurrency limits, KuCoin credentials, and logging. live_mode at the top level controls whether the executor places real orders (live) or validates via order_test and simulates fills (paper). """ import asyncio import logging from decimal import Decimal from pathlib import Path from typing import Optional import yaml from pydantic import BaseModel, Field from pydantic_settings import BaseSettings log = logging.getLogger("executor-config") class ExecutorSettings(BaseModel): """Settings that control executor runtime behaviour.""" socket_path: Path = Field( default=Path("/tmp/executor.sock"), description="Unix domain socket path for fused_engine -> executor", ) concurrent_slots: int = Field(default=1, ge=1, description="Max concurrent triangle executions") enforce_same_base_isolation: bool = Field( default=True, description="Block concurrent triangles sharing the same base currency", ) enforce_pair_isolation: bool = Field( default=True, description="Block concurrent triangles that share any trading pair symbol", ) log_file: Path = Field( default=Path("/tmp/executor.log"), description="Path to log file", ) log_level: str = Field(default="INFO", description="Logging level") rest_port: int = Field( default=8002, description="HTTP REST API port (8000=fh_ob, 8001=oe_em)", ) initial_capital: dict[str, Decimal] = Field( default_factory=lambda: {"USDT": Decimal("10")}, description="Starting capital per currency. Caps the max_volume for each triangle's primary_quote.", ) private_token_url: str = Field( default="https://api.kucoin.com/api/v1/bullet-private", description="KuCoin private bullet token endpoint", ) fill_timeout_ms: float = Field( default=1000, description="Per-leg fill wait timeout in milliseconds", ) class Settings(BaseSettings): """Top-level settings parsed from config.yaml.""" # live_mode is the master switch: False = paper (order_test + simulated fills), # True = real orders on KuCoin. Set at top level of config.yaml. live_mode: bool = Field(default=False, description="false = paper; true = live orders") executor: ExecutorSettings = Field(default_factory=ExecutorSettings) kucoin_api_key: str = Field(default="", description="KuCoin API key") kucoin_api_secret: str = Field(default="", description="KuCoin API secret") kucoin_api_passphrase: str = Field(default="", description="KuCoin API passphrase") @classmethod async def from_yaml(cls, path: Path) -> "Settings": """Load settings from a YAML file, ignoring unknown keys.""" 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) known = {"live_mode", "fused_engine", "executor", "kucoin_api_key", "kucoin_api_secret", "kucoin_api_passphrase", "kcs_discount_active"} unknown = set(data.keys()) - known if unknown: log.warning("ignoring unknown config keys: %s", sorted(unknown)) return cls(**data) model_config = {"env_prefix": "TRIArb_", "extra": "ignore"}