feat: add date filtering to PDF report compiler
Adds --date arg to filter reports by date and auto-generate filename with date and symbols
This commit is contained in:
parent
202901c6a4
commit
b950e3a018
|
|
@ -0,0 +1,501 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Compile all trading agent reports into a single consolidated PDF.
|
||||||
|
|
||||||
|
Creates a PDF with:
|
||||||
|
1. Summary table showing all symbols, their decisions, and analysis dates
|
||||||
|
2. Detailed reports for each symbol (in order specified by REPORT_ORDER)
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python cli/compile_reports.py # Compile all results into single PDF
|
||||||
|
python cli/compile_reports.py --output report.pdf # Custom output filename
|
||||||
|
python cli/compile_reports.py --date 2026-01-18 # Filter to specific date (auto-names output)
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import markdown2
|
||||||
|
from playwright.sync_api import sync_playwright
|
||||||
|
|
||||||
|
# Report order (top to bottom for each symbol's section)
|
||||||
|
REPORT_ORDER = [
|
||||||
|
("final_trade_decision.md", "Final Trade Decision"),
|
||||||
|
("trader_investment_plan.md", "Trader Investment Plan"),
|
||||||
|
("investment_plan.md", "Investment Plan"),
|
||||||
|
("fundamentals_report.md", "Fundamentals Analysis"),
|
||||||
|
("news_report.md", "News Analysis"),
|
||||||
|
("sentiment_report.md", "Sentiment Analysis"),
|
||||||
|
("market_report.md", "Market Analysis"),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Clean GitHub-style markdown CSS
|
||||||
|
CSS_STYLES = """
|
||||||
|
@page {
|
||||||
|
size: A4;
|
||||||
|
margin: 0.75in;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #24292f;
|
||||||
|
max-width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2em;
|
||||||
|
font-weight: 600;
|
||||||
|
border-bottom: 1px solid #d0d7de;
|
||||||
|
padding-bottom: 0.3em;
|
||||||
|
margin-top: 24px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 1.5em;
|
||||||
|
font-weight: 600;
|
||||||
|
border-bottom: 1px solid #d0d7de;
|
||||||
|
padding-bottom: 0.3em;
|
||||||
|
margin-top: 24px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 1.25em;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-top: 24px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-size: 1em;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-top: 24px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul, ol {
|
||||||
|
padding-left: 2em;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
li + li {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
th, td {
|
||||||
|
padding: 6px 13px;
|
||||||
|
border: 1px solid #d0d7de;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
background-color: #f6f8fa;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:nth-child(2n) {
|
||||||
|
background-color: #f6f8fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
border: 0;
|
||||||
|
border-top: 1px solid #d0d7de;
|
||||||
|
margin: 24px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
background-color: rgba(175, 184, 193, 0.2);
|
||||||
|
padding: 0.2em 0.4em;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
|
||||||
|
font-size: 85%;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
background-color: #f6f8fa;
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow-x: auto;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
font-size: 85%;
|
||||||
|
line-height: 1.45;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre code {
|
||||||
|
padding: 0;
|
||||||
|
background: none;
|
||||||
|
font-size: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
border-left: 0.25em solid #d0d7de;
|
||||||
|
padding: 0 1em;
|
||||||
|
margin: 0 0 16px 0;
|
||||||
|
color: #57606a;
|
||||||
|
}
|
||||||
|
|
||||||
|
strong {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Decision color styling */
|
||||||
|
.decision-buy { color: #1a7f37; font-weight: 700; }
|
||||||
|
.decision-sell { color: #cf222e; font-weight: 700; }
|
||||||
|
.decision-hold { color: #9a6700; font-weight: 700; }
|
||||||
|
|
||||||
|
/* Symbol section - page break before each new symbol */
|
||||||
|
.symbol-section {
|
||||||
|
page-break-before: always;
|
||||||
|
}
|
||||||
|
|
||||||
|
.symbol-section:first-of-type {
|
||||||
|
page-break-before: avoid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Report title styling */
|
||||||
|
.report-title {
|
||||||
|
color: #0969da;
|
||||||
|
font-size: 1.3em;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-top: 32px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 2px solid #0969da;
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-title:first-of-type {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def extract_decision(content: str) -> str:
|
||||||
|
"""Extract BUY/SELL/HOLD decision from final trade decision content."""
|
||||||
|
content_lower = content.lower()
|
||||||
|
|
||||||
|
patterns = [
|
||||||
|
r"recommendation[:\s]*\*{0,2}(buy|sell|hold)\*{0,2}",
|
||||||
|
r"\*{0,2}(buy|sell|hold)\*{0,2}[:\s]*recommendation",
|
||||||
|
r"final.*?decision[:\s]*\*{0,2}(buy|sell|hold)\*{0,2}",
|
||||||
|
r"recommend.*?(buy|sell|hold)",
|
||||||
|
r"action[:\s]*\*{0,2}(buy|sell|hold)\*{0,2}",
|
||||||
|
]
|
||||||
|
|
||||||
|
for pattern in patterns:
|
||||||
|
match = re.search(pattern, content_lower)
|
||||||
|
if match:
|
||||||
|
return match.group(1).upper()
|
||||||
|
|
||||||
|
buy_count = len(re.findall(r"\bbuy\b", content_lower))
|
||||||
|
sell_count = len(re.findall(r"\bsell\b", content_lower))
|
||||||
|
hold_count = len(re.findall(r"\bhold\b", content_lower))
|
||||||
|
|
||||||
|
max_count = max(buy_count, sell_count, hold_count)
|
||||||
|
if max_count > 0:
|
||||||
|
if sell_count == max_count:
|
||||||
|
return "SELL"
|
||||||
|
if buy_count == max_count:
|
||||||
|
return "BUY"
|
||||||
|
return "HOLD"
|
||||||
|
|
||||||
|
return "N/A"
|
||||||
|
|
||||||
|
|
||||||
|
def markdown_to_html(md_content: str) -> str:
|
||||||
|
"""Convert markdown to HTML with extras."""
|
||||||
|
return markdown2.markdown(
|
||||||
|
md_content,
|
||||||
|
extras=[
|
||||||
|
"tables",
|
||||||
|
"fenced-code-blocks",
|
||||||
|
"strike",
|
||||||
|
"task_list",
|
||||||
|
"cuddled-lists",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def find_all_reports(results_dir: Path, date_filter: str | None = None) -> list[dict]:
|
||||||
|
"""Find all report directories and extract their data.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
results_dir: Path to the results directory
|
||||||
|
date_filter: Optional date string (YYYY-MM-DD) to filter reports
|
||||||
|
"""
|
||||||
|
all_reports = []
|
||||||
|
|
||||||
|
if not results_dir.exists():
|
||||||
|
return all_reports
|
||||||
|
|
||||||
|
for symbol_dir in sorted(results_dir.iterdir()):
|
||||||
|
if not symbol_dir.is_dir():
|
||||||
|
continue
|
||||||
|
|
||||||
|
symbol = symbol_dir.name
|
||||||
|
if symbol.startswith(".") or " " in symbol:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for date_dir in sorted(symbol_dir.iterdir(), reverse=True):
|
||||||
|
if not date_dir.is_dir():
|
||||||
|
continue
|
||||||
|
|
||||||
|
date = date_dir.name
|
||||||
|
|
||||||
|
# Skip if date doesn't match filter
|
||||||
|
if date_filter and date != date_filter:
|
||||||
|
continue
|
||||||
|
|
||||||
|
reports_dir = date_dir / "reports"
|
||||||
|
|
||||||
|
if not reports_dir.exists():
|
||||||
|
continue
|
||||||
|
|
||||||
|
report_files = []
|
||||||
|
decision = "N/A"
|
||||||
|
|
||||||
|
for filename, title in REPORT_ORDER:
|
||||||
|
file_path = reports_dir / filename
|
||||||
|
if file_path.exists():
|
||||||
|
content = file_path.read_text(encoding="utf-8")
|
||||||
|
html_content = markdown_to_html(content)
|
||||||
|
report_files.append((filename, title, html_content))
|
||||||
|
|
||||||
|
if filename == "final_trade_decision.md":
|
||||||
|
decision = extract_decision(content)
|
||||||
|
|
||||||
|
if report_files:
|
||||||
|
all_reports.append({
|
||||||
|
"symbol": symbol,
|
||||||
|
"date": date,
|
||||||
|
"decision": decision,
|
||||||
|
"reports_dir": reports_dir,
|
||||||
|
"reports": report_files,
|
||||||
|
})
|
||||||
|
|
||||||
|
return all_reports
|
||||||
|
|
||||||
|
|
||||||
|
def build_html_document(all_reports: list[dict]) -> str:
|
||||||
|
"""Build complete HTML document with summary table and all reports."""
|
||||||
|
|
||||||
|
# Build summary table rows
|
||||||
|
summary_rows = []
|
||||||
|
for report_data in all_reports:
|
||||||
|
decision = report_data["decision"]
|
||||||
|
decision_class = f"decision-{decision.lower()}" if decision in ["BUY", "SELL", "HOLD"] else ""
|
||||||
|
summary_rows.append(f'''<tr>
|
||||||
|
<td><strong>{report_data["symbol"]}</strong></td>
|
||||||
|
<td>{report_data["date"]}</td>
|
||||||
|
<td class="{decision_class}">{decision}</td>
|
||||||
|
<td>{len(report_data["reports"])} reports</td>
|
||||||
|
</tr>''')
|
||||||
|
|
||||||
|
summary_table = "\n".join(summary_rows)
|
||||||
|
|
||||||
|
# Build symbol sections
|
||||||
|
symbol_sections = []
|
||||||
|
for report_data in all_reports:
|
||||||
|
symbol = report_data["symbol"]
|
||||||
|
date = report_data["date"]
|
||||||
|
decision = report_data["decision"]
|
||||||
|
decision_class = f"decision-{decision.lower()}" if decision in ["BUY", "SELL", "HOLD"] else ""
|
||||||
|
|
||||||
|
# Build report content - simple flowing structure
|
||||||
|
reports_html_parts = []
|
||||||
|
for _, title, html_content in report_data["reports"]:
|
||||||
|
reports_html_parts.append(f'''<div class="report-title">{title}</div>
|
||||||
|
{html_content}''')
|
||||||
|
|
||||||
|
reports_html = "\n".join(reports_html_parts)
|
||||||
|
|
||||||
|
symbol_sections.append(f'''<div class="symbol-section">
|
||||||
|
<h1>{symbol} Trading Analysis Report</h1>
|
||||||
|
<p><strong>Date:</strong> {date} | <strong>Recommendation:</strong> <span class="{decision_class}">{decision}</span></p>
|
||||||
|
<hr>
|
||||||
|
{reports_html}
|
||||||
|
</div>''')
|
||||||
|
|
||||||
|
all_symbols_html = "\n".join(symbol_sections)
|
||||||
|
|
||||||
|
generated_date = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
|
||||||
|
html = f'''<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Trading Analysis Report</title>
|
||||||
|
<style>
|
||||||
|
{CSS_STYLES}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Trading Analysis Report</h1>
|
||||||
|
<p><em>Generated: {generated_date}</em></p>
|
||||||
|
|
||||||
|
<h2>Summary of Recommendations</h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Symbol</th>
|
||||||
|
<th>Analysis Date</th>
|
||||||
|
<th>Decision</th>
|
||||||
|
<th>Reports</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{summary_table}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
{all_symbols_html}
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<p><em>Report generated by TradingAgents</em></p>
|
||||||
|
</body>
|
||||||
|
</html>'''
|
||||||
|
return html
|
||||||
|
|
||||||
|
|
||||||
|
def compile_to_pdf(html_content: str, output_path: Path) -> bool:
|
||||||
|
"""Generate PDF from HTML using Playwright."""
|
||||||
|
try:
|
||||||
|
with sync_playwright() as p:
|
||||||
|
browser = p.chromium.launch()
|
||||||
|
page = browser.new_page()
|
||||||
|
page.set_content(html_content, wait_until="networkidle")
|
||||||
|
|
||||||
|
page.pdf(
|
||||||
|
path=str(output_path),
|
||||||
|
format="A4",
|
||||||
|
margin={
|
||||||
|
"top": "0.5in",
|
||||||
|
"bottom": "0.5in",
|
||||||
|
"left": "0.5in",
|
||||||
|
"right": "0.5in",
|
||||||
|
},
|
||||||
|
print_background=True,
|
||||||
|
)
|
||||||
|
browser.close()
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error generating PDF: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Compile all trading agent reports into a single consolidated PDF",
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
|
epilog="""
|
||||||
|
Examples:
|
||||||
|
python cli/compile_reports.py
|
||||||
|
python cli/compile_reports.py --output my_report.pdf
|
||||||
|
python cli/compile_reports.py --date 2026-01-18
|
||||||
|
python cli/compile_reports.py --date 2026-01-18 --output custom.pdf
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--output", "-o",
|
||||||
|
default="./results/trading_analysis_report.pdf",
|
||||||
|
help="Output PDF filename (default: ./results/trading_analysis_report.pdf)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--results-dir", "-r",
|
||||||
|
default="./results",
|
||||||
|
help="Results directory (default: ./results)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--date", "-d",
|
||||||
|
help="Filter reports to a specific date (format: YYYY-MM-DD)",
|
||||||
|
)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Validate date format if provided
|
||||||
|
if args.date:
|
||||||
|
import re as re_module
|
||||||
|
if not re_module.match(r"^\d{4}-\d{2}-\d{2}$", args.date):
|
||||||
|
print(f"Error: Invalid date format '{args.date}'. Expected YYYY-MM-DD")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
results_dir = Path(args.results_dir)
|
||||||
|
default_output = "./results/trading_analysis_report.pdf"
|
||||||
|
|
||||||
|
if not results_dir.exists():
|
||||||
|
print(f"Error: Results directory not found: {results_dir}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if args.date:
|
||||||
|
print(f"Scanning {results_dir} for reports on {args.date}...")
|
||||||
|
else:
|
||||||
|
print(f"Scanning {results_dir} for reports...")
|
||||||
|
|
||||||
|
all_reports = find_all_reports(results_dir, date_filter=args.date)
|
||||||
|
|
||||||
|
if not all_reports:
|
||||||
|
if args.date:
|
||||||
|
print(f"No reports found for date {args.date}")
|
||||||
|
else:
|
||||||
|
print("No reports found")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
print(f"Found {len(all_reports)} symbol analysis report(s):\n")
|
||||||
|
|
||||||
|
for report_data in all_reports:
|
||||||
|
decision_indicator = {
|
||||||
|
"BUY": "[BUY]",
|
||||||
|
"SELL": "[SELL]",
|
||||||
|
"HOLD": "[HOLD]",
|
||||||
|
}.get(report_data["decision"], "[N/A]")
|
||||||
|
|
||||||
|
print(f" {report_data['symbol']:6} | {report_data['date']} | {decision_indicator:6} | {len(report_data['reports'])} reports")
|
||||||
|
|
||||||
|
# Determine output path
|
||||||
|
if args.date and args.output == default_output:
|
||||||
|
# Generate dynamic filename from date + symbols (up to 5)
|
||||||
|
symbols = [r["symbol"] for r in all_reports[:5]]
|
||||||
|
symbols_str = "_".join(symbols)
|
||||||
|
output_path = Path(f"./results/trading_report_{args.date}_{symbols_str}.pdf")
|
||||||
|
else:
|
||||||
|
output_path = Path(args.output)
|
||||||
|
|
||||||
|
print("\nGenerating PDF...")
|
||||||
|
|
||||||
|
html_document = build_html_document(all_reports)
|
||||||
|
|
||||||
|
if compile_to_pdf(html_document, output_path):
|
||||||
|
print(f"\n+ PDF created: {output_path}")
|
||||||
|
else:
|
||||||
|
print("\n- Failed to create PDF")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
44
cli/main.py
44
cli/main.py
|
|
@ -429,10 +429,12 @@ def get_user_selections():
|
||||||
box_content += f"\n[dim]Default: {default}[/dim]"
|
box_content += f"\n[dim]Default: {default}[/dim]"
|
||||||
return Panel(box_content, border_style="blue", padding=(1, 2))
|
return Panel(box_content, border_style="blue", padding=(1, 2))
|
||||||
|
|
||||||
# Step 1: Ticker symbol
|
# Step 1: Ticker symbol(s)
|
||||||
console.print(
|
console.print(
|
||||||
create_question_box(
|
create_question_box(
|
||||||
"Step 1: Ticker Symbol", "Enter the ticker symbol to analyze", "SPY"
|
"Step 1: Ticker Symbol(s)",
|
||||||
|
"Enter ticker symbol(s) to analyze (comma-separated for multiple)",
|
||||||
|
"SPY",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
selected_ticker = get_ticker()
|
selected_ticker = get_ticker()
|
||||||
|
|
@ -504,8 +506,11 @@ def get_user_selections():
|
||||||
|
|
||||||
|
|
||||||
def get_ticker():
|
def get_ticker():
|
||||||
"""Get ticker symbol from user input."""
|
"""Get ticker symbol(s) from user input. Supports comma-separated symbols."""
|
||||||
return typer.prompt("", default="SPY")
|
raw_input = typer.prompt("", default="SPY")
|
||||||
|
# Split by comma, strip whitespace, convert to uppercase
|
||||||
|
symbols = [s.strip().upper() for s in raw_input.split(",") if s.strip()]
|
||||||
|
return symbols if len(symbols) > 1 else symbols[0]
|
||||||
|
|
||||||
|
|
||||||
def get_analysis_date():
|
def get_analysis_date():
|
||||||
|
|
@ -743,6 +748,7 @@ def extract_content_string(content):
|
||||||
return str(content)
|
return str(content)
|
||||||
|
|
||||||
def run_analysis():
|
def run_analysis():
|
||||||
|
"""Run analysis for one or more ticker symbols."""
|
||||||
# First get all user selections
|
# First get all user selections
|
||||||
selections = get_user_selections()
|
selections = get_user_selections()
|
||||||
|
|
||||||
|
|
@ -755,13 +761,33 @@ def run_analysis():
|
||||||
config["backend_url"] = selections["backend_url"]
|
config["backend_url"] = selections["backend_url"]
|
||||||
config["llm_provider"] = selections["llm_provider"].lower()
|
config["llm_provider"] = selections["llm_provider"].lower()
|
||||||
|
|
||||||
# Initialize the graph
|
# Normalize ticker(s) to list
|
||||||
|
tickers = selections["ticker"] if isinstance(selections["ticker"], list) else [selections["ticker"]]
|
||||||
|
|
||||||
|
# Initialize the graph once and reuse for all symbols
|
||||||
graph = TradingAgentsGraph(
|
graph = TradingAgentsGraph(
|
||||||
[analyst.value for analyst in selections["analysts"]], config=config, debug=True
|
[analyst.value for analyst in selections["analysts"]], config=config, debug=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
for i, ticker in enumerate(tickers, 1):
|
||||||
|
if len(tickers) > 1:
|
||||||
|
console.print(f"\n[bold cyan]{'═' * 50}[/bold cyan]")
|
||||||
|
console.print(f"[bold cyan] Analyzing {ticker} ({i}/{len(tickers)})[/bold cyan]")
|
||||||
|
console.print(f"[bold cyan]{'═' * 50}[/bold cyan]\n")
|
||||||
|
|
||||||
|
run_single_analysis(ticker, selections, config, graph)
|
||||||
|
|
||||||
|
if i < len(tickers):
|
||||||
|
console.print(f"\n[dim]Moving to next symbol...[/dim]\n")
|
||||||
|
|
||||||
|
if len(tickers) > 1:
|
||||||
|
console.print(f"\n[bold green]Completed analysis for all {len(tickers)} symbols: {', '.join(tickers)}[/bold green]")
|
||||||
|
|
||||||
|
|
||||||
|
def run_single_analysis(ticker: str, selections: dict, config: dict, graph: TradingAgentsGraph):
|
||||||
|
"""Run analysis for a single ticker symbol."""
|
||||||
# Create result directory
|
# Create result directory
|
||||||
results_dir = Path(config["results_dir"]) / selections["ticker"] / selections["analysis_date"]
|
results_dir = Path(config["results_dir"]) / ticker / selections["analysis_date"]
|
||||||
results_dir.mkdir(parents=True, exist_ok=True)
|
results_dir.mkdir(parents=True, exist_ok=True)
|
||||||
report_dir = results_dir / "reports"
|
report_dir = results_dir / "reports"
|
||||||
report_dir.mkdir(parents=True, exist_ok=True)
|
report_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
@ -815,7 +841,7 @@ def run_analysis():
|
||||||
update_display(layout)
|
update_display(layout)
|
||||||
|
|
||||||
# Add initial messages
|
# Add initial messages
|
||||||
message_buffer.add_message("System", f"Selected ticker: {selections['ticker']}")
|
message_buffer.add_message("System", f"Selected ticker: {ticker}")
|
||||||
message_buffer.add_message(
|
message_buffer.add_message(
|
||||||
"System", f"Analysis date: {selections['analysis_date']}"
|
"System", f"Analysis date: {selections['analysis_date']}"
|
||||||
)
|
)
|
||||||
|
|
@ -842,13 +868,13 @@ def run_analysis():
|
||||||
|
|
||||||
# Create spinner text
|
# Create spinner text
|
||||||
spinner_text = (
|
spinner_text = (
|
||||||
f"Analyzing {selections['ticker']} on {selections['analysis_date']}..."
|
f"Analyzing {ticker} on {selections['analysis_date']}..."
|
||||||
)
|
)
|
||||||
update_display(layout, spinner_text)
|
update_display(layout, spinner_text)
|
||||||
|
|
||||||
# Initialize state and get graph args
|
# Initialize state and get graph args
|
||||||
init_agent_state = graph.propagator.create_initial_state(
|
init_agent_state = graph.propagator.create_initial_state(
|
||||||
selections["ticker"], selections["analysis_date"]
|
ticker, selections["analysis_date"]
|
||||||
)
|
)
|
||||||
args = graph.propagator.get_graph_args()
|
args = graph.propagator.get_graph_args()
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue