"""A paper the exchange never quotes is priced by the broker's own mark (T-Invest snapshot).""" from __future__ import annotations from datetime import UTC, date, datetime from decimal import Decimal from sqlalchemy import select from factories import make_account, make_instrument, make_price from fintracker.db import get_sessionmaker from fintracker.models import AccountKind, AccountRole, AssetClass, Instrument from fintracker.models.pricing import PriceDaily from fintracker.sources.tinvest.client import PortfolioSnapshot, PositionLine from fintracker.sources.tinvest.sync import _store_snapshot D = Decimal CAPTURED = datetime(2026, 9, 19, 17, 10, tzinfo=UTC) # 20:10 in Moscow, the same calendar day DAY = date(2026, 9, 19) async def with_uid(instrument_id: int, uid: str) -> None: async with get_sessionmaker()() as session: row = await session.get(Instrument, instrument_id) assert row is not None row.tinvest_uid = uid await session.commit() def line(uid: str, *, price: str | None = "12354.6177", qty: str = "2") -> PositionLine: return PositionLine( instrument_uid=uid, figi=None, quantity=D(qty), average_price=D("11353.3839"), current_price=None if price is None else D(price), currency="RUB", instrument_type="bond", ) async def store(account: int, *positions: PositionLine, at: datetime = CAPTURED) -> None: snapshot = PortfolioSnapshot( account_id="tinv-1", captured_at=at, positions=list(positions), cash=[], payload={} ) async with get_sessionmaker()() as session: await _store_snapshot(session, account, snapshot) await session.commit() async def prices(instrument_id: int) -> list[PriceDaily]: async with get_sessionmaker()() as session: return list( ( await session.execute( select(PriceDaily) .where(PriceDaily.instrument_id == instrument_id) .order_by(PriceDaily.d) ) ) .scalars() .all() ) async def broker_account() -> int: return await make_account( name="ΠΆ", kind=AccountKind.broker, role=AccountRole.investment, balance=None, source="tinvest", source_id="tinv-1", ) # fmt: skip async def test_an_unquoted_paper_gets_the_brokers_mark_as_the_days_close(app): account = await broker_account() note = await make_instrument(ticker="SIBN6P4", asset_class=AssetClass.bond, board=None) await with_uid(note, "uid-note") await store(account, line("uid-note")) (row,) = await prices(note) assert (row.d, row.close, row.currency, row.source) == (DAY, D("12354.6177"), "RUB", "tinvest") async def test_the_mark_is_refreshed_within_the_day(app): account = await broker_account() note = await make_instrument(ticker="SIBN6P4", asset_class=AssetClass.bond, board=None) await with_uid(note, "uid-note") await store(account, line("uid-note", price="12000")) await store(account, line("uid-note", price="12100")) (row,) = await prices(note) assert row.close == D("12100") async def test_an_exchange_priced_paper_is_left_to_the_exchange(app): account = await broker_account() sber = await make_instrument(ticker="SBER") await with_uid(sber, "uid-sber") await make_price(date(2026, 9, 18), instrument_id=sber, close="300") async with get_sessionmaker()() as session: row = ( await session.execute(select(PriceDaily).where(PriceDaily.instrument_id == sber)) ).scalar_one() row.source = "moex" await session.commit() await store(account, line("uid-sber", price="999")) assert [(p.d, p.source) for p in await prices(sber)] == [(date(2026, 9, 18), "moex")] async def test_it_never_overwrites_another_sources_row_for_the_day(app): account = await broker_account() note = await make_instrument(ticker="SIBN6P4", asset_class=AssetClass.bond, board=None) await with_uid(note, "uid-note") await make_price(DAY, instrument_id=note, close="13000") # a manual entry, say async with get_sessionmaker()() as session: row = ( await session.execute(select(PriceDaily).where(PriceDaily.instrument_id == note)) ).scalar_one() row.source = "manual" await session.commit() await store(account, line("uid-note", price="12354")) (row,) = await prices(note) assert (row.close, row.source) == (D("13000"), "manual") async def test_cash_and_lines_without_a_mark_are_skipped(app): account = await broker_account() rub = await make_instrument(ticker="RUB000UTSTOM", asset_class=AssetClass.currency, board=None) bare = await make_instrument(ticker="BARE", asset_class=AssetClass.bond, board=None) await with_uid(rub, "uid-rub") await with_uid(bare, "uid-bare") await store(account, line("uid-rub", price="1"), line("uid-bare", price=None)) assert await prices(rub) == [] assert await prices(bare) == []