Files
trex-core/trex_core/runtime/config.py
T
genichinandClaude Opus 4.8 13837acc60 Implement BAT-006: portfolio-hub ledger integration (REQ-017~019)
- REQ-017: LedgerPort contract (application/ledger_port.py) + portfolio-hub
  adapter + in-memory stub (adapters/ledger/), TREX_PORTFOLIO_HUB_* config
- REQ-018: fill→ledger recording in execution loop; FillStatus carries
  execution_id (deterministic synth) + fee; paper/kis adapters updated
- REQ-019: ledger outbox + retry worker (application/ledger_outbox.py),
  correlation_id idempotency, outcome vocabulary; 002_create_ledger_outbox.sql
- Tests: in-memory/portfolio-hub ledger, execution ledger hook, outbox,
  domain import sweep

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 22:57:31 +09:00

96 lines
3.4 KiB
Python

"""Daemon configuration resolution (REQ-005, REQ-010, REQ-012/013)."""
from __future__ import annotations
import os
from dataclasses import dataclass
from typing import Optional
def _load_dotenv() -> None:
"""Best-effort load of a .env file into os.environ (REQ-010).
Real OS environment variables take precedence over .env (override=False),
matching 12-factor expectations. If python-dotenv is not installed or no
.env exists, this is a no-op (no regression).
"""
try:
from dotenv import find_dotenv, load_dotenv
except ImportError: # pragma: no cover - optional at runtime
return
load_dotenv(find_dotenv(usecwd=True), override=False)
@dataclass(frozen=True)
class KISConfig:
base_url: str
app_key: str
app_secret: str
account_ref: str
timeout: float
allow_live: bool
redis_url: str = "" # REQ-016: optional token cache backend
@dataclass(frozen=True)
class PortfolioHubConfig:
"""portfolio-hub ledger adapter config (REQ-017)."""
base_url: str
token: str = ""
timeout: float = 5.0
@dataclass(frozen=True)
class DaemonConfig:
dsn: str
total_slots: int
host: str
port: int
broker: str # 'paper' | 'kis'
execution_interval_sec: float
kis: Optional[KISConfig] = None
# REQ-017/019: ledger writer config (None => in-memory stub ledger,
# appropriate for slice-1 paper-broker integration tests).
portfolio_hub: Optional[PortfolioHubConfig] = None
ledger_interval_sec: float = 1.0
@staticmethod
def from_env() -> "DaemonConfig":
_load_dotenv()
dsn = os.environ.get("TREX_POSTGRESQL_DSN")
if not dsn:
raise RuntimeError(
"TREX_POSTGRESQL_DSN is required to start the slot-core daemon."
)
broker = os.environ.get("TREX_BROKER", "paper").strip().lower()
kis: Optional[KISConfig] = None
if broker == "kis":
kis = KISConfig(
base_url=os.environ["TREX_KIS_BROKER_BASE_URL"],
app_key=os.environ["TREX_KIS_BROKER_APP_KEY"],
app_secret=os.environ["TREX_KIS_BROKER_APP_SECRET"],
account_ref=os.environ["TREX_KIS_BROKER_ACCOUNT_REF"],
timeout=float(os.environ.get("TREX_KIS_BROKER_TIMEOUT", "5")),
allow_live=os.environ.get("TREX_KIS_ALLOW_LIVE", "").lower() in ("1", "true", "yes"),
redis_url=os.environ.get("TREX_KIS_REDIS_URL", ""),
)
portfolio_hub: Optional[PortfolioHubConfig] = None
pf_base = os.environ.get("TREX_PORTFOLIO_HUB_BASE_URL", "").strip()
if pf_base:
portfolio_hub = PortfolioHubConfig(
base_url=pf_base,
token=os.environ.get("TREX_PORTFOLIO_HUB_TOKEN", "").strip(),
timeout=float(os.environ.get("TREX_PORTFOLIO_HUB_TIMEOUT", "5")),
)
return DaemonConfig(
dsn=dsn,
total_slots=int(os.environ.get("TREX_TOTAL_SLOTS", "10")),
host=os.environ.get("TREX_API_HOST", "127.0.0.1"),
port=int(os.environ.get("TREX_API_PORT", "8080")),
broker=broker,
execution_interval_sec=float(os.environ.get("TREX_EXECUTION_INTERVAL_SEC", "1.0")),
kis=kis,
portfolio_hub=portfolio_hub,
ledger_interval_sec=float(os.environ.get("TREX_LEDGER_INTERVAL_SEC", "1.0")),
)