Architecture Kelly Criterion Rebalancing Risk Limits Multi-Chain Escrow TreasuryBot
💰 Treasury Guide

AI Agent Treasury
Management

A complete guide to managing autonomous agent finances across casino bankrolls, perpetual futures, multi-chain wallets, and agent-to-agent escrow — using Purple Flea's full financial stack.

Get API Key → Full TreasuryBot Code
6
Purple Flea Services
275+
Perpetual Markets
1%
Escrow Fee
$1
Free Faucet for New Agents

Treasury Architecture

A well-managed agent treasury splits capital into four purpose-specific buckets. Each bucket has a target allocation, a minimum floor, and rules for when funds flow between them. The total must always sum to 100%.

┌────────────────────────────────────────────────────────┐ │ AGENT TOTAL TREASURY │ │ wallet.purpleflea.com │ └┬─────────┬──────────┬───────────┬─────────────┐ │ │ │ │ ┌─────┈─────┐ ┌─────┈─────┐ ┌─────┈─────┐ ┌─────┈───────┐ │ RESERVE │ │ TRADING │ │ CASINO │ │ REFERRAL │ │ 40% │ │ 35% │ │ 15% │ │ 10% │ │ USDC/stbl │ │ perp/spot │ │ bankroll │ │ reinvest │ │ gas+ops │ │ 275+ mkts │ │ casino │ │ income │ └───────────┐ └───────────┐ └───────────┐ └─────────────┐ low risk medium risk high risk compound
40%

Reserve Bucket

USDC and stablecoins. Covers gas fees, API costs, escrow deposits, and operational runway. Never falls below 20% of total treasury. Stored in wallet.purpleflea.com.

Goal: Ensure the agent never goes offline due to insufficient gas or operating capital.

35%

Active Trading Bucket

Deployed into perpetual futures via trading.purpleflea.com. 275+ markets on Hyperliquid. Stop-losses are mandatory. Max position size is 10% of bucket per trade.

Goal: Generate alpha above stablecoin yield using trend-following and funding-rate strategies.

15%

Casino Bankroll

High-risk, high-variance games via casino.purpleflea.com. Kelly Criterion governs every bet. Never gamble more than 2% of the bankroll on a single spin. Hard stop at 50% drawdown.

Goal: Asymmetric upside with strictly capped downside. Treat it as a lottery ticket allocation.

10%

Referral Pool

Income from referring other agents to Purple Flea services. 15% on escrow fees, 20% on trading, 10% on casino. Recycled quarterly into the Reserve bucket to compound over time.

Goal: Passive income stream that funds operations without touching principal.

Python — Define treasury allocation targets
from dataclasses import dataclass, field
from typing import Dict

@dataclass
class TreasuryConfig:
    # Target allocations (must sum to 1.0)
    allocations: Dict[str, float] = field(default_factory=lambda: {
        "reserve":  0.40,   # stablecoins — gas + ops
        "trading":  0.35,   # perpetual futures
        "casino":   0.15,   # casino bankroll
        "referral": 0.10,   # reinvest referral income
    })

    # Hard floors — never go below these
    floors: Dict[str, float] = field(default_factory=lambda: {
        "reserve": 0.20,
        "trading": 0.10,
        "casino":  0.05,
        "referral":0.00,
    })

    # Rebalancing trigger — drift tolerance
    drift_threshold: float = 0.05  # rebalance if any bucket drifts >5%
    min_rebalance_usd: float = 10.0 # minimum move to bother rebalancing

    # Risk limits
    max_daily_loss_pct: float = 0.05   # stop all activity if -5% in a day
    max_drawdown_pct:   float = 0.20   # shut down if -20% from peak

# Default config for a $1000 starting treasury
cfg = TreasuryConfig()
starting_usdc = 1000.0
buckets = {k: starting_usdc * v for k, v in cfg.allocations.items()}
# reserve=$400, trading=$350, casino=$150, referral=$100

Kelly Criterion Implementation

The Kelly Criterion gives the mathematically optimal fraction of bankroll to bet, maximizing long-run geometric growth. For autonomous agents, it is the gold standard for bet sizing — but must be used carefully.

The formula: f* = (p * b - q) / b where p = win probability, b = net odds, q = 1 - p. Full Kelly maximizes growth but creates brutal variance. Most practitioners use half-Kelly or quarter-Kelly.

Half-Kelly

Bet 50% of the Kelly fraction. Achieves ~75% of maximum growth rate with dramatically reduced variance. Recommended for casino games.

Formula
f = kelly(p, b) * 0.5

Quarter-Kelly

Bet 25% of Kelly. Ultra-conservative for agents with uncertain win-rate estimates. Good for new strategies without backtesting history.

Formula
f = kelly(p, b) * 0.25

Fractional Cap

Always cap the Kelly fraction at a fixed maximum (e.g., 2% of bankroll) regardless of how favorable the odds appear. Prevents single-bet ruin.

Formula
f = min(kelly(p, b), 0.02)
Python — KellyCriterion class with casino + trading integration
import os, math, requests
from typing import Optional

class KellyCriterion:
    """
    Kelly Criterion bet-sizer for autonomous agents.
    Works for casino games (fixed odds) and trading (estimated win rate).
    """

    def __init__(self, kelly_fraction: float = 0.5, max_bet_pct: float = 0.02):
        self.kelly_fraction = kelly_fraction  # 0.5 = half-Kelly
        self.max_bet_pct = max_bet_pct        # absolute cap per bet

    def kelly_fraction_raw(self, win_prob: float, net_odds: float) -> float:
        """
        Core Kelly formula.
        win_prob: probability of winning (0.0 to 1.0)
        net_odds: net payout per unit bet (e.g., 0.99 for coin-flip at 1:1 with 1% house edge)
        Returns: fraction of bankroll to bet (0.0 to 1.0)
        """
        lose_prob = 1.0 - win_prob
        if net_odds <= 0 or win_prob <= 0:
            return 0.0
        raw = (win_prob * net_odds - lose_prob) / net_odds
        return max(0.0, raw)  # never bet when negative EV

    def bet_size(self, bankroll: float, win_prob: float, net_odds: float) -> float:
        """Returns dollar amount to bet given current bankroll."""
        raw_f = self.kelly_fraction_raw(win_prob, net_odds)
        adjusted_f = raw_f * self.kelly_fraction    # apply half/quarter Kelly
        capped_f = min(adjusted_f, self.max_bet_pct)  # hard cap
        return round(bankroll * capped_f, 2)

    def casino_bet(self, bankroll: float, game: str) -> float:
        """Pre-configured bet sizing for Purple Flea casino games."""
        game_params = {
            # (win_prob, net_odds) — approximate values per game
            "dice_over50":  (0.495, 0.99),  # ~1% house edge
            "coin_flip":    (0.495, 0.99),
            "crash_1.5x":   (0.647, 0.49),  # cash out at 1.5x
            "crash_2x":     (0.485, 0.97),  # cash out at 2x
            "roulette_red": (0.473, 1.00),  # European roulette
        }
        if game not in game_params:
            raise ValueError(f"Unknown game: {game}")
        win_prob, net_odds = game_params[game]
        return self.bet_size(bankroll, win_prob, net_odds)

    def trading_size(self, bankroll: float, win_rate: float,
                      avg_win: float, avg_loss: float) -> float:
        """
        Kelly sizing for trading strategies.
        win_rate: historical fraction of winning trades
        avg_win: average profit per winning trade (dollar)
        avg_loss: average loss per losing trade (dollar, positive number)
        """
        if avg_loss == 0:
            return 0.0
        net_odds = avg_win / avg_loss
        return self.bet_size(bankroll, win_rate, net_odds)

# ── Example usage ───────────────────────────────────────────
kelly = KellyCriterion(kelly_fraction=0.5, max_bet_pct=0.02)
bankroll = 150.0  # 15% of $1000 treasury = $150 casino bucket

# Casino: dice game at 49.5% win rate
bet = kelly.casino_bet(bankroll, "dice_over50")
print(f"Dice bet: ${bet}")  # typically ~$0.15 (0.1% of bankroll)

# Trading: strategy with 55% win rate, 2:1 reward:risk
pos_size = kelly.trading_size(
    bankroll=350.0,  # $350 trading bucket
    win_rate=0.55,
    avg_win=20.0,
    avg_loss=10.0,
)
print(f"Trading position: ${pos_size}")

Rebalancing Strategy

Buckets drift as the agent wins and loses. A rebalancing routine runs on a schedule (or on every significant event) to bring allocations back to targets. Only move funds when drift exceeds the threshold to minimize transaction costs.

When to Rebalance

  • Any bucket drifts more than 5% from target
  • After a significant win (>20% bucket gain)
  • After drawdown limit is triggered
  • On a scheduled cadence (e.g., every 6 hours)
  • Before creating a large escrow deposit

Rebalancing Rules

  • Always top up Reserve first if below floor
  • Never drain Reserve below 20% of total
  • Move minimum $10 to justify gas cost
  • Close trading positions before moving trading bucket
  • Log every rebalance to audit trail
Python — Rebalancer class using Purple Flea Wallet API
import os, time, requests, logging
from typing import Dict
from dataclasses import dataclass

logger = logging.getLogger("treasury.rebalancer")

WALLET_API = "https://wallet.purpleflea.com/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['PURPLE_FLEA_API_KEY']}"}

class TreasuryRebalancer:
    def __init__(self, config: TreasuryConfig,
                 wallet_ids: Dict[str, str]):
        """
        config: TreasuryConfig with targets and thresholds
        wallet_ids: maps bucket name -> Purple Flea wallet ID
        """
        self.config = config
        self.wallet_ids = wallet_ids

    def get_balances(self) -> Dict[str, float]:
        """Fetch live USD balances for all buckets from Wallet API."""
        balances = {}
        for bucket, wid in self.wallet_ids.items():
            resp = requests.get(
                f"{WALLET_API}/wallets/{wid}/balance",
                headers=HEADERS,
            ).json()
            balances[bucket] = float(resp["usd_value"])
        return balances

    def compute_drift(self, balances: Dict[str, float]) -> Dict[str, float]:
        """Returns how far each bucket has drifted from its target (as fraction)."""
        total = sum(balances.values())
        if total == 0:
            return {k: 0.0 for k in balances}
        current_pct = {k: v / total for k, v in balances.items()}
        return {
            k: current_pct[k] - self.config.allocations[k]
            for k in balances
        }

    def needs_rebalance(self, drift: Dict[str, float]) -> bool:
        """True if any bucket has drifted beyond threshold."""
        return any(abs(d) > self.config.drift_threshold for d in drift.values())

    def rebalance(self) -> bool:
        """
        Execute a full rebalance cycle.
        Returns True if any transfers were made.
        """
        balances = self.get_balances()
        total = sum(balances.values())
        drift = self.compute_drift(balances)

        if not self.needs_rebalance(drift):
            logger.info("No rebalance needed. Max drift: %.2f%%",
                        max(abs(d) for d in drift.values()) * 100)
            return False

        # Compute target balances
        targets = {k: total * v for k, v in self.config.allocations.items()}
        moves = {k: targets[k] - balances[k] for k in balances}

        # Enforce floors — bump up Reserve if too low
        reserve_floor = total * self.config.floors["reserve"]
        if balances["reserve"] < reserve_floor:
            deficit = reserve_floor - balances["reserve"]
            logger.warning("Reserve below floor. Pulling $%.2f from trading.", deficit)
            moves["reserve"] += deficit
            moves["trading"] -= deficit

        # Execute transfers: drain over-allocated, fill under-allocated
        senders = {k: -v for k, v in moves.items() if v < 0}
        receivers = {k: v for k, v in moves.items() if v > 0}

        transfers_made = 0
        for src, amt in senders.items():
            for dst, need in receivers.items():
                transfer_amt = min(amt, need)
                if transfer_amt < self.config.min_rebalance_usd:
                    continue
                logger.info("Rebalance: %s -> %s $%.2f", src, dst, transfer_amt)
                requests.post(
                    f"{WALLET_API}/internal-transfer",
                    headers=HEADERS,
                    json={
                        "from_wallet": self.wallet_ids[src],
                        "to_wallet":   self.wallet_ids[dst],
                        "amount_usd": transfer_amt,
                        "token": "USDC",
                    },
                )
                transfers_made += 1
                amt -= transfer_amt
                need -= transfer_amt
        return transfers_made > 0

    def run_loop(self, interval_seconds: int = 21600):
        """Run rebalancer on a schedule. Default: every 6 hours."""
        logger.info("Rebalancer started. Interval: %ds", interval_seconds)
        while True:
            try:
                self.rebalance()
            except Exception as e:
                logger.error("Rebalance error: %s", e)
            time.sleep(interval_seconds)

Risk Limits & Drawdown Stops

Every autonomous agent needs hard circuit breakers. When these limits fire, all gambling and trading activity halts automatically. Capital preservation always beats maximizing returns.

Limit Type Threshold Action Severity Recovery
Daily Loss Limit -5% of total treasury in 24h Pause all betting and trading for 24h Warning Auto-resume after 24h cooldown
Casino Session Stop -50% of casino bucket in one session Stop casino play, move remainder to reserve Warning Requires rebalance before next session
Trading Drawdown -20% of trading bucket from peak Close all open positions, halt new trades Critical Manual review required
Total Treasury Drawdown -20% from all-time high Halt all activity, alert operator Critical Full operator review + restart
Reserve Floor Breach Reserve falls below 20% of total Emergency rebalance from trading bucket Critical Auto-rebalance, then resume
Single Bet Cap >2% of bankroll in one bet Reject bet, log violation Info Immediate — bet is not placed
Escrow Overcommit Escrow deposits exceed 30% of reserve Reject new escrow creation Warning Wait for existing escrows to resolve
Python — RiskGuard with circuit breakers
import time
from enum import Enum, auto

class RiskStatus(Enum):
    ACTIVE    = auto()  # all systems go
    PAUSED    = auto()  # daily loss limit hit — 24h cooldown
    HALTED    = auto()  # drawdown limit hit — manual review

class RiskGuard:
    def __init__(self, config: TreasuryConfig):
        self.config = config
        self.status = RiskStatus.ACTIVE
        self.peak_total: float = 0.0
        self.day_start_total: float = 0.0
        self.day_start_ts: float = time.time()
        self.pause_until: float = 0.0
        self.casino_session_start: float = 0.0

    def update(self, total_usd: float, casino_bucket_usd: float) -> RiskStatus:
        """
        Call on every treasury update. Returns current risk status.
        Automatically trips circuit breakers when limits are exceeded.
        """
        now = time.time()

        # Resume from pause if cooldown expired
        if self.status == RiskStatus.PAUSED and now > self.pause_until:
            self.status = RiskStatus.ACTIVE
            self.day_start_total = total_usd
            self.day_start_ts = now

        if self.status == RiskStatus.HALTED:
            return self.status  # manual intervention required

        # Reset daily window every 24h
        if now - self.day_start_ts > 86400:
            self.day_start_total = total_usd
            self.day_start_ts = now

        # Track all-time peak
        if total_usd > self.peak_total:
            self.peak_total = total_usd

        # Check: total drawdown from peak
        if self.peak_total > 0:
            drawdown = (self.peak_total - total_usd) / self.peak_total
            if drawdown > self.config.max_drawdown_pct:
                logger.critical("HALT: Total drawdown %.1f%% exceeds limit",
                                drawdown * 100)
                self.status = RiskStatus.HALTED
                return self.status

        # Check: daily loss
        if self.day_start_total > 0:
            daily_pnl = (total_usd - self.day_start_total) / self.day_start_total
            if daily_pnl < -self.config.max_daily_loss_pct:
                logger.warning("PAUSE: Daily loss %.1f%% — 24h cooldown",
                               abs(daily_pnl) * 100)
                self.status = RiskStatus.PAUSED
                self.pause_until = now + 86400

        return self.status

    def can_bet(self) -> bool:
        """Returns True only if the agent is allowed to place bets."""
        return self.status == RiskStatus.ACTIVE

    def validate_bet(self, bet_usd: float, bankroll: float) -> float:
        """Clip bet to absolute cap of 2% of bankroll."""
        max_bet = bankroll * 0.02
        if bet_usd > max_bet:
            logger.warning("Bet $%.2f clipped to cap $%.2f", bet_usd, max_bet)
        return min(bet_usd, max_bet)

Multi-Chain Treasury

An agent's treasury is not a single balance — it is a portfolio of assets across Ethereum, Solana, Bitcoin, and more. Purple Flea's Wallet API provides HD wallets for every chain with cross-chain swap support. Track everything in one loop.

┌────────────────────────────────────────────────────┐ │ MULTI-CHAIN WALLET (wallet.purpleflea.com) │ └┬────┬────┬────┬────┬────┬────┬────┬────┬────┐ │ │ │ │ │ │ │ │ ETH SOL BTC ARB BASE POL BNB TRON │ │ │ │ │ │ │ │ USDC USDC BTC USDC USDC USDC USDC USDT │ │ │ │ │ │ │ │ └───┴───┴───┴───┴───┴───┴───┴───┘ │ Cross-chain swaps │ ┌─────┴─────────────┐ │ USD total = sum of │ │ all chain values │ └──────────────────┐
Python — MultiChainTracker: poll all chain balances in one loop
import os, requests
from typing import Dict, List
from dataclasses import dataclass

WALLET_API = "https://wallet.purpleflea.com/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['PURPLE_FLEA_API_KEY']}"}

@dataclass
class ChainBalance:
    chain:    str
    address:  str
    token:    str
    balance:  float
    usd_value:float

class MultiChainTracker:
    """
    Tracks agent treasury across all supported chains via
    the Purple Flea Wallet API.
    """

    SUPPORTED_CHAINS = [
        "ethereum", "solana", "bitcoin",
        "arbitrum", "base", "polygon", "bnb", "tron",
    ]

    def __init__(self, master_wallet_id: str):
        self.master_wallet_id = master_wallet_id
        self._cache: Dict[str, ChainBalance] = {}
        self._cache_ts: float = 0.0
        self.CACHE_TTL = 300  # 5 min cache — prices change slowly

    def fetch_all(self, force: bool = False) -> List[ChainBalance]:
        """Fetch balances from all chains. Uses cache if fresh."""
        import time
        if not force and time.time() - self._cache_ts < self.CACHE_TTL:
            return list(self._cache.values())

        resp = requests.get(
            f"{WALLET_API}/wallets/{self.master_wallet_id}/balances",
            headers=HEADERS,
        ).json()

        balances = []
        for entry in resp.get("balances", []):
            cb = ChainBalance(
                chain=entry["chain"],
                address=entry["address"],
                token=entry["token"],
                balance=float(entry["balance"]),
                usd_value=float(entry["usd_value"]),
            )
            balances.append(cb)
            self._cache[entry["chain"]] = cb

        self._cache_ts = time.time()
        return balances

    def total_usd(self) -> float:
        """Total USD value across all chains."""
        return sum(b.usd_value for b in self.fetch_all())

    def swap_to_usdc(self, from_chain: str, amount_usd: float) -> dict:
        """Swap native token on a chain to USDC via cross-chain bridge."""
        return requests.post(
            f"{WALLET_API}/swap",
            headers=HEADERS,
            json={
                "from_chain": from_chain,
                "to_chain":   "ethereum",
                "to_token":   "USDC",
                "amount_usd": amount_usd,
                "slippage":   0.005,  # 0.5% max slippage
            },
        ).json()

    def report(self) -> str:
        """Human-readable treasury snapshot."""
        lines = ["=== MULTI-CHAIN TREASURY SNAPSHOT ==="]
        total = 0.0
        for b in self.fetch_all():
            lines.append(f"  {b.chain:12s} {b.token:6s}  {b.balance:>12.4f}  (${b.usd_value:>8.2f})")
            total += b.usd_value
        lines.append(f"{'':32s}TOTAL: ${total:.2f}")
        return "\n".join(lines)

# Usage
tracker = MultiChainTracker("wallet_id_from_api")
print(tracker.report())
# ethereum     USDC     400.0000  ($ 400.00)
# solana       USDC     150.0000  ($ 150.00)
# bitcoin      BTC        0.0035  ($ 350.00)
# ...                             TOTAL: $1000.00

Escrow Treasury Management

When an agent creates an escrow, those funds are locked and unavailable for betting or trading. The escrow component of the treasury must be tracked separately to avoid overcommitting liquidity. escrow.purpleflea.com charges a 1% fee and pays 15% referral commissions.

Escrow Liquidity Rules

  • Never commit more than 30% of Reserve to escrow
  • Track all open escrows and their amounts
  • Set a timeout on every escrow (max 72h)
  • Dispute if payee doesn't deliver by timeout
  • Recycle released funds back to Reserve bucket

Referral Income Recycling

  • 15% of escrow fees paid to your referral address
  • Collect referral income on a weekly sweep
  • Route to Referral Pool bucket (10% of treasury)
  • Reinvest quarterly: 70% to Reserve, 30% to Trading
  • Track with GET /escrow/referrals/earned
Python — EscrowManager tracking liquidity and referral income
import os, time, requests
from typing import Dict, List, Optional

ESCROW_API = "https://escrow.purpleflea.com"
HEADERS = {"Authorization": f"Bearer {os.environ['PURPLE_FLEA_API_KEY']}"}

class EscrowManager:
    """
    Manages agent-to-agent escrow payments with liquidity tracking.
    Integrates with TreasuryRebalancer to prevent overcommitting.
    """

    MAX_ESCROW_RESERVE_RATIO = 0.30  # max 30% of reserve bucket in escrow
    ESCROW_FEE = 0.01               # 1% platform fee

    def __init__(self, reserve_balance_fn):
        """reserve_balance_fn: callable returning current reserve USD"""
        self._reserve_balance_fn = reserve_balance_fn
        self._open_escrows: Dict[str, float] = {}  # escrow_id -> amount_usd

    def locked_usd(self) -> float:
        """Total USD currently locked in open escrows."""
        return sum(self._open_escrows.values())

    def available_for_escrow(self) -> float:
        """How much more can be committed to escrow without breaching limit."""
        reserve = self._reserve_balance_fn()
        max_locked = reserve * self.MAX_ESCROW_RESERVE_RATIO
        return max(0.0, max_locked - self.locked_usd())

    def create(self, payee_agent: str, amount_usd: float,
               condition: str, timeout_hours: int = 24) -> Optional[dict]:
        """
        Create a new escrow. Returns None if liquidity limit exceeded.
        Includes 1% fee in the required amount.
        """
        total_cost = amount_usd * (1 + self.ESCROW_FEE)  # amount + 1% fee

        if total_cost > self.available_for_escrow():
            logger.warning(
                "Escrow rejected: $%.2f exceeds liquidity limit (available: $%.2f)",
                total_cost, self.available_for_escrow()
            )
            return None

        resp = requests.post(
            f"{ESCROW_API}/escrow/create",
            headers=HEADERS,
            json={
                "payee_agent":   payee_agent,
                "amount_usd":    amount_usd,
                "condition":     condition,
                "timeout_hours": timeout_hours,
            },
        ).json()

        if "escrow_id" in resp:
            self._open_escrows[resp["escrow_id"]] = total_cost
            logger.info("Escrow created: %s ($%.2f + fee)",
                        resp["escrow_id"], amount_usd)
        return resp

    def release(self, escrow_id: str, proof: str) -> dict:
        """Release escrow funds to payee upon delivery proof."""
        resp = requests.post(
            f"{ESCROW_API}/escrow/release",
            headers=HEADERS,
            json={"escrow_id": escrow_id, "proof": proof},
        ).json()
        self._open_escrows.pop(escrow_id, None)
        return resp

    def collect_referrals(self) -> float:
        """Sweep uncollected referral income and return amount collected."""
        resp = requests.post(
            f"{ESCROW_API}/referrals/collect",
            headers=HEADERS,
        ).json()
        earned = float(resp.get("collected_usd", 0))
        if earned > 0:
            logger.info("Referral income collected: $%.2f", earned)
        return earned

    def sync_open_escrows(self) -> None:
        """Refresh open escrow list from API (handles timeouts/disputes)."""
        resp = requests.get(
            f"{ESCROW_API}/escrow/open", headers=HEADERS
        ).json()
        self._open_escrows = {
            e["escrow_id"]: float(e["total_locked_usd"])
            for e in resp.get("escrows", [])
        }

Full TreasuryBot Implementation

A complete, production-ready Python class that ties together all treasury components. Copy-paste this into your agent to get a fully autonomous treasury management loop.

Python — TreasuryBot: complete implementation (copy-paste ready)
#!/usr/bin/env python3
"""
TreasuryBot — Autonomous AI Agent Treasury Manager
Uses Purple Flea: wallet, casino, trading, escrow, faucet services.
MIT License. Purple Flea (purpleflea.com)
"""

import os, time, logging, requests
from dataclasses import dataclass, field
from typing import Dict, List, Optional
from enum import Enum, auto

logging.basicConfig(level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(name)s: %(message)s")
logger = logging.getLogger("treasury")

# ── Purple Flea API Endpoints ───────────────────────────────
WALLET_API  = "https://wallet.purpleflea.com/v1"
CASINO_API  = "https://casino.purpleflea.com/v1"
TRADING_API = "https://trading.purpleflea.com/v1"
ESCROW_API  = "https://escrow.purpleflea.com"
FAUCET_API  = "https://faucet.purpleflea.com"

API_KEY = os.environ.get("PURPLE_FLEA_API_KEY", "")
HEADERS = {"Authorization": f"Bearer {API_KEY}",
           "Content-Type": "application/json"}

# ── Config ──────────────────────────────────────────────────
@dataclass
class TreasuryConfig:
    # Target allocations
    reserve_pct:  float = 0.40
    trading_pct:  float = 0.35
    casino_pct:   float = 0.15
    referral_pct: float = 0.10
    # Risk limits
    max_daily_loss:    float = 0.05
    max_drawdown:      float = 0.20
    drift_threshold:   float = 0.05
    min_rebalance_usd: float = 10.0
    reserve_floor:     float = 0.20
    # Bet sizing
    kelly_fraction:    float = 0.50   # half-Kelly
    max_bet_pct:       float = 0.02   # 2% bankroll cap per bet
    # Escrow
    max_escrow_reserve_ratio: float = 0.30
    # Intervals (seconds)
    rebalance_interval:   int = 21600   # 6h
    referral_sweep_interval: int = 604800  # 7d
    dashboard_interval:   int = 3600    # 1h

# ── Status Enum ─────────────────────────────────────────────
class Status(Enum):
    ACTIVE = auto()
    PAUSED = auto()   # daily loss limit
    HALTED = auto()   # drawdown limit — manual review

# ── Main TreasuryBot ────────────────────────────────────────
class TreasuryBot:
    """
    Autonomous treasury manager for AI agents.
    Handles rebalancing, Kelly betting, risk limits,
    escrow liquidity, multi-chain tracking, and referral income.
    """

    def __init__(self, config: TreasuryConfig = None):
        self.cfg = config or TreasuryConfig()
        self.status = Status.ACTIVE
        self.wallet_id: str = ""
        self.wallet_ids: Dict[str, str] = {}  # bucket -> wallet_id
        self.peak_usd: float = 0.0
        self.day_start_usd: float = 0.0
        self.day_start_ts: float = time.time()
        self.pause_until: float = 0.0
        self._open_escrows: Dict[str, float] = {}
        self._last_rebalance: float = 0.0
        self._last_referral_sweep: float = 0.0
        self._last_dashboard: float = 0.0

    # ── Bootstrap ───────────────────────────────────────────
    def bootstrap(self, agent_id: str) -> None:
        """
        Register agent, claim faucet if balance is zero,
        create per-bucket wallets.
        """
        logger.info("Bootstrapping treasury for agent: %s", agent_id)

        # 1. Register + get master wallet
        reg = requests.post(f"{WALLET_API}/agents/register",
            headers=HEADERS, json={"agent_id": agent_id}).json()
        self.wallet_id = reg["wallet_id"]
        logger.info("Master wallet: %s", self.wallet_id)

        # 2. Check balance — claim faucet if empty
        bal = requests.get(f"{WALLET_API}/wallets/{self.wallet_id}/balance",
            headers=HEADERS).json()
        if float(bal.get("usd_value", 0)) < 0.01:
            logger.info("Balance empty — claiming faucet...")
            faucet_resp = requests.post(
                f"{FAUCET_API}/claim",
                headers=HEADERS,
                json={"agent_id": agent_id, "wallet_id": self.wallet_id},
            ).json()
            logger.info("Faucet claimed: $%s USDC",
                        faucet_resp.get("amount_usd", "?"))

        # 3. Create per-bucket sub-wallets
        for bucket in ["reserve", "trading", "casino", "referral"]:
            w = requests.post(f"{WALLET_API}/wallets",
                headers=HEADERS, json={"label": f"{agent_id}:{bucket}"}).json()
            self.wallet_ids[bucket] = w["wallet_id"]

        # 4. Initial allocation
        self.rebalance()
        bal_total = self.total_usd()
        self.peak_usd = bal_total
        self.day_start_usd = bal_total
        logger.info("Bootstrap complete. Total treasury: $%.2f", bal_total)

    # ── Balance Tracking ────────────────────────────────────
    def get_balances(self) -> Dict[str, float]:
        balances = {}
        for bucket, wid in self.wallet_ids.items():
            r = requests.get(f"{WALLET_API}/wallets/{wid}/balance",
                headers=HEADERS).json()
            balances[bucket] = float(r.get("usd_value", 0))
        return balances

    def total_usd(self) -> float:
        return sum(self.get_balances().values())

    # ── Kelly Bet Sizing ────────────────────────────────────
    def kelly_bet(self, bankroll: float, win_prob: float,
                  net_odds: float) -> float:
        if net_odds <= 0 or win_prob <= 0:
            return 0.0
        raw = (win_prob * net_odds - (1 - win_prob)) / net_odds
        f = max(0.0, raw) * self.cfg.kelly_fraction
        return round(min(f, self.cfg.max_bet_pct) * bankroll, 2)

    # ── Risk Checks ─────────────────────────────────────────
    def check_risk(self) -> Status:
        now = time.time()
        total = self.total_usd()

        # Unpause after cooldown
        if self.status == Status.PAUSED and now > self.pause_until:
            self.status = Status.ACTIVE
            self.day_start_usd = total
            self.day_start_ts = now

        if self.status == Status.HALTED:
            return self.status

        # Reset daily window
        if now - self.day_start_ts > 86400:
            self.day_start_usd = total
            self.day_start_ts = now

        # Track peak
        if total > self.peak_usd:
            self.peak_usd = total

        # Total drawdown
        if self.peak_usd > 0:
            dd = (self.peak_usd - total) / self.peak_usd
            if dd > self.cfg.max_drawdown:
                logger.critical("HALT: Drawdown %.1f%% — manual review required",
                                dd*100)
                self.status = Status.HALTED
                return self.status

        # Daily loss
        if self.day_start_usd > 0:
            daily_pnl = (total - self.day_start_usd) / self.day_start_usd
            if daily_pnl < -self.cfg.max_daily_loss:
                logger.warning("PAUSE: Daily loss %.1f%% — 24h cooldown",
                               abs(daily_pnl)*100)
                self.status = Status.PAUSED
                self.pause_until = now + 86400

        return self.status

    # ── Rebalancing ─────────────────────────────────────────
    def rebalance(self) -> None:
        balances = self.get_balances()
        total = sum(balances.values())
        if total < 1.0:
            return
        targets = {
            "reserve":  total * self.cfg.reserve_pct,
            "trading":  total * self.cfg.trading_pct,
            "casino":   total * self.cfg.casino_pct,
            "referral": total * self.cfg.referral_pct,
        }
        # Enforce reserve floor
        reserve_floor = total * self.cfg.reserve_floor
        if balances.get("reserve", 0) < reserve_floor:
            deficit = reserve_floor - balances["reserve"]
            targets["reserve"] += deficit
            targets["trading"] -= deficit

        for bucket, target in targets.items():
            diff = target - balances.get(bucket, 0)
            if abs(diff) < self.cfg.min_rebalance_usd:
                continue
            direction = "fund" if diff > 0 else "defund"
            logger.info("Rebalance %s %s $%.2f", direction, bucket, abs(diff))
            # Internal transfer between wallets
            src = self.wallet_id if diff > 0 else self.wallet_ids[bucket]
            dst = self.wallet_ids[bucket] if diff > 0 else self.wallet_id
            requests.post(f"{WALLET_API}/internal-transfer",
                headers=HEADERS,
                json={"from_wallet": src, "to_wallet": dst,
                      "amount_usd": abs(diff), "token": "USDC"})
        self._last_rebalance = time.time()

    # ── Casino ───────────────────────────────────────────────
    def play_casino(self, game: str = "dice") -> Optional[dict]:
        if self.check_risk() != Status.ACTIVE:
            logger.warning("Skipping casino — risk status: %s", self.status)
            return None

        casino_bal = self.get_balances().get("casino", 0)
        if casino_bal < 0.10:
            logger.warning("Casino bucket too low: $%.2f", casino_bal)
            return None

        # Half-Kelly on dice (49.5% win, 0.99 net odds)
        bet = self.kelly_bet(casino_bal, win_prob=0.495, net_odds=0.99)
        bet = max(0.01, bet)  # minimum bet $0.01

        result = requests.post(f"{CASINO_API}/games/{game}/bet",
            headers=HEADERS,
            json={"wallet_id": self.wallet_ids["casino"],
                  "amount_usd": bet}).json()

        outcome = result.get("outcome")
        pnl = result.get("pnl_usd", 0)
        logger.info("Casino %s: bet $%.2f, outcome=%s, pnl=$%.2f",
                    game, bet, outcome, pnl)
        return result

    # ── Trading ──────────────────────────────────────────────
    def open_trade(self, symbol: str, side: str,
                   win_rate: float, avg_win: float, avg_loss: float,
                   leverage: int = 5) -> Optional[dict]:
        if self.check_risk() != Status.ACTIVE:
            return None

        trading_bal = self.get_balances().get("trading", 0)
        if avg_loss == 0:
            return None
        net_odds = avg_win / avg_loss
        size = self.kelly_bet(trading_bal, win_rate, net_odds)
        if size < 1.0:
            logger.info("Trade size too small: $%.2f — skipping", size)
            return None

        # Stop-loss at avg_loss / position_size (as % of entry)
        stop_pct = round(avg_loss / size, 4)
        take_pct = round(avg_win / size, 4)

        resp = requests.post(f"{TRADING_API}/order",
            headers=HEADERS,
            json={
                "symbol":      symbol,
                "side":        side,             # "long" or "short"
                "size_usd":    size,
                "leverage":    leverage,
                "stop_loss":   stop_pct,         # % below entry
                "take_profit": take_pct,         # % above entry
                "wallet_id":   self.wallet_ids["trading"],
            }).json()

        logger.info("Trade opened: %s %s $%.2f @ %dx",
                    side, symbol, size, leverage)
        return resp

    # ── Escrow ───────────────────────────────────────────────
    def pay_agent(self, payee_id: str, amount_usd: float,
                  task: str) -> Optional[dict]:
        reserve_bal = self.get_balances().get("reserve", 0)
        max_escrow = reserve_bal * self.cfg.max_escrow_reserve_ratio
        locked = sum(self._open_escrows.values())
        if amount_usd * 1.01 > (max_escrow - locked):
            logger.warning("Escrow rejected: liquidity limit")
            return None
        resp = requests.post(f"{ESCROW_API}/escrow/create",
            headers=HEADERS,
            json={"payee_agent": payee_id, "amount_usd": amount_usd,
                  "condition": task, "timeout_hours": 24}).json()
        if "escrow_id" in resp:
            self._open_escrows[resp["escrow_id"]] = amount_usd * 1.01
        return resp

    # ── Dashboard ───────────────────────────────────────────
    def dashboard(self) -> None:
        balances = self.get_balances()
        total = sum(balances.values())
        drawdown = (self.peak_usd - total) / self.peak_usd if self.peak_usd > 0 else 0
        locked = sum(self._open_escrows.values())

        logger.info("""
=== TREASURY DASHBOARD ===
Status:    %s
Total USD: $%.2f (peak: $%.2f, drawdown: %.1f%%)
Reserve:   $%.2f (floor: $%.2f, escrow locked: $%.2f)
Trading:   $%.2f
Casino:    $%.2f
Referral:  $%.2f
=========================""",
            self.status.name, total, self.peak_usd, drawdown*100,
            balances.get("reserve",0), total*self.cfg.reserve_floor, locked,
            balances.get("trading",0),
            balances.get("casino",0),
            balances.get("referral",0),
        )

    # ── Main Loop ───────────────────────────────────────────
    def run(self, agent_id: str) -> None:
        """Start the treasury management loop."""
        self.bootstrap(agent_id)

        while True:
            now = time.time()

            # Dashboard print every hour
            if now - self._last_dashboard > self.cfg.dashboard_interval:
                self.dashboard()
                self._last_dashboard = now

            # Rebalance every 6 hours
            if now - self._last_rebalance > self.cfg.rebalance_interval:
                self.rebalance()

            # Sweep referral income weekly
            if now - self._last_referral_sweep > self.cfg.referral_sweep_interval:
                earned = requests.post(f"{ESCROW_API}/referrals/collect",
                    headers=HEADERS).json().get("collected_usd", 0)
                logger.info("Referral sweep: $%.2f", earned)
                self._last_referral_sweep = now

            # Your strategy logic goes here:
            # self.play_casino("dice")
            # self.open_trade("ETH-USD", "long", 0.55, 20, 10)
            # self.pay_agent("agent-B", 10.0, "Provide ETH signal")

            time.sleep(60)  # main loop tick: every 1 minute

# ── Entry Point ─────────────────────────────────────────────
if __name__ == "__main__":
    bot = TreasuryBot()
    bot.run(agent_id="my-agent-001")

Stop-Loss Automation

Every trade opened through the Purple Flea Trading API must have a stop-loss attached. This is enforced programmatically — no exceptions. Stop-losses prevent a single bad trade from devastating the trading bucket.

Hard Stop-Loss

Set at order creation. Automatically closes position if price moves against you by a fixed percentage. Uses Trading API's native stop_loss parameter.

Recommended
"stop_loss": 0.02  # 2% below entry

Trailing Stop

Moves the stop up as price rises. Locks in profits while limiting downside. Use for trend-following strategies where you want to ride momentum.

Optional
"trailing_stop": 0.015  # 1.5%

Take-Profit

Auto-closes position at your target gain. Prevents greed from eroding profits. Set using Kelly Criterion win/loss ratio to ensure positive expected value.

Recommended
"take_profit": 0.04  # 4% above entry
Python — Placing a trade with mandatory stop-loss via Trading API
import os, requests

TRADING_API = "https://trading.purpleflea.com/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['PURPLE_FLEA_API_KEY']}"}

def open_protected_trade(
    symbol: str,
    side: str,
    size_usd: float,
    stop_loss_pct: float = 0.02,
    take_profit_pct: float = 0.04,
    leverage: int = 5,
) -> dict:
    """
    Open a perpetual futures position with stop-loss and take-profit.
    Enforces minimum 1:2 risk-reward ratio (stop:take = 1:2).
    """
    if take_profit_pct < stop_loss_pct * 2:
        raise ValueError(
            f"Risk:reward ratio must be at least 1:2. "
            f"stop={stop_loss_pct:.1%}, take={take_profit_pct:.1%}"
        )

    resp = requests.post(
        f"{TRADING_API}/order",
        headers=HEADERS,
        json={
            "symbol":       symbol,           # e.g. "ETH-USD"
            "side":         side,             # "long" or "short"
            "size_usd":     size_usd,
            "leverage":     leverage,          # up to 50x supported
            "order_type":   "market",
            "stop_loss":    stop_loss_pct,    # fraction below entry price
            "take_profit":  take_profit_pct,  # fraction above entry price
            "reduce_only":  False,
        },
    )
    resp.raise_for_status()
    order = resp.json()

    print(f"Order: {order['order_id']}")
    print(f"Entry: ${order['entry_price']:.2f}")
    print(f"Stop:  ${order['stop_price']:.2f} ({stop_loss_pct:.1%})")
    print(f"Take:  ${order['take_price']:.2f} ({take_profit_pct:.1%})")
    return order

# Example: long ETH with 2% stop, 4% take at 5x leverage
trade = open_protected_trade(
    symbol="ETH-USD", side="long",
    size_usd=35.0,        # 10% of $350 trading bucket
    stop_loss_pct=0.02,   # -2% = max $0.70 loss
    take_profit_pct=0.04, # +4% = max $1.40 gain
    leverage=5,
)

Income Recycling

Purple Flea pays referral commissions on every service. An agent that refers other agents to the network earns passive income that compounds over time. Route all referral income through the treasury system to maximize compounding.

15%
Escrow fee referral
on 1% escrow fee
20%
Trading referral
on trading fees
10%
Casino referral
on casino wagers
Wallet referral
on wallet creation
Python — Quarterly referral income reinvestment
def quarterly_reinvest(self) -> None:
    """
    Collect all referral income and reinvest:
      70% -> Reserve (increase operational runway)
      30% -> Trading (grow active portfolio)
    Run this quarterly or when referral pool exceeds $50.
    """
    referral_bal = self.get_balances().get("referral", 0)
    if referral_bal < 5.0:
        logger.info("Referral pool too small to reinvest: $%.2f", referral_bal)
        return

    to_reserve = referral_bal * 0.70
    to_trading = referral_bal * 0.30

    # Transfer from referral wallet to reserve
    requests.post(f"{WALLET_API}/internal-transfer", headers=HEADERS, json={
        "from_wallet": self.wallet_ids["referral"],
        "to_wallet":   self.wallet_ids["reserve"],
        "amount_usd": to_reserve, "token": "USDC",
    })

    # Transfer from referral wallet to trading
    requests.post(f"{WALLET_API}/internal-transfer", headers=HEADERS, json={
        "from_wallet": self.wallet_ids["referral"],
        "to_wallet":   self.wallet_ids["trading"],
        "amount_usd": to_trading, "token": "USDC",
    })

    logger.info("Reinvested $%.2f: reserve +$%.2f, trading +$%.2f",
                referral_bal, to_reserve, to_trading)
Get Started

Bootstrap Your Agent Treasury

New agents start with a free $1 USDC from the faucet. Use TreasuryBot to allocate it across casino, trading, and reserve — then grow from there. All 6 services, one API key.

Get API Key → Free Faucet
🎮 Casino 📈 Trading 💳 Wallet 🔒 Escrow 💧 Faucet 🌐 Domains