What Makes an Agent Different from a Chatbot?
A chatbot responds to questions. An agent acts on its own. A trading agent runs in a loop, decides when to check the market, determines which instruments need attention, fetches the relevant data, analyzes it, and takes action — all without human prompting.
The key ingredients are: a planning loop, tool access (the TickAtlas API for market data), memory (to track what it has already analyzed), and guardrails (to prevent it from doing something catastrophic).
Agent Architecture
+------------------+
| Agent Loop |
| (runs every N |
| minutes) |
+--------+---------+
|
+---------------+---------------+
| | |
+--------v---+ +------v------+ +------v------+
| Observe | | Think | | Act |
| (fetch | | (LLM | | (send alert,|
| market | | analyzes | | log trade, |
| data) | | data) | | update |
+------------+ +-------------+ | memory) |
+-------------+ Step 1: The Agent Loop
import time
import json
from datetime import datetime
class TradingAgent:
def __init__(self, api_key: str, llm_client, symbols: list[str]):
self.api_key = api_key
self.llm = llm_client
self.symbols = symbols
self.memory = [] # Track past observations and decisions
self.check_interval = 300 # 5 minutes
def run(self):
"""Main agent loop — runs indefinitely."""
print(f"[{datetime.utcnow()}] Agent started. Watching {self.symbols}")
while True:
try:
observations = self.observe()
analysis = self.think(observations)
self.act(analysis)
except Exception as e:
print(f"Agent error: {e}")
time.sleep(self.check_interval) Step 2: Observe — Fetch Market Data
import requests
CLAW_BASE = "https://tickatlas.com/v1"
def observe(self) -> list[dict]:
"""Fetch indicators for all watched symbols."""
observations = []
headers = {"X-API-Key": self.api_key}
for symbol in self.symbols:
resp = requests.get(f"{CLAW_BASE}/indicators", params={
"symbol": symbol,
"timeframe": "H1",
}, headers=headers)
if resp.status_code == 200:
data = resp.json()["data"]
observations.append({
"symbol": symbol,
"indicators": data["indicators"],
"ohlcv": data.get("ohlcv", {}),
"updated_at": data["updated_at"]
})
return observations Step 3: Think — LLM Analysis
This is where the LLM earns its keep. It receives the raw indicator data plus the agent's memory of recent observations and produces a structured analysis.
def think(self, observations: list[dict]) -> dict:
"""Use LLM to analyze observations and decide on actions."""
# Build context from memory
recent_memory = self.memory[-10:] # Last 10 observations
prompt = f"""You are an autonomous trading analyst agent.
Current observations:
{json.dumps(observations, indent=2)}
Recent memory (previous analyses):
{json.dumps(recent_memory, indent=2)}
Analyze each symbol and return a JSON object with this structure:
{{
"signals": [
{{
"symbol": "EURUSD",
"action": "ALERT_BUY" | "ALERT_SELL" | "WATCH" | "NO_ACTION",
"confidence": 0.0-1.0,
"reasoning": "brief explanation",
"key_levels": {{"support": 1.0820, "resistance": 1.0890}}
}}
],
"market_summary": "One paragraph overview of current conditions"
}}
Rules:
- Only signal ALERT_BUY/ALERT_SELL when confidence > 0.7
- Consider what has changed since the last observation
- Flag any divergences between indicators
- Note when ADX < 20 (no trend) — avoid trend signals in ranging markets"""
response = self.llm.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=2000,
messages=[{"role": "user", "content": prompt}]
)
# Parse the structured response
text = response.content[0].text
analysis = json.loads(text)
# Save to memory
self.memory.append({
"timestamp": datetime.utcnow().isoformat(),
"observations": observations,
"analysis": analysis
})
return analysis Step 4: Act — Execute Decisions
def act(self, analysis: dict):
"""Execute the agent's decisions."""
for signal in analysis.get("signals", []):
if signal["action"] in ("ALERT_BUY", "ALERT_SELL"):
self.send_alert(signal)
elif signal["action"] == "WATCH":
print(f"[WATCH] {signal['symbol']}: {signal['reasoning']}")
def send_alert(self, signal: dict):
"""Send alert via Telegram, email, or webhook."""
msg = (
f"{'BUY' if 'BUY' in signal['action'] else 'SELL'} Alert: "
f"{signal['symbol']}\n"
f"Confidence: {signal['confidence']:.0%}\n"
f"Reason: {signal['reasoning']}\n"
f"Support: {signal['key_levels']['support']}\n"
f"Resistance: {signal['key_levels']['resistance']}"
)
print(f"[ALERT] {msg}")
# Send to Telegram, Slack, email, etc. Safety Guardrails
Never Auto-Execute Trades
Agents should generate alerts and analysis, not place orders. Keep a human in the loop for execution decisions.
Rate Limit LLM Calls
At $3-15 per million tokens, an agent checking 10 symbols every 5 minutes can get expensive. Use the LLM for analysis, not for every data fetch.
Bounded Memory
Cap the memory buffer. An agent with unlimited memory will eventually exceed the LLM's context window and degrade in quality.
Confidence Thresholds
Only act on high-confidence signals. The agent's structured output includes a confidence score — filter aggressively.
Running the Agent
import anthropic
import os
if __name__ == "__main__":
llm = anthropic.Anthropic()
agent = TradingAgent(
api_key=os.environ["CLAW_API_KEY"],
llm_client=llm,
symbols=["EURUSD", "GBPUSD", "XAUUSD", "BTCUSD"]
)
agent.run() Further Reading
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.