"""Tests for stock selector engine.""" from unittest.mock import AsyncMock, MagicMock from datetime import datetime, timezone from strategy_engine.stock_selector import ( SentimentCandidateSource, StockSelector, _extract_json_array, _parse_llm_selections, ) async def test_sentiment_candidate_source(): mock_db = MagicMock() mock_db.get_top_symbol_scores = AsyncMock( return_value=[ {"symbol": "AAPL", "composite": 0.8, "news_count": 5}, {"symbol": "NVDA", "composite": 0.6, "news_count": 3}, ] ) source = SentimentCandidateSource(mock_db) candidates = await source.get_candidates() assert len(candidates) == 2 assert candidates[0].symbol == "AAPL" assert candidates[0].source == "sentiment" def test_parse_llm_selections_valid(): llm_response = """ [ {"symbol": "NVDA", "side": "BUY", "conviction": 0.85, "reason": "AI demand", "key_news": ["NVDA beats earnings"]}, {"symbol": "XOM", "side": "BUY", "conviction": 0.72, "reason": "Oil surge", "key_news": ["Oil prices up"]} ] """ selections = _parse_llm_selections(llm_response) assert len(selections) == 2 assert selections[0].symbol == "NVDA" assert selections[0].conviction == 0.85 def test_parse_llm_selections_invalid(): selections = _parse_llm_selections("not json") assert selections == [] def test_parse_llm_selections_with_markdown(): llm_response = """ Here are my picks: ```json [ {"symbol": "TSLA", "side": "BUY", "conviction": 0.7, "reason": "Momentum", "key_news": ["Tesla rally"]} ] ``` """ selections = _parse_llm_selections(llm_response) assert len(selections) == 1 assert selections[0].symbol == "TSLA" def test_extract_json_array_from_markdown(): text = '```json\n[{"symbol": "AAPL", "score": 0.9}]\n```' result = _extract_json_array(text) assert result == [{"symbol": "AAPL", "score": 0.9}] def test_extract_json_array_bare(): text = '[{"symbol": "TSLA"}]' result = _extract_json_array(text) assert result == [{"symbol": "TSLA"}] def test_extract_json_array_invalid(): assert _extract_json_array("not json") is None def test_extract_json_array_filters_non_dicts(): text = '[{"symbol": "AAPL"}, "bad", 42]' result = _extract_json_array(text) assert result == [{"symbol": "AAPL"}] async def test_selector_close(): selector = StockSelector( db=MagicMock(), broker=MagicMock(), alpaca=MagicMock(), anthropic_api_key="test" ) # No session yet - close should be safe await selector.close() assert selector._http_session is None async def test_selector_blocks_on_risk_off(): mock_db = MagicMock() mock_db.get_latest_market_sentiment = AsyncMock( return_value={ "fear_greed": 15, "fear_greed_label": "Extreme Fear", "vix": 35.0, "fed_stance": "neutral", "market_regime": "risk_off", "updated_at": datetime.now(timezone.utc), } ) selector = StockSelector( db=mock_db, broker=MagicMock(), alpaca=MagicMock(), anthropic_api_key="test" ) result = await selector.select() assert result == []