improved documentation and usage of date_parser decorator
This commit is contained in:
parent
32e4f25f59
commit
17b3e348a2
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user