feat: update graph workflow for Polymarket 3-way debate
This commit is contained in:
parent
26657acab5
commit
7e45020dbb
|
|
@ -1,6 +1,5 @@
|
||||||
# TradingAgents/graph/propagation.py
|
|
||||||
|
|
||||||
from typing import Dict, Any, List, Optional
|
from typing import Dict, Any, List, Optional
|
||||||
|
|
||||||
from tradingagents.agents.utils.agent_states import (
|
from tradingagents.agents.utils.agent_states import (
|
||||||
AgentState,
|
AgentState,
|
||||||
InvestDebateState,
|
InvestDebateState,
|
||||||
|
|
@ -9,57 +8,54 @@ from tradingagents.agents.utils.agent_states import (
|
||||||
|
|
||||||
|
|
||||||
class Propagator:
|
class Propagator:
|
||||||
"""Handles state initialization and propagation through the graph."""
|
"""Handles state initialization and graph argument configuration."""
|
||||||
|
|
||||||
def __init__(self, max_recur_limit=100):
|
def __init__(self, max_recur_limit=100):
|
||||||
"""Initialize with configuration parameters."""
|
|
||||||
self.max_recur_limit = max_recur_limit
|
self.max_recur_limit = max_recur_limit
|
||||||
|
|
||||||
def create_initial_state(
|
def create_initial_state(self, event_id, event_question, trade_date):
|
||||||
self, company_name: str, trade_date: str
|
"""Create the initial agent state for a Polymarket event analysis."""
|
||||||
) -> Dict[str, Any]:
|
|
||||||
"""Create the initial state for the agent graph."""
|
|
||||||
return {
|
return {
|
||||||
"messages": [("human", company_name)],
|
"messages": [("human", event_question)],
|
||||||
"company_of_interest": company_name,
|
"event_id": event_id,
|
||||||
|
"event_question": event_question,
|
||||||
"trade_date": str(trade_date),
|
"trade_date": str(trade_date),
|
||||||
"investment_debate_state": InvestDebateState(
|
"sender": "",
|
||||||
{
|
"odds_report": "",
|
||||||
"bull_history": "",
|
|
||||||
"bear_history": "",
|
|
||||||
"history": "",
|
|
||||||
"current_response": "",
|
|
||||||
"judge_decision": "",
|
|
||||||
"count": 0,
|
|
||||||
}
|
|
||||||
),
|
|
||||||
"risk_debate_state": RiskDebateState(
|
|
||||||
{
|
|
||||||
"aggressive_history": "",
|
|
||||||
"conservative_history": "",
|
|
||||||
"neutral_history": "",
|
|
||||||
"history": "",
|
|
||||||
"latest_speaker": "",
|
|
||||||
"current_aggressive_response": "",
|
|
||||||
"current_conservative_response": "",
|
|
||||||
"current_neutral_response": "",
|
|
||||||
"judge_decision": "",
|
|
||||||
"count": 0,
|
|
||||||
}
|
|
||||||
),
|
|
||||||
"market_report": "",
|
|
||||||
"fundamentals_report": "",
|
|
||||||
"sentiment_report": "",
|
"sentiment_report": "",
|
||||||
"news_report": "",
|
"news_report": "",
|
||||||
|
"event_report": "",
|
||||||
|
"investment_debate_state": {
|
||||||
|
"yes_history": "",
|
||||||
|
"no_history": "",
|
||||||
|
"timing_history": "",
|
||||||
|
"history": "",
|
||||||
|
"current_yes_response": "",
|
||||||
|
"current_no_response": "",
|
||||||
|
"current_timing_response": "",
|
||||||
|
"latest_speaker": "",
|
||||||
|
"judge_decision": "",
|
||||||
|
"count": 0,
|
||||||
|
},
|
||||||
|
"investment_plan": "",
|
||||||
|
"trader_plan": "",
|
||||||
|
"risk_debate_state": {
|
||||||
|
"aggressive_history": "",
|
||||||
|
"conservative_history": "",
|
||||||
|
"neutral_history": "",
|
||||||
|
"history": "",
|
||||||
|
"latest_speaker": "",
|
||||||
|
"current_aggressive_response": "",
|
||||||
|
"current_conservative_response": "",
|
||||||
|
"current_neutral_response": "",
|
||||||
|
"judge_decision": "",
|
||||||
|
"count": 0,
|
||||||
|
},
|
||||||
|
"final_decision": "",
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_graph_args(self, callbacks: Optional[List] = None) -> Dict[str, Any]:
|
def get_graph_args(self, callbacks=None):
|
||||||
"""Get arguments for the graph invocation.
|
"""Get the arguments for graph invocation."""
|
||||||
|
|
||||||
Args:
|
|
||||||
callbacks: Optional list of callback handlers for tool execution tracking.
|
|
||||||
Note: LLM callbacks are handled separately via LLM constructor.
|
|
||||||
"""
|
|
||||||
config = {"recursion_limit": self.max_recur_limit}
|
config = {"recursion_limit": self.max_recur_limit}
|
||||||
if callbacks:
|
if callbacks:
|
||||||
config["callbacks"] = callbacks
|
config["callbacks"] = callbacks
|
||||||
|
|
|
||||||
|
|
@ -15,45 +15,42 @@ class Reflector:
|
||||||
def _get_reflection_prompt(self) -> str:
|
def _get_reflection_prompt(self) -> str:
|
||||||
"""Get the system prompt for reflection."""
|
"""Get the system prompt for reflection."""
|
||||||
return """
|
return """
|
||||||
You are an expert financial analyst tasked with reviewing trading decisions/analysis and providing a comprehensive, step-by-step analysis.
|
You are an expert prediction market analyst tasked with reviewing trading decisions/analysis and providing a comprehensive, step-by-step analysis.
|
||||||
Your goal is to deliver detailed insights into investment decisions and highlight opportunities for improvement, adhering strictly to the following guidelines:
|
Your goal is to deliver detailed insights into prediction market decisions and highlight opportunities for improvement, adhering strictly to the following guidelines:
|
||||||
|
|
||||||
1. Reasoning:
|
1. Reasoning:
|
||||||
- For each trading decision, determine whether it was correct or incorrect. A correct decision results in an increase in returns, while an incorrect decision does the opposite.
|
- For each prediction decision, determine whether it was correct or incorrect. A correct decision results in a positive return, while an incorrect decision does the opposite.
|
||||||
- Analyze the contributing factors to each success or mistake. Consider:
|
- Analyze the contributing factors to each success or mistake. Consider:
|
||||||
- Market intelligence.
|
- Market odds and price movements.
|
||||||
- Technical indicators.
|
- Order book depth and whale activity.
|
||||||
- Technical signals.
|
- News and event analysis.
|
||||||
- Price movement analysis.
|
|
||||||
- Overall market data analysis
|
|
||||||
- News analysis.
|
|
||||||
- Social media and sentiment analysis.
|
- Social media and sentiment analysis.
|
||||||
- Fundamental data analysis.
|
- Timing and event resolution timeline.
|
||||||
- Weight the importance of each factor in the decision-making process.
|
- Weight the importance of each factor in the decision-making process.
|
||||||
|
|
||||||
2. Improvement:
|
2. Improvement:
|
||||||
- For any incorrect decisions, propose revisions to maximize returns.
|
- For any incorrect decisions, propose revisions to maximize returns.
|
||||||
- Provide a detailed list of corrective actions or improvements, including specific recommendations (e.g., changing a decision from HOLD to BUY on a particular date).
|
- Provide a detailed list of corrective actions or improvements, including specific recommendations (e.g., changing a decision from SKIP to YES on a particular event).
|
||||||
|
|
||||||
3. Summary:
|
3. Summary:
|
||||||
- Summarize the lessons learned from the successes and mistakes.
|
- Summarize the lessons learned from the successes and mistakes.
|
||||||
- Highlight how these lessons can be adapted for future trading scenarios and draw connections between similar situations to apply the knowledge gained.
|
- Highlight how these lessons can be adapted for future prediction market scenarios and draw connections between similar situations to apply the knowledge gained.
|
||||||
|
|
||||||
4. Query:
|
4. Query:
|
||||||
- Extract key insights from the summary into a concise sentence of no more than 1000 tokens.
|
- Extract key insights from the summary into a concise sentence of no more than 1000 tokens.
|
||||||
- Ensure the condensed sentence captures the essence of the lessons and reasoning for easy reference.
|
- Ensure the condensed sentence captures the essence of the lessons and reasoning for easy reference.
|
||||||
|
|
||||||
Adhere strictly to these instructions, and ensure your output is detailed, accurate, and actionable. You will also be given objective descriptions of the market from a price movements, technical indicator, news, and sentiment perspective to provide more context for your analysis.
|
Adhere strictly to these instructions, and ensure your output is detailed, accurate, and actionable. You will also be given objective descriptions of the market from an odds, news, and sentiment perspective to provide more context for your analysis.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def _extract_current_situation(self, current_state: Dict[str, Any]) -> str:
|
def _extract_current_situation(self, current_state: Dict[str, Any]) -> str:
|
||||||
"""Extract the current market situation from the state."""
|
"""Extract the current market situation from the state."""
|
||||||
curr_market_report = current_state["market_report"]
|
curr_odds_report = current_state["odds_report"]
|
||||||
curr_sentiment_report = current_state["sentiment_report"]
|
curr_sentiment_report = current_state["sentiment_report"]
|
||||||
curr_news_report = current_state["news_report"]
|
curr_news_report = current_state["news_report"]
|
||||||
curr_fundamentals_report = current_state["fundamentals_report"]
|
curr_event_report = current_state["event_report"]
|
||||||
|
|
||||||
return f"{curr_market_report}\n\n{curr_sentiment_report}\n\n{curr_news_report}\n\n{curr_fundamentals_report}"
|
return f"{curr_odds_report}\n\n{curr_sentiment_report}\n\n{curr_news_report}\n\n{curr_event_report}"
|
||||||
|
|
||||||
def _reflect_on_component(
|
def _reflect_on_component(
|
||||||
self, component_type: str, report: str, situation: str, returns_losses
|
self, component_type: str, report: str, situation: str, returns_losses
|
||||||
|
|
@ -70,30 +67,40 @@ Adhere strictly to these instructions, and ensure your output is detailed, accur
|
||||||
result = self.quick_thinking_llm.invoke(messages).content
|
result = self.quick_thinking_llm.invoke(messages).content
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def reflect_bull_researcher(self, current_state, returns_losses, bull_memory):
|
def reflect_yes_advocate(self, current_state, returns_losses, yes_memory):
|
||||||
"""Reflect on bull researcher's analysis and update memory."""
|
"""Reflect on YES advocate's analysis and update memory."""
|
||||||
situation = self._extract_current_situation(current_state)
|
situation = self._extract_current_situation(current_state)
|
||||||
bull_debate_history = current_state["investment_debate_state"]["bull_history"]
|
yes_debate_history = current_state["investment_debate_state"]["yes_history"]
|
||||||
|
|
||||||
result = self._reflect_on_component(
|
result = self._reflect_on_component(
|
||||||
"BULL", bull_debate_history, situation, returns_losses
|
"YES", yes_debate_history, situation, returns_losses
|
||||||
)
|
)
|
||||||
bull_memory.add_situations([(situation, result)])
|
yes_memory.add_situations([(situation, result)])
|
||||||
|
|
||||||
def reflect_bear_researcher(self, current_state, returns_losses, bear_memory):
|
def reflect_no_advocate(self, current_state, returns_losses, no_memory):
|
||||||
"""Reflect on bear researcher's analysis and update memory."""
|
"""Reflect on NO advocate's analysis and update memory."""
|
||||||
situation = self._extract_current_situation(current_state)
|
situation = self._extract_current_situation(current_state)
|
||||||
bear_debate_history = current_state["investment_debate_state"]["bear_history"]
|
no_debate_history = current_state["investment_debate_state"]["no_history"]
|
||||||
|
|
||||||
result = self._reflect_on_component(
|
result = self._reflect_on_component(
|
||||||
"BEAR", bear_debate_history, situation, returns_losses
|
"NO", no_debate_history, situation, returns_losses
|
||||||
)
|
)
|
||||||
bear_memory.add_situations([(situation, result)])
|
no_memory.add_situations([(situation, result)])
|
||||||
|
|
||||||
|
def reflect_timing_advocate(self, current_state, returns_losses, timing_memory):
|
||||||
|
"""Reflect on timing advocate's analysis and update memory."""
|
||||||
|
situation = self._extract_current_situation(current_state)
|
||||||
|
timing_debate_history = current_state["investment_debate_state"]["timing_history"]
|
||||||
|
|
||||||
|
result = self._reflect_on_component(
|
||||||
|
"TIMING", timing_debate_history, situation, returns_losses
|
||||||
|
)
|
||||||
|
timing_memory.add_situations([(situation, result)])
|
||||||
|
|
||||||
def reflect_trader(self, current_state, returns_losses, trader_memory):
|
def reflect_trader(self, current_state, returns_losses, trader_memory):
|
||||||
"""Reflect on trader's decision and update memory."""
|
"""Reflect on trader's decision and update memory."""
|
||||||
situation = self._extract_current_situation(current_state)
|
situation = self._extract_current_situation(current_state)
|
||||||
trader_decision = current_state["trader_investment_plan"]
|
trader_decision = current_state["trader_plan"]
|
||||||
|
|
||||||
result = self._reflect_on_component(
|
result = self._reflect_on_component(
|
||||||
"TRADER", trader_decision, situation, returns_losses
|
"TRADER", trader_decision, situation, returns_losses
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,14 @@ from tradingagents.agents.utils.agent_states import AgentState
|
||||||
|
|
||||||
from .conditional_logic import ConditionalLogic
|
from .conditional_logic import ConditionalLogic
|
||||||
|
|
||||||
|
# Map analyst key to (display name, factory function, clear name)
|
||||||
|
_ANALYST_MAP = {
|
||||||
|
"odds": ("Odds Analyst", create_odds_analyst, "Msg Clear Odds"),
|
||||||
|
"social": ("Social Analyst", create_social_media_analyst, "Msg Clear Social"),
|
||||||
|
"news": ("News Analyst", create_news_analyst, "Msg Clear News"),
|
||||||
|
"event": ("Event Analyst", create_event_analyst, "Msg Clear Event"),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class GraphSetup:
|
class GraphSetup:
|
||||||
"""Handles the setup and configuration of the agent graph."""
|
"""Handles the setup and configuration of the agent graph."""
|
||||||
|
|
@ -21,6 +29,7 @@ class GraphSetup:
|
||||||
tool_nodes: Dict[str, ToolNode],
|
tool_nodes: Dict[str, ToolNode],
|
||||||
bull_memory,
|
bull_memory,
|
||||||
bear_memory,
|
bear_memory,
|
||||||
|
timing_memory,
|
||||||
trader_memory,
|
trader_memory,
|
||||||
invest_judge_memory,
|
invest_judge_memory,
|
||||||
risk_manager_memory,
|
risk_manager_memory,
|
||||||
|
|
@ -32,66 +41,55 @@ class GraphSetup:
|
||||||
self.tool_nodes = tool_nodes
|
self.tool_nodes = tool_nodes
|
||||||
self.bull_memory = bull_memory
|
self.bull_memory = bull_memory
|
||||||
self.bear_memory = bear_memory
|
self.bear_memory = bear_memory
|
||||||
|
self.timing_memory = timing_memory
|
||||||
self.trader_memory = trader_memory
|
self.trader_memory = trader_memory
|
||||||
self.invest_judge_memory = invest_judge_memory
|
self.invest_judge_memory = invest_judge_memory
|
||||||
self.risk_manager_memory = risk_manager_memory
|
self.risk_manager_memory = risk_manager_memory
|
||||||
self.conditional_logic = conditional_logic
|
self.conditional_logic = conditional_logic
|
||||||
|
|
||||||
def setup_graph(
|
def setup_graph(
|
||||||
self, selected_analysts=["market", "social", "news", "fundamentals"]
|
self, selected_analysts=["odds", "social", "news", "event"]
|
||||||
):
|
):
|
||||||
"""Set up and compile the agent workflow graph.
|
"""Set up and compile the agent workflow graph.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
selected_analysts (list): List of analyst types to include. Options are:
|
selected_analysts (list): List of analyst types to include. Options are:
|
||||||
- "market": Market analyst
|
- "odds": Odds analyst
|
||||||
- "social": Social media analyst
|
- "social": Social media analyst
|
||||||
- "news": News analyst
|
- "news": News analyst
|
||||||
- "fundamentals": Fundamentals analyst
|
- "event": Event analyst
|
||||||
"""
|
"""
|
||||||
if len(selected_analysts) == 0:
|
if len(selected_analysts) == 0:
|
||||||
raise ValueError("Trading Agents Graph Setup Error: no analysts selected!")
|
raise ValueError("Trading Agents Graph Setup Error: no analysts selected!")
|
||||||
|
|
||||||
|
# Validate analyst keys
|
||||||
|
for key in selected_analysts:
|
||||||
|
if key not in _ANALYST_MAP:
|
||||||
|
raise ValueError(
|
||||||
|
f"Unknown analyst type '{key}'. Valid options: {list(_ANALYST_MAP.keys())}"
|
||||||
|
)
|
||||||
|
|
||||||
# Create analyst nodes
|
# Create analyst nodes
|
||||||
analyst_nodes = {}
|
analyst_nodes = {}
|
||||||
delete_nodes = {}
|
delete_nodes = {}
|
||||||
tool_nodes = {}
|
tool_nodes = {}
|
||||||
|
|
||||||
if "market" in selected_analysts:
|
for key in selected_analysts:
|
||||||
analyst_nodes["market"] = create_market_analyst(
|
display_name, factory, clear_name = _ANALYST_MAP[key]
|
||||||
self.quick_thinking_llm
|
analyst_nodes[key] = factory(self.quick_thinking_llm)
|
||||||
)
|
delete_nodes[key] = create_msg_delete()
|
||||||
delete_nodes["market"] = create_msg_delete()
|
tool_nodes[key] = self.tool_nodes[key]
|
||||||
tool_nodes["market"] = self.tool_nodes["market"]
|
|
||||||
|
|
||||||
if "social" in selected_analysts:
|
|
||||||
analyst_nodes["social"] = create_social_media_analyst(
|
|
||||||
self.quick_thinking_llm
|
|
||||||
)
|
|
||||||
delete_nodes["social"] = create_msg_delete()
|
|
||||||
tool_nodes["social"] = self.tool_nodes["social"]
|
|
||||||
|
|
||||||
if "news" in selected_analysts:
|
|
||||||
analyst_nodes["news"] = create_news_analyst(
|
|
||||||
self.quick_thinking_llm
|
|
||||||
)
|
|
||||||
delete_nodes["news"] = create_msg_delete()
|
|
||||||
tool_nodes["news"] = self.tool_nodes["news"]
|
|
||||||
|
|
||||||
if "fundamentals" in selected_analysts:
|
|
||||||
analyst_nodes["fundamentals"] = create_fundamentals_analyst(
|
|
||||||
self.quick_thinking_llm
|
|
||||||
)
|
|
||||||
delete_nodes["fundamentals"] = create_msg_delete()
|
|
||||||
tool_nodes["fundamentals"] = self.tool_nodes["fundamentals"]
|
|
||||||
|
|
||||||
# Create researcher and manager nodes
|
# Create researcher and manager nodes
|
||||||
bull_researcher_node = create_bull_researcher(
|
yes_advocate_node = create_yes_advocate(
|
||||||
self.quick_thinking_llm, self.bull_memory
|
self.quick_thinking_llm, self.bull_memory
|
||||||
)
|
)
|
||||||
bear_researcher_node = create_bear_researcher(
|
no_advocate_node = create_no_advocate(
|
||||||
self.quick_thinking_llm, self.bear_memory
|
self.quick_thinking_llm, self.bear_memory
|
||||||
)
|
)
|
||||||
|
timing_advocate_node = create_timing_advocate(
|
||||||
|
self.quick_thinking_llm, self.timing_memory
|
||||||
|
)
|
||||||
research_manager_node = create_research_manager(
|
research_manager_node = create_research_manager(
|
||||||
self.deep_thinking_llm, self.invest_judge_memory
|
self.deep_thinking_llm, self.invest_judge_memory
|
||||||
)
|
)
|
||||||
|
|
@ -109,16 +107,16 @@ class GraphSetup:
|
||||||
workflow = StateGraph(AgentState)
|
workflow = StateGraph(AgentState)
|
||||||
|
|
||||||
# Add analyst nodes to the graph
|
# Add analyst nodes to the graph
|
||||||
for analyst_type, node in analyst_nodes.items():
|
for key in selected_analysts:
|
||||||
workflow.add_node(f"{analyst_type.capitalize()} Analyst", node)
|
display_name, _, clear_name = _ANALYST_MAP[key]
|
||||||
workflow.add_node(
|
workflow.add_node(display_name, analyst_nodes[key])
|
||||||
f"Msg Clear {analyst_type.capitalize()}", delete_nodes[analyst_type]
|
workflow.add_node(clear_name, delete_nodes[key])
|
||||||
)
|
workflow.add_node(f"tools_{key}", tool_nodes[key])
|
||||||
workflow.add_node(f"tools_{analyst_type}", tool_nodes[analyst_type])
|
|
||||||
|
|
||||||
# Add other nodes
|
# Add researcher, manager, trader, and risk nodes
|
||||||
workflow.add_node("Bull Researcher", bull_researcher_node)
|
workflow.add_node("YES Advocate", yes_advocate_node)
|
||||||
workflow.add_node("Bear Researcher", bear_researcher_node)
|
workflow.add_node("NO Advocate", no_advocate_node)
|
||||||
|
workflow.add_node("Timing Advocate", timing_advocate_node)
|
||||||
workflow.add_node("Research Manager", research_manager_node)
|
workflow.add_node("Research Manager", research_manager_node)
|
||||||
workflow.add_node("Trader", trader_node)
|
workflow.add_node("Trader", trader_node)
|
||||||
workflow.add_node("Aggressive Analyst", aggressive_analyst)
|
workflow.add_node("Aggressive Analyst", aggressive_analyst)
|
||||||
|
|
@ -128,49 +126,60 @@ class GraphSetup:
|
||||||
|
|
||||||
# Define edges
|
# Define edges
|
||||||
# Start with the first analyst
|
# Start with the first analyst
|
||||||
first_analyst = selected_analysts[0]
|
first_key = selected_analysts[0]
|
||||||
workflow.add_edge(START, f"{first_analyst.capitalize()} Analyst")
|
first_display_name = _ANALYST_MAP[first_key][0]
|
||||||
|
workflow.add_edge(START, first_display_name)
|
||||||
|
|
||||||
# Connect analysts in sequence
|
# Connect analysts in sequence using conditional logic
|
||||||
for i, analyst_type in enumerate(selected_analysts):
|
for i, key in enumerate(selected_analysts):
|
||||||
current_analyst = f"{analyst_type.capitalize()} Analyst"
|
display_name, _, clear_name = _ANALYST_MAP[key]
|
||||||
current_tools = f"tools_{analyst_type}"
|
tools_node = f"tools_{key}"
|
||||||
current_clear = f"Msg Clear {analyst_type.capitalize()}"
|
cond_fn = getattr(self.conditional_logic, f"should_continue_{key}")
|
||||||
|
|
||||||
# Add conditional edges for current analyst
|
|
||||||
workflow.add_conditional_edges(
|
workflow.add_conditional_edges(
|
||||||
current_analyst,
|
display_name,
|
||||||
getattr(self.conditional_logic, f"should_continue_{analyst_type}"),
|
cond_fn,
|
||||||
[current_tools, current_clear],
|
[tools_node, clear_name],
|
||||||
)
|
)
|
||||||
workflow.add_edge(current_tools, current_analyst)
|
workflow.add_edge(tools_node, display_name)
|
||||||
|
|
||||||
# Connect to next analyst or to Bull Researcher if this is the last analyst
|
# Connect clear node to next analyst or to YES Advocate
|
||||||
if i < len(selected_analysts) - 1:
|
if i < len(selected_analysts) - 1:
|
||||||
next_analyst = f"{selected_analysts[i+1].capitalize()} Analyst"
|
next_display_name = _ANALYST_MAP[selected_analysts[i + 1]][0]
|
||||||
workflow.add_edge(current_clear, next_analyst)
|
workflow.add_edge(clear_name, next_display_name)
|
||||||
else:
|
else:
|
||||||
workflow.add_edge(current_clear, "Bull Researcher")
|
workflow.add_edge(clear_name, "YES Advocate")
|
||||||
|
|
||||||
# Add remaining edges
|
# 3-way investment debate: YES → NO → Timing → YES (cycle until done)
|
||||||
workflow.add_conditional_edges(
|
workflow.add_conditional_edges(
|
||||||
"Bull Researcher",
|
"YES Advocate",
|
||||||
self.conditional_logic.should_continue_debate,
|
self.conditional_logic.should_continue_debate,
|
||||||
{
|
{
|
||||||
"Bear Researcher": "Bear Researcher",
|
"NO Advocate": "NO Advocate",
|
||||||
"Research Manager": "Research Manager",
|
"Research Manager": "Research Manager",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
workflow.add_conditional_edges(
|
workflow.add_conditional_edges(
|
||||||
"Bear Researcher",
|
"NO Advocate",
|
||||||
self.conditional_logic.should_continue_debate,
|
self.conditional_logic.should_continue_debate,
|
||||||
{
|
{
|
||||||
"Bull Researcher": "Bull Researcher",
|
"Timing Advocate": "Timing Advocate",
|
||||||
"Research Manager": "Research Manager",
|
"Research Manager": "Research Manager",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
workflow.add_conditional_edges(
|
||||||
|
"Timing Advocate",
|
||||||
|
self.conditional_logic.should_continue_debate,
|
||||||
|
{
|
||||||
|
"YES Advocate": "YES Advocate",
|
||||||
|
"Research Manager": "Research Manager",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
workflow.add_edge("Research Manager", "Trader")
|
workflow.add_edge("Research Manager", "Trader")
|
||||||
workflow.add_edge("Trader", "Aggressive Analyst")
|
workflow.add_edge("Trader", "Aggressive Analyst")
|
||||||
|
|
||||||
|
# 3-way risk debate
|
||||||
workflow.add_conditional_edges(
|
workflow.add_conditional_edges(
|
||||||
"Aggressive Analyst",
|
"Aggressive Analyst",
|
||||||
self.conditional_logic.should_continue_risk_analysis,
|
self.conditional_logic.should_continue_risk_analysis,
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,59 @@
|
||||||
# TradingAgents/graph/signal_processing.py
|
"""Signal processing for extracting structured prediction decisions."""
|
||||||
|
|
||||||
from langchain_openai import ChatOpenAI
|
import json
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
class SignalProcessor:
|
class SignalProcessor:
|
||||||
"""Processes trading signals to extract actionable decisions."""
|
"""Processes raw LLM output into structured prediction decisions."""
|
||||||
|
|
||||||
def __init__(self, quick_thinking_llm: ChatOpenAI):
|
def __init__(self, quick_thinking_llm):
|
||||||
"""Initialize with an LLM for processing."""
|
self.llm = quick_thinking_llm
|
||||||
self.quick_thinking_llm = quick_thinking_llm
|
|
||||||
|
|
||||||
def process_signal(self, full_signal: str) -> str:
|
def process_signal(self, full_signal: str) -> str:
|
||||||
"""
|
"""Extract structured JSON decision from the final decision text."""
|
||||||
Process a full trading signal to extract the core decision.
|
prompt = f"""Extract the final prediction decision from the following analysis.
|
||||||
|
Return ONLY a valid JSON object with these exact fields:
|
||||||
|
- "action": one of "YES", "NO", or "SKIP"
|
||||||
|
- "confidence": a float between 0.0 and 1.0
|
||||||
|
- "edge": estimated probability minus market price (float, can be negative)
|
||||||
|
- "position_size": recommended bet size as fraction of bankroll (float 0.0-1.0)
|
||||||
|
- "reasoning": one sentence summary
|
||||||
|
- "time_horizon": time until event resolution
|
||||||
|
|
||||||
Args:
|
Analysis:
|
||||||
full_signal: Complete trading signal text
|
{full_signal}
|
||||||
|
|
||||||
Returns:
|
Return ONLY the JSON object, no other text."""
|
||||||
Extracted decision (BUY, SELL, or HOLD)
|
|
||||||
"""
|
|
||||||
messages = [
|
|
||||||
(
|
|
||||||
"system",
|
|
||||||
"You are an efficient assistant designed to analyze paragraphs or financial reports provided by a group of analysts. Your task is to extract the investment decision: SELL, BUY, or HOLD. Provide only the extracted decision (SELL, BUY, or HOLD) as your output, without adding any additional text or information.",
|
|
||||||
),
|
|
||||||
("human", full_signal),
|
|
||||||
]
|
|
||||||
|
|
||||||
return self.quick_thinking_llm.invoke(messages).content
|
response = self.llm.invoke(prompt)
|
||||||
|
content = response.content if hasattr(response, "content") else str(response)
|
||||||
|
|
||||||
|
try:
|
||||||
|
json_match = re.search(r'\{[^\{\}]*\}', content, re.DOTALL)
|
||||||
|
if json_match:
|
||||||
|
parsed = json.loads(json_match.group())
|
||||||
|
required = ["action", "confidence", "edge", "position_size", "reasoning", "time_horizon"]
|
||||||
|
if all(k in parsed for k in required):
|
||||||
|
parsed["action"] = parsed["action"].upper().strip()
|
||||||
|
if parsed["action"] not in ("YES", "NO", "SKIP"):
|
||||||
|
parsed["action"] = "SKIP"
|
||||||
|
return json.dumps(parsed)
|
||||||
|
except (json.JSONDecodeError, AttributeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
action = "SKIP"
|
||||||
|
text_upper = content.upper()
|
||||||
|
if "YES" in text_upper and "NO" not in text_upper:
|
||||||
|
action = "YES"
|
||||||
|
elif "NO" in text_upper and "YES" not in text_upper:
|
||||||
|
action = "NO"
|
||||||
|
|
||||||
|
return json.dumps({
|
||||||
|
"action": action,
|
||||||
|
"confidence": 0.5,
|
||||||
|
"edge": 0.0,
|
||||||
|
"position_size": 0.0,
|
||||||
|
"reasoning": "Could not parse structured output from LLM response.",
|
||||||
|
"time_horizon": "unknown",
|
||||||
|
})
|
||||||
|
|
|
||||||
|
|
@ -20,17 +20,18 @@ from tradingagents.agents.utils.agent_states import (
|
||||||
)
|
)
|
||||||
from tradingagents.dataflows.config import set_config
|
from tradingagents.dataflows.config import set_config
|
||||||
|
|
||||||
# Import the new abstract tool methods from agent_utils
|
# Import Polymarket tools from agent_utils
|
||||||
from tradingagents.agents.utils.agent_utils import (
|
from tradingagents.agents.utils.agent_utils import (
|
||||||
get_stock_data,
|
get_market_data,
|
||||||
get_indicators,
|
get_price_history,
|
||||||
get_fundamentals,
|
get_event_news,
|
||||||
get_balance_sheet,
|
get_global_news,
|
||||||
get_cashflow,
|
get_whale_activity,
|
||||||
get_income_statement,
|
get_event_details,
|
||||||
get_news,
|
get_orderbook,
|
||||||
get_insider_transactions,
|
get_market_stats,
|
||||||
get_global_news
|
get_leaderboard_signals,
|
||||||
|
get_social_sentiment,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .conditional_logic import ConditionalLogic
|
from .conditional_logic import ConditionalLogic
|
||||||
|
|
@ -45,7 +46,7 @@ class TradingAgentsGraph:
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
selected_analysts=["market", "social", "news", "fundamentals"],
|
selected_analysts=["odds", "social", "news", "event"],
|
||||||
debug=False,
|
debug=False,
|
||||||
config: Dict[str, Any] = None,
|
config: Dict[str, Any] = None,
|
||||||
callbacks: Optional[List] = None,
|
callbacks: Optional[List] = None,
|
||||||
|
|
@ -97,6 +98,7 @@ class TradingAgentsGraph:
|
||||||
# Initialize memories
|
# Initialize memories
|
||||||
self.bull_memory = FinancialSituationMemory("bull_memory", self.config)
|
self.bull_memory = FinancialSituationMemory("bull_memory", self.config)
|
||||||
self.bear_memory = FinancialSituationMemory("bear_memory", self.config)
|
self.bear_memory = FinancialSituationMemory("bear_memory", self.config)
|
||||||
|
self.timing_memory = FinancialSituationMemory("timing_memory", self.config)
|
||||||
self.trader_memory = FinancialSituationMemory("trader_memory", self.config)
|
self.trader_memory = FinancialSituationMemory("trader_memory", self.config)
|
||||||
self.invest_judge_memory = FinancialSituationMemory("invest_judge_memory", self.config)
|
self.invest_judge_memory = FinancialSituationMemory("invest_judge_memory", self.config)
|
||||||
self.risk_manager_memory = FinancialSituationMemory("risk_manager_memory", self.config)
|
self.risk_manager_memory = FinancialSituationMemory("risk_manager_memory", self.config)
|
||||||
|
|
@ -115,6 +117,7 @@ class TradingAgentsGraph:
|
||||||
self.tool_nodes,
|
self.tool_nodes,
|
||||||
self.bull_memory,
|
self.bull_memory,
|
||||||
self.bear_memory,
|
self.bear_memory,
|
||||||
|
self.timing_memory,
|
||||||
self.trader_memory,
|
self.trader_memory,
|
||||||
self.invest_judge_memory,
|
self.invest_judge_memory,
|
||||||
self.risk_manager_memory,
|
self.risk_manager_memory,
|
||||||
|
|
@ -127,7 +130,7 @@ class TradingAgentsGraph:
|
||||||
|
|
||||||
# State tracking
|
# State tracking
|
||||||
self.curr_state = None
|
self.curr_state = None
|
||||||
self.ticker = None
|
self.event_id = None
|
||||||
self.log_states_dict = {} # date to full state dict
|
self.log_states_dict = {} # date to full state dict
|
||||||
|
|
||||||
# Set up the graph
|
# Set up the graph
|
||||||
|
|
@ -151,49 +154,22 @@ class TradingAgentsGraph:
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
def _create_tool_nodes(self) -> Dict[str, ToolNode]:
|
def _create_tool_nodes(self) -> Dict[str, ToolNode]:
|
||||||
"""Create tool nodes for different data sources using abstract methods."""
|
"""Create tool nodes for different data sources using Polymarket tools."""
|
||||||
return {
|
return {
|
||||||
"market": ToolNode(
|
"odds": ToolNode([get_market_data, get_price_history, get_orderbook]),
|
||||||
[
|
"social": ToolNode([get_social_sentiment, get_whale_activity]),
|
||||||
# Core stock data tools
|
"news": ToolNode([get_event_news, get_global_news]),
|
||||||
get_stock_data,
|
"event": ToolNode([get_event_details, get_market_stats, get_leaderboard_signals]),
|
||||||
# Technical indicators
|
|
||||||
get_indicators,
|
|
||||||
]
|
|
||||||
),
|
|
||||||
"social": ToolNode(
|
|
||||||
[
|
|
||||||
# News tools for social media analysis
|
|
||||||
get_news,
|
|
||||||
]
|
|
||||||
),
|
|
||||||
"news": ToolNode(
|
|
||||||
[
|
|
||||||
# News and insider information
|
|
||||||
get_news,
|
|
||||||
get_global_news,
|
|
||||||
get_insider_transactions,
|
|
||||||
]
|
|
||||||
),
|
|
||||||
"fundamentals": ToolNode(
|
|
||||||
[
|
|
||||||
# Fundamental analysis tools
|
|
||||||
get_fundamentals,
|
|
||||||
get_balance_sheet,
|
|
||||||
get_cashflow,
|
|
||||||
get_income_statement,
|
|
||||||
]
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def propagate(self, company_name, trade_date):
|
def propagate(self, event_id, event_question, trade_date):
|
||||||
"""Run the trading agents graph for a company on a specific date."""
|
"""Run the trading agents graph for a Polymarket event on a specific date."""
|
||||||
|
|
||||||
self.ticker = company_name
|
self.event_id = event_id
|
||||||
|
|
||||||
# Initialize state
|
# Initialize state
|
||||||
init_agent_state = self.propagator.create_initial_state(
|
init_agent_state = self.propagator.create_initial_state(
|
||||||
company_name, trade_date
|
event_id, event_question, trade_date
|
||||||
)
|
)
|
||||||
args = self.propagator.get_graph_args()
|
args = self.propagator.get_graph_args()
|
||||||
|
|
||||||
|
|
@ -219,29 +195,27 @@ class TradingAgentsGraph:
|
||||||
self._log_state(trade_date, final_state)
|
self._log_state(trade_date, final_state)
|
||||||
|
|
||||||
# Return decision and processed signal
|
# Return decision and processed signal
|
||||||
return final_state, self.process_signal(final_state["final_trade_decision"])
|
return final_state, self.process_signal(final_state["final_decision"])
|
||||||
|
|
||||||
def _log_state(self, trade_date, final_state):
|
def _log_state(self, trade_date, final_state):
|
||||||
"""Log the final state to a JSON file."""
|
"""Log the final state to a JSON file."""
|
||||||
self.log_states_dict[str(trade_date)] = {
|
self.log_states_dict[str(trade_date)] = {
|
||||||
"company_of_interest": final_state["company_of_interest"],
|
"event_id": final_state["event_id"],
|
||||||
|
"event_question": final_state["event_question"],
|
||||||
"trade_date": final_state["trade_date"],
|
"trade_date": final_state["trade_date"],
|
||||||
"market_report": final_state["market_report"],
|
"odds_report": final_state["odds_report"],
|
||||||
"sentiment_report": final_state["sentiment_report"],
|
"sentiment_report": final_state["sentiment_report"],
|
||||||
"news_report": final_state["news_report"],
|
"news_report": final_state["news_report"],
|
||||||
"fundamentals_report": final_state["fundamentals_report"],
|
"event_report": final_state["event_report"],
|
||||||
"investment_debate_state": {
|
"investment_debate_state": {
|
||||||
"bull_history": final_state["investment_debate_state"]["bull_history"],
|
"yes_history": final_state["investment_debate_state"]["yes_history"],
|
||||||
"bear_history": final_state["investment_debate_state"]["bear_history"],
|
"no_history": final_state["investment_debate_state"]["no_history"],
|
||||||
|
"timing_history": final_state["investment_debate_state"]["timing_history"],
|
||||||
"history": final_state["investment_debate_state"]["history"],
|
"history": final_state["investment_debate_state"]["history"],
|
||||||
"current_response": final_state["investment_debate_state"][
|
"judge_decision": final_state["investment_debate_state"]["judge_decision"],
|
||||||
"current_response"
|
|
||||||
],
|
|
||||||
"judge_decision": final_state["investment_debate_state"][
|
|
||||||
"judge_decision"
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
"trader_investment_decision": final_state["trader_investment_plan"],
|
"investment_plan": final_state["investment_plan"],
|
||||||
|
"trader_plan": final_state["trader_plan"],
|
||||||
"risk_debate_state": {
|
"risk_debate_state": {
|
||||||
"aggressive_history": final_state["risk_debate_state"]["aggressive_history"],
|
"aggressive_history": final_state["risk_debate_state"]["aggressive_history"],
|
||||||
"conservative_history": final_state["risk_debate_state"]["conservative_history"],
|
"conservative_history": final_state["risk_debate_state"]["conservative_history"],
|
||||||
|
|
@ -249,38 +223,28 @@ class TradingAgentsGraph:
|
||||||
"history": final_state["risk_debate_state"]["history"],
|
"history": final_state["risk_debate_state"]["history"],
|
||||||
"judge_decision": final_state["risk_debate_state"]["judge_decision"],
|
"judge_decision": final_state["risk_debate_state"]["judge_decision"],
|
||||||
},
|
},
|
||||||
"investment_plan": final_state["investment_plan"],
|
"final_decision": final_state["final_decision"],
|
||||||
"final_trade_decision": final_state["final_trade_decision"],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Save to file
|
# Save to file
|
||||||
directory = Path(f"eval_results/{self.ticker}/TradingAgentsStrategy_logs/")
|
safe_id = str(self.event_id).replace("/", "_")
|
||||||
|
directory = Path(f"eval_results/{safe_id}/TradingAgentsStrategy_logs/")
|
||||||
directory.mkdir(parents=True, exist_ok=True)
|
directory.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
with open(
|
with open(
|
||||||
f"eval_results/{self.ticker}/TradingAgentsStrategy_logs/full_states_log_{trade_date}.json",
|
f"eval_results/{safe_id}/TradingAgentsStrategy_logs/full_states_log_{trade_date}.json",
|
||||||
"w",
|
"w",
|
||||||
encoding="utf-8",
|
encoding="utf-8",
|
||||||
) as f:
|
) as f:
|
||||||
json.dump(self.log_states_dict, f, indent=4)
|
json.dump(self.log_states_dict, f, indent=4)
|
||||||
|
|
||||||
def reflect_and_remember(self, returns_losses):
|
def reflect_and_remember(self, returns_losses):
|
||||||
"""Reflect on decisions and update memory based on returns."""
|
"""Reflect on decisions and update memory based on outcomes.
|
||||||
self.reflector.reflect_bull_researcher(
|
|
||||||
self.curr_state, returns_losses, self.bull_memory
|
Phase 1 no-op: reflection is deferred until Phase 2 when outcome data
|
||||||
)
|
is available from resolved Polymarket events.
|
||||||
self.reflector.reflect_bear_researcher(
|
"""
|
||||||
self.curr_state, returns_losses, self.bear_memory
|
pass
|
||||||
)
|
|
||||||
self.reflector.reflect_trader(
|
|
||||||
self.curr_state, returns_losses, self.trader_memory
|
|
||||||
)
|
|
||||||
self.reflector.reflect_invest_judge(
|
|
||||||
self.curr_state, returns_losses, self.invest_judge_memory
|
|
||||||
)
|
|
||||||
self.reflector.reflect_risk_manager(
|
|
||||||
self.curr_state, returns_losses, self.risk_manager_memory
|
|
||||||
)
|
|
||||||
|
|
||||||
def process_signal(self, full_signal):
|
def process_signal(self, full_signal):
|
||||||
"""Process a signal to extract the core decision."""
|
"""Process a signal to extract the core decision."""
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue