feat(config): BAT-028 M1 — max_active_slots per-instance TOML (dcy-020/REQ-060)

자산 중심 모드 게이트 + M 값. DaemonInstanceConfig/DaemonConfig 에
max_active_slots: Optional[int] 추가, from_toml 파싱(>0 검증), from_config 전파.
미설정(None) → legacy pool 모드. additive — 전체 375 passed, ruff clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-10 11:06:56 +09:00
co-authored by Claude Opus 4.8
parent 8bd9495c4b
commit 587e987c5c
+15
View File
@@ -143,6 +143,10 @@ class DaemonInstanceConfig:
# Optional — absent keys fall back to KST / midnight defaults. # Optional — absent keys fall back to KST / midnight defaults.
trading_timezone: str = "Asia/Seoul" trading_timezone: str = "Asia/Seoul"
daily_preallocation_time: str = "00:00" daily_preallocation_time: str = "00:00"
# dcy-020 / REQ-060 (BAT-028): max concurrent active (holding) slots, M.
# Presence is the asset-centric mode gate: set -> universe reconcile + FCFS/M
# admission; absent -> legacy pool model (preallocation + find_allocatable).
max_active_slots: Optional[int] = None
@staticmethod @staticmethod
def from_toml(path: str) -> "DaemonInstanceConfig": def from_toml(path: str) -> "DaemonInstanceConfig":
@@ -155,11 +159,18 @@ class DaemonInstanceConfig:
raise RuntimeError( raise RuntimeError(
f"instance TOML missing required 'instance_key': {path}" f"instance TOML missing required 'instance_key': {path}"
) )
raw_max = data.get("max_active_slots")
max_active = int(raw_max) if raw_max is not None else None
if max_active is not None and max_active <= 0:
raise RuntimeError(
f"instance TOML 'max_active_slots' must be > 0, got {raw_max}: {path}"
)
return DaemonInstanceConfig( return DaemonInstanceConfig(
instance_key=str(instance_key), instance_key=str(instance_key),
portfolio_hub_token=str(data.get("portfolio_hub_token") or ""), portfolio_hub_token=str(data.get("portfolio_hub_token") or ""),
trading_timezone=str(data.get("trading_timezone") or "Asia/Seoul"), trading_timezone=str(data.get("trading_timezone") or "Asia/Seoul"),
daily_preallocation_time=str(data.get("daily_preallocation_time") or "00:00"), daily_preallocation_time=str(data.get("daily_preallocation_time") or "00:00"),
max_active_slots=max_active,
) )
@@ -172,6 +183,9 @@ class DaemonConfig:
# BAT-013 / REQ-049: instance identity for multi-instance queue naming. # BAT-013 / REQ-049: instance identity for multi-instance queue naming.
# "default" keeps legacy literal queue names (backward-compat). # "default" keeps legacy literal queue names (backward-compat).
instance_key: str = "default" instance_key: str = "default"
# dcy-020 / REQ-060 (BAT-028): M (max concurrent active slots) + asset-centric
# mode gate. None -> legacy pool model.
max_active_slots: Optional[int] = None
kis: Optional[KISConfig] = None kis: Optional[KISConfig] = None
# REQ-011: paper broker tuning (only consulted when broker == 'paper'). # REQ-011: paper broker tuning (only consulted when broker == 'paper').
paper: PaperBrokerConfig = PaperBrokerConfig() paper: PaperBrokerConfig = PaperBrokerConfig()
@@ -316,4 +330,5 @@ class DaemonConfig:
portfolio_hub=pf, portfolio_hub=pf,
trading_timezone=inst.trading_timezone, trading_timezone=inst.trading_timezone,
daily_preallocation_time=inst.daily_preallocation_time, daily_preallocation_time=inst.daily_preallocation_time,
max_active_slots=inst.max_active_slots,
) )