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):
|
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 parse_dates(func):
|
||||||
def wrapper_func(*args, **kwargs):
|
def wrapper_func(*args, **kwargs):
|
||||||
date_format = kwargs.get("date_format", None)
|
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 dateutil.relativedelta import relativedelta
|
||||||
|
|
||||||
from .core import AllFrequencies, TimeSeriesCore, date_parser
|
from .core import AllFrequencies, TimeSeriesCore, date_parser
|
||||||
from .utils import (
|
from .utils import _find_closest_date, _interval_to_years, _preprocess_match_options
|
||||||
_find_closest_date,
|
|
||||||
_interval_to_years,
|
|
||||||
_parse_date,
|
|
||||||
_preprocess_match_options,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
@date_parser(0, 1)
|
||||||
def create_date_series(
|
def create_date_series(
|
||||||
start_date: Union[str, datetime.datetime],
|
start_date: Union[str, datetime.datetime],
|
||||||
end_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:
|
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}")
|
raise ValueError(f"eomonth cannot be set to True if frequency is higher than {AllFrequencies.M.name}")
|
||||||
|
|
||||||
start_date = _parse_date(start_date)
|
# start_date = _parse_date(start_date)
|
||||||
end_date = _parse_date(end_date)
|
# end_date = _parse_date(end_date)
|
||||||
datediff = (end_date - start_date).days / frequency.days + 1
|
datediff = (end_date - start_date).days / frequency.days + 1
|
||||||
dates = []
|
dates = []
|
||||||
|
|
||||||
@ -261,7 +257,6 @@ class TimeSeries(TimeSeriesCore):
|
|||||||
(datetime.datetime(2020, 1, 1, 0, 0), .0567)
|
(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)
|
as_on_delta, prior_delta = _preprocess_match_options(as_on_match, prior_match, closest)
|
||||||
|
|
||||||
prev_date = as_on - relativedelta(**{interval_type: interval_value})
|
prev_date = as_on - relativedelta(**{interval_type: interval_value})
|
||||||
@ -359,9 +354,6 @@ class TimeSeries(TimeSeriesCore):
|
|||||||
TimeSeries.calculate_returns
|
TimeSeries.calculate_returns
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# from_date = _parse_date(from_date, date_format)
|
|
||||||
# to_date = _parse_date(to_date, date_format)
|
|
||||||
|
|
||||||
if frequency is None:
|
if frequency is None:
|
||||||
frequency = self.frequency
|
frequency = self.frequency
|
||||||
else:
|
else:
|
||||||
|
@ -85,20 +85,26 @@ def _preprocess_match_options(as_on_match: str, prior_match: str, closest: str)
|
|||||||
return as_on_delta, prior_delta
|
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"""
|
"""Helper function to find data for the closest available date"""
|
||||||
|
|
||||||
if delta.days < 0 and date < min(data):
|
if delta.days < 0 and date < min(data):
|
||||||
raise DateOutOfRangeError(date, 'min')
|
raise DateOutOfRangeError(date, "min")
|
||||||
if delta.days > 0 and date > max(data):
|
if delta.days > 0 and date > max(data):
|
||||||
raise DateOutOfRangeError(date, 'max')
|
raise DateOutOfRangeError(date, "max")
|
||||||
|
|
||||||
row = data.get(date, None)
|
row = data.get(date, None)
|
||||||
if row is not None:
|
if row is not None:
|
||||||
return date, row
|
return date, row
|
||||||
|
|
||||||
if delta and limit_days != 0:
|
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":
|
if if_not_found == "fail":
|
||||||
raise DateNotFoundError("Data not found for date", date)
|
raise DateNotFoundError("Data not found for date", date)
|
||||||
|
@ -182,59 +182,60 @@ class TestFincalBasic:
|
|||||||
|
|
||||||
class TestReturns:
|
class TestReturns:
|
||||||
data = [
|
data = [
|
||||||
('2020-01-01', 10),
|
("2020-01-01", 10),
|
||||||
('2020-02-01', 12),
|
("2020-02-01", 12),
|
||||||
('2020-03-01', 14),
|
("2020-03-01", 14),
|
||||||
('2020-04-01', 16),
|
("2020-04-01", 16),
|
||||||
('2020-05-01', 18),
|
("2020-05-01", 18),
|
||||||
('2020-06-01', 20),
|
("2020-06-01", 20),
|
||||||
('2020-07-01', 22),
|
("2020-07-01", 22),
|
||||||
('2020-08-01', 24),
|
("2020-08-01", 24),
|
||||||
('2020-09-01', 26),
|
("2020-09-01", 26),
|
||||||
('2020-10-01', 28),
|
("2020-10-01", 28),
|
||||||
('2020-11-01', 30),
|
("2020-11-01", 30),
|
||||||
('2020-12-01', 32),
|
("2020-12-01", 32),
|
||||||
('2021-01-01', 34)
|
("2021-01-01", 34),
|
||||||
]
|
]
|
||||||
|
|
||||||
def test_returns_calc(self):
|
def test_returns_calc(self):
|
||||||
ts = TimeSeries(self.data, frequency='M')
|
ts = TimeSeries(self.data, frequency="M")
|
||||||
returns = ts.calculate_returns("2021-01-01", compounding=False, interval_type='years', interval_value=1)
|
returns = ts.calculate_returns("2021-01-01", compounding=False, interval_type="years", interval_value=1)
|
||||||
assert returns[1] == 2.4
|
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
|
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
|
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
|
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
|
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
|
assert round(returns[1], 4) == 5.727
|
||||||
with pytest.raises(DateNotFoundError):
|
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):
|
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):
|
def test_date_formats(self):
|
||||||
ts = TimeSeries(self.data, frequency='M')
|
ts = TimeSeries(self.data, frequency="M")
|
||||||
FincalOptions.date_format = '%d-%m-%Y'
|
FincalOptions.date_format = "%d-%m-%Y"
|
||||||
with pytest.raises(ValueError):
|
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')
|
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)
|
returns2 = ts.calculate_returns("10-04-2020", interval_type="days", interval_value=90)
|
||||||
assert round(returns1[1], 4) == round(returns2[1], 4) == 5.727
|
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):
|
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')
|
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)
|
returns2 = ts.calculate_returns("04-10-2020", interval_type="days", interval_value=90)
|
||||||
assert round(returns1[1], 4) == round(returns2[1], 4) == 5.727
|
assert round(returns1[1], 4) == round(returns2[1], 4) == 5.727
|
||||||
|
|
||||||
def test_limits(self):
|
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):
|
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