Skip to content

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.