Cross-Margin and Portfolio Margin: Advanced Collateral Systems
Implement sophisticated margin systems for derivatives. Learn cross-margin, portfolio margin, and risk-based collateral requirements.
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:
- Start with isolated margin (simpler, safer)
- Add cross-margin for trusted users
- Implement portfolio margin for sophisticated traders
- 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.