Skip to Content

isA Trade

Multi-agent trading system with 4 specialized AI agents for automated market analysis and trade execution.

Overview

isA Trade is a coordinated multi-agent architecture where specialized agents collaborate to make trading decisions. Each agent runs as an independent FastAPI service communicating via NATS messaging.

AgentRole
OrchestratorCentral CIO — coordinates agents, makes final decisions. Runs on port 18790.
Research AgentFundamental analysis, earnings, news sentiment
Technical AgentChart patterns, indicators, signal generation
Risk AgentPortfolio monitoring, position validation, veto power

All four agents run in-process in a single orchestrator via the isA Agent SDK SwarmOrchestrator — there are no separate per-agent HTTP servers.

Quick Start

# Full dev setup (venv + dependencies) make dev-setup cp .env.example .env # add API keys + isA_user connection config # Start infrastructure (no local Postgres — isA_user + isA_Data are external deps) make docker-up # Start the orchestrator (agents run in-process) make run-local # Check health make health-check

Architecture

isA Trade is a pure-stateless agent team (ADR 0001, epic #429) — it owns zero persistent tables. Agents run in-process via the isA Agent SDK’s SwarmOrchestrator and only ever read current state and decide; they never own data. State ownership is split across three planes:

┌─────────────────────────────────────────────────┐ │ Market Data Feeds │ │ yfinance • Finnhub • FRED • CCXT │ └──────────────────────┬──────────────────────────┘ ┌─────────────────────────────────────────────────┐ │ Orchestrator (Port 18790) │ │ Coordinates analysis, makes decisions │ │ (in-cycle memory only — no DB) │ └───────┬──────────────┬──────────────┬───────────┘ ↓ ↓ ↓ ┌──────────────┐ ┌───────────┐ ┌──────────────┐ │ Research │ │ Technical │ │ Risk │ │ Agent │ │ Agent │ │ Agent │ │ Fundamental │ │ Indicators│ │ VETO POWER │ │ Sentiment │ │ Patterns │ │ Portfolio │ │ Earnings │ │ Signals │ │ Limits │ └──────────────┘ └───────────┘ └──────────────┘ │ │ │ └──────────────┼──────────────┘ ┌─────────────────────────────────────────────────┐ │ isA_user (system-of-record) │ │ portfolio • orders • account • audit — Postgres │ ├─────────────────────────────────────────────────┤ │ isA_Data (analytics lakehouse) │ │ OHLCV • features • PnL history — Delta/MinIO │ └─────────────────────────────────────────────────┘

Data ownership (three-plane separation)

PlaneOwnsRole
isA Tradenothing durable (in-cycle memory only)compute / decisions — orchestrator + Research/Technical/Risk agents
isA_userportfolio, orders, account, audittransactional system-of-record, reached via a typed IsaUserClient (httpx + circuit breaker) — never a shared DB
isA_DataOHLCV / features / PnL historyanalytics lakehouse (Delta on MinIO), read via embedded DuckDB (isadata://); edition-gated (bigdata_enabled, off by default on SaaS/lite editions)

isA Trade previously owned a local Postgres trade schema (positions, portfolio snapshots, trades, signals, risk events). That schema — along with its Alembic migrations and docker-compose postgres service — was fully removed as part of epic #429; the trade journal and PnL snapshots are now no-ops on the isA Trade side since isA_user owns them via its execution + broker-sync flows.

Refuse-to-trade on unavailability. Because isA_user is now a hard runtime dependency, IsaUserHaltPolicy continuously probes isA_user /health and drives the Kill Switch: the system halts (never trades on stale or absent state) if isA_user is unreachable, and auto-resumes once it recovers. The execute path returns 503 while halted.

[!NOTE] get_portfolio_history returns not_available until the isA_Data PnL product lands; PnL time-series is analytics, not transactional, so it is deferred to isA_Data rather than re-homed into isA_user.

Trading Cockpit ↔ Mate auth delegation

The Trading Cockpit UI delegates into isA Trade through isA Mate rather than calling the orchestrator directly. All four hops in that delegation chain (Cockpit → Mate → isA Trade → isA_user) share a consistent 300s timeout, and the end-to-end delegation path is verified green. Autonomous/background trading events currently use a lax expiry check on the delegated token — tighter expiry validation is tracked as follow-up work, not yet shipped.

Risk Management

The Risk Agent enforces hard limits that cannot be overridden:

RuleLimit
Max position risk2% of portfolio
Max sector concentration25%
Daily loss circuit breaker3%
Max open positions15
Drawdown circuit breaker10%
Max correlation with existing0.8

Agent Capabilities

Research Agent

  • Financial statement analysis
  • Earnings reports and guidance
  • News sentiment scoring
  • Sector and macro analysis

Technical Agent

  • Chart pattern recognition
  • Technical indicators (RSI, MACD, Bollinger, etc.)
  • Signal generation and regime detection
  • Volume and momentum analysis

Orchestrator

  • Multi-agent coordination and pipeline routing
  • Decision fusion from all agent signals
  • Trade journal and audit logging
  • Session management

Tech Stack

ToolPurpose
Python 3.11+Language
FastAPIOrchestrator HTTP surface
isA Agent SDKAgent framework (in-process SwarmOrchestrator)
isA_userSystem-of-record — portfolio, orders, account, audit (via IsaUserClient)
isA_DataAnalytics lakehouse — OHLCV, features, PnL history (Delta on MinIO, DuckDB reads)
Pandas, NumPy, SciPyData analysis
yfinance, Finnhub, FREDMarket data
CCXTCrypto exchange connectivity

Risk Management & Approvals

isA Trade enforces multiple layers of safety controls that protect the portfolio from catastrophic losses. These controls are hardcoded and non-overridable — no agent, operator, or configuration change can bypass them during live operation.

[!CAUTION] Safety-critical system. The risk management rules described below are enforced at the code level. Disabling or weakening them requires source code changes and should never be done in a production environment.

Live Trading Approval Gate

Before any real money is at risk, the system requires an explicit approval gate. This prevents accidental live trading during development, testing, or misconfiguration.

How it works:

  1. The orchestrator starts with a TRADING_MODE environment variable (paper or live).
  2. When TRADING_MODE=live, the system checks the LIVE_TRADING_GATE environment variable.
  3. If LIVE_TRADING_GATE is not set to exactly approved (case-insensitive), the system silently falls back to paper trading using a simulated PaperBroker.
  4. Even when the gate is approved, a live broker (Alpaca, CCXT, Moomoo, or Polymarket) must also be configured with valid API keys — otherwise the system still falls back to paper mode.

[!WARNING] Double-lock design. Both TRADING_MODE=live AND LIVE_TRADING_GATE=approved must be set for real orders to execute. If either is missing or incorrect, the system uses the paper broker with no real money at risk.

# Paper trading (default — no gate required) TRADING_MODE=paper # Live trading — requires both settings TRADING_MODE=live LIVE_TRADING_GATE=approved ALPACA_API_KEY=your-key-here # or other broker credentials

Risk Agent Veto Power

The Risk Agent acts as Chief Risk Officer (CRO) with absolute veto authority over all trade decisions. This is the most powerful safety mechanism in the system.

Veto protocol:

  • The Risk Agent’s veto is absolute — there is no appeal process and no override mechanism.
  • When a veto is issued, immediate compliance is required with no exceptions.
  • The Orchestrator must respect the veto even if the Research Agent and Technical Agent both strongly recommend the trade.
  • Other agents may request clarification on a veto but cannot challenge the decision.
  • All veto reasons are logged to the audit trail but are not subject to debate.

The Risk Agent vetoes a trade when any of these conditions are true:

ConditionThresholdEffect
Position risk too high> 2% of portfolio per positionVeto with suggested smaller size
Sector over-concentrated> 25% in one sectorVeto
Daily loss limit hit> 3% loss in current dayVeto all new trades
Too many open positions> 15 positionsVeto
Drawdown circuit breaker> 10% from peak equityVeto all trading
High correlation> 0.8 with existing positionsVeto

When a trade is vetoed, the ValidationResult includes:

  • veto: true and a veto_reason explaining which rule was violated
  • A risk_score (0-100) indicating overall risk level
  • suggested_adjustments when applicable (e.g., a smaller position size that would pass)

Circuit Breakers

Circuit breakers are automatic safety triggers managed by the Kill Switch module. When a circuit breaker trips, trading halts immediately without human intervention.

[!CAUTION] Circuit breakers cannot be disabled. They are initialized at startup with the execution engine and run continuously. When triggered, they send critical alerts via the notification system and record the event in Prometheus metrics.

Circuit breaker thresholds:

TriggerThresholdBehavior
Max drawdown10% from peak equityHalt → 5 min cooldown
Daily loss3% of current equityHalt → 5 min cooldown
Order rate limit20 orders per minuteHalt → 5 min cooldown
Consecutive losses5 losing trades in a rowHalt → 5 min cooldown

Since the stateless migration (epic #429), the Kill Switch also halts trading whenever IsaUserHaltPolicy detects isA_user is unreachable — the system-of-record being unavailable is treated as a circuit-breaker condition, not a soft error.

Kill Switch states:

ACTIVE ──→ COOLDOWN ──→ ACTIVE │ ↑ │ (auto-halt: drawdown, daily loss, │ rate limit, consecutive losses) └──→ HALTED ──→ ACTIVE (manual halt) (manual resume)
  • ACTIVE — Normal trading. All orders are processed.
  • COOLDOWN — Automatic halt triggered by a circuit breaker. Trading resumes automatically after a 5-minute cooldown period. No manual intervention required.
  • HALTED — Manual halt triggered by an operator (or emergency close). Requires explicit resume() call to restart trading.

Emergency close: The /api/v1/execute/emergency-close endpoint triggers an immediate HALT, closes all open positions, and sends critical alerts. Use this when you need to exit the market immediately.

Risk Management Summary

RuleLimitEnforced By
Max position risk2% of portfolioRisk Agent (veto)
Max position size10% of portfolioRisk Agent (warning)
Max sector concentration25%Risk Agent (veto)
Daily loss circuit breaker3%Kill Switch (auto-halt) + Risk Agent (veto)
Drawdown circuit breaker10%Kill Switch (auto-halt) + Risk Agent (veto)
Max open positions15Risk Agent (veto)
Max correlation0.8Risk Agent (veto)
Order rate limit20/minKill Switch (auto-halt)
Consecutive losses5Kill Switch (auto-halt)
Position sizingHalf-Kelly criterionRisk Agent (sizing)
Live trading gateLIVE_TRADING_GATE=approvedExecution module (startup)

Monitoring & Alerting

isA Trade exposes Prometheus metrics via a /metrics endpoint and ships alerting rules for production monitoring. Metrics use the isa_trade_* naming prefix and are defined in src/orchestrator/metrics.py.

Key Metrics

MetricTypeDescription
isa_trade_orders_totalCounterTotal orders submitted (labels: side, status, broker)
isa_trade_order_latency_secondsHistogramOrder submission latency by broker
isa_trade_slippage_bpsHistogramOrder slippage in basis points
isa_trade_kill_switch_stateGaugeKill switch state (0=active, 1=halted, 2=cooldown)
isa_trade_portfolio_equityGaugeCurrent portfolio equity in dollars
isa_trade_active_positionsGaugeNumber of active positions
isa_trade_agent_duration_secondsHistogramPer-agent invocation duration (labels: agent)
isa_trade_http_requests_totalCounterHTTP requests by method, path, and status code

Alerting Rules

Alerting rules live in deployment/prometheus/alerts.yml and cover three categories. Validate with promtool check rules deployment/prometheus/alerts.yml.

Kill Switch Alerts

AlertExpressionSeverityDescription
TradingKillSwitchHaltedisa_trade_kill_switch_state == 1CriticalKill switch entered HALTED state. All trading suspended. Manual resume() required.
TradingCircuitBreakerTrippedisa_trade_kill_switch_state != 0WarningKill switch is not ACTIVE. Check logs for the circuit breaker trigger reason.

Risk Alerts

AlertExpressionSeverityDescription
TradingDrawdownExceededDrawdown from 24h peak > 5%CriticalPortfolio drawdown exceeded 5% threshold. Review positions.
TradingDailyLossExceeded24h loss > 2%CriticalPortfolio lost more than 2% in 24 hours. Assess risk exposure.

Availability Alerts

AlertExpressionSeverityDescription
TradingServiceDownup{job="isa-trade"} == 0 for 1mCriticalPrometheus cannot scrape the service. Check pod status.
TradingHighErrorRate5xx rate > 5% over 5mWarningHTTP error rate above threshold. Check application logs.
TradingHighOrderLatencyP95 order latency > 5s over 5mWarningOrder latency degraded. Possible broker or market data issues.

Dashboard Setup

To set up Prometheus scraping, add the isA Trade target to your Prometheus config:

scrape_configs: - job_name: isa-trade scrape_interval: 15s static_configs: - targets: ["localhost:18790"]

Load the alerting rules:

rule_files: - /path/to/isA_Trade/deployment/prometheus/alerts.yml

Monitor key metrics during operation:

# Watch live metrics watch -n 5 'curl -s http://localhost:18790/metrics | grep -E "isa_trade_(http_requests|agent_invocation|order_latency)"'

Load Testing

isA Trade includes a Locust-based load testing framework for benchmarking API performance under concurrent user load. The test file lives at tests/load/locustfile.py.

Setup

# Install dev dependencies (includes locust) pip install -e ".[dev]" # Start infrastructure and orchestrator make docker-up make run-local

Running Load Tests

# Quick run: 10 users, ramp 2/sec, 60 second duration make load-test # Interactive mode with Locust web UI locust -f tests/load/locustfile.py # Headless with custom parameters locust -f tests/load/locustfile.py --headless \ -u 50 -r 5 -t 300s \ --csv=results/load-test \ --html=results/load-test-report.html # Target a different environment TARGET_HOST=http://staging:18790 locust -f tests/load/locustfile.py --headless -u 20 -r 5 -t 120s
FlagDescriptionDefault
-uTotal concurrent users10
-rUser spawn rate per second2
-tTest duration60s
--csvExport CSV results to path
--htmlExport HTML report to path

Test Scenarios

The Locust test simulates realistic traffic with weighted endpoint distribution:

EndpointMethodWeightDescription
/healthGET5Health check (baseline latency)
/readyGET2Readiness probe
/api/v1/portfolioGET3Portfolio state
/api/v1/portfolio/riskGET2Risk metrics
/api/v1/capabilitiesGET1Capabilities listing
/api/v1/analyze (equity)POST10Full analysis for equity symbols
/api/v1/analyze (crypto)POST3Full analysis for crypto symbols
/api/v1/analyze (tech-only)POST2Technical-only analysis
/api/v1/pipelinePOST5Full multi-agent pipeline execution
/api/v1/tradesGET1Trade history
/statusGET1System status

Performance Baselines

Track these metrics across load test runs to detect regressions:

MetricTarget
Requests/sec (avg)Record per environment
Failure rate< 1%
p50 latency (ms)Record per endpoint
p95 latency (ms)Record per endpoint
p99 latency (ms)Record per endpoint

Known Bottlenecks

  1. Agent analysis latency/api/v1/analyze and /api/v1/pipeline invoke LLM calls via the SwarmOrchestrator. Latency is dominated by external LLM API calls, not application code.
  2. Sequential pipeline stages/api/v1/pipeline runs agents sequentially (research, technical, risk). Prefer /api/v1/analyze for latency-sensitive paths where agents run in parallel via DAG.
  3. No connection pooling for external APIs — Each agent call creates independent HTTP connections to LLM providers, which may exhaust connection limits under high concurrency.

Security Scanning

isA Trade integrates Trivy container vulnerability scanning into its CI pipeline. Every Docker image is scanned before it can be pushed to the container registry.

How It Works

The CI workflow (.github/workflows/ci.yml) runs Trivy after each Docker build:

  1. Build — Docker image is built from deployment/docker/Dockerfile and tagged with the version and latest.
  2. Scan — Trivy scans the image for CRITICAL and HIGH severity vulnerabilities. Unfixed vulnerabilities are ignored.
  3. Gate — If Trivy finds any fixable CRITICAL or HIGH vulnerability, the CI pipeline fails (exit-code: 1), blocking the image push.
  4. Report — Results are uploaded as a SARIF file to GitHub Code Scanning and as a build artifact (retained for 30 days).

Configuration

SettingValue
Severity filterCRITICAL,HIGH
Exit code on findings1 (pipeline fails)
Ignore unfixedtrue
Output formatSARIF
Results retention30 days

Viewing Results

  • GitHub Security tab — Trivy SARIF results appear under Code Scanning alerts in the repository’s Security tab.
  • CI artifacts — Download the trivy-scan-results artifact from any CI run to inspect the raw SARIF output.

Data Sources

SourceData
yfinanceStock prices, fundamentals
FinnhubReal-time quotes, news
FREDMacro economic data
CCXTCrypto exchange data
AlpacaBroker integration (optional)