Merge pull request #71 from aguzererler/perf-optimize-completed-reports-count-11317210005896921131
⚡ [Performance] Optimize redundant iteration in get_completed_reports_count
This commit is contained in:
commit
822b786fbe
414
cli/main.py
414
cli/main.py
|
|
@ -15,14 +15,12 @@ load_dotenv(Path(__file__).resolve().parent.parent / ".env")
|
||||||
from rich.panel import Panel
|
from rich.panel import Panel
|
||||||
from rich.spinner import Spinner
|
from rich.spinner import Spinner
|
||||||
from rich.live import Live
|
from rich.live import Live
|
||||||
from rich.columns import Columns
|
|
||||||
from rich.markdown import Markdown
|
from rich.markdown import Markdown
|
||||||
from rich.layout import Layout
|
from rich.layout import Layout
|
||||||
from rich.text import Text
|
from rich.text import Text
|
||||||
from rich.table import Table
|
from rich.table import Table
|
||||||
from collections import deque
|
from collections import deque
|
||||||
import time
|
import time
|
||||||
from rich.tree import Tree
|
|
||||||
from rich import box
|
from rich import box
|
||||||
from rich.align import Align
|
from rich.align import Align
|
||||||
from rich.rule import Rule
|
from rich.rule import Rule
|
||||||
|
|
@ -32,7 +30,6 @@ from tradingagents.report_paths import get_daily_dir, get_market_dir, get_ticker
|
||||||
from tradingagents.daily_digest import append_to_digest
|
from tradingagents.daily_digest import append_to_digest
|
||||||
from tradingagents.notebook_sync import sync_to_notebooklm
|
from tradingagents.notebook_sync import sync_to_notebooklm
|
||||||
from tradingagents.default_config import DEFAULT_CONFIG
|
from tradingagents.default_config import DEFAULT_CONFIG
|
||||||
from cli.models import AnalystType
|
|
||||||
from cli.utils import *
|
from cli.utils import *
|
||||||
from tradingagents.graph.scanner_graph import ScannerGraph
|
from tradingagents.graph.scanner_graph import ScannerGraph
|
||||||
from cli.announcements import fetch_announcements, display_announcements
|
from cli.announcements import fetch_announcements, display_announcements
|
||||||
|
|
@ -55,7 +52,11 @@ class MessageBuffer:
|
||||||
FIXED_AGENTS = {
|
FIXED_AGENTS = {
|
||||||
"Research Team": ["Bull Researcher", "Bear Researcher", "Research Manager"],
|
"Research Team": ["Bull Researcher", "Bear Researcher", "Research Manager"],
|
||||||
"Trading Team": ["Trader"],
|
"Trading Team": ["Trader"],
|
||||||
"Risk Management": ["Aggressive Analyst", "Neutral Analyst", "Conservative Analyst"],
|
"Risk Management": [
|
||||||
|
"Aggressive Analyst",
|
||||||
|
"Neutral Analyst",
|
||||||
|
"Conservative Analyst",
|
||||||
|
],
|
||||||
"Portfolio Management": ["Portfolio Manager"],
|
"Portfolio Management": ["Portfolio Manager"],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -136,14 +137,9 @@ class MessageBuffer:
|
||||||
This prevents interim updates (like debate rounds) from counting as completed.
|
This prevents interim updates (like debate rounds) from counting as completed.
|
||||||
"""
|
"""
|
||||||
count = 0
|
count = 0
|
||||||
for section in self.report_sections:
|
for section, (_, finalizing_agent) in self.REPORT_SECTIONS.items():
|
||||||
if section not in self.REPORT_SECTIONS:
|
if self.report_sections.get(section) is not None:
|
||||||
continue
|
if self.agent_status.get(finalizing_agent) == "completed":
|
||||||
_, finalizing_agent = self.REPORT_SECTIONS[section]
|
|
||||||
# Report is complete if it has content AND its finalizing agent is done
|
|
||||||
has_content = self.report_sections.get(section) is not None
|
|
||||||
agent_done = self.agent_status.get(finalizing_agent) == "completed"
|
|
||||||
if has_content and agent_done:
|
|
||||||
count += 1
|
count += 1
|
||||||
return count
|
return count
|
||||||
|
|
||||||
|
|
@ -198,7 +194,12 @@ class MessageBuffer:
|
||||||
report_parts = []
|
report_parts = []
|
||||||
|
|
||||||
# Analyst Team Reports - use .get() to handle missing sections
|
# Analyst Team Reports - use .get() to handle missing sections
|
||||||
analyst_sections = ["market_report", "sentiment_report", "news_report", "fundamentals_report"]
|
analyst_sections = [
|
||||||
|
"market_report",
|
||||||
|
"sentiment_report",
|
||||||
|
"news_report",
|
||||||
|
"fundamentals_report",
|
||||||
|
]
|
||||||
if any(self.report_sections.get(section) for section in analyst_sections):
|
if any(self.report_sections.get(section) for section in analyst_sections):
|
||||||
report_parts.append("## Analyst Team Reports")
|
report_parts.append("## Analyst Team Reports")
|
||||||
if self.report_sections.get("market_report"):
|
if self.report_sections.get("market_report"):
|
||||||
|
|
@ -258,7 +259,7 @@ def create_layout():
|
||||||
def format_tokens(n):
|
def format_tokens(n):
|
||||||
"""Format token count for display."""
|
"""Format token count for display."""
|
||||||
if n >= 1000:
|
if n >= 1000:
|
||||||
return f"{n/1000:.1f}k"
|
return f"{n / 1000:.1f}k"
|
||||||
return str(n)
|
return str(n)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -299,7 +300,11 @@ def update_display(layout, spinner_text=None, stats_handler=None, start_time=Non
|
||||||
],
|
],
|
||||||
"Research Team": ["Bull Researcher", "Bear Researcher", "Research Manager"],
|
"Research Team": ["Bull Researcher", "Bear Researcher", "Research Manager"],
|
||||||
"Trading Team": ["Trader"],
|
"Trading Team": ["Trader"],
|
||||||
"Risk Management": ["Aggressive Analyst", "Neutral Analyst", "Conservative Analyst"],
|
"Risk Management": [
|
||||||
|
"Aggressive Analyst",
|
||||||
|
"Neutral Analyst",
|
||||||
|
"Conservative Analyst",
|
||||||
|
],
|
||||||
"Portfolio Management": ["Portfolio Manager"],
|
"Portfolio Management": ["Portfolio Manager"],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -561,34 +566,40 @@ def get_user_selections():
|
||||||
console.print(
|
console.print(
|
||||||
create_question_box(
|
create_question_box(
|
||||||
"Step 5: Quick-Thinking Setup",
|
"Step 5: Quick-Thinking Setup",
|
||||||
"Provider and model for analysts & risk debaters (fast, high volume)"
|
"Provider and model for analysts & risk debaters (fast, high volume)",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
quick_provider, quick_backend_url = select_llm_provider()
|
quick_provider, quick_backend_url = select_llm_provider()
|
||||||
selected_shallow_thinker = select_shallow_thinking_agent(quick_provider)
|
selected_shallow_thinker = select_shallow_thinking_agent(quick_provider)
|
||||||
quick_thinking_level, quick_reasoning_effort = _ask_provider_thinking_config(quick_provider)
|
quick_thinking_level, quick_reasoning_effort = _ask_provider_thinking_config(
|
||||||
|
quick_provider
|
||||||
|
)
|
||||||
|
|
||||||
# Step 6: Mid-thinking provider + model
|
# Step 6: Mid-thinking provider + model
|
||||||
console.print(
|
console.print(
|
||||||
create_question_box(
|
create_question_box(
|
||||||
"Step 6: Mid-Thinking Setup",
|
"Step 6: Mid-Thinking Setup",
|
||||||
"Provider and model for researchers & trader (reasoning, argument formation)"
|
"Provider and model for researchers & trader (reasoning, argument formation)",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
mid_provider, mid_backend_url = select_llm_provider()
|
mid_provider, mid_backend_url = select_llm_provider()
|
||||||
selected_mid_thinker = select_mid_thinking_agent(mid_provider)
|
selected_mid_thinker = select_mid_thinking_agent(mid_provider)
|
||||||
mid_thinking_level, mid_reasoning_effort = _ask_provider_thinking_config(mid_provider)
|
mid_thinking_level, mid_reasoning_effort = _ask_provider_thinking_config(
|
||||||
|
mid_provider
|
||||||
|
)
|
||||||
|
|
||||||
# Step 7: Deep-thinking provider + model
|
# Step 7: Deep-thinking provider + model
|
||||||
console.print(
|
console.print(
|
||||||
create_question_box(
|
create_question_box(
|
||||||
"Step 7: Deep-Thinking Setup",
|
"Step 7: Deep-Thinking Setup",
|
||||||
"Provider and model for investment judge & risk manager (final decisions)"
|
"Provider and model for investment judge & risk manager (final decisions)",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
deep_provider, deep_backend_url = select_llm_provider()
|
deep_provider, deep_backend_url = select_llm_provider()
|
||||||
selected_deep_thinker = select_deep_thinking_agent(deep_provider)
|
selected_deep_thinker = select_deep_thinking_agent(deep_provider)
|
||||||
deep_thinking_level, deep_reasoning_effort = _ask_provider_thinking_config(deep_provider)
|
deep_thinking_level, deep_reasoning_effort = _ask_provider_thinking_config(
|
||||||
|
deep_provider
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"ticker": selected_ticker,
|
"ticker": selected_ticker,
|
||||||
|
|
@ -638,8 +649,12 @@ def save_report_to_disk(final_state, ticker: str, save_path: Path):
|
||||||
analyst_parts.append(("News Analyst", final_state["news_report"]))
|
analyst_parts.append(("News Analyst", final_state["news_report"]))
|
||||||
if final_state.get("fundamentals_report"):
|
if final_state.get("fundamentals_report"):
|
||||||
analysts_dir.mkdir(exist_ok=True)
|
analysts_dir.mkdir(exist_ok=True)
|
||||||
(analysts_dir / "fundamentals.md").write_text(final_state["fundamentals_report"])
|
(analysts_dir / "fundamentals.md").write_text(
|
||||||
analyst_parts.append(("Fundamentals Analyst", final_state["fundamentals_report"]))
|
final_state["fundamentals_report"]
|
||||||
|
)
|
||||||
|
analyst_parts.append(
|
||||||
|
("Fundamentals Analyst", final_state["fundamentals_report"])
|
||||||
|
)
|
||||||
if analyst_parts:
|
if analyst_parts:
|
||||||
content = "\n\n".join(f"### {name}\n{text}" for name, text in analyst_parts)
|
content = "\n\n".join(f"### {name}\n{text}" for name, text in analyst_parts)
|
||||||
sections.append(f"## I. Analyst Team Reports\n\n{content}")
|
sections.append(f"## I. Analyst Team Reports\n\n{content}")
|
||||||
|
|
@ -662,7 +677,9 @@ def save_report_to_disk(final_state, ticker: str, save_path: Path):
|
||||||
(research_dir / "manager.md").write_text(debate["judge_decision"])
|
(research_dir / "manager.md").write_text(debate["judge_decision"])
|
||||||
research_parts.append(("Research Manager", debate["judge_decision"]))
|
research_parts.append(("Research Manager", debate["judge_decision"]))
|
||||||
if research_parts:
|
if research_parts:
|
||||||
content = "\n\n".join(f"### {name}\n{text}" for name, text in research_parts)
|
content = "\n\n".join(
|
||||||
|
f"### {name}\n{text}" for name, text in research_parts
|
||||||
|
)
|
||||||
sections.append(f"## II. Research Team Decision\n\n{content}")
|
sections.append(f"## II. Research Team Decision\n\n{content}")
|
||||||
|
|
||||||
# 3. Trading
|
# 3. Trading
|
||||||
|
|
@ -670,7 +687,9 @@ def save_report_to_disk(final_state, ticker: str, save_path: Path):
|
||||||
trading_dir = save_path / "3_trading"
|
trading_dir = save_path / "3_trading"
|
||||||
trading_dir.mkdir(exist_ok=True)
|
trading_dir.mkdir(exist_ok=True)
|
||||||
(trading_dir / "trader.md").write_text(final_state["trader_investment_plan"])
|
(trading_dir / "trader.md").write_text(final_state["trader_investment_plan"])
|
||||||
sections.append(f"## III. Trading Team Plan\n\n### Trader\n{final_state['trader_investment_plan']}")
|
sections.append(
|
||||||
|
f"## III. Trading Team Plan\n\n### Trader\n{final_state['trader_investment_plan']}"
|
||||||
|
)
|
||||||
|
|
||||||
# 4. Risk Management
|
# 4. Risk Management
|
||||||
if final_state.get("risk_debate_state"):
|
if final_state.get("risk_debate_state"):
|
||||||
|
|
@ -698,7 +717,9 @@ def save_report_to_disk(final_state, ticker: str, save_path: Path):
|
||||||
portfolio_dir = save_path / "5_portfolio"
|
portfolio_dir = save_path / "5_portfolio"
|
||||||
portfolio_dir.mkdir(exist_ok=True)
|
portfolio_dir.mkdir(exist_ok=True)
|
||||||
(portfolio_dir / "decision.md").write_text(risk["judge_decision"])
|
(portfolio_dir / "decision.md").write_text(risk["judge_decision"])
|
||||||
sections.append(f"## V. Portfolio Manager Decision\n\n### Portfolio Manager\n{risk['judge_decision']}")
|
sections.append(
|
||||||
|
f"## V. Portfolio Manager Decision\n\n### Portfolio Manager\n{risk['judge_decision']}"
|
||||||
|
)
|
||||||
|
|
||||||
# Write consolidated report
|
# Write consolidated report
|
||||||
header = f"# Trading Analysis Report: {ticker}\n\nGenerated: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
|
header = f"# Trading Analysis Report: {ticker}\n\nGenerated: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
|
||||||
|
|
@ -722,9 +743,15 @@ def display_complete_report(final_state):
|
||||||
if final_state.get("fundamentals_report"):
|
if final_state.get("fundamentals_report"):
|
||||||
analysts.append(("Fundamentals Analyst", final_state["fundamentals_report"]))
|
analysts.append(("Fundamentals Analyst", final_state["fundamentals_report"]))
|
||||||
if analysts:
|
if analysts:
|
||||||
console.print(Panel("[bold]I. Analyst Team Reports[/bold]", border_style="cyan"))
|
console.print(
|
||||||
|
Panel("[bold]I. Analyst Team Reports[/bold]", border_style="cyan")
|
||||||
|
)
|
||||||
for title, content in analysts:
|
for title, content in analysts:
|
||||||
console.print(Panel(Markdown(content), title=title, border_style="blue", padding=(1, 2)))
|
console.print(
|
||||||
|
Panel(
|
||||||
|
Markdown(content), title=title, border_style="blue", padding=(1, 2)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# II. Research Team Reports
|
# II. Research Team Reports
|
||||||
if final_state.get("investment_debate_state"):
|
if final_state.get("investment_debate_state"):
|
||||||
|
|
@ -737,14 +764,32 @@ def display_complete_report(final_state):
|
||||||
if debate.get("judge_decision"):
|
if debate.get("judge_decision"):
|
||||||
research.append(("Research Manager", debate["judge_decision"]))
|
research.append(("Research Manager", debate["judge_decision"]))
|
||||||
if research:
|
if research:
|
||||||
console.print(Panel("[bold]II. Research Team Decision[/bold]", border_style="magenta"))
|
console.print(
|
||||||
|
Panel("[bold]II. Research Team Decision[/bold]", border_style="magenta")
|
||||||
|
)
|
||||||
for title, content in research:
|
for title, content in research:
|
||||||
console.print(Panel(Markdown(content), title=title, border_style="blue", padding=(1, 2)))
|
console.print(
|
||||||
|
Panel(
|
||||||
|
Markdown(content),
|
||||||
|
title=title,
|
||||||
|
border_style="blue",
|
||||||
|
padding=(1, 2),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# III. Trading Team
|
# III. Trading Team
|
||||||
if final_state.get("trader_investment_plan"):
|
if final_state.get("trader_investment_plan"):
|
||||||
console.print(Panel("[bold]III. Trading Team Plan[/bold]", border_style="yellow"))
|
console.print(
|
||||||
console.print(Panel(Markdown(final_state["trader_investment_plan"]), title="Trader", border_style="blue", padding=(1, 2)))
|
Panel("[bold]III. Trading Team Plan[/bold]", border_style="yellow")
|
||||||
|
)
|
||||||
|
console.print(
|
||||||
|
Panel(
|
||||||
|
Markdown(final_state["trader_investment_plan"]),
|
||||||
|
title="Trader",
|
||||||
|
border_style="blue",
|
||||||
|
padding=(1, 2),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# IV. Risk Management Team
|
# IV. Risk Management Team
|
||||||
if final_state.get("risk_debate_state"):
|
if final_state.get("risk_debate_state"):
|
||||||
|
|
@ -757,14 +802,36 @@ def display_complete_report(final_state):
|
||||||
if risk.get("neutral_history"):
|
if risk.get("neutral_history"):
|
||||||
risk_reports.append(("Neutral Analyst", risk["neutral_history"]))
|
risk_reports.append(("Neutral Analyst", risk["neutral_history"]))
|
||||||
if risk_reports:
|
if risk_reports:
|
||||||
console.print(Panel("[bold]IV. Risk Management Team Decision[/bold]", border_style="red"))
|
console.print(
|
||||||
|
Panel(
|
||||||
|
"[bold]IV. Risk Management Team Decision[/bold]", border_style="red"
|
||||||
|
)
|
||||||
|
)
|
||||||
for title, content in risk_reports:
|
for title, content in risk_reports:
|
||||||
console.print(Panel(Markdown(content), title=title, border_style="blue", padding=(1, 2)))
|
console.print(
|
||||||
|
Panel(
|
||||||
|
Markdown(content),
|
||||||
|
title=title,
|
||||||
|
border_style="blue",
|
||||||
|
padding=(1, 2),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# V. Portfolio Manager Decision
|
# V. Portfolio Manager Decision
|
||||||
if risk.get("judge_decision"):
|
if risk.get("judge_decision"):
|
||||||
console.print(Panel("[bold]V. Portfolio Manager Decision[/bold]", border_style="green"))
|
console.print(
|
||||||
console.print(Panel(Markdown(risk["judge_decision"]), title="Portfolio Manager", border_style="blue", padding=(1, 2)))
|
Panel(
|
||||||
|
"[bold]V. Portfolio Manager Decision[/bold]", border_style="green"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
console.print(
|
||||||
|
Panel(
|
||||||
|
Markdown(risk["judge_decision"]),
|
||||||
|
title="Portfolio Manager",
|
||||||
|
border_style="blue",
|
||||||
|
padding=(1, 2),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def update_research_team_status(status):
|
def update_research_team_status(status):
|
||||||
|
|
@ -824,6 +891,7 @@ def update_analyst_statuses(message_buffer, chunk):
|
||||||
if message_buffer.agent_status.get("Bull Researcher") == "pending":
|
if message_buffer.agent_status.get("Bull Researcher") == "pending":
|
||||||
message_buffer.update_agent_status("Bull Researcher", "in_progress")
|
message_buffer.update_agent_status("Bull Researcher", "in_progress")
|
||||||
|
|
||||||
|
|
||||||
def extract_content_string(content):
|
def extract_content_string(content):
|
||||||
"""Extract string content from various message formats.
|
"""Extract string content from various message formats.
|
||||||
Returns None if no meaningful text content is found.
|
Returns None if no meaningful text content is found.
|
||||||
|
|
@ -832,7 +900,7 @@ def extract_content_string(content):
|
||||||
|
|
||||||
def is_empty(val):
|
def is_empty(val):
|
||||||
"""Check if value is empty using Python's truthiness."""
|
"""Check if value is empty using Python's truthiness."""
|
||||||
if val is None or val == '':
|
if val is None or val == "":
|
||||||
return True
|
return True
|
||||||
if isinstance(val, str):
|
if isinstance(val, str):
|
||||||
s = val.strip()
|
s = val.strip()
|
||||||
|
|
@ -851,16 +919,17 @@ def extract_content_string(content):
|
||||||
return content.strip()
|
return content.strip()
|
||||||
|
|
||||||
if isinstance(content, dict):
|
if isinstance(content, dict):
|
||||||
text = content.get('text', '')
|
text = content.get("text", "")
|
||||||
return text.strip() if not is_empty(text) else None
|
return text.strip() if not is_empty(text) else None
|
||||||
|
|
||||||
if isinstance(content, list):
|
if isinstance(content, list):
|
||||||
text_parts = [
|
text_parts = [
|
||||||
item.get('text', '').strip() if isinstance(item, dict) and item.get('type') == 'text'
|
item.get("text", "").strip()
|
||||||
else (item.strip() if isinstance(item, str) else '')
|
if isinstance(item, dict) and item.get("type") == "text"
|
||||||
|
else (item.strip() if isinstance(item, str) else "")
|
||||||
for item in content
|
for item in content
|
||||||
]
|
]
|
||||||
result = ' '.join(t for t in text_parts if t and not is_empty(t))
|
result = " ".join(t for t in text_parts if t and not is_empty(t))
|
||||||
return result if result else None
|
return result if result else None
|
||||||
|
|
||||||
return str(content).strip() if not is_empty(content) else None
|
return str(content).strip() if not is_empty(content) else None
|
||||||
|
|
@ -875,7 +944,7 @@ def classify_message_type(message) -> tuple[str, str | None]:
|
||||||
"""
|
"""
|
||||||
from langchain_core.messages import AIMessage, HumanMessage, ToolMessage
|
from langchain_core.messages import AIMessage, HumanMessage, ToolMessage
|
||||||
|
|
||||||
content = extract_content_string(getattr(message, 'content', None))
|
content = extract_content_string(getattr(message, "content", None))
|
||||||
|
|
||||||
if isinstance(message, HumanMessage):
|
if isinstance(message, HumanMessage):
|
||||||
if content and content.strip() == "Continue":
|
if content and content.strip() == "Continue":
|
||||||
|
|
@ -920,13 +989,15 @@ def parse_tool_call(tool_call) -> tuple[str, dict | str]:
|
||||||
args = getattr(tool_call, "args", getattr(tool_call, "arguments", {}))
|
args = getattr(tool_call, "args", getattr(tool_call, "arguments", {}))
|
||||||
return tool_name, args
|
return tool_name, args
|
||||||
|
|
||||||
|
|
||||||
def format_tool_args(args, max_length=80) -> str:
|
def format_tool_args(args, max_length=80) -> str:
|
||||||
"""Format tool arguments for terminal display."""
|
"""Format tool arguments for terminal display."""
|
||||||
result = str(args)
|
result = str(args)
|
||||||
if len(result) > max_length:
|
if len(result) > max_length:
|
||||||
return result[:max_length - 3] + "..."
|
return result[: max_length - 3] + "..."
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def run_analysis():
|
def run_analysis():
|
||||||
# First get all user selections
|
# First get all user selections
|
||||||
selections = get_user_selections()
|
selections = get_user_selections()
|
||||||
|
|
@ -940,7 +1011,9 @@ def run_analysis():
|
||||||
config["quick_think_llm_provider"] = selections["quick_provider"]
|
config["quick_think_llm_provider"] = selections["quick_provider"]
|
||||||
config["quick_think_backend_url"] = selections["quick_backend_url"]
|
config["quick_think_backend_url"] = selections["quick_backend_url"]
|
||||||
config["quick_think_google_thinking_level"] = selections.get("quick_thinking_level")
|
config["quick_think_google_thinking_level"] = selections.get("quick_thinking_level")
|
||||||
config["quick_think_openai_reasoning_effort"] = selections.get("quick_reasoning_effort")
|
config["quick_think_openai_reasoning_effort"] = selections.get(
|
||||||
|
"quick_reasoning_effort"
|
||||||
|
)
|
||||||
config["mid_think_llm"] = selections["mid_thinker"]
|
config["mid_think_llm"] = selections["mid_thinker"]
|
||||||
config["mid_think_llm_provider"] = selections["mid_provider"]
|
config["mid_think_llm_provider"] = selections["mid_provider"]
|
||||||
config["mid_think_backend_url"] = selections["mid_backend_url"]
|
config["mid_think_backend_url"] = selections["mid_backend_url"]
|
||||||
|
|
@ -950,7 +1023,9 @@ def run_analysis():
|
||||||
config["deep_think_llm_provider"] = selections["deep_provider"]
|
config["deep_think_llm_provider"] = selections["deep_provider"]
|
||||||
config["deep_think_backend_url"] = selections["deep_backend_url"]
|
config["deep_think_backend_url"] = selections["deep_backend_url"]
|
||||||
config["deep_think_google_thinking_level"] = selections.get("deep_thinking_level")
|
config["deep_think_google_thinking_level"] = selections.get("deep_thinking_level")
|
||||||
config["deep_think_openai_reasoning_effort"] = selections.get("deep_reasoning_effort")
|
config["deep_think_openai_reasoning_effort"] = selections.get(
|
||||||
|
"deep_reasoning_effort"
|
||||||
|
)
|
||||||
# Keep shared llm_provider/backend_url as a fallback (use quick as default)
|
# Keep shared llm_provider/backend_url as a fallback (use quick as default)
|
||||||
config["llm_provider"] = selections["quick_provider"]
|
config["llm_provider"] = selections["quick_provider"]
|
||||||
config["backend_url"] = selections["quick_backend_url"]
|
config["backend_url"] = selections["quick_backend_url"]
|
||||||
|
|
@ -988,6 +1063,7 @@ def run_analysis():
|
||||||
|
|
||||||
def save_message_decorator(obj, func_name):
|
def save_message_decorator(obj, func_name):
|
||||||
func = getattr(obj, func_name)
|
func = getattr(obj, func_name)
|
||||||
|
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
func(*args, **kwargs)
|
func(*args, **kwargs)
|
||||||
|
|
@ -995,10 +1071,12 @@ def run_analysis():
|
||||||
content = content.replace("\n", " ") # Replace newlines with spaces
|
content = content.replace("\n", " ") # Replace newlines with spaces
|
||||||
with open(log_file, "a", encoding="utf-8") as f:
|
with open(log_file, "a", encoding="utf-8") as f:
|
||||||
f.write(f"{timestamp} [{message_type}] {content}\n")
|
f.write(f"{timestamp} [{message_type}] {content}\n")
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
def save_tool_call_decorator(obj, func_name):
|
def save_tool_call_decorator(obj, func_name):
|
||||||
func = getattr(obj, func_name)
|
func = getattr(obj, func_name)
|
||||||
|
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
func(*args, **kwargs)
|
func(*args, **kwargs)
|
||||||
|
|
@ -1006,24 +1084,34 @@ def run_analysis():
|
||||||
args_str = ", ".join(f"{k}={v}" for k, v in args.items())
|
args_str = ", ".join(f"{k}={v}" for k, v in args.items())
|
||||||
with open(log_file, "a", encoding="utf-8") as f:
|
with open(log_file, "a", encoding="utf-8") as f:
|
||||||
f.write(f"{timestamp} [Tool Call] {tool_name}({args_str})\n")
|
f.write(f"{timestamp} [Tool Call] {tool_name}({args_str})\n")
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
def save_report_section_decorator(obj, func_name):
|
def save_report_section_decorator(obj, func_name):
|
||||||
func = getattr(obj, func_name)
|
func = getattr(obj, func_name)
|
||||||
|
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
def wrapper(section_name, content):
|
def wrapper(section_name, content):
|
||||||
func(section_name, content)
|
func(section_name, content)
|
||||||
if section_name in obj.report_sections and obj.report_sections[section_name] is not None:
|
if (
|
||||||
|
section_name in obj.report_sections
|
||||||
|
and obj.report_sections[section_name] is not None
|
||||||
|
):
|
||||||
content = obj.report_sections[section_name]
|
content = obj.report_sections[section_name]
|
||||||
if content:
|
if content:
|
||||||
file_name = f"{section_name}.md"
|
file_name = f"{section_name}.md"
|
||||||
with open(report_dir / file_name, "w", encoding="utf-8") as f:
|
with open(report_dir / file_name, "w", encoding="utf-8") as f:
|
||||||
f.write(content)
|
f.write(content)
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
message_buffer.add_message = save_message_decorator(message_buffer, "add_message")
|
message_buffer.add_message = save_message_decorator(message_buffer, "add_message")
|
||||||
message_buffer.add_tool_call = save_tool_call_decorator(message_buffer, "add_tool_call")
|
message_buffer.add_tool_call = save_tool_call_decorator(
|
||||||
message_buffer.update_report_section = save_report_section_decorator(message_buffer, "update_report_section")
|
message_buffer, "add_tool_call"
|
||||||
|
)
|
||||||
|
message_buffer.update_report_section = save_report_section_decorator(
|
||||||
|
message_buffer, "update_report_section"
|
||||||
|
)
|
||||||
|
|
||||||
# Now start the display layout
|
# Now start the display layout
|
||||||
layout = create_layout()
|
layout = create_layout()
|
||||||
|
|
@ -1052,7 +1140,9 @@ def run_analysis():
|
||||||
spinner_text = (
|
spinner_text = (
|
||||||
f"Analyzing {selections['ticker']} on {selections['analysis_date']}..."
|
f"Analyzing {selections['ticker']} on {selections['analysis_date']}..."
|
||||||
)
|
)
|
||||||
update_display(layout, spinner_text, stats_handler=stats_handler, start_time=start_time)
|
update_display(
|
||||||
|
layout, spinner_text, stats_handler=stats_handler, start_time=start_time
|
||||||
|
)
|
||||||
|
|
||||||
# Initialize state and get graph args with callbacks
|
# Initialize state and get graph args with callbacks
|
||||||
init_agent_state = graph.propagator.create_initial_state(
|
init_agent_state = graph.propagator.create_initial_state(
|
||||||
|
|
@ -1119,7 +1209,9 @@ def run_analysis():
|
||||||
)
|
)
|
||||||
if message_buffer.agent_status.get("Trader") != "completed":
|
if message_buffer.agent_status.get("Trader") != "completed":
|
||||||
message_buffer.update_agent_status("Trader", "completed")
|
message_buffer.update_agent_status("Trader", "completed")
|
||||||
message_buffer.update_agent_status("Aggressive Analyst", "in_progress")
|
message_buffer.update_agent_status(
|
||||||
|
"Aggressive Analyst", "in_progress"
|
||||||
|
)
|
||||||
|
|
||||||
# Risk Management Team - Handle Risk Debate State
|
# Risk Management Team - Handle Risk Debate State
|
||||||
if chunk.get("risk_debate_state"):
|
if chunk.get("risk_debate_state"):
|
||||||
|
|
@ -1130,33 +1222,65 @@ def run_analysis():
|
||||||
judge = risk_state.get("judge_decision", "").strip()
|
judge = risk_state.get("judge_decision", "").strip()
|
||||||
|
|
||||||
if agg_hist:
|
if agg_hist:
|
||||||
if message_buffer.agent_status.get("Aggressive Analyst") != "completed":
|
if (
|
||||||
message_buffer.update_agent_status("Aggressive Analyst", "in_progress")
|
message_buffer.agent_status.get("Aggressive Analyst")
|
||||||
|
!= "completed"
|
||||||
|
):
|
||||||
|
message_buffer.update_agent_status(
|
||||||
|
"Aggressive Analyst", "in_progress"
|
||||||
|
)
|
||||||
message_buffer.update_report_section(
|
message_buffer.update_report_section(
|
||||||
"final_trade_decision", f"### Aggressive Analyst Analysis\n{agg_hist}"
|
"final_trade_decision",
|
||||||
|
f"### Aggressive Analyst Analysis\n{agg_hist}",
|
||||||
)
|
)
|
||||||
if con_hist:
|
if con_hist:
|
||||||
if message_buffer.agent_status.get("Conservative Analyst") != "completed":
|
if (
|
||||||
message_buffer.update_agent_status("Conservative Analyst", "in_progress")
|
message_buffer.agent_status.get("Conservative Analyst")
|
||||||
|
!= "completed"
|
||||||
|
):
|
||||||
|
message_buffer.update_agent_status(
|
||||||
|
"Conservative Analyst", "in_progress"
|
||||||
|
)
|
||||||
message_buffer.update_report_section(
|
message_buffer.update_report_section(
|
||||||
"final_trade_decision", f"### Conservative Analyst Analysis\n{con_hist}"
|
"final_trade_decision",
|
||||||
|
f"### Conservative Analyst Analysis\n{con_hist}",
|
||||||
)
|
)
|
||||||
if neu_hist:
|
if neu_hist:
|
||||||
if message_buffer.agent_status.get("Neutral Analyst") != "completed":
|
if (
|
||||||
message_buffer.update_agent_status("Neutral Analyst", "in_progress")
|
message_buffer.agent_status.get("Neutral Analyst")
|
||||||
|
!= "completed"
|
||||||
|
):
|
||||||
|
message_buffer.update_agent_status(
|
||||||
|
"Neutral Analyst", "in_progress"
|
||||||
|
)
|
||||||
message_buffer.update_report_section(
|
message_buffer.update_report_section(
|
||||||
"final_trade_decision", f"### Neutral Analyst Analysis\n{neu_hist}"
|
"final_trade_decision",
|
||||||
|
f"### Neutral Analyst Analysis\n{neu_hist}",
|
||||||
)
|
)
|
||||||
if judge:
|
if judge:
|
||||||
if message_buffer.agent_status.get("Portfolio Manager") != "completed":
|
if (
|
||||||
message_buffer.update_agent_status("Portfolio Manager", "in_progress")
|
message_buffer.agent_status.get("Portfolio Manager")
|
||||||
message_buffer.update_report_section(
|
!= "completed"
|
||||||
"final_trade_decision", f"### Portfolio Manager Decision\n{judge}"
|
):
|
||||||
|
message_buffer.update_agent_status(
|
||||||
|
"Portfolio Manager", "in_progress"
|
||||||
|
)
|
||||||
|
message_buffer.update_report_section(
|
||||||
|
"final_trade_decision",
|
||||||
|
f"### Portfolio Manager Decision\n{judge}",
|
||||||
|
)
|
||||||
|
message_buffer.update_agent_status(
|
||||||
|
"Aggressive Analyst", "completed"
|
||||||
|
)
|
||||||
|
message_buffer.update_agent_status(
|
||||||
|
"Conservative Analyst", "completed"
|
||||||
|
)
|
||||||
|
message_buffer.update_agent_status(
|
||||||
|
"Neutral Analyst", "completed"
|
||||||
|
)
|
||||||
|
message_buffer.update_agent_status(
|
||||||
|
"Portfolio Manager", "completed"
|
||||||
)
|
)
|
||||||
message_buffer.update_agent_status("Aggressive Analyst", "completed")
|
|
||||||
message_buffer.update_agent_status("Conservative Analyst", "completed")
|
|
||||||
message_buffer.update_agent_status("Neutral Analyst", "completed")
|
|
||||||
message_buffer.update_agent_status("Portfolio Manager", "completed")
|
|
||||||
|
|
||||||
# Update the display
|
# Update the display
|
||||||
update_display(layout, stats_handler=stats_handler, start_time=start_time)
|
update_display(layout, stats_handler=stats_handler, start_time=start_time)
|
||||||
|
|
@ -1190,12 +1314,13 @@ def run_analysis():
|
||||||
if save_choice in ("Y", "YES", ""):
|
if save_choice in ("Y", "YES", ""):
|
||||||
default_path = get_ticker_dir(selections["analysis_date"], selections["ticker"])
|
default_path = get_ticker_dir(selections["analysis_date"], selections["ticker"])
|
||||||
save_path_str = typer.prompt(
|
save_path_str = typer.prompt(
|
||||||
"Save path (press Enter for default)",
|
"Save path (press Enter for default)", default=str(default_path)
|
||||||
default=str(default_path)
|
|
||||||
).strip()
|
).strip()
|
||||||
save_path = Path(save_path_str)
|
save_path = Path(save_path_str)
|
||||||
try:
|
try:
|
||||||
report_file = save_report_to_disk(final_state, selections["ticker"], save_path)
|
report_file = save_report_to_disk(
|
||||||
|
final_state, selections["ticker"], save_path
|
||||||
|
)
|
||||||
console.print(f"\n[green]✓ Report saved to:[/green] {save_path.resolve()}")
|
console.print(f"\n[green]✓ Report saved to:[/green] {save_path.resolve()}")
|
||||||
console.print(f" [dim]Complete report:[/dim] {report_file.name}")
|
console.print(f" [dim]Complete report:[/dim] {report_file.name}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -1228,14 +1353,18 @@ def run_analysis():
|
||||||
set_run_logger(None)
|
set_run_logger(None)
|
||||||
|
|
||||||
# Prompt to display full report
|
# Prompt to display full report
|
||||||
display_choice = typer.prompt("\nDisplay full report on screen?", default="Y").strip().upper()
|
display_choice = (
|
||||||
|
typer.prompt("\nDisplay full report on screen?", default="Y").strip().upper()
|
||||||
|
)
|
||||||
if display_choice in ("Y", "YES", ""):
|
if display_choice in ("Y", "YES", ""):
|
||||||
display_complete_report(final_state)
|
display_complete_report(final_state)
|
||||||
|
|
||||||
|
|
||||||
def run_scan(date: Optional[str] = None):
|
def run_scan(date: Optional[str] = None):
|
||||||
"""Run the 3-phase LLM scanner pipeline via ScannerGraph."""
|
"""Run the 3-phase LLM scanner pipeline via ScannerGraph."""
|
||||||
console.print(Panel("[bold green]Global Macro Scanner[/bold green]", border_style="green"))
|
console.print(
|
||||||
|
Panel("[bold green]Global Macro Scanner[/bold green]", border_style="green")
|
||||||
|
)
|
||||||
if date:
|
if date:
|
||||||
scan_date = date
|
scan_date = date
|
||||||
else:
|
else:
|
||||||
|
|
@ -1247,7 +1376,9 @@ def run_scan(date: Optional[str] = None):
|
||||||
save_dir.mkdir(parents=True, exist_ok=True)
|
save_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
console.print(f"[cyan]Running 3-phase macro scanner for {scan_date}...[/cyan]")
|
console.print(f"[cyan]Running 3-phase macro scanner for {scan_date}...[/cyan]")
|
||||||
console.print("[dim]Phase 1: Geopolitical + Market Movers + Sector scans (parallel)[/dim]")
|
console.print(
|
||||||
|
"[dim]Phase 1: Geopolitical + Market Movers + Sector scans (parallel)[/dim]"
|
||||||
|
)
|
||||||
console.print("[dim]Phase 2: Industry Deep Dive[/dim]")
|
console.print("[dim]Phase 2: Industry Deep Dive[/dim]")
|
||||||
console.print("[dim]Phase 3: Macro Synthesis → stocks to investigate[/dim]\n")
|
console.print("[dim]Phase 3: Macro Synthesis → stocks to investigate[/dim]\n")
|
||||||
|
|
||||||
|
|
@ -1255,7 +1386,9 @@ def run_scan(date: Optional[str] = None):
|
||||||
set_run_logger(run_logger)
|
set_run_logger(run_logger)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
scanner = ScannerGraph(config=DEFAULT_CONFIG.copy(), callbacks=[run_logger.callback])
|
scanner = ScannerGraph(
|
||||||
|
config=DEFAULT_CONFIG.copy(), callbacks=[run_logger.callback]
|
||||||
|
)
|
||||||
with Live(Spinner("dots", text="Scanning..."), console=console, transient=True):
|
with Live(Spinner("dots", text="Scanning..."), console=console, transient=True):
|
||||||
result = scanner.scan(scan_date)
|
result = scanner.scan(scan_date)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -1263,8 +1396,13 @@ def run_scan(date: Optional[str] = None):
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
# Save reports
|
# Save reports
|
||||||
for key in ["geopolitical_report", "market_movers_report", "sector_performance_report",
|
for key in [
|
||||||
"industry_deep_dive_report", "macro_scan_summary"]:
|
"geopolitical_report",
|
||||||
|
"market_movers_report",
|
||||||
|
"sector_performance_report",
|
||||||
|
"industry_deep_dive_report",
|
||||||
|
"macro_scan_summary",
|
||||||
|
]:
|
||||||
content = result.get(key, "")
|
content = result.get(key, "")
|
||||||
if content:
|
if content:
|
||||||
(save_dir / f"{key}.md").write_text(content)
|
(save_dir / f"{key}.md").write_text(content)
|
||||||
|
|
@ -1298,7 +1436,6 @@ def run_scan(date: Optional[str] = None):
|
||||||
except (json.JSONDecodeError, KeyError, ValueError):
|
except (json.JSONDecodeError, KeyError, ValueError):
|
||||||
pass # Summary wasn't valid JSON — already printed as markdown
|
pass # Summary wasn't valid JSON — already printed as markdown
|
||||||
|
|
||||||
|
|
||||||
# Write observability log
|
# Write observability log
|
||||||
run_logger.write_log(save_dir / "run_log.jsonl")
|
run_logger.write_log(save_dir / "run_log.jsonl")
|
||||||
scan_summary = run_logger.summary()
|
scan_summary = run_logger.summary()
|
||||||
|
|
@ -1322,9 +1459,13 @@ def run_scan(date: Optional[str] = None):
|
||||||
if result.get("market_movers_report"):
|
if result.get("market_movers_report"):
|
||||||
scan_parts.append(f"### Market Movers\n{result['market_movers_report']}")
|
scan_parts.append(f"### Market Movers\n{result['market_movers_report']}")
|
||||||
if result.get("sector_performance_report"):
|
if result.get("sector_performance_report"):
|
||||||
scan_parts.append(f"### Sector Performance\n{result['sector_performance_report']}")
|
scan_parts.append(
|
||||||
|
f"### Sector Performance\n{result['sector_performance_report']}"
|
||||||
|
)
|
||||||
if result.get("industry_deep_dive_report"):
|
if result.get("industry_deep_dive_report"):
|
||||||
scan_parts.append(f"### Industry Deep Dive\n{result['industry_deep_dive_report']}")
|
scan_parts.append(
|
||||||
|
f"### Industry Deep Dive\n{result['industry_deep_dive_report']}"
|
||||||
|
)
|
||||||
if result.get("macro_scan_summary"):
|
if result.get("macro_scan_summary"):
|
||||||
scan_parts.append(f"### Macro Scan Summary\n{result['macro_scan_summary']}")
|
scan_parts.append(f"### Macro Scan Summary\n{result['macro_scan_summary']}")
|
||||||
|
|
||||||
|
|
@ -1353,7 +1494,12 @@ def run_pipeline(
|
||||||
save_results,
|
save_results,
|
||||||
)
|
)
|
||||||
|
|
||||||
console.print(Panel("[bold green]Macro → TradingAgents Pipeline[/bold green]", border_style="green"))
|
console.print(
|
||||||
|
Panel(
|
||||||
|
"[bold green]Macro → TradingAgents Pipeline[/bold green]",
|
||||||
|
border_style="green",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if macro_path_str is None:
|
if macro_path_str is None:
|
||||||
macro_output = typer.prompt("Path to macro scan JSON")
|
macro_output = typer.prompt("Path to macro scan JSON")
|
||||||
|
|
@ -1366,18 +1512,26 @@ def run_pipeline(
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
if min_conviction_opt is None:
|
if min_conviction_opt is None:
|
||||||
min_conviction = typer.prompt("Minimum conviction (high/medium/low)", default="medium")
|
min_conviction = typer.prompt(
|
||||||
|
"Minimum conviction (high/medium/low)", default="medium"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
min_conviction = min_conviction_opt
|
min_conviction = min_conviction_opt
|
||||||
|
|
||||||
if ticker_filter_list is None:
|
if ticker_filter_list is None:
|
||||||
tickers_input = typer.prompt("Specific tickers (comma-separated, or blank for all)", default="")
|
tickers_input = typer.prompt(
|
||||||
ticker_filter = [t.strip() for t in tickers_input.split(",") if t.strip()] or None
|
"Specific tickers (comma-separated, or blank for all)", default=""
|
||||||
|
)
|
||||||
|
ticker_filter = [
|
||||||
|
t.strip() for t in tickers_input.split(",") if t.strip()
|
||||||
|
] or None
|
||||||
else:
|
else:
|
||||||
ticker_filter = ticker_filter_list
|
ticker_filter = ticker_filter_list
|
||||||
|
|
||||||
if analysis_date_opt is None:
|
if analysis_date_opt is None:
|
||||||
analysis_date = typer.prompt("Analysis date", default=datetime.datetime.now().strftime("%Y-%m-%d"))
|
analysis_date = typer.prompt(
|
||||||
|
"Analysis date", default=datetime.datetime.now().strftime("%Y-%m-%d")
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
analysis_date = analysis_date_opt
|
analysis_date = analysis_date_opt
|
||||||
|
|
||||||
|
|
@ -1390,7 +1544,9 @@ def run_pipeline(
|
||||||
macro_context, all_candidates = parse_macro_output(macro_path)
|
macro_context, all_candidates = parse_macro_output(macro_path)
|
||||||
candidates = filter_candidates(all_candidates, min_conviction, ticker_filter)
|
candidates = filter_candidates(all_candidates, min_conviction, ticker_filter)
|
||||||
|
|
||||||
console.print(f"\n[cyan]Candidates: {len(candidates)} of {len(all_candidates)} stocks passed filter[/cyan]")
|
console.print(
|
||||||
|
f"\n[cyan]Candidates: {len(candidates)} of {len(all_candidates)} stocks passed filter[/cyan]"
|
||||||
|
)
|
||||||
|
|
||||||
table = Table(title="Selected Stocks", box=box.ROUNDED)
|
table = Table(title="Selected Stocks", box=box.ROUNDED)
|
||||||
table.add_column("Ticker", style="cyan bold")
|
table.add_column("Ticker", style="cyan bold")
|
||||||
|
|
@ -1415,9 +1571,13 @@ def run_pipeline(
|
||||||
run_logger = RunLogger()
|
run_logger = RunLogger()
|
||||||
set_run_logger(run_logger)
|
set_run_logger(run_logger)
|
||||||
|
|
||||||
console.print(f"\n[cyan]Running TradingAgents for {len(candidates)} tickers...[/cyan]")
|
console.print(
|
||||||
|
f"\n[cyan]Running TradingAgents for {len(candidates)} tickers...[/cyan]"
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
with Live(Spinner("dots", text="Analyzing..."), console=console, transient=True):
|
with Live(
|
||||||
|
Spinner("dots", text="Analyzing..."), console=console, transient=True
|
||||||
|
):
|
||||||
results = asyncio.run(
|
results = asyncio.run(
|
||||||
run_all_tickers(candidates, macro_context, config, analysis_date)
|
run_all_tickers(candidates, macro_context, config, analysis_date)
|
||||||
)
|
)
|
||||||
|
|
@ -1446,13 +1606,18 @@ def run_pipeline(
|
||||||
|
|
||||||
# Append to daily digest and sync to NotebookLM
|
# Append to daily digest and sync to NotebookLM
|
||||||
from tradingagents.pipeline.macro_bridge import render_combined_summary
|
from tradingagents.pipeline.macro_bridge import render_combined_summary
|
||||||
|
|
||||||
pipeline_summary = render_combined_summary(results, macro_context)
|
pipeline_summary = render_combined_summary(results, macro_context)
|
||||||
digest_path = append_to_digest(analysis_date, "pipeline", "Pipeline Summary", pipeline_summary)
|
digest_path = append_to_digest(
|
||||||
|
analysis_date, "pipeline", "Pipeline Summary", pipeline_summary
|
||||||
|
)
|
||||||
sync_to_notebooklm(digest_path, analysis_date)
|
sync_to_notebooklm(digest_path, analysis_date)
|
||||||
|
|
||||||
successes = [r for r in results if not r.error]
|
successes = [r for r in results if not r.error]
|
||||||
failures = [r for r in results if r.error]
|
failures = [r for r in results if r.error]
|
||||||
console.print(f"\n[green]Done: {len(successes)} succeeded, {len(failures)} failed[/green]")
|
console.print(
|
||||||
|
f"\n[green]Done: {len(successes)} succeeded, {len(failures)} failed[/green]"
|
||||||
|
)
|
||||||
console.print(f"Reports saved to: {output_dir.resolve()}")
|
console.print(f"Reports saved to: {output_dir.resolve()}")
|
||||||
if failures:
|
if failures:
|
||||||
for r in failures:
|
for r in failures:
|
||||||
|
|
@ -1467,7 +1632,9 @@ def analyze():
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
def scan(
|
def scan(
|
||||||
date: Optional[str] = typer.Option(None, "--date", "-d", help="Scan date in YYYY-MM-DD format (default: today)"),
|
date: Optional[str] = typer.Option(
|
||||||
|
None, "--date", "-d", help="Scan date in YYYY-MM-DD format (default: today)"
|
||||||
|
),
|
||||||
):
|
):
|
||||||
"""Run 3-phase macro scanner (geopolitical → sector → synthesis)."""
|
"""Run 3-phase macro scanner (geopolitical → sector → synthesis)."""
|
||||||
run_scan(date=date)
|
run_scan(date=date)
|
||||||
|
|
@ -1486,7 +1653,11 @@ def run_portfolio(portfolio_id: str, date: str, macro_path: Path):
|
||||||
from tradingagents.graph.portfolio_graph import PortfolioGraph
|
from tradingagents.graph.portfolio_graph import PortfolioGraph
|
||||||
from tradingagents.portfolio.repository import PortfolioRepository
|
from tradingagents.portfolio.repository import PortfolioRepository
|
||||||
|
|
||||||
console.print(Panel("[bold green]Portfolio Manager Execution[/bold green]", border_style="green"))
|
console.print(
|
||||||
|
Panel(
|
||||||
|
"[bold green]Portfolio Manager Execution[/bold green]", border_style="green"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if not macro_path.exists():
|
if not macro_path.exists():
|
||||||
console.print(f"[red]Scan summary not found: {macro_path}[/red]")
|
console.print(f"[red]Scan summary not found: {macro_path}[/red]")
|
||||||
|
|
@ -1504,7 +1675,9 @@ def run_portfolio(portfolio_id: str, date: str, macro_path: Path):
|
||||||
# Check if portfolio exists
|
# Check if portfolio exists
|
||||||
portfolio = repo.get_portfolio(portfolio_id)
|
portfolio = repo.get_portfolio(portfolio_id)
|
||||||
if not portfolio:
|
if not portfolio:
|
||||||
console.print(f"[yellow]Portfolio '{portfolio_id}' not found. Please ensure it is created in the database.[/yellow]")
|
console.print(
|
||||||
|
f"[yellow]Portfolio '{portfolio_id}' not found. Please ensure it is created in the database.[/yellow]"
|
||||||
|
)
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
holdings = repo.get_holdings(portfolio_id)
|
holdings = repo.get_holdings(portfolio_id)
|
||||||
|
|
@ -1520,18 +1693,24 @@ def run_portfolio(portfolio_id: str, date: str, macro_path: Path):
|
||||||
try:
|
try:
|
||||||
prices[ticker] = float(yf.Ticker(ticker).fast_info["lastPrice"])
|
prices[ticker] = float(yf.Ticker(ticker).fast_info["lastPrice"])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
console.print(f"[yellow]Warning: Could not fetch price for {ticker}: {e}[/yellow]")
|
console.print(
|
||||||
|
f"[yellow]Warning: Could not fetch price for {ticker}: {e}[/yellow]"
|
||||||
|
)
|
||||||
prices[ticker] = 0.0
|
prices[ticker] = 0.0
|
||||||
|
|
||||||
console.print(f"[cyan]Running PortfolioGraph for '{portfolio_id}'...[/cyan]")
|
console.print(f"[cyan]Running PortfolioGraph for '{portfolio_id}'...[/cyan]")
|
||||||
try:
|
try:
|
||||||
with Live(Spinner("dots", text="Managing portfolio..."), console=console, transient=True):
|
with Live(
|
||||||
|
Spinner("dots", text="Managing portfolio..."),
|
||||||
|
console=console,
|
||||||
|
transient=True,
|
||||||
|
):
|
||||||
graph = PortfolioGraph(debug=False, repo=repo)
|
graph = PortfolioGraph(debug=False, repo=repo)
|
||||||
result = graph.run(
|
result = graph.run(
|
||||||
portfolio_id=portfolio_id,
|
portfolio_id=portfolio_id,
|
||||||
date=date,
|
date=date,
|
||||||
prices=prices,
|
prices=prices,
|
||||||
scan_summary=scan_summary
|
scan_summary=scan_summary,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
console.print(f"[red]Portfolio execution failed: {e}[/red]")
|
console.print(f"[red]Portfolio execution failed: {e}[/red]")
|
||||||
|
|
@ -1539,16 +1718,26 @@ def run_portfolio(portfolio_id: str, date: str, macro_path: Path):
|
||||||
|
|
||||||
console.print("[green]Portfolio execution completed successfully![/green]")
|
console.print("[green]Portfolio execution completed successfully![/green]")
|
||||||
if "pm_decision" in result:
|
if "pm_decision" in result:
|
||||||
console.print(Panel(Markdown(str(result["pm_decision"])), title="PM Decision", border_style="blue"))
|
console.print(
|
||||||
|
Panel(
|
||||||
|
Markdown(str(result["pm_decision"])),
|
||||||
|
title="PM Decision",
|
||||||
|
border_style="blue",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
def portfolio():
|
def portfolio():
|
||||||
"""Run the Portfolio Manager Phase 6 workflow."""
|
"""Run the Portfolio Manager Phase 6 workflow."""
|
||||||
console.print(Panel("[bold green]Portfolio Manager CLI[/bold green]", border_style="green"))
|
console.print(
|
||||||
|
Panel("[bold green]Portfolio Manager CLI[/bold green]", border_style="green")
|
||||||
|
)
|
||||||
|
|
||||||
portfolio_id = typer.prompt("Portfolio ID", default="main_portfolio")
|
portfolio_id = typer.prompt("Portfolio ID", default="main_portfolio")
|
||||||
date = typer.prompt("Analysis date", default=datetime.datetime.now().strftime("%Y-%m-%d"))
|
date = typer.prompt(
|
||||||
|
"Analysis date", default=datetime.datetime.now().strftime("%Y-%m-%d")
|
||||||
|
)
|
||||||
|
|
||||||
macro_output = typer.prompt("Path to macro scan JSON")
|
macro_output = typer.prompt("Path to macro scan JSON")
|
||||||
macro_path = Path(macro_output)
|
macro_path = Path(macro_output)
|
||||||
|
|
@ -1558,14 +1747,23 @@ def portfolio():
|
||||||
|
|
||||||
@app.command(name="check-portfolio")
|
@app.command(name="check-portfolio")
|
||||||
def check_portfolio(
|
def check_portfolio(
|
||||||
portfolio_id: str = typer.Option("main_portfolio", "--portfolio-id", "-p", help="Portfolio ID"),
|
portfolio_id: str = typer.Option(
|
||||||
date: Optional[str] = typer.Option(None, "--date", "-d", help="Analysis date in YYYY-MM-DD format (default: today)"),
|
"main_portfolio", "--portfolio-id", "-p", help="Portfolio ID"
|
||||||
|
),
|
||||||
|
date: Optional[str] = typer.Option(
|
||||||
|
None, "--date", "-d", help="Analysis date in YYYY-MM-DD format (default: today)"
|
||||||
|
),
|
||||||
):
|
):
|
||||||
"""Run Portfolio Manager to review current holdings only (no new candidates)."""
|
"""Run Portfolio Manager to review current holdings only (no new candidates)."""
|
||||||
import json
|
import json
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
console.print(Panel("[bold green]Portfolio Manager: Holdings Review[/bold green]", border_style="green"))
|
console.print(
|
||||||
|
Panel(
|
||||||
|
"[bold green]Portfolio Manager: Holdings Review[/bold green]",
|
||||||
|
border_style="green",
|
||||||
|
)
|
||||||
|
)
|
||||||
if date is None:
|
if date is None:
|
||||||
date = datetime.datetime.now().strftime("%Y-%m-%d")
|
date = datetime.datetime.now().strftime("%Y-%m-%d")
|
||||||
|
|
||||||
|
|
@ -1583,11 +1781,17 @@ def check_portfolio(
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
def auto(
|
def auto(
|
||||||
portfolio_id: str = typer.Option("main_portfolio", "--portfolio-id", "-p", help="Portfolio ID"),
|
portfolio_id: str = typer.Option(
|
||||||
date: Optional[str] = typer.Option(None, "--date", "-d", help="Analysis date in YYYY-MM-DD format (default: today)"),
|
"main_portfolio", "--portfolio-id", "-p", help="Portfolio ID"
|
||||||
|
),
|
||||||
|
date: Optional[str] = typer.Option(
|
||||||
|
None, "--date", "-d", help="Analysis date in YYYY-MM-DD format (default: today)"
|
||||||
|
),
|
||||||
):
|
):
|
||||||
"""Run end-to-end: scan -> pipeline -> portfolio manager."""
|
"""Run end-to-end: scan -> pipeline -> portfolio manager."""
|
||||||
console.print(Panel("[bold green]TradingAgents Auto Mode[/bold green]", border_style="green"))
|
console.print(
|
||||||
|
Panel("[bold green]TradingAgents Auto Mode[/bold green]", border_style="green")
|
||||||
|
)
|
||||||
if date is None:
|
if date is None:
|
||||||
date = datetime.datetime.now().strftime("%Y-%m-%d")
|
date = datetime.datetime.now().strftime("%Y-%m-%d")
|
||||||
|
|
||||||
|
|
@ -1601,7 +1805,7 @@ def auto(
|
||||||
min_conviction_opt="medium",
|
min_conviction_opt="medium",
|
||||||
ticker_filter_list=None,
|
ticker_filter_list=None,
|
||||||
analysis_date_opt=date,
|
analysis_date_opt=date,
|
||||||
dry_run_opt=False
|
dry_run_opt=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
console.print("\n[bold magenta]--- Step 3: Portfolio Manager ---[/bold magenta]")
|
console.print("\n[bold magenta]--- Step 3: Portfolio Manager ---[/bold magenta]")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue