When to Use
Invoke this skill whenever a strategy distributes execution across multiple brokerage accounts or exchanges (e.g. US equities on IBKR and Alpaca, crypto on Binance). Isolated per-broker position views create blind spots in gross exposure and risk limits. This skill normalizes broker-specific symbol formats, converts multi-currency market values into a single base currency, nets long/short holdings across brokers, and audits position discrepancies against the strategy's own target ledger.
It is the normalization layer: cross-account-aggregate-risk-view explicitly delegates FX conversion, broker-symbol normalization, and multi-currency valuation to this skill and consumes canonical symbols and base-currency values from it. Numbers produced here therefore end up inside firm-wide gross-market-value caps, so this module fails closed rather than emitting a plausible-looking wrong figure.
When NOT to Use
- As a substitute for the broker's own books. This is a derived view of snapshots you supply. The broker's statement is authoritative for settlement, margin and tax; a break found here means investigate, never overwrite the broker.
- For lot-level cost basis, tax lots, or realized P&L.
weighted_avg_cost_baseis an indicative net basis blended from brokers that compute cost basis under different rules, and it is undefined for a flat net position. Usefifo-vs-specific-lot-tax-accounting-methodsandcross-strategy-tax-lot-optimizationfor anything an accountant or a tax authority will read. - For firm-wide limit enforcement, cash, or margin. No cash balances, no margin utilization, no GMV cap. Feed this skill's output into
cross-account-aggregate-risk-view(firm-wide caps) ormargin-utilization-circuit-breaker. - To net opposing orders before routing. This flags a position-level symptom (
is_internally_offset); the execution-side fix belongs tomulti-order-netting-before-routing. - As a live event-driven position tracker. It consolidates point-in-time snapshots. It does not poll brokers, does not consume fill streams, and holds no position state between calls.
- For options risk. It values option positions with a supplied multiplier; it computes no Greeks and no exposure-equivalent delta — see
options-greeks-real-time-portfolio-aggregation.
Prerequisites
- Position snapshots from each broker adapter (
broker_name,broker_symbol, signedquantity,average_cost,current_price, ISO 4217currency, andcontract_multiplierfor anything that is not cash equity or spot). - A symbol translation map (broker symbol → canonical symbol) covering every symbol traded, not just the ones that currently collide.
- An FX rate table expressed as units of base currency per one unit of the quoted currency, containing the base currency itself at exactly 1.0. There is no built-in default table — a hardcoded rate is stale the moment it is written.
- For each broker adapter, a determination of whether its reported cost field already embeds the contract multiplier (IBKR's
avgCostdoes for derivatives; itsavgPricedoes not).
Workflow
-
Ingest Raw Broker Positions:
- Build a
RawBrokerPositionper leg. Quantities are signed — negative is short, matching IBKR, Alpacaqty, and BinancepositionAmt. Validation happens on construction, so a NaN price or a malformed currency code raises here rather than poisoning an aggregate. - Decision point — is this instrument a derivative? If yes,
contract_multiplieris mandatory and cannot be inferred from the symbol. A standard OCC equity option covers 100 shares; corporate actions can leave an adjusted contract delivering something other than 100 shares while keeping a 100 premium multiplier. Read the multiplier from the contract definition, never from the ticker. - Decision point — does this broker's cost field already include the multiplier? Set
average_cost_includes_multiplier=Trueif so. Getting this backwards on a 50× futures contract inflates cost basis 50-fold and reports a large loss on a winning position.
- Build a
-
Normalize Symbols:
- Resolve
broker_symbol→ canonical symbol. - Decision point — what should an unmapped symbol do? In production set
strict_symbol_mapping=Trueso it raises. The default fallback (upper-case the raw ticker) is what silently turnsAAPLandAAPL.USinto two canonical symbols, halving apparent netting and double-counting the asset. If you must run non-strict, checkunmapped_broker_symbols()before trusting the output.
- Resolve
-
Convert to Base Currency:
- Every leg is multiplied by
fx_rates[currency]. An unknown currency raisesMissingFxRateError— it is never assumed to be 1:1. - Decision point — how old are these snapshots? Set
max_snapshot_ageand passvaluation_time. Legs and the FX table are then age-checked and a stale one raises. Consolidating a 40-minute-old crypto snapshot with a live equity snapshot produces a view that was never simultaneously true at any broker.snapshot_skew_secondsreports the spread across a symbol's legs when all of them are timestamped.
- Every leg is multiplied by
-
Aggregate & Net Consolidated Position:
- Per canonical symbol: $Q_{\text{net}} = \sum_b Q_b$, $Q_{\text{gross}} = \sum_b |Q_b|$, signed net market value, gross market value $\sum_b |MV_b|$, cost basis, and unrealized P&L.
- Decision point — which value does the downstream limit consume? A gross-market-value cap must read
gross_market_value_base. The signedtotal_market_value_basecollapses a long-100/short-100 book to roughly zero while both legs still consume margin and carry borrow cost. weighted_avg_cost_baseisNone— not0.0— when the net position is flat within tolerance, because the quotient diverges as net quantity approaches zero.
-
Reconciliation Audit:
- Compare the strategy's internal target ledger against consolidated broker holdings.
- Decision point — classify before reacting.
QUANTITY_MISMATCH(both sides hold it, sizes differ) usually means a partial or missed fill.MISSING_AT_BROKERmeans an expected position does not exist — an unfilled order, or a broker-side forced close-out.UNEXPECTED_AT_BROKERmeans a broker holds something the strategy never intended, which is also the signature of a symbol-mapping failure creating a phantom symbol. Rule out the mapping before treating it as a rogue fill. - Decision point — is the tolerance right for this instrument? The 1e-5 default suits share quantities and is far too coarse for an 8-decimal crypto quantity, where it silently accepts a 50% position error on a small holding. Set
symbol_tolerancesper instrument.
Full procedure: see
references/workflows.md. Standards reference: seereferences/standards.md. Printable pre-flight checklist: seeassets/checklist.md.
Common Pitfalls
- Defaulting an unknown currency to 1:1: a currency missing from the rate table must fail, not pass through unconverted. A ¥10,000,000 position valued at 1:1 enters the firm-wide view as $10,000,000 instead of roughly $67,000 — a plausible-looking number that is wrong by more than two orders of magnitude, with no error anywhere.
- Shipping a hardcoded default FX table: a rate literal is stale the moment it is committed, and a caller who forgets to supply rates gets a confidently wrong exposure rather than a failure. Rates must be injected and timestamped.
- Dropping the contract multiplier: valuing an equity option at
quantity × priceunderstates it 100×. Twenty contracts at a $7.50 premium are $15,000 of exposure, not $150 — the difference between passing and breaching a gross-exposure cap. - Double-applying the multiplier: IBKR's
avgCostalready reflects it for derivatives. Multiplying again turns a $840,000 cost basis into $42,000,000 and flips a $15,000 gain into a multi-million-dollar phantom loss. - Unmatched symbol mismatches: failing to translate exchange-specific suffixes (
AAPL.USvsAAPL) yields two canonical entries for one asset. The offsetting legs never meet, so netting is understated and the same economic exposure is counted twice. - Reporting net market value as exposure: long 100 AAPL at Broker A and short 100 at Broker B nets to roughly zero market value while consuming margin, borrow, and commission on both sides.
gross_market_value_baseandis_internally_offsetare what surface it. - Dividing cost basis by a near-zero net quantity: a book that is long 100 and short 99.999999 has a net of 1e-6, and a naive average cost per contract in the millions. Treat a flat net position as having no defined average cost.
- Consolidating snapshots taken minutes apart: brokers are polled independently. Without an
as_ofper leg and a maximum age, a stale leg is indistinguishable from a live one, and the "point-in-time" view never existed at any point in time. - Setting a reconciliation tolerance at the exact expected increment: a tolerance written as a round decimal is not round in binary floating point —
100.01 - 100.0evaluates to0.010000000000005, which exceeds a0.01tolerance and alerts a purely representational residue as a position break every cycle. Leave headroom. - Formatting break alerts to two decimals: a 5e-08 BTC discrepancy printed as
+0.00reads as a non-event in the very alert raised to report it. - Treating the blended average cost as an accounting figure: it mixes brokers computing cost basis under different rules — Alpaca alone uses weighted average intraday and compressed FIFO end-of-day. It is indicative only.
Verification
- Consolidate two AAPL legs (IBKR long 500 @ $150 cost / $160 mark, Alpaca short 200 @ $155 / $160): verify
net_quantity300,gross_quantity700,total_market_value_base$48,000, andgross_market_value_base$112,000 — the netted figure understates exposure by $64,000, andis_internally_offsetmust be true. - Value 20 equity-option contracts at a $7.50 mark with
contract_multiplier=100: verify $15,000, not $150. Value 3 futures at 5,700 index withcontract_multiplier=50andaverage_cost_includes_multiplier=Trueagainst a 280,000avgCost: verify $855,000 market value, $840,000 cost, $15,000 unrealized — re-applying the 50× would report $42,000,000 of cost. - Convert a 40-share EUR position marked at €640 with a 1.25 USD-per-EUR rate: verify $32,000 (an inverted rate gives $20,480, a 1:1 fallback €25,600-as-dollars).
- Negative checks, each of which must raise: constructing the ledger with no
fx_rates; an FX table missing the base currency or holding it at anything other than 1.0; a position in a currency absent from the table; a NaN or infinite quantity/cost/price; a negative price; a malformed currency code; a non-positivecontract_multiplier; a naive (non-timezone-aware)as_of; a leg older thanmax_snapshot_age; a leg stamped ahead ofvaluation_time; an unmapped symbol understrict_symbol_mapping; duplicate target-ledger keys differing only in case. - Verify a long 100 / short 99.999999 book reports
weighted_avg_cost_base is Nonerather than a cost per share in the hundreds of thousands. - Verify the three
DiscrepancyKindvalues are produced by the three distinct situations, and that a 5e-06 BTC break is caught undersymbol_tolerances={"BTC": 1e-8}while the 1e-5 default silently passes it. - Run
python -m unittest discover -s skills/multi-broker-consolidated-position-view/scriptsand confirm 100% pass rate.