Why Multiple Agents?
A single monolithic trading AI tries to do everything: analyze charts, evaluate risk, interpret news, and decide position sizes. This leads to bloated prompts, inconsistent outputs, and difficulty debugging.
A multi-agent architecture assigns each responsibility to a specialized agent. The Technical Analyst reads indicators. The Risk Manager sizes positions. The Execution Agent handles order logic. Each agent is small, testable, and replaceable.
Reads indicators, identifies setups
Sizes positions, sets stops
Validates and routes orders
System Architecture
TickAtlas API (real-time data)
|
[Data Layer] -- fetches indicators, OHLCV, spreads
|
[Analyst Agent] -- identifies trade setups
|
[Risk Manager Agent] -- evaluates risk, sizes position
|
[Execution Agent] -- validates and prepares order
|
[Human Review] -- final approval before execution The Data Layer
import requests
import json
from dataclasses import dataclass
API_KEY = "YOUR_API_KEY"
BASE_URL = "https://tickatlas.com/v1"
@dataclass
class MarketSnapshot:
symbol: str
timeframe: str
close: float
rsi: float
macd_hist: float
ema_50: float
sma_200: float
atr: float
stochastic_k: float
def fetch_snapshot(symbol: str, timeframe: str) -> MarketSnapshot:
"""Fetch all indicators needed for analysis."""
# One call returns every indicator as a flat name -> number map
resp = requests.get(
f"{BASE_URL}/indicators",
headers={"X-API-Key": API_KEY},
params={
"symbol": symbol,
"timeframe": timeframe,
},
)
data = resp.json()["data"]
values = data["indicators"]
return MarketSnapshot(
symbol=symbol,
timeframe=timeframe,
close=data["ohlcv"]["close"],
rsi=values["RSI_14"],
macd_hist=values["MACD_hist"],
ema_50=values["EMA_50"],
sma_200=values["SMA_200"],
atr=values["ATR_14"],
stochastic_k=values["Stochastic_K"],
) Agent 1: Technical Analyst
import anthropic
claude = anthropic.Anthropic()
ANALYST_PROMPT = """You are a technical analyst. Based on the data below,
identify any trade setup. Respond in JSON.
Symbol: {symbol} ({timeframe})
Close: {close}
RSI(14): {rsi}
MACD Histogram: {macd_hist}
EMA 50: {ema_50}
SMA 200: {sma_200}
Stochastic %K: {stochastic_k}
Respond with:
{{"setup_found": true/false, "direction": "long"/"short"/null,
"reasoning": "...", "confidence": "low"/"medium"/"high",
"entry_zone": [low, high]}}"""
def analyst_agent(snapshot: MarketSnapshot) -> dict:
response = claude.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=500,
messages=[{"role": "user", "content": ANALYST_PROMPT.format(
symbol=snapshot.symbol, timeframe=snapshot.timeframe,
close=snapshot.close, rsi=snapshot.rsi,
macd_hist=snapshot.macd_hist, ema_50=snapshot.ema_50,
sma_200=snapshot.sma_200, stochastic_k=snapshot.stochastic_k,
)}],
)
return json.loads(response.content[0].text) Agent 2: Risk Manager
RISK_PROMPT = """You are a risk manager. Given this trade setup and account
info, determine position sizing and stop placement.
Trade Setup: {setup}
ATR(14): {atr}
Account Balance: \${balance}
Max Risk Per Trade: {risk_pct}%
Respond with:
{{"approved": true/false, "position_size_lots": X.XX,
"stop_loss": X.XXXXX, "take_profit": X.XXXXX,
"risk_amount": X.XX, "risk_reward_ratio": X.X,
"rejection_reason": null/"reason"}}"""
def risk_manager_agent(
setup: dict, snapshot: MarketSnapshot,
balance: float = 10000, risk_pct: float = 1.0
) -> dict:
response = claude.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=500,
messages=[{"role": "user", "content": RISK_PROMPT.format(
setup=json.dumps(setup), atr=snapshot.atr,
balance=balance, risk_pct=risk_pct,
)}],
)
return json.loads(response.content[0].text) Orchestrating the Pipeline
def run_trading_pipeline(symbol: str, timeframe: str) -> dict:
"""Run the full multi-agent trading pipeline."""
# Step 1: Fetch market data
snapshot = fetch_snapshot(symbol, timeframe)
# Step 2: Analyst identifies setup
setup = analyst_agent(snapshot)
if not setup["setup_found"]:
return {"action": "NO_TRADE", "reason": "No setup found"}
# Step 3: Risk manager evaluates
risk = risk_manager_agent(setup, snapshot)
if not risk["approved"]:
return {"action": "REJECTED", "reason": risk["rejection_reason"]}
# Step 4: Return for human review (never auto-execute)
return {
"action": "PENDING_REVIEW",
"symbol": symbol,
"direction": setup["direction"],
"confidence": setup["confidence"],
"entry_zone": setup["entry_zone"],
"stop_loss": risk["stop_loss"],
"take_profit": risk["take_profit"],
"position_size": risk["position_size_lots"],
"risk_amount": risk["risk_amount"],
"reasoning": setup["reasoning"],
}
# Run on multiple symbols
for sym in ["EURUSD", "GBPUSD", "XAUUSD"]:
result = run_trading_pipeline(sym, "H4")
print(f"\n{sym}: {result['action']}")
if result["action"] == "PENDING_REVIEW":
print(f" Direction: {result['direction']}")
print(f" Size: {result['position_size']} lots")
print(f" SL: {result['stop_loss']} / TP: {result['take_profit']}") Key Design Principles
Each agent has a single responsibility
The analyst never calculates position sizes. The risk manager never identifies setups. Clean boundaries make each agent easier to test and improve independently.
Data flows one direction
Raw data goes in, structured decisions come out. No circular dependencies between agents. The pipeline is linear and predictable.
Human in the loop
The system generates trade proposals, not trade executions. A human reviews and approves before any real money moves. This is non-negotiable.
Run this against live data.
Every account starts pay-as-you-go with $2.50 of credit and no card. Paste the key into the samples above and the requests work unchanged.