Compare commits
6 Commits
3bf4ca9c61
...
50c423611d
Author | SHA1 | Date | |
---|---|---|---|
|
50c423611d | ||
|
308a4f1abb | ||
|
0fbca4ae4c | ||
|
66ccd2a3f8 | ||
|
d9ec9b508b | ||
23882b2380 |
@ -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"""
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user