feat: update CLI for Polymarket event input and display
- Replace AnalystType enum: MARKET/FUNDAMENTALS → ODDS/EVENT - Replace get_ticker() with get_event_input() supporting manual URL/ID entry and scan mode - Update ANALYST_ORDER and ANALYST_MAPPING throughout for odds/event analysts - Update MessageBuffer FIXED_AGENTS: Bull/Bear Researcher → YES/NO/Timing Advocate - Update REPORT_SECTIONS: market_report/fundamentals_report → odds_report/event_report, trader_investment_plan → trader_plan, final_trade_decision → final_decision - Update get_user_selections(): event-based flow replacing ticker/date steps - Update run_analysis(): graph.propagate with event_id/event_question, chunk processing for yes/no/timing debate history - Update display_complete_report() and save_report_to_disk() with new field names and folder structure (5_risk_manager instead of 5_portfolio) - Update welcome message and progress display to reflect Polymarket workflow Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7e45020dbb
commit
2474f7ad58
259
cli/main.py
259
cli/main.py
|
|
@ -43,7 +43,7 @@ app = typer.Typer(
|
|||
class MessageBuffer:
|
||||
# Fixed teams that always run (not user-selectable)
|
||||
FIXED_AGENTS = {
|
||||
"Research Team": ["Bull Researcher", "Bear Researcher", "Research Manager"],
|
||||
"Research Team": ["YES Advocate", "NO Advocate", "Timing Advocate", "Research Manager"],
|
||||
"Trading Team": ["Trader"],
|
||||
"Risk Management": ["Aggressive Analyst", "Neutral Analyst", "Conservative Analyst"],
|
||||
"Portfolio Management": ["Portfolio Manager"],
|
||||
|
|
@ -51,23 +51,23 @@ class MessageBuffer:
|
|||
|
||||
# Analyst name mapping
|
||||
ANALYST_MAPPING = {
|
||||
"market": "Market Analyst",
|
||||
"odds": "Odds Analyst",
|
||||
"social": "Social Analyst",
|
||||
"news": "News Analyst",
|
||||
"fundamentals": "Fundamentals Analyst",
|
||||
"event": "Event Analyst",
|
||||
}
|
||||
|
||||
# Report section mapping: section -> (analyst_key for filtering, finalizing_agent)
|
||||
# analyst_key: which analyst selection controls this section (None = always included)
|
||||
# finalizing_agent: which agent must be "completed" for this report to count as done
|
||||
REPORT_SECTIONS = {
|
||||
"market_report": ("market", "Market Analyst"),
|
||||
"odds_report": ("odds", "Odds Analyst"),
|
||||
"sentiment_report": ("social", "Social Analyst"),
|
||||
"news_report": ("news", "News Analyst"),
|
||||
"fundamentals_report": ("fundamentals", "Fundamentals Analyst"),
|
||||
"event_report": ("event", "Event Analyst"),
|
||||
"investment_plan": (None, "Research Manager"),
|
||||
"trader_investment_plan": (None, "Trader"),
|
||||
"final_trade_decision": (None, "Portfolio Manager"),
|
||||
"trader_plan": (None, "Trader"),
|
||||
"final_decision": (None, "Portfolio Manager"),
|
||||
}
|
||||
|
||||
def __init__(self, max_length=100):
|
||||
|
|
@ -169,13 +169,13 @@ class MessageBuffer:
|
|||
if latest_section and latest_content:
|
||||
# Format the current section for display
|
||||
section_titles = {
|
||||
"market_report": "Market Analysis",
|
||||
"odds_report": "Odds Analysis",
|
||||
"sentiment_report": "Social Sentiment",
|
||||
"news_report": "News Analysis",
|
||||
"fundamentals_report": "Fundamentals Analysis",
|
||||
"event_report": "Event Analysis",
|
||||
"investment_plan": "Research Team Decision",
|
||||
"trader_investment_plan": "Trading Team Plan",
|
||||
"final_trade_decision": "Portfolio Management Decision",
|
||||
"trader_plan": "Trader Plan",
|
||||
"final_decision": "Portfolio Management Decision",
|
||||
}
|
||||
self.current_report = (
|
||||
f"### {section_titles[latest_section]}\n{latest_content}"
|
||||
|
|
@ -188,12 +188,12 @@ class MessageBuffer:
|
|||
report_parts = []
|
||||
|
||||
# Analyst Team Reports - use .get() to handle missing sections
|
||||
analyst_sections = ["market_report", "sentiment_report", "news_report", "fundamentals_report"]
|
||||
analyst_sections = ["odds_report", "sentiment_report", "news_report", "event_report"]
|
||||
if any(self.report_sections.get(section) for section in analyst_sections):
|
||||
report_parts.append("## Analyst Team Reports")
|
||||
if self.report_sections.get("market_report"):
|
||||
if self.report_sections.get("odds_report"):
|
||||
report_parts.append(
|
||||
f"### Market Analysis\n{self.report_sections['market_report']}"
|
||||
f"### Odds Analysis\n{self.report_sections['odds_report']}"
|
||||
)
|
||||
if self.report_sections.get("sentiment_report"):
|
||||
report_parts.append(
|
||||
|
|
@ -203,9 +203,9 @@ class MessageBuffer:
|
|||
report_parts.append(
|
||||
f"### News Analysis\n{self.report_sections['news_report']}"
|
||||
)
|
||||
if self.report_sections.get("fundamentals_report"):
|
||||
if self.report_sections.get("event_report"):
|
||||
report_parts.append(
|
||||
f"### Fundamentals Analysis\n{self.report_sections['fundamentals_report']}"
|
||||
f"### Event Analysis\n{self.report_sections['event_report']}"
|
||||
)
|
||||
|
||||
# Research Team Reports
|
||||
|
|
@ -214,14 +214,14 @@ class MessageBuffer:
|
|||
report_parts.append(f"{self.report_sections['investment_plan']}")
|
||||
|
||||
# Trading Team Reports
|
||||
if self.report_sections.get("trader_investment_plan"):
|
||||
report_parts.append("## Trading Team Plan")
|
||||
report_parts.append(f"{self.report_sections['trader_investment_plan']}")
|
||||
if self.report_sections.get("trader_plan"):
|
||||
report_parts.append("## Trader Plan")
|
||||
report_parts.append(f"{self.report_sections['trader_plan']}")
|
||||
|
||||
# Portfolio Management Decision
|
||||
if self.report_sections.get("final_trade_decision"):
|
||||
if self.report_sections.get("final_decision"):
|
||||
report_parts.append("## Portfolio Management Decision")
|
||||
report_parts.append(f"{self.report_sections['final_trade_decision']}")
|
||||
report_parts.append(f"{self.report_sections['final_decision']}")
|
||||
|
||||
self.final_report = "\n\n".join(report_parts) if report_parts else None
|
||||
|
||||
|
|
@ -282,12 +282,12 @@ def update_display(layout, spinner_text=None, stats_handler=None, start_time=Non
|
|||
# Group agents by team - filter to only include agents in agent_status
|
||||
all_teams = {
|
||||
"Analyst Team": [
|
||||
"Market Analyst",
|
||||
"Odds Analyst",
|
||||
"Social Analyst",
|
||||
"News Analyst",
|
||||
"Fundamentals Analyst",
|
||||
"Event Analyst",
|
||||
],
|
||||
"Research Team": ["Bull Researcher", "Bear Researcher", "Research Manager"],
|
||||
"Research Team": ["YES Advocate", "NO Advocate", "Timing Advocate", "Research Manager"],
|
||||
"Trading Team": ["Trader"],
|
||||
"Risk Management": ["Aggressive Analyst", "Neutral Analyst", "Conservative Analyst"],
|
||||
"Portfolio Management": ["Portfolio Manager"],
|
||||
|
|
@ -467,9 +467,9 @@ def get_user_selections():
|
|||
|
||||
# Create welcome box content
|
||||
welcome_content = f"{welcome_ascii}\n"
|
||||
welcome_content += "[bold green]TradingAgents: Multi-Agents LLM Financial Trading Framework - CLI[/bold green]\n\n"
|
||||
welcome_content += "[bold green]TradingAgents: Polymarket Prediction Market Analysis Framework - CLI[/bold green]\n\n"
|
||||
welcome_content += "[bold]Workflow Steps:[/bold]\n"
|
||||
welcome_content += "I. Analyst Team → II. Research Team → III. Trader → IV. Risk Management → V. Portfolio Management\n\n"
|
||||
welcome_content += "I. Analyst Team → II. Research Team (YES/NO/Timing Debate) → III. Trader → IV. Risk Management → V. Portfolio Management\n\n"
|
||||
welcome_content += (
|
||||
"[dim]Built by [Tauric Research](https://github.com/TauricResearch)[/dim]"
|
||||
)
|
||||
|
|
@ -498,29 +498,18 @@ def get_user_selections():
|
|||
box_content += f"\n[dim]Default: {default}[/dim]"
|
||||
return Panel(box_content, border_style="blue", padding=(1, 2))
|
||||
|
||||
# Step 1: Ticker symbol
|
||||
# Step 1: Event input
|
||||
console.print(
|
||||
create_question_box(
|
||||
"Step 1: Ticker Symbol", "Enter the ticker symbol to analyze", "SPY"
|
||||
"Step 1: Polymarket Event", "Enter a Polymarket event ID or URL to analyze"
|
||||
)
|
||||
)
|
||||
selected_ticker = get_ticker()
|
||||
event_info = get_event_input()
|
||||
|
||||
# Step 2: Analysis date
|
||||
default_date = datetime.datetime.now().strftime("%Y-%m-%d")
|
||||
# Step 2: Select analysts
|
||||
console.print(
|
||||
create_question_box(
|
||||
"Step 2: Analysis Date",
|
||||
"Enter the analysis date (YYYY-MM-DD)",
|
||||
default_date,
|
||||
)
|
||||
)
|
||||
analysis_date = get_analysis_date()
|
||||
|
||||
# Step 3: Select analysts
|
||||
console.print(
|
||||
create_question_box(
|
||||
"Step 3: Analysts Team", "Select your LLM analyst agents for the analysis"
|
||||
"Step 2: Analysts Team", "Select your LLM analyst agents for the analysis"
|
||||
)
|
||||
)
|
||||
selected_analysts = select_analysts()
|
||||
|
|
@ -528,32 +517,32 @@ def get_user_selections():
|
|||
f"[green]Selected analysts:[/green] {', '.join(analyst.value for analyst in selected_analysts)}"
|
||||
)
|
||||
|
||||
# Step 4: Research depth
|
||||
# Step 3: Research depth
|
||||
console.print(
|
||||
create_question_box(
|
||||
"Step 4: Research Depth", "Select your research depth level"
|
||||
"Step 3: Research Depth", "Select your research depth level"
|
||||
)
|
||||
)
|
||||
selected_research_depth = select_research_depth()
|
||||
|
||||
# Step 5: OpenAI backend
|
||||
# Step 4: OpenAI backend
|
||||
console.print(
|
||||
create_question_box(
|
||||
"Step 5: OpenAI backend", "Select which service to talk to"
|
||||
"Step 4: LLM Provider", "Select which service to talk to"
|
||||
)
|
||||
)
|
||||
selected_llm_provider, backend_url = select_llm_provider()
|
||||
|
||||
# Step 6: Thinking agents
|
||||
|
||||
# Step 5: Thinking agents
|
||||
console.print(
|
||||
create_question_box(
|
||||
"Step 6: Thinking Agents", "Select your thinking agents for analysis"
|
||||
"Step 5: Thinking Agents", "Select your thinking agents for analysis"
|
||||
)
|
||||
)
|
||||
selected_shallow_thinker = select_shallow_thinking_agent(selected_llm_provider)
|
||||
selected_deep_thinker = select_deep_thinking_agent(selected_llm_provider)
|
||||
|
||||
# Step 7: Provider-specific thinking configuration
|
||||
# Step 6: Provider-specific thinking configuration
|
||||
thinking_level = None
|
||||
reasoning_effort = None
|
||||
|
||||
|
|
@ -561,7 +550,7 @@ def get_user_selections():
|
|||
if provider_lower == "google":
|
||||
console.print(
|
||||
create_question_box(
|
||||
"Step 7: Thinking Mode",
|
||||
"Step 6: Thinking Mode",
|
||||
"Configure Gemini thinking mode"
|
||||
)
|
||||
)
|
||||
|
|
@ -569,15 +558,16 @@ def get_user_selections():
|
|||
elif provider_lower == "openai":
|
||||
console.print(
|
||||
create_question_box(
|
||||
"Step 7: Reasoning Effort",
|
||||
"Step 6: Reasoning Effort",
|
||||
"Configure OpenAI reasoning effort level"
|
||||
)
|
||||
)
|
||||
reasoning_effort = ask_openai_reasoning_effort()
|
||||
|
||||
return {
|
||||
"ticker": selected_ticker,
|
||||
"analysis_date": analysis_date,
|
||||
"event_id": event_info["event_id"],
|
||||
"event_question": event_info.get("event_question", event_info["event_id"]),
|
||||
"analysis_date": datetime.datetime.now().strftime("%Y-%m-%d"),
|
||||
"analysts": selected_analysts,
|
||||
"research_depth": selected_research_depth,
|
||||
"llm_provider": selected_llm_provider.lower(),
|
||||
|
|
@ -589,31 +579,8 @@ def get_user_selections():
|
|||
}
|
||||
|
||||
|
||||
def get_ticker():
|
||||
"""Get ticker symbol from user input."""
|
||||
return typer.prompt("", default="SPY")
|
||||
|
||||
|
||||
def get_analysis_date():
|
||||
"""Get the analysis date from user input."""
|
||||
while True:
|
||||
date_str = typer.prompt(
|
||||
"", default=datetime.datetime.now().strftime("%Y-%m-%d")
|
||||
)
|
||||
try:
|
||||
# Validate date format and ensure it's not in the future
|
||||
analysis_date = datetime.datetime.strptime(date_str, "%Y-%m-%d")
|
||||
if analysis_date.date() > datetime.datetime.now().date():
|
||||
console.print("[red]Error: Analysis date cannot be in the future[/red]")
|
||||
continue
|
||||
return date_str
|
||||
except ValueError:
|
||||
console.print(
|
||||
"[red]Error: Invalid date format. Please use YYYY-MM-DD[/red]"
|
||||
)
|
||||
|
||||
|
||||
def save_report_to_disk(final_state, ticker: str, save_path: Path):
|
||||
def save_report_to_disk(final_state, event_id: str, save_path: Path):
|
||||
"""Save complete analysis report to disk with organized subfolders."""
|
||||
save_path.mkdir(parents=True, exist_ok=True)
|
||||
sections = []
|
||||
|
|
@ -621,10 +588,10 @@ def save_report_to_disk(final_state, ticker: str, save_path: Path):
|
|||
# 1. Analysts
|
||||
analysts_dir = save_path / "1_analysts"
|
||||
analyst_parts = []
|
||||
if final_state.get("market_report"):
|
||||
if final_state.get("odds_report"):
|
||||
analysts_dir.mkdir(exist_ok=True)
|
||||
(analysts_dir / "market.md").write_text(final_state["market_report"])
|
||||
analyst_parts.append(("Market Analyst", final_state["market_report"]))
|
||||
(analysts_dir / "odds.md").write_text(final_state["odds_report"])
|
||||
analyst_parts.append(("Odds Analyst", final_state["odds_report"]))
|
||||
if final_state.get("sentiment_report"):
|
||||
analysts_dir.mkdir(exist_ok=True)
|
||||
(analysts_dir / "sentiment.md").write_text(final_state["sentiment_report"])
|
||||
|
|
@ -633,10 +600,10 @@ def save_report_to_disk(final_state, ticker: str, save_path: Path):
|
|||
analysts_dir.mkdir(exist_ok=True)
|
||||
(analysts_dir / "news.md").write_text(final_state["news_report"])
|
||||
analyst_parts.append(("News Analyst", final_state["news_report"]))
|
||||
if final_state.get("fundamentals_report"):
|
||||
if final_state.get("event_report"):
|
||||
analysts_dir.mkdir(exist_ok=True)
|
||||
(analysts_dir / "fundamentals.md").write_text(final_state["fundamentals_report"])
|
||||
analyst_parts.append(("Fundamentals Analyst", final_state["fundamentals_report"]))
|
||||
(analysts_dir / "event.md").write_text(final_state["event_report"])
|
||||
analyst_parts.append(("Event Analyst", final_state["event_report"]))
|
||||
if analyst_parts:
|
||||
content = "\n\n".join(f"### {name}\n{text}" for name, text in analyst_parts)
|
||||
sections.append(f"## I. Analyst Team Reports\n\n{content}")
|
||||
|
|
@ -646,14 +613,18 @@ def save_report_to_disk(final_state, ticker: str, save_path: Path):
|
|||
research_dir = save_path / "2_research"
|
||||
debate = final_state["investment_debate_state"]
|
||||
research_parts = []
|
||||
if debate.get("bull_history"):
|
||||
if debate.get("yes_history"):
|
||||
research_dir.mkdir(exist_ok=True)
|
||||
(research_dir / "bull.md").write_text(debate["bull_history"])
|
||||
research_parts.append(("Bull Researcher", debate["bull_history"]))
|
||||
if debate.get("bear_history"):
|
||||
(research_dir / "yes_advocate.md").write_text(debate["yes_history"])
|
||||
research_parts.append(("YES Advocate", debate["yes_history"]))
|
||||
if debate.get("no_history"):
|
||||
research_dir.mkdir(exist_ok=True)
|
||||
(research_dir / "bear.md").write_text(debate["bear_history"])
|
||||
research_parts.append(("Bear Researcher", debate["bear_history"]))
|
||||
(research_dir / "no_advocate.md").write_text(debate["no_history"])
|
||||
research_parts.append(("NO Advocate", debate["no_history"]))
|
||||
if debate.get("timing_history"):
|
||||
research_dir.mkdir(exist_ok=True)
|
||||
(research_dir / "timing_advocate.md").write_text(debate["timing_history"])
|
||||
research_parts.append(("Timing Advocate", debate["timing_history"]))
|
||||
if debate.get("judge_decision"):
|
||||
research_dir.mkdir(exist_ok=True)
|
||||
(research_dir / "manager.md").write_text(debate["judge_decision"])
|
||||
|
|
@ -663,11 +634,11 @@ def save_report_to_disk(final_state, ticker: str, save_path: Path):
|
|||
sections.append(f"## II. Research Team Decision\n\n{content}")
|
||||
|
||||
# 3. Trading
|
||||
if final_state.get("trader_investment_plan"):
|
||||
if final_state.get("trader_plan"):
|
||||
trading_dir = save_path / "3_trading"
|
||||
trading_dir.mkdir(exist_ok=True)
|
||||
(trading_dir / "trader.md").write_text(final_state["trader_investment_plan"])
|
||||
sections.append(f"## III. Trading Team Plan\n\n### Trader\n{final_state['trader_investment_plan']}")
|
||||
(trading_dir / "trader.md").write_text(final_state["trader_plan"])
|
||||
sections.append(f"## III. Trader Plan\n\n### Trader\n{final_state['trader_plan']}")
|
||||
|
||||
# 4. Risk Management
|
||||
if final_state.get("risk_debate_state"):
|
||||
|
|
@ -690,15 +661,15 @@ def save_report_to_disk(final_state, ticker: str, save_path: Path):
|
|||
content = "\n\n".join(f"### {name}\n{text}" for name, text in risk_parts)
|
||||
sections.append(f"## IV. Risk Management Team Decision\n\n{content}")
|
||||
|
||||
# 5. Portfolio Manager
|
||||
# 5. Risk Manager / Portfolio Manager
|
||||
if risk.get("judge_decision"):
|
||||
portfolio_dir = save_path / "5_portfolio"
|
||||
portfolio_dir.mkdir(exist_ok=True)
|
||||
(portfolio_dir / "decision.md").write_text(risk["judge_decision"])
|
||||
risk_mgr_dir = save_path / "5_risk_manager"
|
||||
risk_mgr_dir.mkdir(exist_ok=True)
|
||||
(risk_mgr_dir / "decision.md").write_text(risk["judge_decision"])
|
||||
sections.append(f"## V. Portfolio Manager Decision\n\n### Portfolio Manager\n{risk['judge_decision']}")
|
||||
|
||||
# Write consolidated report
|
||||
header = f"# Trading Analysis Report: {ticker}\n\nGenerated: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
|
||||
header = f"# Polymarket Analysis Report: {event_id}\n\nGenerated: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
|
||||
(save_path / "complete_report.md").write_text(header + "\n\n".join(sections))
|
||||
return save_path / "complete_report.md"
|
||||
|
||||
|
|
@ -710,14 +681,14 @@ def display_complete_report(final_state):
|
|||
|
||||
# I. Analyst Team Reports
|
||||
analysts = []
|
||||
if final_state.get("market_report"):
|
||||
analysts.append(("Market Analyst", final_state["market_report"]))
|
||||
if final_state.get("odds_report"):
|
||||
analysts.append(("Odds Analyst", final_state["odds_report"]))
|
||||
if final_state.get("sentiment_report"):
|
||||
analysts.append(("Social Analyst", final_state["sentiment_report"]))
|
||||
if final_state.get("news_report"):
|
||||
analysts.append(("News Analyst", final_state["news_report"]))
|
||||
if final_state.get("fundamentals_report"):
|
||||
analysts.append(("Fundamentals Analyst", final_state["fundamentals_report"]))
|
||||
if final_state.get("event_report"):
|
||||
analysts.append(("Event Analyst", final_state["event_report"]))
|
||||
if analysts:
|
||||
console.print(Panel("[bold]I. Analyst Team Reports[/bold]", border_style="cyan"))
|
||||
for title, content in analysts:
|
||||
|
|
@ -727,10 +698,12 @@ def display_complete_report(final_state):
|
|||
if final_state.get("investment_debate_state"):
|
||||
debate = final_state["investment_debate_state"]
|
||||
research = []
|
||||
if debate.get("bull_history"):
|
||||
research.append(("Bull Researcher", debate["bull_history"]))
|
||||
if debate.get("bear_history"):
|
||||
research.append(("Bear Researcher", debate["bear_history"]))
|
||||
if debate.get("yes_history"):
|
||||
research.append(("YES Advocate", debate["yes_history"]))
|
||||
if debate.get("no_history"):
|
||||
research.append(("NO Advocate", debate["no_history"]))
|
||||
if debate.get("timing_history"):
|
||||
research.append(("Timing Advocate", debate["timing_history"]))
|
||||
if debate.get("judge_decision"):
|
||||
research.append(("Research Manager", debate["judge_decision"]))
|
||||
if research:
|
||||
|
|
@ -738,10 +711,10 @@ def display_complete_report(final_state):
|
|||
for title, content in research:
|
||||
console.print(Panel(Markdown(content), title=title, border_style="blue", padding=(1, 2)))
|
||||
|
||||
# III. Trading Team
|
||||
if final_state.get("trader_investment_plan"):
|
||||
console.print(Panel("[bold]III. Trading Team Plan[/bold]", border_style="yellow"))
|
||||
console.print(Panel(Markdown(final_state["trader_investment_plan"]), title="Trader", border_style="blue", padding=(1, 2)))
|
||||
# III. Trader Plan
|
||||
if final_state.get("trader_plan"):
|
||||
console.print(Panel("[bold]III. Trader Plan[/bold]", border_style="yellow"))
|
||||
console.print(Panel(Markdown(final_state["trader_plan"]), title="Trader", border_style="blue", padding=(1, 2)))
|
||||
|
||||
# IV. Risk Management Team
|
||||
if final_state.get("risk_debate_state"):
|
||||
|
|
@ -766,24 +739,24 @@ def display_complete_report(final_state):
|
|||
|
||||
def update_research_team_status(status):
|
||||
"""Update status for research team members (not Trader)."""
|
||||
research_team = ["Bull Researcher", "Bear Researcher", "Research Manager"]
|
||||
research_team = ["YES Advocate", "NO Advocate", "Timing Advocate", "Research Manager"]
|
||||
for agent in research_team:
|
||||
message_buffer.update_agent_status(agent, status)
|
||||
|
||||
|
||||
# Ordered list of analysts for status transitions
|
||||
ANALYST_ORDER = ["market", "social", "news", "fundamentals"]
|
||||
ANALYST_ORDER = ["odds", "social", "news", "event"]
|
||||
ANALYST_AGENT_NAMES = {
|
||||
"market": "Market Analyst",
|
||||
"odds": "Odds Analyst",
|
||||
"social": "Social Analyst",
|
||||
"news": "News Analyst",
|
||||
"fundamentals": "Fundamentals Analyst",
|
||||
"event": "Event Analyst",
|
||||
}
|
||||
ANALYST_REPORT_MAP = {
|
||||
"market": "market_report",
|
||||
"odds": "odds_report",
|
||||
"social": "sentiment_report",
|
||||
"news": "news_report",
|
||||
"fundamentals": "fundamentals_report",
|
||||
"event": "event_report",
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -818,8 +791,8 @@ def update_analyst_statuses(message_buffer, chunk):
|
|||
|
||||
# When all analysts complete, transition research team to in_progress
|
||||
if not found_active and selected:
|
||||
if message_buffer.agent_status.get("Bull Researcher") == "pending":
|
||||
message_buffer.update_agent_status("Bull Researcher", "in_progress")
|
||||
if message_buffer.agent_status.get("YES Advocate") == "pending":
|
||||
message_buffer.update_agent_status("YES Advocate", "in_progress")
|
||||
|
||||
def extract_content_string(content):
|
||||
"""Extract string content from various message formats.
|
||||
|
|
@ -934,7 +907,7 @@ def run_analysis():
|
|||
start_time = time.time()
|
||||
|
||||
# Create result directory
|
||||
results_dir = Path(config["results_dir"]) / selections["ticker"] / selections["analysis_date"]
|
||||
results_dir = Path(config["results_dir"]) / selections["event_id"] / selections["analysis_date"]
|
||||
results_dir.mkdir(parents=True, exist_ok=True)
|
||||
report_dir = results_dir / "reports"
|
||||
report_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
|
@ -988,7 +961,7 @@ def run_analysis():
|
|||
update_display(layout, stats_handler=stats_handler, start_time=start_time)
|
||||
|
||||
# Add initial messages
|
||||
message_buffer.add_message("System", f"Selected ticker: {selections['ticker']}")
|
||||
message_buffer.add_message("System", f"Selected event: {selections['event_id']}")
|
||||
message_buffer.add_message(
|
||||
"System", f"Analysis date: {selections['analysis_date']}"
|
||||
)
|
||||
|
|
@ -999,19 +972,20 @@ def run_analysis():
|
|||
update_display(layout, stats_handler=stats_handler, start_time=start_time)
|
||||
|
||||
# Update agent status to in_progress for the first analyst
|
||||
first_analyst = f"{selections['analysts'][0].value.capitalize()} Analyst"
|
||||
first_analyst_key = selections['analysts'][0].value
|
||||
first_analyst = ANALYST_AGENT_NAMES.get(first_analyst_key, f"{first_analyst_key.capitalize()} Analyst")
|
||||
message_buffer.update_agent_status(first_analyst, "in_progress")
|
||||
update_display(layout, stats_handler=stats_handler, start_time=start_time)
|
||||
|
||||
# Create spinner text
|
||||
spinner_text = (
|
||||
f"Analyzing {selections['ticker']} on {selections['analysis_date']}..."
|
||||
f"Analyzing event {selections['event_id']}..."
|
||||
)
|
||||
update_display(layout, spinner_text, stats_handler=stats_handler, start_time=start_time)
|
||||
|
||||
# Initialize state and get graph args with callbacks
|
||||
init_agent_state = graph.propagator.create_initial_state(
|
||||
selections["ticker"], selections["analysis_date"]
|
||||
selections["event_id"], selections.get("event_question", selections["event_id"]), selections["analysis_date"]
|
||||
)
|
||||
# Pass callbacks to graph config for tool execution tracking
|
||||
# (LLM tracking is handled separately via LLM constructor)
|
||||
|
|
@ -1049,20 +1023,25 @@ def run_analysis():
|
|||
# Research Team - Handle Investment Debate State
|
||||
if chunk.get("investment_debate_state"):
|
||||
debate_state = chunk["investment_debate_state"]
|
||||
bull_hist = debate_state.get("bull_history", "").strip()
|
||||
bear_hist = debate_state.get("bear_history", "").strip()
|
||||
yes_hist = debate_state.get("yes_history", "").strip()
|
||||
no_hist = debate_state.get("no_history", "").strip()
|
||||
timing_hist = debate_state.get("timing_history", "").strip()
|
||||
judge = debate_state.get("judge_decision", "").strip()
|
||||
|
||||
# Only update status when there's actual content
|
||||
if bull_hist or bear_hist:
|
||||
if yes_hist or no_hist or timing_hist:
|
||||
update_research_team_status("in_progress")
|
||||
if bull_hist:
|
||||
if yes_hist:
|
||||
message_buffer.update_report_section(
|
||||
"investment_plan", f"### Bull Researcher Analysis\n{bull_hist}"
|
||||
"investment_plan", f"### YES Advocate Analysis\n{yes_hist}"
|
||||
)
|
||||
if bear_hist:
|
||||
if no_hist:
|
||||
message_buffer.update_report_section(
|
||||
"investment_plan", f"### Bear Researcher Analysis\n{bear_hist}"
|
||||
"investment_plan", f"### NO Advocate Analysis\n{no_hist}"
|
||||
)
|
||||
if timing_hist:
|
||||
message_buffer.update_report_section(
|
||||
"investment_plan", f"### Timing Advocate Analysis\n{timing_hist}"
|
||||
)
|
||||
if judge:
|
||||
message_buffer.update_report_section(
|
||||
|
|
@ -1072,9 +1051,9 @@ def run_analysis():
|
|||
message_buffer.update_agent_status("Trader", "in_progress")
|
||||
|
||||
# Trading Team
|
||||
if chunk.get("trader_investment_plan"):
|
||||
if chunk.get("trader_plan"):
|
||||
message_buffer.update_report_section(
|
||||
"trader_investment_plan", chunk["trader_investment_plan"]
|
||||
"trader_plan", chunk["trader_plan"]
|
||||
)
|
||||
if message_buffer.agent_status.get("Trader") != "completed":
|
||||
message_buffer.update_agent_status("Trader", "completed")
|
||||
|
|
@ -1092,25 +1071,25 @@ def run_analysis():
|
|||
if message_buffer.agent_status.get("Aggressive Analyst") != "completed":
|
||||
message_buffer.update_agent_status("Aggressive Analyst", "in_progress")
|
||||
message_buffer.update_report_section(
|
||||
"final_trade_decision", f"### Aggressive Analyst Analysis\n{agg_hist}"
|
||||
"final_decision", f"### Aggressive Analyst Analysis\n{agg_hist}"
|
||||
)
|
||||
if con_hist:
|
||||
if message_buffer.agent_status.get("Conservative Analyst") != "completed":
|
||||
message_buffer.update_agent_status("Conservative Analyst", "in_progress")
|
||||
message_buffer.update_report_section(
|
||||
"final_trade_decision", f"### Conservative Analyst Analysis\n{con_hist}"
|
||||
"final_decision", f"### Conservative Analyst Analysis\n{con_hist}"
|
||||
)
|
||||
if neu_hist:
|
||||
if message_buffer.agent_status.get("Neutral Analyst") != "completed":
|
||||
message_buffer.update_agent_status("Neutral Analyst", "in_progress")
|
||||
message_buffer.update_report_section(
|
||||
"final_trade_decision", f"### Neutral Analyst Analysis\n{neu_hist}"
|
||||
"final_decision", f"### Neutral Analyst Analysis\n{neu_hist}"
|
||||
)
|
||||
if judge:
|
||||
if message_buffer.agent_status.get("Portfolio Manager") != "completed":
|
||||
message_buffer.update_agent_status("Portfolio Manager", "in_progress")
|
||||
message_buffer.update_report_section(
|
||||
"final_trade_decision", f"### Portfolio Manager Decision\n{judge}"
|
||||
"final_decision", f"### Portfolio Manager Decision\n{judge}"
|
||||
)
|
||||
message_buffer.update_agent_status("Aggressive Analyst", "completed")
|
||||
message_buffer.update_agent_status("Conservative Analyst", "completed")
|
||||
|
|
@ -1124,14 +1103,14 @@ def run_analysis():
|
|||
|
||||
# Get final state and decision
|
||||
final_state = trace[-1]
|
||||
decision = graph.process_signal(final_state["final_trade_decision"])
|
||||
decision = graph.process_signal(final_state["final_decision"])
|
||||
|
||||
# Update all agent statuses to completed
|
||||
for agent in message_buffer.agent_status:
|
||||
message_buffer.update_agent_status(agent, "completed")
|
||||
|
||||
message_buffer.add_message(
|
||||
"System", f"Completed analysis for {selections['analysis_date']}"
|
||||
"System", f"Completed analysis for event {selections['event_id']}"
|
||||
)
|
||||
|
||||
# Update final report sections
|
||||
|
|
@ -1148,14 +1127,14 @@ def run_analysis():
|
|||
save_choice = typer.prompt("Save report?", default="Y").strip().upper()
|
||||
if save_choice in ("Y", "YES", ""):
|
||||
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
default_path = Path.cwd() / "reports" / f"{selections['ticker']}_{timestamp}"
|
||||
default_path = Path.cwd() / "reports" / f"{selections['event_id']}_{timestamp}"
|
||||
save_path_str = typer.prompt(
|
||||
"Save path (press Enter for default)",
|
||||
default=str(default_path)
|
||||
).strip()
|
||||
save_path = Path(save_path_str)
|
||||
try:
|
||||
report_file = save_report_to_disk(final_state, selections["ticker"], save_path)
|
||||
report_file = save_report_to_disk(final_state, selections["event_id"], save_path)
|
||||
console.print(f"\n[green]✓ Report saved to:[/green] {save_path.resolve()}")
|
||||
console.print(f" [dim]Complete report:[/dim] {report_file.name}")
|
||||
except Exception as e:
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
from enum import Enum
|
||||
from typing import List, Optional, Dict
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class AnalystType(str, Enum):
|
||||
MARKET = "market"
|
||||
ODDS = "odds"
|
||||
SOCIAL = "social"
|
||||
NEWS = "news"
|
||||
FUNDAMENTALS = "fundamentals"
|
||||
EVENT = "event"
|
||||
|
|
|
|||
67
cli/utils.py
67
cli/utils.py
|
|
@ -8,31 +8,66 @@ from cli.models import AnalystType
|
|||
console = Console()
|
||||
|
||||
ANALYST_ORDER = [
|
||||
("Market Analyst", AnalystType.MARKET),
|
||||
("Odds Analyst", AnalystType.ODDS),
|
||||
("Social Media Analyst", AnalystType.SOCIAL),
|
||||
("News Analyst", AnalystType.NEWS),
|
||||
("Fundamentals Analyst", AnalystType.FUNDAMENTALS),
|
||||
("Event Analyst", AnalystType.EVENT),
|
||||
]
|
||||
|
||||
|
||||
def get_ticker() -> str:
|
||||
"""Prompt the user to enter a ticker symbol."""
|
||||
ticker = questionary.text(
|
||||
"Enter the ticker symbol to analyze:",
|
||||
validate=lambda x: len(x.strip()) > 0 or "Please enter a valid ticker symbol.",
|
||||
style=questionary.Style(
|
||||
[
|
||||
("text", "fg:green"),
|
||||
("highlighted", "noinherit"),
|
||||
]
|
||||
),
|
||||
def get_event_input() -> dict:
|
||||
"""Get event selection from user - manual or scan mode."""
|
||||
mode = questionary.select(
|
||||
"Select input mode:",
|
||||
choices=[
|
||||
questionary.Choice("Manual - Enter event URL or ID", value="manual"),
|
||||
questionary.Choice("Scan - Search active markets", value="scan"),
|
||||
],
|
||||
style=questionary.Style([
|
||||
("selected", "fg:green noinherit"),
|
||||
("highlighted", "noinherit"),
|
||||
("pointer", "noinherit"),
|
||||
]),
|
||||
).ask()
|
||||
|
||||
if not ticker:
|
||||
console.print("\n[red]No ticker symbol provided. Exiting...[/red]")
|
||||
if mode is None:
|
||||
console.print("\n[red]No mode selected. Exiting...[/red]")
|
||||
exit(1)
|
||||
|
||||
return ticker.strip().upper()
|
||||
if mode == "manual":
|
||||
event_input = questionary.text(
|
||||
"Enter Polymarket event ID or URL:",
|
||||
validate=lambda x: len(x.strip()) > 0 or "Please enter an event ID or URL.",
|
||||
style=questionary.Style([
|
||||
("text", "fg:green"),
|
||||
("highlighted", "noinherit"),
|
||||
]),
|
||||
).ask()
|
||||
if not event_input:
|
||||
console.print("\n[red]No event provided. Exiting...[/red]")
|
||||
exit(1)
|
||||
# Parse URL if needed
|
||||
event_id = event_input.strip()
|
||||
if "polymarket.com" in event_id:
|
||||
# Extract slug from URL like polymarket.com/event/slug-here
|
||||
parts = event_id.rstrip("/").split("/")
|
||||
event_id = parts[-1] if parts else event_id
|
||||
return {"event_id": event_id, "mode": "manual"}
|
||||
else:
|
||||
# Scan mode - show filter options then search
|
||||
console.print("[dim]Searching active markets...[/dim]")
|
||||
from tradingagents.agents.utils.polymarket_tools import search_markets
|
||||
results = search_markets.invoke({"min_volume": 10000, "limit": 10})
|
||||
console.print(results)
|
||||
|
||||
event_id = questionary.text(
|
||||
"Enter event ID from the results above:",
|
||||
style=questionary.Style([("text", "fg:green"), ("highlighted", "noinherit")]),
|
||||
).ask()
|
||||
if not event_id:
|
||||
console.print("\n[red]No event selected. Exiting...[/red]")
|
||||
exit(1)
|
||||
return {"event_id": event_id.strip(), "mode": "scan"}
|
||||
|
||||
|
||||
def get_analysis_date() -> str:
|
||||
|
|
|
|||
Loading…
Reference in New Issue