Running Autonomous AI Agents with Curupira
How Curupira orchestrates autonomous AI trading agents — from research to execution. Architecture overview, agent lifecycle, monitoring, and how to build your own agent on the platform.
The Problem: From Research to Production
You’ve built a trading strategy. It backtests well. You’ve validated it out-of-sample. Now what?
For most retail quant traders, the answer is a painful multi-week process: write execution code, handle order management, build monitoring, set up alerts, manage API connections, handle errors, deploy to a server, and pray it doesn’t crash at 3 AM during a volatile move.
Curupira exists to collapse that gap. It’s the infrastructure layer that takes a strategy specification and runs it as an autonomous agent — handling execution, risk management, monitoring, and recovery so you can focus on research.
Architecture Overview
Curupira is built around three core principles: agent autonomy, hard risk boundaries, and full observability.
from dataclasses import dataclass, field
from enum import Enum
class AgentState(Enum):
INITIALIZING = "initializing"
RESEARCHING = "researching"
WAITING = "waiting"
ANALYZING = "analyzing"
EXECUTING = "executing"
MONITORING = "monitoring"
ERROR = "error"
SHUTDOWN = "shutdown"
@dataclass
class AgentConfig:
"""Configuration for an Curupira trading agent."""
# Identity
agent_id: str
strategy_name: str
description: str
# Market configuration
exchange: str # 'hyperliquid', 'binance', etc.
symbols: list[str] # ['BTC-USD', 'ETH-USD']
timeframe: str = '5m' # Primary analysis timeframe
# Strategy specification
strategy_module: str = '' # Python module path
analysis_type: str = 'quantitative' # 'quantitative', 'llm', 'hybrid'
analysis_interval_seconds: int = 300 # How often to run analysis
# Risk boundaries (hard limits — agent cannot override)
max_position_pct: float = 0.02 # Max 2% of capital per position
max_total_exposure_pct: float = 0.06 # Max 6% total exposure
max_daily_loss_pct: float = 0.03 # Stop trading at 3% daily loss
max_daily_trades: int = 10 # Maximum trades per day
max_drawdown_pct: float = 0.10 # Shutdown at 10% drawdown
# Operational
heartbeat_interval: int = 60 # Health check every 60s
log_level: str = 'INFO'
alert_channels: list[str] = field(default_factory=lambda: ['discord'])
The Agent Lifecycle
Every Curupira agent follows the same lifecycle, whether it’s running a pure quantitative strategy or an LLM-assisted hybrid:
import asyncio
import logging
from datetime import datetime
class CurupiraAgent:
"""
Base class for all Curupira trading agents.
Handles lifecycle, risk management, and monitoring.
Strategy logic is injected via the strategy module.
"""
def __init__(self, config: AgentConfig):
self.config = config
self.state = AgentState.INITIALIZING
self.logger = logging.getLogger(f"agent.{config.agent_id}")
self.daily_trades = 0
self.daily_pnl = 0.0
self.positions = {}
self.start_time = datetime.utcnow()
async def run(self):
"""Main agent loop."""
self.state = AgentState.INITIALIZING
await self._initialize()
self.logger.info(f"Agent {self.config.agent_id} started: {self.config.strategy_name}")
while self.state != AgentState.SHUTDOWN:
try:
# 1. Check risk limits before doing anything
if not self._check_risk_limits():
self.state = AgentState.WAITING
await asyncio.sleep(self.config.analysis_interval_seconds)
continue
# 2. Fetch market data
self.state = AgentState.RESEARCHING
market_data = await self._fetch_market_data()
# 3. Run strategy analysis
self.state = AgentState.ANALYZING
signal = await self._analyze(market_data)
# 4. Execute if signal is actionable
if signal and signal.get('action') != 'HOLD':
self.state = AgentState.EXECUTING
await self._execute(signal)
# 5. Monitor existing positions
self.state = AgentState.MONITORING
await self._monitor_positions()
# 6. Wait for next cycle
self.state = AgentState.WAITING
await asyncio.sleep(self.config.analysis_interval_seconds)
except Exception as e:
self.state = AgentState.ERROR
self.logger.error(f"Agent error: {e}")
await self._handle_error(e)
def _check_risk_limits(self) -> bool:
"""Hard risk limits that the agent cannot override."""
if self.daily_trades >= self.config.max_daily_trades:
self.logger.warning("Daily trade limit reached")
return False
if self.daily_pnl <= -self.config.max_daily_loss_pct:
self.logger.warning("Daily loss limit reached")
return False
total_exposure = sum(
abs(p.get('size_pct', 0)) for p in self.positions.values()
)
if total_exposure >= self.config.max_total_exposure_pct:
self.logger.warning("Max exposure reached")
return False
return True
async def _initialize(self):
"""Connect to exchange, load strategy, verify configuration."""
pass # Implementation specific to exchange
async def _fetch_market_data(self):
"""Fetch OHLCV, orderbook, funding, OI data."""
pass # Implementation specific to exchange
async def _analyze(self, market_data) -> dict:
"""Run strategy analysis — delegated to strategy module."""
pass # Loaded from config.strategy_module
async def _execute(self, signal: dict):
"""Execute trade with position sizing and order management."""
pass # Implementation specific to exchange
async def _monitor_positions(self):
"""Check stops, targets, and position health."""
pass
async def _handle_error(self, error: Exception):
"""Error recovery: alert, retry, or shutdown."""
pass
The Risk Boundary System
The most critical design decision in Curupira: risk limits are enforced at the infrastructure level, not the strategy level. The agent’s strategy code cannot disable, modify, or bypass risk checks.
@dataclass
class RiskBoundary:
"""
Immutable risk boundaries set at agent creation.
These cannot be modified by the agent at runtime.
Changes require a full redeploy with manual approval.
"""
# Position limits
max_position_size_usd: float
max_total_exposure_usd: float
max_positions: int = 3
# Loss limits
max_loss_per_trade_usd: float
max_daily_loss_usd: float
max_weekly_loss_usd: float
max_total_drawdown_usd: float
# Activity limits
max_trades_per_hour: int = 5
max_trades_per_day: int = 10
min_time_between_trades_seconds: int = 60
# Circuit breakers
emergency_shutdown_loss_pct: float = 0.15
max_slippage_pct: float = 0.5
max_consecutive_losses: int = 8
def validate_order(self, order: dict, portfolio: dict) -> tuple[bool, str]:
"""
Validate a proposed order against all risk boundaries.
Returns (approved, reason).
"""
# Check position size
if order['size_usd'] > self.max_position_size_usd:
return False, f"Position size ${order['size_usd']:.0f} exceeds limit ${self.max_position_size_usd:.0f}"
# Check daily loss
if portfolio['daily_pnl'] <= -self.max_daily_loss_usd:
return False, f"Daily loss limit reached: ${portfolio['daily_pnl']:.0f}"
# Check consecutive losses
if portfolio['consecutive_losses'] >= self.max_consecutive_losses:
return False, f"Consecutive loss limit reached: {portfolio['consecutive_losses']}"
# Check trade frequency
if portfolio['trades_today'] >= self.max_trades_per_day:
return False, f"Daily trade limit reached: {portfolio['trades_today']}"
return True, "Approved"
This separation matters because autonomous agents, by definition, make decisions without human intervention. If the agent can modify its own risk limits, you don’t have risk management — you have suggestions. Curupira treats risk boundaries as immutable infrastructure, not parameters.
Monitoring and Observability
An autonomous agent that you can’t observe is a liability. Curupira provides three layers of observability:
1. Real-time state streaming. Every state transition, every analysis result, every order is logged and streamed to a monitoring dashboard.
2. Heartbeat monitoring. Agents send heartbeats every 60 seconds. If a heartbeat is missed, the monitoring system alerts and can trigger automatic position closure.
3. Decision audit trail. Every decision the agent makes — including decisions not to trade — is logged with the full context that informed it. This is critical for debugging and for improving strategies.
@dataclass
class DecisionLog:
"""Complete audit trail for every agent decision."""
timestamp: str
agent_id: str
state: str
market_context: dict # What the agent saw
signal: dict # What the strategy produced
risk_check: dict # Risk validation result
execution: dict # What actually happened (or why it didn't)
position_after: dict # Portfolio state after decision
latency_ms: float # Total decision latency
Running Your First Agent
Here’s how to deploy a strategy on Curupira. We’ll use the entropy collapse strategy as an example:
# agent_config.yaml
agent_id: "entropy-eurusd-h1"
strategy_name: "Entropy Collapse Volatility Timing"
description: "Shannon entropy collapse detection on EURUSD H1"
exchange: "hyperliquid"
symbols: ["ETH-USD"] # Using ETH as proxy for demo
timeframe: "1h"
strategy_module: "strategies.entropy_collapse"
analysis_type: "quantitative"
analysis_interval_seconds: 3600 # Every hour for H1
# Risk boundaries
max_position_pct: 0.01
max_total_exposure_pct: 0.03
max_daily_loss_pct: 0.02
max_daily_trades: 4
max_drawdown_pct: 0.08
alert_channels: ["discord"]
# strategies/entropy_collapse.py
from curupira.strategy import BaseStrategy
from curupira.signals.entropy import entropy_collapse_signal
class EntropyCollapseStrategy(BaseStrategy):
"""
Entropy collapse strategy adapted for Curupira agent framework.
"""
def __init__(self, lookback=50, threshold=1.8, bins=10):
self.lookback = lookback
self.threshold = threshold
self.bins = bins
async def analyze(self, market_data: dict) -> dict:
df = market_data['ohlcv']
# Compute entropy signal
result = entropy_collapse_signal(
df,
lookback=self.lookback,
entropy_bins=self.bins,
collapse_threshold=self.threshold
)
latest = result.iloc[-1]
if not latest['collapse_signal']:
return {'action': 'HOLD', 'reason': 'No entropy collapse detected'}
# Directional bias from EMA slope
ema_slope = latest.get('ema_slope', 0)
direction = 'LONG' if ema_slope > 0 else 'SHORT'
return {
'action': direction,
'confidence': min(abs(latest['entropy_z']) / 3.0, 1.0),
'stop_loss_atr': 1.5,
'take_profit_atr': 2.0,
'reasoning': f"Entropy z-score: {latest['entropy_z']:.2f}, EMA slope: {ema_slope:.6f}",
}
What’s Next for Curupira
We’re building toward a platform where:
- Strategy development uses our research tools (entropy, Hurst, FVG analysis) as composable building blocks
- Agent deployment is a single command with sensible risk defaults
- Multi-agent coordination allows agents to share information without conflicting positions
- Community strategies can be deployed by anyone, with transparent performance tracking
The goal isn’t to build a black box. It’s to build infrastructure that makes transparent, reproducible, risk-managed autonomous trading accessible to anyone willing to do the research.
For the strategies that run on Curupira, see Entropy Collapse, FVG Magnetism, and our $1.30/Day LLM Agent. For the research philosophy, start with Why We Open-Source Our Quant Research.