Compare commits

...

6 Commits

Author SHA1 Message Date
=
50c423611d added tests for returns 2022-02-24 23:24:20 +05:30
=
308a4f1abb changed years to any period in return calc 2022-02-24 22:38:53 +05:30
=
0fbca4ae4c modified return calculation to include motnhs and days 2022-02-24 11:28:37 +05:30
=
66ccd2a3f8 added interval_to_years function 2022-02-24 11:28:16 +05:30
=
d9ec9b508b added tests for str & repr 2022-02-24 10:11:58 +05:30
23882b2380 value test for ffill and bfill 2022-02-24 09:18:56 +05:30
4 changed files with 128 additions and 9 deletions

View File

@ -114,6 +114,18 @@ def _parse_date(date: str, date_format: str = None):
return date return date
def _interval_to_years(interval_type: Literal['years', 'months', 'day'], interval_value: int) -> int:
"""Converts any time period to years for use with compounding functions"""
day_conversion_factor = {
'years': 1,
'months': 12,
'days': 365
}
years = interval_value/day_conversion_factor[interval_type]
return years
class _IndexSlicer: class _IndexSlicer:
"""Class to create a slice using iloc in TimeSeriesCore""" """Class to create a slice using iloc in TimeSeriesCore"""

View File

@ -1,11 +1,17 @@
from __future__ import annotations from __future__ import annotations
import datetime import datetime
from typing import List, Union from typing import List, Literal, Union
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from .core import AllFrequencies, TimeSeriesCore, _parse_date, _preprocess_match_options from .core import (
AllFrequencies,
TimeSeriesCore,
_interval_to_years,
_parse_date,
_preprocess_match_options,
)
def create_date_series( def create_date_series(
@ -120,7 +126,8 @@ class TimeSeries(TimeSeriesCore):
prior_match: str = "closest", prior_match: str = "closest",
closest: str = "previous", closest: str = "previous",
compounding: bool = True, compounding: bool = True,
years: int = 1, interval_type: Literal['years', 'months', 'days'] = 'years',
interval_value: int = 1,
date_format: str = None date_format: str = None
) -> float: ) -> float:
"""Method to calculate returns for a certain time-period as on a particular date """Method to calculate returns for a certain time-period as on a particular date
@ -172,7 +179,7 @@ class TimeSeries(TimeSeriesCore):
raise ValueError("As on date not found") raise ValueError("As on date not found")
as_on += as_on_delta as_on += as_on_delta
prev_date = as_on - relativedelta(years=years) prev_date = as_on - relativedelta(**{interval_type: interval_value})
while True: while True:
previous = self.data.get(prev_date, None) previous = self.data.get(prev_date, None)
if previous is not None: if previous is not None:
@ -183,6 +190,7 @@ class TimeSeries(TimeSeriesCore):
returns = current / previous returns = current / previous
if compounding: if compounding:
years = _interval_to_years(interval_type, interval_value)
returns = returns ** (1 / years) returns = returns ** (1 / years)
return returns - 1 return returns - 1
@ -195,7 +203,8 @@ class TimeSeries(TimeSeriesCore):
prior_match: str = "closest", prior_match: str = "closest",
closest: str = "previous", closest: str = "previous",
compounding: bool = True, compounding: bool = True,
years: int = 1, interval_type: Literal['years', 'months', 'days'] = 'years',
interval_value: int = 1,
date_format: str = None date_format: str = None
) -> List[tuple]: ) -> List[tuple]:
"""Calculates the rolling return""" """Calculates the rolling return"""
@ -220,7 +229,8 @@ class TimeSeries(TimeSeriesCore):
returns = self.calculate_returns( returns = self.calculate_returns(
as_on=i, as_on=i,
compounding=compounding, compounding=compounding,
years=years, interval_type=interval_type,
interval_value=interval_value,
as_on_match=as_on_match, as_on_match=as_on_match,
prior_match=prior_match, prior_match=prior_match,
closest=closest, closest=closest,

View File

@ -1,5 +1,6 @@
import datetime import datetime
from typing import Mapping import random
from typing import Literal, Mapping, Sequence
from fincal.core import AllFrequencies, Frequency, Series, TimeSeriesCore from fincal.core import AllFrequencies, Frequency, Series, TimeSeriesCore
from fincal.fincal import create_date_series from fincal.fincal import create_date_series
@ -15,6 +16,48 @@ class TestFrequency:
assert D.freq_type == 'days' assert D.freq_type == 'days'
def create_test_data(
frequency: str,
eomonth: bool,
n: int,
gaps: float,
month_position: Literal["start", "middle", "end"],
date_as_str: bool,
as_outer_type: Literal["dict", "list"] = "list",
as_inner_type: Literal["dict", "list", "tuple"] = "tuple",
) -> Sequence[tuple]:
start_dates = {
"start": datetime.datetime(2016, 1, 1),
"middle": datetime.datetime(2016, 1, 15),
"end": datetime.datetime(2016, 1, 31),
}
end_date = datetime.datetime(2021, 12, 31)
dates = create_date_series(start_dates[month_position], end_date, frequency=frequency, eomonth=eomonth)
dates = dates[:n]
if gaps:
num_gaps = int(len(dates) * gaps)
to_remove = random.sample(dates, num_gaps)
for i in to_remove:
dates.remove(i)
if date_as_str:
dates = [i.strftime("%Y-%m-%d") for i in dates]
values = [random.randint(8000, 90000) / 100 for _ in dates]
data = list(zip(dates, values))
if as_outer_type == "list":
if as_inner_type == "list":
data = [list(i) for i in data]
elif as_inner_type == "dict[1]":
data = [dict((i,)) for i in data]
elif as_inner_type == "dict[2]":
data = [dict(date=i, value=j) for i, j in data]
elif as_outer_type == "dict":
data = dict(data)
return data
class TestAllFrequencies: class TestAllFrequencies:
def test_attributes(self): def test_attributes(self):
assert hasattr(AllFrequencies, 'D') assert hasattr(AllFrequencies, 'D')
@ -53,6 +96,15 @@ class TestSeries:
class TestTimeSeriesCore: class TestTimeSeriesCore:
data = [('2021-01-01', 220), ('2021-02-01', 230), ('2021-03-01', 240)] data = [('2021-01-01', 220), ('2021-02-01', 230), ('2021-03-01', 240)]
def test_repr_str(self):
ts = TimeSeriesCore(self.data, frequency='M')
assert str(ts) in repr(ts).replace('\t', ' ')
data = create_test_data(frequency="D", eomonth=False, n=50, gaps=0, month_position="start", date_as_str=True)
ts = TimeSeriesCore(data, frequency="D")
assert '...' in str(ts)
assert '...' in repr(ts)
def test_creation(self): def test_creation(self):
ts = TimeSeriesCore(self.data, frequency='M') ts = TimeSeriesCore(self.data, frequency='M')
assert isinstance(ts, TimeSeriesCore) assert isinstance(ts, TimeSeriesCore)

View File

@ -115,7 +115,7 @@ class TestDateSeries:
assert datetime.datetime(2020, 11, 30) in d assert datetime.datetime(2020, 11, 30) in d
class TestFincal: class TestFincalBasic:
def test_creation(self): def test_creation(self):
data = create_test_data(frequency="D", eomonth=False, n=50, gaps=0, month_position="start", date_as_str=True) data = create_test_data(frequency="D", eomonth=False, n=50, gaps=0, month_position="start", date_as_str=True)
time_series = TimeSeries(data, frequency="D") time_series = TimeSeries(data, frequency="D")
@ -130,7 +130,7 @@ class TestFincal:
time_series = TimeSeries(data, frequency="D") time_series = TimeSeries(data, frequency="D")
assert len(time_series) == 450 assert len(time_series) == 450
def test_ffill(self): def test_fill(self):
data = create_test_data(frequency="D", eomonth=False, n=500, gaps=0.1, month_position="start", date_as_str=True) data = create_test_data(frequency="D", eomonth=False, n=500, gaps=0.1, month_position="start", date_as_str=True)
time_series = TimeSeries(data, frequency="D") time_series = TimeSeries(data, frequency="D")
ffill_data = time_series.ffill() ffill_data = time_series.ffill()
@ -140,6 +140,23 @@ class TestFincal:
assert ffill_data is None assert ffill_data is None
assert len(time_series) >= 498 assert len(time_series) >= 498
data = create_test_data(frequency="D", eomonth=False, n=500, gaps=0.1, month_position="start", date_as_str=True)
time_series = TimeSeries(data, frequency="D")
bfill_data = time_series.bfill()
assert len(bfill_data) >= 498
bfill_data = time_series.bfill(inplace=True)
assert bfill_data is None
assert len(time_series) >= 498
data = [("2021-01-01", 220), ("2021-01-02", 230), ("2021-03-04", 240)]
ts = TimeSeries(data, frequency="D")
ff = ts.ffill()
assert ff["2021-01-03"][1] == 230
bf = ts.bfill()
assert bf["2021-01-03"][1] == 240
def test_iloc_slicing(self): def test_iloc_slicing(self):
data = create_test_data(frequency="D", eomonth=False, n=50, gaps=0, month_position="start", date_as_str=True) data = create_test_data(frequency="D", eomonth=False, n=50, gaps=0, month_position="start", date_as_str=True)
time_series = TimeSeries(data, frequency="D") time_series = TimeSeries(data, frequency="D")
@ -159,3 +176,31 @@ class TestFincal:
assert isinstance(time_series["values"], Series) assert isinstance(time_series["values"], Series)
assert len(time_series.dates) == 50 assert len(time_series.dates) == 50
assert len(time_series.values) == 50 assert len(time_series.values) == 50
def test_returns_calc(self):
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)
]
ts = TimeSeries(data, frequency='M')
returns = ts.calculate_returns("2021-01-01", compounding=False, interval_type='years', interval_value=1)
assert returns == 2.4
returns = ts.calculate_returns("2020-04-01", compounding=False, interval_type='months', interval_value=3)
assert round(returns, 4) == 0.6
returns = ts.calculate_returns("2020-04-01", compounding=True, interval_type='months', interval_value=3)
assert round(returns, 4) == 5.5536
returns = ts.calculate_returns("2020-04-01", compounding=False, interval_type='days', interval_value=90)
assert round(returns, 4) == 0.6
returns = ts.calculate_returns("2020-04-01", compounding=True, interval_type='days', interval_value=90)
assert round(returns, 4) == 5.727