feat(config): expose paper broker tuning via env (REQ-010 AC-5, BAT-009)

Wire the existing PaperBroker scenario knobs (fill_after_polls /
qty_per_poll / reject_symbols) to daemon env so REQ-011 AC-2 e2e
scenarios (delayed / partial / reject) can be reproduced by a daemon
restart. Unset == prior PaperBroker() behaviour, byte-for-byte (no
regression).

- config.py: PaperBrokerConfig + from_env parsing (TREX_PAPER_*)
- daemon.py: _build_broker injects config.paper into PaperBroker
- .env.example: document the 3 paper tuning keys (paper-only, e2e)
- tests: paper broker defaults + env tuning parsing

SDLC: REQ-010 CHG-20260606-01 (AC-5 added) / -02 (Implemented);
BAT-009 batch-lite release-candidate; REL-001 feedback handoff records
pre-deployment e2e observations incl. event-publisher idle-drop and
paper correlation_id collision (GitHub #4, #5).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-06 16:49:51 +09:00
co-authored by Claude Opus 4.8
parent f8c5661147
commit 2f57aad7b2
13 changed files with 280 additions and 8 deletions
+25
View File
@@ -40,6 +40,19 @@ class PortfolioHubConfig:
timeout: float = 5.0
@dataclass(frozen=True)
class PaperBrokerConfig:
"""Paper broker tuning for e2e test scenarios (REQ-011).
Defaults reproduce immediate, full fills with no rejects, so an unset
environment is byte-for-byte the prior `PaperBroker()` behaviour.
"""
fill_after_polls: int = 0
qty_per_poll: Optional[int] = None
reject_symbols: frozenset[str] = frozenset()
@dataclass(frozen=True)
class RabbitMQConfig:
"""RabbitMQ transport adapter config (REQ-022, dcy-006 OQ-6)."""
@@ -63,6 +76,8 @@ class DaemonConfig:
broker: str # 'paper' | 'kis'
execution_interval_sec: float
kis: Optional[KISConfig] = None
# REQ-011: paper broker tuning (only consulted when broker == 'paper').
paper: PaperBrokerConfig = PaperBrokerConfig()
# REQ-017/019: ledger writer config (None => in-memory stub ledger,
# appropriate for slice-1 paper-broker integration tests).
portfolio_hub: Optional[PortfolioHubConfig] = None
@@ -93,6 +108,15 @@ class DaemonConfig:
allow_live=os.environ.get("TREX_KIS_ALLOW_LIVE", "").lower() in ("1", "true", "yes"),
redis_url=os.environ.get("TREX_KIS_REDIS_URL", ""),
)
qpp_raw = os.environ.get("TREX_PAPER_QTY_PER_POLL", "").strip()
reject_raw = os.environ.get("TREX_PAPER_REJECT_SYMBOLS", "")
paper = PaperBrokerConfig(
fill_after_polls=int(os.environ.get("TREX_PAPER_FILL_AFTER_POLLS", "0")),
qty_per_poll=int(qpp_raw) if qpp_raw else None,
reject_symbols=frozenset(
s.strip() for s in reject_raw.split(",") if s.strip()
),
)
portfolio_hub: Optional[PortfolioHubConfig] = None
pf_base = os.environ.get("TREX_PORTFOLIO_HUB_BASE_URL", "").strip()
if pf_base:
@@ -118,6 +142,7 @@ class DaemonConfig:
broker=broker,
execution_interval_sec=float(os.environ.get("TREX_EXECUTION_INTERVAL_SEC", "1.0")),
kis=kis,
paper=paper,
portfolio_hub=portfolio_hub,
ledger_interval_sec=float(os.environ.get("TREX_LEDGER_INTERVAL_SEC", "1.0")),
rabbitmq=rabbitmq,
+5 -1
View File
@@ -78,7 +78,11 @@ def _build_broker(config: DaemonConfig) -> BrokerPort:
base_url=k.base_url, timeout=k.timeout, allow_live=k.allow_live,
token_cache=token_cache,
)
return PaperBroker()
return PaperBroker(
fill_after_polls=config.paper.fill_after_polls,
qty_per_poll=config.paper.qty_per_poll,
reject_symbols=set(config.paper.reject_symbols),
)
def _build_ledger(config: DaemonConfig) -> LedgerPort: