TradingAgent
Enables natural-language paper trading with real-time market data, FinBERT sentiment analysis, conditional orders, and price alerts.
README
TradingAgent
MCP-native conversational paper trading agent with FinBERT sentiment analysis.
"Buy 10 AAPL if it drops below $200 today" — type it, the agent handles the rest.
Table of Contents
- Architecture
- Quick Start
- Running the Agent
- Example Conversation
- Tools
- Safety
- FinBERT as a Production Service
- Project Structure
- Tests
- Environment Variables
- State Persistence
- Tech Stack
Architecture
┌──────────────────────┐ ┌──────────────────────────────────────┐
│ CLI Agent (REPL) │ │ Web UI (FastAPI) │
│ claude-sonnet-4-6 │ │ Dark chat · WebSocket · marked.js │
│ agentic loop │ │ ngrok → shareable demo URL │
└──────────┬───────────┘ └──────────────────┬───────────────────┘
│ MCP (stdio) │ MCP (stdio, per session)
└──────────────────┬──────────────────┘
│
┌──────────────────▼──────────────────┐
│ FastMCP Server │
│ 20 tools · 4 modules │
└────┬──────────┬──────────┬──────────┘
│ │ │ │
trading market-data sentiment watchlist
8 tools 5 tools FinBERT 4 tools
│ │ │ │
└──────────┴──────────┴──────────┘
│
┌─────────────▼──────────────┐
│ 5-Layer Guardrail │
│ input·llm·tool·exec·output │
└─────────────┬──────────────┘
│
┌─────────────▼──────────────┐
│ Alpaca Paper API │
│ real quotes · fake money │
└─────────────┬──────────────┘
│
┌─────────────▼──────────────┐
│ Alert Daemon │
│ polls prices every 30s │
│ fires conditional orders │
└────────────────────────────┘
Quick Start
No Alpaca account needed for mock mode.
1 — Install
# Clone and enter project
git clone https://github.com/cactus001/TRADE-AGENT.git
cd TRADE-AGENT
# Install dependencies (Apple Silicon — use Homebrew Python)
uv sync --python /opt/homebrew/bin/python3.12
# Install dev dependencies for tests
uv sync --extra dev
2 — Configure
cp .env.example .env
Open .env and fill in:
ANTHROPIC_API_KEY=sk-ant-... # required — get from console.anthropic.com
ALPACA_API_KEY= # optional — paper trading keys from alpaca.markets
ALPACA_SECRET_KEY= # optional — leave blank to use --mock mode
Note: Mock mode works without any Alpaca keys and runs FinBERT on real fake news headlines.
Running the Agent
Option A — CLI REPL (terminal chat)
# Mock mode (no Alpaca keys needed)
uv run python -m agent.cli_agent --mock
# Live paper trading (requires Alpaca keys in .env)
uv run python -m agent.cli_agent
Type natural language commands. Press Ctrl+C to exit — the session transcript saves automatically to transcripts/.
Option B — Web UI (shareable chat interface)
# Mock mode
uv run python -m src.webapp --mock
# Live paper trading
uv run python -m src.webapp
Open http://localhost:8000 in your browser — you'll see a dark-themed chat UI with:
- Live tool call chips showing which MCP tools are running
- Animated thinking indicator during inference
- Markdown tables for portfolio/order data
PAPER TRADING — NO REAL MONEYwatermark on all order confirmations
Option C — Share via ngrok (live demo from anywhere)
Use this for interviews, demos, or sharing with anyone over the internet.
Step 1 — Install ngrok
brew install ngrok
Step 2 — Authenticate
- Go to dashboard.ngrok.com/get-started/your-authtoken
- Copy your personal authtoken
- Run:
ngrok config add-authtoken YOUR_REAL_TOKEN_HERE
Step 3 — Start the web server
uv run python -m src.webapp --mock
Step 4 — Open the tunnel (in a second terminal)
ngrok http 8000
ngrok will print a public URL like:
Forwarding https://oboe-routing-difficult.ngrok-free.app → http://localhost:8000
Share that URL — anyone with the link can open the chat interface and interact with TradingAgent in real time from any browser, no setup needed.
Option D — Docker (Redis + Web UI)
Docker Compose spins up two services:
- redis —
redis:7-alpine, persists to a named volume, health-checked before the agent starts - trading-agent — builds from
Dockerfile, runs the web UI on port 8000, connects to Redis automatically
# Copy env file and add your Anthropic key
cp .env.example .env
# edit .env — add ANTHROPIC_API_KEY (REDIS_URL is set automatically by compose)
# Build and start both services
docker-compose up --build
# Or run detached
docker-compose up --build -d
Open http://localhost:8000 — the chat UI is live.
# Tail logs
docker-compose logs -f trading-agent
# Stop everything
docker-compose down
# Stop and wipe all volumes (resets state + Redis cache)
docker-compose down -v
Run the CLI agent instead of web UI:
docker-compose run --rm trading-agent uv run python -m agent.cli_agent --mock
Example Conversation
You: what's my portfolio looking like
Agent: Portfolio — $101,432.20 total (+1.4% today)
TSLA 15 shares $248.20 +$190.50 (+5.4%)
NVDA 5 shares $134.50 -$43.00 (-6.0%)
Cash $94,890.50 available
You: what's the sentiment on NVDA right now
Agent: NVDA Sentiment (last 4h): BEARISH — score -0.41
Top drivers:
• "Analyst cuts NVDA target to $125" (negative, -0.81)
• "Nvidia faces rising competition" (negative, -0.62)
Recommendation: Consider waiting for stabilisation before adding.
You: buy 10 AAPL if it drops below $200 today
Agent: Day Limit Buy Preview [PAPER TRADING — NO REAL MONEY]
10 shares AAPL · Limit @ $200.00
AAPL currently: $212.95
Expires: 4:00 PM ET today · Max cost: $2,000.00
Confirm? (yes/no)
You: yes
Agent: ✅ Order placed (ID: abc-12345)
Limit buy active — AAPL will be purchased automatically if price hits $200.
[PAPER TRADING — NO REAL MONEY]
You: alert me when TSLA drops below 240
Agent: ✅ Alert set — will notify when TSLA < $240.00
Current price: $248.20 (monitoring every 30s)
Tools
20 tools across 4 modules:
| Module | Tools |
|---|---|
| trading | get_account, get_positions, get_orders, place_order, cancel_order, cancel_all_orders, get_portfolio_history, get_asset_info |
| market-data | get_quote, get_bars, get_news, get_market_status, search_symbol |
| sentiment | get_sentiment, get_market_mood, explain_sentiment |
| watchlist | set_price_alert, get_active_alerts, cancel_alert, get_trade_history |
Safety
A 5-layer guardrail pipeline runs on every order:
| Layer | What it catches |
|---|---|
| Input guard | Prompt injection patterns in user messages |
| LLM guard | Injected instructions hidden in news headlines; hardens system prompt |
| Tool guard | Invalid ticker formats, negative qty, missing required prices, sanity limits |
| Execution guard | Single order > 20% portfolio, daily loss > 5%, > 10 orders/hr, wash trades (< 5 min), circuit breaker (SPY down > 5%) |
| Output guard | Broker rejections, post-trade concentration warnings |
place_order enforces a mandatory two-step flow: confirm=False (preview) must be called before confirm=True (execute). No order reaches the broker without an explicit user confirmation in the conversation.
FinBERT as a Production Service
src/models/finbert.py promotes FinBERT (ProsusAI/finbert) from a standalone script to a production-grade callable MCP service:
| Pattern | Implementation |
|---|---|
| Singleton | Module-level _instance, one model loaded per process |
| Lazy loading | Model not loaded until first analyze() call |
| Double-checked locking | threading.Lock with if self._loaded checked inside the lock |
| Device auto-detection | CUDA → Apple MPS → CPU, no environment config needed |
| Batch inference | Chunks news lists into BATCH_SIZE=16 to avoid OOM |
| Normalised score | Returns pos_prob − neg_prob in [-1.0, +1.0] |
| Redis cache | sha256(headline) key, 1h TTL — identical headlines never hit GPU twice |
| Partial-hit pattern | Per-batch cache lookup; only misses go to FinBERT, hits served in <1ms |
| Graceful degradation | Redis unavailable → cache is a no-op, inference runs normally |
On Apple Silicon, inference runs on the MPS GPU (confirmed: device: mps). With Redis warm, repeated get_sentiment calls on the same news cycle return instantly.
Project Structure
TRADE-AGENT/
├── agent/
│ └── cli_agent.py # REPL — manual agentic loop, auto-saves transcripts
├── src/
│ ├── server.py # FastMCP entry point — registers all 4 tool modules
│ ├── webapp.py # FastAPI + WebSocket web interface
│ ├── config.py # Pydantic settings — env vars with defaults
│ ├── state_manager.py # Persistent state (~/.trading-agent/state.json)
│ ├── alert_daemon.py # Background thread — polls prices every 30s
│ ├── models/
│ │ └── finbert.py # FinBERT singleton service (production ML pattern)
│ ├── cache/
│ │ └── redis_cache.py # Redis sentiment cache — partial-hit, 1h TTL, graceful degradation
│ ├── clients/
│ │ └── alpaca_client.py # Thin wrapper around alpaca-py SDK
│ ├── guardrails/
│ │ ├── input_guard.py # Regex injection pattern detection
│ │ ├── llm_guard.py # News sanitisation + system prompt hardening
│ │ ├── tool_guard.py # Symbol/qty/price validation
│ │ ├── execution_guard.py # Size, loss, velocity, wash-trade, circuit-breaker
│ │ ├── output_guard.py # Broker rejection + concentration check
│ │ └── guard_registry.py # Wires all 5 layers into one object
│ ├── tools/
│ │ ├── trading.py # 8 trading tools (place_order confirm gate)
│ │ ├── market_data.py # 5 market data tools
│ │ ├── sentiment.py # 3 FinBERT sentiment tools
│ │ └── watchlist.py # 4 alert/history tools
│ └── static/
│ └── index.html # Dark chat UI (WebSocket, marked.js, tool chips)
├── mock/
│ └── mock_provider.py # MockAlpacaClient — full demo without API keys
├── tests/
│ ├── test_guardrails.py # 15 tests across all 5 guardrail layers
│ └── test_sentiment.py # 7 tests — singleton, batch, device detection
├── transcripts/ # Auto-saved session logs (git-ignored)
├── pyproject.toml
├── docker-compose.yml
├── Dockerfile
└── .env.example
Tests
uv run pytest tests/ -v
# 22 passed
Environment Variables
| Variable | Required | Default | Description |
|---|---|---|---|
ANTHROPIC_API_KEY |
Yes | — | Claude API key — console.anthropic.com |
ALPACA_API_KEY |
No | — | Alpaca paper trading key — alpaca.markets |
ALPACA_SECRET_KEY |
No | — | Alpaca paper trading secret |
REDIS_URL |
No | redis://localhost:6379 |
Redis connection string — auto-set by docker-compose |
MAX_ORDER_PCT |
No | 0.20 |
Max single order as fraction of portfolio |
DAILY_LOSS_LIMIT |
No | -0.05 |
Halt buys if day P&L falls below this |
VELOCITY_LIMIT |
No | 10 |
Max orders per hour |
State Persistence
The agent persists alerts, trade history, and the velocity/wash-trade counters to:
~/.trading-agent/state.json
This file lives outside the project directory and is never committed. Delete it to reset all state.
Tech Stack
| Component | Technology |
|---|---|
| Agent SDK | Anthropic Python SDK (claude-sonnet-4-6) |
| Tool protocol | MCP (Model Context Protocol) via FastMCP |
| Broker API | Alpaca Paper Trading (alpaca-py) |
| Sentiment model | ProsusAI/FinBERT (HuggingFace Transformers) |
| ML runtime | PyTorch 2.x — MPS / CUDA / CPU auto |
| Inference cache | Redis 7 — sha256-keyed, 1h TTL, graceful degradation |
| Web server | FastAPI + Uvicorn + WebSocket |
| Frontend | Vanilla JS, marked.js, CSS custom properties |
| Containers | Docker + docker-compose (two-service: redis + trading-agent) |
| Tunnel | ngrok (free tier) |
| Package manager | uv |
| Tests | pytest + pytest-asyncio |
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.