Overview
Every scenario in Virtufin binds a triplet of worlds: market, portfolio
and strategy (see the
scenarios spec).
Until now the portfolio leg was known only through what the executors
derive from their own fills (sc.<scenarioId>.position.*, folded into
the strategy's portfolio state by strategy-devkit). The venue's own
view of the account -- deposits, withdrawals, fees, dust conversions,
manual trades on the exchange UI, fills missed during an outage -- was
never observed. For an act portfolio leg that is not good enough.
This repo holds the workers that ingest that venue-side view.
Boundary with the executors
| Concern | Owner |
|---|---|
Place, amend, cancel orders; order lifecycle events (order.accepted, order.filled, ...) |
executors (execution-workers, execution-devkit) |
Position lifecycle events derived from fills (position.opened, .increased, .flipped, ...) |
executors / the strategy's portfolio fold |
| Balances, holdings, ledger entries and open orders as the venue reports them | this repo |
| Reconciling venue truth against executor-derived positions | a downstream consumer (future reconciliation worker or the strategy's fold) |
A portfolio worker never acts on the venue. It subscribes to the
account's push stream (Binance user data stream, Alpaca trading stream)
through the WebSocketManager, pulls REST snapshots on start and on a
periodic Trigger, and publishes world-scoped portfolio events plus rows
in the portfolio tables of the shared database.
Why a separate repo
The same reason market-data-workers is separate from the
WebSocketManager: the bridge forwards raw frames verbatim and knows
nothing about accounts; turning those frames into a correct account
state (snapshot-then-stream, dedupe, reconnect diffs) needs its own
state machine, tests and deploy story. Keeping it apart from the
executors keeps the observe/act boundary visible in the repo layout.
Design pattern: one instance per account
A worker instance tracks one venue account (config.account) in one
portfolio world (config.world: act, or hyp.<NAME> for a paper or
testnet account). Events carry exchange, portfolioworld,
clocktype, eventtime and ingesttime; like market data they carry
no scenarioid, because an account is shared by every scenario that
uses this portfolio leg.