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:
- Data layer (tool functions → Polymarket API)
- Agent prompts (stock → prediction market context)
- Output format (BUY/SELL/HOLD → YES/NO/SKIP + structured JSON)
- 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_market → should_continue_odds, should_continue_fundamentals → should_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_nameparameter →event_id+event_question- Report field names updated:
market_report→odds_report,fundamentals_report→event_report InvestDebateStategainstiming_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_memory→yes_memory,bear_memory→no_memory, newtiming_memory_create_tool_nodes()maps to new Polymarket toolsGraphSetup.__init__signature updated to accepttiming_memorypropagate()updated: signature, field name references (final_decisionnotfinal_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: callsreflect_yes_advocate,reflect_no_advocate,reflect_timing_advocate
reflection.py
- Rename methods:
reflect_bull_researcher→reflect_yes_advocate,reflect_bear_researcher→reflect_no_advocate - Add:
reflect_timing_advocate(same pattern, newtiming_memory) - Update
_extract_current_situation:market_report→odds_report,fundamentals_report→event_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_analyst → create_odds_analyst, create_fundamentals_analyst → create_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 directlyScan— 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: market → odds, fundamentals → event. CLI constants updated:
ANALYST_MAPPING:{"odds": "Odds Analyst", "social": "Social Analyst", "news": "News Analyst", "event": "Event Analyst"}REPORT_SECTIONS:odds_report,event_reportetc.ANALYST_ORDER:["odds", "social", "news", "event"]
Output Display
MessageBufferagent names and report sections updated to new namesFIXED_AGENTS: Research Team adds"Timing Advocate"→["YES Advocate", "NO Advocate", "Timing Advocate", "Research Manager"]REPORT_SECTIONSfield 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)