feat: persist unmanaged position lifecycle state
Co-authored-by: genichin <4282227+genichin@users.noreply.github.com>
This commit is contained in:
co-authored by
genichin
parent
ae2e598209
commit
fe51d211d9
@@ -33,7 +33,11 @@ from typing import cast
|
||||
|
||||
from trex_core.application.alarm_emitter import AlarmEmitter
|
||||
from trex_core.application.broker_port import BrokerPort
|
||||
from trex_core.application.portfolio import ReconcileWarningStore, SnapshotStore
|
||||
from trex_core.application.portfolio import (
|
||||
ReconcileWarningStore,
|
||||
SnapshotStore,
|
||||
UnmanagedPositionStore,
|
||||
)
|
||||
from trex_core.domain.slot.state import SlotState
|
||||
|
||||
_log = logging.getLogger(__name__)
|
||||
@@ -341,6 +345,25 @@ def _retry_pending_resolved(
|
||||
)
|
||||
|
||||
|
||||
def _notify_unmanaged_position_transition(
|
||||
alarm_emitter: AlarmEmitter,
|
||||
*,
|
||||
event_type: str,
|
||||
instance_key: str,
|
||||
symbol: str,
|
||||
quantity: int,
|
||||
) -> None:
|
||||
alarm_emitter.publish_alarm(
|
||||
f"unmanaged_position_{event_type}",
|
||||
f"unmanaged position {event_type}: symbol={symbol}",
|
||||
fields={
|
||||
"instance_key": instance_key,
|
||||
"symbol": symbol,
|
||||
"broker_quantity": quantity,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def reconcile_core_holdings(
|
||||
*,
|
||||
slot_repo,
|
||||
@@ -350,6 +373,7 @@ def reconcile_core_holdings(
|
||||
alarm_emitter: AlarmEmitter | None = None,
|
||||
warn_store: ReconcileWarningStore | None = None,
|
||||
notification_store=None,
|
||||
unmanaged_position_store: UnmanagedPositionStore | None = None,
|
||||
) -> dict[str, str]:
|
||||
"""dcy-020 / REQ-062 (BAT-029) + REQ-103 (BAT-050): surface core-slot holding
|
||||
vs broker divergence and set durable reconcile-warning for B case.
|
||||
@@ -377,6 +401,7 @@ def reconcile_core_holdings(
|
||||
decisions: dict[str, str] = {}
|
||||
matched: set[str] = set()
|
||||
active_b_identity_keys: set[str] = set() # req-0007: track active B mismatches for retry
|
||||
active_unmanaged_symbols: set[str] = set()
|
||||
for slot in slot_repo.list_holding_slots(instance_key):
|
||||
sym = slot.symbol
|
||||
matched.add(sym)
|
||||
@@ -458,10 +483,78 @@ def reconcile_core_holdings(
|
||||
)
|
||||
else:
|
||||
decisions[f"broker:{sym}"] = "unmanaged_position"
|
||||
_log.warning(
|
||||
"unmanaged position: symbol=%s qty=%d instance_key=%s",
|
||||
sym, bq, instance_key,
|
||||
active_unmanaged_symbols.add(sym)
|
||||
if unmanaged_position_store is None:
|
||||
_log.warning(
|
||||
"unmanaged position: symbol=%s qty=%d instance_key=%s",
|
||||
sym, bq, instance_key,
|
||||
)
|
||||
else:
|
||||
try:
|
||||
observed = unmanaged_position_store.observe(
|
||||
instance_key=instance_key,
|
||||
symbol=sym,
|
||||
quantity=bq,
|
||||
)
|
||||
except Exception: # noqa: BLE001
|
||||
_log.exception(
|
||||
"failed to persist unmanaged_position for symbol=%s instance=%s",
|
||||
sym,
|
||||
instance_key,
|
||||
)
|
||||
_log.warning(
|
||||
"unmanaged position: symbol=%s qty=%d instance_key=%s",
|
||||
sym, bq, instance_key,
|
||||
)
|
||||
else:
|
||||
transition = observed["transition"]
|
||||
if transition == "detected":
|
||||
_log.warning(
|
||||
"unmanaged position detected: symbol=%s qty=%d instance_key=%s",
|
||||
sym, bq, instance_key,
|
||||
)
|
||||
if alarm_emitter is not None:
|
||||
_notify_unmanaged_position_transition(
|
||||
alarm_emitter,
|
||||
event_type="detected",
|
||||
instance_key=instance_key,
|
||||
symbol=sym,
|
||||
quantity=bq,
|
||||
)
|
||||
elif transition == "quantity_changed":
|
||||
_log.warning(
|
||||
"unmanaged position quantity changed: symbol=%s "
|
||||
"previous_qty=%s qty=%d instance_key=%s",
|
||||
sym, observed.get("previous_quantity"), bq, instance_key,
|
||||
)
|
||||
if alarm_emitter is not None:
|
||||
_notify_unmanaged_position_transition(
|
||||
alarm_emitter,
|
||||
event_type="changed",
|
||||
instance_key=instance_key,
|
||||
symbol=sym,
|
||||
quantity=bq,
|
||||
)
|
||||
if unmanaged_position_store is not None:
|
||||
try:
|
||||
for state in unmanaged_position_store.resolve_absent(
|
||||
instance_key=instance_key,
|
||||
observed_symbols=active_unmanaged_symbols,
|
||||
):
|
||||
_log.info(
|
||||
"unmanaged position resolved: symbol=%s qty=%d instance_key=%s",
|
||||
state["symbol"], state["quantity"], instance_key,
|
||||
)
|
||||
if alarm_emitter is not None:
|
||||
_notify_unmanaged_position_transition(
|
||||
alarm_emitter,
|
||||
event_type="resolved",
|
||||
instance_key=instance_key,
|
||||
symbol=state["symbol"],
|
||||
quantity=state["quantity"],
|
||||
)
|
||||
except Exception: # noqa: BLE001
|
||||
_log.exception("reconcile_core_holdings: unmanaged_position resolve failed")
|
||||
# req-0007 / ac-0024: retry pending_resolved for B-case identities no longer mismatched.
|
||||
if alarm_emitter is not None and notification_store is not None:
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user