MarginEngine
SPAN-like portfolio margin calculation library
The MarginEngine library provides portfolio margin calculations using SPAN-like stress testing. It evaluates user positions against multiple market scenarios to determine appropriate margin requirements and unrealized PnL.
Overview
The margin system protects the protocol from user defaults by ensuring users maintain sufficient collateral:
| Margin Type | Description | Formula |
|---|---|---|
| Initial Margin (IM) | Required to maintain positions | Stress Loss + Adverse PnL Buffer + Notional Buffer |
| Maintenance Margin (MM) | Liquidation trigger | IM × 80% |
Stress Testing Approach
┌─────────────────────────────────────────────────────────────────────┐
│ SPAN-Like 4-Corner Stress Test │
├────────────────┬────────────────┬────────────────┬──────────────────┤
│ Scenario 1 │ Scenario 2 │ Scenario 3 │ Scenario 4 │
│ Spot: -30% │ Spot: -30% │ Spot: +30% │ Spot: +30% │
│ IV: +50% │ IV: -30% │ IV: +50% │ IV: -30% │
└───────┬────────┴───────┬────────┴───────┬────────┴────────┬─────────┘
│ │ │ │
└────────────────┴────────┬───────┴─────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ Stress Loss = max(loss across all 4 scenarios) │
└─────────────────────────────────────────────────────┘Key Concepts
Margin Components
Initial Margin combines three protective layers:
| Component | Purpose | Calculation |
|---|---|---|
| Stress Loss | Protects against market moves | Max portfolio loss under 4 stress scenarios |
| Adverse PnL Buffer | Additional safety buffer on stress loss | Stress Loss × 0.05 |
| Notional Buffer | Baseline requirement | Total Notional × 15% |
Note: Premium balance is NOT included in margin—it's already reflected in equity (reducing equity when negative).
Stress Scenarios
The library tests 4 corner scenarios representing extreme market conditions. See Margin System: Stress Testing for the complete rationale behind these parameters.
| Scenario | Spot Change | IV Change | Tests |
|---|---|---|---|
| 1 | -30% | +50% | Crash with vol spike (worst for short puts) |
| 2 | -30% | -30% | Crash with vol crush (worst for long calls) |
| 3 | +30% | +50% | Rally with vol spike (worst for short calls) |
| 4 | +30% | -30% | Rally with vol crush (worst for long puts) |
Note: Long-only portfolios cannot be liquidated if deposit ≥ premium paid, because their maximum loss is bounded by the premium already paid. If deposit < premium paid, liquidation can occur. See Margin System: Unliquidatable Long Position for details.
Types
PositionData
Position information needed for margin calculation:
struct PositionData {
bytes32 seriesId; // Option series identifier
int256 optionBalance; // Signed: positive = long, negative = short
int256 premiumBalance; // Signed: positive = receivable, negative = payable (WAD)
bytes32 pairId; // Trading pair (e.g., ETH-USDC)
uint256 strike; // Strike price (WAD)
uint256 expiry; // Expiration timestamp
bool isCall; // true = call, false = put
uint256 lastUpdated; // Timestamp of last position update
}MarginResult
Complete margin calculation output:
struct MarginResult {
int256 unrealizedPnL; // Current PnL (WAD)
uint256 stressLoss; // Max loss across scenarios (WAD)
uint256 initialMargin; // IM requirement (WAD)
uint256 maintenanceMargin; // MM = IM × 80% (WAD)
}StressScenario
Parameters for stress testing:
struct StressScenario {
uint256 spotMultiplier; // WAD (e.g., 0.70e18 = -30%)
uint256 ivMultiplier; // WAD (e.g., 1.50e18 = +50%)
}Main Functions
calculateMargin
Calculate complete margin information for a portfolio:
function calculateMargin(
PositionData[] memory positions,
address quoter
) internal view returns (MarginResult memory result)Parameters:
positions: Array of position dataquoter: DiffusalOptionsQuoter address for pricing
Returns: Complete MarginResult with PnL and all margin components
calculateUnrealizedPnL
Calculate portfolio unrealized PnL:
function calculateUnrealizedPnL(
PositionData[] memory positions,
address quoter
) internal view returns (int256 pnl)Formula per position:
- Longs (optionBalance > 0) profit when current mark increases
- Shorts (optionBalance < 0) profit when current mark decreases
- Premium balance is separate and deterministic (not mark-to-market)
- Returns WAD-scaled signed value
calculateStressLoss
Calculate maximum portfolio loss across stress scenarios:
function calculateStressLoss(
PositionData[] memory positions,
address quoter
) internal view returns (uint256 maxLoss)Algorithm:
- For each of 4 stress scenarios
- For each position, calculate stressed mark price
- Compare stressed mark to current mark
- Sum position losses
- Return maximum loss across all scenarios
calculateInitialMargin
Calculate Initial Margin requirement:
function calculateInitialMargin(
PositionData[] memory positions,
address quoter
) internal view returns (uint256 im)Formula:
Where:
- StressLoss = max loss across 4 scenarios
- AdversePnLBuffer = StressLoss × 0.05
- NotionalBuffer = totalNotional × 15%
Note: Premium is in equity, not margin. See Margin System: Why Premium is NOT in Margin.
calculateMaintenanceMargin
Calculate Maintenance Margin from Initial Margin:
function calculateMaintenanceMargin(uint256 initialMargin)
internal pure returns (uint256 mm)Formula:
Calculation Example
Portfolio
| Position | Type | Size | Entry | Strike | Expiry |
|---|---|---|---|---|---|
| ETH-USDC Call | Long | +2 | $150 | $3200 | 30d |
| ETH-USDC Put | Short | -1 | $100 | $2800 | 30d |
Current Market
- ETH Spot: $3000
- IV: 60%
- Risk-free: 5%
Step 1: Unrealized PnL
// Call: current mark = $145, entry = $150
// callPnL = ($145 - $150) × 2 = -$10
// Put: current mark = $85, entry = $100
// putPnL = ($85 - $100) × (-1) = +$15
// Total PnL = -$10 + $15 = +$5Step 2: Stress Testing
Scenario 1 (Spot -30%, IV +50%):
- Stressed spot: $2100
- Stressed IV: 90%
- Call value: ~$5 (deep OTM)
- Put value: ~$720 (deep ITM)
- Call loss: (5) × 2 = $280
- Put loss: (720) × (-1) = $635
- Total: $915 loss
Scenario 4 (Spot +30%, IV -30%):
- Stressed spot: $3900
- Stressed IV: 42%
- Call value: ~$705 (deep ITM)
- Put value: ~$1 (deep OTM)
- Call gain: (145) × 2 = $1120
- Put gain: (85) × (-1) = $84
- Total: $1204 profit
Max Loss = $915 (from Scenario 1)
Step 3: Margin Calculation
// Stress Loss
stressLoss = $915
// Adverse PnL Buffer (5% of stress loss)
adversePnLBuffer = $915 × 0.05 = $45.75
// Notional Buffer
notional = (2 × $3000) + (1 × $3000) = $9000
notionalBuffer = $9000 × 15% = $1350
// Initial Margin
IM = $915 + $45.75 + $1350 = $2310.75
// Maintenance Margin
MM = $2310.75 × 80% = $1848.60Internal Functions
_getMarkPrice
Get current option mark price:
function _getMarkPrice(
IDiffusalOptionsQuoter q,
PositionData memory pos
) private view returns (uint256)- Returns 0 for expired options
- Uses quoter's
getOptionQuote()
_calculateScenarioLoss
Calculate portfolio loss for a single stress scenario:
function _calculateScenarioLoss(
PositionData[] memory positions,
IDiffusalOptionsQuoter q,
StressScenario memory scenario
) private view returns (int256 totalLoss)_getStressedMarkPrice
Calculate option price under stressed conditions:
function _getStressedMarkPrice(
IDiffusalOptionsQuoter q,
PositionData memory pos,
StressScenario memory scenario
) private view returns (uint256)Process:
- Get current oracle data (spot, IV, rate)
- Apply stress multipliers
- Reprice option with stressed parameters
_calculateTotalNotional
Calculate total notional value:
function _calculateTotalNotional(
PositionData[] memory positions,
IDiffusalOptionsQuoter q
) private view returns (uint256 totalNotional)Formula:
_int64ToWad
Convert oracle int64 with exponent to WAD:
function _int64ToWad(int64 value, int32 expo)
private pure returns (uint256 wadValue)Constants Reference
All margin constants are defined in Constants.sol. See Margin Calculations: Constants for the complete reference.
Security Considerations
Stress Scenario Coverage
The 4-corner approach tests extreme but plausible scenarios:
- ±30% spot: Covers historical daily moves (99th percentile)
- +50%/-30% IV: Covers volatility regime changes
Oracle Dependency
Margin calculations rely on:
- Current spot prices from oracle
- Current IV from oracle
- Accurate option pricing via quoter
Edge Cases
| Scenario | Handling |
|---|---|
| Expired options | Excluded from stress testing |
| Zero positions | Returns 0 for all components |
| Negative PnL | Adds 5% buffer to adverse PnL |
| Empty portfolio | Returns zero margin |
Integration Points
Depends On
| Contract | Purpose |
|---|---|
| DiffusalOptionsQuoter | Mark prices and stressed pricing |
| BlackScholesWad | Option pricing under stress |
| Constants | Stress parameters |
Used By
| Contract | Purpose |
|---|---|
| DiffusalCollateralVault | Enforces margin requirements |
| DiffusalLiquidationEngine | Calculates debt |
Code Reference
Source: packages/contracts/src/utils/MarginEngine.sol
Dependencies:
- OpenZeppelin
SafeCast— Safe type conversions BlackScholesWad— Stress scenario pricingConstants— Margin parameters
Related
- Margin System (Protocol) — High-level margin mechanics
- Margin Calculations (Math) — Mathematical details
- DiffusalCollateralVault — Vault using this library
- Liquidation — What happens when margin fails