""" Historical Memory Builder for TradingAgents This module creates agent memories from historical stock data by: 1. Analyzing market conditions at time T 2. Observing actual stock performance at time T + delta 3. Creating situation -> outcome mappings for each agent type 4. Storing memories in ChromaDB for future retrieval """ import os from datetime import datetime, timedelta from typing import List, Dict, Tuple, Optional from tradingagents.tools.executor import execute_tool from tradingagents.agents.utils.memory import FinancialSituationMemory class HistoricalMemoryBuilder: """Build agent memories from historical stock data.""" def __init__(self, config: dict): """Initialize the memory builder. Args: config: TradingAgents configuration dictionary """ self.config = config self.memories_created = { "bull": 0, "bear": 0, "trader": 0, "invest_judge": 0, "risk_manager": 0 } def _get_stock_data_for_period(self, ticker: str, date: str) -> Dict[str, str]: """Gather all available data for a stock on a specific date. Args: ticker: Stock ticker symbol date: Date in YYYY-MM-DD format Returns: Dictionary with market_report, news_report, sentiment_report, fundamentals_report """ data = {} try: # Get technical/price data (what Market Analyst sees) stock_data = execute_tool("get_stock_data", symbol=ticker, start_date=date) indicators = execute_tool("get_indicators", symbol=ticker, start_date=date) data["market_report"] = f"Stock Data:\n{stock_data}\n\nTechnical Indicators:\n{indicators}" except Exception as e: data["market_report"] = f"Error fetching market data: {e}" try: # Get news (what News Analyst sees) news = execute_tool("get_news", symbol=ticker, from_date=date, to_date=date) data["news_report"] = news except Exception as e: data["news_report"] = f"Error fetching news: {e}" try: # Get sentiment (what Social Analyst sees) sentiment = execute_tool("get_reddit_discussions", symbol=ticker, from_date=date, to_date=date) data["sentiment_report"] = sentiment except Exception as e: data["sentiment_report"] = f"Error fetching sentiment: {e}" try: # Get fundamentals (what Fundamentals Analyst sees) fundamentals = execute_tool("get_fundamentals", symbol=ticker) data["fundamentals_report"] = fundamentals except Exception as e: data["fundamentals_report"] = f"Error fetching fundamentals: {e}" return data def _calculate_returns(self, ticker: str, start_date: str, end_date: str) -> Optional[float]: """Calculate stock returns between two dates. Args: ticker: Stock ticker symbol start_date: Starting date (YYYY-MM-DD) end_date: Ending date (YYYY-MM-DD) Returns: Percentage return, or None if data unavailable """ try: # Get stock prices for both dates start_data = execute_tool("get_stock_data", symbol=ticker, start_date=start_date, end_date=start_date) end_data = execute_tool("get_stock_data", symbol=ticker, start_date=end_date, end_date=end_date) # Parse prices (this is simplified - you'd need to parse the actual response) # Assuming response has close price - adjust based on actual API response import re start_match = re.search(r'Close[:\s]+\$?([\d.]+)', str(start_data)) end_match = re.search(r'Close[:\s]+\$?([\d.]+)', str(end_data)) if start_match and end_match: start_price = float(start_match.group(1)) end_price = float(end_match.group(1)) return ((end_price - start_price) / start_price) * 100 return None except Exception as e: print(f"Error calculating returns: {e}") return None def _create_bull_researcher_memory(self, situation: str, returns: float, ticker: str, date: str) -> str: """Create memory for bull researcher based on outcome. Returns lesson learned from bullish perspective. """ if returns > 5: return f"""SUCCESSFUL BULLISH ANALYSIS for {ticker} on {date}: The market conditions indicated strong bullish signals, and the stock delivered {returns:.2f}% returns. Key takeaways: - When similar conditions appear (strong fundamentals + positive sentiment + bullish technicals), aggressive BUY positions are warranted - The combination of factors in this situation was a reliable indicator of upward momentum - Continue to weight these signals heavily in future bullish arguments Recommendation: In similar situations, advocate strongly for BUY positions with high conviction. """ elif returns < -5: return f"""INCORRECT BULLISH SIGNALS for {ticker} on {date}: Despite apparent bullish indicators, the stock declined {abs(returns):.2f}%. Lessons learned: - The bullish signals in this situation were misleading or outweighed by hidden risks - Need to look deeper at: macro conditions, sector headwinds, or fundamental weaknesses that weren't apparent - Be more cautious when similar patterns appear; consider bear arguments more seriously Recommendation: In similar situations, temper bullish enthusiasm and scrutinize fundamentals more carefully. """ else: return f"""NEUTRAL OUTCOME for {ticker} on {date}: Stock moved {returns:.2f}%, indicating mixed signals. Lesson: This pattern of indicators doesn't provide strong directional conviction. Look for clearer signals before making strong bullish arguments. """ def _create_bear_researcher_memory(self, situation: str, returns: float, ticker: str, date: str) -> str: """Create memory for bear researcher based on outcome.""" if returns < -5: return f"""SUCCESSFUL BEARISH ANALYSIS for {ticker} on {date}: Bearish indicators correctly predicted decline of {abs(returns):.2f}%. Key takeaways: - The risk factors identified were valid and material - Similar warning signs should be treated seriously in future analysis - When these patterns appear, advocate strongly for SELL or reduce positions Recommendation: In similar situations, maintain bearish stance with high conviction. """ elif returns > 5: return f"""INCORRECT BEARISH SIGNALS for {ticker} on {date}: Despite bearish indicators, stock rallied {returns:.2f}%. Lessons learned: - The bearish concerns were either overstated or offset by stronger positive factors - Market sentiment or momentum can override fundamental concerns in short term - Need to better assess whether bearish factors are already priced in Recommendation: In similar situations, be more cautious about strong SELL recommendations. """ else: return f"""NEUTRAL OUTCOME for {ticker} on {date}: Stock moved {returns:.2f}%, mixed signals. Lesson: These indicators don't provide clear bearish conviction. Need stronger warning signs for definitive bearish stance. """ def _create_trader_memory(self, situation: str, returns: float, ticker: str, date: str) -> str: """Create memory for trader based on outcome.""" if abs(returns) < 2: action = "HOLD" result = "correct - low volatility" elif returns > 5: action = "BUY" result = "would have been optimal" elif returns < -5: action = "SELL or avoid" result = "would have been optimal" else: action = "modest position" result = "moderate returns" return f"""TRADING OUTCOME for {ticker} on {date}: Stock returned {returns:.2f}% over the evaluation period. Optimal action: {action} - {result} Market conditions at the time: {situation[:500]}... Trading lesson: - When similar market conditions appear, consider {action} strategy - Risk/reward profile: {'Favorable' if abs(returns) > 3 else 'Neutral'} - Position sizing: {'Aggressive' if abs(returns) > 7 else 'Moderate' if abs(returns) > 3 else 'Conservative'} Recommendation: Pattern recognition suggests {action} in similar future scenarios. """ def _create_invest_judge_memory(self, situation: str, returns: float, ticker: str, date: str) -> str: """Create memory for investment judge/research manager.""" if returns > 5: verdict = "Strong BUY recommendation was warranted" elif returns > 2: verdict = "Moderate BUY recommendation was appropriate" elif returns < -5: verdict = "SELL or AVOID recommendation was warranted" elif returns < -2: verdict = "HOLD or reduce exposure was appropriate" else: verdict = "HOLD recommendation was appropriate" return f"""INVESTMENT DECISION REVIEW for {ticker} on {date}: Actual outcome: {returns:.2f}% return Optimal decision: {verdict} When synthesizing bull/bear arguments in similar conditions: - Weight the arguments based on which perspective proved more accurate - {"Bull arguments were stronger" if returns > 0 else "Bear arguments were stronger"} - Factor reliability: {'High' if abs(returns) > 5 else 'Medium' if abs(returns) > 2 else 'Low'} Recommendation for similar situations: {verdict} """ def _create_risk_manager_memory(self, situation: str, returns: float, ticker: str, date: str) -> str: """Create memory for risk manager.""" volatility = "HIGH" if abs(returns) > 10 else "MEDIUM" if abs(returns) > 5 else "LOW" if abs(returns) > 10: risk_assessment = "High risk - extreme volatility observed" elif abs(returns) > 5: risk_assessment = "Moderate risk - significant movement" else: risk_assessment = "Low risk - stable price action" return f"""RISK ASSESSMENT REVIEW for {ticker} on {date}: Observed volatility: {volatility} (actual return: {returns:.2f}%) Risk factors that materialized: - Price volatility: {volatility} - Directional risk: {'Significant downside' if returns < -5 else 'Significant upside' if returns > 5 else 'Minimal'} Risk management lesson: In similar market conditions: - Position size: {'Small (high risk)' if abs(returns) > 10 else 'Moderate' if abs(returns) > 5 else 'Standard'} - Stop loss: {'Tight (±5%)' if abs(returns) > 10 else 'Standard (±7%)' if abs(returns) > 5 else 'Relaxed (±10%)'} - Diversification: {'Critical' if abs(returns) > 10 else 'Recommended' if abs(returns) > 5 else 'Standard'} Recommendation: {risk_assessment} """ def build_memories_for_stock( self, ticker: str, start_date: str, end_date: str, lookforward_days: int = 7, interval_days: int = 30 ) -> Dict[str, List[Tuple[str, str]]]: """Build historical memories for a stock across a date range. Args: ticker: Stock ticker symbol start_date: Start date (YYYY-MM-DD) end_date: End date (YYYY-MM-DD) lookforward_days: How many days forward to measure returns (default: 7) interval_days: Days between memory samples (default: 30) Returns: Dictionary mapping agent type to list of (situation, lesson) tuples """ memories = { "bull": [], "bear": [], "trader": [], "invest_judge": [], "risk_manager": [] } current_date = datetime.strptime(start_date, "%Y-%m-%d") end_dt = datetime.strptime(end_date, "%Y-%m-%d") print(f"\n🧠 Building historical memories for {ticker}") print(f" Period: {start_date} to {end_date}") print(f" Lookforward: {lookforward_days} days") print(f" Sampling interval: {interval_days} days\n") sample_count = 0 while current_date <= end_dt: date_str = current_date.strftime("%Y-%m-%d") future_date_str = (current_date + timedelta(days=lookforward_days)).strftime("%Y-%m-%d") print(f" šŸ“Š Sampling {date_str}...", end=" ") # Get historical data for this period data = self._get_stock_data_for_period(ticker, date_str) situation = f"{data['market_report']}\n\n{data['sentiment_report']}\n\n{data['news_report']}\n\n{data['fundamentals_report']}" # Calculate actual returns returns = self._calculate_returns(ticker, date_str, future_date_str) if returns is not None: print(f"Return: {returns:+.2f}%") # Create agent-specific memories memories["bull"].append(( situation, self._create_bull_researcher_memory(situation, returns, ticker, date_str) )) memories["bear"].append(( situation, self._create_bear_researcher_memory(situation, returns, ticker, date_str) )) memories["trader"].append(( situation, self._create_trader_memory(situation, returns, ticker, date_str) )) memories["invest_judge"].append(( situation, self._create_invest_judge_memory(situation, returns, ticker, date_str) )) memories["risk_manager"].append(( situation, self._create_risk_manager_memory(situation, returns, ticker, date_str) )) sample_count += 1 else: print("āš ļø No data") # Move to next interval current_date += timedelta(days=interval_days) print(f"\nāœ… Created {sample_count} memory samples for {ticker}") for agent_type in memories: self.memories_created[agent_type] += len(memories[agent_type]) return memories def populate_agent_memories( self, tickers: List[str], start_date: str, end_date: str, lookforward_days: int = 7, interval_days: int = 30 ) -> Dict[str, FinancialSituationMemory]: """Build and populate memories for all agent types across multiple stocks. Args: tickers: List of stock ticker symbols start_date: Start date for historical analysis end_date: End date for historical analysis lookforward_days: Days forward to measure returns interval_days: Days between samples Returns: Dictionary of populated memory instances for each agent type """ # Initialize memory stores agent_memories = { "bull": FinancialSituationMemory("bull_memory", self.config), "bear": FinancialSituationMemory("bear_memory", self.config), "trader": FinancialSituationMemory("trader_memory", self.config), "invest_judge": FinancialSituationMemory("invest_judge_memory", self.config), "risk_manager": FinancialSituationMemory("risk_manager_memory", self.config) } print("=" * 70) print("šŸ—ļø HISTORICAL MEMORY BUILDER") print("=" * 70) # Build memories for each ticker for ticker in tickers: memories = self.build_memories_for_stock( ticker=ticker, start_date=start_date, end_date=end_date, lookforward_days=lookforward_days, interval_days=interval_days ) # Add memories to each agent's memory store for agent_type, memory_list in memories.items(): if memory_list: agent_memories[agent_type].add_situations(memory_list) # Print summary print("\n" + "=" * 70) print("šŸ“Š MEMORY CREATION SUMMARY") print("=" * 70) for agent_type, count in self.memories_created.items(): print(f" {agent_type.ljust(15)}: {count} memories") print("=" * 70 + "\n") return agent_memories # Example usage if __name__ == "__main__": from tradingagents.default_config import DEFAULT_CONFIG # Initialize builder builder = HistoricalMemoryBuilder(DEFAULT_CONFIG) # Build memories for specific stocks over past year tickers = ["AAPL", "GOOGL", "MSFT", "NVDA", "TSLA"] memories = builder.populate_agent_memories( tickers=tickers, start_date="2024-01-01", end_date="2024-12-01", lookforward_days=7, # 1-week returns interval_days=30 # Sample monthly ) # Test retrieval test_situation = "Strong earnings beat with positive sentiment and bullish technical indicators in tech sector" print("\nšŸ” Testing memory retrieval...") print(f"Query: {test_situation}\n") for agent_type, memory in memories.items(): print(f"\n{agent_type.upper()} MEMORIES:") results = memory.get_memories(test_situation, n_matches=2) for i, result in enumerate(results, 1): print(f"\n Match {i} (similarity: {result['similarity_score']:.2f}):") print(f" {result['recommendation'][:200]}...")