feat: add bounded KIS account read recovery

This commit is contained in:
jordan
2026-07-29 17:40:01 +09:00
parent 3dd8eefc92
commit 0266992238
29 changed files with 970 additions and 26 deletions
+14 -1
View File
@@ -30,6 +30,7 @@ from typing import Callable, Optional
from trex_core.application.alarm_emitter import AlarmEmitter
from trex_core.application.broker_port import (
BalanceItem,
BrokerPort,
CancelOutcome,
CancelState,
@@ -84,6 +85,7 @@ class ExecutionService:
stale_pending_threshold_sec: Optional[int] = None,
instance_key: str = "default",
alarm_emitter: Optional[AlarmEmitter] = None,
balance_reader: Optional[Callable[[], list[BalanceItem]]] = None,
) -> None:
self._svc = slot_service
self._repo = repo
@@ -104,6 +106,10 @@ class ExecutionService:
)
# dcy-033 / REQ-100: best-effort alarm emitter (None = no alarms sent).
self._alarm_emitter = alarm_emitter
# dcy-041 / GitHub #45: pending-sell quantity may use a bounded
# account snapshot only through this injected reader. Reconciliation
# remains wired directly to the live broker.
self._sell_dispatch_balance_reader = balance_reader or broker.get_balance
# ------------------------------------------------------------
def run_once(self) -> ExecutionResult:
@@ -136,7 +142,7 @@ class ExecutionService:
# surfaces via reconcile_core_holdings (REQ-062 A). No mirror
# fallback by design.
if sell_balance is None:
sell_balance = self._balance_map()
sell_balance = self._sell_dispatch_balance_map()
bal = sell_balance.get(slot.symbol or "", 0)
# dcy-021 / REQ-062 (D4): full sell (slot.qty is None) => broker
# balance; partial sell => min(order qty, broker balance). Broker
@@ -313,6 +319,13 @@ class ExecutionService:
"""Current broker balance as {symbol: hldg_qty} (REQ-062 ground truth)."""
return {b.symbol: int(b.hldg_qty) for b in self._broker.get_balance()}
def _sell_dispatch_balance_map(self) -> dict:
"""Balance used only to size a not-yet-submitted pending sell."""
return {
b.symbol: int(b.hldg_qty)
for b in self._sell_dispatch_balance_reader()
}
def _resolve_fill_price(self, fs, slot) -> tuple[float | None, bool]:
"""Resolve the fill price from FillStatus, with limit_price backfill.