summaryrefslogtreecommitdiff
path: root/services/strategy-engine/tests/test_stock_selector.py
blob: a2f5bca39b8ff165cb960c9a4f273564599e8739 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
"""Tests for stock selector engine."""

import pytest
from unittest.mock import AsyncMock, MagicMock, patch
from datetime import datetime, timezone
from decimal import Decimal

from shared.models import OrderSide
from shared.sentiment_models import SymbolScore, MarketSentiment, SelectedStock, Candidate

from strategy_engine.stock_selector import (
    SentimentCandidateSource,
    StockSelector,
    _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"


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 == []