Scorched
by willcassell
README.md
# Scorched — Your AI Trading Assistant
An AI-powered stock trading bot you can actually talk to. Every weekday, Scorched researches the market, generates trade recommendations through a multi-stage Claude pipeline, and manages a portfolio — all on its own. Check in through a live dashboard, or **open Claude Desktop and ask your bot what it's thinking in plain English.**
Starts as paper trading with simulated money. When you're ready, connect an Alpaca brokerage account to trade for real.
> **Who this is for:** Developers, hobbyist traders, and AI tinkerers who want to experiment with an autonomous trading system they fully control. This is a personal trading framework for learning and experimentation — not financial advice, not a production trading platform, and not a substitute for professional investment management. Past performance of any trading system does not guarantee future results. Use at your own risk.
> **Safe defaults:** Scorched starts in paper-trading mode with simulated money. Before doing anything else: set `SETTINGS_PIN` in your `.env`, do not expose port 8000 to the public internet, and use a VPN or IP allowlist if running on a cloud VM. See [DEPLOY.md](DEPLOY.md) for full security guidance.
## New here? Start with [START_HERE.md](START_HERE.md)
---
## Two Ways to Use It
### 1. Talk to Your Bot (MCP)
Scorched runs a built-in **MCP server** — the open protocol that lets AI assistants use external tools. Point Claude Desktop (or any MCP client) at your bot and have a conversation:
> **You:** "What did you buy today and why?"
> **You:** "How's the portfolio doing?"
> **You:** "What does your playbook say about tech stocks?"
> **You:** "Run today's analysis — what looks good?"
Claude calls the bot's tools behind the scenes and answers in plain English. No commands to memorize, no API knowledge needed. **This is the easiest way to get started.**
**Quick setup (Claude Desktop):**
```json
{
"mcpServers": {
"tradebot": {
"url": "http://localhost:8000/mcp"
}
}
}
```
That's it. Open Claude Desktop and start asking questions.
### 2. Set It and Forget It (Dashboard + Cron)
The bot also runs fully autonomously on a daily schedule — no manual intervention required. Cron jobs trigger each phase of the trading day, and a live dashboard at `http://host:8000` shows your portfolio, today's picks, and performance history. Auto-refreshes every 5 minutes.
```
Cron (VM)
│
│ 9:35 AM ET — Phase 0: post-open data prefetch (zero LLM cost)
│ 9:45 AM ET — Phase 1: Claude analysis + recommendations
│ 9:55 AM ET — Phase 1.5: circuit breaker safety gate
│ 10:15 AM ET — Phase 2: submit approved orders
│ 10:45 AM ET — Phase 2.5: reconcile Alpaca fills + sync positions
│ Every 5 min — Intraday position monitoring (9:35 AM–3:55 PM ET)
│ 4:01 PM ET — Phase 3: EOD review + playbook update
│
▼
Scorched (FastAPI + PostgreSQL)
│
├── Phase 0: Fetches market data (Alpaca, yfinance, FRED, Twelvedata, Alpha Vantage, Finnhub, EDGAR)
├── Phase 0: Runs momentum screener (top 20 S&P 500 movers)
├── Phase 1: Calls Claude (claude-sonnet-4-6) — multi-call pipeline
│ Call 1: Analysis w/ extended thinking → structured candidates + position actions
│ Call 2: Decision → 0–3 concrete trade recommendations
│ Call 3: Risk committee → challenge and reject weak picks
│ Call 4: Position management → review open positions EOD
├── Phase 2.5: Reconciles pending Alpaca fills + syncs positions vs broker
├── Tracks portfolio state in PostgreSQL
└── Dashboard auto-refreshes at http://host:8000
```
**NYSE holidays** are detected automatically — if the market is closed, no Claude calls are made.
---
## Stack
| Layer | Technology |
|-------|-----------|
| Runtime | Python 3.11, FastAPI |
| AI | Anthropic Claude (`claude-sonnet-4-6`, extended thinking on Call 1) |
| MCP | `mcp[cli]` (FastMCP, Streamable HTTP) — talk to your bot from Claude Desktop |
| Database | PostgreSQL 16 via SQLAlchemy 2.0 async + asyncpg |
| Migrations | Alembic |
| Market data | Alpaca Data API — prices, bars (IEX), snapshots, news, screener (Polygon removed Apr 2026) |
| Fundamentals/options | yfinance — PE, market cap, options chains, earnings dates, insider purchases, index symbols |
| Macro data | FRED API — Fed rate, CPI, yield curve, PCE, credit spreads, economic calendar |
| Technicals | Twelvedata RSI(14) for full watchlist + Alpha Vantage fallback for screener picks |
| Analyst consensus | Finnhub (recommendation trends) |
| Insider filings | SEC EDGAR Form 4 (free, no key) |
| Holiday detection | `pandas-market-calendars` (NYSE calendar) |
| Automation | cron on the VM |
| Deployment | Docker Compose |
---
## MCP Server — Talk to Your Bot
The bot runs a **Streamable HTTP MCP server** at `http://host:8000/mcp`. Any MCP-compatible client — Claude Desktop, Cursor, your own agents — can connect and interact with the full trading system through natural language.
**Why this matters:**
- **No technical knowledge required.** Ask "what did you buy today?" and get a plain-English answer. The AI client calls the right tools automatically.
- **The heavy lifting is behind the tools.** A single question like "what looks good today?" triggers a multi-stage pipeline that pulls from 7+ data sources, runs technical analysis, and passes picks through a risk committee. You just ask.
- **Human-in-the-loop by default.** Recommendations come back as pending. Nothing executes without explicit confirmation.
- **Works with any MCP client.** Claude Desktop, Cursor, Claude Code, or any tool that speaks the MCP protocol.
**Connect Claude Desktop** — add this to your Claude Desktop MCP config:
```json
{
"mcpServers": {
"tradebot": {
"url": "http://your-server:8000/mcp"
}
}
}
```
**Things you can ask:**
- *"How's my portfolio doing?"* — pulls live positions, P&L, and tax status
- *"Run today's analysis"* — triggers the full research + recommendation pipeline
- *"What does the playbook say?"* — reads the bot's evolving strategy document
- *"Show me the market summary"* — end-of-day index and sector performance
- *"Confirm trade #42 at $185.50 for 10 shares"* — executes a specific recommendation
- *"Reject recommendation #43"* — skips a pick while keeping the audit trail clean
**Available tools (7):**
| Tool | What it does |
|------|-------------|
| `get_recommendations` | Research stocks + generate up to 3 trade picks via Claude pipeline |
| `get_opening_prices` | Fetch actual opening auction prices for any symbols |
| `confirm_trade` | Execute a trade — updates portfolio, tracks P&L and taxes |
| `reject_recommendation` | Skip a pick (audit trail stays clean) |
| `get_portfolio` | Live portfolio snapshot — positions, unrealized P&L, tax classification |
| `get_market_summary` | EOD performance for major indices + all S&P 500 sector ETFs |
| `read_playbook` | Read the bot's living strategy doc — lessons learned from past trades |
---
## REST API
| Method | Path | Description |
|--------|------|-------------|
| `GET` | `/` | Dashboard (HTML) |
| `GET` | `/health` | Health check |
| `GET` | `/api/v1/portfolio` | Portfolio snapshot |
| `GET` | `/api/v1/portfolio/history` | Trade history (paginated) |
| `GET` | `/api/v1/portfolio/tax-summary` | YTD realized gains by ST/LT |
| `GET` | `/api/v1/recommendations` | List past sessions |
| `POST` | `/api/v1/recommendations/generate` | Trigger recommendation run |
| `POST` | `/api/v1/trades/confirm` | Confirm a trade |
| `POST` | `/api/v1/trades/{rec_id}/reject` | Reject a recommendation |
| `GET` | `/api/v1/market/summary` | EOD market + sector summary |
| `GET` | `/api/v1/strategy` | Read strategy settings |
| `PUT` | `/api/v1/strategy` | Update strategy settings (PIN-protected if configured) |
| `GET` | `/api/v1/playbook` | Read strategy playbook |
| `POST` | `/api/v1/intraday/evaluate` | Intraday trigger check + Claude exit evaluation |
| `GET` | `/api/v1/costs` | Claude API cost tracker |
---
## Database Schema
8 tables in PostgreSQL:
| Table | Purpose |
|-------|---------|
| `portfolio` | Single-row: cash balance, starting capital, peak value (drawdown tracking), benchmark start prices |
| `positions` | One row per held ticker; avg cost basis updated on each buy |
| `recommendation_sessions` | One row per trading day; caches raw research + Claude response + analysis thinking |
| `trade_recommendations` | Up to 3 rows per session; status: `pending` → `confirmed`/`rejected` |
| `trade_history` | Append-only audit log of executed trades |
| `playbook` | Single-row living strategy document (updated by Claude before each session) |
| `token_usage` | Per-call Claude API token tracking (input, output, thinking tokens) |
| `api_call_log` | External API call tracking — service, endpoint, status, response time, errors |
**Tax model:** simplified ST/LT classification based on `first_purchase_date` (no per-lot tracking). ST rate: 37%, LT rate: 20%.
---
## Project Structure
```
scorched/
├── Dockerfile
├── docker-compose.yml
├── entrypoint.sh # Runs alembic upgrade then starts uvicorn
├── pyproject.toml
├── strategy.md # Human-readable strategy reference
├── analyst_guidance.md # Signal interpretation tables + hard rules for Claude prompts
├── advisor.md # CPA/financial advisor reference document
├── DEPLOY.md # Full deployment + cron guide
├── alembic/
│ ├── env.py
│ └── versions/ # Migration files
├── cron/ # Cron job scripts (phase 0, intraday monitor, etc.)
├── scripts/ # Utility scripts (setup_cron.py, etc.)
└── src/
└── scorched/
├── main.py # FastAPI app; mounts MCP at /mcp
├── config.py # pydantic-settings Settings
├── database.py # Async SQLAlchemy engine + session
├── models.py # 8 ORM models
├── schemas.py # Pydantic request/response schemas
├── mcp_tools.py # 7 MCP tool definitions (FastMCP)
├── tax.py # classify_gain(), estimate_tax()
├── cost.py # Claude token cost calculator
├── tz.py # market_today(), market_now(), MARKET_TZ
├── api_tracker.py # External API call tracking + health aggregation
├── correlation.py # 20-day return correlation check
├── circuit_breaker.py # Pre-execution gate (gap-down, SPY, VIX)
├── drawdown_gate.py # Portfolio drawdown enforcement
├── trailing_stops.py # ATR-based trailing stop logic
├── intraday.py # Pure intraday trigger check functions
├── http_retry.py # Retry wrapper for external HTTP APIs
├── static/
│ └── dashboard.html
├── broker/ # BrokerAdapter ABC, PaperBroker, AlpacaBroker
├── api/ # FastAPI routers
│ ├── costs.py
│ ├── market.py
│ ├── playbook.py
│ ├── portfolio.py
│ ├── recommendations.py
│ ├── strategy.py
│ ├── trades.py
│ ├── system.py # /system/health, /system/errors, /system/trend
│ ├── intraday.py # Intraday trigger eval + auto-sell
│ ├── prefetch.py # Phase 0 data prefetch
│ ├── onboarding.py
│ └── broker_status.py # Position reconciliation
└── services/
├── portfolio.py # apply_buy(), apply_sell(), get_portfolio_state()
├── recommender.py # Claude 4-call pipeline + NYSE holiday check
├── research.py # Data orchestration: Alpaca, yfinance, FRED, Twelvedata, Alpha Vantage, Finnhub, EDGAR
├── alpaca_data.py # Alpaca Data API: snapshots, bars (IEX), news, screener
├── technicals.py # MACD, Bollinger, MA crossover, support/resistance, ATR
├── finnhub_data.py # Analyst consensus, recommendation trends
├── economic_calendar.py # FRED-based upcoming release tracking
├── risk_review.py # Call 3: adversarial risk committee review
├── position_mgmt.py # Call 4: EOD position management review
├── reflection.py # Weekly trade reflection + learnings
├── playbook.py # Playbook read/update
└── strategy.py # load_strategy() from strategy.json
```
---
## Quick Start (Local)
### Prerequisites
- Docker + Docker Compose
- An Anthropic API key
### 1. Clone and configure
```bash
git clone https://github.com/willcassell/scorched.git
cd scorched
cp .env.example .env
# Edit .env — at minimum set ANTHROPIC_API_KEY
```
### 2. Start everything
```bash
docker compose up -d --build
```
This starts PostgreSQL, waits for it to be healthy, then starts the app (Alembic migrations run automatically at startup).
### 3. Verify
```bash
curl http://localhost:8000/health
# {"status":"ok","db":"connected"}
# Open dashboard
open http://localhost:8000
```
### 4. Trigger a recommendation run manually
```bash
curl -s -X POST http://localhost:8000/api/v1/recommendations/generate \
-H "Content-Type: application/json" \
-d '{}'
```
---
## Environment Variables
Create `.env` in the project root (see `.env.example` for full template):
```bash
# Required
ANTHROPIC_API_KEY=sk-ant-api03-...
# Portfolio
STARTING_CAPITAL=100000 # Starting cash in dollars
# Tax rates (optional — these are the defaults)
SHORT_TERM_TAX_RATE=0.37
LONG_TERM_TAX_RATE=0.20
# Server
PORT=8000
HOST=0.0.0.0
# Optional data sources (enable richer context)
FRED_API_KEY= # Free: https://fredaccount.stlouisfed.org
TWELVEDATA_API_KEY= # Free tier: 800 calls/day, RSI for full watchlist
ALPHA_VANTAGE_API_KEY= # Free tier: 25 calls/day, RSI fallback for screener
FINNHUB_API_KEY= # Free: analyst consensus
# (Polygon.io was removed April 2026 — Alpaca news replaced it)
# Optional: require a PIN to update strategy via dashboard
SETTINGS_PIN=
# Optional broker (default: paper trading, no broker needed)
BROKER_MODE=paper # "paper", "alpaca_paper", or "alpaca_live"
ALPACA_API_KEY=
ALPACA_SECRET_KEY=
# Optional notifications
TELEGRAM_BOT_TOKEN=
TELEGRAM_CHAT_ID=
```
> **Note:** `DATABASE_URL` is not needed in `.env` when using Docker Compose — it's set automatically via the `environment` block in `docker-compose.yml`.
---
## Deployment (Oracle Cloud / Ubuntu VM)
See **[DEPLOY.md](DEPLOY.md)** for the full guide, including:
- rsync command to copy files to the VM
- `.env` setup on the VM
- Firewall / port configuration
- Cron job setup for the automated daily cycle
- DST time adjustments
**Cron timezone:** Crontab entries use **Eastern Time (NYSE local)** directly — no DST math required. The Docker container sets `TZ=America/New_York`. Each cron script runs a sanity check that warns via Telegram if it fires at the wrong ET hour.
---
## Dashboard
The web dashboard at `http://host:8000` shows:
- Portfolio performance and today's picks
- Open positions with buy thesis, P&L, and tax classification
- Today's market analysis (with extended thinking toggle)
- Recent closed trades
- Tax summary (ST/LT breakdown)
- Living strategy playbook
- Claude API cost tracker (with daily spend progress bar)
Auto-refreshes every 5 minutes.
---
## Useful Commands
```bash
# Logs
docker compose logs tradebot -f
docker compose logs tradebot --tail=50
# Rebuild after code changes (keeps postgres data)
docker compose up -d --build tradebot
# Shell into the app container
docker compose exec tradebot sh
# PostgreSQL shell
docker compose exec postgres psql -U scorched scorched
# Force a fresh recommendation run (bypass today's cache)
curl -s -X POST http://localhost:8000/api/v1/recommendations/generate \
-H "Content-Type: application/json" \
-d '{"force": true}'
# Wipe database (destructive!)
docker compose down -v
```
---
## Security
**If running on a public VM, do not expose port 8000 directly to the internet.**
The recommended setup is one of:
- **Tailscale or WireGuard VPN** — only your devices can reach the bot
- **Reverse proxy with auth** — nginx or Caddy with basic auth or client certificates
- **Cloud firewall** — restrict port 8000 to your IP only (`sudo ufw allow from YOUR_IP to any port 8000`)
MCP mutation tools (`confirm_trade`, `reject_recommendation`, `get_recommendations`) require the owner PIN when `SETTINGS_PIN` is configured. Read-only tools (`get_portfolio`, `get_market_summary`, `read_playbook`, `get_opening_prices`) do not require a PIN.
REST mutation endpoints also require the PIN via the `X-Owner-Pin` header.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues