"""Tests for the macro bridge module — JSON parsing, filtering, and report rendering.""" import json import tempfile from pathlib import Path import pytest EXAMPLE_MACRO_JSON = { "timeframe": "1 month", "region": "Global", "executive_summary": "Test summary", "macro_context": { "economic_cycle": "Late expansion", "central_bank_stance": "Fed on hold", "geopolitical_risks": ["US-China tensions"], "key_indicators": [ {"name": "10Y UST", "status": "4.45%", "signal": "neutral"} ], }, "key_themes": [ { "theme": "AI infrastructure", "description": "Hyperscaler capex elevated", "conviction": "high", "timeframe": "3-6 months", "supporting_factors": ["NVDA revenue"], } ], "sector_opportunities": [], "stocks_to_investigate": [ { "ticker": "NVDA", "name": "NVIDIA Corporation", "sector": "Technology — Semiconductors", "rationale": "AI accelerator dominance", "thesis_angle": "growth", "conviction": "high", "key_catalysts": ["Blackwell ramp"], "risks": ["export controls"], }, { "ticker": "LMT", "name": "Lockheed Martin", "sector": "Defense", "rationale": "F-35 backlog", "thesis_angle": "catalyst", "conviction": "medium", "key_catalysts": ["NATO orders"], "risks": ["budget risk"], }, { "ticker": "XYZ", "name": "Low Conv Corp", "sector": "Other", "rationale": "Speculative", "thesis_angle": "momentum", "conviction": "low", "key_catalysts": [], "risks": [], }, ], "risk_factors": ["Higher for longer"], } @pytest.fixture def macro_json_file(tmp_path): path = tmp_path / "macro_output.json" path.write_text(json.dumps(EXAMPLE_MACRO_JSON)) return path class TestParseMacroOutput: def test_parses_context_and_candidates(self, macro_json_file): from tradingagents.pipeline.macro_bridge import parse_macro_output ctx, candidates = parse_macro_output(macro_json_file) assert ctx.economic_cycle == "Late expansion" assert ctx.executive_summary == "Test summary" assert len(candidates) == 3 assert candidates[0].ticker == "NVDA" assert candidates[0].conviction == "high" def test_missing_fields_default_gracefully(self, tmp_path): from tradingagents.pipeline.macro_bridge import parse_macro_output minimal = {"stocks_to_investigate": [{"ticker": "TEST"}]} path = tmp_path / "minimal.json" path.write_text(json.dumps(minimal)) ctx, candidates = parse_macro_output(path) assert len(candidates) == 1 assert candidates[0].ticker == "TEST" assert candidates[0].conviction == "medium" # default class TestFilterCandidates: def test_filter_high_conviction(self, macro_json_file): from tradingagents.pipeline.macro_bridge import ( parse_macro_output, filter_candidates, ) _, candidates = parse_macro_output(macro_json_file) filtered = filter_candidates(candidates, "high", None) assert len(filtered) == 1 assert filtered[0].ticker == "NVDA" def test_filter_medium_conviction(self, macro_json_file): from tradingagents.pipeline.macro_bridge import ( parse_macro_output, filter_candidates, ) _, candidates = parse_macro_output(macro_json_file) filtered = filter_candidates(candidates, "medium", None) assert len(filtered) == 2 tickers = {c.ticker for c in filtered} assert tickers == {"NVDA", "LMT"} def test_filter_by_ticker(self, macro_json_file): from tradingagents.pipeline.macro_bridge import ( parse_macro_output, filter_candidates, ) _, candidates = parse_macro_output(macro_json_file) filtered = filter_candidates(candidates, "low", ["LMT"]) assert len(filtered) == 1 assert filtered[0].ticker == "LMT" def test_sorted_by_conviction_desc(self, macro_json_file): from tradingagents.pipeline.macro_bridge import ( parse_macro_output, filter_candidates, ) _, candidates = parse_macro_output(macro_json_file) filtered = filter_candidates(candidates, "low", None) assert filtered[0].conviction == "high" assert filtered[-1].conviction == "low" class TestReportRendering: def test_render_ticker_report(self, macro_json_file): from tradingagents.pipeline.macro_bridge import ( parse_macro_output, TickerResult, render_ticker_report, ) ctx, candidates = parse_macro_output(macro_json_file) result = TickerResult( ticker="NVDA", candidate=candidates[0], macro_context=ctx, analysis_date="2026-03-17", final_trade_decision="BUY", ) report = render_ticker_report(result) assert "NVDA" in report assert "NVIDIA" in report assert "BUY" in report assert "Macro" in report def test_render_combined_summary(self, macro_json_file): from tradingagents.pipeline.macro_bridge import ( parse_macro_output, TickerResult, render_combined_summary, ) ctx, candidates = parse_macro_output(macro_json_file) results = [ TickerResult( ticker=c.ticker, candidate=c, macro_context=ctx, analysis_date="2026-03-17", final_trade_decision="HOLD", ) for c in candidates[:2] ] summary = render_combined_summary(results, ctx) assert "NVDA" in summary assert "LMT" in summary assert "Summary" in summary def test_save_results(self, macro_json_file, tmp_path): from tradingagents.pipeline.macro_bridge import ( parse_macro_output, TickerResult, save_results, ) ctx, candidates = parse_macro_output(macro_json_file) results = [ TickerResult( ticker="NVDA", candidate=candidates[0], macro_context=ctx, analysis_date="2026-03-17", final_trade_decision="BUY", ) ] output_dir = tmp_path / "output" save_results(results, ctx, output_dir) assert (output_dir / "summary.md").exists() assert (output_dir / "results.json").exists() assert (output_dir / "NVDA" / "2026-03-17_deep_dive.md").exists()