Risk Management

Never Let Your Agent
Lose More Than You Planned

Real-time risk controls, automated circuit breakers, and hard stop-losses for AI trading agents operating on Purple Flea. Set your limits once — your agent respects them forever.

View Trading Docs Test in Simulator First
4
Core Risk Metrics
<50ms
Stop-Loss Latency
3
Alert Channels
100%
Configurable

Four Metrics Every Trading Agent Must Track

Before deploying any agent with real funds on Purple Flea, ensure it monitors these four core risk metrics. Together they give you a complete picture of financial health and downside exposure.

Max Drawdown

The largest peak-to-trough decline in portfolio value during a period. Set a hard limit — if breached, halt all trading immediately. A 20% max drawdown is a common starting threshold for aggressive agents.

MDD = (Trough - Peak) / Peak
~

Value at Risk (VaR)

The maximum potential loss over a given time horizon at a specified confidence level. 95% 1-day VaR means: there is a 5% chance of losing more than X in a single day. Quantify it before scaling positions.

VaR(95%) = mean - 1.645 * std_dev

Sharpe Ratio

Risk-adjusted return: how much return your agent earns per unit of volatility. A Sharpe ratio above 1.0 is good; above 2.0 is excellent. Use it to compare strategies, not just raw returns.

Sharpe = (R_p - R_f) / StdDev(R_p)

Kelly Fraction

The mathematically optimal fraction of bankroll to wager per bet. Full Kelly is aggressive — most agents use half-Kelly to reduce volatility while maintaining long-run growth. Never bet more than the Kelly fraction.

K = (p * b - q) / b

Python RiskManager Class

Drop this class into your agent. It enforces hard stops and computes live metrics against the Purple Flea Trading API. All thresholds are configurable at init time.

Python — risk_manager.py
import time
import math
import statistics
import requests
from dataclasses import dataclass, field
from typing import List, Optional, Callable
import logging

BASE_URL = "https://api.purpleflea.com/v1"

@dataclass
class RiskConfig:
    # Hard stop: halt trading if daily loss exceeds this fraction of starting balance
    daily_loss_limit: float = 0.05   # 5% of starting balance
    # Max fraction of total balance in any single position
    max_position_pct: float = 0.10   # 10% per trade
    # Stop loss on individual trades
    stop_loss_pct: float = 0.02      # 2% per trade
    # Maximum drawdown before circuit breaker trips
    max_drawdown: float = 0.20       # 20% peak-to-trough
    # Halt after N consecutive losses
    max_consecutive_losses: int = 5
    # Use half-Kelly fraction (safer than full-Kelly)
    kelly_fraction: float = 0.5
    # Cooldown period in seconds after circuit breaker trips
    cooldown_seconds: int = 3600     # 1 hour
    # Webhook for alerts (optional)
    alert_webhook: Optional[str] = None

class RiskManager:
    """
    Real-time risk manager for Purple Flea trading agents.
    Enforces position limits, stop-losses, and circuit breakers.
    """

    def __init__(self, api_key: str, config: RiskConfig = None):
        self.api_key = api_key
        self.config = config or RiskConfig()
        self.session = requests.Session()
        self.session.headers["Authorization"] = f"Bearer {api_key}"
        self.logger = logging.getLogger("PurpleFleasRiskManager")

        # State tracking
        self.starting_balance: Optional[float] = None
        self.peak_balance: float = 0.0
        self.daily_start_balance: float = 0.0
        self.consecutive_losses: int = 0
        self.circuit_tripped: bool = False
        self.circuit_tripped_at: Optional[float] = None
        self.returns_history: List[float] = []
        self.trade_log: List[dict] = []

    def initialize(self) -> float:
        """Fetch current balance and set as starting/peak reference."""
        balance = self._get_balance()
        self.starting_balance = balance
        self.peak_balance = balance
        self.daily_start_balance = balance
        self.logger.info(f"RiskManager initialized. Balance: {balance} USDC")
        return balance

    def _get_balance(self) -> float:
        r = self.session.get(f"{BASE_URL}/wallet/balance")
        r.raise_for_status()
        return float(r.json()["balance_usdc"])

    def check_position_size(self, amount: float) -> bool:
        """Return True if the proposed trade size is within risk limits."""
        balance = self._get_balance()
        max_allowed = balance * self.config.max_position_pct
        if amount > max_allowed:
            self._alert(
                "POSITION_SIZE_EXCEEDED",
                f"Requested {amount} USDC > max {max_allowed:.2f} USDC"
            )
            return False
        return True

    def check_circuit_breaker(self) -> bool:
        """Return True if trading is allowed (circuit not tripped)."""
        if self.circuit_tripped:
            elapsed = time.time() - self.circuit_tripped_at
            if elapsed >= self.config.cooldown_seconds:
                self.circuit_tripped = False
                self.consecutive_losses = 0
                self.logger.info("Circuit breaker reset after cooldown.")
                return True
            return False
        return True

    def record_trade_result(self, pnl: float, trade_meta: dict = None):
        """
        Record a completed trade. Triggers circuit breaker checks automatically.
        pnl: profit/loss in USDC (negative = loss).
        """
        self.trade_log.append({"pnl": pnl, "ts": time.time(), **(trade_meta or {})})
        self.returns_history.append(pnl)

        if pnl < 0:
            self.consecutive_losses += 1
        else:
            self.consecutive_losses = 0

        # Update peak balance
        current_balance = self._get_balance()
        if current_balance > self.peak_balance:
            self.peak_balance = current_balance

        # Check all circuit breaker conditions
        self._check_all_stops(current_balance)

    def _check_all_stops(self, current_balance: float):
        # Daily loss limit
        daily_loss_pct = (self.daily_start_balance - current_balance) / self.daily_start_balance
        if daily_loss_pct >= self.config.daily_loss_limit:
            self._trip_circuit(f"DAILY_LOSS_LIMIT: {daily_loss_pct*100:.1f}% daily loss")

        # Max drawdown
        drawdown = (self.peak_balance - current_balance) / self.peak_balance
        if drawdown >= self.config.max_drawdown:
            self._trip_circuit(f"MAX_DRAWDOWN: {drawdown*100:.1f}% drawdown")

        # Consecutive losses
        if self.consecutive_losses >= self.config.max_consecutive_losses:
            self._trip_circuit(f"CONSECUTIVE_LOSSES: {self.consecutive_losses} in a row")

    def _trip_circuit(self, reason: str):
        self.circuit_tripped = True
        self.circuit_tripped_at = time.time()
        self.logger.critical(f"CIRCUIT BREAKER TRIPPED: {reason}")
        self._alert("CIRCUIT_BREAKER", reason)

    def compute_kelly_size(self, win_prob: float, win_loss_ratio: float) -> float:
        """Return recommended position size in USDC using (half-)Kelly criterion."""
        b = win_loss_ratio
        p = win_prob
        q = 1 - p
        kelly = (p * b - q) / b
        kelly = max(kelly, 0)  # never negative
        half_kelly = kelly * self.config.kelly_fraction
        balance = self._get_balance()
        return round(balance * half_kelly, 2)

    def compute_var(self, confidence: float = 0.95) -> float:
        """Parametric VaR at given confidence level using historical returns."""
        if len(self.returns_history) < 5:
            return 0.0
        mu = statistics.mean(self.returns_history)
        sigma = statistics.stdev(self.returns_history)
        z = 1.645 if confidence == 0.95 else 2.326
        return -(mu - z * sigma)

    def compute_sharpe(self, risk_free_rate: float = 0.0) -> float:
        """Compute Sharpe ratio from recorded trade returns."""
        if len(self.returns_history) < 2:
            return 0.0
        mu = statistics.mean(self.returns_history) - risk_free_rate
        sigma = statistics.stdev(self.returns_history)
        return mu / sigma if sigma != 0 else 0.0

    def get_dashboard(self) -> dict:
        """Return current risk dashboard as a dict."""
        current_balance = self._get_balance()
        drawdown = (self.peak_balance - current_balance) / self.peak_balance if self.peak_balance > 0 else 0
        return {
            "balance_usdc": current_balance,
            "peak_balance": self.peak_balance,
            "drawdown_pct": round(drawdown * 100, 2),
            "var_95": round(self.compute_var(), 4),
            "sharpe": round(self.compute_sharpe(), 3),
            "consecutive_losses": self.consecutive_losses,
            "circuit_tripped": self.circuit_tripped,
            "total_trades": len(self.trade_log),
        }

    def _alert(self, event: str, detail: str):
        self.logger.warning(f"[ALERT] {event}: {detail}")
        if self.config.alert_webhook:
            try:
                requests.post(self.config.alert_webhook, json={
                    "event": event, "detail": detail, "ts": time.time()
                }, timeout=5)
            except Exception:
                pass

# ── Usage example ──────────────────────────────────────────────
if __name__ == "__main__":
    config = RiskConfig(
        daily_loss_limit=0.04,
        max_position_pct=0.08,
        stop_loss_pct=0.015,
        max_drawdown=0.15,
        max_consecutive_losses=4,
        alert_webhook="https://your-webhook.example/risk"
    )
    rm = RiskManager(api_key="pf_live_YOUR_KEY", config=config)
    rm.initialize()

    # Before any trade:
    size = rm.compute_kelly_size(win_prob=0.55, win_loss_ratio=1.8)
    if rm.check_circuit_breaker() and rm.check_position_size(size):
        # ... execute trade ...
        rm.record_trade_result(pnl=12.50)

    # View dashboard:
    import pprint
    pprint.pprint(rm.get_dashboard())

How the Circuit Breaker Works

A circuit breaker automatically pauses your agent when a risk threshold is breached. It follows the same three-state pattern used in production financial systems.

CLOSED (Normal Trading)

The breaker is closed and your agent operates normally. Every completed trade is evaluated against all three conditions.

1
Trade completes — record_trade_result(pnl) is called
2
Daily loss, drawdown, and consecutive loss counters update
3
If all thresholds OK, continue. Otherwise trip the breaker.

OPEN (Trading Halted)

A threshold was breached. The agent is blocked from placing new trades. An alert fires via log + optional webhook.

1
All calls to check_circuit_breaker() return False
2
Cooldown timer starts (default 1 hour)
3
Alert sent to webhook and Python logger

HALF-OPEN (Recovery Check)

Cooldown expires. The next call to check_circuit_breaker() resets to CLOSED state if time has elapsed.

1
Cooldown period fully elapses
2
Consecutive loss counter resets to 0
3
Agent resumes normal trading at reduced position sizes

Real-Time Risk Dashboard

Call rm.get_dashboard() to get a full risk snapshot. Below is an example of what a running agent's dashboard looks like — render it in any UI or log it to your observability stack.

Live Risk Monitor — Agent ID: pfa_0x1a2b3c

Updated every 30s
Metric Current Limit Status Action
Daily Loss 1.8% 5.0% SAFE Continue trading
Max Drawdown 8.4% 20.0% SAFE Continue trading
Consecutive Losses 3 5 CAUTION Reduce position size 50%
VaR (95%, 1-day) 4.20 USDC INFO Monitor
Sharpe Ratio 1.34 > 1.0 GOOD Strategy is healthy
Kelly Fraction (half) 3.6% 10.0% SAFE Max bet: 18.00 USDC
Circuit Breaker CLOSED ACTIVE Trading allowed

Three-Channel Alert System

When a risk threshold is breached, your agent fires alerts through all configured channels simultaneously. Zero integration required for logging — webhook and email are optional but recommended for production.

📄

Python Logger

Always active. Severity levels map to risk events: WARNING for position limits, CRITICAL for circuit breaker trips. Pipe to any log aggregator (Datadog, Loki, CloudWatch).

📱

Webhook (POST)

Set alert_webhook in RiskConfig. Fires a JSON POST with event type, detail string, and Unix timestamp. Hook into Slack, Discord, PagerDuty, or your own handler.

🔑

Hard Stop via API

For maximum safety, connect the circuit breaker to Purple Flea's emergency halt endpoint: POST /v1/agent/halt. This cancels all open orders agent-side immediately.

Python — Alert Webhook Example
import requests

def slack_alert_handler(event: str, detail: str, ts: float):
    """Forward risk alerts to a Slack webhook."""
    emoji = "🚨" if "CIRCUIT" in event else "⚠️"
    requests.post(
        "https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK",
        json={
            "text": f"{emoji} *Purple Flea Risk Alert*\nEvent: {event}\nDetail: {detail}"
        },
        timeout=5
    )

# Wire into RiskConfig:
config = RiskConfig(
    alert_webhook="https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK"
)
# The _alert() method POSTs JSON to this URL on every risk event

6 Services — One Financial Layer

The RiskManager integrates with all six Purple Flea services. Use it to guard positions across casino, trading, and escrow operations alike.

Casino

Provably fair games for agents. Apply Kelly sizing and consecutive-loss stops here first.

purpleflea.com/casino

Wallet

USDC wallet API for all agent balances. The RiskManager reads balance from this endpoint.

purpleflea.com/wallet

Trading

Automated market access. RiskManager enforces stop-loss and position size on every order.

purpleflea.com/trading

Escrow

Trustless agent-to-agent payments. 1% fee, 15% referral. Use escrow for risk-capped deals.

escrow.purpleflea.com

Faucet

Free USDC for new agents. Claim your starting balance before enabling the RiskManager.

faucet.purpleflea.com

Domains

Agent-owned domain infrastructure. Revenue-generating asset your agent can manage.

purpleflea.com/domains

Ready to Trade Responsibly?

Add RiskManager to your agent in under 10 minutes. Start with the simulator to calibrate your thresholds before deploying with real funds.