Skip to content

Instantly share code, notes, and snippets.

@myselfshravan
Created March 15, 2026 14:26
Show Gist options
  • Select an option

  • Save myselfshravan/e89770d8cfde54f937bcdf4f23677052 to your computer and use it in GitHub Desktop.

Select an option

Save myselfshravan/e89770d8cfde54f937bcdf4f23677052 to your computer and use it in GitHub Desktop.
Multi-Agent AI Trading System

sudo-trade

trades need sudo privileges

AI-powered algorithmic trading system for Indian markets. Multiple AI agents collaborate during market hours — researching, screening, debating bull vs bear, analyzing, and executing trades — all through a plugin architecture where every layer is independently swappable.

Paper trading first. Real money when it's ready.

How It Works

flowchart LR
    subgraph Agents
        R[Research Agent]
        SC[Screener Agent]
        DB[Bull + Bear<br/>Debaters]
        AN[Analysis Agent]
        EX[Execution Agent]
    end

    subgraph Core
        MA[Master Agent]
        EB[EventBus]
    end

    subgraph Brokers
        GR[Groww<br/>DATA]
        KT[Kite<br/>EXECUTION]
    end

    MA -- orchestrates --> R & SC & DB & AN & EX
    R -- research:complete --> EB
    SC -- screened --> EB
    DB -- debate:argument --> EB
    AN -- analysis:complete --> EB
    EB -- trade:requested --> EX
    GR -- market data --> EB
    EX -- place_order --> KT
    KT -- executes --> NSE[NSE / BSE]
Loading

Every arrow is an event on the EventBus. Agents never import each other — the MasterAgent orchestrates the pipeline. Subscribe price data from Groww, execute trades via Kite. Or use one broker for both.

Multi-Agent Pipeline

The MasterAgent runs a phase-based pipeline during IST market hours:

sequenceDiagram
    participant S as Scheduler
    participant M as Master
    participant R as Researcher
    participant SC as Screener
    participant Bull as Bull Debater
    participant Bear as Bear Debater
    participant J as Consensus Judge
    participant A as Analyst
    participant E as Executor

    S->>M: PRE_MARKET (9:00)
    M->>R: research task
    R->>M: findings (news, social, filings)

    S->>M: OPENING (9:15)
    M->>SC: screen universe
    SC->>M: top 5 picks

    loop For each pick
        M->>Bull: argue bull case
        M->>Bear: argue bear case
        Bull->>M: round 1 argument
        Bear->>M: round 1 argument
        Bull->>M: round 2 rebuttal
        Bear->>M: round 2 rebuttal
        M->>J: evaluate debate
        J->>M: verdict (strong_buy/buy/hold/sell)
    end

    M->>A: deep analysis (if verdict strong)
    A->>M: signals (sentiment + technical)
    M->>E: trade signal
    E->>M: executed (or queued for approval)
Loading

Agent Roles

Agent What it does
ResearchAgent Scans news, filings, social media via Provider plugins. LLM extracts market-moving insights
ScreenerAgent Fetches quotes from Groww, applies quantitative filters (momentum, volume spikes), LLM ranks qualitatively
DebateAgent (x2) Bull and bear agents argue with evidence. Multi-round: initial case → rebuttal. System prompts force each side
ConsensusEngine Neutral "judge" LLM evaluates both sides. Produces verdict: strong_buy / buy / hold / sell / strong_sell
AnalysisAgent Runs all analyzers (sentiment, technical) in parallel on confirmed picks
ExecutionAgent LLM decides order type, sizing, timing. Human approval by default (auto_execute = false)
MasterAgent The brain. Subscribes to all events, orchestrates the pipeline, makes final go/no-go calls

Market Phases

Phase IST Activity
PRE_MARKET 9:00–9:15 Research overnight news
OPENING 9:15–9:30 Screen for opportunities, start debates
MORNING 9:30–12:00 Execute trades, monitor positions
AFTERNOON 12:00–14:00 Re-evaluate, scan new opportunities
CLOSING 14:00–15:30 Close intraday positions
POST_MARKET 15:30–16:00 Daily summary report

Skips weekends and NSE holidays automatically.

Architecture

graph TB
    subgraph Core
        ENG[Engine]
        EB[EventBus]
        PR[PluginRegistry]
        CFG[Config]
    end

    subgraph Agents
        MA[MasterAgent]
        RES[Researcher]
        SCR[Screener]
        DBT[Debaters<br/>Bull + Bear]
        ANL[Analyst]
        EXA[Executor Agent]
        SCHED[MarketScheduler]
        COST[CostTracker]
        MEM[SharedState]
    end

    subgraph Plugins
        BRK[Brokers<br/>Groww · Kite]
        PRV[Providers<br/>News · Social]
        ANA[Analyzers<br/>Sentiment · Technical]
        LLM[LLM Client<br/>Claude · GPT · Gemini]
        STR[Strategies<br/>MA Crossover · Custom]
        EXE[Executors<br/>Paper · Live]
        INT[Interfaces<br/>API · WebSocket]
    end

    ENG --> EB & PR & CFG
    PR --> BRK & PRV & ANA & LLM & STR & EXE & INT
    MA --> RES & SCR & DBT & ANL & EXA
    MA --> SCHED & COST & MEM
    BRK <--> EB
    MA <--> EB
    RES <--> EB
    SCR <--> EB
    INT <--> EB
Loading

Everything is a plugin. Agents are plugins too — they register under the "agent" category and communicate via EventBus. The MasterAgent coordinates; individual agents are independently swappable.

Event Flow

The EventBus is the contract. All communication is events:

Event Payload Emitted By
market:tick symbol, price, timestamp Broker, Backtester
market:quote symbol, price, volume, ohlc Broker
signal:generated Signal Analyzers
agent:research:complete symbols, findings ResearchAgent
agent:screened symbols, picks ScreenerAgent
agent:debate:argument position, symbol DebateAgent
agent:debate:complete symbol, consensus MasterAgent
agent:analysis:complete symbols, signals AnalysisAgent
agent:trade:requested TradeSignal MasterAgent
agent:trade:executed TradeResult ExecutionAgent
agent:trade:pending signal, pending_count ExecutionAgent
schedule:phase_change phase, old_phase, time Scheduler

Multi-Broker Design

flowchart LR
    subgraph "BrokerRole.DATA"
        G[Groww]
    end
    subgraph "BrokerRole.EXECUTION"
        K[Kite / Zerodha]
    end

    G -- "market:tick<br/>market:quote<br/>historical" --> EB[EventBus]
    EB -- trade:signal --> K
    K -- order execution --> NSE[NSE / BSE]
Loading
  • DATA — market quotes, historical candles, WebSocket streaming
  • EXECUTION — order placement and management
  • BOTH — everything

Agents don't know which broker handles what. They call data_broker for data and executor for trades. Routing is automatic.

API Interface

HTTP + WebSocket server for external tools (Claude Code, dashboards, mobile apps):

GET  /status             — all agent states + market phase
GET  /signals            — latest analysis signals
GET  /consensus/:symbol  — debate result for a symbol
GET  /pending            — pending trades awaiting approval
GET  /cost               — LLM cost report
POST /task               — submit task {"type": "research|screen|debate|analyze"}
POST /trade/approve/:idx — approve a pending trade
POST /trade/reject/:idx  — reject a pending trade
WS   /ws                 — real-time event stream
# Submit a research task
curl -X POST http://localhost:8080/task \
  -H "Content-Type: application/json" \
  -d '{"type": "screen", "symbols": ["RELIANCE", "TCS", "INFY"]}'

# Check agent status
curl http://localhost:8080/status

# Approve a pending trade
curl -X POST http://localhost:8080/trade/approve/0

Per-Agent LLM Routing

Each agent can use a completely different LLM provider, model, and API key:

flowchart TB
    subgraph "LLM Router"
        R[Router]
    end

    subgraph "Agent → Model mapping"
        MA[Master Agent] -- "claude-opus-4-6<br/>Anthropic API" --> R
        DB[Debaters] -- "claude-opus-4-6<br/>Anthropic API" --> R
        RS[Researcher] -- "gpt-4o-mini<br/>OpenAI API" --> R
        SC[Screener] -- "gpt-4o-mini<br/>OpenAI API" --> R
        AN[Analyst] -- "claude-opus-4-6<br/>Anthropic API" --> R
    end

    R --> A1[api.anthropic.com]
    R --> A2[api.openai.com]
    R --> A3[local LLM / any endpoint]
Loading

Configured via env vars — no code changes needed:

# Default for all agents
AI_BASE_URL=https://api.openai.com/v1
AI_MODEL=gpt-4
AI_API_KEY=sk-...

# Master uses Claude Opus for final decisions (different provider + key)
AI_MASTER_BASE_URL=https://api.anthropic.com/v1
AI_MASTER_MODEL=claude-opus-4-6
AI_MASTER_API_KEY=sk-ant-...

# Debaters also use Claude (creative argumentation)
AI_DEBATER_MODEL=claude-opus-4-6

# Research + screening use cheaper models
AI_RESEARCHER_MODEL=gpt-4o-mini
AI_SCREENER_MODEL=gpt-4o-mini

Pattern: AI_{AGENT}_BASE_URL, AI_{AGENT}_MODEL, AI_{AGENT}_API_KEY. Falls back to defaults. debater_bull and debater_bear both resolve to DEBATER config.

LLM Cost Management

Multiple agents making LLM calls adds up fast. Built-in cost tracking:

  • Daily budget: configurable limit (default $5/day)
  • Per-agent budgets: master gets $2, others get $1
  • Token tracking: every LLM call logged with cost estimate per model
  • Auto-gating: agents skip LLM calls when budget exhausted, degrade gracefully
  • Per-model pricing: knows token costs for Claude Opus, Sonnet, GPT-4, GPT-4o, GPT-4o-mini
# Check current spend
curl http://localhost:8080/cost

Paper Trading

The PaperExecutor simulates trade execution without risking real money:

  • Tracks capital, positions, and P&L
  • Validates capital for buys, position existence for sells
  • Logs every trade with order IDs (PAPER-XXXXXXXX)
  • ExecutionAgent defaults to auto_execute=false — trades queue for human approval via the API

Switch to live by implementing a LiveExecutor that routes to Kite's place_order(). The agent pipeline stays identical.

Backtesting

The backtester replays historical data through the same EventBus. Strategies are 100% agnostic:

flowchart LR
    CSV[CSV / JSON<br/>Historical Data] --> BT[Backtester]
    BT -- "market:tick<br/>market:quote" --> EB[EventBus]
    EB --> S[Strategy]
    S -- trade:signal --> EB
    EB --> BT
    BT --> R[BacktestResult<br/>P&L · Win Rate<br/>Drawdown · Sharpe]
Loading

Same strategy class, same events, same code path. Live or backtest.

  ══════════════════════════════════════════════════
  Backtest Report: ma_crossover
  ══════════════════════════════════════════════════
  Period:       2024-04-01 → 2025-03-07
  Initial:      ₹1,00,000
  Final:        ₹1,03,420
  Total P&L:    ₹3,420 (+3.4%)
  Trades:       12 (Win: 7, Loss: 5)
  Win Rate:     58.3%
  Max Drawdown: 4.2%
  Sharpe Ratio: 1.14
  ══════════════════════════════════════════════════

Project Structure

sudo-trade/
├── src/
│   ├── core/               # Engine, EventBus, PluginRegistry, Config, Logger
│   ├── agents/             # Multi-agent AI system (12 files)
│   │   ├── master.py       # MasterAgent — orchestrates everything
│   │   ├── researcher.py   # ResearchAgent — news + social scanning
│   │   ├── screener.py     # ScreenerAgent — stock screening
│   │   ├── debater.py      # DebateAgent — bull/bear argumentation
│   │   ├── consensus.py    # ConsensusEngine — neutral judge
│   │   ├── analyst.py      # AnalysisAgent — deep analysis
│   │   ├── executor.py     # ExecutionAgent — trade execution
│   │   ├── scheduler.py    # MarketScheduler — IST phases + holidays
│   │   ├── memory.py       # AgentMemory + SharedState
│   │   └── cost.py         # LLMCostTracker
│   ├── brokers/            # Broker plugins + instrument map + rate limiter
│   │   ├── groww.py        # Groww: REST + WebSocket, TOTP auth, rate-limited
│   │   ├── kite.py         # Kite Connect (stub)
│   │   ├── instruments.py  # Instrument CSV cache + symbol resolution
│   │   └── rate_limiter.py # Token-bucket per-second + per-minute limits
│   ├── analysis/           # Analyzers (sentiment, technical)
│   │   └── sentiment.py    # LLM-powered market sentiment analyzer
│   ├── llm/                # LLM client + router
│   │   ├── client.py       # OpenAI-compatible client via aiohttp
│   │   └── router.py       # LLMRouter — per-agent model/endpoint/key
│   ├── strategy/           # Strategy plugins (signals in, trade decisions out)
│   ├── execution/          # Executors
│   │   └── paper.py        # PaperExecutor — simulated trades, P&L tracking
│   ├── providers/          # Data sources
│   │   └── news.py         # RSS news (MoneyControl, Economic Times)
│   ├── interfaces/         # API + WebSocket interface
│   │   └── api.py          # HTTP + WS API for external agents
│   ├── backtesting/        # Backtest engine, data loaders, fill simulator
│   └── __main__.py         # Entry point — wires everything together
├── scripts/
│   ├── groww_cli.py        # CLI: quote, stream, holdings, positions, historical
│   └── backtest_cli.py     # CLI: run backtests with formatted reports
├── configs/
│   └── default.toml        # Runtime config (agents, brokers, API, LLM budgets)
├── .github/
│   └── workflows/ci.yml    # GitHub Actions: lint + test on push/PR
├── data/                   # Runtime data (gitignored except reference/)
├── docs/                   # Broker API specs, strategy notes
├── logs/                   # Structured JSON logs (gitignored)
└── tests/                  # 39 tests — agents, brokers, backtester, events

Setup

# Clone
git clone https://github.com/myselfshravan/sudo-trade.git
cd sudo-trade

# Install dependencies (Python 3.13+)
uv sync

# Configure credentials
cp .env.example .env
# Edit .env with your API keys

Environment Variables

# Groww (required for live data)
GROWW_API_KEY=your_api_key
GROWW_API_SECRET=your_secret        # or use TOTP below
GROWW_TOTP_SECRET=your_base32_totp  # from Groww 2FA setup

# LLM — default for all agents
AI_BASE_URL=https://api.openai.com/v1  # any OpenAI-compatible endpoint
AI_MODEL=gpt-4                          # model name at that endpoint
AI_API_KEY=your_key                     # or OPENAI_API_KEY / ANTHROPIC_API_KEY

# Per-agent LLM overrides (optional — see "Per-Agent LLM Routing" section)
# AI_MASTER_BASE_URL=https://api.anthropic.com/v1
# AI_MASTER_MODEL=claude-opus-4-6
# AI_MASTER_API_KEY=sk-ant-...
# AI_RESEARCHER_MODEL=gpt-4o-mini

# Agent system
AGENT_DAILY_BUDGET_USD=5.0     # daily LLM spend limit
AGENT_AUTO_EXECUTE=false       # require manual trade approval

# Engine
SUDO_TRADE_MODE=paper  # paper | live

Usage

Run the Engine (Agents + API + Scheduler)

uv run python -m src

This starts all agents, the market scheduler, and the API server at http://localhost:8080.

Live Market Data

# Get a quote
uv run python -m scripts.groww_cli quote RELIANCE

# Batch quotes
uv run python -m scripts.groww_cli quotes RELIANCE INFY TCS

# Stream real-time ticks via WebSocket
uv run python -m scripts.groww_cli stream RELIANCE INFY

# Portfolio
uv run python -m scripts.groww_cli holdings
uv run python -m scripts.groww_cli positions

# Historical candles
uv run python -m scripts.groww_cli historical RELIANCE --interval 1d --days 30

Backtesting

# Run MA crossover backtest on sample data
uv run python -m scripts.backtest_cli \
  --data data/reference/sample_RELIANCE.csv \
  --strategy ma_crossover \
  --capital 100000

# Custom MA windows
uv run python -m scripts.backtest_cli \
  --data data/reference/sample_RELIANCE.csv \
  --strategy ma_crossover \
  --short 10 --long 30

Interact via API

# Trigger a stock screen
curl -X POST http://localhost:8080/task \
  -d '{"type": "screen", "symbols": ["RELIANCE", "TCS"]}'

# Start a debate on a stock
curl -X POST http://localhost:8080/task \
  -d '{"type": "debate", "symbols": ["RELIANCE"]}'

# Check what agents are doing
curl http://localhost:8080/status

# See pending trades
curl http://localhost:8080/pending

# Approve trade #0
curl -X POST http://localhost:8080/trade/approve/0

Development

# Run tests (39 tests)
uv run pytest

# Lint
uv run ruff check .

# Format
uv run ruff format .

# Add a dependency
uv add <package>

# Add a dev dependency
uv add --dev <package>

CI runs automatically on every push/PR via GitHub Actions (lint + test).

Writing a Strategy

A strategy subscribes to events and emits trade signals. This exact code runs on live data and backtester replay:

class MyStrategy:
    name = "my_strategy"

    def __init__(self, events: EventBus):
        self._events = events

    async def start(self) -> None:
        self._events.on("market:tick", self._on_tick)

    async def stop(self) -> None:
        self._events.off("market:tick", self._on_tick)

    async def _on_tick(self, symbol: str, price: float, timestamp: datetime, **_):
        if should_buy:
            signal = TradeSignal(
                action=TradeAction.BUY,
                symbol=symbol,
                quantity=1,
                confidence=0.8,
                reasoning="my reason",
                timestamp=timestamp,  # use event timestamp, not datetime.now()
            )
            await self._events.emit("trade:signal", signal=signal)

Register it: engine.add("strategy", MyStrategy(events))

Stack

  • Python 3.13 with uv for package management
  • asyncio everywhere — async by default
  • aiohttp for HTTP (broker REST APIs, LLM calls, API server)
  • growwapi for Groww broker integration
  • pyotp for TOTP-based authentication
  • ruff for linting + formatting
  • pytest + pytest-asyncio for testing
  • GitHub Actions for CI
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment