Diffusal
Contracts

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 TypeDescriptionFormula
Initial Margin (IM)Required to maintain positionsStress Loss + Adverse PnL + Notional Buffer
Maintenance Margin (MM)Liquidation triggerIM × 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:

ComponentPurposeCalculation
Stress LossProtects against market movesMax portfolio loss under 4 stress scenarios
Adverse PnL BufferProtects against unrealized lossesmax(0, -PnL) × 1.05
Notional BufferBaseline requirementTotal 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.

ScenarioSpot ChangeIV ChangeTests
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 data
  • quoter: 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:

PnL=(currentMarkentryPremium)×size\text{PnL} = (\text{currentMark} - \text{entryPremium}) \times \text{size}
  • 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:

  1. For each of 4 stress scenarios
  2. For each position, calculate stressed mark price
  3. Compare stressed mark to current mark
  4. Sum position losses
  5. Return maximum loss across all scenarios

calculateInitialMargin

Calculate Initial Margin requirement:

function calculateInitialMargin(
    PositionData[] memory positions,
    address quoter
) internal view returns (uint256 im)

Formula:

IM=StressLoss+AdversePnLBuffer+NotionalBuffer\text{IM} = \text{StressLoss} + \text{AdversePnLBuffer} + \text{NotionalBuffer}

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:

MM=IM×0.8\text{MM} = \text{IM} \times 0.8

Calculation Example

Portfolio

PositionTypeSizeEntryStrikeExpiry
ETH-USD CallLong+2$150$320030d
ETH-USD PutShort-1$100$280030d

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 = +$5

Step 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: (145145 - 5) × 2 = $280
  • Put loss: (8585 - 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: (705705 - 145) × 2 = $1120
  • Put gain: (11 - 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% = $1812

Internal 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:

  1. Get current oracle data (spot, IV, rate)
  2. Apply stress multipliers
  3. Reprice option with stressed parameters

_calculateTotalNotional

Calculate total notional value:

function _calculateTotalNotional(
    PositionData[] memory positions,
    IDiffusalOptionsQuoter q
) private view returns (uint256 totalNotional)

Formula:

Notional=sizei×spoti\text{Notional} = \sum |size_i| \times spot_i

_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

OperationApprox. GasNotes
Per position PnL~50kOne option pricing
Per stress scenario~200k4 option pricings
Full margin calc~1M4 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

ScenarioHandling
Expired optionsExcluded from stress testing
Zero positionsReturns 0 for all components
Negative PnLAdds 5% buffer to adverse PnL
Empty portfolioReturns zero margin

Integration Points

Depends On

ContractPurpose
DiffusalOptionsQuoterMark prices and stressed pricing
BlackScholesWadOption pricing under stress
ConstantsStress parameters

Used By

ContractPurpose
DiffusalCollateralVaultEnforces margin requirements
DiffusalLiquidationEngineCalculates debt

Code Reference

Source: packages/contracts/src/utils/MarginEngine.sol

Dependencies:

  • OpenZeppelin SafeCast — Safe type conversions
  • BlackScholesWad — Stress scenario pricing
  • Constants — Margin parameters

On this page