Skip to content

Commit 4ba3162

Browse files
committed
Add benchmarking for HT_TRENDLINE and MA functions, along with unit tests for TA utility functions and MA functionality
1 parent 2fd91dd commit 4ba3162

5 files changed

Lines changed: 220 additions & 2 deletions

File tree

benchmark/bench_ht_trendline.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from utils import bench
2+
import tqdm
3+
import numpy as np
4+
5+
import sys
6+
import os
7+
8+
sys.path.append(
9+
os.path.abspath(os.path.join(os.path.dirname(__file__), '../'))
10+
)
11+
12+
from tabox import HT_TRENDLINE as this_HT_TRENDLINE
13+
from talib import HT_TRENDLINE as that_HT_TRENDLINE
14+
15+
@bench
16+
def bench_this_ht_trendline():
17+
for i in range(100, 2000):
18+
close = np.random.random(i)
19+
this_ret = this_HT_TRENDLINE(close)
20+
21+
@bench
22+
def bench_that_ht_trendline():
23+
for i in range(100, 2000):
24+
close = np.random.random(i)
25+
that_ret = that_HT_TRENDLINE(close)
26+
27+
if __name__ == '__main__':
28+
bench_this_ht_trendline()
29+
bench_that_ht_trendline()

benchmark/bench_ma.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,17 @@
1616
def bench_this_ma():
1717
for i in range(100, 2000):
1818
t = 14
19+
matype = 0 # SMA
1920
close = np.random.random(i)
20-
this_ret = this_MA(close, timeperiod=t)
21+
this_ret = this_MA(close, timeperiod=t, matype=matype)
2122

2223
@bench
2324
def bench_that_ma():
2425
for i in range(100, 2000):
2526
t = 14
27+
matype = 0 # SMA
2628
close = np.random.random(i)
27-
that_ret = that_MA(close, timeperiod=t)
29+
that_ret = that_MA(close, timeperiod=t, matype=matype)
2830

2931
if __name__ == '__main__':
3032
bench_this_ma()

tabox/tests/test_defs.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import unittest
2+
import numpy as np
3+
from tabox.ta_func.ta_defs import (
4+
TA_INTEGER_MIN, TA_INTEGER_MAX, TA_REAL_MIN, TA_REAL_MAX,
5+
TA_INTEGER_DEFAULT, TA_REAL_DEFAULT
6+
)
7+
8+
class TestTADefs(unittest.TestCase):
9+
10+
def test_constants_values(self):
11+
"""Test that constants have expected values"""
12+
self.assertEqual(TA_INTEGER_MIN, -2147483647)
13+
self.assertEqual(TA_INTEGER_MAX, 2147483647)
14+
self.assertEqual(TA_INTEGER_DEFAULT, -2147483648)
15+
16+
self.assertEqual(TA_REAL_MIN, -3e37)
17+
self.assertEqual(TA_REAL_MAX, 3e37)
18+
self.assertEqual(TA_REAL_DEFAULT, -4e37)
19+
20+
def test_constants_relationships(self):
21+
"""Test relationships between constants"""
22+
self.assertLess(TA_INTEGER_MIN, TA_INTEGER_MAX)
23+
self.assertLess(TA_INTEGER_DEFAULT, TA_INTEGER_MIN)
24+
25+
self.assertLess(TA_REAL_MIN, TA_REAL_MAX)
26+
self.assertLess(TA_REAL_DEFAULT, TA_REAL_MIN)
27+
28+
def test_default_values_outside_range(self):
29+
"""Test that default values are outside valid ranges"""
30+
self.assertLess(TA_INTEGER_DEFAULT, TA_INTEGER_MIN)
31+
self.assertLess(TA_REAL_DEFAULT, TA_REAL_MIN)
32+
33+
if __name__ == '__main__':
34+
unittest.main()

tabox/tests/test_ma.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import numpy as np
2+
3+
from tabox.ta_func.ta_MA import MA as this_MA
4+
from talib import MA as that_MA
5+
6+
import unittest
7+
8+
class TestMA(unittest.TestCase):
9+
10+
def test_random_vector(self):
11+
for i in range(100, 300):
12+
for t in [3, 5, 7, 13, 30]:
13+
for matype in range(9): # Test all 9 MA types
14+
close = np.random.random(i)
15+
this_ret = this_MA(close, timeperiod=t, matype=matype)
16+
that_ret = that_MA(close, timeperiod=t, matype=matype)
17+
18+
self.assertTrue(np.allclose(this_ret, that_ret, equal_nan=True))
19+
20+
def test_ma_types(self):
21+
"""Test all supported MA types"""
22+
close = np.random.random(100)
23+
24+
# Test each MA type individually
25+
for matype in range(9):
26+
result = this_MA(close, timeperiod=14, matype=matype)
27+
self.assertIsInstance(result, np.ndarray)
28+
self.assertEqual(len(result), len(close))
29+
30+
def test_edge_cases(self):
31+
"""Test edge cases"""
32+
close = np.array([1.0, 2.0, 3.0, 4.0, 5.0])
33+
34+
# Test minimum period
35+
result = this_MA(close, timeperiod=1, matype=0)
36+
np.testing.assert_array_equal(result, close)
37+
38+
# Test period larger than data
39+
result = this_MA(close, timeperiod=10, matype=0)
40+
self.assertTrue(np.all(np.isnan(result)))
41+
42+
if __name__ == '__main__':
43+
unittest.main()

tabox/tests/test_utils.py

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import unittest
2+
import numpy as np
3+
from tabox.ta_func.ta_utils import (
4+
check_array, check_timeperiod, check_begidx1, check_begidx2,
5+
check_begidx3, check_begidx4, check_length2, check_length3,
6+
check_length4, make_double_array
7+
)
8+
9+
class TestTAUtils(unittest.TestCase):
10+
11+
def test_check_array(self):
12+
"""Test array validation function"""
13+
# Test numpy array input
14+
arr = np.array([1.0, 2.0, 3.0], dtype=np.float64)
15+
result = check_array(arr)
16+
np.testing.assert_array_equal(result, arr)
17+
18+
# Test list input
19+
lst = [1.0, 2.0, 3.0]
20+
result = check_array(lst)
21+
expected = np.array(lst, dtype=np.float64)
22+
np.testing.assert_array_equal(result, expected)
23+
24+
# Test wrong dtype
25+
arr_int = np.array([1, 2, 3], dtype=np.int32)
26+
with self.assertRaises(Exception):
27+
check_array(arr_int)
28+
29+
# Test wrong dimensions
30+
arr_2d = np.array([[1.0, 2.0], [3.0, 4.0]], dtype=np.float64)
31+
with self.assertRaises(Exception):
32+
check_array(arr_2d)
33+
34+
def test_check_timeperiod(self):
35+
"""Test timeperiod validation"""
36+
# Valid timeperiod
37+
try:
38+
check_timeperiod(5)
39+
except Exception:
40+
self.fail("check_timeperiod raised exception for valid timeperiod")
41+
42+
# Invalid timeperiods
43+
with self.assertRaises(Exception):
44+
check_timeperiod(1)
45+
with self.assertRaises(Exception):
46+
check_timeperiod(0)
47+
with self.assertRaises(Exception):
48+
check_timeperiod(-1)
49+
50+
def test_check_begidx1(self):
51+
"""Test single array begin index check"""
52+
# Normal array
53+
arr = np.array([1.0, 2.0, 3.0], dtype=np.float64)
54+
result = check_begidx1(arr)
55+
self.assertEqual(result, 0)
56+
57+
# Array with leading NaN
58+
arr_nan = np.array([np.nan, np.nan, 1.0, 2.0], dtype=np.float64)
59+
result = check_begidx1(arr_nan)
60+
self.assertEqual(result, 2)
61+
62+
# All NaN array
63+
arr_all_nan = np.array([np.nan, np.nan, np.nan], dtype=np.float64)
64+
with self.assertRaises(Exception):
65+
check_begidx1(arr_all_nan)
66+
67+
def test_check_begidx2(self):
68+
"""Test dual array begin index check"""
69+
arr1 = np.array([1.0, 2.0, 3.0], dtype=np.float64)
70+
arr2 = np.array([4.0, 5.0, 6.0], dtype=np.float64)
71+
result = check_begidx2(arr1, arr2)
72+
self.assertEqual(result, 0)
73+
74+
# With NaN in first array
75+
arr1_nan = np.array([np.nan, 2.0, 3.0], dtype=np.float64)
76+
arr2 = np.array([4.0, 5.0, 6.0], dtype=np.float64)
77+
result = check_begidx2(arr1_nan, arr2)
78+
self.assertEqual(result, 1)
79+
80+
def test_check_length2(self):
81+
"""Test dual array length validation"""
82+
arr1 = np.array([1.0, 2.0, 3.0], dtype=np.float64)
83+
arr2 = np.array([4.0, 5.0, 6.0], dtype=np.float64)
84+
length = check_length2(arr1, arr2)
85+
self.assertEqual(length, 3)
86+
87+
# Different lengths
88+
arr1 = np.array([1.0, 2.0], dtype=np.float64)
89+
arr2 = np.array([4.0, 5.0, 6.0], dtype=np.float64)
90+
with self.assertRaises(Exception):
91+
check_length2(arr1, arr2)
92+
93+
def test_make_double_array(self):
94+
"""Test double array creation"""
95+
length = 5
96+
lookback = 2
97+
result = make_double_array(length, lookback)
98+
99+
self.assertEqual(len(result), length)
100+
self.assertEqual(result.dtype, np.float64)
101+
102+
# Check that first 'lookback' elements are NaN
103+
for i in range(lookback):
104+
self.assertTrue(np.isnan(result[i]))
105+
106+
# Check that remaining elements are not NaN (they should be uninitialized but not NaN)
107+
# Actually, np.empty might contain arbitrary values, so we just check length and dtype
108+
109+
if __name__ == '__main__':
110+
unittest.main()

0 commit comments

Comments
 (0)