TradingAgents/docs/superpowers/specs/2026-03-21-polymarket-agent...

14 KiB

TradingAgents → Polymarket Prediction Agent Design

Overview

Fork the TradingAgents multi-agent financial analysis framework to analyze Polymarket prediction markets. The system accepts any event category (politics, crypto, sports, economics, etc.), produces a structured YES/NO/SKIP recommendation with confidence and edge calculations, and outputs analysis reports.

Phase 1: Analysis and reporting only (human executes bets manually). Phase 2 (future): Automated betting via Relayer API.

Approach

Incremental replacement (Approach A). Modify the existing codebase layer by layer:

  1. Data layer (tool functions → Polymarket API)
  2. Agent prompts (stock → prediction market context)
  3. Output format (BUY/SELL/HOLD → YES/NO/SKIP + structured JSON)
  4. CLI (ticker input → event URL/scan)

1. Data Layer

Replace tradingagents/agents/utils/agent_utils.py tool functions with Polymarket API calls.

Tool Mapping

Old Tool New Tool API Source Description
get_stock_data get_market_data Gamma + CLOB Event/market metadata, current price, spread, volume
get_indicators get_price_history CLOB prices-history Price timeseries with self-computed trend indicators
get_news get_event_news Tavily API Event keyword-based web search
get_global_news get_global_news Tavily API Macro news (similar to existing)
get_insider_transactions get_whale_activity Data API holders + trades Whale/smart money position changes
get_fundamentals get_event_details Gamma events/{id} Resolution criteria, deadline, description, resolutionSource
get_balance_sheet get_orderbook CLOB book Orderbook depth (bid/ask distribution)
get_cashflow get_market_stats Data API openInterest + Gamma OI, volume trends, liquidity
get_income_statement get_leaderboard_signals Data API leaderboard Top trader performance + market positions

New Tools

Tool API Description
get_social_sentiment X API / Reddit API Social sentiment for event keywords
search_markets Gamma events + filters Auto-scan: discover events by volume, deadline, category

Data Flow

User input (event URL/ID or auto-scan)
    ↓
search_markets or URL parsing → event_id, token_ids extraction
    ↓
Each Analyst receives tool access for that event

Multi-Market Events

Polymarket events can contain multiple markets (e.g., "Who will win?" has YES/NO tokens per candidate). Handling:

  • Single-market events: Analyze directly.
  • Multi-market events: CLI displays the list of markets within the event, user selects one specific market to analyze. Each market is a binary YES/NO outcome.

Error Handling

Polymarket APIs (Gamma, CLOB, Data) can fail due to rate limits, downtime, or unexpected data.

  • Retry with exponential backoff (max 3 retries) for transient failures.
  • Timeout: 30s per API call.
  • If a tool fails after retries, the analyst produces a partial report noting which data was unavailable.

2. Agent Structure

Phase 1: 4 Analysts (Parallel)

Agent Tools Output Field Prompt Focus
Odds Analyst get_market_data, get_price_history, get_orderbook odds_report Price trends, orderbook asymmetry, volume patterns, smart money direction
News Analyst get_event_news, get_global_news news_report Latest event-related news, macro impact, timeline analysis
Social Analyst get_social_sentiment, get_whale_activity sentiment_report Social opinion + whale/top trader position direction
Event Analyst get_event_details, get_market_stats, get_leaderboard_signals event_report Resolution criteria interpretation, deadline, base probability estimation, similar event comparison

Phase 2: 3-Way Debate (Sequential)

Expanded from Bull/Bear 2-way to YES/NO/Timing 3-way debate.

Agent Role Memory
YES Advocate Argues for event occurrence. Based on existing Bull. Yes
NO Advocate Argues against event occurrence. Based on existing Bear. Yes
Timing Advocate "Might be right, but not now" — market timing / probability distortion perspective Yes
Research Manager Judges 3-way debate → investment_plan (YES/NO/SKIP + confidence) Yes

Debate flow:

YES → NO → Timing → YES → NO → Timing → ... → Judge

Phase 3: Trader

Input: investment_plan + 4 analyst reports. Output: trader_plan with YES/NO/SKIP + confidence + edge + position_size.

Phase 4: Risk Debate (Existing structure retained)

Aggressive/Conservative/Neutral 3-way debate → Risk Manager judgment. Prompts modified from stock to prediction market context only.

Final output: final_decision in JSON format.


3. AgentState Changes

class InvestDebateState(TypedDict):
    yes_history: str           # was bull_history
    no_history: str            # was bear_history
    timing_history: str        # new
    history: str
    current_yes_response: str  # per-advocate responses (mirrors RiskDebateState)
    current_no_response: str
    current_timing_response: str
    latest_speaker: str        # new — mirrors RiskDebateState pattern
    judge_decision: str
    count: int

class RiskDebateState(TypedDict):
    # unchanged — Aggressive/Conservative/Neutral retained
    aggressive_history: str
    conservative_history: str
    neutral_history: str
    history: str
    latest_speaker: str
    current_aggressive_response: str
    current_conservative_response: str
    current_neutral_response: str
    judge_decision: str
    count: int

class AgentState(MessagesState):
    event_id: str              # was company_of_interest
    event_question: str        # new — "Will X happen?"
    trade_date: str
    sender: str
    odds_report: str           # was market_report
    sentiment_report: str      # retained
    news_report: str           # retained
    event_report: str          # was fundamentals_report
    investment_debate_state: InvestDebateState
    investment_plan: str
    trader_plan: str           # was trader_investment_plan
    risk_debate_state: RiskDebateState
    final_decision: str        # was final_trade_decision

4. Graph Workflow Changes

setup.py — Node composition

Old: Market → Social → News → Fundamentals (parallel)
     → Bull ↔ Bear (2-way cycle) → Research Manager

New: Odds → Social → News → Event (parallel)
     → YES ↔ NO ↔ Timing (3-way cycle) → Research Manager

conditional_logic.py — Debate routing

Mirrors the existing risk debate pattern using latest_speaker inspection (not count-modulo). Rename analyst methods: should_continue_marketshould_continue_odds, should_continue_fundamentalsshould_continue_event.

def should_continue_debate(self, state):  # method on ConditionalLogic class
    count = state["investment_debate_state"]["count"]
    if count >= 3 * self.max_debate_rounds:  # 3 speakers per round
        return "Research Manager"
    latest = state["investment_debate_state"].get("latest_speaker", "")
    if latest.startswith("YES"):
        return "NO Advocate"
    elif latest.startswith("NO"):
        return "Timing Advocate"
    else:  # Initial entry or after Timing → start with YES
        return "YES Advocate"

setup.py — 3-Way debate edge definitions

Each debater node gets conditional edges to the next speaker or Research Manager:

# Last analyst's Msg Clear → YES Advocate (was Bull Researcher)
graph.add_edge(f"{last_analyst}_clear", "YES Advocate")

# YES Advocate → {NO Advocate, Research Manager}
graph.add_conditional_edges("YES Advocate", should_continue_debate,
    {"NO Advocate": "NO Advocate", "Research Manager": "Research Manager"})

# NO Advocate → {Timing Advocate, Research Manager}
graph.add_conditional_edges("NO Advocate", should_continue_debate,
    {"Timing Advocate": "Timing Advocate", "Research Manager": "Research Manager"})

# Timing Advocate → {YES Advocate, Research Manager}
graph.add_conditional_edges("Timing Advocate", should_continue_debate,
    {"YES Advocate": "YES Advocate", "Research Manager": "Research Manager"})

3-Way advocate state preservation

Each advocate node reads all three histories but only writes its own. Pseudocode:

# In YES Advocate node:
def yes_node(state):
    # Read: no_history, timing_history, yes_history (for context)
    # Write: yes_history (append), current_response, latest_speaker = "YES Advocate"
    # Preserve: no_history, timing_history unchanged

# Same pattern for NO and Timing advocates

propagation.py — Initial state

  • company_name parameter → event_id + event_question
  • Report field names updated: market_reportodds_report, fundamentals_reportevent_report
  • InvestDebateState gains timing_history: "", latest_speaker: ""
  • propagate() signature: propagate(self, event_id, event_question, trade_date)

signal_processing.py — Output parsing

Old: Extract BUY/SELL/HOLD from text. New: Extract structured JSON using Pydantic validation:

from pydantic import BaseModel

class PredictionDecision(BaseModel):
    action: Literal["YES", "NO", "SKIP"]
    confidence: float    # 0.0 ~ 1.0
    edge: float          # estimated probability - market price
    position_size: float # recommended bet size
    reasoning: str
    time_horizon: str

Use llm.with_structured_output(PredictionDecision) where supported, fallback to JSON extraction + Pydantic parse.

trading_graph.py — Memory/tool initialization

  • bull_memoryyes_memory, bear_memoryno_memory, new timing_memory
  • _create_tool_nodes() maps to new Polymarket tools
  • GraphSetup.__init__ signature updated to accept timing_memory
  • propagate() updated: signature, field name references (final_decision not final_trade_decision)
  • _log_state() updated: all field names changed (odds_report, event_report, yes_history, no_history, timing_history, trader_plan, final_decision)
  • reflect_and_remember() updated: calls reflect_yes_advocate, reflect_no_advocate, reflect_timing_advocate

reflection.py

  • Rename methods: reflect_bull_researcherreflect_yes_advocate, reflect_bear_researcherreflect_no_advocate
  • Add: reflect_timing_advocate (same pattern, new timing_memory)
  • Update _extract_current_situation: market_reportodds_report, fundamentals_reportevent_report

agents/init.py

Update exports: remove create_bull_researcher, create_bear_researcher. Add create_yes_advocate, create_no_advocate, create_timing_advocate. Rename create_market_analystcreate_odds_analyst, create_fundamentals_analystcreate_event_analyst.


5. CLI Changes

Input Flow

Old: Ticker → Date → Analysts → Depth → Provider → Model
New: Mode → Event → Analysts → Depth → Provider → Model

Step 1: Mode Selection

  • Manual — Enter event URL or ID directly
  • Scan — Auto-discover by filters (category, min volume, deadline, etc.)

Step 2: Event Specification

  • Manual: Parse URL (polymarket.com/event/xxx → event_id) or direct ID input
  • Scan: Call search_markets → select from result list

Steps 3-6: Same flow, but analyst keys change: marketodds, fundamentalsevent. CLI constants updated:

  • ANALYST_MAPPING: {"odds": "Odds Analyst", "social": "Social Analyst", "news": "News Analyst", "event": "Event Analyst"}
  • REPORT_SECTIONS: odds_report, event_report etc.
  • ANALYST_ORDER: ["odds", "social", "news", "event"]

Output Display

  • MessageBuffer agent names and report sections updated to new names
  • FIXED_AGENTS: Research Team adds "Timing Advocate"["YES Advocate", "NO Advocate", "Timing Advocate", "Research Manager"]
  • REPORT_SECTIONS field names: odds_report, event_report, trader_plan, final_decision
  • Final result: Structured JSON decision + reports

Report Saving

reports/{event_id}_{timestamp}/
├── 1_analysts/
│   ├── odds.md
│   ├── sentiment.md
│   ├── news.md
│   └── event.md
├── 2_research/
│   ├── yes_advocate.md
│   ├── no_advocate.md
│   ├── timing_advocate.md
│   └── manager.md
├── 3_trading/
│   └── trader.md
├── 4_risk/
│   ├── aggressive.md
│   ├── conservative.md
│   └── neutral.md
├── 5_risk_manager/
│   └── decision.json
└── complete_report.md

6. Configuration & Dependencies

default_config.py

DEFAULT_CONFIG = {
    # Retained
    "llm_provider": "openrouter",
    "deep_think_llm": "z-ai/glm-4.5-air:free",
    "quick_think_llm": "nvidia/nemotron-3-nano-30b-a3b:free",
    "max_debate_rounds": 1,        # 1 round = 3 turns in 3-way debate
    "max_risk_discuss_rounds": 1,

    # New
    "tavily_api_key": None,         # loaded from TAVILY_API_KEY env
    "twitter_bearer_token": None,   # loaded from env
    "reddit_client_id": None,       # loaded from env
    "polymarket_relayer_key": None, # Phase 2 auto-betting (unused in Phase 1)

    # Auto-scan defaults
    "scan_defaults": {
        "min_volume_24h": 10000,
        "min_liquidity": 5000,
        "max_days_to_end": 30,
        "categories": [],           # empty = all
    },
}

.env Additions

TAVILY_API_KEY=...
TWITTER_BEARER_TOKEN=...      # optional
REDDIT_CLIENT_ID=...          # optional
REDDIT_CLIENT_SECRET=...      # optional

New Dependencies (requirements.txt)

py-clob-client          # Polymarket CLOB API client
tavily-python           # Web search
tweepy                  # X API (optional)
praw                    # Reddit API (optional)

Social APIs (tweepy, praw) gracefully skip when keys are absent — the corresponding analyst produces a "no social data available" report instead.


7. Reflection & Memory (Phase 1)

In Phase 1 (analysis only, no bet execution), there are no realized returns to learn from. reflect_and_remember is disabled in Phase 1. Memory will be seeded when Phase 2 (auto-betting) provides actual outcomes to reflect on. Alternatively, a post-resolution accuracy check can be added as a Phase 1.5 enhancement: after an event resolves, compare the agent's prediction against the outcome and store reflections.


Non-Goals (Phase 1)

  • Automated bet execution (Phase 2)
  • Backtesting framework
  • Multi-event portfolio optimization
  • Real-time WebSocket streaming (batch analysis only)