Compare commits

..

10 Commits

Author SHA1 Message Date
38fb9ca7d0 tests fro transform method 2022-05-24 21:11:46 +05:30
0a113fdd8a completed transform function 2022-05-24 21:11:34 +05:30
9a71cdf355 transform testing 2022-05-24 21:11:15 +05:30
66ad448516 Added proper support for & in Series 2022-05-24 12:50:05 +05:30
49cebecb88 __and__, __or__ in Series 2022-05-22 17:39:42 +05:30
da0bfcbcb1 Merge branch 'master' of http://192.168.0.114:3000/buddy/fincal 2022-05-22 14:37:04 +05:30
cad069d351 test files 2022-05-22 14:35:07 +05:30
130f4e58e9 started working on transform with aggregation 2022-05-18 21:39:57 -07:00
2ca6167c8b fixed issue with create_date_series
Caused year to go -1 because it was not getting replaced
2022-05-16 22:30:08 +05:30
95e9bfd51c More Sharpe tests 2022-05-16 22:29:18 +05:30
6 changed files with 583 additions and 132 deletions

View File

@ -7,6 +7,7 @@ from collections import UserList
from dataclasses import dataclass
from numbers import Number
from typing import Any, Callable, Iterable, List, Literal, Mapping, Sequence, Type
from unittest import skip
from dateutil.relativedelta import relativedelta
@ -156,14 +157,14 @@ class Series(UserList):
else:
return self.data[i]
def _comparison_validator(self, other):
def _comparison_validator(self, other, skip_bool: bool = False):
"""Validates other before making comparison"""
if isinstance(other, (str, datetime.datetime, datetime.date)):
other = _parse_date(other)
return other
if self.dtype == bool:
if self.dtype == bool and not skip_bool:
raise TypeError("Comparison operation not supported for boolean series")
elif isinstance(other, Series):
@ -221,7 +222,23 @@ class Series(UserList):
if isinstance(other, Series):
return Series([j != other[i] for i, j in enumerate(self)], "bool")
return Series([i == other for i in self.data], "bool")
return Series([i != other for i in self.data], "bool")
def __and__(self, other):
other = self._comparison_validator(other, skip_bool=True)
if isinstance(other, Series):
return Series([j and other[i] for i, j in enumerate(self)], "bool")
return Series([i and other for i in self.data], "bool")
def __or__(self, other):
other = self._comparison_validator(other, skip_bool=True)
if isinstance(other, Series):
return Series([j or other[i] for i, j in enumerate(self)], "bool")
return Series([i or other for i in self.data], "bool")
def _math_validator(self, other):

View File

@ -81,7 +81,7 @@ def create_date_series(
extend_by_days = 7 - end_date.weekday()
end_date += relativedelta(days=extend_by_days)
# To-do: Add code to ensure coverage for other frequencies as well
# TODO: Add code to ensure coverage for other frequencies as well
datediff = (end_date - start_date).days / frequency.days + 1
dates = []
@ -91,8 +91,8 @@ def create_date_series(
date = start_date + relativedelta(**diff)
if eomonth:
next_month = 1 if date.month == 12 else date.month + 1
date = date.replace(day=1).replace(month=next_month) - relativedelta(days=1)
replacement = {"month": date.month + 1} if date.month < 12 else {"year": date.year + 1}
date = date.replace(day=1).replace(**replacement) - relativedelta(days=1)
if date <= end_date:
if frequency.days > 1 or not skip_weekends:
@ -727,7 +727,10 @@ class TimeSeries(TimeSeriesCore):
)
closest: str = "previous" if method == "ffill" else "next"
new_ts: dict = {dt: self.get(dt, closest=closest)[1] for dt in new_dates}
new_ts = {}
for dt in new_dates:
new_ts.update({dt: self.get(dt, closest=closest)[1]})
# new_ts: dict = {dt: self.get(dt, closest=closest)[1] for dt in new_dates}
output_ts: TimeSeries = TimeSeries(new_ts, frequency=to_frequency.symbol)
return output_ts
@ -780,6 +783,67 @@ class TimeSeries(TimeSeriesCore):
return statistics.mean(self.values)
def transform(
self, to_frequency: Literal["W", "M", "Q", "H", "Y"], method: Literal["sum", "mean"], eomonth: bool = False
) -> TimeSeries:
"""Transform a time series object into a lower frequency object with an aggregation function.
Parameters
----------
to_frequency:
Frequency to which the time series needs to be transformed
method:
Aggregation method to be used. Can be either mean or sum
eomonth:
User end of month dates. Only applicable for frequencies monthly and lower.
Returns
-------
Returns a TimeSeries object
Raises
-------
ValueError:
* If invalid input is passed for frequency
* if invalid input is passed for method
* If to_frequency is higher than the current frequency
"""
try:
to_frequency: Frequency = getattr(AllFrequencies, to_frequency)
except AttributeError:
raise ValueError(f"Invalid argument for to_frequency {to_frequency}")
if to_frequency.days <= self.frequency.days:
raise ValueError("TimeSeries can be only shrunk to a lower frequency")
if method not in ["sum", "mean"]:
raise ValueError(f"Method not recognised: {method}")
dates = create_date_series(
self.start_date,
self.end_date
+ datetime.timedelta(to_frequency.days), # need extra date at the end for calculation of last value
to_frequency.symbol,
ensure_coverage=True,
)
prev_date = dates[0]
new_ts_dict = {}
for date in dates[1:]:
cur_data = self[(self.dates >= prev_date) & (self.dates < date)]
if method == "sum":
value = sum(cur_data.values)
elif method == "mean":
value = cur_data.mean()
new_ts_dict.update({prev_date: value})
prev_date = date
return self.__class__(new_ts_dict, to_frequency.symbol)
def _preprocess_csv(file_path: str | pathlib.Path, delimiter: str = ",", encoding: str = "utf-8") -> List[list]:
"""Preprocess csv data"""

Binary file not shown.

View File

@ -2,38 +2,21 @@
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"id": "e1ecfa55",
"metadata": {},
"execution_count": 2,
"id": "e40a5526-458a-4d11-8eaa-3b584f723738",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"import fincal as fc"
"import fincal as fc\n",
"import datetime\n",
"from dateutil.relativedelta import relativedelta"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "ccac3896",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"fincal.fincal.TimeSeries"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"fc.TimeSeries"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "a54bfbdf",
"metadata": {},
"outputs": [],
@ -41,8 +24,8 @@
"data = [\n",
" (\"2022-01-01\", 10),\n",
" (\"2022-01-02\", 12),\n",
" (\"2022-01-03\", 14)\n",
" # (\"2022-01-04\", 16),\n",
" (\"2022-01-03\", 14),\n",
" (\"2022-01-04\", 16)\n",
" # (\"2022-01-06\", 18),\n",
" # (\"2022-01-07\", 20),\n",
" # (\"2022-01-09\", 22),\n",
@ -57,52 +40,70 @@
},
{
"cell_type": "code",
"execution_count": 5,
"execution_count": 3,
"id": "fcc5f8f1",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"TimeSeries([(datetime.datetime(2022, 1, 1, 0, 0), 10),\n",
"\t(datetime.datetime(2022, 1, 2, 0, 0), 12),\n",
"\t(datetime.datetime(2022, 1, 3, 0, 0), 14)], frequency='M')"
"TimeSeries([(datetime.datetime(2022, 1, 1, 0, 0), 10.0),\n",
"\t(datetime.datetime(2022, 1, 2, 0, 0), 12.0),\n",
"\t(datetime.datetime(2022, 1, 3, 0, 0), 14.0),\n",
"\t(datetime.datetime(2022, 1, 4, 0, 0), 16.0)], frequency='D')"
]
},
"execution_count": 5,
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"ts = fc.TimeSeries(data, 'M')\n",
"ts = fc.TimeSeries(data, 'D')\n",
"ts2 = fc.TimeSeries(data, 'D')\n",
"ts"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "c9e9cb1b",
"execution_count": 21,
"id": "c091da16-d3a2-4d5b-93da-099d67373932",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"TimeSeries([(datetime.datetime(2022, 1, 1, 0, 0), 10),\n",
"\t(datetime.datetime(2022, 1, 2, 0, 0), 12),\n",
"\t(datetime.datetime(2022, 1, 3, 0, 0), 14),\n",
"\t(datetime.datetime(2022, 1, 4, 0, 0), 15),\n",
"\t(datetime.datetime(2022, 1, 5, 0, 0), 16)], frequency='M')"
"Series([datetime.datetime(2021, 1, 1, 0, 0), datetime.datetime(2021, 1, 2, 0, 0)], data_type='datetime')"
]
},
"execution_count": 7,
"execution_count": 21,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"ts['2022-01-04'] = 15\n",
"ts"
"fc.Series(['2021-01-01', '2021-01-02'], data_type='date')"
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "77fc30d8-2843-40c4-9842-d943e6ef9813",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Series([11.0, 14.0, 17.0, 20.0], data_type='float')"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"ts.values + fc.Series([1, 2, 3, 4])"
]
},
{
@ -112,20 +113,16 @@
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"TimeSeries([(datetime.datetime(2022, 1, 1, 0, 0), 10),\n",
"\t (datetime.datetime(2022, 1, 8, 0, 0), 20),\n",
"\t (datetime.datetime(2022, 1, 15, 0, 0), 28)\n",
"\t ...\n",
"\t (datetime.datetime(2022, 12, 17, 0, 0), 28),\n",
"\t (datetime.datetime(2022, 12, 24, 0, 0), 28),\n",
"\t (datetime.datetime(2022, 12, 31, 0, 0), 28)], frequency='W')"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
"ename": "ValueError",
"evalue": "TimeSeries can be only expanded to a higher frequency",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)",
"Input \u001b[0;32mIn [8]\u001b[0m, in \u001b[0;36m<cell line: 1>\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mts\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mexpand\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[38;5;124;43mW\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[38;5;124;43mffill\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[43m)\u001b[49m\n",
"File \u001b[0;32m~/Documents/projects/fincal/fincal/fincal.py:624\u001b[0m, in \u001b[0;36mTimeSeries.expand\u001b[0;34m(self, to_frequency, method, skip_weekends, eomonth)\u001b[0m\n\u001b[1;32m 621\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mInvalid argument for to_frequency \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mto_frequency\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 623\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m to_frequency\u001b[38;5;241m.\u001b[39mdays \u001b[38;5;241m>\u001b[39m\u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mfrequency\u001b[38;5;241m.\u001b[39mdays:\n\u001b[0;32m--> 624\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mTimeSeries can be only expanded to a higher frequency\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 626\u001b[0m new_dates \u001b[38;5;241m=\u001b[39m create_date_series(\n\u001b[1;32m 627\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mstart_date,\n\u001b[1;32m 628\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mend_date,\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 632\u001b[0m ensure_coverage\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m,\n\u001b[1;32m 633\u001b[0m )\n\u001b[1;32m 635\u001b[0m closest: \u001b[38;5;28mstr\u001b[39m \u001b[38;5;241m=\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mprevious\u001b[39m\u001b[38;5;124m\"\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m method \u001b[38;5;241m==\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mffill\u001b[39m\u001b[38;5;124m\"\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mnext\u001b[39m\u001b[38;5;124m\"\u001b[39m\n",
"\u001b[0;31mValueError\u001b[0m: TimeSeries can be only expanded to a higher frequency"
]
}
],
"source": [
@ -159,48 +156,72 @@
},
{
"cell_type": "code",
"execution_count": 13,
"id": "36eefec7-7dbf-4a28-ac50-2e502d9d6864",
"execution_count": 2,
"id": "9431eb8c",
"metadata": {},
"outputs": [],
"source": [
"weekly_data = [('2017-01-01', 67),\n",
"('2017-01-08', 79),\n",
"('2017-01-15', 73),\n",
"('2017-01-22', 63),\n",
"('2017-01-29', 85),\n",
"('2017-02-05', 66),\n",
"('2017-02-12', 78),\n",
"('2017-02-19', 75),\n",
"('2017-02-26', 76),\n",
"('2017-03-05', 82),\n",
"('2017-03-12', 85),\n",
"('2017-03-19', 63),\n",
"('2017-03-26', 78),\n",
"('2017-04-02', 65),\n",
"('2017-04-09', 85),\n",
"('2017-04-16', 86),\n",
"('2017-04-23', 67),\n",
"('2017-04-30', 65),\n",
"('2017-05-07', 82),\n",
"('2017-05-14', 73),\n",
"('2017-05-21', 78),\n",
"('2017-05-28', 74),\n",
"('2017-06-04', 62),\n",
"('2017-06-11', 84),\n",
"('2017-06-18', 83)]"
"from fincal.utils import _is_eomonth"
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "39bd8598-ab0f-4c81-8428-ad8248e686d3",
"execution_count": 5,
"id": "36eefec7-7dbf-4a28-ac50-2e502d9d6864",
"metadata": {},
"outputs": [],
"source": [
"weekly_data = [\n",
" ('2018-01-31', 26),\n",
" ('2018-02-28', 44),\n",
" ('2018-03-30', 40),\n",
" ('2018-04-30', 36),\n",
" ('2018-05-31', 31),\n",
" ('2018-06-30', 45),\n",
" ('2018-07-30', 31),\n",
" ('2018-08-31', 42),\n",
" ('2018-09-30', 40),\n",
" ('2018-10-30', 30),\n",
" ('2018-11-30', 35),\n",
" ('2018-12-31', 37),\n",
" ('2019-01-31', 31),\n",
" ('2019-02-28', 44),\n",
" ('2019-03-31', 31),\n",
" ('2019-04-29', 32),\n",
" ('2019-05-30', 39),\n",
" ('2019-06-30', 27),\n",
" ('2019-07-31', 35),\n",
" ('2019-08-31', 33),\n",
" ('2019-09-30', 29),\n",
" ('2019-10-30', 26),\n",
" ('2019-11-30', 39),\n",
" ('2019-12-30', 30),\n",
" ('2020-01-30', 29)\n",
"]\n",
"week_ts = fc.TimeSeries(weekly_data, 'W')"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "e1071f90",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"False"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"_is_eomonth(week_ts.dates)"
]
},
{
"cell_type": "code",
"execution_count": 22,
@ -227,31 +248,10 @@
},
{
"cell_type": "code",
"execution_count": 23,
"execution_count": null,
"id": "a549c5c0-c89a-4cc3-b396-c4afa77a9879",
"metadata": {},
"outputs": [
{
"ename": "OverflowError",
"evalue": "date value out of range",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mKeyError\u001b[0m Traceback (most recent call last)",
"File \u001b[0;32m~/Documents/projects/fincal/fincal/core.py:405\u001b[0m, in \u001b[0;36mTimeSeriesCore.get\u001b[0;34m(self, date, default, closest)\u001b[0m\n\u001b[1;32m 404\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m--> 405\u001b[0m item \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_get_item_from_date\u001b[49m\u001b[43m(\u001b[49m\u001b[43mdate\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 406\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m item\n",
"File \u001b[0;32m~/Documents/projects/fincal/fincal/core.py:69\u001b[0m, in \u001b[0;36mdate_parser.<locals>.parse_dates.<locals>.wrapper_func\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m 68\u001b[0m args[j] \u001b[38;5;241m=\u001b[39m parsed_date\n\u001b[0;32m---> 69\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n",
"File \u001b[0;32m~/Documents/projects/fincal/fincal/core.py:328\u001b[0m, in \u001b[0;36mTimeSeriesCore._get_item_from_date\u001b[0;34m(self, date)\u001b[0m\n\u001b[1;32m 326\u001b[0m \u001b[38;5;129m@date_parser\u001b[39m(\u001b[38;5;241m1\u001b[39m)\n\u001b[1;32m 327\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m_get_item_from_date\u001b[39m(\u001b[38;5;28mself\u001b[39m, date: \u001b[38;5;28mstr\u001b[39m \u001b[38;5;241m|\u001b[39m datetime\u001b[38;5;241m.\u001b[39mdatetime):\n\u001b[0;32m--> 328\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m date, \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mdata\u001b[49m\u001b[43m[\u001b[49m\u001b[43mdate\u001b[49m\u001b[43m]\u001b[49m\n",
"\u001b[0;31mKeyError\u001b[0m: datetime.datetime(1, 1, 1, 0, 0)",
"\nDuring handling of the above exception, another exception occurred:\n",
"\u001b[0;31mOverflowError\u001b[0m Traceback (most recent call last)",
"Input \u001b[0;32mIn [23]\u001b[0m, in \u001b[0;36m<cell line: 1>\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mweek_ts\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43msync\u001b[49m\u001b[43m(\u001b[49m\u001b[43mts\u001b[49m\u001b[43m)\u001b[49m\n",
"File \u001b[0;32m~/Documents/projects/fincal/fincal/fincal.py:733\u001b[0m, in \u001b[0;36mTimeSeries.sync\u001b[0;34m(self, other, fill_method)\u001b[0m\n\u001b[1;32m 731\u001b[0m new_other[dt] \u001b[38;5;241m=\u001b[39m other[dt][\u001b[38;5;241m1\u001b[39m]\n\u001b[1;32m 732\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m--> 733\u001b[0m new_other[dt] \u001b[38;5;241m=\u001b[39m other\u001b[38;5;241m.\u001b[39mget(dt, closest\u001b[38;5;241m=\u001b[39mclosest)[\u001b[38;5;241m1\u001b[39m]\n\u001b[1;32m 735\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__class__\u001b[39m(new_other, frequency\u001b[38;5;241m=\u001b[39mother\u001b[38;5;241m.\u001b[39mfrequency\u001b[38;5;241m.\u001b[39msymbol)\n",
"File \u001b[0;32m~/Documents/projects/fincal/fincal/core.py:69\u001b[0m, in \u001b[0;36mdate_parser.<locals>.parse_dates.<locals>.wrapper_func\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m 67\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 68\u001b[0m args[j] \u001b[38;5;241m=\u001b[39m parsed_date\n\u001b[0;32m---> 69\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n",
"File \u001b[0;32m~/Documents/projects/fincal/fincal/core.py:408\u001b[0m, in \u001b[0;36mTimeSeriesCore.get\u001b[0;34m(self, date, default, closest)\u001b[0m\n\u001b[1;32m 406\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m item\n\u001b[1;32m 407\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mKeyError\u001b[39;00m:\n\u001b[0;32m--> 408\u001b[0m date \u001b[38;5;241m+\u001b[39m\u001b[38;5;241m=\u001b[39m delta\n",
"\u001b[0;31mOverflowError\u001b[0m: date value out of range"
]
}
],
"outputs": [],
"source": [
"week_ts.sync(ts)"
]
@ -277,8 +277,8 @@
"output_type": "stream",
"text": [
"['date', 'nav']\n",
"CPU times: user 56.9 ms, sys: 3.3 ms, total: 60.2 ms\n",
"Wall time: 60.2 ms\n"
"CPU times: user 57.5 ms, sys: 3.38 ms, total: 60.8 ms\n",
"Wall time: 60.5 ms\n"
]
}
],
@ -289,7 +289,7 @@
},
{
"cell_type": "code",
"execution_count": 3,
"execution_count": 7,
"id": "b7c176d4-d89f-4bda-9d67-75463eb90468",
"metadata": {},
"outputs": [
@ -297,9 +297,9 @@
"name": "stdout",
"output_type": "stream",
"text": [
"(datetime.datetime(2022, 2, 9, 0, 0), 311.209991)\n",
"(datetime.datetime(2022, 2, 10, 0, 0), 302.380005)\n",
"(datetime.datetime(2022, 2, 11, 0, 0), 295.040009)\n",
"(datetime.datetime(2022, 2, 12, 0, 0), 296.0)\n",
"(datetime.datetime(2022, 2, 13, 0, 0), 296.0)\n",
"(datetime.datetime(2022, 2, 14, 0, 0), 295.0)\n",
"(datetime.datetime(2022, 2, 15, 0, 0), 300.470001)\n",
"(datetime.datetime(2022, 2, 16, 0, 0), 299.5)\n",
@ -315,7 +315,7 @@
},
{
"cell_type": "code",
"execution_count": 4,
"execution_count": 6,
"id": "69c57754-a6fb-4881-9359-ba17c7fb8be5",
"metadata": {},
"outputs": [
@ -323,19 +323,19 @@
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 1.85 ms, sys: 143 µs, total: 1.99 ms\n",
"Wall time: 2 ms\n"
"CPU times: user 1.76 ms, sys: 123 µs, total: 1.88 ms\n",
"Wall time: 1.88 ms\n"
]
}
],
"source": [
"%%time\n",
"ts['2022-02-12'] = 295"
"ts['2022-02-12'] = 296"
]
},
{
"cell_type": "code",
"execution_count": 5,
"execution_count": 8,
"id": "7aa02023-406e-4700-801c-c06390ddf914",
"metadata": {},
"outputs": [
@ -343,8 +343,8 @@
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 3.7 ms, sys: 121 µs, total: 3.82 ms\n",
"Wall time: 3.84 ms\n"
"CPU times: user 3.61 ms, sys: 68 µs, total: 3.68 ms\n",
"Wall time: 3.7 ms\n"
]
},
{
@ -355,7 +355,7 @@
" 'drawdown': -0.7456453305351521}"
]
},
"execution_count": 5,
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
@ -364,9 +364,335 @@
"%%time\n",
"ts.max_drawdown()"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "72cb4da4-1318-4b9b-b563-adac46accfb3",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from typing import Mapping\n",
"isinstance(ts, Mapping)"
]
},
{
"cell_type": "code",
"execution_count": 23,
"id": "96bbecbf",
"metadata": {},
"outputs": [],
"source": [
"import fincal as fc"
]
},
{
"cell_type": "code",
"execution_count": 24,
"id": "19199c92",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"['amfi_code', 'date', 'nav']\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"/Users/gourav/Documents/projects/fincal/fincal/core.py:308: UserWarning: The input data contains duplicate dates which have been ignored.\n",
" warnings.warn(\"The input data contains duplicate dates which have been ignored.\")\n"
]
},
{
"data": {
"text/plain": [
"TimeSeries([(datetime.datetime(2013, 1, 2, 0, 0), 18.972),\n",
"\t (datetime.datetime(2013, 1, 3, 0, 0), 19.011),\n",
"\t (datetime.datetime(2013, 1, 4, 0, 0), 19.008)\n",
"\t ...\n",
"\t (datetime.datetime(2022, 2, 10, 0, 0), 86.5),\n",
"\t (datetime.datetime(2022, 2, 11, 0, 0), 85.226),\n",
"\t (datetime.datetime(2022, 2, 14, 0, 0), 82.533)], frequency='D')"
]
},
"execution_count": 24,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"ts = fc.read_csv('test_files/nav_history_daily - copy.csv', col_index=(1, 2), frequency='D', date_format='%d-%m-%y')\n",
"ts"
]
},
{
"cell_type": "code",
"execution_count": 28,
"id": "51c9ae9a",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"0.12031455056454916"
]
},
"execution_count": 28,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"fc.sharpe_ratio(\n",
" ts,\n",
" risk_free_rate=0.06,\n",
" from_date='2013-02-04',\n",
" to_date='2022-02-14',\n",
" return_period_unit='months',\n",
" return_period_value=1\n",
")"
]
},
{
"cell_type": "markdown",
"id": "b3fb7b59-eaa3-41a5-b1ab-89d63b69edb0",
"metadata": {},
"source": [
"# Data generator"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "aead3e77-2670-4541-846a-5537b01f3d2e",
"metadata": {},
"outputs": [],
"source": [
"import random\n",
"import math\n",
"import fincal as fc\n",
"from typing import List\n",
"import datetime\n",
"from dateutil.relativedelta import relativedelta"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "f287e05f",
"metadata": {},
"outputs": [],
"source": [
"def create_prices(s0: float, mu: float, sigma: float, num_prices: int) -> list:\n",
" \"\"\"Generates a price following a geometric brownian motion process based on the input of the arguments.\n",
"\n",
" Since this function is used only to generate data for tests, the seed is fixed as 1234.\n",
" Many of the tests rely on exact values generated using this seed.\n",
" If the seed is changed, those tests will fail.\n",
"\n",
" Parameters:\n",
" ------------\n",
" s0: float\n",
" Asset inital price.\n",
"\n",
" mu: float\n",
" Interest rate expressed annual terms.\n",
"\n",
" sigma: float\n",
" Volatility expressed annual terms.\n",
"\n",
" num_prices: int\n",
" number of prices to generate\n",
"\n",
" Returns:\n",
" --------\n",
" Returns a list of values generated using GBM algorithm\n",
" \"\"\"\n",
"\n",
" random.seed(1234) # WARNING! Changing the seed will cause most tests to fail\n",
" all_values = []\n",
" for _ in range(num_prices):\n",
" s0 *= math.exp(\n",
" (mu - 0.5 * sigma**2) * (1.0 / 365.0) + sigma * math.sqrt(1.0 / 365.0) * random.gauss(mu=0, sigma=1)\n",
" )\n",
" all_values.append(round(s0, 2))\n",
"\n",
" return all_values\n",
"\n",
"\n",
"def sample_data_generator(\n",
" frequency: fc.Frequency,\n",
" num: int = 1000,\n",
" skip_weekends: bool = False,\n",
" mu: float = 0.1,\n",
" sigma: float = 0.05,\n",
" eomonth: bool = False,\n",
") -> List[tuple]:\n",
" \"\"\"Creates TimeSeries data\n",
"\n",
" Parameters:\n",
" -----------\n",
" frequency: Frequency\n",
" The frequency of the time series data to be generated.\n",
"\n",
" num: int\n",
" Number of date: value pairs to be generated.\n",
"\n",
" skip_weekends: bool\n",
" Whether weekends (saturday, sunday) should be skipped.\n",
" Gets used only if the frequency is daily.\n",
"\n",
" mu: float\n",
" Mean return for the values.\n",
"\n",
" sigma: float\n",
" standard deviation of the values.\n",
"\n",
" Returns:\n",
" --------\n",
" Returns a TimeSeries object\n",
" \"\"\"\n",
"\n",
" start_date = datetime.datetime(2017, 1, 1)\n",
" timedelta_dict = {\n",
" frequency.freq_type: int(\n",
" frequency.value * num * (7 / 5 if frequency == fc.AllFrequencies.D and skip_weekends else 1)\n",
" )\n",
" }\n",
" end_date = start_date + relativedelta(**timedelta_dict)\n",
" dates = fc.create_date_series(start_date, end_date, frequency.symbol, skip_weekends=skip_weekends, eomonth=eomonth)\n",
" values = create_prices(1000, mu, sigma, num)\n",
" ts = list(zip(dates, values))\n",
" return ts\n"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "c85b5dd9-9a88-4608-ac58-1a141295f63f",
"metadata": {},
"outputs": [],
"source": [
"data = sample_data_generator(num=261, frequency=fc.AllFrequencies.W)\n",
"ts = fc.TimeSeries(data, \"W\")"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "0488a4d0-bca1-4341-9fae-1fd254adc0dc",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"TimeSeries([(datetime.datetime(2017, 1, 1, 0, 0), 1003.03),\n",
"\t (datetime.datetime(2017, 1, 8, 0, 0), 1002.71),\n",
"\t (datetime.datetime(2017, 1, 15, 0, 0), 1008.77)\n",
"\t ...\n",
"\t (datetime.datetime(2021, 12, 12, 0, 0), 1107.21),\n",
"\t (datetime.datetime(2021, 12, 19, 0, 0), 1106.66),\n",
"\t (datetime.datetime(2021, 12, 26, 0, 0), 1104.32)], frequency='W')"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"ts"
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "04624145-4fce-484c-aa69-0d17d159b598",
"metadata": {},
"outputs": [],
"source": [
"tst = ts.transform('Q', 'mean', False)"
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "75ed1666-5fc8-4707-bf42-62d44adcae18",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"20"
]
},
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"len(tst)"
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "bccd7d1c-2d57-444c-af68-290f476f2b05",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"(datetime.datetime(2017, 1, 1, 0, 0), 1010.4553846153846)\n",
"(datetime.datetime(2017, 4, 1, 0, 0), 1019.34)\n",
"(datetime.datetime(2017, 7, 1, 0, 0), 1015.3515384615384)\n",
"(datetime.datetime(2017, 10, 1, 0, 0), 1031.2892857142858)\n",
"(datetime.datetime(2018, 1, 1, 0, 0), 1054.7216666666666)\n",
"(datetime.datetime(2018, 4, 1, 0, 0), 1059.736153846154)\n",
"(datetime.datetime(2018, 7, 1, 0, 0), 1049.1100000000001)\n",
"(datetime.datetime(2018, 10, 1, 0, 0), 1051.663076923077)\n",
"(datetime.datetime(2019, 1, 1, 0, 0), 1062.2869230769231)\n",
"(datetime.datetime(2019, 4, 1, 0, 0), 1059.7423076923076)\n",
"(datetime.datetime(2019, 7, 1, 0, 0), 1050.7661538461539)\n",
"(datetime.datetime(2019, 10, 1, 0, 0), 1045.2061538461537)\n",
"(datetime.datetime(2020, 1, 1, 0, 0), 1046.11)\n",
"(datetime.datetime(2020, 4, 1, 0, 0), 1053.126923076923)\n",
"(datetime.datetime(2020, 7, 1, 0, 0), 1053.273846153846)\n",
"(datetime.datetime(2020, 10, 1, 0, 0), 1064.2384615384615)\n",
"(datetime.datetime(2021, 1, 1, 0, 0), 1073.1538461538462)\n",
"(datetime.datetime(2021, 4, 1, 0, 0), 1094.3215384615385)\n",
"(datetime.datetime(2021, 7, 1, 0, 0), 1104.3584615384616)\n",
"(datetime.datetime(2021, 10, 1, 0, 0), 1112.806923076923)\n"
]
}
],
"source": [
"for i in tst:\n",
" print(i)"
]
}
],
"metadata": {
"interpreter": {
"hash": "71e6a8e087576f7c2a714460e6ef0339bac111b70cc81e9aa980fde63219ab06"
},
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",

View File

@ -340,20 +340,54 @@ class TestExpand:
class TestShrink:
# To-do
# TODO
pass
class TestMeanReturns:
# To-do
# TODO
pass
class TestReadCsv:
# To-do
# TODO
pass
class TestTransform:
def test_daily_to_yearly(self, create_test_data):
ts_data = create_test_data(AllFrequencies.D, num=782, skip_weekends=True)
ts = TimeSeries(ts_data, "D")
tst = ts.transform("Y", "mean")
assert isinstance(tst, TimeSeries)
assert len(tst) == 3
assert "2019-01-02" in tst
assert tst.iloc[2] == (datetime.datetime(2019, 1, 2), 1238.5195)
def test_weekly_to_monthly(self, create_test_data):
ts_data = create_test_data(AllFrequencies.W, num=261)
ts = TimeSeries(ts_data, "W")
tst = ts.transform("M", "mean")
assert isinstance(tst, TimeSeries)
assert "2017-01-01" in tst
assert tst.iloc[0] == (datetime.datetime(2017, 1, 1), 1007.33)
def test_weekly_to_qty(self, create_test_data):
ts_data = create_test_data(AllFrequencies.W, num=261)
ts = TimeSeries(ts_data, "W")
tst = ts.transform("Q", "mean")
assert len(tst) == 20
assert "2018-01-01" in tst
assert round(tst.iloc[4][1], 2) == 1054.72
def test_weekly_to_yearly(self, create_test_data):
ts_data = create_test_data(AllFrequencies.W, num=261)
ts = TimeSeries(ts_data, "W")
tst = ts.transform("Y", "mean")
assert "2019-01-01" in tst
assert round(tst.iloc[2][1], 2) == 1054.50
class TestReturnsAgain:
data = [
("2020-01-01", 10),

View File

@ -72,3 +72,13 @@ class TestSharpe:
return_period_value=1,
)
assert round(sharpe_ratio, 4) == 0.4898
sharpe_ratio = fc.sharpe_ratio(
ts,
risk_free_rate=0.052,
from_date="2018-01-01",
to_date="2021-12-31",
return_period_unit="months",
return_period_value=12,
)
assert round(sharpe_ratio, 4) == 0.3199