diff --git a/tradingagents/agents/analysts/fundamentals_analyst.py b/tradingagents/agents/analysts/fundamentals_analyst.py index 6aa49cf3..56b75f65 100644 --- a/tradingagents/agents/analysts/fundamentals_analyst.py +++ b/tradingagents/agents/analysts/fundamentals_analyst.py @@ -8,6 +8,7 @@ from tradingagents.agents.utils.agent_utils import ( get_insider_transactions, get_language_instruction, ) +from tradingagents.agents.utils.strategy_utils import get_signal_section from tradingagents.dataflows.config import get_config @@ -15,6 +16,7 @@ def create_fundamentals_analyst(llm): def fundamentals_analyst_node(state): current_date = state["trade_date"] instrument_context = build_instrument_context(state["company_of_interest"]) + signal_section = get_signal_section(state, "fundamentals") tools = [ get_fundamentals, @@ -41,7 +43,7 @@ def create_fundamentals_analyst(llm): " If you or any other assistant has the FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL** or deliverable," " prefix your response with FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL** so the team knows to stop." " You have access to the following tools: {tool_names}.\n{system_message}" - "For your reference, the current date is {current_date}. {instrument_context}", + "For your reference, the current date is {current_date}. {instrument_context}{signal_section}", ), MessagesPlaceholder(variable_name="messages"), ] @@ -51,6 +53,7 @@ def create_fundamentals_analyst(llm): prompt = prompt.partial(tool_names=", ".join([tool.name for tool in tools])) prompt = prompt.partial(current_date=current_date) prompt = prompt.partial(instrument_context=instrument_context) + prompt = prompt.partial(signal_section=signal_section) chain = prompt | llm.bind_tools(tools) diff --git a/tradingagents/agents/analysts/market_analyst.py b/tradingagents/agents/analysts/market_analyst.py index fef8f751..89607b99 100644 --- a/tradingagents/agents/analysts/market_analyst.py +++ b/tradingagents/agents/analysts/market_analyst.py @@ -5,6 +5,7 @@ from tradingagents.agents.utils.agent_utils import ( get_language_instruction, get_stock_data, ) +from tradingagents.agents.utils.strategy_utils import get_signal_section from tradingagents.dataflows.config import get_config @@ -13,6 +14,7 @@ def create_market_analyst(llm): def market_analyst_node(state): current_date = state["trade_date"] instrument_context = build_instrument_context(state["company_of_interest"]) + signal_section = get_signal_section(state, "market") tools = [ get_stock_data, @@ -60,7 +62,7 @@ Volume-Based Indicators: " If you or any other assistant has the FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL** or deliverable," " prefix your response with FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL** so the team knows to stop." " You have access to the following tools: {tool_names}.\n{system_message}" - "For your reference, the current date is {current_date}. {instrument_context}", + "For your reference, the current date is {current_date}. {instrument_context}{signal_section}", ), MessagesPlaceholder(variable_name="messages"), ] @@ -70,6 +72,7 @@ Volume-Based Indicators: prompt = prompt.partial(tool_names=", ".join([tool.name for tool in tools])) prompt = prompt.partial(current_date=current_date) prompt = prompt.partial(instrument_context=instrument_context) + prompt = prompt.partial(signal_section=signal_section) chain = prompt | llm.bind_tools(tools) diff --git a/tradingagents/agents/analysts/news_analyst.py b/tradingagents/agents/analysts/news_analyst.py index e0fe93c5..b4a79161 100644 --- a/tradingagents/agents/analysts/news_analyst.py +++ b/tradingagents/agents/analysts/news_analyst.py @@ -5,6 +5,7 @@ from tradingagents.agents.utils.agent_utils import ( get_language_instruction, get_news, ) +from tradingagents.agents.utils.strategy_utils import get_signal_section from tradingagents.dataflows.config import get_config @@ -12,6 +13,7 @@ def create_news_analyst(llm): def news_analyst_node(state): current_date = state["trade_date"] instrument_context = build_instrument_context(state["company_of_interest"]) + signal_section = get_signal_section(state, "news") tools = [ get_news, @@ -35,7 +37,7 @@ def create_news_analyst(llm): " If you or any other assistant has the FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL** or deliverable," " prefix your response with FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL** so the team knows to stop." " You have access to the following tools: {tool_names}.\n{system_message}" - "For your reference, the current date is {current_date}. {instrument_context}", + "For your reference, the current date is {current_date}. {instrument_context}{signal_section}", ), MessagesPlaceholder(variable_name="messages"), ] @@ -45,6 +47,7 @@ def create_news_analyst(llm): prompt = prompt.partial(tool_names=", ".join([tool.name for tool in tools])) prompt = prompt.partial(current_date=current_date) prompt = prompt.partial(instrument_context=instrument_context) + prompt = prompt.partial(signal_section=signal_section) chain = prompt | llm.bind_tools(tools) result = chain.invoke(state["messages"]) diff --git a/tradingagents/agents/analysts/social_media_analyst.py b/tradingagents/agents/analysts/social_media_analyst.py index 34a53c46..79f96fb6 100644 --- a/tradingagents/agents/analysts/social_media_analyst.py +++ b/tradingagents/agents/analysts/social_media_analyst.py @@ -1,5 +1,6 @@ from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from tradingagents.agents.utils.agent_utils import build_instrument_context, get_language_instruction, get_news +from tradingagents.agents.utils.strategy_utils import get_signal_section from tradingagents.dataflows.config import get_config @@ -7,6 +8,7 @@ def create_social_media_analyst(llm): def social_media_analyst_node(state): current_date = state["trade_date"] instrument_context = build_instrument_context(state["company_of_interest"]) + signal_section = get_signal_section(state, "social") tools = [ get_news, @@ -29,7 +31,7 @@ def create_social_media_analyst(llm): " If you or any other assistant has the FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL** or deliverable," " prefix your response with FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL** so the team knows to stop." " You have access to the following tools: {tool_names}.\n{system_message}" - "For your reference, the current date is {current_date}. {instrument_context}", + "For your reference, the current date is {current_date}. {instrument_context}{signal_section}", ), MessagesPlaceholder(variable_name="messages"), ] @@ -39,6 +41,7 @@ def create_social_media_analyst(llm): prompt = prompt.partial(tool_names=", ".join([tool.name for tool in tools])) prompt = prompt.partial(current_date=current_date) prompt = prompt.partial(instrument_context=instrument_context) + prompt = prompt.partial(signal_section=signal_section) chain = prompt | llm.bind_tools(tools) diff --git a/tradingagents/agents/managers/portfolio_manager.py b/tradingagents/agents/managers/portfolio_manager.py index 6c69ae9f..281758c2 100644 --- a/tradingagents/agents/managers/portfolio_manager.py +++ b/tradingagents/agents/managers/portfolio_manager.py @@ -1,10 +1,12 @@ from tradingagents.agents.utils.agent_utils import build_instrument_context, get_language_instruction +from tradingagents.agents.utils.strategy_utils import get_signal_section def create_portfolio_manager(llm, memory): def portfolio_manager_node(state) -> dict: instrument_context = build_instrument_context(state["company_of_interest"]) + signal_section = get_signal_section(state, "risk") history = state["risk_debate_state"]["history"] risk_debate_state = state["risk_debate_state"] @@ -52,7 +54,8 @@ def create_portfolio_manager(llm, memory): --- -Be decisive and ground every conclusion in specific evidence from the analysts.{get_language_instruction()}""" +Be decisive and ground every conclusion in specific evidence from the analysts.{get_language_instruction()} +{signal_section}""" response = llm.invoke(prompt) diff --git a/tradingagents/agents/managers/research_manager.py b/tradingagents/agents/managers/research_manager.py index 5b4b4fdc..3771443f 100644 --- a/tradingagents/agents/managers/research_manager.py +++ b/tradingagents/agents/managers/research_manager.py @@ -1,10 +1,12 @@ from tradingagents.agents.utils.agent_utils import build_instrument_context +from tradingagents.agents.utils.strategy_utils import get_signal_section def create_research_manager(llm, memory): def research_manager_node(state) -> dict: instrument_context = build_instrument_context(state["company_of_interest"]) + signal_section = get_signal_section(state, "researcher") history = state["investment_debate_state"].get("history", "") market_research_report = state["market_report"] sentiment_report = state["sentiment_report"] @@ -38,7 +40,8 @@ Here are your past reflections on mistakes: Here is the debate: Debate History: -{history}""" +{history} +{signal_section}""" response = llm.invoke(prompt) new_investment_debate_state = { diff --git a/tradingagents/agents/researchers/bear_researcher.py b/tradingagents/agents/researchers/bear_researcher.py index a44212dc..fe0be50b 100644 --- a/tradingagents/agents/researchers/bear_researcher.py +++ b/tradingagents/agents/researchers/bear_researcher.py @@ -1,5 +1,8 @@ +from tradingagents.agents.utils.strategy_utils import get_signal_section + + def create_bear_researcher(llm, memory): def bear_node(state) -> dict: investment_debate_state = state["investment_debate_state"] @@ -11,6 +14,7 @@ def create_bear_researcher(llm, memory): sentiment_report = state["sentiment_report"] news_report = state["news_report"] fundamentals_report = state["fundamentals_report"] + signal_section = get_signal_section(state, "researcher") curr_situation = f"{market_research_report}\n\n{sentiment_report}\n\n{news_report}\n\n{fundamentals_report}" past_memories = memory.get_memories(curr_situation, n_matches=2) @@ -39,7 +43,7 @@ Conversation history of the debate: {history} Last bull argument: {current_response} Reflections from similar situations and lessons learned: {past_memory_str} Use this information to deliver a compelling bear argument, refute the bull's claims, and engage in a dynamic debate that demonstrates the risks and weaknesses of investing in the stock. You must also address reflections and learn from lessons and mistakes you made in the past. -""" +{signal_section}""" response = llm.invoke(prompt) diff --git a/tradingagents/agents/researchers/bull_researcher.py b/tradingagents/agents/researchers/bull_researcher.py index d23d4d76..3db7c828 100644 --- a/tradingagents/agents/researchers/bull_researcher.py +++ b/tradingagents/agents/researchers/bull_researcher.py @@ -1,5 +1,8 @@ +from tradingagents.agents.utils.strategy_utils import get_signal_section + + def create_bull_researcher(llm, memory): def bull_node(state) -> dict: investment_debate_state = state["investment_debate_state"] @@ -11,6 +14,7 @@ def create_bull_researcher(llm, memory): sentiment_report = state["sentiment_report"] news_report = state["news_report"] fundamentals_report = state["fundamentals_report"] + signal_section = get_signal_section(state, "researcher") curr_situation = f"{market_research_report}\n\n{sentiment_report}\n\n{news_report}\n\n{fundamentals_report}" past_memories = memory.get_memories(curr_situation, n_matches=2) @@ -37,7 +41,7 @@ Conversation history of the debate: {history} Last bear argument: {current_response} Reflections from similar situations and lessons learned: {past_memory_str} Use this information to deliver a compelling bull argument, refute the bear's concerns, and engage in a dynamic debate that demonstrates the strengths of the bull position. You must also address reflections and learn from lessons and mistakes you made in the past. -""" +{signal_section}""" response = llm.invoke(prompt) diff --git a/tradingagents/agents/risk_mgmt/aggressive_debator.py b/tradingagents/agents/risk_mgmt/aggressive_debator.py index 2dab1152..d14e8d9b 100644 --- a/tradingagents/agents/risk_mgmt/aggressive_debator.py +++ b/tradingagents/agents/risk_mgmt/aggressive_debator.py @@ -1,5 +1,8 @@ +from tradingagents.agents.utils.strategy_utils import get_signal_section + + def create_aggressive_debator(llm): def aggressive_node(state) -> dict: risk_debate_state = state["risk_debate_state"] @@ -13,6 +16,7 @@ def create_aggressive_debator(llm): sentiment_report = state["sentiment_report"] news_report = state["news_report"] fundamentals_report = state["fundamentals_report"] + signal_section = get_signal_section(state, "risk") trader_decision = state["trader_investment_plan"] @@ -28,7 +32,8 @@ Latest World Affairs Report: {news_report} Company Fundamentals Report: {fundamentals_report} Here is the current conversation history: {history} Here are the last arguments from the conservative analyst: {current_conservative_response} Here are the last arguments from the neutral analyst: {current_neutral_response}. If there are no responses from the other viewpoints yet, present your own argument based on the available data. -Engage actively by addressing any specific concerns raised, refuting the weaknesses in their logic, and asserting the benefits of risk-taking to outpace market norms. Maintain a focus on debating and persuading, not just presenting data. Challenge each counterpoint to underscore why a high-risk approach is optimal. Output conversationally as if you are speaking without any special formatting.""" +Engage actively by addressing any specific concerns raised, refuting the weaknesses in their logic, and asserting the benefits of risk-taking to outpace market norms. Maintain a focus on debating and persuading, not just presenting data. Challenge each counterpoint to underscore why a high-risk approach is optimal. Output conversationally as if you are speaking without any special formatting. +{signal_section}""" response = llm.invoke(prompt) diff --git a/tradingagents/agents/risk_mgmt/conservative_debator.py b/tradingagents/agents/risk_mgmt/conservative_debator.py index 99a8315e..c356c245 100644 --- a/tradingagents/agents/risk_mgmt/conservative_debator.py +++ b/tradingagents/agents/risk_mgmt/conservative_debator.py @@ -1,5 +1,8 @@ +from tradingagents.agents.utils.strategy_utils import get_signal_section + + def create_conservative_debator(llm): def conservative_node(state) -> dict: risk_debate_state = state["risk_debate_state"] @@ -13,6 +16,7 @@ def create_conservative_debator(llm): sentiment_report = state["sentiment_report"] news_report = state["news_report"] fundamentals_report = state["fundamentals_report"] + signal_section = get_signal_section(state, "risk") trader_decision = state["trader_investment_plan"] @@ -28,7 +32,8 @@ Latest World Affairs Report: {news_report} Company Fundamentals Report: {fundamentals_report} Here is the current conversation history: {history} Here is the last response from the aggressive analyst: {current_aggressive_response} Here is the last response from the neutral analyst: {current_neutral_response}. If there are no responses from the other viewpoints yet, present your own argument based on the available data. -Engage by questioning their optimism and emphasizing the potential downsides they may have overlooked. Address each of their counterpoints to showcase why a conservative stance is ultimately the safest path for the firm's assets. Focus on debating and critiquing their arguments to demonstrate the strength of a low-risk strategy over their approaches. Output conversationally as if you are speaking without any special formatting.""" +Engage by questioning their optimism and emphasizing the potential downsides they may have overlooked. Address each of their counterpoints to showcase why a conservative stance is ultimately the safest path for the firm's assets. Focus on debating and critiquing their arguments to demonstrate the strength of a low-risk strategy over their approaches. Output conversationally as if you are speaking without any special formatting. +{signal_section}""" response = llm.invoke(prompt) diff --git a/tradingagents/agents/risk_mgmt/neutral_debator.py b/tradingagents/agents/risk_mgmt/neutral_debator.py index e99ff0af..f4edd43b 100644 --- a/tradingagents/agents/risk_mgmt/neutral_debator.py +++ b/tradingagents/agents/risk_mgmt/neutral_debator.py @@ -1,5 +1,8 @@ +from tradingagents.agents.utils.strategy_utils import get_signal_section + + def create_neutral_debator(llm): def neutral_node(state) -> dict: risk_debate_state = state["risk_debate_state"] @@ -13,6 +16,7 @@ def create_neutral_debator(llm): sentiment_report = state["sentiment_report"] news_report = state["news_report"] fundamentals_report = state["fundamentals_report"] + signal_section = get_signal_section(state, "risk") trader_decision = state["trader_investment_plan"] @@ -28,7 +32,8 @@ Latest World Affairs Report: {news_report} Company Fundamentals Report: {fundamentals_report} Here is the current conversation history: {history} Here is the last response from the aggressive analyst: {current_aggressive_response} Here is the last response from the conservative analyst: {current_conservative_response}. If there are no responses from the other viewpoints yet, present your own argument based on the available data. -Engage actively by analyzing both sides critically, addressing weaknesses in the aggressive and conservative arguments to advocate for a more balanced approach. Challenge each of their points to illustrate why a moderate risk strategy might offer the best of both worlds, providing growth potential while safeguarding against extreme volatility. Focus on debating rather than simply presenting data, aiming to show that a balanced view can lead to the most reliable outcomes. Output conversationally as if you are speaking without any special formatting.""" +Engage actively by analyzing both sides critically, addressing weaknesses in the aggressive and conservative arguments to advocate for a more balanced approach. Challenge each of their points to illustrate why a moderate risk strategy might offer the best of both worlds, providing growth potential while safeguarding against extreme volatility. Focus on debating rather than simply presenting data, aiming to show that a balanced view can lead to the most reliable outcomes. Output conversationally as if you are speaking without any special formatting. +{signal_section}""" response = llm.invoke(prompt) diff --git a/tradingagents/agents/trader/trader.py b/tradingagents/agents/trader/trader.py index 07e9f262..a0920f9d 100644 --- a/tradingagents/agents/trader/trader.py +++ b/tradingagents/agents/trader/trader.py @@ -1,12 +1,14 @@ import functools from tradingagents.agents.utils.agent_utils import build_instrument_context +from tradingagents.agents.utils.strategy_utils import get_signal_section def create_trader(llm, memory): def trader_node(state, name): company_name = state["company_of_interest"] instrument_context = build_instrument_context(company_name) + signal_section = get_signal_section(state, "researcher") investment_plan = state["investment_plan"] market_research_report = state["market_report"] sentiment_report = state["sentiment_report"] @@ -31,7 +33,8 @@ def create_trader(llm, memory): messages = [ { "role": "system", - "content": f"""You are a trading agent analyzing market data to make investment decisions. Based on your analysis, provide a specific recommendation to buy, sell, or hold. End with a firm decision and always conclude your response with 'FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL**' to confirm your recommendation. Apply lessons from past decisions to strengthen your analysis. Here are reflections from similar situations you traded in and the lessons learned: {past_memory_str}""", + "content": f"""You are a trading agent analyzing market data to make investment decisions. Based on your analysis, provide a specific recommendation to buy, sell, or hold. End with a firm decision and always conclude your response with 'FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL**' to confirm your recommendation. Apply lessons from past decisions to strengthen your analysis. Here are reflections from similar situations you traded in and the lessons learned: {past_memory_str} +{signal_section}""", }, context, ] diff --git a/tradingagents/agents/utils/agent_states.py b/tradingagents/agents/utils/agent_states.py index 6423b936..03f6b023 100644 --- a/tradingagents/agents/utils/agent_states.py +++ b/tradingagents/agents/utils/agent_states.py @@ -1,4 +1,4 @@ -from typing import Annotated +from typing import Annotated, Any from typing_extensions import TypedDict from langgraph.graph import MessagesState @@ -70,3 +70,6 @@ class AgentState(MessagesState): RiskDebateState, "Current state of the debate on evaluating risk" ] final_trade_decision: Annotated[str, "Final decision made by the Risk Analysts"] + + # Quantitative strategy signals (computed once, consumed by all nodes) + strategy_signals: Annotated[list[dict[str, Any]], "Deterministic strategy signals from the strategies framework"] diff --git a/tradingagents/agents/utils/strategy_utils.py b/tradingagents/agents/utils/strategy_utils.py new file mode 100644 index 00000000..8423f409 --- /dev/null +++ b/tradingagents/agents/utils/strategy_utils.py @@ -0,0 +1,18 @@ +"""Utility to extract formatted strategy signals from agent state.""" + +from __future__ import annotations + +from typing import Any + + +def get_signal_section(state: dict[str, Any], role: str) -> str: + """Return a formatted strategy signals section for *role*, or empty string.""" + signals = state.get("strategy_signals") + if not signals: + return "" + try: + from tradingagents.strategies import format_signals_for_role + section = format_signals_for_role(signals, role) + return f"\n\n{section}" if section else "" + except Exception: + return "" diff --git a/tradingagents/graph/propagation.py b/tradingagents/graph/propagation.py index 0fd10c0c..4f005445 100644 --- a/tradingagents/graph/propagation.py +++ b/tradingagents/graph/propagation.py @@ -1,5 +1,6 @@ # TradingAgents/graph/propagation.py +import logging from typing import Dict, Any, List, Optional from tradingagents.agents.utils.agent_states import ( AgentState, @@ -7,6 +8,8 @@ from tradingagents.agents.utils.agent_states import ( RiskDebateState, ) +logger = logging.getLogger(__name__) + class Propagator: """Handles state initialization and propagation through the graph.""" @@ -19,6 +22,14 @@ class Propagator: self, company_name: str, trade_date: str ) -> Dict[str, Any]: """Create the initial state for the agent graph.""" + # Compute strategy signals once up-front + strategy_signals: list[dict[str, Any]] = [] + try: + from tradingagents.strategies import compute_signals + strategy_signals = compute_signals(company_name, str(trade_date)) + except Exception: + logger.warning("Strategy signal computation failed; continuing without signals", exc_info=True) + return { "messages": [("human", company_name)], "company_of_interest": company_name, @@ -51,6 +62,7 @@ class Propagator: "fundamentals_report": "", "sentiment_report": "", "news_report": "", + "strategy_signals": strategy_signals, } def get_graph_args(self, callbacks: Optional[List] = None) -> Dict[str, Any]: diff --git a/tradingagents/graph/trading_graph.py b/tradingagents/graph/trading_graph.py index 78bc13e5..29114bea 100644 --- a/tradingagents/graph/trading_graph.py +++ b/tradingagents/graph/trading_graph.py @@ -254,6 +254,7 @@ class TradingAgentsGraph: }, "investment_plan": final_state["investment_plan"], "final_trade_decision": final_state["final_trade_decision"], + "strategy_signals": final_state.get("strategy_signals", []), } # Save to file