diff --git a/tradingagents/agents/config/__init__.py b/tradingagents/agents/config/__init__.py new file mode 100644 index 00000000..7a43a184 --- /dev/null +++ b/tradingagents/agents/config/__init__.py @@ -0,0 +1,17 @@ +"""Configuration for trading agents.""" + +from .analyst_config import AnalystConfig, get_analyst_config +from .prompt_builder import ( + build_market_analyst_prompt, + build_news_analyst_prompt, + build_social_media_analyst_prompt, +) + +__all__ = [ + "AnalystConfig", + "get_analyst_config", + "build_market_analyst_prompt", + "build_news_analyst_prompt", + "build_social_media_analyst_prompt", +] + diff --git a/tradingagents/agents/config/analyst_config.py b/tradingagents/agents/config/analyst_config.py new file mode 100644 index 00000000..6916bdfb --- /dev/null +++ b/tradingagents/agents/config/analyst_config.py @@ -0,0 +1,113 @@ +"""Centralized configuration for analyst tools and prompts based on asset class.""" + +from typing import List, Dict, Any +from langchain_core.tools import BaseTool + + +class AnalystConfig: + """Configuration for analysts based on asset class.""" + + def __init__(self, asset_class: str = "equity"): + self.asset_class = asset_class.lower() + + def get_tools_for_analyst(self, analyst_type: str) -> List[BaseTool]: + """Get the appropriate tools for a given analyst based on asset class. + + Args: + analyst_type: One of 'market', 'news', 'social', 'fundamentals' + + Returns: + List of tools for that analyst + """ + # Import here to avoid circular dependencies + from tradingagents.agents.utils.agent_utils import ( + get_stock_data, + get_indicators, + get_news, + get_commodity_news, + get_global_news, + get_insider_sentiment, + get_insider_transactions, + get_fundamentals, + get_balance_sheet, + get_cashflow, + get_income_statement, + ) + from tradingagents.agents.utils.commodity_data_tools import get_commodity_data + + tools_map = { + "equity": { + "market": [get_stock_data, get_indicators], + "news": [get_news, get_global_news, get_insider_sentiment, get_insider_transactions], + "social": [get_news, get_global_news], + "fundamentals": [get_fundamentals, get_balance_sheet, get_cashflow, get_income_statement], + }, + "commodity": { + "market": [get_commodity_data], + "news": [get_commodity_news, get_global_news], + "social": [get_commodity_news, get_global_news], + "fundamentals": [], # Not applicable for commodities + } + } + + return tools_map.get(self.asset_class, tools_map["equity"]).get(analyst_type, []) + + def get_prompt_config(self, analyst_type: str) -> Dict[str, str]: + """Get prompt configuration for a given analyst based on asset class. + + Returns a dict with prompt templates and asset-specific terminology. + """ + if self.asset_class == "commodity": + return { + "asset_term": "commodity", + "asset_name_var": "ticker", # Still use ticker variable name for compatibility + "market": { + "focus": "supply/demand factors, geopolitical events, weather impacts (for agriculture), and macroeconomic trends", + "data_tool": "get_commodity_data", + "instructions": "call get_commodity_data to retrieve commodity price data", + }, + "news": { + "focus": "supply/demand factors, geopolitical events, weather impacts (for agriculture), and macroeconomic trends", + "primary_tool": "get_commodity_news(commodity, start_date, end_date)", + "primary_note": "searches by topic like 'energy' for oil, 'economy_macro' for agriculture", + "fallback_note": "If get_commodity_news returns limited results, make sure to use get_global_news to provide additional market context.", + }, + "social": { + "focus": "trader sentiment, supply/demand expectations, geopolitical concerns, and market psychology", + "primary_tool": "get_commodity_news(commodity, start_date, end_date)", + "primary_note": "searches by topic like 'energy' for oil", + "fallback_note": "If get_commodity_news returns limited results, supplement with get_global_news(curr_date, look_back_days, limit) for broader market context.", + } + } + else: # equity + return { + "asset_term": "company", + "asset_name_var": "ticker", + "market": { + "focus": "price trends, volume, volatility, and technical indicators", + "data_tool": "get_stock_data and get_indicators", + "instructions": "call get_stock_data first to retrieve historical price data, then get_indicators for technical analysis", + }, + "news": { + "focus": "company-specific events, earnings, product launches, and market sentiment", + "primary_tool": "get_news(ticker, start_date, end_date)", + "primary_note": "for company-specific or targeted news searches", + "fallback_note": "Use get_global_news(curr_date, look_back_days, limit) for broader macroeconomic context.", + }, + "social": { + "focus": "social media discussions, public sentiment, and community perception", + "primary_tool": "get_news(ticker, start_date, end_date)", + "primary_note": "to search for company-specific news and social media discussions", + "fallback_note": "If needed, use get_global_news(curr_date, look_back_days, limit) for broader market context.", + } + } + + +# Singleton instance can be created per graph +_config_instance = None + + +def get_analyst_config(asset_class: str = "equity") -> AnalystConfig: + """Get or create analyst configuration for the given asset class.""" + return AnalystConfig(asset_class) + diff --git a/tradingagents/agents/config/prompt_builder.py b/tradingagents/agents/config/prompt_builder.py new file mode 100644 index 00000000..bf73d564 --- /dev/null +++ b/tradingagents/agents/config/prompt_builder.py @@ -0,0 +1,80 @@ +"""Build analyst prompts dynamically based on asset class configuration.""" + + +def build_market_analyst_prompt(asset_class: str, ticker: str) -> str: + """Build system message for market analyst based on asset class.""" + from .analyst_config import get_analyst_config + + config = get_analyst_config(asset_class) + prompt_cfg = config.get_prompt_config("market") + + return ( + f"You are a market analyst specializing in {prompt_cfg['asset_term']} analysis. " + f"Your task is to analyze {ticker} and provide comprehensive technical analysis. " + f"Focus on {prompt_cfg['focus']}. " + f"\n\nIMPORTANT: First, {prompt_cfg['instructions']}. " + "After retrieving the data, provide detailed analysis with specific numbers, dates, and trends. " + "Do not simply state the trends are mixed, provide detailed and fine-grained analysis and insights that may help traders make decisions." + " Make sure to append a Markdown table at the end of the report to organize key points in the report, organized and easy to read." + ) + + +def build_news_analyst_prompt(asset_class: str, ticker: str) -> str: + """Build system message for news analyst based on asset class.""" + from .analyst_config import get_analyst_config + + config = get_analyst_config(asset_class) + prompt_cfg = config.get_prompt_config("news") + asset_term = prompt_cfg["asset_term"] + + if asset_class.lower() == "commodity": + return ( + f"You are a news researcher tasked with analyzing recent news and trends for the commodity {ticker}. " + "Please write a comprehensive report of relevant news over the past week that impacts this commodity's price. " + f"Use the available tools: {prompt_cfg['primary_tool']} for commodity-specific news ({prompt_cfg['primary_note']}), " + f"and get_global_news(curr_date, look_back_days, limit) for broader macroeconomic context. " + f"IMPORTANT: {prompt_cfg['fallback_note']} " + f"Focus on {prompt_cfg['focus']}. " + "Do not simply state the trends are mixed, provide detailed and fine-grained analysis." + " Make sure to append a Markdown table at the end of the report to organize key points." + ) + else: + return ( + "You are a news researcher tasked with analyzing recent news and trends over the past week. " + "Please write a comprehensive report of the current state of the world that is relevant for trading and macroeconomics. " + f"Use the available tools: {prompt_cfg['primary_tool']} {prompt_cfg['primary_note']}, " + f"and get_global_news(curr_date, look_back_days, limit) for broader macroeconomic news. " + "Do not simply state the trends are mixed, provide detailed and fine-grained analysis and insights that may help traders make decisions." + " Make sure to append a Markdown table at the end of the report to organize key points in the report, organized and easy to read." + ) + + +def build_social_media_analyst_prompt(asset_class: str, ticker: str) -> str: + """Build system message for social media analyst based on asset class.""" + from .analyst_config import get_analyst_config + + config = get_analyst_config(asset_class) + prompt_cfg = config.get_prompt_config("social") + asset_term = prompt_cfg["asset_term"] + + if asset_class.lower() == "commodity": + return ( + f"You are a social media and news researcher/analyst tasked with analyzing recent discussions and sentiment for the commodity {ticker}. " + "Your objective is to write a comprehensive report detailing market sentiment, trader discussions, and public perception over the past week. " + f"Use {prompt_cfg['primary_tool']} to search for commodity-related news and discussions ({prompt_cfg['primary_note']}). " + f"IMPORTANT: {prompt_cfg['fallback_note']} " + f"Focus on {prompt_cfg['focus']}. " + "Do not simply state the trends are mixed, provide detailed and fine-grained analysis." + " Make sure to append a Markdown table at the end of the report to organize key points." + ) + else: + return ( + f"You are a social media and {asset_term} specific news researcher/analyst tasked with analyzing social media posts, recent {asset_term} news, and public sentiment for a specific {asset_term} over the past week. " + f"Your objective is to write a comprehensive long report detailing your analysis, insights, and implications for traders and investors on this {asset_term}'s current state after looking at social media and what people are saying about that {asset_term}, " + f"analyzing sentiment data of what people feel each day about the {asset_term}, and looking at recent {asset_term} news. " + f"Use the {prompt_cfg['primary_tool']} tool {prompt_cfg['primary_note']}. " + f"{prompt_cfg['fallback_note']} " + "Try to look at all sources possible from social media to sentiment to news. Do not simply state the trends are mixed, provide detailed and fine-grained analysis and insights that may help traders make decisions." + " Make sure to append a Markdown table at the end of the report to organize key points in the report, organized and easy to read." + ) + diff --git a/tradingagents/graph/trading_graph.py b/tradingagents/graph/trading_graph.py index 76f33397..545355ad 100644 --- a/tradingagents/graph/trading_graph.py +++ b/tradingagents/graph/trading_graph.py @@ -31,6 +31,7 @@ from tradingagents.agents.utils.agent_utils import ( get_cashflow, get_income_statement, get_news, + get_commodity_news, get_insider_sentiment, get_insider_transactions, get_global_news @@ -122,47 +123,16 @@ class TradingAgentsGraph: self.graph = self.graph_setup.setup_graph(selected_analysts) def _create_tool_nodes(self) -> Dict[str, ToolNode]: - """Create tool nodes for different data sources using abstract methods.""" - is_commodity = self.config.get("asset_class", "equity").lower() == "commodity" - - market_tools = [] - if is_commodity: - # Only expose commodity tool to prevent LLM from selecting stock data - market_tools = [ - get_commodity_data, - ] - else: - market_tools = [ - get_stock_data, - get_indicators, - ] - + """Create tool nodes for different data sources using centralized config.""" + from tradingagents.agents.config import get_analyst_config + + analyst_config = get_analyst_config(self.config.get("asset_class", "equity")) + return { - "market": ToolNode(market_tools), - "social": ToolNode( - [ - # News tools for social media analysis - get_news, - ] - ), - "news": ToolNode( - [ - # News and insider information - get_news, - get_global_news, - get_insider_sentiment, - get_insider_transactions, - ] - ), - "fundamentals": ToolNode( - [ - # Fundamental analysis tools - get_fundamentals, - get_balance_sheet, - get_cashflow, - get_income_statement, - ] - ), + "market": ToolNode(analyst_config.get_tools_for_analyst("market")), + "social": ToolNode(analyst_config.get_tools_for_analyst("social")), + "news": ToolNode(analyst_config.get_tools_for_analyst("news")), + "fundamentals": ToolNode(analyst_config.get_tools_for_analyst("fundamentals")), } def propagate(self, company_name, trade_date):