"""Derived positions against the broker's snapshot — and only where a snapshot exists.""" from datetime import UTC, datetime, timedelta from decimal import Decimal from sqlalchemy import select from factories import make_account, make_event, make_instrument, refresh from fintracker.analytics import today_local from fintracker.db import get_sessionmaker from fintracker.models import ( AccountKind, AccountRole, CashSnapshot, EventKind, MetricDataQuality, PositionSnapshot, ) D = Decimal AS_OF = datetime(2026, 9, 19, 12, 0, tzinfo=UTC) async def broker(name: str, *, source: str = "tinvest") -> int: return await make_account( name=name, kind=AccountKind.broker, role=AccountRole.investment, balance=None, source=source, ) async def buy(account: int, instrument: int, qty: int) -> None: await make_event( today_local() - timedelta(days=5), account_id=account, kind=EventKind.buy, instrument_id=instrument, quantity=qty, price=100, amount=-100 * qty, ) async def snapshot(account: int, instrument: int | None, qty: str = "0", cash: str = "0") -> None: async with get_sessionmaker()() as session: if instrument is not None: session.add( PositionSnapshot( account_id=account, instrument_id=instrument, as_of=AS_OF, source="tinvest", qty=D(qty), currency="RUB", ) ) # fmt: skip session.add( CashSnapshot( account_id=account, currency="RUB", as_of=AS_OF, source="tinvest", balance=D(cash) ) ) await session.commit() async def flagged() -> set[int] | None: async with get_sessionmaker()() as session: row = ( await session.execute( select(MetricDataQuality).where( MetricDataQuality.check_name == "position_vs_snapshot" ) ) ).scalar_one_or_none() if row is None or row.ref is None: return None return set(row.ref["instruments"]) async def test_matching_snapshot_is_no_finding(app): account = await broker("Т") sber = await make_instrument(ticker="SBER") await buy(account, sber, 10) await snapshot(account, sber, "10") await refresh() assert await flagged() is None async def test_a_position_the_broker_does_not_list_is_flagged(app): account = await broker("Т") sber, gazp = await make_instrument(ticker="SBER"), await make_instrument(ticker="GAZP") await buy(account, sber, 10) await buy(account, gazp, 5) await snapshot(account, sber, "10") # the broker lists SBER only await refresh() assert await flagged() == {gazp} async def test_a_snapshot_that_lists_nothing_still_catches_a_leftover_position(app): """Everything sold at the broker leaves a cash snapshot and no positions: the ledger still holding a paper is exactly what the check is for.""" account = await broker("Т") sber = await make_instrument(ticker="SBER") await buy(account, sber, 10) await snapshot(account, None, cash="500") await refresh() assert await flagged() == {sber} async def test_an_account_fed_by_a_report_has_no_snapshot_and_is_not_flagged(app): """Sber and VTB send no snapshot; their positions are reconciled against the report.""" tinvest = await broker("Т") sber_account = await broker("Сбер", source="report_sber") sber, mtss = await make_instrument(ticker="SBER"), await make_instrument(ticker="MTSS") await buy(tinvest, sber, 10) await snapshot(tinvest, sber, "10") await buy(sber_account, sber, 20) await buy(sber_account, mtss, 3) await refresh() assert await flagged() is None