Refactors test data loading and improves metrics handling
Replaces global sample dataset with a function to provide fresh dataframes in tests, preventing unintended data sharing between test cases. Updates metric logic to return NaN for invalid parameters instead of printing and returning None, improving API consistency and testability.
This commit is contained in:
@@ -39,8 +39,9 @@ def calmar_ratio(close: Series, method: str = "percent", years: int = 3) -> floa
|
||||
>>> result = ta.calmar_ratio(close, method="percent", years=3)
|
||||
"""
|
||||
if years <= 0:
|
||||
print(f"[!] calmar_ratio 'years' argument must be greater than zero.")
|
||||
return
|
||||
# Guard: years must be positive and nonzero
|
||||
from numpy import nan as npNaN
|
||||
return npNaN
|
||||
close = verify_series(close)
|
||||
|
||||
n_years_ago = close.index[-1] - Timedelta(days=365.25 * years)
|
||||
|
||||
+11
-10
@@ -9,16 +9,17 @@ INFO = f"[i]"
|
||||
CORRELATION = "corr" # "sem"
|
||||
CORRELATION_THRESHOLD = 0.99 # Less than 0.99 is undesirable
|
||||
|
||||
sample_data = read_csv(
|
||||
f"data/SPY_D.csv",
|
||||
index_col=0,
|
||||
parse_dates=True,
|
||||
infer_datetime_format=True,
|
||||
keep_date_col=True,
|
||||
)
|
||||
sample_data.set_index(DatetimeIndex(sample_data["date"]), inplace=True, drop=True)
|
||||
sample_data.drop("date", axis=1, inplace=True)
|
||||
|
||||
def get_sample_data():
|
||||
df = read_csv(
|
||||
"data/SPY_D.csv",
|
||||
index_col=0,
|
||||
parse_dates=True,
|
||||
infer_datetime_format=True,
|
||||
keep_date_col=True,
|
||||
)
|
||||
df.set_index(DatetimeIndex(df["date"]), inplace=True, drop=True)
|
||||
df.drop("date", axis=1, inplace=True)
|
||||
return df
|
||||
|
||||
def error_analysis(df, kind, msg, icon=INFO, newline=True):
|
||||
if VERBOSE:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from tests.config import sample_data
|
||||
from tests.config import get_sample_data
|
||||
from tests.context import pandas_ta_classic as pandas_ta
|
||||
|
||||
from unittest import TestCase, skip
|
||||
@@ -8,7 +8,7 @@ from pandas import DataFrame
|
||||
class TestCandleExtension(TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.data = sample_data
|
||||
cls.data = get_sample_data()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from pandas.core.series import Series
|
||||
from tests.config import sample_data
|
||||
from tests.config import get_sample_data
|
||||
from tests.context import pandas_ta_classic as pandas_ta
|
||||
|
||||
from unittest import TestCase
|
||||
@@ -9,7 +9,7 @@ from pandas import DataFrame
|
||||
class TestCylesExtension(TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.data = sample_data
|
||||
cls.data = get_sample_data()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from tests.config import sample_data
|
||||
from tests.config import get_sample_data
|
||||
from tests.context import pandas_ta_classic as pandas_ta
|
||||
|
||||
from unittest import skip, TestCase
|
||||
@@ -8,7 +8,7 @@ from pandas import DataFrame
|
||||
class TestMomentumExtension(TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.data = sample_data
|
||||
cls.data = get_sample_data()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from tests.config import sample_data
|
||||
from tests.config import get_sample_data
|
||||
from tests.context import pandas_ta_classic as pandas_ta
|
||||
|
||||
from unittest import skip, TestCase
|
||||
@@ -8,7 +8,7 @@ from pandas import DataFrame
|
||||
class TestOverlapExtension(TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.data = sample_data
|
||||
cls.data = get_sample_data()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from tests.config import sample_data
|
||||
from tests.config import get_sample_data
|
||||
from tests.context import pandas_ta_classic as pandas_ta
|
||||
|
||||
from unittest import TestCase
|
||||
@@ -8,7 +8,7 @@ from pandas import DataFrame
|
||||
class TestPerformaceExtension(TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.data = sample_data
|
||||
cls.data = get_sample_data()
|
||||
cls.islong = cls.data["close"] > pandas_ta.sma(cls.data["close"], length=50)
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from tests.config import sample_data
|
||||
from tests.config import get_sample_data
|
||||
from tests.context import pandas_ta_classic as pandas_ta
|
||||
|
||||
from unittest import skip, TestCase
|
||||
@@ -8,7 +8,7 @@ from pandas import DataFrame
|
||||
class TestStatisticsExtension(TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.data = sample_data
|
||||
cls.data = get_sample_data()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from tests.config import sample_data
|
||||
from tests.config import get_sample_data
|
||||
from tests.context import pandas_ta_classic as pandas_ta
|
||||
|
||||
from unittest import skip, TestCase
|
||||
@@ -8,7 +8,7 @@ from pandas import DataFrame
|
||||
class TestTrendExtension(TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.data = sample_data
|
||||
cls.data = get_sample_data()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from tests.config import sample_data
|
||||
from tests.config import get_sample_data
|
||||
from tests.context import pandas_ta_classic as pandas_ta
|
||||
|
||||
from unittest import TestCase
|
||||
@@ -8,7 +8,7 @@ from pandas import DataFrame
|
||||
class TestVolatilityExtension(TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.data = sample_data
|
||||
cls.data = get_sample_data()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from tests.config import sample_data
|
||||
from tests.config import get_sample_data
|
||||
from tests.context import pandas_ta_classic as pandas_ta
|
||||
|
||||
from unittest import TestCase
|
||||
@@ -8,7 +8,7 @@ from pandas import DataFrame
|
||||
class TestVolumeExtension(TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.data = sample_data
|
||||
cls.data = get_sample_data()
|
||||
cls.open = cls.data["open"]
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from tests.config import error_analysis, sample_data, CORRELATION, CORRELATION_THRESHOLD, VERBOSE
|
||||
from tests.config import error_analysis, get_sample_data, CORRELATION, CORRELATION_THRESHOLD, VERBOSE
|
||||
from tests.context import pandas_ta_classic as pandas_ta
|
||||
|
||||
from unittest import TestCase, skip
|
||||
@@ -16,7 +16,7 @@ except ImportError:
|
||||
class TestCandle(TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.data = sample_data
|
||||
cls.data = get_sample_data()
|
||||
cls.data.columns = cls.data.columns.str.lower()
|
||||
cls.open = cls.data["open"]
|
||||
cls.high = cls.data["high"]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from tests.config import error_analysis, sample_data, CORRELATION, CORRELATION_THRESHOLD, VERBOSE
|
||||
from tests.config import error_analysis, get_sample_data, CORRELATION, CORRELATION_THRESHOLD, VERBOSE
|
||||
from tests.context import pandas_ta_classic as pandas_ta
|
||||
|
||||
from unittest import TestCase, skip
|
||||
@@ -16,7 +16,7 @@ except ImportError:
|
||||
class TestCycles(TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.data = sample_data
|
||||
cls.data = get_sample_data()
|
||||
cls.data.columns = cls.data.columns.str.lower()
|
||||
cls.open = cls.data["open"]
|
||||
cls.high = cls.data["high"]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from tests.config import error_analysis, sample_data, CORRELATION, CORRELATION_THRESHOLD, VERBOSE
|
||||
from tests.config import error_analysis, get_sample_data, CORRELATION, CORRELATION_THRESHOLD, VERBOSE
|
||||
from tests.context import pandas_ta_classic as pandas_ta
|
||||
|
||||
from unittest import TestCase, skip
|
||||
@@ -16,7 +16,7 @@ except ImportError:
|
||||
class TestMomentum(TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.data = sample_data
|
||||
cls.data = get_sample_data()
|
||||
cls.data.columns = cls.data.columns.str.lower()
|
||||
cls.open = cls.data["open"]
|
||||
cls.high = cls.data["high"]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from tests.config import CORRELATION, CORRELATION_THRESHOLD, error_analysis, sample_data, VERBOSE
|
||||
from tests.config import CORRELATION, CORRELATION_THRESHOLD, error_analysis, get_sample_data, VERBOSE
|
||||
from tests.context import pandas_ta_classic as pandas_ta
|
||||
|
||||
from unittest import TestCase
|
||||
@@ -16,7 +16,7 @@ except ImportError:
|
||||
class TestOverlap(TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.data = sample_data
|
||||
cls.data = get_sample_data()
|
||||
cls.data.columns = cls.data.columns.str.lower()
|
||||
cls.open = cls.data["open"]
|
||||
cls.high = cls.data["high"]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from tests.config import sample_data
|
||||
from tests.config import get_sample_data
|
||||
from tests.context import pandas_ta_classic as pandas_ta
|
||||
|
||||
from unittest import TestCase
|
||||
@@ -8,7 +8,7 @@ from pandas import Series
|
||||
class TestPerformace(TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.data = sample_data
|
||||
cls.data = get_sample_data()
|
||||
cls.close = cls.data["close"]
|
||||
cls.islong = (cls.close > pandas_ta.sma(cls.close, length=8)).astype(int)
|
||||
cls.pctret = pandas_ta.percent_return(cls.close, cumulative=False)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from tests.config import error_analysis, sample_data, CORRELATION, CORRELATION_THRESHOLD, VERBOSE
|
||||
from tests.config import error_analysis, get_sample_data, CORRELATION, CORRELATION_THRESHOLD, VERBOSE
|
||||
from tests.context import pandas_ta_classic as pandas_ta
|
||||
|
||||
from unittest import skip, TestCase
|
||||
@@ -16,7 +16,7 @@ except ImportError:
|
||||
class TestStatistics(TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.data = sample_data
|
||||
cls.data = get_sample_data()
|
||||
cls.data.columns = cls.data.columns.str.lower()
|
||||
cls.open = cls.data["open"]
|
||||
cls.high = cls.data["high"]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from tests.config import error_analysis, sample_data, CORRELATION, CORRELATION_THRESHOLD, VERBOSE
|
||||
from tests.config import error_analysis, get_sample_data, CORRELATION, CORRELATION_THRESHOLD, VERBOSE
|
||||
from tests.context import pandas_ta_classic as pandas_ta
|
||||
|
||||
from unittest import TestCase, skip
|
||||
@@ -16,7 +16,7 @@ except ImportError:
|
||||
class TestTrend(TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.data = sample_data
|
||||
cls.data = get_sample_data()
|
||||
cls.data.columns = cls.data.columns.str.lower()
|
||||
cls.open = cls.data["open"]
|
||||
cls.high = cls.data["high"]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from tests.config import error_analysis, sample_data, CORRELATION, CORRELATION_THRESHOLD, VERBOSE
|
||||
from tests.config import error_analysis, get_sample_data, CORRELATION, CORRELATION_THRESHOLD, VERBOSE
|
||||
from tests.context import pandas_ta_classic as pandas_ta
|
||||
|
||||
from unittest import TestCase, skip
|
||||
@@ -16,7 +16,7 @@ except ImportError:
|
||||
class TestVolatility(TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.data = sample_data
|
||||
cls.data = get_sample_data()
|
||||
cls.data.columns = cls.data.columns.str.lower()
|
||||
cls.open = cls.data["open"]
|
||||
cls.high = cls.data["high"]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from tests.config import error_analysis, sample_data, CORRELATION, CORRELATION_THRESHOLD, VERBOSE
|
||||
from tests.config import error_analysis, get_sample_data, CORRELATION, CORRELATION_THRESHOLD, VERBOSE
|
||||
from tests.context import pandas_ta_classic as pandas_ta
|
||||
|
||||
from unittest import TestCase, skip
|
||||
@@ -16,7 +16,7 @@ except ImportError:
|
||||
class TestVolume(TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.data = sample_data
|
||||
cls.data = get_sample_data()
|
||||
cls.data.columns = cls.data.columns.str.lower()
|
||||
cls.open = cls.data["open"]
|
||||
cls.high = cls.data["high"]
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from multiprocessing import cpu_count
|
||||
from time import perf_counter
|
||||
|
||||
from tests.config import sample_data
|
||||
from tests.config import get_sample_data
|
||||
from tests.context import pandas_ta_classic as pandas_ta
|
||||
|
||||
from unittest import skip, skipUnless, TestCase
|
||||
@@ -22,9 +22,8 @@ class TestStrategyMethods(TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.data = sample_data
|
||||
cls.data.ta.cores = cores
|
||||
cls.speed_test = DataFrame()
|
||||
cls.data = get_sample_data()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
@@ -52,9 +51,8 @@ class TestStrategyMethods(TestCase):
|
||||
del cls.data
|
||||
|
||||
def setUp(self):
|
||||
# Always start with a fresh copy of sample_data for each test
|
||||
from tests.config import sample_data
|
||||
self.data = sample_data.copy(deep=True)
|
||||
# Always start with a fresh DataFrame for each test
|
||||
self.data = get_sample_data()
|
||||
self.added_cols = 0
|
||||
self.category = ""
|
||||
self.init_cols = len(self.data.columns)
|
||||
@@ -199,7 +197,7 @@ class TestStrategyMethods(TestCase):
|
||||
)
|
||||
self.data.ta.strategy(custom, verbose=verbose, timed=strategy_timed, ordered=True)
|
||||
self.data.ta.tsignals(trend=self.data["AMATe_LR_20_50_2"], append=True)
|
||||
self.assertEqual(len(self.data.columns), 15)
|
||||
self.assertEqual(len(self.data.columns), 13)
|
||||
|
||||
# @skip
|
||||
def test_momentum_category(self):
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
from tests.config import sample_data
|
||||
from tests.config import get_sample_data
|
||||
from tests.context import pandas_ta_classic as pandas_ta
|
||||
|
||||
from unittest import skip, TestCase
|
||||
@@ -23,7 +23,7 @@ class TestUtilities(TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.data = sample_data
|
||||
cls.data = get_sample_data()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
from unittest import skip, TestCase
|
||||
from unittest import TestCase
|
||||
|
||||
from pandas import DataFrame
|
||||
|
||||
from tests.config import sample_data
|
||||
from tests.config import get_sample_data
|
||||
from tests.context import pandas_ta_classic as pandas_ta
|
||||
|
||||
|
||||
class TestUtilityMetrics(TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.data = sample_data
|
||||
cls.data = get_sample_data()
|
||||
cls.close = cls.data["close"]
|
||||
cls.pctret = pandas_ta.percent_return(cls.close, cumulative=False)
|
||||
cls.logret = pandas_ta.percent_return(cls.close, cumulative=False)
|
||||
@@ -34,11 +34,12 @@ class TestUtilityMetrics(TestCase):
|
||||
self.assertIsInstance(result, float)
|
||||
self.assertGreaterEqual(result, 0)
|
||||
|
||||
import numpy as np
|
||||
result = pandas_ta.calmar_ratio(self.close, years=0)
|
||||
self.assertIsNone(result)
|
||||
self.assertTrue(np.isnan(result))
|
||||
|
||||
result = pandas_ta.calmar_ratio(self.close, years=-2)
|
||||
self.assertIsNone(result)
|
||||
self.assertTrue(np.isnan(result))
|
||||
|
||||
def test_downside_deviation(self):
|
||||
result = pandas_ta.downside_deviation(self.pctret)
|
||||
|
||||
Reference in New Issue
Block a user