test(filter): add failing tests for OHLCV cache helper methods
This commit is contained in:
parent
c0b5353327
commit
79599a8ace
|
|
@ -0,0 +1,135 @@
|
|||
"""Tests for OHLCV-cache-backed filter enrichment."""
|
||||
import pandas as pd
|
||||
import pytest
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
|
||||
def _make_ohlcv(closes: list[float]) -> pd.DataFrame:
|
||||
"""Build a minimal OHLCV DataFrame from a list of closing prices."""
|
||||
dates = pd.date_range("2026-01-01", periods=len(closes), freq="B")
|
||||
return pd.DataFrame(
|
||||
{
|
||||
"Open": closes,
|
||||
"High": closes,
|
||||
"Low": closes,
|
||||
"Close": closes,
|
||||
"Volume": [1_000_000] * len(closes),
|
||||
},
|
||||
index=dates,
|
||||
)
|
||||
|
||||
|
||||
def _make_filter(config_overrides=None):
|
||||
"""Instantiate a CandidateFilter with minimal config."""
|
||||
from tradingagents.dataflows.discovery.filter import CandidateFilter
|
||||
|
||||
config = {
|
||||
"discovery": {
|
||||
"ohlcv_cache_dir": "data/ohlcv_cache",
|
||||
"filters": {
|
||||
"min_average_volume": 0,
|
||||
"volume_lookback_days": 10,
|
||||
"filter_same_day_movers": True,
|
||||
"intraday_movement_threshold": 10.0,
|
||||
"filter_recent_movers": True,
|
||||
"recent_movement_lookback_days": 7,
|
||||
"recent_movement_threshold": 10.0,
|
||||
"recent_mover_action": "filter",
|
||||
"volume_cache_key": "default",
|
||||
"min_market_cap": 0,
|
||||
"compression_atr_pct_max": 2.0,
|
||||
"compression_bb_width_max": 6.0,
|
||||
"compression_min_volume_ratio": 1.3,
|
||||
"filter_fundamental_risk": False,
|
||||
"min_z_score": None,
|
||||
"min_f_score": None,
|
||||
},
|
||||
"enrichment": {
|
||||
"batch_news_vendor": "google",
|
||||
"batch_news_batch_size": 150,
|
||||
"news_lookback_days": 0.5,
|
||||
"context_max_snippets": 2,
|
||||
"context_snippet_max_chars": 140,
|
||||
},
|
||||
"max_candidates_to_analyze": 200,
|
||||
"analyze_all_candidates": False,
|
||||
"final_recommendations": 15,
|
||||
"truncate_ranking_context": False,
|
||||
"max_news_chars": 500,
|
||||
"max_insider_chars": 300,
|
||||
"max_recommendations_chars": 300,
|
||||
"log_tool_calls": False,
|
||||
"log_tool_calls_console": False,
|
||||
"log_prompts_console": False,
|
||||
"tool_log_max_chars": 10_000,
|
||||
"tool_log_exclude": [],
|
||||
}
|
||||
}
|
||||
if config_overrides:
|
||||
config["discovery"]["filters"].update(config_overrides)
|
||||
|
||||
# Create a mock tool executor
|
||||
mock_tool_executor = MagicMock()
|
||||
|
||||
return CandidateFilter(config, mock_tool_executor)
|
||||
|
||||
|
||||
def test_current_price_comes_from_ohlcv_cache():
|
||||
"""current_price on the candidate should be the last close from the OHLCV cache."""
|
||||
closes = [100.0] * 210 + [123.45] # last close = 123.45
|
||||
ohlcv_data = {"AAPL": _make_ohlcv(closes)}
|
||||
|
||||
f = _make_filter()
|
||||
price = f._price_from_cache("AAPL", ohlcv_data)
|
||||
assert price == pytest.approx(123.45)
|
||||
|
||||
|
||||
def test_intraday_check_from_cache_not_moved():
|
||||
"""intraday check: <10% day-over-day change → already_moved=False."""
|
||||
closes = [100.0] * 210 + [105.0] # +5% last day — under threshold
|
||||
ohlcv_data = {"AAPL": _make_ohlcv(closes)}
|
||||
|
||||
f = _make_filter()
|
||||
result = f._intraday_from_cache("AAPL", ohlcv_data, threshold=10.0)
|
||||
assert result["already_moved"] is False
|
||||
assert result["intraday_change_pct"] == pytest.approx(5.0)
|
||||
|
||||
|
||||
def test_intraday_check_from_cache_moved():
|
||||
"""intraday check: >10% day-over-day change → already_moved=True."""
|
||||
closes = [100.0] * 210 + [115.0] # +15% last day — over threshold
|
||||
ohlcv_data = {"AAPL": _make_ohlcv(closes)}
|
||||
|
||||
f = _make_filter()
|
||||
result = f._intraday_from_cache("AAPL", ohlcv_data, threshold=10.0)
|
||||
assert result["already_moved"] is True
|
||||
assert result["intraday_change_pct"] == pytest.approx(15.0)
|
||||
|
||||
|
||||
def test_recent_move_check_from_cache_leading():
|
||||
"""recent-move check: <10% change over 7 days → status=leading."""
|
||||
closes = [100.0] * 205 + [103.0] * 7 # flat last 7 days
|
||||
ohlcv_data = {"AAPL": _make_ohlcv(closes)}
|
||||
|
||||
f = _make_filter()
|
||||
result = f._recent_move_from_cache("AAPL", ohlcv_data, lookback_days=7, threshold=10.0)
|
||||
assert result["status"] == "leading"
|
||||
assert abs(result["price_change_pct"]) < 10.0
|
||||
|
||||
|
||||
def test_recent_move_check_from_cache_lagging():
|
||||
"""recent-move check: >10% change over 7 days → status=lagging."""
|
||||
closes = [100.0] * 205 + [100.0] * 6 + [115.0] # +15% in last day within window
|
||||
ohlcv_data = {"AAPL": _make_ohlcv(closes)}
|
||||
|
||||
f = _make_filter()
|
||||
result = f._recent_move_from_cache("AAPL", ohlcv_data, lookback_days=7, threshold=10.0)
|
||||
assert result["status"] == "lagging"
|
||||
|
||||
|
||||
def test_cache_miss_returns_none():
|
||||
"""If ticker is not in ohlcv_data, helper returns None."""
|
||||
f = _make_filter()
|
||||
assert f._price_from_cache("MISSING", {}) is None
|
||||
assert f._intraday_from_cache("MISSING", {}, threshold=10.0) is None
|
||||
assert f._recent_move_from_cache("MISSING", {}, lookback_days=7, threshold=10.0) is None
|
||||
Loading…
Reference in New Issue