Pure mark-to-market valuation of open positions against caller- supplied snapshots.
The engine never fetches prices; this module turns a set of
PaperEx.MarketSnapshots the caller already has (e.g. from a
snapshot cache) into a net liquidation value for open positions.
Nothing here mutates state or performs IO.
Pricing policy
:conservative(default) — value each position at the price you could actually close it at right now: longs at the best bid (what a sale would fetch), shorts at the best ask (what a buyback would cost). Missing book side → the position is unpriced.:midpoint— value both sides at the snapshot midpoint. Requires both book sides; either side empty → unpriced.
Net liquidation value
Longs add shares * price; shorts subtract shares * price
(the cost to cover). The total is what closing every open
position at current prices would do to cash.
All-or-nothing totals
open_position_value/3 returns {:error, {:unpriced, ids}} when
any open position lacks a usable snapshot/price — a partial total
silently missing positions reads as "covered everything" when it
didn't. Callers wanting partial data use the :by_position
breakdown on the success shape, or filter positions first.
Summary
Functions
Net liquidation value of the open positions in portfolio_or_positions
against snapshots (a map of market_id => MarketSnapshot).
Types
Functions
@spec open_position_value( PaperEx.Portfolio.t() | [PaperEx.Position.t()], snapshots(), keyword() ) :: {:ok, %{value: number(), by_position: [position_value()]}} | {:error, {:unpriced, [term()]}}
Net liquidation value of the open positions in portfolio_or_positions
against snapshots (a map of market_id => MarketSnapshot).
Returns {:ok, %{value: total, by_position: [...]}} when every
open position priced, or {:error, {:unpriced, market_ids}}
listing the markets that could not be priced (missing snapshot or
empty required book side).
Options:
:pricing—:conservative(default) or:midpoint.