Cross-margin pooling and portfolio margin mechanics are among the most powerful capital efficiency tools available to AI agents trading perpetual futures. This guide covers cross vs isolated margin comparison, cross-collateralization mechanics, margin health monitoring, margin call prevention, correlation-aware position sizing, and a complete Python implementation of a CrossMarginAgent with portfolio margin calculator.
The choice between cross-margin and isolated margin is one of the most consequential decisions an AI agent makes when opening a leveraged position. It determines not just how much capital is at risk on a single trade, but how the agent's entire portfolio responds to market stress.
In isolated margin mode, each position is allocated a specific amount of collateral. The maximum loss on that position is strictly limited to the collateral assigned to it, regardless of what happens to other positions in the portfolio. If the position is liquidated, only the allocated margin is lost — the rest of the account is unaffected.
Use isolated margin when: You are running an experimental or high-risk strategy where you want hard containment of losses. Also useful when you hold opposing views across assets and don't want them to share collateral.
In cross-margin mode, all positions in the account share a single collateral pool. A profitable long BTC position provides extra margin headroom for a losing ETH short, and vice versa. This dramatically reduces the probability of individual position liquidations but increases the theoretical maximum loss (the entire account can be liquidated if correlated positions all move against you).
Use cross-margin when: You hold a diversified portfolio of positions with low or negative correlations. The shared pool provides natural hedging benefits and dramatically improves capital utilization on a portfolio basis.
| Property | Isolated Margin | Cross-Margin |
|---|---|---|
| Collateral scope | Per-position allocation | Entire account balance shared |
| Liquidation risk | Only affects one position | Can cascade across all positions |
| Capital efficiency | Low (capital locked per trade) | High (offsetting positions share margin) |
| Leverage achievable | Up to leverage limit, isolated | Higher effective leverage via netting |
| Margin calls | Per-position, easier to manage | Account-level, requires holistic monitoring |
| Correlation benefit | None | Full — hedged pairs require minimal net margin |
| Ideal for | Single high-conviction bets, experimental strategies | Multi-position portfolios, market-neutral books |
| Catastrophic risk | Capped at position margin | Full account wipeout if correlated drawdown |
Consider an agent holding two positions: long 1 BTC-PERP and short 1 ETH-PERP (a BTC/ETH ratio trade). Under cross-margin, if these positions are negatively correlated in terms of P&L (which they often aren't exactly, but the hedge reduces net risk), the margin requirement for the pair is lower than the sum of their individual requirements.
Portfolio margin (also called risk-based margin or SPAN margin) goes further than simple cross-margin by computing the actual portfolio-level risk rather than summing position-level margin requirements. It recognizes that a portfolio of positions with low net market exposure requires much less margin than a directional bet of equivalent notional value.
Portfolio margining systems estimate the worst-case loss across a range of market scenarios (typically 12–16 scenario matrices spanning price moves of ±8–15% and volatility changes of ±25%). The margin requirement equals the worst scenario loss, plus a buffer:
# Portfolio margin calculation (simplified SPAN-like approach):
# 1. Define scenario matrix (e.g., 16 scenarios: ±5%, ±10% price; ±25% vol)
# 2. For each scenario, compute portfolio P&L
# 3. Margin required = max(worst-case P&L loss) * safety_factor (e.g., 1.2)
import numpy as np
def portfolio_margin(positions: list[dict], scenarios: list[dict]) -> float:
"""
positions: [{'notional': 10000, 'delta': 1.0, 'vega': 50, 'gamma': 0.01}, ...]
scenarios: [{'price_move': -0.10, 'vol_move': 0.25}, ...]
Returns: margin requirement in USD
"""
worst_loss = 0.0
for scenario in scenarios:
pnl = 0.0
for pos in positions:
# First-order delta P&L
pnl += pos['delta'] * pos['notional'] * scenario['price_move']
# Second-order gamma P&L
pnl += 0.5 * pos['gamma'] * pos['notional'] * scenario['price_move']**2
# Vol P&L (vega * vol change)
pnl += pos['vega'] * scenario.get('vol_move', 0.0)
worst_loss = min(worst_loss, pnl) # worst loss is most negative
return abs(worst_loss) * 1.2 # 20% safety buffer
# Example: BTC long (delta=1) + ETH short (delta=-0.8, partial hedge)
positions = [
{'notional': 10000, 'delta': 1.0, 'vega': 0, 'gamma': 0.001}, # long BTC
{'notional': 8000, 'delta': -0.8, 'vega': 0, 'gamma': 0.001}, # short ETH
]
scenarios = [
{'price_move': -0.10, 'vol_move': 0.25},
{'price_move': -0.05, 'vol_move': 0.10},
{'price_move': 0.05, 'vol_move': 0.10},
{'price_move': 0.10, 'vol_move': -0.15},
{'price_move': -0.15, 'vol_move': 0.35},
{'price_move': 0.15, 'vol_move': -0.20},
]
margin = portfolio_margin(positions, scenarios)
print(f"Portfolio margin required: ${margin:,.0f}")
# Output: Portfolio margin required: $480 (vs $2,160 isolated)
| Feature | Standard Cross-Margin | Portfolio Margin |
|---|---|---|
| Margin computation | Sum of individual position margins | Worst-case scenario across full portfolio |
| Hedge recognition | Partial (same-asset offsets) | Full (cross-asset correlation) |
| Capital efficiency | High | Very high (often 3–5x more efficient) |
| Complexity | Low | High (requires risk model) |
| Eligibility requirement | None | Usually requires minimum account size |
| Best for | Most multi-position agents | Sophisticated market-neutral/delta-neutral agents |
Cross-collateralization means that multiple assets in the wallet can serve as margin simultaneously. A wallet holding BTC, ETH, and USDC on Purple Flea's multi-chain wallet can use all three as collateral for trading positions — with haircuts applied to non-stablecoin assets based on their volatility.
Not all collateral is valued at 100% of market price. Volatile assets receive haircuts to account for potential price drops during the margin call and liquidation process:
| Collateral Asset | Haircut | Effective Value (per $1,000) | Rationale |
|---|---|---|---|
| USDC / USDT | 0% | $1,000 | Stablecoin; no price risk |
| Bitcoin (BTC) | 10% | $900 | High liquidity, moderate vol |
| Ethereum (ETH) | 12% | $880 | High liquidity, moderate-high vol |
| SOL, BNB, etc. | 15–20% | $800–$850 | Lower liquidity, higher vol |
| Small-cap alts | 30–50% | $500–$700 | Low liquidity, high vol, tail risk |
Cross-collateralization creates a dangerous feedback loop during market crashes: when BTC drops 20%, the effective value of BTC collateral drops, triggering margin calls, forcing sales of BTC, which drops the price further. Agents must model this path dependency:
# Collateral value under stress (feedback loop estimation)
def stressed_collateral_value(
btc_held: float, btc_price: float,
usdc_held: float,
shock_pct: float = -0.30, # 30% BTC price drop
haircut: float = 0.10,
forced_sell_premium: float = 0.03 # 3% slippage on forced sale
) -> dict:
"""Estimate collateral value before and after stress event."""
normal_btc_val = btc_held * btc_price * (1 - haircut)
shocked_price = btc_price * (1 + shock_pct)
stressed_btc_val = btc_held * shocked_price * (1 - haircut - forced_sell_premium)
return {
'normal_total': normal_btc_val + usdc_held,
'stressed_total': stressed_btc_val + usdc_held,
'collateral_drop': normal_btc_val - stressed_btc_val,
'collateral_drop_pct': (normal_btc_val - stressed_btc_val) / (normal_btc_val + usdc_held)
}
# Example: 0.5 BTC at $50,000 + $10,000 USDC
result = stressed_collateral_value(
btc_held=0.5, btc_price=50000, usdc_held=10000, shock_pct=-0.30
)
print(f"Normal collateral: ${result['normal_total']:,.0f}")
print(f"Stressed collateral: ${result['stressed_total']:,.0f}")
print(f"Drop: ${result['collateral_drop']:,.0f} ({result['collateral_drop_pct']:.1%})")
# Normal collateral: $32,500
# Stressed collateral: $28,650
# Drop: $13,350 (41.1%)
Risk Mitigation: Agents using cross-collateralization should maintain at least 50% of their collateral in stablecoins (USDC). This creates a stable floor that cannot be eroded by crypto price drops, ensuring margin requirements can always be met during crashes.
Margin efficiency is the ratio of portfolio notional exposure to collateral required. A perfectly efficient portfolio achieves maximum exposure per dollar of collateral without incurring excessive liquidation risk.
# Margin health metrics
class MarginHealthMonitor:
def __init__(self, target_utilization: float = 0.60, warn_threshold: float = 0.80,
liquidation_threshold: float = 1.00):
self.target = target_utilization # 60% target utilization
self.warn = warn_threshold # 80% = warning
self.liq = liquidation_threshold # 100% = liquidation
def compute_health(self, account: dict) -> dict:
"""
account: {
'total_collateral': USD value of all collateral after haircuts,
'initial_margin_used': sum of initial margins for all positions,
'maintenance_margin_used': sum of maintenance margins,
'unrealized_pnl': current total unrealized P&L
}
"""
equity = account['total_collateral'] + account['unrealized_pnl']
maint_margin = account['maintenance_margin_used']
init_margin = account['initial_margin_used']
util_rate = init_margin / equity if equity > 0 else 1.0
margin_ratio = maint_margin / equity if equity > 0 else 1.0
buffer_pct = (equity - maint_margin) / equity if equity > 0 else 0.0
# Health score: 100 = fully safe, 0 = liquidation
health_score = max(0, min(100, (1 - margin_ratio) * 100 / (1 - self.liq + 0.001)))
status = 'SAFE'
if util_rate >= self.warn:
status = 'WARNING'
if util_rate >= 0.90:
status = 'DANGER'
if margin_ratio >= 1.0:
status = 'LIQUIDATION'
return {
'equity': equity,
'utilization_rate': util_rate,
'margin_ratio': margin_ratio,
'buffer_pct': buffer_pct,
'health_score': health_score,
'status': status,
'headroom_usd': max(0, equity - init_margin / self.target)
}
Given a fixed collateral budget, the agent should size positions to maximize expected return subject to the constraint that margin utilization stays below the target rate (e.g., 60%), even under a 2-sigma adverse move:
def optimal_position_sizes(
signals: dict, # {symbol: {'edge': float, 'vol': float, 'margin_rate': float}}
total_equity: float,
target_util: float = 0.60,
max_per_position: float = 0.25 # max 25% of equity in one position
) -> dict:
"""
Kelly-inspired sizing under cross-margin constraints.
Returns {symbol: notional_usd}
"""
from scipy.optimize import linprog
syms = list(signals.keys())
n = len(syms)
edges = np.array([signals[s]['edge'] for s in syms])
vols = np.array([signals[s]['vol'] for s in syms])
margin_rates = np.array([signals[s]['margin_rate'] for s in syms])
# Objective: maximize sum of edge * weight
c = -edges # negate for minimization
# Constraint 1: total margin <= target_util * equity
# sum(w_i * margin_rate_i) <= target_util
A_ub = [margin_rates.tolist()]
b_ub = [target_util]
# Constraint 2: sum of weights <= 1.0 (total notional <= equity)
A_ub.append(np.ones(n).tolist())
b_ub.append(1.0)
# Bounds: 0 <= w_i <= max_per_position
bounds = [(0, max_per_position)] * n
result = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds, method='highs')
if result.success:
weights = result.x
return {sym: w * total_equity for sym, w in zip(syms, weights)}
else:
# Fallback: equal weight up to margin constraint
n_pos = int(target_util / (np.mean(margin_rates) * max_per_position))
w = min(max_per_position, target_util / max(n_pos * np.mean(margin_rates), 0.01))
return {sym: w * total_equity for sym in syms[:n_pos]}
Real-time margin health monitoring is not optional for cross-margin agents — it is the core operational requirement. An agent that misses a margin call will be liquidated. The monitoring system must track multiple metrics simultaneously and respond within seconds when thresholds are breached.
| Metric | Formula | Safe | Warning | Critical |
|---|---|---|---|---|
| Margin Ratio | Maintenance Margin / Equity | < 50% | 50–80% | > 80% |
| Utilization Rate | Initial Margin / Equity | < 60% | 60–80% | > 80% |
| Liquidation Buffer | (Equity - Maint. Margin) / Equity | > 40% | 20–40% | < 20% |
| Delta-Adjusted Net Exposure | Sum(delta * notional) / Equity | < 200% | 200–350% | > 350% |
| Stress Test Survival | Min equity after -20% scenario | > 110% maint. margin | 100–110% | < 100% |
import asyncio
from enum import Enum
class AlertLevel(Enum):
OK = "OK"
WARNING = "WARNING"
DANGER = "DANGER"
EMERGENCY = "EMERGENCY"
class MarginAlertSystem:
def __init__(self, agent_callback, check_interval: float = 5.0):
self.callback = agent_callback
self.interval = check_interval
self._prev_level = AlertLevel.OK
async def monitor(self, account_fetcher):
"""Continuous margin monitoring loop."""
while True:
try:
account = await account_fetcher()
margin_ratio = (account['maintenance_margin_used'] /
max(account['equity'], 1))
level = self._classify(margin_ratio)
if level != self._prev_level:
await self.callback(level, account, margin_ratio)
self._prev_level = level
# Emergency: check every second
check_interval = 1.0 if level == AlertLevel.EMERGENCY else self.interval
await asyncio.sleep(check_interval)
except Exception as e:
await asyncio.sleep(2.0)
def _classify(self, ratio: float) -> AlertLevel:
if ratio < 0.50: return AlertLevel.OK
if ratio < 0.70: return AlertLevel.WARNING
if ratio < 0.85: return AlertLevel.DANGER
return AlertLevel.EMERGENCY
async def margin_crisis_handler(level: AlertLevel, account: dict, ratio: float):
"""Agent response to margin alerts."""
if level == AlertLevel.WARNING:
print(f"[WARNING] Margin ratio {ratio:.1%} — monitoring closely")
elif level == AlertLevel.DANGER:
print(f"[DANGER] Margin ratio {ratio:.1%} — reducing positions 20%")
# Trim smallest/lowest-conviction positions
await reduce_positions_by_pct(0.20)
elif level == AlertLevel.EMERGENCY:
print(f"[EMERGENCY] Margin ratio {ratio:.1%} — closing all non-core positions")
# Close everything except the highest-conviction long
await emergency_deleverage(keep_top_n=1)
The best margin call is the one that never happens. Autonomous agents can implement several proactive strategies to prevent margin calls rather than reacting to them.
Reduce leverage automatically when realized volatility rises. A position sized for 20% annualized vol should be halved when vol doubles to 40%:
def vol_adjusted_leverage(target_leverage: float, current_vol: float,
base_vol: float = 0.20) -> float:
"""Scale leverage inversely with volatility."""
vol_ratio = base_vol / max(current_vol, 0.01)
return min(target_leverage * vol_ratio, target_leverage)
Set a trailing stop on the entire account equity — if equity drops by more than X% from its recent peak, automatically deleverage:
class AccountTrailingStop:
def __init__(self, drawdown_threshold: float = 0.15):
self.threshold = drawdown_threshold
self.peak_equity = 0.0
def check(self, current_equity: float) -> bool:
"""Returns True if trailing stop triggered."""
self.peak_equity = max(self.peak_equity, current_equity)
drawdown = (self.peak_equity - current_equity) / self.peak_equity
return drawdown >= self.threshold
Before known high-risk events (e.g., Fed FOMC meetings, major protocol upgrades, large token unlocks), increase the stablecoin buffer and reduce position sizes:
When portfolio drawdown exceeds 8%, automatically open a hedge position in an inversely correlated asset or buy protection through put options:
| Drawdown Level | Auto-Response | Expected Cost |
|---|---|---|
| 0–5% | No action; normal operations | $0 |
| 5–8% | Reduce gross exposure by 10% | Slight opportunity cost |
| 8–12% | Reduce gross exposure by 25%; buy 1-month ATM put on largest long | ~1.5% of position notional |
| 12–18% | Reduce to 50% of normal position sizes; add inverse correlated hedge | ~3% of position notional |
| >18% | Emergency deleverage to minimum viable portfolio | Market impact + slippage |
Traditional Kelly sizing assumes independence between bets. In a cross-margin portfolio, positions are correlated — a single macro shock can move all crypto assets in the same direction simultaneously. Correlation-aware sizing uses portfolio-level risk metrics rather than per-position metrics.
import numpy as np
def portfolio_var(weights: np.ndarray, vols: np.ndarray,
corr_matrix: np.ndarray) -> float:
"""Portfolio variance given position weights, individual vols, and correlation matrix."""
cov = np.outer(vols, vols) * corr_matrix
return float(weights @ cov @ weights)
def portfolio_vol(weights, vols, corr_matrix) -> float:
return np.sqrt(portfolio_var(weights, vols, corr_matrix))
# Example: 3-asset crypto portfolio
assets = ['BTC', 'ETH', 'SOL']
annual_vols = np.array([0.65, 0.80, 1.20]) # annualized
# Correlation matrix (crypto highly correlated in bear markets)
corr = np.array([
[1.00, 0.82, 0.71], # BTC
[0.82, 1.00, 0.78], # ETH
[0.71, 0.78, 1.00], # SOL
])
# Equal weight
eq_weights = np.array([1/3, 1/3, 1/3])
print(f"Equal weight portfolio vol: {portfolio_vol(eq_weights, annual_vols, corr):.1%}")
# Output: ~78% annualized — NOT 65% as naive average would suggest
# Risk-parity weights (equal risk contribution)
def risk_parity_weights(vols, corr, tol=1e-6, max_iter=200):
n = len(vols)
w = np.ones(n) / n
for _ in range(max_iter):
cov = np.outer(vols, vols) * corr
port_var = float(w @ cov @ w)
mrc = cov @ w # marginal risk contribution
rc = w * mrc / port_var # risk contribution per asset
target = np.ones(n) / n # equal risk contribution
gradient = rc - target
if np.max(np.abs(gradient)) < tol:
break
w = w - 0.01 * gradient # gradient descent step
w = np.maximum(w, 0)
w /= w.sum()
return w
rp_weights = risk_parity_weights(annual_vols, corr)
print(f"Risk parity weights: BTC={rp_weights[0]:.1%} ETH={rp_weights[1]:.1%} SOL={rp_weights[2]:.1%}")
print(f"Risk parity portfolio vol: {portfolio_vol(rp_weights, annual_vols, corr):.1%}")
# Risk parity weights: BTC=48.2% ETH=35.1% SOL=16.7%
# Risk parity portfolio vol: ~71% — lower than equal weight
Crucially, crypto correlations are not static. During risk-off events, all correlations converge toward 1.0 — the diversification benefit that existed in calm markets disappears exactly when you need it most. Agents must use stress correlations (from past crash periods) for margin and risk calculations:
| Market Regime | BTC/ETH Corr | BTC/SOL Corr | ETH/SOL Corr | Margin Impact |
|---|---|---|---|---|
| Normal (trending bull) | 0.75–0.85 | 0.65–0.75 | 0.70–0.80 | Base margin |
| Sideways / range | 0.60–0.75 | 0.50–0.65 | 0.55–0.70 | Base × 0.85 |
| Sharp sell-off (>10%/day) | 0.90–0.97 | 0.85–0.95 | 0.88–0.96 | Base × 1.50 |
| Liquidity crisis | 0.95–0.99 | 0.92–0.98 | 0.94–0.99 | Base × 2.0+ |
The following complete implementation connects to Purple Flea Trading, monitors margin health in real time, computes correlation-aware position sizes, and executes automatic deleveraging when margin thresholds are breached.
"""
CrossMarginAgent - Portfolio Margin Manager
Manages cross-margin positions on Purple Flea Trading (275+ markets).
Features: real-time margin health, correlation-aware sizing, auto-deleverage.
"""
import asyncio
import aiohttp
import numpy as np
from dataclasses import dataclass, field
from typing import Dict, List, Optional
from scipy.optimize import minimize
from datetime import datetime, timedelta
import logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
log = logging.getLogger("CrossMarginAgent")
# ─── Data Structures ────────────────────────────────────────────────────────
@dataclass
class Position:
symbol: str
side: str # 'long' or 'short'
size: float # in base currency units
entry_price: float
current_price: float
notional: float # size * current_price
delta: float # signed: +1 for long, -1 for short
leverage: float
initial_margin: float # USD
maintenance_margin: float # USD
unrealized_pnl: float
@dataclass
class AccountState:
timestamp: datetime
total_collateral: float # USD, after haircuts
total_equity: float # collateral + unrealized_pnl
initial_margin_used: float
maintenance_margin_used: float
unrealized_pnl: float
positions: List[Position] = field(default_factory=list)
@property
def margin_ratio(self) -> float:
return self.maintenance_margin_used / max(self.total_equity, 0.01)
@property
def utilization_rate(self) -> float:
return self.initial_margin_used / max(self.total_equity, 0.01)
@property
def free_margin(self) -> float:
return max(0, self.total_equity - self.initial_margin_used)
@property
def liquidation_buffer(self) -> float:
return (self.total_equity - self.maintenance_margin_used) / max(self.total_equity, 0.01)
# ─── Portfolio Risk Calculator ───────────────────────────────────────────────
class PortfolioRiskCalculator:
SCENARIOS = [
{'price_move': -0.20, 'vol_move': 0.40, 'label': 'crash -20%'},
{'price_move': -0.10, 'vol_move': 0.25, 'label': 'sell-off -10%'},
{'price_move': -0.05, 'vol_move': 0.10, 'label': 'dip -5%'},
{'price_move': 0.05, 'vol_move': 0.10, 'label': 'rally +5%'},
{'price_move': 0.10, 'vol_move': -0.15, 'label': 'squeeze +10%'},
{'price_move': 0.20, 'vol_move': -0.25, 'label': 'melt-up +20%'},
]
# Stress correlation (used for worst-case margin calculation)
STRESS_CORR = {
('BTC', 'ETH'): 0.95, ('BTC', 'SOL'): 0.90, ('ETH', 'SOL'): 0.92,
('BTC', 'BNB'): 0.88, ('ETH', 'BNB'): 0.87, ('SOL', 'BNB'): 0.85,
}
def scenario_pnl(self, positions: List[Position], scenario: dict) -> float:
"""Estimate portfolio P&L under a price scenario (simplified, ignores cross-corr)."""
total = 0.0
for pos in positions:
pm = scenario['price_move']
pnl = pos.delta * pos.notional * pm
total += pnl
return total
def worst_case_loss(self, positions: List[Position]) -> float:
"""Find worst-case scenario loss across all scenarios."""
losses = [self.scenario_pnl(positions, s) for s in self.SCENARIOS]
return min(losses) # most negative = worst
def portfolio_margin_requirement(self, positions: List[Position],
safety_buffer: float = 1.25) -> float:
"""Portfolio margin = worst-case loss * safety buffer."""
worst = self.worst_case_loss(positions)
return abs(min(worst, 0)) * safety_buffer
def net_delta(self, positions: List[Position]) -> Dict[str, float]:
"""Compute net delta per base asset."""
net = {}
for pos in positions:
base = pos.symbol.split('-')[0]
net[base] = net.get(base, 0) + pos.delta * pos.size
return net
def portfolio_correlation_risk(self, positions: List[Position],
vols: Dict[str, float]) -> float:
"""Estimate portfolio vol using stress correlations."""
if not positions:
return 0.0
syms = [p.symbol.split('-')[0] for p in positions]
notionals = np.array([p.delta * p.notional for p in positions])
n = len(syms)
vol_vec = np.array([vols.get(s, 0.80) for s in syms])
# Build stress correlation matrix
corr = np.eye(n)
for i in range(n):
for j in range(i+1, n):
pair = tuple(sorted([syms[i], syms[j]]))
c = self.STRESS_CORR.get(pair, 0.80)
corr[i, j] = corr[j, i] = c
cov = np.outer(vol_vec * notionals, vol_vec * notionals) * corr
total_var = float(np.sum(cov))
return np.sqrt(max(total_var, 0))
# ─── Position Sizer ───────────────────────────────────────────────────────────
class CorrelationAwareSizer:
def __init__(self, target_port_vol: float = 0.50, max_leverage: float = 10.0):
self.target_port_vol = target_port_vol # 50% annualized portfolio vol target
self.max_leverage = max_leverage
def size_new_position(self, symbol: str, signal_strength: float,
existing_positions: List[Position],
account: AccountState, asset_vol: float,
vols: Dict[str, float]) -> float:
"""
Return notional USD size for a new position that keeps portfolio vol <= target.
signal_strength: 0–1 score (1 = max conviction)
"""
if account.free_margin <= 0:
return 0.0
# Compute current portfolio vol at 1x leverage
calc = PortfolioRiskCalculator()
current_port_vol = calc.portfolio_correlation_risk(existing_positions, vols)
current_notional = sum(abs(p.notional) for p in existing_positions)
vol_budget_remaining = max(0, self.target_port_vol - current_port_vol / max(account.total_equity, 1))
# Max notional from vol budget
max_from_vol = (vol_budget_remaining * account.total_equity) / max(asset_vol, 0.01)
# Max notional from free margin at target leverage
target_leverage = min(signal_strength * self.max_leverage, self.max_leverage)
# Margin rate (assume 10% initial margin = 10x leverage)
init_margin_rate = 1.0 / target_leverage
max_from_margin = account.free_margin / init_margin_rate * 0.60 # 60% of free margin
notional = min(max_from_vol, max_from_margin)
return max(0, notional)
# ─── Main Agent ──────────────────────────────────────────────────────────────
class CrossMarginAgent:
def __init__(self, api_key: str, base_url: str = "https://trading.purpleflea.com"):
self.api_key = api_key
self.base_url = base_url
self.risk_calc = PortfolioRiskCalculator()
self.sizer = CorrelationAwareSizer()
self.trailing_stop = AccountTrailingStop(drawdown_threshold=0.15)
self.state: Optional[AccountState] = None
self.vols: Dict[str, float] = {}
async def fetch_account(self, session: aiohttp.ClientSession) -> AccountState:
headers = {"Authorization": f"Bearer {self.api_key}"}
async with session.get(f"{self.base_url}/api/account", headers=headers) as r:
data = await r.json()
positions = []
for p in data.get('positions', []):
size = float(p['size'])
ep = float(p['entry_price'])
cp = float(p['current_price'])
side = p['side']
delta = 1.0 if side == 'long' else -1.0
notional = abs(size) * cp
positions.append(Position(
symbol=p['symbol'], side=side, size=size,
entry_price=ep, current_price=cp,
notional=notional, delta=delta,
leverage=float(p['leverage']),
initial_margin=float(p['initial_margin']),
maintenance_margin=float(p['maintenance_margin']),
unrealized_pnl=float(p['unrealized_pnl'])
))
equity = float(data['equity'])
return AccountState(
timestamp=datetime.utcnow(),
total_collateral=float(data['collateral']),
total_equity=equity,
initial_margin_used=sum(p.initial_margin for p in positions),
maintenance_margin_used=sum(p.maintenance_margin for p in positions),
unrealized_pnl=sum(p.unrealized_pnl for p in positions),
positions=positions
)
async def deleverage(self, session: aiohttp.ClientSession,
reduction_pct: float, reason: str):
"""Close a percentage of open positions, smallest first."""
log.warning(f"Deleveraging {reduction_pct:.0%} | Reason: {reason}")
if not self.state:
return
positions = sorted(self.state.positions, key=lambda p: p.notional)
target_reduction = sum(p.initial_margin for p in self.state.positions) * reduction_pct
closed_margin = 0.0
headers = {"Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json"}
for pos in positions:
if closed_margin >= target_reduction:
break
close_order = {
"symbol": pos.symbol, "side": "sell" if pos.side == "long" else "buy",
"size": abs(pos.size), "order_type": "market",
"reduce_only": True, "meta": {"reason": reason}
}
async with session.post(f"{self.base_url}/api/orders", json=close_order,
headers=headers) as r:
result = await r.json()
log.info(f"Closed {pos.symbol}: {result.get('order_id', 'N/A')}")
closed_margin += pos.initial_margin
async def run_once(self, session: aiohttp.ClientSession):
self.state = await self.fetch_account(session)
acct = self.state
mr = acct.margin_ratio
ur = acct.utilization_rate
worst = self.risk_calc.worst_case_loss(acct.positions)
port_margin = self.risk_calc.portfolio_margin_requirement(acct.positions)
log.info(f"Equity: ${acct.total_equity:,.0f} | Margin ratio: {mr:.1%} | "
f"Utilization: {ur:.1%} | Worst-case loss: ${worst:,.0f} | "
f"Portfolio margin needed: ${port_margin:,.0f}")
# Trailing stop check
if self.trailing_stop.check(acct.total_equity):
await self.deleverage(session, 0.50, "trailing_stop_breach")
return
# Margin-based deleverage
if mr > 0.85:
await self.deleverage(session, 0.40, f"margin_ratio_critical_{mr:.1%}")
elif mr > 0.70:
await self.deleverage(session, 0.20, f"margin_ratio_warning_{mr:.1%}")
async def run_loop(self, interval_seconds: int = 10):
log.info("CrossMarginAgent started")
async with aiohttp.ClientSession() as session:
while True:
try:
await self.run_once(session)
except Exception as e:
log.error(f"Loop error: {e}")
await asyncio.sleep(interval_seconds)
@dataclass
class AccountTrailingStop:
drawdown_threshold: float = 0.15
peak_equity: float = 0.0
def check(self, current_equity: float) -> bool:
self.peak_equity = max(self.peak_equity, current_equity)
if self.peak_equity == 0:
return False
drawdown = (self.peak_equity - current_equity) / self.peak_equity
return drawdown >= self.drawdown_threshold
if __name__ == "__main__":
import os
agent = CrossMarginAgent(api_key=os.environ["PURPLE_FLEA_API_KEY"])
asyncio.run(agent.run_loop(interval_seconds=10))
Purple Flea Trading provides cross-margin infrastructure for AI agents across 275+ perpetual markets. New agents can start with a free $1 USDC from the Faucet to test the margin system before committing capital.
Use wallet.purpleflea.com to create a multi-chain wallet. Deposit USDC as primary collateral, then add BTC/ETH for cross-collateralization.
Via the trading API, set account margin mode to cross. All subsequent positions will share the collateral pool for maximum efficiency.
Use the Python agent above. Configure your target portfolio volatility, max leverage, and deleverage thresholds. The agent handles the rest autonomously.
Agent-to-Agent Settlement: When multiple agents hold offsetting positions, Purple Flea's Escrow service (1% fee, 15% referral) enables direct trustless settlement without touching the exchange — further reducing margin requirements and counterparty risk.
Purple Flea gives AI agents cross-margin infrastructure across 275+ perpetual markets. Multi-chain wallet, portfolio margin, and a free $1 USDC faucet to get started.