test: cover unmanaged position lifecycle flow

Co-authored-by: genichin <4282227+genichin@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-09-12 13:27:12 +00:00
committed by GitHub
co-authored by genichin
parent fe51d211d9
commit f2b825e720
6 changed files with 45 additions and 42 deletions
+20 -18
View File
@@ -12,8 +12,8 @@ from __future__ import annotations
import logging
import os
import threading
from datetime import date, datetime, time, timedelta, timezone
from typing import Callable, Optional
from collections.abc import Callable
from datetime import UTC, date, datetime, time, timedelta, timezone
from trex_core.application.alarm_emitter import AlarmEmitter
from trex_core.application.broker_port import BrokerPort
@@ -23,10 +23,10 @@ from trex_core.application.portfolio import (
PortfolioHubReadPort,
PortfolioHubUnavailableError,
ReconcileNotificationStore,
UnmanagedPositionStore,
ReconcileWarningStore,
SnapshotStore,
TradingContext,
UnmanagedPositionStore,
)
from trex_core.application.reconciliation import reconcile_positions
@@ -45,32 +45,32 @@ class SnapshotScheduler:
store: SnapshotStore,
broker: BrokerPort,
schedule_kst: time = time(hour=7, minute=30),
clock: Optional[Callable[[], datetime]] = None,
clock: Callable[[], datetime] | None = None,
slot_repo=None,
slot_service=None,
instance_key: str = "default",
alarm_emitter: Optional[AlarmEmitter] = None,
warn_store: Optional[ReconcileWarningStore] = None,
notification_store: Optional[ReconcileNotificationStore] = None,
unmanaged_position_store: Optional[UnmanagedPositionStore] = None,
alarm_emitter: AlarmEmitter | None = None,
warn_store: ReconcileWarningStore | None = None,
notification_store: ReconcileNotificationStore | None = None,
unmanaged_position_store: UnmanagedPositionStore | None = None,
credential_fetch_enabled: bool = False,
kis_reinit_callback: Optional[Callable[[BrokerCredentialPayload], None]] = None,
credential_refresh: Optional[Callable[[], object]] = None,
kis_reinit_callback: Callable[[BrokerCredentialPayload], None] | None = None,
credential_refresh: Callable[[], object] | None = None,
) -> None:
self._read = read_port
self._store = store
self._broker = broker
self._schedule_kst = schedule_kst
self._clock = clock or (lambda: datetime.now(tz=timezone.utc))
self._daemon_id: Optional[str] = None
self._last_fired_date: Optional[datetime] = None
self._clock = clock or (lambda: datetime.now(tz=UTC))
self._daemon_id: str | None = None
self._last_fired_date: datetime | None = None
# dcy-020 / REQ-058 (BAT-030): asset-centric slot reconcile after each
# universe fetch — always on when slot_repo is wired (the reconcile is
# what creates/retires the permanent asset slots).
self._slot_repo = slot_repo
self._slot_service = slot_service
self._instance_key = instance_key
self._last_universe_size: Optional[int] = None
self._last_universe_size: int | None = None
# dcy-033 / REQ-100: best-effort alarm emitter.
self._alarm_emitter = alarm_emitter
# BAT-050 / REQ-103: durable reconcile-warning store (Optional).
@@ -84,7 +84,9 @@ class SnapshotScheduler:
# dcy-036 / REQ-113 AC-2 / REQ-115 AC-1: optional callback to reinitialize
# KISBroker with the fetched day-start credential. None = no reinit (paper
# broker or credential_fetch_enabled=False). Set by daemon.py assembly.
self._kis_reinit_callback: Optional[Callable[[BrokerCredentialPayload], None]] = kis_reinit_callback
self._kis_reinit_callback: Callable[[BrokerCredentialPayload], None] | None = (
kis_reinit_callback
)
# REQ-119: provider-owned refresh supersedes the legacy raw fetch hook.
self._credential_refresh = credential_refresh
# REQ-113: detect break-glass at init time (env var set by operator).
@@ -93,8 +95,8 @@ class SnapshotScheduler:
in ("1", "true", "yes")
)
# REQ-115: day-scoped credential cache. None until first successful fetch.
self._cached_credential: Optional[BrokerCredentialPayload] = None
self._credential_date: Optional[date] = None
self._cached_credential: BrokerCredentialPayload | None = None
self._credential_date: date | None = None
# REQ-114: entry gate. True = new entries allowed (default).
# credential_fetch_enabled=False keeps this always True (no gate).
self._entry_allowed: bool = True
@@ -266,7 +268,7 @@ class SnapshotScheduler:
try:
self._credential_refresh()
self._entry_allowed = True
except Exception as exc: # provider exposes only sanitized failure
except Exception as exc: # noqa: BLE001 - provider exposes only sanitized failure
_log.error("DAY_START_LOAD_FAILED: broker credential refresh failed: %s (instance=%s)",
exc.__class__.__name__, self._instance_key)
self._entry_allowed = False
+6 -6
View File
@@ -737,12 +737,12 @@ def _build_parser() -> argparse.ArgumentParser:
def _cmd_sync(args, cfg, confirm) -> int:
"""REQ-105 (dcy-034, BAT-051): sync slot qty to broker ground truth."""
from trex_core.adapters.portfolio.reconcile_warning_store import (
PostgreSQLReconcileWarningStore,
)
from trex_core.adapters.portfolio.reconcile_notification_store import (
PostgreSQLReconcileNotificationStore,
)
from trex_core.adapters.portfolio.reconcile_warning_store import (
PostgreSQLReconcileWarningStore,
)
from trex_core.application.reconciliation import sync_slot_to_broker
if not args.portfolio_id and not args.all:
@@ -891,12 +891,12 @@ def _cmd_sync(args, cfg, confirm) -> int:
def _cmd_verify(args, cfg) -> int:
"""REQ-106 (dcy-034, BAT-051): re-check C-case reconcile-warning (read-centric)."""
from trex_core.adapters.persistence.postgresql import PostgreSQLSlotRepository
from trex_core.adapters.portfolio.reconcile_warning_store import (
PostgreSQLReconcileWarningStore,
)
from trex_core.adapters.portfolio.reconcile_notification_store import (
PostgreSQLReconcileNotificationStore,
)
from trex_core.adapters.portfolio.reconcile_warning_store import (
PostgreSQLReconcileWarningStore,
)
from trex_core.application.portfolio import SnapshotStore
from trex_core.application.reconciliation import verify_slot_reconcile
+4 -5
View File
@@ -31,11 +31,11 @@ from trex_core.adapters.messaging import (
RabbitMQTransport,
topology,
)
from trex_core.adapters.persistence.migrate import apply_migrations
from trex_core.adapters.persistence.account_snapshot import (
PostgreSQLAccountSnapshotStore,
)
from trex_core.adapters.persistence.ledger_skip_store import PostgreSQLLedgerSkipStore
from trex_core.adapters.persistence.migrate import apply_migrations
from trex_core.adapters.persistence.postgresql import (
PgStrategyStateStore,
PostgreSQLIntentStore,
@@ -55,16 +55,15 @@ from trex_core.adapters.portfolio.reconcile_notification_store import (
from trex_core.adapters.portfolio.unmanaged_position_store import (
PostgreSQLUnmanagedPositionStore,
)
from trex_core.application.alarm_emitter import AlarmEmitter
from trex_core.application.account_snapshot import AccountSnapshotService
from trex_core.application.alarm_emitter import AlarmEmitter
from trex_core.application.broker_port import BrokerPort
from trex_core.application.execution import ExecutionService
from trex_core.application.intents import IntentService
from trex_core.application.intents import BuyIntent
from trex_core.application.intents import BuyIntent, IntentService
from trex_core.application.killswitch import KillSwitchService
from trex_core.application.ledger_outbox import LedgerWriter
from trex_core.application.ledger_port import LedgerPort
from trex_core.application.messaging import MessagingTransport
from trex_core.application.killswitch import KillSwitchService
from trex_core.application.messaging_handlers import (
make_control_rpc_handler,
make_intent_rpc_handler,