Conversation
…response The adapter knew four API paths and all four were about quotes, so nothing could read a position; the instrument catalogue loaded only shares and etfs, so a bond had no venue to take a market from. GetAccounts, GetPortfolio and Bonds join them, and entity.BrokerSyncer joins WalletSyncer and ExchangeSyncer as a third shape — ExchangeBalance is Symbol/Amount/Decimals and loses the three things a broker line needs: its instrument id, the currency of THAT row, and the instrument type that decides whether it can be valued at all. The market comes from the venue, resolved in the adapter because that is provider knowledge, and by inverting the existing venues table rather than writing a second copy of it — two hand-kept mappings would eventually disagree, and the disagreement would surface as one company owning two asset rows. THE CAPTURE CORRECTED FOUR THINGS, three of which were already written down as facts. It is committed to testdata/ (sanitised: pseudonymous account ids, rounded money, blanked names; structure untouched) and its composition matches the 2026-08-27 measurement exactly — 34 share, 7 currency, 4 etf, 2 bond. A cash line never states its own currency. currentPrice.currency is "rub" on every one of them, including the dollars, because that field says what the position is WORTH IN. Reading the code from there — which this branch did until the capture landed — books 0.88 dollars as 0.88 roubles, and merges dollars, roubles and euros into one row. The currency is in the ticker: USD000UTSTOM, RUB000UTSTOM, EUR_RUB__TOM_CETS. The unit test missed it because it used a rouble line, where the two agree. Partial blocking does not exist here. All seven blocked positions carry blockedLots = 0: the broker says THAT a line is restricted, never how much. splitByLiquidity still handles a quantity, because the field exists and ignoring it silently is its own bug, but a test now records that the branch is unexercised so nobody reads it as observed behaviour. The portfolio response does carry a venue, in classCode. That makes it the fallback for an instrument the catalogue does not hold, ahead of inferring one from the currency: DE0005190003 trades on SPBXM_OTC quoted in euros, which no currency rule places correctly. And the id field is not always a FIGI. It is a real one, or a synthetic T-Invest id (TCS00A1055Y4), or — for the four blocked foreign holdings that have no FIGI at all — the ISIN, repeated in the ticker. All three are stable within this provider, which is what the asset_external_refs namespace makes sufficient. The token is confirmed read-only on all three accounts, which is the assumption the whole "dev talks to the live API" decision rests on. make check exit 0. Reverting the bonds load, the liquidity split, the cash exception, or the ticker-derived currency each fails its own test. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015TZ3u7wZtv1Dwe24Kz7FPV
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The adapter knew four API paths and all four were about quotes, so nothing could read a position; the catalogue loaded only shares and etfs, so a bond had no venue to take a market from.
Adds
GetAccounts,GetPortfolio,Bonds, andentity.BrokerSynceras a third sync shape besideWalletSyncerandExchangeSyncer—ExchangeBalanceisSymbol/Amount/Decimalsand loses the three things a broker line needs: its instrument id, the currency of that row, and the instrument type.First step of the chain behind
stocksreading $0.00 while the broker holds ≈389 808 ₽. Supersedesvcy0.The capture corrected four things
Committed to
testdata/— sanitised (pseudonymous account ids, rounded money, blanked names; structure untouched). Composition matches the 2026-08-27 measurement exactly: 34 share, 7 currency, 4 etf, 2 bond.A cash line never states its own currency.
currentPrice.currencyisrubon every one of them, including the dollars, because that field says what the position is worth in. This branch read the code from there until the capture landed — booking 0.88 dollars as 0.88 roubles, and merging dollars, roubles and euros into one row. The currency is in the ticker:USD000UTSTOM,RUB000UTSTOM,EUR_RUB__TOM_CETS. The unit test missed it because it used a rouble line, where the two agree.Partial blocking does not exist here. All seven blocked positions carry
blockedLots = 0— the broker says that a line is restricted, never how much.splitByLiquiditystill handles a quantity (the field exists; ignoring it silently is its own bug), but a test records that the branch is unexercised, so nobody reads it as observed behaviour.The portfolio response does carry a venue, in
classCode— the brief said it did not. That makes it the fallback ahead of inferring a market from the currency:DE0005190003trades onSPBXM_OTCquoted in euros, which no currency rule places correctly.The id field is not always a FIGI. It is a real one, a synthetic T-Invest id (
TCS00A1055Y4), or — for the four blocked foreign holdings with no FIGI at all — the ISIN repeated in the ticker. All three are stable within the provider, which is what theasset_external_refsnamespace makes sufficient.Also confirmed: the token is read-only on all three accounts — the assumption the whole "dev talks to the live API" decision rests on.
Design notes
The market is resolved in the adapter (venue is provider knowledge) by inverting the existing
venuestable rather than writing a second copy: two hand-kept mappings would eventually disagree, and the disagreement would surface as one company owning two asset rows.Quantity is read from
Quotationat scale 9 — a property of the wire format, not of the instrument. Inferring it ("shares are whole numbers") is how a fractional lot becomes a rounding error nobody sees; the capture has a 0.88 cash line.Unresolved positions are counted, not dropped (
entity.BrokerSkips), and the one place this adapter guesses — an instrument absent from the catalogue — is counted separately inDefaultedMarketso the guess is never silent. Repair path isuusf.Verification
make checkexit 0. Reverting the bonds load, the liquidity split, the cash exception, or the ticker-derived currency each fails its own test.Capture-driven tests assert the wire shape; mapping rules are exercised against a synthetic universe, because a real capture cannot carry a catalogue of thousands of rows.
Not in scope
Nothing calls this yet —
SyncAccountstill refuses a broker account (lab0), and the scheduled sweep filters it out in SQL (c1nz).🤖 Generated with Claude Code
https://claude.ai/code/session_015TZ3u7wZtv1Dwe24Kz7FPV