feat: replace per-agent BM25 memory with persistent append-only decision log

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Zhigong Liu
2026-04-19 22:13:53 -04:00
parent 8536ccacdd
commit 6abc768c1d
15 changed files with 1046 additions and 340 deletions
+35 -102
View File
@@ -1,120 +1,53 @@
# TradingAgents/graph/reflection.py
from typing import Any, Dict
from typing import Any
class Reflector:
"""Handles reflection on decisions and updating memory."""
"""Handles reflection on trading decisions."""
def __init__(self, quick_thinking_llm: Any):
"""Initialize the reflector with an LLM."""
self.quick_thinking_llm = quick_thinking_llm
self.reflection_system_prompt = self._get_reflection_prompt()
self.log_reflection_prompt = self._get_log_reflection_prompt()
def _get_reflection_prompt(self) -> str:
"""Get the system prompt for reflection."""
return """
You are an expert financial analyst tasked with reviewing trading decisions/analysis and providing a comprehensive, step-by-step analysis.
Your goal is to deliver detailed insights into investment decisions and highlight opportunities for improvement, adhering strictly to the following guidelines:
def _get_log_reflection_prompt(self) -> str:
"""Concise prompt for reflect_on_final_decision (Phase B log entries).
1. Reasoning:
- For each trading decision, determine whether it was correct or incorrect. A correct decision results in an increase in returns, while an incorrect decision does the opposite.
- Analyze the contributing factors to each success or mistake. Consider:
- Market intelligence.
- Technical indicators.
- Technical signals.
- Price movement analysis.
- Overall market data analysis
- News analysis.
- Social media and sentiment analysis.
- Fundamental data analysis.
- Weight the importance of each factor in the decision-making process.
Produces 2-4 sentences of plain prose — compact enough to be re-injected
into future agent prompts without bloating the context window.
"""
return (
"You are a trading analyst reviewing your own past decision now that the outcome is known.\n"
"Write exactly 2-4 sentences of plain prose (no bullets, no headers, no markdown).\n\n"
"Cover in order:\n"
"1. Was the directional call correct? (cite the alpha figure)\n"
"2. Which part of the investment thesis held or failed?\n"
"3. One concrete lesson to apply to the next similar analysis.\n\n"
"Be specific and terse. Your output will be stored verbatim in a decision log "
"and re-read by future analysts, so every word must earn its place."
)
2. Improvement:
- For any incorrect decisions, propose revisions to maximize returns.
- Provide a detailed list of corrective actions or improvements, including specific recommendations (e.g., changing a decision from HOLD to BUY on a particular date).
3. Summary:
- Summarize the lessons learned from the successes and mistakes.
- Highlight how these lessons can be adapted for future trading scenarios and draw connections between similar situations to apply the knowledge gained.
4. Query:
- Extract key insights from the summary into a concise sentence of no more than 1000 tokens.
- Ensure the condensed sentence captures the essence of the lessons and reasoning for easy reference.
Adhere strictly to these instructions, and ensure your output is detailed, accurate, and actionable. You will also be given objective descriptions of the market from a price movements, technical indicator, news, and sentiment perspective to provide more context for your analysis.
"""
def _extract_current_situation(self, current_state: Dict[str, Any]) -> str:
"""Extract the current market situation from the state."""
curr_market_report = current_state["market_report"]
curr_sentiment_report = current_state["sentiment_report"]
curr_news_report = current_state["news_report"]
curr_fundamentals_report = current_state["fundamentals_report"]
return f"{curr_market_report}\n\n{curr_sentiment_report}\n\n{curr_news_report}\n\n{curr_fundamentals_report}"
def _reflect_on_component(
self, component_type: str, report: str, situation: str, returns_losses
def reflect_on_final_decision(
self,
final_decision: str,
raw_return: float,
alpha_return: float,
) -> str:
"""Generate reflection for a component."""
"""Single reflection call on the final trade decision with outcome context.
Used by Phase B deferred reflection. The final_trade_decision already
synthesises all analyst insights, so no separate market context is needed.
"""
messages = [
("system", self.reflection_system_prompt),
("system", self.log_reflection_prompt),
(
"human",
f"Returns: {returns_losses}\n\nAnalysis/Decision: {report}\n\nObjective Market Reports for Reference: {situation}",
(
f"Raw return: {raw_return:+.1%}\n"
f"Alpha vs SPY: {alpha_return:+.1%}\n\n"
f"Final Decision:\n{final_decision}"
),
),
]
result = self.quick_thinking_llm.invoke(messages).content
return result
def reflect_bull_researcher(self, current_state, returns_losses, bull_memory):
"""Reflect on bull researcher's analysis and update memory."""
situation = self._extract_current_situation(current_state)
bull_debate_history = current_state["investment_debate_state"]["bull_history"]
result = self._reflect_on_component(
"BULL", bull_debate_history, situation, returns_losses
)
bull_memory.add_situations([(situation, result)])
def reflect_bear_researcher(self, current_state, returns_losses, bear_memory):
"""Reflect on bear researcher's analysis and update memory."""
situation = self._extract_current_situation(current_state)
bear_debate_history = current_state["investment_debate_state"]["bear_history"]
result = self._reflect_on_component(
"BEAR", bear_debate_history, situation, returns_losses
)
bear_memory.add_situations([(situation, result)])
def reflect_trader(self, current_state, returns_losses, trader_memory):
"""Reflect on trader's decision and update memory."""
situation = self._extract_current_situation(current_state)
trader_decision = current_state["trader_investment_plan"]
result = self._reflect_on_component(
"TRADER", trader_decision, situation, returns_losses
)
trader_memory.add_situations([(situation, result)])
def reflect_invest_judge(self, current_state, returns_losses, invest_judge_memory):
"""Reflect on investment judge's decision and update memory."""
situation = self._extract_current_situation(current_state)
judge_decision = current_state["investment_debate_state"]["judge_decision"]
result = self._reflect_on_component(
"INVEST JUDGE", judge_decision, situation, returns_losses
)
invest_judge_memory.add_situations([(situation, result)])
def reflect_portfolio_manager(self, current_state, returns_losses, portfolio_manager_memory):
"""Reflect on portfolio manager's decision and update memory."""
situation = self._extract_current_situation(current_state)
judge_decision = current_state["risk_debate_state"]["judge_decision"]
result = self._reflect_on_component(
"PORTFOLIO MANAGER", judge_decision, situation, returns_losses
)
portfolio_manager_memory.add_situations([(situation, result)])
return self.quick_thinking_llm.invoke(messages).content