Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 40 additions & 4 deletions gui/run_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from queue import Empty, Queue
from typing import Any

from tradingagents.agents.utils.rating import parse_rating
from .agent_map import (
ANALYSTS,
FIXED_TEAMS,
Expand Down Expand Up @@ -333,9 +334,21 @@ def _worker(run: Run, persist_cb=None) -> None:
risk_state: dict = {}
final_state: dict = {}

# Memory-log parity with CLI propagate(): resolve pending outcomes
# for this ticker, then inject past context so agents see prior
# decisions. Failures here must never block the run.
past_context = ""
try:
if hasattr(graph, "_resolve_pending_entries"):
graph._resolve_pending_entries(run.ticker)
past_context = graph.memory_log.get_past_context(run.ticker) or ""
except Exception:
logger.exception("memory-log context load failed (continuing)")

init_state = {
"trade_date": run.date,
"company_of_interest": run.ticker,
"past_context": past_context,
"investment_debate_state": {"bull_history": "", "bear_history": "",
"judge_decision": "", "count": 0},
"risk_debate_state": {"aggressive_history": "", "conservative_history": "",
Expand Down Expand Up @@ -363,17 +376,40 @@ def _worker(run: Run, persist_cb=None) -> None:
run.roster[agent] = "completed"
run.emit({"type": "agents_update", "agents": dict(run.roster)})

for section in run.reports:
if section in final_state and final_state[section]:
run.reports[section] = str(final_state[section])
# final_state is keyed by node name (graph.stream chunk shape);
# report fields live INSIDE each node's output state.
for node_state in final_state.values():
if not isinstance(node_state, dict):
continue
for section in run.reports:
if node_state.get(section):
run.reports[section] = str(node_state[section])

if debate_state.get("bull_history") or debate_state.get("bear_history"):
# Format a combined debate report so the existing UI sees it.
run.reports["investment_plan"] = _format_debate(debate_state)
if risk_state.get("aggressive_history") or risk_state.get("conservative_history"):
run.reports["final_trade_decision"] = _format_risk(risk_state)

run.decision = (str(final_state.get("final_trade_decision", "")).strip() or None)
# Portfolio Manager verdict: parse the 5-tier rating out of the
# judge decision text (deterministic — same parser the memory
# log uses). final_state never carried this key (node-name keys),
# which is why every run displayed "No Decision".
_pm_text = (risk_state.get("judge_decision")
or run.reports.get("final_trade_decision") or "")
run.decision = parse_rating(_pm_text) if _pm_text.strip() else None

# CLI-parity memory write: propagate() stores the decision for
# deferred reflection; GUI streaming bypassed it, so the History
# tab's trading_memory.md was never created.
if _pm_text.strip():
try:
graph.memory_log.store_decision(
ticker=run.ticker, trade_date=run.date,
final_trade_decision=_pm_text,
)
except Exception:
logger.exception("memory-log store failed (continuing)")

_persist_reports(run, results_dir, debate_state, risk_state)

Expand Down
11 changes: 7 additions & 4 deletions tradingagents/agents/researchers/bear_researcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@ def bear_node(state) -> dict:
bear_history = investment_debate_state.get("bear_history", "")

current_response = investment_debate_state.get("current_response", "")
market_research_report = state["market_report"]
sentiment_report = state["sentiment_report"]
news_report = state["news_report"]
fundamentals_report = state["fundamentals_report"]
# .get(): analyst subsets are legal — a missing report reads as
# "(not produced this run)" instead of crashing the debate.
_missing = "(not produced this run)"
market_research_report = state.get("market_report") or _missing
sentiment_report = state.get("sentiment_report") or _missing
news_report = state.get("news_report") or _missing
fundamentals_report = state.get("fundamentals_report") or _missing

prompt = f"""You are a Bear Analyst making the case against investing in the stock. Your goal is to present a well-reasoned argument emphasizing risks, challenges, and negative indicators. Leverage the provided research and data to highlight potential downsides and counter bullish arguments effectively.

Expand Down
11 changes: 7 additions & 4 deletions tradingagents/agents/researchers/bull_researcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@ def bull_node(state) -> dict:
bull_history = investment_debate_state.get("bull_history", "")

current_response = investment_debate_state.get("current_response", "")
market_research_report = state["market_report"]
sentiment_report = state["sentiment_report"]
news_report = state["news_report"]
fundamentals_report = state["fundamentals_report"]
# .get(): analyst subsets are legal — a missing report reads as
# "(not produced this run)" instead of crashing the debate.
_missing = "(not produced this run)"
market_research_report = state.get("market_report") or _missing
sentiment_report = state.get("sentiment_report") or _missing
news_report = state.get("news_report") or _missing
fundamentals_report = state.get("fundamentals_report") or _missing

prompt = f"""You are a Bull Analyst advocating for investing in the stock. Your task is to build a strong, evidence-based case emphasizing growth potential, competitive advantages, and positive market indicators. Leverage the provided research and data to address concerns and counter bearish arguments effectively.

Expand Down
11 changes: 7 additions & 4 deletions tradingagents/agents/risk_mgmt/aggressive_debator.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@ def aggressive_node(state) -> dict:
current_conservative_response = risk_debate_state.get("current_conservative_response", "")
current_neutral_response = risk_debate_state.get("current_neutral_response", "")

market_research_report = state["market_report"]
sentiment_report = state["sentiment_report"]
news_report = state["news_report"]
fundamentals_report = state["fundamentals_report"]
# .get(): analyst subsets are legal — a missing report reads as
# "(not produced this run)" instead of crashing the debate.
_missing = "(not produced this run)"
market_research_report = state.get("market_report") or _missing
sentiment_report = state.get("sentiment_report") or _missing
news_report = state.get("news_report") or _missing
fundamentals_report = state.get("fundamentals_report") or _missing

trader_decision = state["trader_investment_plan"]

Expand Down
11 changes: 7 additions & 4 deletions tradingagents/agents/risk_mgmt/conservative_debator.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@ def conservative_node(state) -> dict:
current_aggressive_response = risk_debate_state.get("current_aggressive_response", "")
current_neutral_response = risk_debate_state.get("current_neutral_response", "")

market_research_report = state["market_report"]
sentiment_report = state["sentiment_report"]
news_report = state["news_report"]
fundamentals_report = state["fundamentals_report"]
# .get(): analyst subsets are legal — a missing report reads as
# "(not produced this run)" instead of crashing the debate.
_missing = "(not produced this run)"
market_research_report = state.get("market_report") or _missing
sentiment_report = state.get("sentiment_report") or _missing
news_report = state.get("news_report") or _missing
fundamentals_report = state.get("fundamentals_report") or _missing

trader_decision = state["trader_investment_plan"]

Expand Down
11 changes: 7 additions & 4 deletions tradingagents/agents/risk_mgmt/neutral_debator.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@ def neutral_node(state) -> dict:
current_aggressive_response = risk_debate_state.get("current_aggressive_response", "")
current_conservative_response = risk_debate_state.get("current_conservative_response", "")

market_research_report = state["market_report"]
sentiment_report = state["sentiment_report"]
news_report = state["news_report"]
fundamentals_report = state["fundamentals_report"]
# .get(): analyst subsets are legal — a missing report reads as
# "(not produced this run)" instead of crashing the debate.
_missing = "(not produced this run)"
market_research_report = state.get("market_report") or _missing
sentiment_report = state.get("sentiment_report") or _missing
news_report = state.get("news_report") or _missing
fundamentals_report = state.get("fundamentals_report") or _missing

trader_decision = state["trader_investment_plan"]

Expand Down
8 changes: 4 additions & 4 deletions tradingagents/graph/trading_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -385,10 +385,10 @@ def _log_state(self, trade_date, final_state):
self.log_states_dict[str(trade_date)] = {
"company_of_interest": final_state["company_of_interest"],
"trade_date": final_state["trade_date"],
"market_report": final_state["market_report"],
"sentiment_report": final_state["sentiment_report"],
"news_report": final_state["news_report"],
"fundamentals_report": final_state["fundamentals_report"],
"market_report": final_state.get("market_report", ""),
"sentiment_report": final_state.get("sentiment_report", ""),
"news_report": final_state.get("news_report", ""),
"fundamentals_report": final_state.get("fundamentals_report", ""),
"investment_debate_state": {
"bull_history": final_state["investment_debate_state"]["bull_history"],
"bear_history": final_state["investment_debate_state"]["bear_history"],
Expand Down