Add OpenTelemetry instrumentation plan with /metrics endpoints for external Prometheus scraping, and passkey/WebAuthn authentication flow with JWT sessions. [ci skip]
11 KiB
Trading Bot Design
Overview
An automated stock trading bot that combines news sentiment analysis with technical strategies to make buy/sell decisions on US equities. The system continuously learns from its own trade outcomes to improve decision-making without human supervision.
Constraints & Decisions
| Decision | Choice |
|---|---|
| Markets | US equities only |
| Initial mode | Paper trading (Alpaca sandbox) |
| Trading style | Adaptive (both swing and day trading) |
| News sources | Free only: RSS, Reddit, Twitter/X |
| Sentiment analysis | Tiered: FinBERT (local) + Ollama (local LLM for ambiguous cases) |
| Tech stack | Python (FastAPI) backend + React (TypeScript) frontend |
| Learning approach | Phase 1: strategy weight adjustment. Phase 2: deep RL |
| Deployment | Docker Compose (k8s later) |
| Database | PostgreSQL + TimescaleDB |
| Message broker | Redis Streams |
| Primary brokerage | Alpaca (with abstraction layer for future swaps) |
Architecture
Event-driven microservices communicating via Redis Streams.
Services
-
News Fetcher - Polls free news sources on a schedule:
- RSS feeds (Yahoo Finance, Reuters, MarketWatch, SEC filings)
- Reddit (r/wallstreetbets, r/stocks, r/investing via PRAW)
- Twitter/X financial accounts
- Publishes raw articles to Redis Stream
news:raw
-
Sentiment Analyzer - Consumes
news:raw, produces scored sentiment:- Tier 1: FinBERT model (runs locally) for fast scoring
- Tier 2: Ollama (Mistral/Llama 3) for articles where FinBERT confidence < threshold
- Extracts: ticker mentions, sentiment score (-1 to +1), confidence, key entities
- Publishes to
news:scored
-
Signal Generator - Combines sentiment with technical indicators:
- Consumes
news:scoredand market data (from Alpaca WebSocket) - Applies strategy rules (momentum, mean reversion, news-driven)
- Weighted ensemble combines signals (weights adjusted by learning engine)
- Publishes trade signals to
signals:generated
- Consumes
-
Trade Executor - Executes trades via brokerage API:
- Consumes
signals:generated - Risk management: position sizing, stop-losses, max portfolio exposure
- Interfaces with Alpaca through an abstraction layer
- Records all trades to PostgreSQL
- Publishes execution results to
trades:executed
- Consumes
-
Learning Engine - The continuous feedback loop:
- Consumes
trades:executed - Tracks trade outcomes over configurable time windows
- Computes reward signals: realized P&L, risk-adjusted return (Sharpe), max drawdown
- Adjusts strategy weights based on which strategies are profitable
- Stores performance metrics in TimescaleDB
- Consumes
-
API Gateway (FastAPI) - Serves the dashboard:
- Portfolio state, trade history, signal history
- Performance metrics (ROI, Sharpe, win rate, drawdown)
- Real-time WebSocket for live updates
- Manual override endpoints (pause trading, force close position)
-
Dashboard (React/TypeScript) - The UI
Data Flow
RSS/Reddit/X -> News Fetcher -> [news:raw] -> Sentiment Analyzer -> [news:scored]
|
Alpaca WebSocket -> Signal Generator <-- [news:scored] |
| |
[signals:generated] |
| |
Trade Executor -> Alpaca API |
| |
[trades:executed] |
| |
Learning Engine -> adjusts Signal Generator weights |
| |
TimescaleDB (metrics) + PostgreSQL (trades, news) |
| |
API Gateway -> Dashboard |
Data Model
PostgreSQL Tables
Core trading:
trades- ticker, side, qty, price, timestamp, strategy_id, signal_id, status, pnlpositions- ticker, qty, avg_entry, unrealized_pnl, stop_losssignals- ticker, direction, strength, strategy_sources, sentiment_score, acted_onstrategies- name, description, current weight, active flagstrategy_weights_history- strategy_id, old_weight, new_weight, timestamp
News & sentiment:
articles- source, url, title, published_at, fetched_at, content_hasharticle_sentiments- article_id, ticker, score, confidence, model_used
Learning:
trade_outcomes- trade_id, hold_duration, realized_pnl, roi_pct, was_profitablelearning_adjustments- strategy_id, old_weight, new_weight, reason, reward_signal, timestamp
TimescaleDB Hypertables
market_data- OHLCV bars partitioned by timeportfolio_snapshots- periodic portfolio value snapshotsstrategy_metrics- per-strategy performance over time
Redis
- Streams:
news:raw,news:scored,signals:generated,trades:executed - Cache: current positions, latest prices, strategy weights
Learning Loop (Phase 1)
Multi-armed bandit style strategy weight adjustment:
- Track: Tag every trade with contributing strategies and their weights
- Evaluate: After position close, compute realized P&L, ROI, risk-adjusted return
- Attribute: Distribute credit/blame proportionally to signal strength
- Adjust: Update weights via exponential moving average:
new_weight = (1 - lr) * old_weight + lr * reward_signal - Log: Record every adjustment for auditability
Guardrails:
- Minimum 20 trades per strategy before adjustments
- Max 10% weight shift per adjustment cycle
- Weight floor of 0.05 (no strategy permanently silenced)
- Weights normalized to sum to 1.0
- Decay factor for recency bias
Backtesting Engine
Runs as a standalone process (or Docker container triggered from dashboard):
- Replays historical market data from TimescaleDB
- Replays historical news sentiment from articles/sentiments tables
- Shares code with live system (same strategies, same signal generation)
- Uses simulated executor (not Alpaca)
- Configurable: date range, initial capital, commission model, slippage, strategy weights
- Output: trade log, equity curve, Sharpe, max drawdown, win rate, per-strategy attribution
Dashboard Views
- Portfolio Overview - Value, daily P&L, equity curve, open positions, key metrics
- Trade Log - All trades with expandable detail (news context, strategies, ROI, profitable badge)
- Strategy Performance - Per-strategy metrics, weight allocation, weight history, adjustments log
- News & Sentiment Feed - Live processed articles, ticker filtering, correlation view
- Backtesting - Config form, results dashboard, multi-run comparison
Real-time updates via WebSocket. Toast notifications for trade executions.
Project Structure
trading-bot/
├── services/
│ ├── news-fetcher/
│ ├── sentiment-analyzer/
│ ├── signal-generator/
│ ├── trade-executor/
│ ├── learning-engine/
│ └── api-gateway/
├── dashboard/
├── shared/
│ ├── models/ # SQLAlchemy models
│ ├── schemas/ # Pydantic schemas
│ ├── broker/ # Brokerage abstraction layer
│ └── strategies/ # Strategy implementations
├── backtester/
├── docker/ # Dockerfiles per service
├── docker-compose.yml # Full stack orchestration
├── tests/
├── alembic/ # Database migrations
└── docs/plans/
Key Libraries
Backend: FastAPI, SQLAlchemy (async), alpaca-py, transformers (FinBERT), praw, redis-py, websockets, ollama-python, py-webauthn Frontend: React 18, TypeScript, TanStack Query, Recharts or TradingView lightweight-charts, Tailwind CSS, @simplewebauthn/browser ML: transformers (FinBERT), ollama, numpy, pandas Observability: opentelemetry-sdk, opentelemetry-exporter-prometheus, opentelemetry-instrumentation-fastapi Testing: pytest, pytest-asyncio, React Testing Library
Brokerage Choice
Alpaca is the primary brokerage:
- Free API, commission-free US equity trading
- Built-in paper trading (sandbox environment)
- Official Python SDK (alpaca-py): market/limit/stop/trailing-stop orders
- REST + WebSocket + SSE endpoints
- Built-in NewsClient for financial news
- 200 requests/minute rate limit
- $0 minimum balance
An abstraction layer in shared/broker/ allows adding other brokerages (Interactive Brokers, Tradier) later without changing strategy or execution logic.
Observability
All Python services are instrumented with OpenTelemetry:
- Metrics:
opentelemetry-sdk+opentelemetry-exporter-prometheusexpose a/metricsendpoint on each service for external Prometheus scraping - Traces: Distributed tracing across services via OpenTelemetry (OTLP export to any collector)
- Key metrics per service:
- News Fetcher: articles fetched/min, source error rates, fetch latency
- Sentiment Analyzer: articles scored/min, FinBERT vs Ollama routing ratio, inference latency
- Signal Generator: signals generated/min, per-strategy signal counts
- Trade Executor: trades executed/min, order fill latency, rejection rate
- Learning Engine: adjustment frequency, weight drift magnitude
- API Gateway: request rate, response latency (p50/p95/p99), error rate
- Business metrics exposed via API Gateway
/metrics:- Portfolio value, daily P&L, drawdown, Sharpe ratio
- Per-strategy win rate and weight allocation
No Prometheus or Grafana deployed — the existing infrastructure handles scraping and visualization.
Authentication & Security
Passkey-based authentication (WebAuthn/FIDO2):
- Sign up: User registers with username + passkey (biometric/security key). Server stores credential public key in PostgreSQL
users/user_credentialstables - Sign in: Challenge-response via WebAuthn API. No passwords stored or transmitted
- Session management: JWT issued after successful passkey authentication, short-lived access token + refresh token
- API Gateway middleware: All API endpoints require valid JWT (except
/auth/*routes and/metrics) - CORS: Restricted to dashboard origin
- Secrets: Alpaca API keys and signing keys stored as environment variables via
.envfiles, never committed to git
Additional Tables
users- id, username, display_name, created_atuser_credentials- id, user_id, credential_id, public_key, sign_count, created_at
Docker Compose Services
news-fetcher- Python servicesentiment-analyzer- Python service (needs GPU access or CPU for FinBERT)signal-generator- Python servicetrade-executor- Python servicelearning-engine- Python serviceapi-gateway- Python (FastAPI) servicedashboard- Node/nginx serving React buildpostgres- PostgreSQL 16 + TimescaleDB extensionredis- Redis 7 with Streamsollama- Ollama server for local LLM inference