Deliver BAT-004: broker execution + KIS adapter + durable handoff
Implement REQ-011~015 (dcy-003): the order-execution layer on top of the BAT-002 slot-core daemon, connecting intents to real orders. - application: BrokerPort (submit/poll_fill/cancel) + ExecutionService (claim -> submit -> poll -> transition -> rollback -> reconcile) - adapters/broker: PaperBroker (deterministic async double) and KISBroker (KIS Open API: token, order-cash, inquire-daily-ccld, cancel; VTS sandbox tr_ids; live account gated behind allow_live) - persistence: option-A execution queue (find_pending_for_dispatch FOR UPDATE SKIP LOCKED, mark_dispatched idempotency guard, clear_dispatch) - runtime: background execution loop in the daemon lifespan + startup reconcile; config selects paper|kis Verification (real KIS VTS 모의투자 + PostgreSQL): 92 passed, ruff clean, domain import sweep shows zero broker/web/db deps, buy->holding / sell->closed / reject->idle, idempotent dispatch (exactly one order), reconcile after crash creates no duplicate order, KIS sandbox token+submit round-trip, and a daemon E2E through the background loop. Align the four baseline docs. BAT-004 -> release-candidate; REQ-011~015 -> Implemented. KIS live-account go-live, partial-fill accrual, option-B outbox, connection pooling, and the safety floor remain deferred to follow-up Discoveries. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
"""Daemon configuration resolution (REQ-005, REQ-010)."""
|
||||
"""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:
|
||||
@@ -19,12 +20,25 @@ def _load_dotenv() -> None:
|
||||
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
|
||||
|
||||
|
||||
@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
|
||||
|
||||
@staticmethod
|
||||
def from_env() -> "DaemonConfig":
|
||||
@@ -34,9 +48,23 @@ class DaemonConfig:
|
||||
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"),
|
||||
)
|
||||
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,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user