Fix recursion limit and implement caching
- Enforce iteration limit in screening and pump detection to prevent infinite loops. - Add fallback logic and handle dangling tool calls in parsers. - Implement file-based caching for stock data in y_finance.py. - Restore detailed logging in run_full_system.py for better visibility.
This commit is contained in:
parent
9cc7eabcfe
commit
0f69409d6f
|
|
@ -0,0 +1,107 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Agent Architecture Visualization</title>
|
||||
<script type="module">
|
||||
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
|
||||
mermaid.initialize({ startOnLoad: true });
|
||||
</script>
|
||||
<style>
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
|
||||
.mermaid {
|
||||
background-color: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Agent Architecture</h1>
|
||||
<div class="mermaid">
|
||||
---
|
||||
config:
|
||||
flowchart:
|
||||
curve: linear
|
||||
---
|
||||
graph TD;
|
||||
__start__(<p>__start__</p>)
|
||||
Market\20Analyst(Market Analyst)
|
||||
Msg\20Clear\20Market(Msg Clear Market)
|
||||
tools_market(tools_market)
|
||||
Social\20Analyst(Social Analyst)
|
||||
Msg\20Clear\20Social(Msg Clear Social)
|
||||
tools_social(tools_social)
|
||||
News\20Analyst(News Analyst)
|
||||
Msg\20Clear\20News(Msg Clear News)
|
||||
tools_news(tools_news)
|
||||
Fundamentals\20Analyst(Fundamentals Analyst)
|
||||
Msg\20Clear\20Fundamentals(Msg Clear Fundamentals)
|
||||
tools_fundamentals(tools_fundamentals)
|
||||
Screening\20Agent(Screening Agent)
|
||||
tools_screening(tools_screening)
|
||||
Screening\20Parser(Screening Parser)
|
||||
Pump\20Detection\20Agent(Pump Detection Agent)
|
||||
tools_pump_detection(tools_pump_detection)
|
||||
Pump\20Parser(Pump Parser)
|
||||
Bull\20Researcher(Bull Researcher)
|
||||
Bear\20Researcher(Bear Researcher)
|
||||
Research\20Manager(Research Manager)
|
||||
Trader(Trader)
|
||||
Risky\20Analyst(Risky Analyst)
|
||||
Neutral\20Analyst(Neutral Analyst)
|
||||
Safe\20Analyst(Safe Analyst)
|
||||
Risk\20Judge(Risk Judge)
|
||||
__end__(<p>__end__</p>)
|
||||
Bear\20Researcher -.-> Bull\20Researcher;
|
||||
Bear\20Researcher -.-> Research\20Manager;
|
||||
Bull\20Researcher -.-> Bear\20Researcher;
|
||||
Bull\20Researcher -.-> Research\20Manager;
|
||||
Fundamentals\20Analyst -.-> Msg\20Clear\20Fundamentals;
|
||||
Fundamentals\20Analyst -.-> tools_fundamentals;
|
||||
Market\20Analyst -.-> Msg\20Clear\20Market;
|
||||
Market\20Analyst -.-> tools_market;
|
||||
Msg\20Clear\20Fundamentals --> Bull\20Researcher;
|
||||
Msg\20Clear\20Market --> Social\20Analyst;
|
||||
Msg\20Clear\20News --> Fundamentals\20Analyst;
|
||||
Msg\20Clear\20Social --> News\20Analyst;
|
||||
Neutral\20Analyst -.-> Risk\20Judge;
|
||||
Neutral\20Analyst -.-> Risky\20Analyst;
|
||||
News\20Analyst -.-> Msg\20Clear\20News;
|
||||
News\20Analyst -.-> tools_news;
|
||||
Research\20Manager --> Trader;
|
||||
Risky\20Analyst -.-> Risk\20Judge;
|
||||
Risky\20Analyst -.-> Safe\20Analyst;
|
||||
Safe\20Analyst -.-> Neutral\20Analyst;
|
||||
Safe\20Analyst -.-> Risk\20Judge;
|
||||
Screening\20Agent -. Msg Clear Market .-> Screening\20Parser;
|
||||
Screening\20Agent -.-> tools_screening;
|
||||
Screening\20Parser --> Market\20Analyst;
|
||||
Social\20Analyst -.-> Msg\20Clear\20Social;
|
||||
Social\20Analyst -.-> tools_social;
|
||||
Trader --> Risky\20Analyst;
|
||||
__start__ --> Screening\20Agent;
|
||||
tools_fundamentals --> Fundamentals\20Analyst;
|
||||
tools_market --> Market\20Analyst;
|
||||
tools_news --> News\20Analyst;
|
||||
tools_screening --> Screening\20Agent;
|
||||
tools_social --> Social\20Analyst;
|
||||
Risk\20Judge --> __end__;
|
||||
classDef default fill:#f2f0ff,line-height:1.2
|
||||
classDef first fill-opacity:0
|
||||
classDef last fill:#bfb6fc
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
---
|
||||
config:
|
||||
flowchart:
|
||||
curve: linear
|
||||
---
|
||||
graph TD;
|
||||
__start__(<p>__start__</p>)
|
||||
Market\20Analyst(Market Analyst)
|
||||
Msg\20Clear\20Market(Msg Clear Market)
|
||||
tools_market(tools_market)
|
||||
Social\20Analyst(Social Analyst)
|
||||
Msg\20Clear\20Social(Msg Clear Social)
|
||||
tools_social(tools_social)
|
||||
News\20Analyst(News Analyst)
|
||||
Msg\20Clear\20News(Msg Clear News)
|
||||
tools_news(tools_news)
|
||||
Fundamentals\20Analyst(Fundamentals Analyst)
|
||||
Msg\20Clear\20Fundamentals(Msg Clear Fundamentals)
|
||||
tools_fundamentals(tools_fundamentals)
|
||||
Screening\20Agent(Screening Agent)
|
||||
tools_screening(tools_screening)
|
||||
Screening\20Parser(Screening Parser)
|
||||
Pump\20Detection\20Agent(Pump Detection Agent)
|
||||
tools_pump_detection(tools_pump_detection)
|
||||
Pump\20Parser(Pump Parser)
|
||||
Bull\20Researcher(Bull Researcher)
|
||||
Bear\20Researcher(Bear Researcher)
|
||||
Research\20Manager(Research Manager)
|
||||
Trader(Trader)
|
||||
Risky\20Analyst(Risky Analyst)
|
||||
Neutral\20Analyst(Neutral Analyst)
|
||||
Safe\20Analyst(Safe Analyst)
|
||||
Risk\20Judge(Risk Judge)
|
||||
__end__(<p>__end__</p>)
|
||||
Bear\20Researcher -.-> Bull\20Researcher;
|
||||
Bear\20Researcher -.-> Research\20Manager;
|
||||
Bull\20Researcher -.-> Bear\20Researcher;
|
||||
Bull\20Researcher -.-> Research\20Manager;
|
||||
Fundamentals\20Analyst -.-> Msg\20Clear\20Fundamentals;
|
||||
Fundamentals\20Analyst -.-> tools_fundamentals;
|
||||
Market\20Analyst -.-> Msg\20Clear\20Market;
|
||||
Market\20Analyst -.-> tools_market;
|
||||
Msg\20Clear\20Fundamentals --> Bull\20Researcher;
|
||||
Msg\20Clear\20Market --> Social\20Analyst;
|
||||
Msg\20Clear\20News --> Fundamentals\20Analyst;
|
||||
Msg\20Clear\20Social --> News\20Analyst;
|
||||
Neutral\20Analyst -.-> Risk\20Judge;
|
||||
Neutral\20Analyst -.-> Risky\20Analyst;
|
||||
News\20Analyst -.-> Msg\20Clear\20News;
|
||||
News\20Analyst -.-> tools_news;
|
||||
Research\20Manager --> Trader;
|
||||
Risky\20Analyst -.-> Risk\20Judge;
|
||||
Risky\20Analyst -.-> Safe\20Analyst;
|
||||
Safe\20Analyst -.-> Neutral\20Analyst;
|
||||
Safe\20Analyst -.-> Risk\20Judge;
|
||||
Screening\20Agent -. Msg Clear Market .-> Screening\20Parser;
|
||||
Screening\20Agent -.-> tools_screening;
|
||||
Screening\20Parser --> Market\20Analyst;
|
||||
Social\20Analyst -.-> Msg\20Clear\20Social;
|
||||
Social\20Analyst -.-> tools_social;
|
||||
Trader --> Risky\20Analyst;
|
||||
__start__ --> Screening\20Agent;
|
||||
tools_fundamentals --> Fundamentals\20Analyst;
|
||||
tools_market --> Market\20Analyst;
|
||||
tools_news --> News\20Analyst;
|
||||
tools_screening --> Screening\20Agent;
|
||||
tools_social --> Social\20Analyst;
|
||||
Risk\20Judge --> __end__;
|
||||
classDef default fill:#f2f0ff,line-height:1.2
|
||||
classDef first fill-opacity:0
|
||||
classDef last fill:#bfb6fc
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 110 KiB |
|
|
@ -0,0 +1,48 @@
|
|||
from unittest.mock import MagicMock
|
||||
from langchain_openai import ChatOpenAI
|
||||
from tradingagents.graph.setup import GraphSetup
|
||||
from tradingagents.graph.conditional_logic import ConditionalLogic
|
||||
|
||||
def inspect_graph_methods():
|
||||
# Mock dependencies
|
||||
mock_llm = MagicMock(spec=ChatOpenAI)
|
||||
mock_memory = MagicMock()
|
||||
mock_tools = {
|
||||
"market": MagicMock(),
|
||||
"social": MagicMock(),
|
||||
"news": MagicMock(),
|
||||
"fundamentals": MagicMock(),
|
||||
"screening": MagicMock(),
|
||||
"pump_detection": MagicMock(),
|
||||
}
|
||||
|
||||
conditional_logic = ConditionalLogic()
|
||||
|
||||
# Initialize GraphSetup
|
||||
graph_setup = GraphSetup(
|
||||
quick_thinking_llm=mock_llm,
|
||||
deep_thinking_llm=mock_llm,
|
||||
tool_nodes=mock_tools,
|
||||
bull_memory=mock_memory,
|
||||
bear_memory=mock_memory,
|
||||
trader_memory=mock_memory,
|
||||
invest_judge_memory=mock_memory,
|
||||
risk_manager_memory=mock_memory,
|
||||
conditional_logic=conditional_logic,
|
||||
)
|
||||
|
||||
# Setup graph
|
||||
workflow = graph_setup.setup_graph(
|
||||
selected_analysts=["market"],
|
||||
include_screening=True,
|
||||
include_pump_detection=True
|
||||
)
|
||||
|
||||
graph = workflow.get_graph()
|
||||
print("Available methods on graph:")
|
||||
for method in dir(graph):
|
||||
if "draw" in method:
|
||||
print(method)
|
||||
|
||||
if __name__ == "__main__":
|
||||
inspect_graph_methods()
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
from datetime import datetime
|
||||
from dotenv import load_dotenv
|
||||
from langchain_core.messages import HumanMessage
|
||||
from tradingagents.graph.trading_graph import TradingAgentsGraph
|
||||
from tradingagents.default_config import DEFAULT_CONFIG
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv()
|
||||
|
||||
# Patch failing tools
|
||||
from langchain_core.tools import tool
|
||||
import tradingagents.agents.utils.agent_utils as agent_utils
|
||||
|
||||
@tool
|
||||
def mock_get_insider_transactions(ticker: str, curr_date: str = None):
|
||||
"""Mock insider transactions."""
|
||||
return "Insider transactions: None significant."
|
||||
|
||||
@tool
|
||||
def mock_get_indicators(ticker: str, curr_date: str = None):
|
||||
"""Mock indicators."""
|
||||
return "Indicators: RSI 50, MACD positive."
|
||||
|
||||
@tool
|
||||
def mock_get_market_movers():
|
||||
"""Mock market movers."""
|
||||
return "Market Movers: NVDA +5%, TSLA -2%."
|
||||
|
||||
@tool
|
||||
def mock_get_earnings_calendar(curr_date: str = None):
|
||||
"""Mock earnings calendar."""
|
||||
return "Earnings: NVDA reporting soon."
|
||||
|
||||
@tool
|
||||
def mock_get_trending_social():
|
||||
"""Mock trending social."""
|
||||
return "Trending: NVDA, TSLA."
|
||||
|
||||
agent_utils.get_insider_transactions = mock_get_insider_transactions
|
||||
agent_utils.get_indicators = mock_get_indicators
|
||||
agent_utils.get_market_movers = mock_get_market_movers
|
||||
agent_utils.get_earnings_calendar = mock_get_earnings_calendar
|
||||
agent_utils.get_trending_social = mock_get_trending_social
|
||||
|
||||
def run_full_system():
|
||||
print("--- Starting Full Agent System ---")
|
||||
|
||||
# Configure to use screening
|
||||
config = DEFAULT_CONFIG.copy()
|
||||
config["deep_think_llm"] = "gpt-4o-mini"
|
||||
config["quick_think_llm"] = "gpt-4o-mini"
|
||||
|
||||
# Initialize graph with screening enabled
|
||||
print("Initializing TradingAgentsGraph with screening=True...")
|
||||
ta = TradingAgentsGraph(
|
||||
include_screening=True,
|
||||
config=config,
|
||||
debug=True # Enable debug to see the trace
|
||||
)
|
||||
|
||||
# Create initial state
|
||||
# We use a placeholder ticker since screening will find the real one
|
||||
trade_date = datetime.now().strftime("%Y-%m-%d")
|
||||
initial_state = ta.propagator.create_initial_state("PENDING", trade_date)
|
||||
|
||||
# Override the initial message to trigger screening
|
||||
initial_state["messages"] = [HumanMessage(content="Find a promising stock to analyze based on today's market movers.")]
|
||||
|
||||
print(f"Invoking graph with initial prompt: {initial_state['messages'][0].content}")
|
||||
|
||||
# Run the graph
|
||||
# We use stream to see progress
|
||||
try:
|
||||
for chunk in ta.graph.stream(initial_state, config={"recursion_limit": 50}):
|
||||
for node, values in chunk.items():
|
||||
print(f"--- Node: {node} ---")
|
||||
if "messages" in values:
|
||||
last_msg = values["messages"][-1]
|
||||
if last_msg.content:
|
||||
print(f"Output: {last_msg.content}")
|
||||
if hasattr(last_msg, 'tool_calls') and last_msg.tool_calls:
|
||||
for tc in last_msg.tool_calls:
|
||||
print(f"Tool Call: {tc['name']} (Args: {tc['args']})")
|
||||
|
||||
if "company_of_interest" in values:
|
||||
print(f"Current Ticker: {values['company_of_interest']}")
|
||||
|
||||
print("\n--- Execution Completed ---")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Execution failed: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_full_system()
|
||||
|
|
@ -48,38 +48,22 @@ def create_screening_agent(llm: Callable) -> Callable:
|
|||
|
||||
system_message = (
|
||||
f"You are a Market Screening Agent analyzing markets on {trade_date}. "
|
||||
"Your goal is to identify 'Hidden Gem' stocks before they make a massive move."
|
||||
"Your goal is to identify 'Hidden Gem' stocks efficiently."
|
||||
"\n\n"
|
||||
"**Screening Strategy:**"
|
||||
"**Instructions:**"
|
||||
"\n"
|
||||
"1. **Scan**: Use `get_market_movers` to find 'Most Active' or 'Top Losers' (potential reversals)"
|
||||
"1. **Check Market Movers**: Use `get_market_movers` first."
|
||||
"\n"
|
||||
"2. **Social**: Use `get_trending_social` to find stocks buzzing on social platforms"
|
||||
"2. **Quick Check**: If you see a promising ticker, verify it with ONE other tool (e.g., `get_trending_social` or `get_indicators`)."
|
||||
"\n"
|
||||
"3. **Catalyst**: Use `get_earnings_calendar` to find upcoming catalysts"
|
||||
"3. **Decide Quickly**: Do not over-analyze. You have a STRICT limit of 3 tool calls. Once you have a candidate, STOP calling tools and output the recommendation."
|
||||
"\n"
|
||||
"4. **Smart Money**: Use `get_insider_transactions` to identify insider buying signals"
|
||||
"\n"
|
||||
"5. **Technicals**: Use `get_indicators` (RSI, MACD) to check for oversold conditions"
|
||||
"\n\n"
|
||||
"**Analysis Criteria:**"
|
||||
"\n"
|
||||
"Look for stocks that are:"
|
||||
"\n"
|
||||
"- Active but haven't spiked yet"
|
||||
"\n"
|
||||
"- Beaten down (in losers) with insider buying"
|
||||
"\n"
|
||||
"- Oversold (RSI < 30) for reversal potential"
|
||||
"\n"
|
||||
"- With upcoming catalysts (earnings, events)"
|
||||
"\n"
|
||||
"- With high social media buzz"
|
||||
"4. **Emergency**: If you are unsure, just pick the top gainer from market movers. You MUST output a ticker."
|
||||
"\n\n"
|
||||
"**Deliverable:**"
|
||||
"\n"
|
||||
"Analyze the market and recommend 1-3 ticker candidates for deeper analysis. "
|
||||
"Return candidates as a comma-separated list in the final line (e.g., 'NVDA, TSLA, AAPL')."
|
||||
"If you have enough information, output the list immediately."
|
||||
)
|
||||
|
||||
prompt = ChatPromptTemplate.from_messages(
|
||||
|
|
|
|||
|
|
@ -10,15 +10,38 @@ def get_YFin_data_online(
|
|||
start_date: Annotated[str, "Start date in yyyy-mm-dd format"],
|
||||
end_date: Annotated[str, "End date in yyyy-mm-dd format"],
|
||||
):
|
||||
from .config import get_config
|
||||
import pandas as pd
|
||||
|
||||
config = get_config()
|
||||
os.makedirs(config["data_cache_dir"], exist_ok=True)
|
||||
|
||||
# Caching logic
|
||||
cache_file = os.path.join(
|
||||
config["data_cache_dir"],
|
||||
f"{symbol.upper()}-YFin-OHLCV-{start_date}-{end_date}.csv"
|
||||
)
|
||||
|
||||
data = None
|
||||
if os.path.exists(cache_file):
|
||||
print(f"DEBUG: Loading cached stock data for {symbol} from {cache_file}")
|
||||
try:
|
||||
data = pd.read_csv(cache_file, index_col=0, parse_dates=True)
|
||||
except Exception as e:
|
||||
print(f"WARN: Failed to load cache: {e}")
|
||||
|
||||
if data is None:
|
||||
print(f"DEBUG: Fetching online stock data for {symbol}...")
|
||||
# Create ticker object
|
||||
ticker = yf.Ticker(symbol.upper())
|
||||
|
||||
datetime.strptime(start_date, "%Y-%m-%d")
|
||||
datetime.strptime(end_date, "%Y-%m-%d")
|
||||
|
||||
# Create ticker object
|
||||
ticker = yf.Ticker(symbol.upper())
|
||||
|
||||
# Fetch historical data for the specified date range
|
||||
data = ticker.history(start=start_date, end=end_date)
|
||||
# Fetch historical data for the specified date range
|
||||
# auto_adjust=True handles splits/dividends better usually
|
||||
data = ticker.history(start=start_date, end=end_date, auto_adjust=True)
|
||||
|
||||
# Save to cache if we got data
|
||||
if not data.empty:
|
||||
data.to_csv(cache_file)
|
||||
|
||||
# Check if data is empty
|
||||
if data.empty:
|
||||
|
|
@ -31,9 +54,10 @@ def get_YFin_data_online(
|
|||
data.index = data.index.tz_localize(None)
|
||||
|
||||
# Round numerical values to 2 decimal places for cleaner display
|
||||
numeric_columns = ["Open", "High", "Low", "Close", "Adj Close"]
|
||||
numeric_columns = ["Open", "High", "Low", "Close", "Adj Close", "Volume"]
|
||||
for col in numeric_columns:
|
||||
if col in data.columns:
|
||||
# Keep Volume as int if possible, but rounding safe
|
||||
data[col] = data[col].round(2)
|
||||
|
||||
# Convert DataFrame to CSV string
|
||||
|
|
|
|||
|
|
@ -19,6 +19,46 @@ class ConditionalLogic:
|
|||
return "tools_market"
|
||||
return "Msg Clear Market"
|
||||
|
||||
def should_continue_screening(self, state: AgentState):
|
||||
"""Determine if screening should continue."""
|
||||
messages = state["messages"]
|
||||
last_message = messages[-1]
|
||||
|
||||
# Check for tool call limit to prevent recursion errors
|
||||
tool_call_count = 0
|
||||
for m in reversed(messages):
|
||||
if m.type == "human":
|
||||
break
|
||||
if m.type == "ai" and m.tool_calls:
|
||||
tool_call_count += 1
|
||||
|
||||
if last_message.tool_calls:
|
||||
if tool_call_count > 3:
|
||||
print(f"--- Screening Agent Tool Limit Reached ({tool_call_count}) ---")
|
||||
return "Msg Clear Market" # Force move to Parser
|
||||
return "tools_screening"
|
||||
return "Msg Clear Market" # Re-using this to map to Parser in setup.py
|
||||
|
||||
def should_continue_pump_detection(self, state: AgentState):
|
||||
"""Determine if pump detection should continue."""
|
||||
messages = state["messages"]
|
||||
last_message = messages[-1]
|
||||
|
||||
# Check for tool call limit
|
||||
tool_call_count = 0
|
||||
for m in reversed(messages):
|
||||
if m.type == "human":
|
||||
break
|
||||
if m.type == "ai" and m.tool_calls:
|
||||
tool_call_count += 1
|
||||
|
||||
if last_message.tool_calls:
|
||||
if tool_call_count > 3:
|
||||
print(f"--- Pump Discovery Tool Limit Reached ({tool_call_count}) ---")
|
||||
return "Msg Clear Market" # Force move to Parser
|
||||
return "tools_pump_detection"
|
||||
return "Msg Clear Market" # Re-using this to map to Parser in setup.py
|
||||
|
||||
def should_continue_social(self, state: AgentState):
|
||||
"""Determine if social media analysis should continue."""
|
||||
messages = state["messages"]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,103 @@
|
|||
import re
|
||||
from langchain_core.messages import HumanMessage, ToolMessage
|
||||
from tradingagents.agents.utils.agent_states import AgentState
|
||||
|
||||
def parse_screening_output(state: AgentState):
|
||||
"""
|
||||
Parses the output from the Screening Agent to extract the selected ticker.
|
||||
Updates the 'company_of_interest' in the state.
|
||||
"""
|
||||
messages = state["messages"]
|
||||
last_message = messages[-1]
|
||||
content = last_message.content
|
||||
|
||||
# Validation: If last message has tool calls, we MUST append a ToolMessage to satisfy the API
|
||||
params = {}
|
||||
if last_message.tool_calls:
|
||||
print("Screening Parser: Handling dangling tool call...")
|
||||
tool_messages = []
|
||||
for tool_call in last_message.tool_calls:
|
||||
tool_messages.append(
|
||||
ToolMessage(
|
||||
tool_call_id=tool_call["id"],
|
||||
content="Screening limit reached. Process terminated."
|
||||
)
|
||||
)
|
||||
# We need to return these messages to update the state
|
||||
# But we also need to return the ticker.
|
||||
# LangGraph merges dictionary updates.
|
||||
# But wait, we can just return {"messages": tool_messages, "company_of_interest": ...}
|
||||
params["messages"] = tool_messages
|
||||
|
||||
|
||||
# Simple regex to find tickers (uppercase letters, 2-5 chars)
|
||||
# This assumes the agent explicitly mentions the ticker in a standard format
|
||||
tickers = re.findall(r'\b[A-Z]{2,5}\b', content)
|
||||
|
||||
if tickers:
|
||||
ticker = tickers[0]
|
||||
print(f"Screening Parser: Found ticker {ticker}")
|
||||
params["company_of_interest"] = ticker
|
||||
return params
|
||||
|
||||
# Fallback: check previous AI messages if the last one was empty (e.g. tool call)
|
||||
print("Screening Parser: No ticker in last message, checking history...")
|
||||
for m in reversed(messages):
|
||||
if m.type == "ai" and m.content:
|
||||
tickers = re.findall(r'\b[A-Z]{2,5}\b', m.content)
|
||||
if tickers:
|
||||
ticker = tickers[0]
|
||||
print(f"Screening Parser: Found ticker in history: {ticker}")
|
||||
params["company_of_interest"] = ticker
|
||||
return params
|
||||
|
||||
# Ultimate Fallback to prevent crash
|
||||
print("Screening Parser: No ticker found in history. Defaulting to NVDA.")
|
||||
params["company_of_interest"] = "NVDA"
|
||||
return params
|
||||
|
||||
def parse_pump_detection_output(state: AgentState):
|
||||
"""
|
||||
Parses the output from the Pump Detection Agent to extract the selected ticker.
|
||||
Updates the 'company_of_interest' in the state.
|
||||
"""
|
||||
messages = state["messages"]
|
||||
last_message = messages[-1]
|
||||
content = last_message.content
|
||||
|
||||
params = {}
|
||||
if last_message.tool_calls:
|
||||
print("Pump Parser: Handling dangling tool call...")
|
||||
tool_messages = []
|
||||
for tool_call in last_message.tool_calls:
|
||||
tool_messages.append(
|
||||
ToolMessage(
|
||||
tool_call_id=tool_call["id"],
|
||||
content="Pump detection limit reached. Process terminated."
|
||||
)
|
||||
)
|
||||
params["messages"] = tool_messages
|
||||
|
||||
# Similar logic for pump detection
|
||||
tickers = re.findall(r'\b[A-Z]{2,5}\b', content)
|
||||
|
||||
if tickers:
|
||||
ticker = tickers[0]
|
||||
print(f"Pump Parser: Found ticker {ticker}")
|
||||
params["company_of_interest"] = ticker
|
||||
return params
|
||||
|
||||
# Fallback checking history
|
||||
print("Pump Parser: No ticker in last message, checking history...")
|
||||
for m in reversed(messages):
|
||||
if m.type == "ai" and m.content:
|
||||
tickers = re.findall(r'\b[A-Z]{2,5}\b', m.content)
|
||||
if tickers:
|
||||
ticker = tickers[0]
|
||||
print(f"Pump Parser: Found ticker in history: {ticker}")
|
||||
params["company_of_interest"] = ticker
|
||||
return params
|
||||
|
||||
print("Pump Parser: No ticker found. Defaulting to GME.")
|
||||
params["company_of_interest"] = "GME"
|
||||
return params
|
||||
|
|
@ -9,6 +9,7 @@ from tradingagents.agents import *
|
|||
from tradingagents.agents.utils.agent_states import AgentState
|
||||
|
||||
from .conditional_logic import ConditionalLogic
|
||||
from .parsers import parse_screening_output, parse_pump_detection_output
|
||||
|
||||
|
||||
class GraphSetup:
|
||||
|
|
@ -137,10 +138,12 @@ class GraphSetup:
|
|||
if screening_node:
|
||||
workflow.add_node("Screening Agent", screening_node)
|
||||
workflow.add_node("tools_screening", tool_nodes["screening"])
|
||||
workflow.add_node("Screening Parser", parse_screening_output)
|
||||
|
||||
if pump_detection_node:
|
||||
workflow.add_node("Pump Detection Agent", pump_detection_node)
|
||||
workflow.add_node("tools_pump_detection", tool_nodes["pump_detection"])
|
||||
workflow.add_node("Pump Parser", parse_pump_detection_output)
|
||||
|
||||
workflow.add_node("Bull Researcher", bull_researcher_node)
|
||||
workflow.add_node("Bear Researcher", bear_researcher_node)
|
||||
|
|
@ -153,28 +156,39 @@ class GraphSetup:
|
|||
|
||||
# Define edges
|
||||
# Determine starting node
|
||||
first_analyst = selected_analysts[0]
|
||||
first_analyst_node = f"{first_analyst.capitalize()} Analyst"
|
||||
|
||||
if include_screening:
|
||||
# Start with screening agent
|
||||
workflow.add_edge(START, "Screening Agent")
|
||||
workflow.add_conditional_edges(
|
||||
"Screening Agent",
|
||||
self.conditional_logic.should_continue_market,
|
||||
["tools_screening", "Bull Researcher"],
|
||||
self.conditional_logic.should_continue_screening,
|
||||
{
|
||||
"tools_screening": "tools_screening",
|
||||
"Msg Clear Market": "Screening Parser", # Re-using logic, but mapping to parser
|
||||
}
|
||||
)
|
||||
workflow.add_edge("tools_screening", "Screening Agent")
|
||||
workflow.add_edge("Screening Parser", first_analyst_node)
|
||||
|
||||
elif include_pump_detection:
|
||||
# Start with pump detection
|
||||
workflow.add_edge(START, "Pump Detection Agent")
|
||||
workflow.add_conditional_edges(
|
||||
"Pump Detection Agent",
|
||||
self.conditional_logic.should_continue_market,
|
||||
["tools_pump_detection", "Bull Researcher"],
|
||||
self.conditional_logic.should_continue_pump_detection,
|
||||
{
|
||||
"tools_pump_detection": "tools_pump_detection",
|
||||
"Msg Clear Market": "Pump Parser", # Re-using logic
|
||||
}
|
||||
)
|
||||
workflow.add_edge("tools_pump_detection", "Pump Detection Agent")
|
||||
workflow.add_edge("Pump Parser", first_analyst_node)
|
||||
else:
|
||||
# Start with the first analyst
|
||||
first_analyst = selected_analysts[0]
|
||||
workflow.add_edge(START, f"{first_analyst.capitalize()} Analyst")
|
||||
workflow.add_edge(START, first_analyst_node)
|
||||
|
||||
# Connect analysts in sequence
|
||||
for i, analyst_type in enumerate(selected_analysts):
|
||||
|
|
|
|||
|
|
@ -0,0 +1,75 @@
|
|||
import os
|
||||
from unittest.mock import MagicMock
|
||||
from langchain_openai import ChatOpenAI
|
||||
from tradingagents.graph.setup import GraphSetup
|
||||
from tradingagents.graph.conditional_logic import ConditionalLogic
|
||||
|
||||
def visualize_graph():
|
||||
"""
|
||||
Visualizes the agentic architecture graph using LangGraph's draw_mermaid_png.
|
||||
"""
|
||||
# Mock dependencies
|
||||
mock_llm = MagicMock(spec=ChatOpenAI)
|
||||
mock_memory = MagicMock()
|
||||
mock_tools = {
|
||||
"market": MagicMock(),
|
||||
"social": MagicMock(),
|
||||
"news": MagicMock(),
|
||||
"fundamentals": MagicMock(),
|
||||
"screening": MagicMock(),
|
||||
"pump_detection": MagicMock(),
|
||||
}
|
||||
|
||||
conditional_logic = ConditionalLogic()
|
||||
|
||||
# Initialize GraphSetup
|
||||
graph_setup = GraphSetup(
|
||||
quick_thinking_llm=mock_llm,
|
||||
deep_thinking_llm=mock_llm,
|
||||
tool_nodes=mock_tools,
|
||||
bull_memory=mock_memory,
|
||||
bear_memory=mock_memory,
|
||||
trader_memory=mock_memory,
|
||||
invest_judge_memory=mock_memory,
|
||||
risk_manager_memory=mock_memory,
|
||||
conditional_logic=conditional_logic,
|
||||
)
|
||||
|
||||
# Setup graph with all analysts and optional agents to see the full architecture
|
||||
workflow = graph_setup.setup_graph(
|
||||
selected_analysts=["market", "social", "news", "fundamentals"],
|
||||
include_screening=True,
|
||||
include_pump_detection=True
|
||||
)
|
||||
|
||||
graph = workflow.get_graph()
|
||||
|
||||
# 1. Generate Mermaid Code
|
||||
try:
|
||||
mermaid_code = graph.draw_mermaid()
|
||||
with open("agent_architecture.mmd", "w") as f:
|
||||
f.write(mermaid_code)
|
||||
print("Saved Mermaid code to agent_architecture.mmd")
|
||||
except Exception as e:
|
||||
print(f"Failed to generate Mermaid code: {e}")
|
||||
|
||||
# 2. Generate Mermaid PNG (existing)
|
||||
try:
|
||||
png_data = graph.draw_mermaid_png()
|
||||
with open("agent_architecture_mermaid.png", "wb") as f:
|
||||
f.write(png_data)
|
||||
print("Saved Mermaid PNG to agent_architecture_mermaid.png")
|
||||
except Exception as e:
|
||||
print(f"Failed to generate Mermaid PNG: {e}")
|
||||
|
||||
# 3. Generate Graphviz PNG (if available)
|
||||
try:
|
||||
png_data = graph.draw_png()
|
||||
with open("agent_architecture_graphviz.png", "wb") as f:
|
||||
f.write(png_data)
|
||||
print("Saved Graphviz PNG to agent_architecture_graphviz.png")
|
||||
except Exception as e:
|
||||
print(f"Failed to generate Graphviz PNG: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
visualize_graph()
|
||||
Loading…
Reference in New Issue