feat(028-strategy-signals-contrib): wire strategy signals into analyst prompts
- Add strategy_signals field to AgentState (list of StrategySignal dicts) - Compute signals once in Propagator.create_initial_state() with graceful fallback - Add strategy_utils.get_signal_section() helper for role-based formatting - Inject signals into all 4 analyst nodes (market, fundamentals, news, social) - Inject signals into bull/bear researchers and research manager - Inject signals into all 3 risk debators (aggressive, conservative, neutral) - Inject signals into portfolio manager and trader - Log strategy_signals in state output JSON
This commit is contained in:
parent
4e97302e22
commit
99815917c7
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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"])
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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 ""
|
||||
|
|
@ -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]:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue