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 + Notional Buffer |
| Maintenance Margin (MM) | Liquidation trigger | IM × 80% |
Stress Testing Approach
SPAN-Like 4-Corner Stress Test
Scenario 1: Scenario 2:
Spot: -30% Spot: -30%
IV: +50% IV: -30%
Scenario 3: Scenario 4:
Spot: +30% Spot: +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 | Protects against unrealized losses | max(0, -PnL) × 1.05 |
| Notional Buffer | Baseline requirement | Total Notional × 15% |
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 longs) |
| 2 | -30% | -30% | Crash with vol crush |
| 3 | +30% | +50% | Rally with vol spike |
| 4 | +30% | -30% | Rally with vol crush (worst for shorts) |
Types
PositionData
Position information needed for margin calculation:
struct PositionData {
bytes32 seriesId; // Option series identifier
int256 size; // Signed: positive = long, negative = short
uint256 entryPremium; // Entry price (WAD)
bytes32 pairId; // Trading pair (e.g., ETH-USD)
uint256 strike; // Strike price (WAD)
uint256 expiry; // Expiration timestamp
bool isCall; // true = call, false = put
}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 profit when current mark > entry
- Shorts profit when current mark < entry
- 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 = max(0, -unrealizedPnL) × 1.05
- NotionalBuffer = totalNotional × 15%
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-USD Call | Long | +2 | $150 | $3200 | 30d |
| ETH-USD 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 (PnL is +$5, so no buffer)
adversePnLBuffer = max(0, -$5) × 1.05 = $0
// Notional Buffer
notional = (2 × $3000) + (1 × $3000) = $9000
notionalBuffer = $9000 × 15% = $1350
// Initial Margin
IM = $915 + $0 + $1350 = $2265
// Maintenance Margin
MM = $2265 × 80% = $1812Internal 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.
Gas Considerations
| Operation | Approx. Gas | Notes |
|---|---|---|
| Per position PnL | ~50k | One option pricing |
| Per stress scenario | ~200k | 4 option pricings |
| Full margin calc | ~1M | 4 scenarios × positions |
Optimization: The quoter caches oracle data, reducing gas for batch operations.
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