diff --git a/agent_architecture.html b/agent_architecture.html new file mode 100644 index 00000000..bef503fe --- /dev/null +++ b/agent_architecture.html @@ -0,0 +1,107 @@ + + + +
+ + +__start__
) + 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__(__end__
) + 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 +__start__
) + 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__(__end__
) + 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 diff --git a/agent_architecture.png b/agent_architecture.png new file mode 100644 index 00000000..e6455716 Binary files /dev/null and b/agent_architecture.png differ diff --git a/agent_architecture_mermaid.png b/agent_architecture_mermaid.png new file mode 100644 index 00000000..3b9232ae Binary files /dev/null and b/agent_architecture_mermaid.png differ diff --git a/inspect_graph.py b/inspect_graph.py new file mode 100644 index 00000000..ea116277 --- /dev/null +++ b/inspect_graph.py @@ -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() diff --git a/run_full_system.py b/run_full_system.py new file mode 100644 index 00000000..23f23688 --- /dev/null +++ b/run_full_system.py @@ -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() diff --git a/tradingagents/agents/screeners/screening_agent.py b/tradingagents/agents/screeners/screening_agent.py index c664d5db..6416456e 100644 --- a/tradingagents/agents/screeners/screening_agent.py +++ b/tradingagents/agents/screeners/screening_agent.py @@ -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( diff --git a/tradingagents/dataflows/y_finance.py b/tradingagents/dataflows/y_finance.py index 21a3dee7..5702cd88 100644 --- a/tradingagents/dataflows/y_finance.py +++ b/tradingagents/dataflows/y_finance.py @@ -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 diff --git a/tradingagents/graph/conditional_logic.py b/tradingagents/graph/conditional_logic.py index dc28c9b2..f6eed539 100644 --- a/tradingagents/graph/conditional_logic.py +++ b/tradingagents/graph/conditional_logic.py @@ -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"] diff --git a/tradingagents/graph/parsers.py b/tradingagents/graph/parsers.py new file mode 100644 index 00000000..7b13efd5 --- /dev/null +++ b/tradingagents/graph/parsers.py @@ -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 diff --git a/tradingagents/graph/setup.py b/tradingagents/graph/setup.py index 6472c6c3..04f1b8f5 100644 --- a/tradingagents/graph/setup.py +++ b/tradingagents/graph/setup.py @@ -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): diff --git a/visualize_graph.py b/visualize_graph.py new file mode 100644 index 00000000..8bd47132 --- /dev/null +++ b/visualize_graph.py @@ -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()