Fix tests
This commit is contained in:
+21
-21
@@ -67,27 +67,27 @@ class TestPriceRepairAssumptions(unittest.TestCase):
|
||||
if vol_match.all():
|
||||
# print(" - volume match 100%")
|
||||
pass
|
||||
elif vol_match_ndiff == 1 and not vol_match[0]:
|
||||
# print(" - volume almost-perfect match, only first different")
|
||||
# debug = True
|
||||
elif vol_match_ndiff == 1 and (not vol_match[-1]):
|
||||
# Almost perfect, only last row different. Not my fault.
|
||||
pass
|
||||
else:
|
||||
# print(f" - volume match {vol_match_nmatch}/{len(vol_match)} {vol_match.to_numpy()}")
|
||||
print(f" - volume significantly different in first row: vol_diff_pct={vol_diff_pct*100}%")
|
||||
print(f" - volume significantly different in first or last row: vol_diff_pct={vol_diff_pct*100}%")
|
||||
debug = True
|
||||
|
||||
if debug:
|
||||
print("- investigate:")
|
||||
print(f" - tkr = {tkr}")
|
||||
print(f" - interval = {interval}")
|
||||
print(f" - period = {period}")
|
||||
print("- df_truth:")
|
||||
print(df_truth)#[['Open', 'Close', 'Volume']])
|
||||
print(df_truth[['Open', 'Close', 'Volume']])
|
||||
df_1d = dat.history(interval='1d', period=period)
|
||||
print("- df_1d:")
|
||||
print(df_1d)#[['Open', 'Close', 'Volume']])
|
||||
print(df_1d[['Open', 'Close', 'Volume']])
|
||||
print("- dfr:")
|
||||
print(dfr)#[['Open', 'Close', 'Volume']])
|
||||
return
|
||||
print(dfr[['Open', 'Close', 'Volume']])
|
||||
self.assertFalse(True)
|
||||
|
||||
|
||||
|
||||
@@ -356,7 +356,6 @@ class TestPriceRepair(unittest.TestCase):
|
||||
self.assertTrue("Repaired?" in df_repaired.columns)
|
||||
self.assertFalse(df_repaired["Repaired?"].isna().any())
|
||||
|
||||
@unittest.skip("Currently failing - need to investigate")
|
||||
def test_repair_zeroes_daily(self):
|
||||
tkr = "BBIL.L"
|
||||
dat = yf.Ticker(tkr, session=self.session)
|
||||
@@ -388,15 +387,13 @@ class TestPriceRepair(unittest.TestCase):
|
||||
# Test that 'Adj Close' is reconstructed correctly,
|
||||
# particularly when a dividend occurred within 1 day.
|
||||
|
||||
self.skipTest("Currently failing because Yahoo returning slightly different data for interval 1d vs 1h on day Aug 6 2024")
|
||||
|
||||
tkr = "INTC"
|
||||
df = _pd.DataFrame(data={"Open": [2.020000e+01, 2.032000e+01, 1.992000e+01, 1.910000e+01, 2.008000e+01],
|
||||
"High": [2.039000e+01, 2.063000e+01, 2.025000e+01, 2.055000e+01, 2.015000e+01],
|
||||
"Low": [1.929000e+01, 1.975000e+01, 1.895000e+01, 1.884000e+01, 1.950000e+01],
|
||||
"Close": [2.011000e+01, 1.983000e+01, 1.899000e+01, 2.049000e+01, 1.971000e+01],
|
||||
"Adj Close": [1.998323e+01, 1.970500e+01, 1.899000e+01, 2.049000e+01, 1.971000e+01],
|
||||
"Volume": [1.473857e+08, 1.066704e+08, 9.797230e+07, 9.683680e+07, 7.639450e+07],
|
||||
df = _pd.DataFrame(data={"Open": [2.008000e+01, 1.910000e+01, 1.992000e+01, 2.032000e+01, 2.020000e+01],
|
||||
"High": [2.015000e+01, 2.055000e+01, 2.025000e+01, 2.063000e+01, 2.039000e+01],
|
||||
"Low": [1.950000e+01, 1.884000e+01, 1.895000e+01, 1.975000e+01, 1.929000e+01],
|
||||
"Close": [1.971000e+01, 2.049000e+01, 1.899000e+01, 1.983000e+01, 2.011000e+01],
|
||||
"Adj Close": [1.971000e+01, 2.049000e+01, 1.899000e+01, 1.970500e+01, 1.998323e+01],
|
||||
"Volume": [7.639450e+07, 9.683680e+07, 9.797230e+07, 1.066704e+08, 1.473857e+08],
|
||||
"Dividends": [0.000000e+00, 0.000000e+00, 1.250000e-01, 0.000000e+00, 0.000000e+00]},
|
||||
index=_pd.to_datetime([_dt.datetime(2024, 8, 9),
|
||||
_dt.datetime(2024, 8, 8),
|
||||
@@ -422,9 +419,13 @@ class TestPriceRepair(unittest.TestCase):
|
||||
try:
|
||||
self.assertTrue(_np.isclose(df_slice_bad_repaired[c], df_slice[c], rtol=rtol).all())
|
||||
except Exception:
|
||||
print(f"# column = {c}")
|
||||
print("# correct:") ; print(df_slice[c])
|
||||
print("# repaired:") ; print(df_slice_bad_repaired[c])
|
||||
df_slice_bad['Adj'] = df_slice_bad['Adj Close'] / df_slice_bad['Close']
|
||||
df_slice_bad_repaired['Adj'] = df_slice_bad_repaired['Adj Close'] / df_slice_bad_repaired['Close']
|
||||
df_slice['Adj'] = df_slice['Adj Close'] / df_slice['Close']
|
||||
print(f"# column={c}, i={i}, j={j}")
|
||||
print("# bad:") ; print(df_slice_bad[['Close', 'Adj Close', 'Adj', 'Dividends']])
|
||||
print("# repaired:") ; print(df_slice_bad_repaired[['Close', 'Adj Close', 'Adj', 'Dividends']])
|
||||
print("# correct:") ; print(df_slice[['Close', 'Adj Close', 'Adj', 'Dividends']])
|
||||
raise
|
||||
self.assertTrue("Repaired?" in df_slice_bad_repaired.columns)
|
||||
self.assertFalse(df_slice_bad_repaired["Repaired?"].isna().any())
|
||||
@@ -581,7 +582,6 @@ class TestPriceRepair(unittest.TestCase):
|
||||
bad_tkrs.append('4063.T') # Div with same-day split not split adjusted
|
||||
|
||||
# Adj too small
|
||||
bad_tkrs += ['ADIG.L']
|
||||
bad_tkrs += ['CLC.L']
|
||||
bad_tkrs += ['RGL.L']
|
||||
bad_tkrs += ['SERE.L']
|
||||
|
||||
@@ -48,7 +48,7 @@ class TestPriceHistory(unittest.TestCase):
|
||||
def test_download_multi_small_interval(self):
|
||||
use_tkrs = ["AAPL", "0Q3.DE", "ATVI"]
|
||||
df = yf.download(use_tkrs, period="1d", interval="5m", auto_adjust=True)
|
||||
self.assertEqual(df.index.tz, _dt.timezone.utc)
|
||||
self.assertEqual(df.index.tz, _tz.timezone("America/New_York"))
|
||||
|
||||
def test_download_with_invalid_ticker(self):
|
||||
#Checks if using an invalid symbol gives the same output as not using an invalid symbol in combination with a valid symbol (AAPL)
|
||||
|
||||
+56
-28
@@ -20,7 +20,7 @@ from yfinance.config import YfConfig
|
||||
import unittest
|
||||
# import requests_cache
|
||||
from unittest.mock import patch, MagicMock
|
||||
from typing import Union, Any, get_args, _GenericAlias
|
||||
from typing import Union, Any, get_args, get_origin
|
||||
# from urllib.parse import urlparse, parse_qs, urlencode, urlunparse
|
||||
|
||||
ticker_attributes = (
|
||||
@@ -62,17 +62,18 @@ ticker_attributes = (
|
||||
def assert_attribute_type(testClass: unittest.TestCase, instance, attribute_name, expected_type):
|
||||
try:
|
||||
attribute = getattr(instance, attribute_name)
|
||||
if attribute is not None and expected_type is not Any:
|
||||
err_msg = f'{attribute_name} type is {type(attribute)} not {expected_type}'
|
||||
if isinstance(expected_type, _GenericAlias) and expected_type.__origin__ is Union:
|
||||
allowed_types = get_args(expected_type)
|
||||
testClass.assertTrue(isinstance(attribute, allowed_types), err_msg)
|
||||
else:
|
||||
testClass.assertEqual(type(attribute), expected_type, err_msg)
|
||||
except Exception:
|
||||
testClass.assertRaises(
|
||||
YFNotImplementedError, lambda: getattr(instance, attribute_name)
|
||||
)
|
||||
except YFNotImplementedError:
|
||||
# Some attributes legitimately raise on missing/bad tickers.
|
||||
return
|
||||
|
||||
if attribute is not None and expected_type is not Any:
|
||||
err_msg = f'{attribute_name} type is {type(attribute)} not {expected_type}'
|
||||
if get_origin(expected_type) is Union:
|
||||
allowed_types = get_args(expected_type)
|
||||
testClass.assertTrue(isinstance(attribute, allowed_types), err_msg)
|
||||
else:
|
||||
testClass.assertEqual(type(attribute), expected_type, err_msg)
|
||||
|
||||
|
||||
class TestTicker(unittest.TestCase):
|
||||
session = None
|
||||
@@ -308,23 +309,50 @@ class TestTickerHistory(unittest.TestCase):
|
||||
|
||||
def test_download(self):
|
||||
tomorrow = pd.Timestamp.now().date() + pd.Timedelta(days=1) # helps with caching
|
||||
for t in [False, True]:
|
||||
for i in [False, True]:
|
||||
for m in [False, True]:
|
||||
for threads in [False, True]:
|
||||
for ignore_tz in [False, True]:
|
||||
for mli in [False, True]:
|
||||
for n in [1, 'all']:
|
||||
symbols = self.symbols[0] if n == 1 else self.symbols
|
||||
data = yf.download(symbols, end=tomorrow, session=self.session,
|
||||
threads=t, ignore_tz=i, multi_level_index=m)
|
||||
self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
|
||||
self.assertFalse(data.empty, "data is empty")
|
||||
if i:
|
||||
self.assertIsNone(data.index.tz)
|
||||
else:
|
||||
self.assertIsNotNone(data.index.tz)
|
||||
if (not m) and n == 1:
|
||||
self.assertFalse(isinstance(data.columns, pd.MultiIndex))
|
||||
else:
|
||||
self.assertIsInstance(data.columns, pd.MultiIndex)
|
||||
for interval in ['1d', '1h']:
|
||||
if n == 1:
|
||||
symbols = self.symbols[0]
|
||||
else:
|
||||
# Add some other countries
|
||||
symbols = self.symbols + ['BATS.L', '7974.T']
|
||||
data = yf.download(symbols, end=tomorrow, session=self.session,
|
||||
threads=threads, ignore_tz=ignore_tz, multi_level_index=mli,
|
||||
interval=interval, progress=False)
|
||||
self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
|
||||
self.assertFalse(data.empty, "data is empty")
|
||||
if ignore_tz:
|
||||
self.assertIsNone(data.index.tz)
|
||||
else:
|
||||
self.assertIsNotNone(data.index.tz)
|
||||
self.assertEqual(str(data.index.tz), "America/New_York")
|
||||
if (not mli) and n == 1:
|
||||
self.assertFalse(isinstance(data.columns, pd.MultiIndex))
|
||||
else:
|
||||
self.assertIsInstance(data.columns, pd.MultiIndex)
|
||||
|
||||
if interval == '1d':
|
||||
if ignore_tz:
|
||||
self.assertTrue((data.index.hour == 0).all())
|
||||
self.assertTrue((data.index.minute == 0).all())
|
||||
else:
|
||||
self.assertTrue((data.index.minute == 0).all())
|
||||
if n == 1:
|
||||
self.assertTrue((data.index.hour == 0).all())
|
||||
else:
|
||||
self.assertTrue((data.index.hour != 0).any())
|
||||
elif interval == '1h':
|
||||
hours = pd.Index(data.index.hour)
|
||||
if ignore_tz:
|
||||
self.assertTrue(((hours >= 7) & (hours <= 19)).all())
|
||||
else:
|
||||
if n == 1:
|
||||
self.assertTrue(((hours >= 7) & (hours <= 19)).all())
|
||||
else:
|
||||
self.assertTrue((~((hours >= 7) & (hours <= 19))).any())
|
||||
|
||||
# Hopefully one day we find an equivalent "requests_cache" that works with "curl_cffi"
|
||||
# def test_no_expensive_calls_introduced(self):
|
||||
|
||||
@@ -424,7 +424,7 @@ class Calendars:
|
||||
limit=limit,
|
||||
offset=offset,
|
||||
force=force,
|
||||
)
|
||||
).sort_values('Event Start Date', ascending=False)
|
||||
|
||||
@log_indent_decorator
|
||||
def get_ipo_info_calendar(
|
||||
|
||||
@@ -654,6 +654,11 @@ class PriceHistory:
|
||||
else:
|
||||
df2 = df.resample(resample_period, label='left', closed='left', offset=offset).agg(resample_map)
|
||||
df2.loc[df2['Stock Splits']==1.0, 'Stock Splits'] = 0.0
|
||||
|
||||
# Handle NaNs from very long holidays.
|
||||
prev_close = df2['Close'].shift(1).ffill()
|
||||
for c in ['Open', 'High', 'Low', 'Close']:
|
||||
df2[c] = df2[c].fillna(prev_close)
|
||||
return df2
|
||||
|
||||
@utils.log_indent_decorator
|
||||
@@ -2944,6 +2949,8 @@ class PriceHistory:
|
||||
def _calc_volume_zscore(volume, block):
|
||||
# print(f"_calc_volume_zscore(volume={volume})")
|
||||
values = block['Volume'].to_numpy()
|
||||
if len(values) == 0 or (values == 0).all():
|
||||
return 0
|
||||
std = np.std(values, ddof=1)
|
||||
if std == 0.0:
|
||||
return 0
|
||||
|
||||
Reference in New Issue
Block a user