Compare commits

..

No commits in common. "17b3e348a241b9279b3b56ddef92e67df952d5f8" and "60f25de710fbaed222873ad076c88fdf44456831" have entirely different histories.

4 changed files with 49 additions and 73 deletions

View File

@ -18,25 +18,6 @@ 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)

View File

@ -6,10 +6,14 @@ 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 _find_closest_date, _interval_to_years, _preprocess_match_options from .utils import (
_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],
@ -51,8 +55,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 = []
@ -257,11 +261,11 @@ 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})
current = _find_closest_date(self.data, as_on, closest_max_days, as_on_delta, if_not_found) current = _find_closest_date(self.data, as_on, closest_max_days, as_on_delta, if_not_found)
if current[1] != str("nan"):
previous = _find_closest_date(self.data, prev_date, closest_max_days, prior_delta, if_not_found) previous = _find_closest_date(self.data, prev_date, closest_max_days, prior_delta, if_not_found)
if current[1] == str("nan") or previous[1] == str("nan"): if current[1] == str("nan") or previous[1] == str("nan"):
@ -354,6 +358,9 @@ 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:

View File

@ -85,19 +85,13 @@ 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( def _find_closest_date(data, date, limit_days, delta, if_not_found):
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:

View File

@ -182,60 +182,54 @@ 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):
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)