215 lines
6.8 KiB
Python
215 lines
6.8 KiB
Python
"""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()
|