triangular_arbitrage_bot/executor/config.py

94 lines
3.5 KiB
Python

"""
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",
)
await_balance: bool = Field(
default=False,
description="Wait for balance WS update before next leg",
)
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"}