triangular_arbitrage_bot/oe_em/config.py

90 lines
3.0 KiB
Python

"""
Configuration schema for the opportunity engine (oe_em).
Parsed from config.yaml into OeEmSettings. Controls logging, signal
thresholds, the fee discount flag, symbol subscription, and the socket
path to the executor.
"""
import asyncio
from pathlib import Path
from typing import Optional
import yaml
from pydantic import BaseModel, Field
from pydantic_settings import BaseSettings
class OeEmSettings(BaseModel):
"""Settings that control oe_em's runtime behaviour."""
fh_ob_url: str = Field(
default="http://127.0.0.1:8000",
description="REST URL of fh_ob server",
)
socket_path: Path = Field(
default=Path("/tmp/fh_ob.sock"),
description="Unix domain socket path for fh_ob",
)
log_level: str = Field(default="INFO", description="Logging level")
log_file: Path = Field(
default=Path("/tmp/oe_em.log"),
description="Path to log file. Logs are written here in addition to stdout.",
)
signal_threshold_bps: float = Field(
default=0.2,
description="Minimum net return in basis points to fire a signal",
)
opportunity_log_path: Path = Field(
default=Path("/tmp/opportunities.log"),
description="Path to log detected opportunities",
)
stats_interval_seconds: float = Field(
default=60.0,
description="Seconds between stats log lines. 0 to disable.",
)
cooldown_seconds: float = Field(
default=0.0,
description="Deprecated — use executor's in-flight blocking instead. "
"Kept here for operational flexibility; set to 0.",
)
excluded_currencies: list[str] = Field(
default_factory=list,
description="Currencies to exclude from triangle enumeration",
)
hold_currencies: list[str] = Field(
default=["USDT"],
description="Currencies held as capital. Only triangles starting and ending in one of these are evaluated.",
)
kcs_discount_active: bool = Field(
default=False,
description="If true, all taker fees are multiplied by 0.8 (KCS 20% fee discount)",
)
executor_socket_path: Path = Field(
default=Path("/tmp/executor.sock"),
description="Unix domain socket path for executor",
)
send_signals: bool = Field(
default=False,
description="If true, emit signals to executor socket when opportunities are found",
)
class Settings(BaseSettings):
"""Top-level settings parsed from config.yaml."""
oe_em: OeEmSettings = Field(default_factory=OeEmSettings)
fh_ob_url: Optional[str] = None
@classmethod
async def from_yaml(cls, path: Path) -> "Settings":
"""Load settings from a YAML file."""
loop = asyncio.get_running_loop()
def _read() -> dict:
with open(path) as f:
return yaml.safe_load(f)
data = await loop.run_in_executor(None, _read)
return cls(**data)
model_config = {"env_prefix": "TRIArb_", "extra": "ignore"}