-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_signals.py
More file actions
103 lines (75 loc) · 3.09 KB
/
test_signals.py
File metadata and controls
103 lines (75 loc) · 3.09 KB
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
"""
Tests 1 & 2: generate_signals state machine correctness.
"""
import numpy as np
import pandas as pd
import pytest
from src.strategy.signals import generate_signals
def _series(values):
return pd.Series(values, dtype=float)
# ---------------------------------------------------------------------------
# Test 1: basic entry / exit via mean reversion
# ---------------------------------------------------------------------------
def test_basic_entry_exit():
"""
z = [0, 0, -2.5, -2.0, -1.0, 0.5, 0, 0, 2.5, 2.0, 1.0, -0.5, 0]
entry_z=2, exit_z=0, stop_z=4
Expected positions:
index 0-1: FLAT (z never crosses ±2)
index 2-4: LONG (entered at -2.5, z still < 0 so exit_z=0 not crossed)
index 5: FLAT (z=0.5 > -exit_z=0 → exit)
index 6-7: FLAT
index 8-10: SHORT (entered at 2.5, z still > 0)
index 11: FLAT (z=-0.5 < exit_z=0 → exit)
index 12: FLAT
"""
z = _series([0, 0, -2.5, -2.0, -1.0, 0.5, 0, 0, 2.5, 2.0, 1.0, -0.5, 0])
expected = [0, 0, +1, +1, +1, 0, 0, 0, -1, -1, -1, 0, 0]
pos = generate_signals(z, entry_z=2, exit_z=0, stop_z=4)
assert list(pos) == expected, f"Got {list(pos)}"
def test_output_index_preserved():
z = _series([0, -2.5, -1.0, 0.5])
pos = generate_signals(z, entry_z=2, exit_z=0, stop_z=4)
assert list(pos.index) == list(z.index)
def test_output_dtype_integer():
z = _series([0.0, -2.5, 0.5])
pos = generate_signals(z, entry_z=2, exit_z=0, stop_z=4)
assert pos.dtype in (int, np.int64, np.int32)
# ---------------------------------------------------------------------------
# Test 2: stop-loss fires before mean reversion
# ---------------------------------------------------------------------------
def test_stop_loss():
"""
z = [0, -2.5, -3.0, -4.5, -2.0, 0]
entry_z=2, exit_z=0, stop_z=4
index 0: FLAT
index 1: LONG (z=-2.5 < -2)
index 2: LONG (z=-3.0, stop is z < -4)
index 3: FLAT (z=-4.5 < -stop_z=-4 → stop loss)
index 4-5: FLAT (no re-entry: z=-2.0 is not < -2 strictly)
"""
z = _series([0, -2.5, -3.0, -4.5, -2.0, 0])
expected = [0, +1, +1, 0, 0, 0]
pos = generate_signals(z, entry_z=2, exit_z=0, stop_z=4)
assert list(pos) == expected, f"Got {list(pos)}"
def test_stop_loss_short_side():
"""
Symmetric: SHORT position stopped out when z blows through +stop_z.
"""
z = _series([0, 2.5, 3.0, 4.5, 2.0, 0])
expected = [0, -1, -1, 0, 0, 0]
pos = generate_signals(z, entry_z=2, exit_z=0, stop_z=4)
assert list(pos) == expected, f"Got {list(pos)}"
def test_flat_series_stays_flat():
z = _series([0.0] * 10)
pos = generate_signals(z, entry_z=2, exit_z=0, stop_z=4)
assert (pos == 0).all()
def test_no_reentry_same_bar_as_stop():
"""
After a stop the position must be FLAT on that bar, not immediately re-entered.
"""
z = _series([0, -2.5, -4.5, -2.5])
pos = generate_signals(z, entry_z=2, exit_z=0, stop_z=4)
# bar 2: stop fires → FLAT; bar 3: z=-2.5 < -2 → re-entry allowed
assert pos.iloc[2] == 0
assert pos.iloc[3] == +1