Skip to content

Commit 7ba0110

Browse files
Pigbibicodex
andcommitted
fix: require report-date price coverage
Co-Authored-By: Codex <noreply@openai.com>
1 parent 5cbb44e commit 7ba0110

2 files changed

Lines changed: 20 additions & 9 deletions

File tree

src/quant_advisor_research/recommendation_review.py

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,6 @@ def final_recommendations(report: dict[str, Any]) -> list[dict[str, Any]]:
4545
return [item for item in recommendations if isinstance(item, dict) and item.get("symbol")]
4646

4747

48-
def first_bar_on_or_after(bars: list[PriceBar], target: dt.date) -> PriceBar | None:
49-
for bar in sorted(bars, key=lambda item: item.date):
50-
if bar.date >= target:
51-
return bar
52-
return None
53-
54-
5548
def last_bar_on_or_before(bars: list[PriceBar], target: dt.date) -> PriceBar | None:
5649
candidates = [bar for bar in bars if bar.date <= target]
5750
return max(candidates, key=lambda item: item.date) if candidates else None
@@ -120,9 +113,9 @@ def build_review_item(
120113
data_source: str,
121114
) -> dict[str, Any]:
122115
symbol = str(pick.get("symbol", "")).upper()
123-
start_bar = first_bar_on_or_after(symbol_bars, report_as_of)
116+
start_bar = last_bar_on_or_before(symbol_bars, report_as_of)
124117
end_bar = last_bar_on_or_before(symbol_bars, review_as_of)
125-
benchmark_start = first_bar_on_or_after(benchmark_bars, report_as_of)
118+
benchmark_start = last_bar_on_or_before(benchmark_bars, report_as_of)
126119
benchmark_end = last_bar_on_or_before(benchmark_bars, review_as_of)
127120
has_price_data = bool(start_bar and end_bar and start_bar.date <= end_bar.date)
128121

tests/test_recommendation_review.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,24 @@ def test_long_horizon_cannot_be_labeled_lagging_after_a_few_weeks(tmp_path: Path
131131
assert item["outcome"] == "in_progress"
132132

133133

134+
def test_review_requires_price_coverage_on_or_before_report_date(tmp_path: Path) -> None:
135+
cache_dir = tmp_path / "market-cache"
136+
write_cached_bars("MU", make_bars(dt.date(2026, 2, 1), [100] * 300), cache_dir=cache_dir)
137+
write_cached_bars("SPY", make_bars(dt.date(2026, 2, 1), [100] * 300), cache_dir=cache_dir)
138+
report_path = tmp_path / "advisory_report_2026-01-05.json"
139+
write_report(report_path, horizon="long")
140+
141+
review = build_recommendation_review(
142+
report_paths=[report_path], as_of=dt.date(2026, 11, 1), benchmark="SPY",
143+
cache_dir=cache_dir, cache_max_age_days=400, use_network=False,
144+
)
145+
146+
item = review["review_items"][0]
147+
assert item["start_price_date"] == ""
148+
assert item["maturity_status"] == "insufficient_price_data"
149+
assert item["outcome"] == "insufficient_price_data"
150+
151+
134152
def test_recommendation_review_marks_same_day_report_as_pending(tmp_path: Path) -> None:
135153
report_path = tmp_path / "advisory_report_2026-01-05.json"
136154
write_report(report_path)

0 commit comments

Comments
 (0)