improved documentation and usage of date_parser decorator

This commit is contained in:
Gourav Kumar 2022-03-05 23:23:31 +05:30
parent 32e4f25f59
commit 17b3e348a2
4 changed files with 68 additions and 50 deletions

View File

@ -18,6 +18,25 @@ class Frequency:
def date_parser(*pos):
"""Decorator to parse dates in any function
Accepts the 0-indexed position of the parameter for which date parsing needs to be done.
Works even if function is used with keyword arguments while not maintaining parameter order.
Example:
--------
>>> @date_parser(2, 3)
>>> def calculate_difference(diff_units='days', return_type='int', date1, date2):
... diff = date2 - date1
... if return_type == 'int':
... return diff.days
... return diff
...
>>> calculate_difference(date1='2019-01-01'm date2='2020-01-01')
datetime.timedelta(365)
Each of the dates is automatically parsed into a datetime.datetime object from string.
"""
def parse_dates(func):
def wrapper_func(*args, **kwargs):
date_format = kwargs.get("date_format", None)

View File

@ -6,14 +6,10 @@ from typing import Iterable, List, Literal, Mapping, Union
from dateutil.relativedelta import relativedelta
from .core import AllFrequencies, TimeSeriesCore, date_parser
from .utils import (
_find_closest_date,
_interval_to_years,
_parse_date,
_preprocess_match_options,
)
from .utils import _find_closest_date, _interval_to_years, _preprocess_match_options
@date_parser(0, 1)
def create_date_series(
start_date: Union[str, datetime.datetime],
end_date: Union[str, datetime.datetime],
@ -55,8 +51,8 @@ def create_date_series(
if eomonth and frequency.days < AllFrequencies.M.days:
raise ValueError(f"eomonth cannot be set to True if frequency is higher than {AllFrequencies.M.name}")
start_date = _parse_date(start_date)
end_date = _parse_date(end_date)
# start_date = _parse_date(start_date)
# end_date = _parse_date(end_date)
datediff = (end_date - start_date).days / frequency.days + 1
dates = []
@ -261,7 +257,6 @@ class TimeSeries(TimeSeriesCore):
(datetime.datetime(2020, 1, 1, 0, 0), .0567)
"""
# as_on = _parse_date(as_on, date_format)
as_on_delta, prior_delta = _preprocess_match_options(as_on_match, prior_match, closest)
prev_date = as_on - relativedelta(**{interval_type: interval_value})
@ -359,9 +354,6 @@ class TimeSeries(TimeSeriesCore):
TimeSeries.calculate_returns
"""
# from_date = _parse_date(from_date, date_format)
# to_date = _parse_date(to_date, date_format)
if frequency is None:
frequency = self.frequency
else:

View File

@ -85,20 +85,26 @@ def _preprocess_match_options(as_on_match: str, prior_match: str, closest: str)
return as_on_delta, prior_delta
def _find_closest_date(data, date, limit_days, delta, if_not_found):
def _find_closest_date(
data: Mapping[datetime.datetime, float],
date: datetime.datetime,
limit_days: int,
delta: datetime.timedelta,
if_not_found: Literal["fail", "nan"],
):
"""Helper function to find data for the closest available date"""
if delta.days < 0 and date < min(data):
raise DateOutOfRangeError(date, 'min')
raise DateOutOfRangeError(date, "min")
if delta.days > 0 and date > max(data):
raise DateOutOfRangeError(date, 'max')
raise DateOutOfRangeError(date, "max")
row = data.get(date, None)
if row is not None:
return date, row
if delta and limit_days != 0:
return _find_closest_date(data, date + delta, limit_days-1, delta, if_not_found)
return _find_closest_date(data, date + delta, limit_days - 1, delta, if_not_found)
if if_not_found == "fail":
raise DateNotFoundError("Data not found for date", date)

View File

@ -182,59 +182,60 @@ class TestFincalBasic:
class TestReturns:
data = [
('2020-01-01', 10),
('2020-02-01', 12),
('2020-03-01', 14),
('2020-04-01', 16),
('2020-05-01', 18),
('2020-06-01', 20),
('2020-07-01', 22),
('2020-08-01', 24),
('2020-09-01', 26),
('2020-10-01', 28),
('2020-11-01', 30),
('2020-12-01', 32),
('2021-01-01', 34)
("2020-01-01", 10),
("2020-02-01", 12),
("2020-03-01", 14),
("2020-04-01", 16),
("2020-05-01", 18),
("2020-06-01", 20),
("2020-07-01", 22),
("2020-08-01", 24),
("2020-09-01", 26),
("2020-10-01", 28),
("2020-11-01", 30),
("2020-12-01", 32),
("2021-01-01", 34),
]
def test_returns_calc(self):
ts = TimeSeries(self.data, frequency='M')
returns = ts.calculate_returns("2021-01-01", compounding=False, interval_type='years', interval_value=1)
ts = TimeSeries(self.data, frequency="M")
returns = ts.calculate_returns("2021-01-01", compounding=False, interval_type="years", interval_value=1)
assert returns[1] == 2.4
returns = ts.calculate_returns("2020-04-01", compounding=False, interval_type='months', interval_value=3)
returns = ts.calculate_returns("2020-04-01", compounding=False, interval_type="months", interval_value=3)
assert round(returns[1], 4) == 0.6
returns = ts.calculate_returns("2020-04-01", compounding=True, interval_type='months', interval_value=3)
returns = ts.calculate_returns("2020-04-01", compounding=True, interval_type="months", interval_value=3)
assert round(returns[1], 4) == 5.5536
returns = ts.calculate_returns("2020-04-01", compounding=False, interval_type='days', interval_value=90)
returns = ts.calculate_returns("2020-04-01", compounding=False, interval_type="days", interval_value=90)
assert round(returns[1], 4) == 0.6
returns = ts.calculate_returns("2020-04-01", compounding=True, interval_type='days', interval_value=90)
returns = ts.calculate_returns("2020-04-01", compounding=True, interval_type="days", interval_value=90)
assert round(returns[1], 4) == 5.727
returns = ts.calculate_returns("2020-04-10", compounding=True, interval_type='days', interval_value=90)
returns = ts.calculate_returns("2020-04-10", compounding=True, interval_type="days", interval_value=90)
assert round(returns[1], 4) == 5.727
with pytest.raises(DateNotFoundError):
ts.calculate_returns("2020-04-10", interval_type='days', interval_value=90, as_on_match='exact')
ts.calculate_returns("2020-04-10", interval_type="days", interval_value=90, as_on_match="exact")
with pytest.raises(DateNotFoundError):
ts.calculate_returns("2020-04-10", interval_type='days', interval_value=90, prior_match='exact')
ts.calculate_returns("2020-04-10", interval_type="days", interval_value=90, prior_match="exact")
def test_date_formats(self):
ts = TimeSeries(self.data, frequency='M')
FincalOptions.date_format = '%d-%m-%Y'
ts = TimeSeries(self.data, frequency="M")
FincalOptions.date_format = "%d-%m-%Y"
with pytest.raises(ValueError):
ts.calculate_returns("2020-04-10", compounding=True, interval_type='days', interval_value=90)
ts.calculate_returns("2020-04-10", compounding=True, interval_type="days", interval_value=90)
returns1 = ts.calculate_returns("2020-04-10", interval_type='days', interval_value=90, date_format='%Y-%m-%d')
returns2 = ts.calculate_returns("10-04-2020", interval_type='days', interval_value=90)
returns1 = ts.calculate_returns("2020-04-10", interval_type="days", interval_value=90, date_format="%Y-%m-%d")
returns2 = ts.calculate_returns("10-04-2020", interval_type="days", interval_value=90)
assert round(returns1[1], 4) == round(returns2[1], 4) == 5.727
FincalOptions.date_format = '%m-%d-%Y'
FincalOptions.date_format = "%m-%d-%Y"
with pytest.raises(ValueError):
ts.calculate_returns("2020-04-10", compounding=True, interval_type='days', interval_value=90)
ts.calculate_returns("2020-04-10", compounding=True, interval_type="days", interval_value=90)
returns1 = ts.calculate_returns("2020-04-10", interval_type='days', interval_value=90, date_format='%Y-%m-%d')
returns2 = ts.calculate_returns("04-10-2020", interval_type='days', interval_value=90)
returns1 = ts.calculate_returns("2020-04-10", interval_type="days", interval_value=90, date_format="%Y-%m-%d")
returns2 = ts.calculate_returns("04-10-2020", interval_type="days", interval_value=90)
assert round(returns1[1], 4) == round(returns2[1], 4) == 5.727
def test_limits(self):
ts = TimeSeries(self.data, frequency='M')
ts = TimeSeries(self.data, frequency="M")
FincalOptions.date_format = "%Y-%m-%d"
with pytest.raises(DateNotFoundError):
ts.calculate_returns("2020-04-25", interval_type='days', interval_value=90, closest_max_days=10)
ts.calculate_returns("2020-04-25", interval_type="days", interval_value=90, closest_max_days=10)