Cross-Margin and Portfolio Margin: Advanced Collateral Systems

Implement sophisticated margin systems for derivatives. Learn cross-margin, portfolio margin, and risk-based collateral requirements.

Kevan Shah · · 17 min read

Traditional isolated margin systems require separate collateral for each position—inefficient and capital-intensive. Advanced margin systems like cross-margin and portfolio margin optimize capital usage by considering correlations and offsetting risks across positions. These systems attract sophisticated traders and increase trading volume.

This guide explores the architecture and implementation of advanced margin systems for perpetual DEXs.

Isolated vs Cross Margin

Isolated Margin

contract IsolatedMargin {
    struct IsolatedPosition {
        address trader;
        address market;
        uint256 size;
        uint256 collateral;  // Dedicated to this position only
        uint256 leverage;
        bool isOpen;
    }
    
    mapping(bytes32 => IsolatedPosition) public positions;
    
    function openIsolatedPosition(
        address market,
        uint256 size,
        uint256 collateral,
        uint256 leverage
    ) external returns (bytes32 positionId) {
        // Each position has its own collateral
        positionId = keccak256(abi.encodePacked(msg.sender, market, block.timestamp));
        
        positions[positionId] = IsolatedPosition({
            trader: msg.sender,
            market: market,
            size: size,
            collateral: collateral,
            leverage: leverage,
            isOpen: true
        });
        
        // Transfer dedicated collateral
        collateralToken.transferFrom(msg.sender, address(this), collateral);
    }
}

Cross Margin

contract CrossMargin {
    struct CrossAccount {
        address trader;
        uint256 totalCollateral;      // Shared across all positions
        int256 totalUnrealizedPnL;
        uint256 totalMaintenanceMargin;
        uint256 totalNotional;
        bool isLiquidatable;
        mapping(address => Position) positions;
    }
    
    struct Position {
        address market;
        uint256 size;
        uint256 entryPrice;
        bool isLong;
        uint256 notional;
        uint256 maintenanceMargin;
        int256 unrealizedPnL;
    }
    
    mapping(address => CrossAccount) public crossAccounts;
    
    function depositCollateral(uint256 amount) external {
        CrossAccount storage account = crossAccounts[msg.sender];
        
        collateralToken.transferFrom(msg.sender, address(this), amount);
        account.totalCollateral += amount;
        
        emit CollateralDeposited(msg.sender, amount);
    }
    
    function openPosition(
        address market,
        uint256 size,
        bool isLong
    ) external {
        CrossAccount storage account = crossAccounts[msg.sender];
        
        // Calculate margin requirement for this position
        uint256 price = oracle.getPrice(market);
        uint256 notional = (size * price) / 1e18;
        uint256 mmRequired = (notional * MAINTENANCE_MARGIN) / 10000;
        
        // Check if account has sufficient available margin
        uint256 availableMargin = calculateAvailableMargin(msg.sender);
        require(availableMargin >= mmRequired, "Insufficient margin");
        
        // Add position to account
        account.positions[market] = Position({
            market: market,
            size: size,
            entryPrice: price,
            isLong: isLong,
            notional: notional,
            maintenanceMargin: mmRequired,
            unrealizedPnL: 0
        });
        
        account.totalMaintenanceMargin += mmRequired;
        account.totalNotional += notional;
        
        emit PositionOpened(msg.sender, market, size, isLong);
    }
    
    function calculateAvailableMargin(address trader) 
        public view returns (uint256) {
        CrossAccount storage account = crossAccounts[trader];
        
        int256 equity = int256(account.totalCollateral) + account.totalUnrealizedPnL;
        
        if (equity <= 0) return 0;
        
        uint256 mmRequired = account.totalMaintenanceMargin;
        
        return uint256(equity) > mmRequired ? uint256(equity) - mmRequired : 0;
    }
    
    function updatePnL(address trader, address market) external {
        CrossAccount storage account = crossAccounts[trader];
        Position storage position = account.positions[market];
        
        uint256 currentPrice = oracle.getPrice(market);
        
        // Calculate new PnL
        int256 newPnL;
        if (position.isLong) {
            newPnL = int256(((currentPrice - position.entryPrice) * position.size) / 1e18);
        } else {
            newPnL = int256(((position.entryPrice - currentPrice) * position.size) / 1e18);
        }
        
        // Update account totals
        account.totalUnrealizedPnL = account.totalUnrealizedPnL - position.unrealizedPnL + newPnL;
        position.unrealizedPnL = newPnL;
        
        emit PnLUpdated(trader, market, newPnL);
    }
    
    function withdrawCollateral(uint256 amount) external {
        CrossAccount storage account = crossAccounts[msg.sender];
        
        uint256 available = calculateAvailableMargin(msg.sender);
        require(available >= amount, "Insufficient available margin");
        
        account.totalCollateral -= amount;
        collateralToken.transfer(msg.sender, amount);
        
        emit CollateralWithdrawn(msg.sender, amount);
    }
}

Portfolio Margin with SPAN Methodology

contract PortfolioMargin {
    struct RiskScenario {
        uint256 priceChangeBps;    // Price shock in basis points
        uint256 volatilityChangeBps;
        uint256 weight;
    }
    
    RiskScenario[] public riskScenarios;
    
    struct PortfolioPosition {
        address market;
        uint256 size;
        uint256 entryPrice;
        bool isLong;
        uint256 delta;
        uint256 gamma;
        uint256 vega;
        uint256 theta;
    }
    
    struct MarginRequirement {
        uint256 totalRequirement;
        uint256 scenarioRequirements;
        uint256 minimumRequirement;
    }
    
    function calculatePortfolioMargin(address trader) 
        external view returns (MarginRequirement memory) {
        
        PortfolioPosition[] memory positions = getTraderPositions(trader);
        
        uint256[] memory scenarioPnLs = new uint256[](riskScenarios.length);
        
        // Calculate PnL for each risk scenario
        for (uint s = 0; s < riskScenarios.length; s++) {
            int256 scenarioPnL = 0;
            
            for (uint p = 0; p < positions.length; p++) {
                PortfolioPosition memory pos = positions[p];
                
                // Apply price shock
                uint256 shockedPrice = applyPriceShock(
                    pos.entryPrice,
                    riskScenarios[s].priceChangeBps
                );
                
                // Calculate PnL using Greeks
                int256 priceDelta = int256(shockedPrice) - int256(pos.entryPrice);
                int256 gammaImpact = int256((pos.gamma * priceDelta * priceDelta) / 1e18);
                
                int256 positionPnL = (int256(pos.delta) * priceDelta) / 1e18;
                positionPnL += gammaImpact / 2;
                
                // Apply direction
                if (!pos.isLong) {
                    positionPnL = -positionPnL;
                }
                
                scenarioPnL += positionPnL;
            }
            
            scenarioPnLs[s] = scenarioPnL < 0 ? uint256(-scenarioPnL) : 0;
        }
        
        // Find maximum loss scenario
        uint256 maxScenarioLoss = 0;
        for (uint i = 0; i < scenarioPnLs.length; i++) {
            if (scenarioPnLs[i] > maxScenarioLoss) {
                maxScenarioLoss = scenarioPnLs[i];
            }
        }
        
        // Calculate minimum requirement based on notional
        uint256 totalNotional = calculateTotalNotional(positions);
        uint256 minimumRequirement = (totalNotional * 300) / 10000; // 3% minimum
        
        uint256 totalRequirement = maxScenarioLoss > minimumRequirement 
            ? maxScenarioLoss 
            : minimumRequirement;
        
        return MarginRequirement({
            totalRequirement: totalRequirement,
            scenarioRequirements: maxScenarioLoss,
            minimumRequirement: minimumRequirement
        });
    }
    
    function applyPriceShock(
        uint256 price,
        uint256 shockBps
    ) internal pure returns (uint256) {
        return price + ((price * shockBps) / 10000);
    }
    
    function calculateGreeks(
        address market,
        uint256 size,
        uint256 entryPrice
    ) external view returns (uint256 delta, uint256 gamma, uint256 vega) {
        // Simplified Greeks calculation
        // In production, use Black-Scholes or binomial model
        
        uint256 currentPrice = oracle.getPrice(market);
        uint256 volatility = oracle.getVolatility(market);
        
        // Delta ≈ size * (currentPrice / entryPrice)
        delta = (size * currentPrice) / entryPrice;
        
        // Gamma (simplified)
        gamma = (delta * volatility) / 10000;
        
        // Vega (simplified)
        vega = (size * currentPrice * volatility) / 10000;
        
        return (delta, gamma, vega);
    }
}

Correlation-Based Margin Offsets

contract CorrelationMargin {
    struct CorrelationMatrix {
        mapping(address => mapping(address => int256)) correlations; // -1e18 to 1e18
    }
    
    CorrelationMatrix public correlations;
    
    struct PositionPair {
        address position1;
        address position2;
        uint256 correlation;
        uint256 offsetBenefit;
    }
    
    function calculateOffsetBenefit(address trader) 
        external view returns (uint256 totalOffset) {
        
        Position[] memory positions = getTraderPositions(trader);
        
        for (uint i = 0; i < positions.length; i++) {
            for (uint j = i + 1; j < positions.length; j++) {
                int256 correlation = getCorrelation(
                    positions[i].market,
                    positions[j].market
                );
                
                // Check if positions offset each other
                bool isHedge = (positions[i].isLong != positions[j].isLong);
                
                if (isHedge && correlation > 0.7e18) { // Strong positive correlation
                    uint256 offset = calculatePairOffset(positions[i], positions[j]);
                    totalOffset += offset;
                }
            }
        }
    }
    
    function calculatePairOffset(
        Position memory pos1,
        Position memory pos2
    ) internal pure returns (uint256) {
        // Calculate overlapping notional
        uint256 minNotional = pos1.notional < pos2.notional 
            ? pos1.notional 
            : pos2.notional;
        
        // 80% offset benefit for perfect hedge
        return (minNotional * 8000) / 10000;
    }
    
    function updateCorrelation(
        address asset1,
        address asset2,
        int256 correlation
    ) external onlyRiskManager {
        require(correlation >= -1e18 && correlation <= 1e18, "Invalid correlation");
        
        correlations.correlations[asset1][asset2] = correlation;
        correlations.correlations[asset2][asset1] = correlation;
    }
    
    function getCorrelation(address asset1, address asset2) 
        public view returns (int256) {
        if (asset1 == asset2) return 1e18;
        return correlations.correlations[asset1][asset2];
    }
}

Tiered Margin Requirements

contract TieredMargin {
    struct MarginTier {
        uint256 maxPositionSize;
        uint256 initialMarginRate;
        uint256 maintenanceMarginRate;
        uint256 liquidationFee;
    }
    
    MarginTier[] public marginTiers;
    
    constructor() {
        // Tier 1: Small positions, lower margin
        marginTiers.push(MarginTier({
            maxPositionSize: 100000e18,      // $100k
            initialMarginRate: 1000,         // 10%
            maintenanceMarginRate: 500,      // 5%
            liquidationFee: 250             // 2.5%
        }));
        
        // Tier 2: Medium positions
        marginTiers.push(MarginTier({
            maxPositionSize: 500000e18,      // $500k
            initialMarginRate: 1250,         // 12.5%
            maintenanceMarginRate: 625,      // 6.25%
            liquidationFee: 300             // 3%
        }));
        
        // Tier 3: Large positions, higher margin
        marginTiers.push(MarginTier({
            maxPositionSize: 2000000e18,     // $2M
            initialMarginRate: 1500,         // 15%
            maintenanceMarginRate: 750,      // 7.5%
            liquidationFee: 350             // 3.5%
        }));
        
        // Tier 4: Very large positions
        marginTiers.push(MarginTier({
            maxPositionSize: type(uint256).max,
            initialMarginRate: 2000,         // 20%
            maintenanceMarginRate: 1000,     // 10%
            liquidationFee: 400             // 4%
        }));
    }
    
    function getMarginTier(uint256 positionSize) 
        external view returns (MarginTier memory) {
        
        for (uint i = 0; i < marginTiers.length; i++) {
            if (positionSize <= marginTiers[i].maxPositionSize) {
                return marginTiers[i];
            }
        }
        
        return marginTiers[marginTiers.length - 1];
    }
    
    function calculateMarginRequirement(
        uint256 positionSize,
        uint256 price,
        bool isInitial
    ) external view returns (uint256) {
        MarginTier memory tier = getMarginTier(positionSize);
        uint256 notional = (positionSize * price) / 1e18;
        
        uint256 marginRate = isInitial 
            ? tier.initialMarginRate 
            : tier.maintenanceMarginRate;
        
        return (notional * marginRate) / 10000;
    }
}

Collateral Optimization

contract CollateralOptimizer {
    struct CollateralAsset {
        address token;
        uint256 weight;           // Discount factor (e.g., 0.95 for USDC)
        uint256 maxAllocation;    // Max % of total collateral
        uint256 currentAllocation;
        bool isStable;
    }
    
    mapping(address => CollateralAsset) public collateralAssets;
    address[] public supportedCollateral;
    
    struct CollateralAllocation {
        address token;
        uint256 amount;
        uint256 weightedValue;
    }
    
    function addCollateral(
        address token,
        uint256 weight,
        uint256 maxAllocation,
        bool isStable
    ) external onlyGovernance {
        collateralAssets[token] = CollateralAsset({
            token: token,
            weight: weight,
            maxAllocation: maxAllocation,
            currentAllocation: 0,
            isStable: isStable
        });
        supportedCollateral.push(token);
    }
    
    function depositCollateral(address token, uint256 amount) external {
        CollateralAsset storage asset = collateralAssets[token];
        require(asset.token != address(0), "Unsupported collateral");
        
        // Check allocation limit
        uint256 newAllocation = ((asset.currentAllocation + amount) * 10000) / 
                               getTotalCollateral();
        require(newAllocation <= asset.maxAllocation, "Exceeds max allocation");
        
        // Transfer tokens
        IERC20(token).transferFrom(msg.sender, address(this), amount);
        
        // Update allocations
        asset.currentAllocation += amount;
        
        // Record weighted value
        uint256 weightedValue = (amount * asset.weight) / 10000;
        updateTraderCollateral(msg.sender, token, amount, weightedValue);
    }
    
    function calculateTotalCollateralValue(address trader) 
        external view returns (uint256 totalValue) {
        
        for (uint i = 0; i < supportedCollateral.length; i++) {
            address token = supportedCollateral[i];
            CollateralAsset memory asset = collateralAssets[token];
            uint256 balance = getTraderCollateralBalance(trader, token);
            
            // Apply weight
            uint256 weightedValue = (balance * asset.weight) / 10000;
            totalValue += weightedValue;
        }
    }
    
    function getLiquidationPrices(address trader) 
        external view returns (address[] memory tokens, uint256[] memory prices) {
        
        tokens = supportedCollateral;
        prices = new uint256[](tokens.length);
        
        for (uint i = 0; i < tokens.length; i++) {
            if (!collateralAssets[tokens[i]].isStable) {
                prices[i] = oracle.getPrice(tokens[i]);
            } else {
                prices[i] = 1e18; // Stablecoins = $1
            }
        }
    }
}

Stress Testing and Scenario Analysis

contract StressTestEngine {
    struct StressScenario {
        string name;
        mapping(address => int256) priceShocks; // Basis points
        mapping(address => int256) volatilityShocks;
        uint256 globalVolatilityMultiplier;
    }
    
    mapping(bytes32 => StressScenario) public scenarios;
    bytes32[] public scenarioList;
    
    function createScenario(
        string memory name,
        address[] memory assets,
        int256[] memory priceShocks,
        int256[] memory volShocks,
        uint256 globalVolMult
    ) external onlyRiskManager returns (bytes32 scenarioId) {
        scenarioId = keccak256(abi.encodePacked(name));
        
        StressScenario storage scenario = scenarios[scenarioId];
        scenario.name = name;
        scenario.globalVolatilityMultiplier = globalVolMult;
        
        for (uint i = 0; i < assets.length; i++) {
            scenario.priceShocks[assets[i]] = priceShocks[i];
            scenario.volatilityShocks[assets[i]] = volShocks[i];
        }
        
        scenarioList.push(scenarioId);
    }
    
    function runStressTest(address trader, bytes32 scenarioId) 
        external view returns (int256 portfolioPnL, bool isLiquidated) {
        
        StressScenario storage scenario = scenarios[scenarioId];
        Position[] memory positions = getTraderPositions(trader);
        
        portfolioPnL = 0;
        
        for (uint i = 0; i < positions.length; i++) {
            Position memory pos = positions[i];
            
            // Apply stress shocks
            int256 priceShock = scenario.priceShocks[pos.market];
            uint256 stressedPrice = applyShock(pos.entryPrice, priceShock);
            
            // Calculate PnL under stress
            int256 pnl;
            if (pos.isLong) {
                pnl = int256(((stressedPrice - pos.entryPrice) * pos.size) / 1e18);
            } else {
                pnl = int256(((pos.entryPrice - stressedPrice) * pos.size) / 1e18);
            }
            
            portfolioPnL += pnl;
        }
        
        // Check if liquidated under stress
        uint256 collateral = getTotalCollateral(trader);
        int256 equity = int256(collateral) + portfolioPnL;
        uint256 mmRequired = getMaintenanceMarginRequired(trader);
        
        isLiquidated = equity < int256(mmRequired);
    }
    
    function applyShock(uint256 price, int256 shockBps) 
        internal pure returns (uint256) {
        if (shockBps >= 0) {
            return price + ((price * uint256(shockBps)) / 10000);
        } else {
            return price - ((price * uint256(-shockBps)) / 10000);
        }
    }
    
    function runAllScenarios(address trader) 
        external view returns (bytes32[] memory ids, int256[] memory pnls, bool[] memory liquidated) {
        
        ids = scenarioList;
        pnls = new int256[](ids.length);
        liquidated = new bool[](ids.length);
        
        for (uint i = 0; i < ids.length; i++) {
            (pnls[i], liquidated[i]) = runStressTest(trader, ids[i]);
        }
    }
}

Real-Time Margin Health Monitoring

contract MarginHealthMonitor {
    struct HealthMetrics {
        address trader;
        uint256 totalCollateral;
        int256 totalUnrealizedPnL;
        uint256 totalMaintenanceMargin;
        uint256 marginRatio;
        uint256 availableMargin;
        RiskLevel riskLevel;
        bytes32[] atRiskPositions;
    }
    
    enum RiskLevel {
        HEALTHY,      // > 150% margin ratio
        CAUTION,      // 110-150%
        WARNING,      // 100-110%
        LIQUIDATABLE  // < 100%
    }
    
    mapping(address => HealthMetrics) public traderHealth;
    
    uint256 public constant UPDATE_INTERVAL = 1 minutes;
    mapping(address => uint256) public lastHealthCheck;
    
    event HealthUpdated(address indexed trader, RiskLevel level, uint256 marginRatio);
    event MarginCall(address indexed trader, uint256 requiredMargin);
    
    function updateHealthMetrics(address trader) external {
        require(
            block.timestamp >= lastHealthCheck[trader] + UPDATE_INTERVAL,
            "Update too frequent"
        );
        
        HealthMetrics storage health = traderHealth[trader];
        
        // Get cross-margin account data
        CrossAccount memory account = crossMargin.getAccount(trader);
        
        health.trader = trader;
        health.totalCollateral = account.totalCollateral;
        health.totalUnrealizedPnL = account.totalUnrealizedPnL;
        health.totalMaintenanceMargin = account.totalMaintenanceMargin;
        
        // Calculate margin ratio
        int256 equity = int256(health.totalCollateral) + health.totalUnrealizedPnL;
        if (equity > 0 && health.totalMaintenanceMargin > 0) {
            health.marginRatio = (uint256(equity) * 10000) / health.totalMaintenanceMargin;
        } else {
            health.marginRatio = 0;
        }
        
        // Calculate available margin
        health.availableMargin = equity > int256(health.totalMaintenanceMargin)
            ? uint256(equity - int256(health.totalMaintenanceMargin))
            : 0;
        
        // Determine risk level
        health.riskLevel = determineRiskLevel(health.marginRatio);
        
        // Identify at-risk positions
        health.atRiskPositions = identifyAtRiskPositions(trader);
        
        lastHealthCheck[trader] = block.timestamp;
        
        emit HealthUpdated(trader, health.riskLevel, health.marginRatio);
        
        // Take action if needed
        if (health.riskLevel == RiskLevel.WARNING) {
            emit MarginCall(trader, health.totalMaintenanceMargin - uint256(equity));
        }
    }
    
    function determineRiskLevel(uint256 marginRatio) internal pure returns (RiskLevel) {
        if (marginRatio >= 15000) return RiskLevel.HEALTHY;
        if (marginRatio >= 11000) return RiskLevel.CAUTION;
        if (marginRatio >= 10000) return RiskLevel.WARNING;
        return RiskLevel.LIQUIDATABLE;
    }
    
    function identifyAtRiskPositions(address trader) 
        internal view returns (bytes32[] memory) {
        // Return positions closest to liquidation
        // Implementation depends on position structure
    }
    
    function getHealthSummary(address trader) 
        external view returns (
            RiskLevel level,
            uint256 marginRatio,
            uint256 availableMargin,
            bool canTrade
        ) {
        HealthMetrics memory health = traderHealth[trader];
        
        return (
            health.riskLevel,
            health.marginRatio,
            health.availableMargin,
            health.riskLevel != RiskLevel.LIQUIDATABLE
        );
    }
}

Best Practices

1. Gradual Rollout

contract GradualMarginRollout {
    enum MarginMode {
        ISOLATED_ONLY,
        CROSS_MARGIN_BETA,
        CROSS_MARGIN_FULL,
        PORTFOLIO_MARGIN
    }
    
    MarginMode public currentMode = MarginMode.ISOLATED_ONLY;
    mapping(address => bool) public crossMarginWhitelisted;
    
    function upgradeMarginMode(MarginMode newMode) external onlyGovernance {
        require(uint256(newMode) == uint256(currentMode) + 1, "Invalid upgrade");
        currentMode = newMode;
    }
    
    function whitelistForCrossMargin(address trader) external onlyRiskManager {
        crossMarginWhitelisted[trader] = true;
    }
}

2. Conservative Parameters

  • Start with higher margin requirements
  • Gradually reduce as system proves stable
  • Monitor for unexpected liquidations

3. Real-Time Monitoring

  • Sub-second margin updates
  • Proactive margin calls
  • Health score dashboards

4. User Education

  • Clear UI showing margin usage
  • Warnings before margin calls
  • Educational materials on cross-margin risks

5. Emergency Controls

  • Ability to pause new positions
  • Increase margin requirements during volatility
  • Circuit breakers

Conclusion

Advanced margin systems transform capital efficiency:

Cross-Margin Benefits:

  • 30-50% reduction in capital requirements
  • Better user experience
  • Increased trading volume

Portfolio Margin Benefits:

  • Sophisticated risk recognition
  • Attracts professional traders
  • Competitive advantage

Implementation Priority:

  1. Start with isolated margin (simpler, safer)
  2. Add cross-margin for trusted users
  3. Implement portfolio margin for sophisticated traders
  4. Continuously optimize parameters

The exchanges that master advanced margin systems will attract the most sophisticated traders and capture the lion's share of derivatives volume.