TradingAgents/docs/design_reference.md

15 KiB

TradeDog — Design Reference

All architecture patterns, code snippets, schemas, and design decisions for TradeDog. Referenced by TradeDog_Roadmap.md — use the roadmap for task tracking, use this doc for implementation details.


Table of Contents


Architecture Target

[Watchlist Scanner]
       |
[Research Pipeline] <-- Fundamentals / Sentiment / News / Technical
       |
[Bull vs Bear Debate]
       |
[Trader -> Risk Manager -> Fund Manager]
       |
  Conviction Score
       |
[Auto-Buy Engine] <--> [Broker API: Alpaca / IBKR]
       |
[Position Monitor -- runs every N minutes]
  |-- Profit target hit -> SELL
  |-- Trailing stop triggered -> SELL
  |-- Reversal signal detected -> SELL
  |-- Time-based exit (optional)
       |
[Trade Logger + Dashboard]

Data Source Strategy

Source Use Case Cost Notes
FinnHub Real-time quotes, news, insider trades Free tier Already wired in
yfinance OHLCV history, fundamentals fallback Free Add as secondary/fallback
Polygon.io Higher-quality tick data if needed later Paid Skip for now
Alpha Vantage Alternative fundamentals Free tier Keep as backup

Both FinnHub and yfinance have excellent US coverage. yfinance is the primary fallback when FinnHub returns empty or errors.


Watchlist Design

Starter watchlist (~36 quality tickers across sectors):

{
  "large_cap": ["AAPL", "MSFT", "NVDA", "GOOGL", "AMZN", "META", "JPM", "UNH", "V", "MA"],
  "growth": ["CRWD", "SNOW", "NET", "DDOG", "SMCI", "ARM", "PLTR"],
  "value": ["BRK-B", "JNJ", "PG", "KO", "WMT", "HD", "MCD"],
  "financials": ["GS", "MS", "BAC", "C", "WFC"],
  "energy": ["XOM", "CVX", "COP", "SLB"]
}

Liquidity Filter — only analyze stocks with sufficient volume to avoid slippage:

MIN_AVG_DAILY_VOLUME = 1_000_000   # 1M shares/day minimum
MIN_MARKET_CAP = 2_000_000_000     # $2B market cap minimum

Market Hours and Scheduling

  • NYSE/NASDAQ: 9:30 AM - 4:00 PM ET
  • Pre-market analysis run: 8:00-9:15 AM ET (agents analyze, build signals)
  • Market open execution window: 9:30-10:30 AM ET (buy signals fire here)
  • Monitoring loop: runs every 5 min during market hours
  • After-hours: position review, log summary, prep next day's watchlist

Execution Layer Architecture

New module: execution/

execution/
├── broker_interface.py    <-- Abstract base class
├── alpaca_broker.py       <-- Alpaca implementation
├── ibkr_broker.py         <-- IBKR implementation (Phase 7)
├── paper_broker.py        <-- Local simulation (no API needed)
└── order_manager.py       <-- Order lifecycle tracking

Start with PaperBroker — a pure Python simulation that tracks positions in a local SQLite database. This lets you test the full loop without any API dependency.


Broker Interface

Define this first. All broker implementations (Paper, Alpaca, IBKR) implement this:

class BrokerInterface:
    def place_market_buy(self, ticker: str, qty: int) -> Order: ...
    def place_market_sell(self, ticker: str, qty: int) -> Order: ...
    def get_positions(self) -> list[Position]: ...
    def get_account(self) -> AccountInfo: ...
    def cancel_order(self, order_id: str) -> bool: ...

Position Sizing Formula

Start simple. Scale by conviction, cap at a percentage of account value:

def calculate_position_size(account_value, conviction_score, price, max_position_pct=0.05):
    max_dollars = account_value * max_position_pct
    dollars_to_invest = max_dollars * conviction_score
    shares = int(dollars_to_invest / price)
    return max(1, shares)

Conviction Scoring Design

The agents currently produce a BUY/SELL/HOLD decision with rationale. Extend this to produce a conviction score (0-100).

Weighted scoring approach:

CONVICTION_WEIGHTS = {
    "technical":    0.25,   # RSI, MACD, moving average signals
    "fundamental":  0.25,   # P/E, growth, financial health
    "sentiment":    0.20,   # Social/news mood
    "bull_bear":    0.30,   # Debate outcome (most decisive)
}

def calculate_conviction(agent_signals: dict) -> float:
    score = 0
    for agent, weight in CONVICTION_WEIGHTS.items():
        # Each agent returns -1.0 (strong sell) to +1.0 (strong buy)
        score += agent_signals[agent] * weight
    return score  # -1.0 to +1.0

Auto-Buy Rules

BUY_THRESHOLD = 0.65      # Must have >65% conviction to buy
MIN_AGENTS_AGREE = 3      # At least 3 of 4 analyst agents must agree direction
MAX_POSITIONS = 10        # Never hold more than 10 stocks at once
COOLDOWN_HOURS = 24       # Don't re-analyze same stock within 24h of last trade

Agent Prompt Additions

Add to each analyst agent's system prompt for structured output parsing:

At the end of your analysis, always output a JSON block:
{"signal": "BUY|SELL|HOLD", "conviction": 0.85, "key_reason": "..."}

Then parse this structured output in the graph state.


Exit Conditions and Rules

Condition Rule Notes
Profit target Exit when gain >= 15% Hard target
Trailing stop Exit when price drops 7% from highest point reached Locks in gains
Stop loss Exit when loss >= 8% from entry Hard floor
Reversal signal Exit when Technical Agent says strong SELL Agent-driven exit
Time-based Exit after 30 days if none of above triggered Prevents zombie positions

Monitor Loop

New module: monitoring/

monitoring/
├── position_monitor.py    <-- Main loop
├── exit_rules.py          <-- All exit condition logic
├── price_feed.py          <-- Real-time price fetching
└── alert_manager.py       <-- Notifications

Core loop pseudocode:

async def monitor_loop(interval_seconds=300):  # Check every 5 min
    while True:
        positions = broker.get_positions()
        for position in positions:
            current_price = price_feed.get_price(position.ticker)
            exit_rule = exit_rules.check(position, current_price)
            if exit_rule.should_exit:
                broker.place_market_sell(position.ticker, position.qty)
                log_exit(position, exit_rule.reason)
        await asyncio.sleep(interval_seconds)

Trailing Stop Implementation

class Position:
    ticker: str
    entry_price: float
    qty: int
    highest_price: float       # Track this, update every check
    entry_time: datetime

def check_trailing_stop(position, current_price, trail_pct=0.07):
    if current_price > position.highest_price:
        position.highest_price = current_price
        # Save updated high to DB

    trail_level = position.highest_price * (1 - trail_pct)
    if current_price <= trail_level:
        return ExitSignal(should_exit=True, reason="TRAILING_STOP")
    return ExitSignal(should_exit=False)

Reversal Detection

Cost-effective approach: don't run the full 7-agent pipeline for monitoring. Only run the Technical Analyst:

async def check_reversal(position):
    tech_signal = technical_agent.quick_check(position.ticker)
    if tech_signal.rsi > 75 and tech_signal.macd_cross == "BEARISH":
        return ExitSignal(should_exit=True, reason="REVERSAL_SIGNAL")

Portfolio Guard Design

Hard limits:

PORTFOLIO_RULES = {
    "max_positions": 10,              # Never hold more than 10 stocks
    "max_sector_exposure": 0.30,      # No single sector > 30% of portfolio
    "max_single_position": 0.08,      # No single stock > 8% of portfolio
    "max_single_exchange": 0.60,      # No more than 60% in NYSE or NASDAQ alone
    "daily_loss_limit": -0.03,        # Stop all buys if down 3% on the day
    "weekly_loss_limit": -0.07,       # Stop all activity if down 7% in a week
    "cash_reserve": 0.10,             # Always keep 10% cash
}

Guard class:

class PortfolioGuard:
    def can_open_position(self, ticker, proposed_size) -> tuple[bool, str]:
        checks = [
            self._check_max_positions(),
            self._check_sector_exposure(ticker),
            self._check_daily_loss(),
            self._check_cash_reserve(proposed_size),
        ]
        failures = [r for r in checks if not r.passed]
        if failures:
            return False, failures[0].reason
        return True, "OK"

Insert PortfolioGuard.can_open_position() between Fund Manager approval and order execution.


Database Schema

Use SQLite to start (zero infrastructure). Migrate to Postgres later if needed.

-- All positions (open and closed)
CREATE TABLE positions (
    id INTEGER PRIMARY KEY,
    ticker TEXT NOT NULL,
    exchange TEXT NOT NULL,        -- 'NYSE' or 'NASDAQ'
    entry_price REAL NOT NULL,
    entry_time DATETIME NOT NULL,
    qty INTEGER NOT NULL,
    highest_price REAL,            -- For trailing stop
    exit_price REAL,
    exit_time DATETIME,
    exit_reason TEXT,              -- 'PROFIT_TARGET', 'TRAILING_STOP', etc.
    status TEXT DEFAULT 'OPEN'     -- 'OPEN' or 'CLOSED'
);

-- All agent signals (for analysis and debugging)
CREATE TABLE signals (
    id INTEGER PRIMARY KEY,
    ticker TEXT NOT NULL,
    timestamp DATETIME NOT NULL,
    conviction_score REAL,
    agent_decision TEXT,           -- JSON of each agent's output
    action_taken TEXT,             -- 'BOUGHT', 'SKIPPED', 'REJECTED_BY_GUARD'
    skip_reason TEXT
);

-- Daily portfolio snapshots
CREATE TABLE portfolio_snapshots (
    id INTEGER PRIMARY KEY,
    snapshot_date DATE NOT NULL,
    total_value REAL,
    cash REAL,
    num_positions INTEGER,
    daily_pnl REAL,
    daily_pnl_pct REAL
);

-- System events and errors
CREATE TABLE system_log (
    id INTEGER PRIMARY KEY,
    timestamp DATETIME NOT NULL,
    level TEXT,                    -- 'INFO', 'WARNING', 'ERROR'
    component TEXT,
    message TEXT
);

Dashboard Specs

Stack: Streamlit + Plotly + Pandas. Run locally.

pip install streamlit plotly pandas

Page 1: Portfolio Overview

  • Current positions table (ticker, entry price, current price, P&L%, trailing stop level)
  • Total portfolio value + daily P&L
  • Cash available
  • Sector exposure chart

Page 2: Signal Feed

  • Live log of agent decisions (last 50)
  • Conviction scores with color coding (green = strong buy, yellow = weak, gray = hold)
  • Pending signals not yet executed

Page 3: Trade History

  • All closed trades with entry/exit/reason/profit
  • Win rate, average return, best/worst trade
  • Monthly return chart

Page 4: Agent Monitor

  • Which tickers were analyzed today
  • Agent breakdown per analysis (which agents said BUY vs SELL)
  • API costs tracker (LLM call count x estimated cost)

Target File Structure

TradeDog/
├── tradingagents/              <-- Upstream framework (minimal changes)
│   ├── agents/
│   ├── dataflows/
│   │   ├── yfinance_fallback.py <-- NEW: Fallback when FinnHub fails
│   │   └── data_validator.py   <-- NEW: Validates data quality
│   ├── graph/trading_graph.py
│   └── default_config.py
│
├── execution/                  <-- NEW: Order execution
│   ├── broker_interface.py
│   ├── paper_broker.py
│   ├── alpaca_broker.py
│   ├── ibkr_broker.py
│   └── order_manager.py
│
├── monitoring/                 <-- NEW: Position monitoring
│   ├── position_monitor.py
│   ├── exit_rules.py
│   ├── price_feed.py
│   └── alert_manager.py
│
├── portfolio/                  <-- NEW: Risk management
│   ├── portfolio_guard.py
│   ├── conviction_gate.py
│   └── position_sizer.py
│
├── database/                   <-- NEW: Data persistence
│   ├── schema.sql
│   ├── db.py
│   └── models.py
│
├── dashboard/                  <-- NEW: Streamlit UI
│   └── app.py
│
├── watchlist/                  <-- NEW: Curated tickers
│   ├── watchlist.json          <-- NYSE/NASDAQ curated tickers
│   └── sector_map.json         <-- Ticker -> sector classification
│
├── scheduler/                  <-- NEW: Orchestrates daily run
│   └── main_loop.py
│
├── tests/
│   └── ...
│
├── docs/
│   ├── architecture.md
│   ├── agent_contracts.md
│   ├── design_reference.md
│   └── TradeDog_Roadmap.md
│
├── .env
├── main.py
└── requirements.txt

Broker Setup Commands

Alpaca (recommended for paper + live):

pip install alpaca-py

Free paper trading API, full NYSE/NASDAQ coverage, no account minimums, clean REST API + Python SDK. Same SDK for paper and live — just swap credentials.

IBKR (alternative for live trading):

pip install ib_insync
# Requires IBKR TWS or Gateway running locally

Build IBKRBroker implementing the same BrokerInterface. Switching brokers is a config change — the rest of the system doesn't care.


Cost Estimate

Item Cost
LLM API (Claude Sonnet for analysts, Opus for Trader/Risk) ~$30-80/mo
FinnHub free tier $0
yfinance $0
Alpaca paper trading $0
IBKR live account $0 (no monthly fee)
Hosting (run on your laptop) $0

Keep costs low by: analyzing each ticker once per day (not per minute), using Claude Haiku for the analyst agents, and only using a more powerful model for the final Trader and Risk Manager decisions.


US Regulatory Note

For personal automated trading in a US brokerage account, you operate under standard retail trading rules. If you make more than 3 day trades in a 5-day rolling window with under $25,000 in the account, you'll trigger Pattern Day Trader rules. Since TradeDog is a swing trading system (holding for days to weeks), this typically isn't an issue — but keep it in mind when sizing up.