Strategy

FX and Stablecoin Arbitrage for AI Agents

Forex principles applied to on-chain markets: USDC/USDT/DAI/FRAX spreads, cross-chain arb, Curve pool mechanics, depegging events, and a Python FXArbitrageAgent that runs autonomously.

March 6, 2026 25 min read By Purple Flea Research

Forex Principles in On-Chain Markets

Traditional foreign exchange (FX) trading involves exploiting price differences between currency pairs across multiple venues: banks, ECNs, and swap markets. Stablecoin markets on decentralized exchanges are structurally identical — and for AI agents, they offer significant advantages over traditional FX:

$180B+
Daily stablecoin transfer volume (2026)
2–15 bps
Typical USDC/USDT spread across venues
8–40 bps
Cross-chain stablecoin spread during bridge congestion
< 0.01%
Curve 3pool fee — lowest in DeFi for stable-stable swaps

FX Arbitrage Fundamentals Applied to Stablecoins

The three classic FX arbitrage strategies translate directly to stablecoin markets:

1. Triangular Arbitrage: In FX, this exploits mispricings between three currency pairs (e.g., USD/EUR, EUR/GBP, GBP/USD). In DeFi: USDC → USDT → DAI → USDC across different pools. If the round-trip converts 1.000 USDC into 1.003 USDC after fees and gas, that 0.3% is pure arbitrage profit.

2. Spatial Arbitrage: The same stablecoin trades at different prices on different exchanges. USDT might be 0.9992 on Uniswap and 0.9998 on Curve — buy low on one, sell high on the other.

3. Statistical Arbitrage (Pairs Trading): Two correlated stablecoins (e.g., USDC and EURC) maintain a known long-run ratio. When the ratio deviates significantly from its mean, the agent takes a position expecting mean reversion. This works especially well for pegged assets with explicit redemption mechanisms that enforce the peg over time.

USDC / USDT / DAI / FRAX Spread Dynamics

The major stablecoins are not interchangeable at par in DeFi markets. Each carries a distinct risk profile, liquidity profile, and market microstructure — and these differences create persistent spreads that disciplined agents can exploit.

Risk Premia by Stablecoin Type

Stablecoin Peg Mechanism Risk Category Typical Spread vs USDC Arb Edge
USDC Fiat-backed (Circle) Lowest Baseline Used as reference leg
USDT Fiat-backed (Tether) Low-Medium 0–8 bps discount Persistent discount = free carry
DAI Overcollateralized CDP Medium -5 to +15 bps Spreads widen on high Maker activity
FRAX Fractional-algorithmic Medium-High -10 to +20 bps Largest spread swings = highest alpha
LUSD ETH-backed, 110% CR Medium 0 to +100 bps Redemption mechanism enforces lower bound
crvUSD Curve LLAMMA CDP Medium -5 to +10 bps Tight — but Curve arbitrage fees are near-zero
EURC Euro-backed (Circle) Low Tracks EUR/USD, 50–200 bps FX carry + EUR rate differentials

Spread Visualization: Typical On-Chain Spreads

Stablecoin Spread Range vs USDC (basis points)

USDT
0–8 bps
DAI
5–15 bps
FRAX
10–20 bps
LUSD
0–100 bps
EURC
50–200 bps

When Spreads Widen

Understanding what causes spread widening is essential for timing entries:

Cross-Chain Stablecoin Arbitrage

The same stablecoin trades at different prices on different blockchain networks. USDC on Ethereum might trade at $1.0003 while USDC on Polygon trades at $0.9997 — a 6 basis point spread purely due to local supply and demand imbalances.

Mechanics of Cross-Chain Arb

The complete cycle of a cross-chain stablecoin arbitrage trade:

  1. Detect: Monitor stablecoin prices across target chains (Ethereum, Arbitrum, Optimism, Base, Polygon, Solana)
  2. Size: Estimate the available arb after bridge fees and gas costs on both sides
  3. Bridge: Transfer the cheap-chain stablecoin to the expensive-chain via canonical bridge or third-party bridge (Stargate, Across, Hop)
  4. Sell: Sell on the expensive-chain venue
  5. Repeat: Hold the proceeds on the target chain for the next opportunity or bridge back
Bridge Protocol Fee Speed Best For Arb Viability
Across Protocol 3–8 bps 1–3 min USDC, ETH, WBTC High
Stargate (LayerZero) 6–15 bps 2–5 min USDC, USDT, wide chain coverage Medium
Hop Protocol 5–12 bps 3–10 min ETH L1↔L2 stablecoins Medium
Circle CCTP ~0 (gas only) 15–20 min USDC (native cross-chain) Very High (large)
Canonical L2 Bridge ~0 (gas only) 7 days (withdraw) Optimistic rollups Too slow for arb

Circle CCTP advantage: Circle's Cross-Chain Transfer Protocol allows USDC to be burned on the source chain and minted on the destination chain natively — no wrapped tokens, no liquidity pools, no counterparty risk. For large USDC arb (>$100K), CCTP often offers the best fee structure despite the 15–20 minute settlement time.

Profitability Threshold Calculation

An agent must clear three cost layers for a cross-chain arb to be profitable:

crosschain_arb_threshold.py — break-even analysis Python
def compute_breakeven_spread(
    bridge_fee_bps: float,
    source_gas_usd: float,
    dest_gas_usd: float,
    dex_fee_bps: float,
    amount_usd: float,
    price_risk_bps: float = 5,  # spread movement risk during bridge time
) -> float:
    """
    Returns the minimum observed spread (in bps) required for
    a cross-chain stablecoin arb to be profitable after all costs.
    """
    # Convert fixed costs to basis points
    gas_bps = (source_gas_usd + dest_gas_usd) / amount_usd * 10000

    # Total cost in bps
    total_cost_bps = bridge_fee_bps + gas_bps + dex_fee_bps + price_risk_bps

    return total_cost_bps


# Example: USDC arb Ethereum → Arbitrum via Across
breakeven = compute_breakeven_spread(
    bridge_fee_bps=6,       # Across fee
    source_gas_usd=4.50,   # Ethereum send gas
    dest_gas_usd=0.05,     # Arbitrum receive gas
    dex_fee_bps=1,         # Curve 3pool fee
    amount_usd=50000,      # trade size
    price_risk_bps=4,      # 2-min bridge, stablecoin vol
)
print(f"Breakeven spread: {breakeven:.1f} bps")
# Output: Breakeven spread: 12.0 bps
# Minimum observable spread to trade: ~12 bps

# Scale sensitivity: how much does size matter?
for size in [10_000, 50_000, 200_000, 1_000_000]:
    be = compute_breakeven_spread(6, 4.50, 0.05, 1, size, 4)
    print(f"  ${size:>10,} → breakeven {be:.1f} bps")

# Output:
#   $    10,000 → breakeven 57.5 bps
#   $    50,000 → breakeven 12.0 bps
#   $   200,000 → breakeven  8.3 bps
#   $ 1,000,000 → breakeven  7.2 bps

The scale sensitivity analysis shows why cross-chain stablecoin arb is primarily a large-capital game on Ethereum mainnet. Smaller agents on L2 chains (where gas is 50–200x cheaper) can run profitable arb at much smaller sizes.

Curve Stable Pools: Mechanics and Edges

Curve Finance is the dominant stablecoin exchange protocol, processing tens of billions of dollars in volume monthly. Understanding Curve's mechanics is essential for any stablecoin arb agent.

The StableSwap Invariant

Curve uses a hybrid AMM invariant that behaves like a constant-sum market maker (x + y = k) near equilibrium and like a constant-product market maker (xy = k) at extreme imbalances. The formula:

A * n^n * ∑x_i + D = A * D * n^n + D^(n+1) / (n^n * ∏x_i)

Where A is the amplification coefficient. A high A (e.g., 200) creates extremely flat pricing near the equilibrium point — this is why Curve offers near-zero slippage for balanced stablecoin swaps. As the pool becomes imbalanced, slippage increases rapidly, signaling an arbitrage opportunity back toward balance.

Identifying Curve Arbitrage Opportunities

Curve arb opportunities arise when a pool's composition deviates significantly from equal weighting. For a 3pool (USDC, USDT, DAI), the balanced state is 1/3 each. When the pool is 45% USDT, 30% USDC, 25% DAI, USDT is cheap (pool wants to get rid of it) and USDC/DAI are expensive.

curve_arb_scanner.py — detect imbalanced Curve pools Python
import httpx import asyncio from dataclasses import dataclass from typing import List, Dict # Curve pool state from their API CURVE_API = "https://api.curve.fi/v1/getPools/ethereum/main" @dataclass class CurvePoolState: pool_address: str name: str coins: List[str] balances_usd: List[float] amplification: int fee_bps: float daily_volume_usd: float @property def total_tvl(self) -> float: return sum(self.balances_usd) @property def weights(self) -> List[float]: total = self.total_tvl if total == 0: return [0.0] * len(self.balances_usd) return [b / total for b in self.balances_usd] def imbalance_score(self) -> float: """ Max deviation of any coin from equal weight. 0 = perfectly balanced, 1 = completely imbalanced. """ n = len(self.coins) equal = 1 / n return max(abs(w - equal) for w in self.weights) def cheap_coins(self, threshold: float = 0.05) -> List[str]: """Coins with weight > equal + threshold (pool wants to sell).""" n = len(self.coins) equal = 1 / n return [ self.coins[i] for i, w in enumerate(self.weights) if w > equal + threshold ] def expensive_coins(self, threshold: float = 0.05) -> List[str]: """Coins with weight < equal - threshold (pool wants to receive).""" n = len(self.coins) equal = 1 / n return [ self.coins[i] for i, w in enumerate(self.weights) if w < equal - threshold ] async def scan_curve_opportunities(min_tvl: float = 1_000_000) -> List[Dict]: """Scan Curve pools for arb opportunities based on pool imbalance.""" async with httpx.AsyncClient() as client: resp = await client.get(CURVE_API, timeout=10) resp.raise_for_status() data = resp.json() opportunities = [] for pool_data in data.get("data", {}).get("poolData", []): try: coins = [c["symbol"] for c in pool_data["coins"]] balances = [float(b) for b in pool_data.get("usdTotal", [])] if not balances or len(balances) != len(coins): continue pool = CurvePoolState( pool_address=pool_data["address"], name=pool_data["name"], coins=coins, balances_usd=balances, amplification=int(pool_data.get("amplificationCoefficient", 100)), fee_bps=float(pool_data.get("fee", 4)), daily_volume_usd=float(pool_data.get("volumeUSD", 0)), ) if pool.total_tvl() < min_tvl: continue imbalance = pool.imbalance_score() if imbalance > 0.05: # > 5% deviation from equal weight opportunities.append({ "pool": pool.name, "address": pool.pool_address, "imbalance": imbalance, "tvl": pool.total_tvl(), "cheap": pool.cheap_coins(), "expensive": pool.expensive_coins(), "fee_bps": pool.fee_bps, }) except (KeyError, ValueError): continue # Sort by imbalance descending opportunities.sort(key=lambda x: x["imbalance"], reverse=True) return opportunities

Depegging Events: Risk and Opportunity

Stablecoin depegs are the highest-variance events in DeFi. A depeg occurs when a stablecoin trades significantly below or above its target price, typically $1.00. For trading agents, depegs represent simultaneous risk (if holding the affected stablecoin) and opportunity (if positioned correctly to arb the recovery or capture the discount).

Historical Depeg Taxonomy

Event Stablecoin Depth Duration Recovery Pattern Agent Strategy
UST/LUNA collapse (2022) UST $0.00 (full) Permanent No recovery Avoid algorithmic with no hard peg
USDC banking scare (2023) USDC $0.877 ~3 days Full recovery Buy USDC on curve, hold to recovery
FRAX algorithmic phase FRAX $0.96 2–4 weeks Partial, then new collateral Short-term arb only
DAI peg stress (2022) DAI $0.94 48 hours Full via PSM arbitrage Classic PSM arb
stETH depeg (2022) stETH $0.94 vs ETH ~3 months Full at Merge Long-horizon arb

Depeg Response Framework for Agents

Agents need a clear decision tree when a depeg alert fires:

  1. Classify the depeg type: Is this a liquidity event (temporary, likely recovers) or a solvency event (structural, may not recover)?
  2. Check the redemption mechanism: Does the stablecoin have a hard redemption path (e.g., USDC at Circle, DAI via Maker PSM)? If yes, arb the discount aggressively up to the redemption cost.
  3. Estimate recovery time: Liquidity depegs typically recover within 1–72 hours. Solvency depegs may never recover. Agent risk appetite must match the expected holding period.
  4. Size the position: Never allocate more than 20% of capital to a depeg play. The reward-to-risk is only favorable when the discount exceeds redemption/bridge costs plus a margin of safety.
  5. Set hard stop-loss: If the depeg deepens beyond your entry point by more than 5%, exit. A deepening depeg is evidence of a structural problem, not a temporary liquidity event.

Critical risk: Never hold a purely algorithmic stablecoin (one without overcollateralization or fiat backing) through a deep depeg. The death spiral mechanic — where protocol tokens are minted to defend the peg, diluting holders and further destroying confidence — can take a stablecoin to zero in hours. The LUNA/UST collapse demonstrated this conclusively in May 2022.

Stablecoin Carry Trade

The traditional FX carry trade borrows in a low-yield currency to invest in a high-yield currency, capturing the interest rate differential. The stablecoin equivalent: borrow low-rate stable assets, deploy into high-rate stable protocols, capture the spread.

The On-Chain Yield Curve

Stablecoin yields vary significantly across protocols and change with market conditions:

Protocol / Asset Typical APY Range Risk Liquidity
AAVE USDC supply 2–8% Low Instant withdrawal
Compound USDT supply 3–10% Low Instant withdrawal
Curve 3pool LP 2–5% + CRV Medium (IL is low for stables) Minutes to exit
Convex Finance (boosted Curve) 5–15% Medium (CVX/CRV price risk) Minutes to exit
Yearn vault (USDC) 4–12% Medium (strategy risk) Instant to hours
Perp protocol funding 10–50%+ (variable) High (funding flips) Instant exit

Carry Trade Implementation

A simple stablecoin carry agent:

  1. Monitor AAVE/Compound borrow rates for USDC (cost of capital)
  2. Monitor Yearn/Convex supply rates for USDT (potential yield)
  3. When spread exceeds gas cost + 2% margin of safety: borrow USDC on AAVE, swap to USDT on Curve, deposit into highest-yield USDT vault
  4. Monitor continuously — unwind if the spread compresses or borrow rates spike

Key risk — rate volatility: DeFi interest rates can change dramatically within hours. A carry position that yields 6% net can flip negative if borrow rates spike during a high-demand period. Agents must monitor rate changes continuously and have automated unwind triggers — not just entry logic.

Python FXArbitrageAgent

The following is a complete FXArbitrageAgent that orchestrates multi-venue stablecoin monitoring, opportunity detection, and execution through Purple Flea's trading infrastructure.

fx_arbitrage_agent.py — full production agent Python
import asyncio
import httpx
import logging
from dataclasses import dataclass, field
from typing import Dict, List, Optional, Tuple
from datetime import datetime, timedelta
from enum import Enum
import time

logger = logging.getLogger("FXArbitrageAgent")

# Purple Flea infrastructure endpoints
PF_TRADING_URL = "https://trading.purpleflea.com/api/v1"
PF_WALLET_URL = "https://wallet.purpleflea.com/api/v1"

# Stablecoin price feeds (Chainlink aggregators via RPC or price API)
PRICE_FEEDS = {
    "USDC": "https://api.coingecko.com/api/v3/simple/price?ids=usd-coin&vs_currencies=usd",
    "USDT": "https://api.coingecko.com/api/v3/simple/price?ids=tether&vs_currencies=usd",
    "DAI":  "https://api.coingecko.com/api/v3/simple/price?ids=dai&vs_currencies=usd",
    "FRAX": "https://api.coingecko.com/api/v3/simple/price?ids=frax&vs_currencies=usd",
}

COINGECKO_ID_MAP = {
    "USDC": "usd-coin",
    "USDT": "tether",
    "DAI":  "dai",
    "FRAX": "frax",
}


class ArbType(Enum):
    SPATIAL = "spatial"          # same chain, different venues
    TRIANGULAR = "triangular"    # A→B→C→A
    CROSS_CHAIN = "cross_chain"  # same coin, different chains
    DEPEG = "depeg"              # stablecoin trading at discount


@dataclass
class StablecoinPrice:
    symbol: str
    price: float
    venue: str
    chain: str
    timestamp: float = field(default_factory=time.time)

    @property
    def depeg_bps(self) -> float:
        """Deviation from $1.00 in basis points."""
        return (1.0 - self.price) * 10000


@dataclass
class ArbOpportunity:
    arb_type: ArbType
    buy_venue: str
    sell_venue: str
    buy_price: float
    sell_price: float
    spread_bps: float
    input_token: str
    output_token: str
    estimated_profit_bps: float
    max_size_usd: float
    confidence: float  # 0-1

    @property
    def is_viable(self) -> bool:
        return self.estimated_profit_bps > 5 and self.confidence > 0.7


@dataclass
class AgentConfig:
    api_key: str
    wallet_id: str
    max_position_usd: float = 25_000
    min_profit_bps: float = 5      # 0.05% minimum net profit
    depeg_threshold_bps: float = 50 # alert on 0.5% depeg
    scan_interval_sec: float = 15
    max_daily_trades: int = 500
    chains: List[str] = field(default_factory=lambda: ["ethereum", "arbitrum", "optimism"])
    stablecoins: List[str] = field(default_factory=lambda: ["USDC", "USDT", "DAI", "FRAX"])


class FXArbitrageAgent:
    """
    Autonomous stablecoin FX arbitrage agent for AI-native financial infrastructure.

    Monitors USDC/USDT/DAI/FRAX prices across multiple venues and chains,
    detects spatial, triangular, and cross-chain arbitrage opportunities,
    and executes via Purple Flea's Trading API with automated risk controls.

    Register and fund via https://faucet.purpleflea.com (free $1 USDC to start).
    """

    def __init__(self, config: AgentConfig):
        self.config = config
        self._client: Optional[httpx.AsyncClient] = None
        self._price_cache: Dict[str, StablecoinPrice] = {}
        self._trade_count_today: int = 0
        self._pnl_today_usd: float = 0.0
        self._last_reset: datetime = datetime.utcnow()
        self._running: bool = False

    async def _client_get(self) -> httpx.AsyncClient:
        if self._client is None:
            self._client = httpx.AsyncClient(timeout=8.0)
        return self._client

    async def _fetch_prices(self) -> Dict[str, float]:
        """Fetch current stablecoin prices from CoinGecko."""
        client = await self._client_get()
        ids = ",".join(COINGECKO_ID_MAP.values())
        url = f"https://api.coingecko.com/api/v3/simple/price?ids={ids}&vs_currencies=usd"
        try:
            resp = await client.get(url)
            resp.raise_for_status()
            data = resp.json()
            prices = {}
            for symbol, cg_id in COINGECKO_ID_MAP.items():
                if cg_id in data:
                    prices[symbol] = data[cg_id]["usd"]
            return prices
        except Exception as e:
            logger.warning(f"Price fetch failed: {e}")
            return {}

    def _compute_fee_bps(self, path_hops: int) -> float:
        """Estimate all-in fee for a trade path."""
        curve_fee_bps = 4  # 0.04% Curve 3pool fee
        gas_bps = 8         # ~8 bps gas at typical sizes
        return (curve_fee_bps + gas_bps) * path_hops

    def _detect_spatial_arb(self, prices: Dict[str, float]) -> List[ArbOpportunity]:
        """Find pairs trading at spreads exceeding cost of swap."""
        opportunities = []
        symbols = list(prices.keys())
        for i, s1 in enumerate(symbols):
            for s2 in symbols[i+1:]:
                p1, p2 = prices[s1], prices[s2]
                spread_bps = abs(p1 - p2) * 10000
                fee_bps = self._compute_fee_bps(1)
                net_profit_bps = spread_bps - fee_bps

                if net_profit_bps >= self.config.min_profit_bps:
                    buy_sym = s1 if p1 < p2 else s2
                    sell_sym = s2 if p1 < p2 else s1
                    buy_price = min(p1, p2)
                    sell_price = max(p1, p2)

                    # Confidence based on spread magnitude and data freshness
                    confidence = min(1.0, net_profit_bps / 20)

                    opportunities.append(ArbOpportunity(
                        arb_type=ArbType.SPATIAL,
                        buy_venue="Curve",
                        sell_venue="Curve",
                        buy_price=buy_price,
                        sell_price=sell_price,
                        spread_bps=spread_bps,
                        input_token=buy_sym,
                        output_token=sell_sym,
                        estimated_profit_bps=net_profit_bps,
                        max_size_usd=min(self.config.max_position_usd, 100_000),
                        confidence=confidence,
                    ))
        return opportunities

    def _detect_depegs(self, prices: Dict[str, float]) -> List[ArbOpportunity]:
        """Detect stablecoins trading significantly below $1."""
        opportunities = []
        for symbol, price in prices.items():
            depeg_bps = (1.0 - price) * 10000
            if depeg_bps > self.config.depeg_threshold_bps:
                logger.warning(f"DEPEG ALERT: {symbol} = ${price:.6f} ({depeg_bps:.0f} bps off peg)")
                # Arb: buy cheap stablecoin, redeem or sell once peg recovers
                net_profit_bps = depeg_bps - self._compute_fee_bps(1) - 20  # extra safety margin
                if net_profit_bps > 0:
                    opportunities.append(ArbOpportunity(
                        arb_type=ArbType.DEPEG,
                        buy_venue="Curve",
                        sell_venue="Redemption",
                        buy_price=price,
                        sell_price=1.0,
                        spread_bps=depeg_bps,
                        input_token="USDC",
                        output_token=symbol,
                        estimated_profit_bps=net_profit_bps,
                        max_size_usd=min(self.config.max_position_usd * 0.2, 5_000),
                        confidence=0.6 if depeg_bps > 200 else 0.8,
                    ))
        return opportunities

    async def _execute_arb(self, opp: ArbOpportunity) -> bool:
        """Submit arb trade via Purple Flea Trading API."""
        client = await self._client_get()
        size = min(opp.max_size_usd, self.config.max_position_usd)

        logger.info(
            f"Executing {opp.arb_type.value} arb: "
            f"{opp.input_token}→{opp.output_token} at {opp.buy_venue}, "
            f"est. profit={opp.estimated_profit_bps:.1f}bps, size=${size:,.0f}"
        )

        try:
            resp = await client.post(
                f"{PF_TRADING_URL}/swap",
                headers={"X-API-Key": self.config.api_key},
                json={
                    "from_token": opp.input_token,
                    "to_token": opp.output_token,
                    "amount_usd": size,
                    "wallet_id": self.config.wallet_id,
                    "slippage_bps": int(opp.spread_bps * 0.5),
                    "mev_protection": "flashbots",
                    "label": f"fx_arb_{opp.arb_type.value}",
                },
            )
            resp.raise_for_status()
            result = resp.json()
            logger.info(f"Trade executed: tx={result.get('tx_hash', 'pending')}")
            self._trade_count_today += 1
            actual_profit = result.get("realized_pnl_usd", 0)
            self._pnl_today_usd += actual_profit
            return True
        except Exception as e:
            logger.error(f"Trade execution failed: {e}")
            return False

    def _reset_daily_stats_if_needed(self) -> None:
        now = datetime.utcnow()
        if (now - self._last_reset).days >= 1:
            logger.info(f"Daily stats: trades={self._trade_count_today}, PnL=${self._pnl_today_usd:.2f}")
            self._trade_count_today = 0
            self._pnl_today_usd = 0
            self._last_reset = now

    async def _scan_cycle(self) -> None:
        """One full scan-detect-execute cycle."""
        self._reset_daily_stats_if_needed()

        if self._trade_count_today >= self.config.max_daily_trades:
            logger.warning("Daily trade limit reached, pausing.")
            return

        prices = await self._fetch_prices()
        if not prices:
            return

        # Detect all opportunity types
        opps = self._detect_spatial_arb(prices)
        opps += self._detect_depegs(prices)

        # Filter viable opportunities, sort by expected profit
        viable = [o for o in opps if o.is_viable]
        viable.sort(key=lambda o: o.estimated_profit_bps, reverse=True)

        if viable:
            logger.info(f"Found {len(viable)} viable arb opportunities")
            # Execute top opportunity only (conservative: one trade per cycle)
            await self._execute_arb(viable[0])
        else:
            logger.debug(f"No viable arb. Prices: {prices}")

    async def run(self) -> None:
        """Main agent loop. Runs indefinitely until stopped."""
        logger.info("FXArbitrageAgent starting. Powered by purpleflea.com")
        self._running = True
        while self._running:
            try:
                await self._scan_cycle()
            except Exception as e:
                logger.error(f"Scan cycle error: {e}")
            await asyncio.sleep(self.config.scan_interval_sec)

    async def stop(self) -> None:
        logger.info(f"Agent stopped. Daily PnL: ${self._pnl_today_usd:.2f}")
        self._running = False
        if self._client:
            await self._client.aclose()


# Entry point
async def main():
    logging.basicConfig(
        level=logging.INFO,
        format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
    )
    config = AgentConfig(
        api_key="YOUR_PURPLE_FLEA_API_KEY",
        wallet_id="YOUR_WALLET_ID",
        max_position_usd=10_000,
        min_profit_bps=8,
        scan_interval_sec=15,
    )
    agent = FXArbitrageAgent(config)
    try:
        await agent.run()
    except KeyboardInterrupt:
        await agent.stop()

if __name__ == "__main__":
    asyncio.run(main())

Purple Flea Integration

The FXArbitrageAgent above is designed to work natively with Purple Flea's financial infrastructure stack. Here is how each Purple Flea service plays a role:

Configuration Recommendations by Strategy

Strategy min_profit_bps max_position_usd scan_interval_sec Notes
Conservative (new agent) 15 1,000 30 Start here; use faucet seed capital
Standard 8 10,000 15 Good balance of frequency and safety
Aggressive 5 50,000 5 Requires robust MEV protection and gas monitoring
Depeg specialist 20 (net) 5,000 10 Depeg events are rare but high yield when they occur

Start Your FX Arbitrage Agent

Get free USDC from the faucet, connect your wallet, and run the FXArbitrageAgent against live markets. Purple Flea handles the infrastructure — you capture the spread.

Key Takeaways