File size: 4,119 Bytes
f64b002
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
"""
Tests for sentiment aggregation logic.
"""

import pytest
import numpy as np
from datetime import datetime, timezone


def calculate_recency_weights(hours_list: list[float], tau: float = 12.0) -> list[float]:
    """
    Calculate recency weights for testing.
    Mirrors the logic in ai_engine.py
    """
    weights = [np.exp(h / tau) for h in hours_list]
    total = sum(weights)
    return [w / total for w in weights]


class TestRecencyWeighting:
    def test_later_gets_higher_weight(self):
        """Articles later in the day should get higher weight."""
        hours = [9.0, 12.0, 16.0]  # 9am, noon, 4pm
        weights = calculate_recency_weights(hours)
        
        # Later hour should have higher weight
        assert weights[2] > weights[1] > weights[0]
    
    def test_weights_sum_to_one(self):
        """Normalized weights should sum to 1."""
        hours = [9.0, 12.0, 15.0, 18.0]
        weights = calculate_recency_weights(hours)
        
        assert abs(sum(weights) - 1.0) < 0.0001
    
    def test_single_article_weight_one(self):
        """Single article should have weight of 1."""
        weights = calculate_recency_weights([12.0])
        assert weights[0] == 1.0
    
    def test_tau_affects_spread(self):
        """Lower tau should increase weight spread."""
        hours = [9.0, 16.0]
        
        weights_high_tau = calculate_recency_weights(hours, tau=24.0)
        weights_low_tau = calculate_recency_weights(hours, tau=6.0)
        
        # With lower tau, the difference should be larger
        spread_high = weights_high_tau[1] - weights_high_tau[0]
        spread_low = weights_low_tau[1] - weights_low_tau[0]
        
        assert spread_low > spread_high


class TestSentimentIndex:
    def test_weighted_average(self):
        """Test weighted average calculation."""
        scores = [0.5, -0.2, 0.3]
        hours = [9.0, 14.0, 16.0]
        weights = calculate_recency_weights(hours)
        
        weighted_avg = sum(s * w for s, w in zip(scores, weights))
        
        # Should be between min and max scores
        assert min(scores) <= weighted_avg <= max(scores)
    
    def test_all_positive_yields_positive(self):
        """All positive scores should yield positive index."""
        scores = [0.3, 0.5, 0.4]
        hours = [9.0, 12.0, 15.0]
        weights = calculate_recency_weights(hours)
        
        weighted_avg = sum(s * w for s, w in zip(scores, weights))
        assert weighted_avg > 0
    
    def test_all_negative_yields_negative(self):
        """All negative scores should yield negative index."""
        scores = [-0.3, -0.5, -0.4]
        hours = [9.0, 12.0, 15.0]
        weights = calculate_recency_weights(hours)
        
        weighted_avg = sum(s * w for s, w in zip(scores, weights))
        assert weighted_avg < 0
    
    def test_equal_positive_negative_near_zero(self):
        """Equal positive and negative with same timing should be near zero."""
        scores = [0.5, -0.5]
        hours = [12.0, 12.0]  # Same time
        weights = calculate_recency_weights(hours)
        
        weighted_avg = sum(s * w for s, w in zip(scores, weights))
        assert abs(weighted_avg) < 0.1


class TestSentimentLabel:
    def test_bullish_threshold(self):
        """Positive sentiment above 0.1 should be Bullish."""
        from app.inference import get_sentiment_label
        
        assert get_sentiment_label(0.15) == "Bullish"
        assert get_sentiment_label(0.5) == "Bullish"
    
    def test_bearish_threshold(self):
        """Negative sentiment below -0.1 should be Bearish."""
        from app.inference import get_sentiment_label
        
        assert get_sentiment_label(-0.15) == "Bearish"
        assert get_sentiment_label(-0.5) == "Bearish"
    
    def test_neutral_range(self):
        """Sentiment between -0.1 and 0.1 should be Neutral."""
        from app.inference import get_sentiment_label
        
        assert get_sentiment_label(0.0) == "Neutral"
        assert get_sentiment_label(0.05) == "Neutral"
        assert get_sentiment_label(-0.05) == "Neutral"