Portfolio margin: the N-point stress grid
How Phasis computes margin requirements — worst-case PnL across N Black–Scholes price scenarios, with no per-leg isolation and zero lock for long-only and debit-spread portfolios.
Phasis uses portfolio margin: instead of treating each position in isolation, the protocol evaluates the entire portfolio's profit-and-loss across a grid of stressed price scenarios and charges margin equal to the worst case.
This allows delta offsets and spread structures to naturally reduce — or eliminate — margin requirements, while ensuring naked short exposure is fully collateralized.
The stress grid
For each underlying asset, the protocol publishes a StressSnapshot containing an N-point stress grid. Each grid point represents one independent Black–Scholes evaluation of all series at a hypothetical future spot price.
Grid parameters (configurable per asset):
- N — number of scenarios (default 11; range 2–31).
- stress_pct — half-width of the price range (default 20%).
- Spot range — evenly spaced from
spot × (1 − stress_pct)tospot × (1 + stress_pct).
Each grid point records, for every active series, the change in theoretical option value relative to the current baseline. These deltas are stored as signed UD30×9 fixed-point numbers.
Computing the margin requirement
The computation lives in compute_stress_margin in margin.move and runs in three phases.
Phase 1 — portfolio PnL per scenario
For each stress scenario j (0 to N−1):
PnL[j] = Σᵢ user_qty[i] × delta[i][j]where i ranges over all positions the account holds in the given underlying. Long positions contribute positive PnL when the underlying rises; short positions contribute negative PnL.
Phase 2 — worst-case selection
stress_result = min(PnL[j] for j in 0..N-1)This is the single most damaging scenario across the grid.
Phase 3 — margin components
Three components are summed to produce the final requirement:
| Component | Description |
|---|---|
margin_1 | stress_result plus intrinsic-value bleed (clamped to zero if positive). |
margin_2 | Time-decay charge on short positions. |
margin_3 | Accelerated time-decay on positions maturing within 24 hours (captures the gamma ramp near expiry). |
requirement = max(0, −(margin_1 + margin_2 + margin_3))
net_value = Σ premiums received − premiums paid (signed)Requirement vs lock
The requirement is the raw worst-case loss. The lock is what actually gets deducted from the account's free balance:
lock = max(0, requirement − max(0, net_value))Collected premiums offset the requirement dollar-for-dollar. The result:
- Long-only portfolio:
net_valueis negative (you paid premium out), somax(0, net_value) = 0, butrequirementis also zero or near-zero for long positions.lock = 0. - Debit spread (long closer strike, short further strike): the short premium collected offsets the requirement from the long.
lock = 0in most debit-spread configurations. - Naked short:
requirementis large and positive;net_valuemay be positive (premium received) but rarely covers the full worst-case loss.lock > 0.
Worked examples
Long 1× BTC $70k call at $3,000 premium (spot $65k):
requirement ≈ $2,000 (downside stress scenario: call expires worthless)
net_value = −$3,000 (you paid $3k)
lock = max(0, $2,000 − max(0, −$3,000))
= max(0, $2,000 − 0) = $2,000
But: long position requirement is actually ≤ 0 in all stress scenarios
→ lock = $0Short 2× BTC $70k call (naked, $3,000 premium per contract collected):
requirement ≈ $8,000 (worst-case: BTC spikes, both shorts lose heavily)
net_value = +$6,000 (you collected $6k premium)
lock = max(0, $8,000 − max(0, $6,000))
= max(0, $8,000 − $6,000) = $2,000You must lock $2,000 in addition to the $6,000 premium already held.
Why N > 2 matters
A two-endpoint model (checking only spot − 20% and spot + 20%) uses linear interpolation and misses interior-dip scenarios. Consider a short butterfly:
- Short 1× $67k call, long 2× $70k call, short 1× $73k call.
- True worst PnL occurs near $70k (the body of the butterfly), not at the wings.
- With N = 2 (endpoints only): interior dip missed, margin under-reported by approximately $70 per contract.
- With N = 11 (interior points evaluated independently via Black–Scholes): the $70k dip is captured and margin is correct.
Phasis uses N = 11 by default. The stress-publisher service recomputes the full grid every ~30 seconds and publishes it on-chain as a StressSnapshot shared object.
Markets & series
How Phasis organizes tradeable instruments — one market per (underlying, expiry), one series per (strike, call/put), each backed by its own embedded DeepBook orderbook.
Settlement & the Pyth oracle
How Phasis halts trading at expiry, reads the Pyth EMA price on-chain, and distributes intrinsic-value payouts to all accounts.