feat(alarm): trex-core 알람 emitter + trex.alerts exchange (dcy-033 BAT-049, Implemented)
AlarmEmitter(application, best-effort 비차단)+trex.alerts topic exchange 가산·격리+8 CRITICAL code 배선. 608 passed, opus QC PASS, 팀장 e2e(텔레그램 HTTP200) 통과. REQ-098~101 Implemented. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VHtM5Ws1HKCKQ4JGi6GRb5
This commit is contained in:
@@ -27,6 +27,7 @@ from dataclasses import dataclass
|
||||
from datetime import datetime, timezone
|
||||
from typing import Callable, Optional
|
||||
|
||||
from trex_core.application.alarm_emitter import AlarmEmitter
|
||||
from trex_core.application.broker_port import BrokerPort, OrderRequest
|
||||
from trex_core.application.ledger_outbox import LedgerOutbox
|
||||
from trex_core.application.ledger_port import LedgerTrade
|
||||
@@ -67,6 +68,7 @@ class ExecutionService:
|
||||
event_emitter: Optional[SlotEventEmitter] = None,
|
||||
stale_pending_threshold_sec: Optional[int] = None,
|
||||
instance_key: str = "default",
|
||||
alarm_emitter: Optional[AlarmEmitter] = None,
|
||||
) -> None:
|
||||
self._svc = slot_service
|
||||
self._repo = repo
|
||||
@@ -85,6 +87,8 @@ class ExecutionService:
|
||||
if stale_pending_threshold_sec and stale_pending_threshold_sec > 0
|
||||
else None
|
||||
)
|
||||
# dcy-033 / REQ-100: best-effort alarm emitter (None = no alarms sent).
|
||||
self._alarm_emitter = alarm_emitter
|
||||
|
||||
# ------------------------------------------------------------
|
||||
def run_once(self) -> ExecutionResult:
|
||||
@@ -130,6 +134,13 @@ class ExecutionService:
|
||||
"(divergence — no order submitted; operator/reconcile attention)",
|
||||
slot.slot_id, slot.symbol,
|
||||
)
|
||||
# dcy-033 / REQ-100: CRITICAL alarm — PENDING_SELL may stick.
|
||||
if self._alarm_emitter is not None:
|
||||
self._alarm_emitter.publish_alarm(
|
||||
"sell_skipped_no_balance",
|
||||
f"매도 dispatch skip: 브로커 잔고 0 (slot={slot.slot_id} symbol={slot.symbol})",
|
||||
fields={"slot_id": str(slot.slot_id), "symbol": slot.symbol},
|
||||
)
|
||||
continue
|
||||
else:
|
||||
qty = int(slot.qty or 0)
|
||||
@@ -265,6 +276,14 @@ class ExecutionService:
|
||||
slot.slot_id, slot.symbol, slot.broker_order_id,
|
||||
avg, limit,
|
||||
)
|
||||
# dcy-033 / REQ-100: CRITICAL alarm — slot stuck in PENDING.
|
||||
if self._alarm_emitter is not None:
|
||||
self._alarm_emitter.publish_alarm(
|
||||
"fill_price_unresolvable",
|
||||
f"체결가 미해결: 슬롯 PENDING 고착 위험 (slot={slot.slot_id} symbol={slot.symbol})",
|
||||
fields={"slot_id": str(slot.slot_id), "symbol": slot.symbol,
|
||||
"broker_order_id": slot.broker_order_id},
|
||||
)
|
||||
return None, False
|
||||
|
||||
def _on_filled(self, slot, fs, side: str, r: ExecutionResult) -> None:
|
||||
|
||||
Reference in New Issue
Block a user