moved create_test_data to a fixture in conftest.py

This commit is contained in:
Gourav Kumar 2022-05-08 18:09:39 +05:30
parent 0d0b2121a3
commit 68d854cb3f
2 changed files with 126 additions and 109 deletions

105
tests/conftest.py Normal file
View File

@ -0,0 +1,105 @@
import datetime
import math
import random
from typing import List
import fincal as fc
import pytest
from dateutil.relativedelta import relativedelta
def conf_add(n1, n2):
return n1 + n2
@pytest.fixture
def conf_fun():
return conf_add
def create_prices(s0: float, mu: float, sigma: float, num_prices: int) -> list:
"""Generates a price following a geometric brownian motion process based on the input of the arguments.
Since this function is used only to generate data for tests, the seed is fixed as 1234.
Many of the tests rely on exact values generated using this seed.
If the seed is changed, those tests will fail.
Parameters:
------------
s0: float
Asset inital price.
mu: float
Interest rate expressed annual terms.
sigma: float
Volatility expressed annual terms.
num_prices: int
number of prices to generate
Returns:
--------
Returns a list of values generated using GBM algorithm
"""
random.seed(1234) # WARNING! Changing the seed will cause most tests to fail
all_values = []
for _ in range(num_prices):
s0 *= math.exp(
(mu - 0.5 * sigma**2) * (1.0 / 365.0) + sigma * math.sqrt(1.0 / 365.0) * random.gauss(mu=0, sigma=1)
)
all_values.append(round(s0, 2))
return all_values
def sample_data_generator(
frequency: fc.Frequency,
num: int = 1000,
skip_weekends: bool = False,
mu: float = 0.1,
sigma: float = 0.05,
eomonth: bool = False,
) -> List[tuple]:
"""Creates TimeSeries data
Parameters:
-----------
frequency: Frequency
The frequency of the time series data to be generated.
num: int
Number of date: value pairs to be generated.
skip_weekends: bool
Whether weekends (saturday, sunday) should be skipped.
Gets used only if the frequency is daily.
mu: float
Mean return for the values.
sigma: float
standard deviation of the values.
Returns:
--------
Returns a TimeSeries object
"""
start_date = datetime.datetime(2017, 1, 1)
timedelta_dict = {
frequency.freq_type: int(
frequency.value * num * (7 / 5 if frequency == fc.AllFrequencies.D and skip_weekends else 1)
)
}
end_date = start_date + relativedelta(**timedelta_dict)
dates = fc.create_date_series(start_date, end_date, frequency.symbol, skip_weekends=skip_weekends, eomonth=eomonth)
values = create_prices(1000, mu, sigma, num)
ts = list(zip(dates, values))
return ts
@pytest.fixture
def create_test_data():
return sample_data_generator

View File

@ -1,10 +1,6 @@
import datetime import datetime
import math
import random
from typing import List
import pytest import pytest
from dateutil.relativedelta import relativedelta
from fincal import ( from fincal import (
AllFrequencies, AllFrequencies,
FincalOptions, FincalOptions,
@ -15,89 +11,6 @@ from fincal import (
from fincal.exceptions import DateNotFoundError from fincal.exceptions import DateNotFoundError
def create_prices(s0: float, mu: float, sigma: float, num_prices: int) -> list:
"""Generates a price following a geometric brownian motion process based on the input of the arguments.
Since this function is used only to generate data for tests, the seed is fixed as 1234.
Many of the tests rely on exact values generated using this seed.
If the seed is changed, those tests will fail.
Parameters:
------------
s0: float
Asset inital price.
mu: float
Interest rate expressed annual terms.
sigma: float
Volatility expressed annual terms.
num_prices: int
number of prices to generate
Returns:
--------
Returns a list of values generated using GBM algorithm
"""
random.seed(1234) # WARNING! Changing the seed will cause most tests to fail
all_values = []
for _ in range(num_prices):
s0 *= math.exp(
(mu - 0.5 * sigma**2) * (1.0 / 365.0) + sigma * math.sqrt(1.0 / 365.0) * random.gauss(mu=0, sigma=1)
)
all_values.append(round(s0, 2))
return all_values
def create_test_data(
frequency: Frequency,
num: int = 1000,
skip_weekends: bool = False,
mu: float = 0.1,
sigma: float = 0.05,
eomonth: bool = False,
) -> List[tuple]:
"""Creates TimeSeries data
Parameters:
-----------
frequency: Frequency
The frequency of the time series data to be generated.
num: int
Number of date: value pairs to be generated.
skip_weekends: bool
Whether weekends (saturday, sunday) should be skipped.
Gets used only if the frequency is daily.
mu: float
Mean return for the values.
sigma: float
standard deviation of the values.
Returns:
--------
Returns a TimeSeries object
"""
start_date = datetime.datetime(2017, 1, 1)
timedelta_dict = {
frequency.freq_type: int(
frequency.value * num * (7 / 5 if frequency == AllFrequencies.D and skip_weekends else 1)
)
}
end_date = start_date + relativedelta(**timedelta_dict)
dates = create_date_series(start_date, end_date, frequency.symbol, skip_weekends=skip_weekends, eomonth=eomonth)
values = create_prices(1000, mu, sigma, num)
ts = list(zip(dates, values))
return ts
class TestDateSeries: class TestDateSeries:
def test_daily(self): def test_daily(self):
start_date = datetime.datetime(2020, 1, 1) start_date = datetime.datetime(2020, 1, 1)
@ -161,14 +74,14 @@ class TestDateSeries:
class TestTimeSeriesCreation: class TestTimeSeriesCreation:
def test_creation_with_list_of_tuples(self): def test_creation_with_list_of_tuples(self, create_test_data):
ts_data = create_test_data(frequency=AllFrequencies.D, num=50) ts_data = create_test_data(frequency=AllFrequencies.D, num=50)
ts = TimeSeries(ts_data, frequency="D") ts = TimeSeries(ts_data, frequency="D")
assert len(ts) == 50 assert len(ts) == 50
assert isinstance(ts.frequency, Frequency) assert isinstance(ts.frequency, Frequency)
assert ts.frequency.days == 1 assert ts.frequency.days == 1
def test_creation_with_string_dates(self): def test_creation_with_string_dates(self, create_test_data):
ts_data = create_test_data(frequency=AllFrequencies.D, num=50) ts_data = create_test_data(frequency=AllFrequencies.D, num=50)
ts_data1 = [(dt.strftime("%Y-%m-%d"), val) for dt, val in ts_data] ts_data1 = [(dt.strftime("%Y-%m-%d"), val) for dt, val in ts_data]
ts = TimeSeries(ts_data1, frequency="D") ts = TimeSeries(ts_data1, frequency="D")
@ -186,19 +99,19 @@ class TestTimeSeriesCreation:
ts = TimeSeries(ts_data1, frequency="D", date_format="%m-%d-%Y %H:%M") ts = TimeSeries(ts_data1, frequency="D", date_format="%m-%d-%Y %H:%M")
datetime.datetime(2017, 1, 1, 0, 0) in ts datetime.datetime(2017, 1, 1, 0, 0) in ts
def test_creation_with_list_of_dicts(self): def test_creation_with_list_of_dicts(self, create_test_data):
ts_data = create_test_data(frequency=AllFrequencies.D, num=50) ts_data = create_test_data(frequency=AllFrequencies.D, num=50)
ts_data1 = [{"date": dt.strftime("%Y-%m-%d"), "value": val} for dt, val in ts_data] ts_data1 = [{"date": dt.strftime("%Y-%m-%d"), "value": val} for dt, val in ts_data]
ts = TimeSeries(ts_data1, frequency="D") ts = TimeSeries(ts_data1, frequency="D")
datetime.datetime(2017, 1, 1) in ts datetime.datetime(2017, 1, 1) in ts
def test_creation_with_list_of_lists(self): def test_creation_with_list_of_lists(self, create_test_data):
ts_data = create_test_data(frequency=AllFrequencies.D, num=50) ts_data = create_test_data(frequency=AllFrequencies.D, num=50)
ts_data1 = [[dt.strftime("%Y-%m-%d"), val] for dt, val in ts_data] ts_data1 = [[dt.strftime("%Y-%m-%d"), val] for dt, val in ts_data]
ts = TimeSeries(ts_data1, frequency="D") ts = TimeSeries(ts_data1, frequency="D")
datetime.datetime(2017, 1, 1) in ts datetime.datetime(2017, 1, 1) in ts
def test_creation_with_dict(self): def test_creation_with_dict(self, create_test_data):
ts_data = create_test_data(frequency=AllFrequencies.D, num=50) ts_data = create_test_data(frequency=AllFrequencies.D, num=50)
ts_data1 = [{dt.strftime("%Y-%m-%d"): val} for dt, val in ts_data] ts_data1 = [{dt.strftime("%Y-%m-%d"): val} for dt, val in ts_data]
ts = TimeSeries(ts_data1, frequency="D") ts = TimeSeries(ts_data1, frequency="D")
@ -206,9 +119,8 @@ class TestTimeSeriesCreation:
class TestTimeSeriesBasics: class TestTimeSeriesBasics:
def test_fill(self, create_test_data):
FincalOptions.get_closest = "exact" FincalOptions.get_closest = "exact"
def test_fill(self):
ts_data = create_test_data(frequency=AllFrequencies.D, num=50, skip_weekends=True) ts_data = create_test_data(frequency=AllFrequencies.D, num=50, skip_weekends=True)
ts = TimeSeries(ts_data, frequency="D") ts = TimeSeries(ts_data, frequency="D")
ffill_data = ts.ffill() ffill_data = ts.ffill()
@ -235,7 +147,7 @@ class TestTimeSeriesBasics:
bf = ts.bfill() bf = ts.bfill()
assert bf["2021-01-03"][1] == 240 assert bf["2021-01-03"][1] == 240
def test_fill_weekly(self): def test_fill_weekly(self, create_test_data):
ts_data = create_test_data(frequency=AllFrequencies.W, num=10) ts_data = create_test_data(frequency=AllFrequencies.W, num=10)
ts_data.pop(2) ts_data.pop(2)
ts_data.pop(6) ts_data.pop(6)
@ -254,7 +166,7 @@ class TestTimeSeriesBasics:
class TestReturns: class TestReturns:
def test_returns_calc(self): def test_returns_calc(self, create_test_data):
ts_data = create_test_data(AllFrequencies.D, skip_weekends=True) ts_data = create_test_data(AllFrequencies.D, skip_weekends=True)
ts = TimeSeries(ts_data, "D") ts = TimeSeries(ts_data, "D")
returns = ts.calculate_returns( returns = ts.calculate_returns(
@ -287,7 +199,7 @@ class TestReturns:
with pytest.raises(DateNotFoundError): with pytest.raises(DateNotFoundError):
ts.calculate_returns("2020-04-04", return_period_unit="months", return_period_value=3, prior_match="exact") ts.calculate_returns("2020-04-04", return_period_unit="months", return_period_value=3, prior_match="exact")
def test_date_formats(self): def test_date_formats(self, create_test_data):
ts_data = create_test_data(AllFrequencies.D, skip_weekends=True) ts_data = create_test_data(AllFrequencies.D, skip_weekends=True)
ts = TimeSeries(ts_data, "D") ts = TimeSeries(ts_data, "D")
FincalOptions.date_format = "%d-%m-%Y" FincalOptions.date_format = "%d-%m-%Y"
@ -314,7 +226,7 @@ class TestReturns:
returns2 = ts.calculate_returns("04-01-2020", return_period_unit="days", return_period_value=90) returns2 = ts.calculate_returns("04-01-2020", return_period_unit="days", return_period_value=90)
assert round(returns1[1], 6) == round(returns2[1], 6) == 0.073632 assert round(returns1[1], 6) == round(returns2[1], 6) == 0.073632
def test_limits(self): def test_limits(self, create_test_data):
FincalOptions.date_format = "%Y-%m-%d" FincalOptions.date_format = "%Y-%m-%d"
ts_data = create_test_data(AllFrequencies.D) ts_data = create_test_data(AllFrequencies.D)
ts = TimeSeries(ts_data, "D") ts = TimeSeries(ts_data, "D")
@ -327,7 +239,7 @@ class TestReturns:
class TestExpand: class TestExpand:
def test_weekly_to_daily(self): def test_weekly_to_daily(self, create_test_data):
ts_data = create_test_data(AllFrequencies.W, 10) ts_data = create_test_data(AllFrequencies.W, 10)
ts = TimeSeries(ts_data, "W") ts = TimeSeries(ts_data, "W")
expanded_ts = ts.expand("D", "ffill") expanded_ts = ts.expand("D", "ffill")
@ -335,7 +247,7 @@ class TestExpand:
assert expanded_ts.frequency.name == "daily" assert expanded_ts.frequency.name == "daily"
assert expanded_ts.iloc[0][1] == expanded_ts.iloc[1][1] assert expanded_ts.iloc[0][1] == expanded_ts.iloc[1][1]
def test_weekly_to_daily_no_weekends(self): def test_weekly_to_daily_no_weekends(self, create_test_data):
ts_data = create_test_data(AllFrequencies.W, 10) ts_data = create_test_data(AllFrequencies.W, 10)
ts = TimeSeries(ts_data, "W") ts = TimeSeries(ts_data, "W")
expanded_ts = ts.expand("D", "ffill", skip_weekends=True) expanded_ts = ts.expand("D", "ffill", skip_weekends=True)
@ -343,7 +255,7 @@ class TestExpand:
assert expanded_ts.frequency.name == "daily" assert expanded_ts.frequency.name == "daily"
assert expanded_ts.iloc[0][1] == expanded_ts.iloc[1][1] assert expanded_ts.iloc[0][1] == expanded_ts.iloc[1][1]
def test_monthly_to_daily(self): def test_monthly_to_daily(self, create_test_data):
ts_data = create_test_data(AllFrequencies.M, 6) ts_data = create_test_data(AllFrequencies.M, 6)
ts = TimeSeries(ts_data, "M") ts = TimeSeries(ts_data, "M")
expanded_ts = ts.expand("D", "ffill") expanded_ts = ts.expand("D", "ffill")
@ -351,7 +263,7 @@ class TestExpand:
assert expanded_ts.frequency.name == "daily" assert expanded_ts.frequency.name == "daily"
assert expanded_ts.iloc[0][1] == expanded_ts.iloc[1][1] assert expanded_ts.iloc[0][1] == expanded_ts.iloc[1][1]
def test_monthly_to_daily_no_weekends(self): def test_monthly_to_daily_no_weekends(self, create_test_data):
ts_data = create_test_data(AllFrequencies.M, 6) ts_data = create_test_data(AllFrequencies.M, 6)
ts = TimeSeries(ts_data, "M") ts = TimeSeries(ts_data, "M")
expanded_ts = ts.expand("D", "ffill", skip_weekends=True) expanded_ts = ts.expand("D", "ffill", skip_weekends=True)
@ -359,7 +271,7 @@ class TestExpand:
assert expanded_ts.frequency.name == "daily" assert expanded_ts.frequency.name == "daily"
assert expanded_ts.iloc[0][1] == expanded_ts.iloc[1][1] assert expanded_ts.iloc[0][1] == expanded_ts.iloc[1][1]
def test_monthly_to_weekly(self): def test_monthly_to_weekly(self, create_test_data):
ts_data = create_test_data(AllFrequencies.M, 6) ts_data = create_test_data(AllFrequencies.M, 6)
ts = TimeSeries(ts_data, "M") ts = TimeSeries(ts_data, "M")
expanded_ts = ts.expand("W", "ffill") expanded_ts = ts.expand("W", "ffill")
@ -367,7 +279,7 @@ class TestExpand:
assert expanded_ts.frequency.name == "weekly" assert expanded_ts.frequency.name == "weekly"
assert expanded_ts.iloc[0][1] == expanded_ts.iloc[1][1] assert expanded_ts.iloc[0][1] == expanded_ts.iloc[1][1]
def test_yearly_to_monthly(self): def test_yearly_to_monthly(self, create_test_data):
ts_data = create_test_data(AllFrequencies.Y, 5) ts_data = create_test_data(AllFrequencies.Y, 5)
ts = TimeSeries(ts_data, "Y") ts = TimeSeries(ts_data, "Y")
expanded_ts = ts.expand("M", "ffill") expanded_ts = ts.expand("M", "ffill")
@ -458,7 +370,7 @@ class TestReturnsAgain:
class TestVolatility: class TestVolatility:
def test_daily_ts(self): def test_daily_ts(self, create_test_data):
ts_data = create_test_data(AllFrequencies.D) ts_data = create_test_data(AllFrequencies.D)
ts = TimeSeries(ts_data, "D") ts = TimeSeries(ts_data, "D")
assert len(ts) == 1000 assert len(ts) == 1000
@ -486,7 +398,7 @@ class TestVolatility:
class TestDrawdown: class TestDrawdown:
def test_daily_ts(self): def test_daily_ts(self, create_test_data):
ts_data = create_test_data(AllFrequencies.D, skip_weekends=True) ts_data = create_test_data(AllFrequencies.D, skip_weekends=True)
ts = TimeSeries(ts_data, "D") ts = TimeSeries(ts_data, "D")
mdd = ts.max_drawdown() mdd = ts.max_drawdown()
@ -500,7 +412,7 @@ class TestDrawdown:
} }
assert mdd == expeced_response assert mdd == expeced_response
def test_weekly_ts(self): def test_weekly_ts(self, create_test_data):
ts_data = create_test_data(AllFrequencies.W, mu=1, sigma=0.5) ts_data = create_test_data(AllFrequencies.W, mu=1, sigma=0.5)
ts = TimeSeries(ts_data, "W") ts = TimeSeries(ts_data, "W")
mdd = ts.max_drawdown() mdd = ts.max_drawdown()
@ -516,7 +428,7 @@ class TestDrawdown:
class TestSync: class TestSync:
def test_weekly_to_daily(self): def test_weekly_to_daily(self, create_test_data):
daily_data = create_test_data(AllFrequencies.D, num=15) daily_data = create_test_data(AllFrequencies.D, num=15)
weekly_data = create_test_data(AllFrequencies.W, num=3) weekly_data = create_test_data(AllFrequencies.W, num=3)