TradingAgents/tradingagents/reporting.py

184 lines
7.1 KiB
Python

from __future__ import annotations
import datetime as dt
from pathlib import Path
from typing import Any, Mapping
def save_report_bundle(
final_state: Mapping[str, Any],
ticker: str,
save_path: Path,
*,
generated_at: dt.datetime | None = None,
language: str = "English",
) -> Path:
"""Persist a complete TradingAgents report bundle to disk."""
generated_at = generated_at or dt.datetime.now()
save_path = Path(save_path)
save_path.mkdir(parents=True, exist_ok=True)
labels = _labels_for(language)
analysis_date = _coerce_text(final_state.get("analysis_date"))
trade_date = _coerce_text(final_state.get("trade_date"))
sections: list[str] = []
analysts_dir = save_path / "1_analysts"
analyst_parts: list[tuple[str, str]] = []
for file_name, title, key in (
("market.md", labels["market_analyst"], "market_report"),
("sentiment.md", labels["social_analyst"], "sentiment_report"),
("news.md", labels["news_analyst"], "news_report"),
("fundamentals.md", labels["fundamentals_analyst"], "fundamentals_report"),
):
content = _coerce_text(final_state.get(key))
if not content:
continue
analysts_dir.mkdir(exist_ok=True)
_write_text(analysts_dir / file_name, content)
analyst_parts.append((title, content))
if analyst_parts:
sections.append(
f"## {labels['section_analysts']}\n\n"
+ "\n\n".join(f"### {title}\n{content}" for title, content in analyst_parts)
)
debate = final_state.get("investment_debate_state") or {}
research_dir = save_path / "2_research"
research_parts: list[tuple[str, str]] = []
for file_name, title, key in (
("bull.md", labels["bull_researcher"], "bull_history"),
("bear.md", labels["bear_researcher"], "bear_history"),
("manager.md", labels["research_manager"], "judge_decision"),
):
content = _coerce_text(debate.get(key))
if not content:
continue
research_dir.mkdir(exist_ok=True)
_write_text(research_dir / file_name, content)
research_parts.append((title, content))
if research_parts:
sections.append(
f"## {labels['section_research']}\n\n"
+ "\n\n".join(f"### {title}\n{content}" for title, content in research_parts)
)
trader_plan = _coerce_text(final_state.get("trader_investment_plan"))
if trader_plan:
trading_dir = save_path / "3_trading"
trading_dir.mkdir(exist_ok=True)
_write_text(trading_dir / "trader.md", trader_plan)
sections.append(
f"## {labels['section_trading']}\n\n### {labels['trader']}\n{trader_plan}"
)
risk = final_state.get("risk_debate_state") or {}
risk_dir = save_path / "4_risk"
risk_parts: list[tuple[str, str]] = []
for file_name, title, key in (
("aggressive.md", labels["aggressive_analyst"], "aggressive_history"),
("conservative.md", labels["conservative_analyst"], "conservative_history"),
("neutral.md", labels["neutral_analyst"], "neutral_history"),
):
content = _coerce_text(risk.get(key))
if not content:
continue
risk_dir.mkdir(exist_ok=True)
_write_text(risk_dir / file_name, content)
risk_parts.append((title, content))
if risk_parts:
sections.append(
f"## {labels['section_risk']}\n\n"
+ "\n\n".join(f"### {title}\n{content}" for title, content in risk_parts)
)
portfolio_decision = _coerce_text(risk.get("judge_decision"))
if portfolio_decision:
portfolio_dir = save_path / "5_portfolio"
portfolio_dir.mkdir(exist_ok=True)
_write_text(portfolio_dir / "decision.md", portfolio_decision)
sections.append(
f"## {labels['section_portfolio']}\n\n"
f"### {labels['portfolio_manager']}\n{portfolio_decision}"
)
metadata_lines = [f"{labels['generated_at']}: {generated_at.strftime('%Y-%m-%d %H:%M:%S')}"]
if analysis_date:
metadata_lines.append(f"{labels['analysis_date']}: {analysis_date}")
if trade_date:
metadata_lines.append(f"{labels['trade_date']}: {trade_date}")
header = f"# {labels['report_title']}: {ticker}\n\n" + "\n".join(metadata_lines) + "\n\n"
complete_report = save_path / "complete_report.md"
_write_text(complete_report, header + "\n\n".join(sections))
return complete_report
def _coerce_text(value: Any) -> str:
if value is None:
return ""
if isinstance(value, str):
return value
if isinstance(value, list):
return "\n".join(str(item) for item in value)
return str(value)
def _write_text(path: Path, content: str) -> None:
path.write_text(content, encoding="utf-8")
def _labels_for(language: str) -> dict[str, str]:
if str(language).strip().lower() == "korean":
return {
"report_title": "트레이딩 분석 리포트",
"generated_at": "생성 시각",
"analysis_date": "분석 기준일",
"trade_date": "시장 데이터 기준일",
"section_analysts": "I. 애널리스트 팀 리포트",
"section_research": "II. 리서치 팀 판단",
"section_trading": "III. 트레이딩 팀 계획",
"section_risk": "IV. 리스크 관리 팀 판단",
"section_portfolio": "V. 포트폴리오 매니저 최종 판단",
"market_analyst": "시장 애널리스트",
"social_analyst": "소셜 심리 애널리스트",
"news_analyst": "뉴스 애널리스트",
"fundamentals_analyst": "펀더멘털 애널리스트",
"bull_researcher": "강세 리서처",
"bear_researcher": "약세 리서처",
"research_manager": "리서치 매니저",
"trader": "트레이더",
"aggressive_analyst": "공격적 리스크 애널리스트",
"conservative_analyst": "보수적 리스크 애널리스트",
"neutral_analyst": "중립 리스크 애널리스트",
"portfolio_manager": "포트폴리오 매니저",
}
return {
"report_title": "Trading Analysis Report",
"generated_at": "Generated",
"analysis_date": "Analysis date",
"trade_date": "Market data date",
"section_analysts": "I. Analyst Team Reports",
"section_research": "II. Research Team Decision",
"section_trading": "III. Trading Team Plan",
"section_risk": "IV. Risk Management Team Decision",
"section_portfolio": "V. Portfolio Manager Decision",
"market_analyst": "Market Analyst",
"social_analyst": "Social Analyst",
"news_analyst": "News Analyst",
"fundamentals_analyst": "Fundamentals Analyst",
"bull_researcher": "Bull Researcher",
"bear_researcher": "Bear Researcher",
"research_manager": "Research Manager",
"trader": "Trader",
"aggressive_analyst": "Aggressive Analyst",
"conservative_analyst": "Conservative Analyst",
"neutral_analyst": "Neutral Analyst",
"portfolio_manager": "Portfolio Manager",
}