sbasu2512 commited on
Commit
e2f072d
·
1 Parent(s): 9940813

add tests

Browse files
.gitignore CHANGED
@@ -29,7 +29,6 @@ Thumbs.db
29
  ./webscrapper.db-wal
30
  .env
31
  .env.example
32
- /tests
33
  ._requirements.txt
34
  ._dockerfile
35
  /logs
 
29
  ./webscrapper.db-wal
30
  .env
31
  .env.example
 
32
  ._requirements.txt
33
  ._dockerfile
34
  /logs
tests/manual_test.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ def run_manual_test(prediction_date):
2
+
3
+ # 1. Generate today's feature rows
4
+ features = build_live_features(
5
+ as_of_date=prediction_date
6
+ )
7
+
8
+ # 2. Validate against model metadata
9
+ validate_features(features)
10
+
11
+ # 3. Run XGBoost
12
+ predictions = predict(features)
13
+
14
+ # 4. Rank
15
+ predictions = rank_predictions(predictions)
16
+
17
+ # 5. Store
18
+ save_predictions(predictions)
19
+
20
+ # 6. Resolve using historical future data
21
+ resolve_predictions(
22
+ as_of_date=prediction_date
23
+ )
24
+
25
+ # 7. Calculate metrics
26
+ calculate_metrics()
27
+
28
+ # 8. Check retraining condition
29
+ check_retrain()
tests/test_metrics.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datetime import date
2
+ import pytest
3
+ from evaluation.metrics import (
4
+ precision_at_threshold, hit_rate, positive_rate,
5
+ average_return, binary_pr_auc, rank_deciles, calculate_metrics
6
+ )
7
+
8
+ def test_precision():
9
+ assert precision_at_threshold([.9,.8,.4,.2], [1,0,1,0], .5) == .5
10
+
11
+ def test_rates():
12
+ assert hit_rate([1,0,1,0,0]) == .4
13
+ assert positive_rate([1,0,1,0,0]) == .4
14
+
15
+ def test_average_return():
16
+ assert average_return([.03,.05,-.01]) == pytest.approx(.0233333333)
17
+
18
+ def test_pr_auc():
19
+ assert binary_pr_auc([.9,.8,.2,.1], [1,1,0,0]) == pytest.approx(1.0)
20
+
21
+ def test_deciles():
22
+ probs = list(reversed([i/100 for i in range(1,101)]))
23
+ deciles = rank_deciles(probs, [1]*100, [.03]*100)
24
+ assert len(deciles) == 10
25
+ assert all(d["count"] == 10 for d in deciles)
26
+
27
+ def test_complete_metrics():
28
+ rows = [
29
+ {"prediction_date":date(2026,7,1),"symbol":"AAA","predicted_probability":.9,"actual_label":1,"actual_return":.05},
30
+ {"prediction_date":date(2026,7,1),"symbol":"BBB","predicted_probability":.4,"actual_label":0,"actual_return":-.02},
31
+ {"prediction_date":date(2026,7,2),"symbol":"AAA","predicted_probability":.8,"actual_label":1,"actual_return":.04},
32
+ {"prediction_date":date(2026,7,2),"symbol":"BBB","predicted_probability":.3,"actual_label":0,"actual_return":-.01},
33
+ ]
34
+ result = calculate_metrics(rows, threshold=.5)
35
+ assert result.prediction_days == 2
36
+ assert result.resolved_predictions == 4
37
+ assert result.precision == 1.0
38
+ assert result.hit_rate == .5
39
+ assert result.average_return == pytest.approx(.015)
40
+ assert len(result.deciles) == 10
tests/test_resolver.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datetime import date
2
+
3
+ # from evaluation.resolver import resolve_prediction_rows
4
+
5
+ from jobs import resolver
6
+
7
+
8
+ def test_positive_prediction():
9
+ result = resolver.resolve_prediction_rows(
10
+ prediction_date=date(2026, 8, 3),
11
+ symbol="ABC",
12
+ future_rows=[
13
+ {"date": date(2026, 8, 4), "open": 100.0, "close": 101.0},
14
+ {"date": date(2026, 8, 5), "open": 101.0, "close": 102.0},
15
+ {"date": date(2026, 8, 6), "open": 102.0, "close": 103.0},
16
+ {"date": date(2026, 8, 7), "open": 103.0, "close": 104.0},
17
+ {"date": date(2026, 8, 10), "open": 104.0, "close": 104.0},
18
+ ],
19
+ )
20
+
21
+ assert result is not None
22
+ assert result.entry_date == date(2026, 8, 4)
23
+ assert result.entry_open == 100.0
24
+ assert result.max_close == 104.0
25
+ assert result.actual_return == 0.04
26
+ assert result.actual_label == 1
27
+
28
+
29
+ def test_exact_three_percent_is_positive():
30
+ result = resolver.resolve_prediction_rows(
31
+ prediction_date=date(2026, 8, 3),
32
+ symbol="ABC",
33
+ future_rows=[
34
+ {"date": date(2026, 8, 4), "open": 100.0, "close": 103.0},
35
+ {"date": date(2026, 8, 5), "open": 103.0, "close": 101.0},
36
+ {"date": date(2026, 8, 6), "open": 101.0, "close": 100.0},
37
+ {"date": date(2026, 8, 7), "open": 100.0, "close": 99.0},
38
+ {"date": date(2026, 8, 10), "open": 99.0, "close": 98.0},
39
+ ],
40
+ )
41
+
42
+ assert result is not None
43
+ assert result.actual_return == 0.03
44
+ assert result.actual_label == 1
45
+
46
+
47
+ def test_below_three_percent_is_negative():
48
+ result = resolver.resolve_prediction_rows(
49
+ prediction_date=date(2026, 8, 3),
50
+ symbol="ABC",
51
+ future_rows=[
52
+ {"date": date(2026, 8, 4), "open": 100.0, "close": 102.99},
53
+ {"date": date(2026, 8, 5), "open": 102.0, "close": 102.5},
54
+ {"date": date(2026, 8, 6), "open": 102.0, "close": 101.0},
55
+ {"date": date(2026, 8, 7), "open": 101.0, "close": 100.0},
56
+ {"date": date(2026, 8, 10), "open": 100.0, "close": 99.0},
57
+ ],
58
+ )
59
+
60
+ assert result is not None
61
+ assert result.actual_return < 0.03
62
+ assert result.actual_label == 0
63
+
64
+
65
+ def test_incomplete_window_stays_unresolved():
66
+ result = resolver.resolve_prediction_rows(
67
+ prediction_date=date(2026, 8, 3),
68
+ symbol="ABC",
69
+ future_rows=[
70
+ {"date": date(2026, 8, 4), "open": 100.0, "close": 105.0},
71
+ {"date": date(2026, 8, 5), "open": 105.0, "close": 105.0},
72
+ {"date": date(2026, 8, 6), "open": 105.0, "close": 105.0},
73
+ ],
74
+ )
75
+
76
+ assert result is None
tests/test_retrain_trigger.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pytest
2
+ from evaluation.retrain_trigger import (
3
+ calculate_deterioration, evaluate_retrain_trigger
4
+ )
5
+
6
+ def test_15_percent_deterioration_triggers():
7
+ result = evaluate_retrain_trigger(0.60, 0.51)
8
+ assert result.should_flag is True
9
+ assert result.deterioration == pytest.approx(0.15)
10
+
11
+ def test_less_than_15_percent_deterioration_does_not_trigger():
12
+ result = evaluate_retrain_trigger(0.60, 0.52)
13
+ assert result.should_flag is False
14
+ assert result.deterioration == pytest.approx(0.1333333333)
15
+
16
+ def test_improvement_does_not_trigger():
17
+ result = evaluate_retrain_trigger(0.60, 0.65)
18
+ assert result.should_flag is False
19
+ assert result.deterioration < 0
20
+
21
+ def test_no_metric_does_not_trigger():
22
+ result = evaluate_retrain_trigger(0.60, None)
23
+ assert result.should_flag is False
24
+ assert result.deterioration is None
25
+
26
+ def test_deterioration_formula():
27
+ assert calculate_deterioration(0.50, 0.425) == pytest.approx(0.15)