Compare commits

...

10 Commits

15 changed files with 8549 additions and 376 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

View File

@ -2,7 +2,7 @@
"cells": [
{
"cell_type": "code",
"execution_count": 14,
"execution_count": 1,
"id": "3f7938c0-98e3-43b8-86e8-4f000cda7ce5",
"metadata": {},
"outputs": [],
@ -16,92 +16,196 @@
},
{
"cell_type": "code",
"execution_count": 16,
"id": "757eafc2-f804-4e7e-a3b8-2d09cd62e646",
"metadata": {},
"outputs": [],
"source": [
"dfd = pd.read_csv('test_files/nav_history_daily - copy.csv')"
]
},
{
"cell_type": "code",
"execution_count": 17,
"id": "59b3d4a9-8ef4-4652-9e20-1bac69ab4ff9",
"metadata": {},
"outputs": [],
"source": [
"dfd = dfd[dfd['amfi_code'] == 118825].reset_index(drop=True)"
]
},
{
"cell_type": "code",
"execution_count": 19,
"id": "4bc95ae0-8c33-4eab-acf9-e765d22979b8",
"execution_count": 2,
"id": "4b8ccd5f-dfff-4202-82c4-f66a30c122b6",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Warning: The input data contains duplicate dates which have been ignored.\n"
"CPU times: user 152 ms, sys: 284 ms, total: 436 ms\n",
"Wall time: 61.3 ms\n"
]
}
],
"source": [
"ts = TimeSeries([(i.date, i.nav) for i in dfd.itertuples()], frequency='D')"
]
},
{
"cell_type": "code",
"execution_count": 20,
"id": "f2c3218c-3984-43d6-8638-41a74a9d0b58",
"metadata": {},
"outputs": [
},
{
"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.53299999999999)], frequency='D')"
"TimeSeries([(datetime.datetime(2021, 5, 28, 0, 0), 249.679993),\n",
"\t(datetime.datetime(2022, 1, 31, 0, 0), 310.980011)], frequency='D')"
]
},
"execution_count": 20,
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"ts"
"%%time\n",
"dfd = pd.read_csv('test_files/msft.csv')\n",
"# dfd = dfd[dfd['amfi_code'] == 118825].reset_index(drop=True)\n",
"ts = TimeSeries([(i.date, i.nav) for i in dfd.itertuples()], frequency='D')\n",
"repr(ts)\n",
"ts[['2022-01-31', '2021-05-28']]"
]
},
{
"cell_type": "code",
"execution_count": 22,
"id": "dc469722-c816-4b57-8d91-7a3b865f86be",
"execution_count": 3,
"id": "a0232e05-27c7-4d2d-a4bc-5dcf42666983",
"metadata": {},
"outputs": [
{
"ename": "TypeError",
"evalue": "getattr(): attribute name must be string",
"evalue": "Type List cannot be instantiated; use list() instead",
"output_type": "error",
"traceback": [
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[1;31mTypeError\u001b[0m Traceback (most recent call last)",
"File \u001b[1;32m<timed eval>:1\u001b[0m, in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n",
"File \u001b[1;32mD:\\Documents\\Projects\\fincal\\fincal\\fincal.py:203\u001b[0m, in \u001b[0;36mTimeSeries.calculate_rolling_returns\u001b[1;34m(self, from_date, to_date, frequency, as_on_match, prior_match, closest, compounding, years)\u001b[0m\n\u001b[0;32m 200\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mAttributeError\u001b[39;00m:\n\u001b[0;32m 201\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 frequency \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mfrequency\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m--> 203\u001b[0m dates \u001b[38;5;241m=\u001b[39m \u001b[43mcreate_date_series\u001b[49m\u001b[43m(\u001b[49m\u001b[43mfrom_date\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mto_date\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mfrequency\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m 204\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m frequency \u001b[38;5;241m==\u001b[39m AllFrequencies\u001b[38;5;241m.\u001b[39mD:\n\u001b[0;32m 205\u001b[0m dates \u001b[38;5;241m=\u001b[39m [i \u001b[38;5;28;01mfor\u001b[39;00m i \u001b[38;5;129;01min\u001b[39;00m dates \u001b[38;5;28;01mif\u001b[39;00m i \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mtime_series]\n",
"File \u001b[1;32mD:\\Documents\\Projects\\fincal\\fincal\\fincal.py:16\u001b[0m, in \u001b[0;36mcreate_date_series\u001b[1;34m(start_date, end_date, frequency, eomonth)\u001b[0m\n\u001b[0;32m 11\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mcreate_date_series\u001b[39m(\n\u001b[0;32m 12\u001b[0m start_date: datetime\u001b[38;5;241m.\u001b[39mdatetime, end_date: datetime\u001b[38;5;241m.\u001b[39mdatetime, frequency: \u001b[38;5;28mstr\u001b[39m, eomonth: \u001b[38;5;28mbool\u001b[39m \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mFalse\u001b[39;00m\n\u001b[0;32m 13\u001b[0m ) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m List[datetime\u001b[38;5;241m.\u001b[39mdatetime]:\n\u001b[0;32m 14\u001b[0m \u001b[38;5;124;03m\"\"\"Creates a date series using a frequency\"\"\"\u001b[39;00m\n\u001b[1;32m---> 16\u001b[0m frequency \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mgetattr\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43mAllFrequencies\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mfrequency\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m 17\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m eomonth \u001b[38;5;129;01mand\u001b[39;00m frequency\u001b[38;5;241m.\u001b[39mdays \u001b[38;5;241m<\u001b[39m AllFrequencies\u001b[38;5;241m.\u001b[39mM\u001b[38;5;241m.\u001b[39mdays:\n\u001b[0;32m 18\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;124meomonth cannot be set to True if frequency is higher than \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mAllFrequencies\u001b[38;5;241m.\u001b[39mM\u001b[38;5;241m.\u001b[39mname\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n",
"\u001b[1;31mTypeError\u001b[0m: getattr(): attribute name must be string"
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)",
"Input \u001b[0;32mIn [3]\u001b[0m, in \u001b[0;36m<cell line: 7>\u001b[0;34m()\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mfincal\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mcore\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m Frequency\n\u001b[1;32m 3\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mtyping\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m List, Tuple\n\u001b[1;32m 5\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mcreate_test_data\u001b[39m(\n\u001b[1;32m 6\u001b[0m frequency: Frequency,\n\u001b[1;32m 7\u001b[0m num: \u001b[38;5;28mint\u001b[39m \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m1000\u001b[39m,\n\u001b[1;32m 8\u001b[0m skip_weekends: \u001b[38;5;28mbool\u001b[39m \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mFalse\u001b[39;00m,\n\u001b[1;32m 9\u001b[0m mu: \u001b[38;5;28mfloat\u001b[39m \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m0.1\u001b[39m,\n\u001b[1;32m 10\u001b[0m sigma: \u001b[38;5;28mfloat\u001b[39m \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m0.05\u001b[39m,\n\u001b[1;32m 11\u001b[0m eomonth: \u001b[38;5;28mbool\u001b[39m \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mFalse\u001b[39;00m,\n\u001b[0;32m---> 12\u001b[0m ) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m \u001b[43mList\u001b[49m\u001b[43m(\u001b[49m\u001b[43mTuple\u001b[49m\u001b[43m)\u001b[49m:\n\u001b[1;32m 13\u001b[0m \u001b[38;5;124;03m\"\"\"Creates TimeSeries data\u001b[39;00m\n\u001b[1;32m 14\u001b[0m \n\u001b[1;32m 15\u001b[0m \u001b[38;5;124;03m Parameters:\u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 35\u001b[0m \u001b[38;5;124;03m Returns a TimeSeries object\u001b[39;00m\n\u001b[1;32m 36\u001b[0m \u001b[38;5;124;03m \"\"\"\u001b[39;00m\n\u001b[1;32m 38\u001b[0m start_date \u001b[38;5;241m=\u001b[39m datetime\u001b[38;5;241m.\u001b[39mdatetime(\u001b[38;5;241m2017\u001b[39m, \u001b[38;5;241m1\u001b[39m, \u001b[38;5;241m1\u001b[39m)\n",
"File \u001b[0;32m/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/typing.py:941\u001b[0m, in \u001b[0;36m_BaseGenericAlias.__call__\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 939\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m__call__\u001b[39m(\u001b[38;5;28mself\u001b[39m, \u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs):\n\u001b[1;32m 940\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_inst:\n\u001b[0;32m--> 941\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mType \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_name\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m cannot be instantiated; \u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 942\u001b[0m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124muse \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m__origin__\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__name__\u001b[39m\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m() instead\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 943\u001b[0m result \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m__origin__(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n\u001b[1;32m 944\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n",
"\u001b[0;31mTypeError\u001b[0m: Type List cannot be instantiated; use list() instead"
]
}
],
"source": [
"from fincal.fincal import create_date_series\n",
"from fincal.core import Frequency\n",
"from typing import List, Tuple\n",
"\n",
"def create_test_data(\n",
" frequency: 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 == AllFrequencies.D and skip_weekends else 1)\n",
" )\n",
" }\n",
" end_date = start_date + relativedelta(**timedelta_dict)\n",
" dates = 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"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "53dbc8a6-d7b1-4d82-ac3d-ee3908ff086d",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": 1,
"id": "aa1584d5-1df0-4661-aeeb-5e8c424de06d",
"metadata": {},
"outputs": [],
"source": [
"from fincal import fincal\n",
"from fincal.core import FincalOptions\n",
"import csv"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "7d51fca1-f731-47c8-99c9-6e199cfeca92",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"['date', 'nav']\n",
"CPU times: user 47.7 ms, sys: 3.16 ms, total: 50.9 ms\n",
"Wall time: 50.3 ms\n"
]
},
{
"data": {
"text/plain": [
"TimeSeries([(datetime.datetime(1992, 2, 19, 0, 0), '2.398438'),\n",
"\t (datetime.datetime(1992, 2, 20, 0, 0), '2.447917'),\n",
"\t (datetime.datetime(1992, 2, 21, 0, 0), '2.385417')\n",
"\t ...\n",
"\t (datetime.datetime(2022, 2, 16, 0, 0), '299.5'),\n",
"\t (datetime.datetime(2022, 2, 17, 0, 0), '290.730011'),\n",
"\t (datetime.datetime(2022, 2, 18, 0, 0), '287.929993')], frequency='M')"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"%%time\n",
"FincalOptions.date_format = '%Y-%m-%d'\n",
"fincal.read_csv('test_files/msft.csv', frequency='M')"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "b689f64c-6764-45b5-bccf-f23b351f6419",
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "6c9b2dd7-9983-40cd-8ac4-3530a3892f17",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 61.4 ms, sys: 2.35 ms, total: 63.7 ms\n",
"Wall time: 62.6 ms\n"
]
}
],
"source": [
"%%time\n",
"ts.calculate_rolling_returns(from_date='2020-01-01', to_date='2021-01-01')"
"dfd = pd.read_csv(\"test_files/msft.csv\")\n",
"ts = fincal.TimeSeries([(i.date, i.nav) for i in dfd.itertuples()], frequency=\"D\")"
]
}
],
@ -121,7 +225,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.3"
"version": "3.10.2"
}
},
"nbformat": 4,

View File

@ -1 +1,3 @@
from fincal import *
from .core import *
from .fincal import *
from .utils import *

View File

@ -2,7 +2,8 @@ from __future__ import annotations
import datetime
import inspect
from collections import UserDict, UserList
import warnings
from collections import UserList
from dataclasses import dataclass
from numbers import Number
from typing import Callable, Iterable, List, Literal, Mapping, Sequence, Type
@ -99,6 +100,11 @@ class _IndexSlicer:
return self.parent.__class__(item, self.parent.frequency.symbol)
def __setitem__(self, key, value):
raise NotImplementedError(
"iloc cannot be used for setting a value as value will always be inserted in order of date"
)
class Series(UserList):
"""Container for a series of objects, all objects must be of the same type"""
@ -150,7 +156,13 @@ class Series(UserList):
if isinstance(other, (str, datetime.datetime, datetime.date)):
other = _parse_date(other)
if self.dtype == float and isinstance(other, Number) or isinstance(other, self.dtype):
if isinstance(other, Series):
if len(self) != len(other):
raise ValueError("Length of Series must be same for comparison")
gt = Series([j > other[i] for i, j in enumerate(self)], "bool")
elif self.dtype == float and isinstance(other, Number) or isinstance(other, self.dtype):
gt = Series([i > other for i in self.data], "bool")
else:
raise Exception(f"Cannot compare type {self.dtype.__name__} to {type(other).__name__}")
@ -164,7 +176,13 @@ class Series(UserList):
if isinstance(other, (str, datetime.datetime, datetime.date)):
other = _parse_date(other)
if self.dtype == float and isinstance(other, Number) or isinstance(other, self.dtype):
if isinstance(other, Series):
if len(self) != len(other):
raise ValueError("Length of Series must be same for comparison")
ge = Series([j >= other[i] for i, j in enumerate(self)], "bool")
elif self.dtype == float and isinstance(other, Number) or isinstance(other, self.dtype):
ge = Series([i >= other for i in self.data], "bool")
else:
raise Exception(f"Cannot compare type {self.dtype.__name__} to {type(other).__name__}")
@ -178,7 +196,13 @@ class Series(UserList):
if isinstance(other, (str, datetime.datetime, datetime.date)):
other = _parse_date(other)
if self.dtype == float and isinstance(other, Number) or isinstance(other, self.dtype):
if isinstance(other, Series):
if len(self) != len(other):
raise ValueError("Length of Series must be same for comparison")
lt = Series([j < other[i] for i, j in enumerate(self)], "bool")
elif self.dtype == float and isinstance(other, Number) or isinstance(other, self.dtype):
lt = Series([i < other for i in self.data], "bool")
else:
raise Exception(f"Cannot compare type {self.dtype.__name__} to {type(other).__name__}")
@ -191,7 +215,13 @@ class Series(UserList):
if isinstance(other, (str, datetime.datetime, datetime.date)):
other = _parse_date(other)
if self.dtype == float and isinstance(other, Number) or isinstance(other, self.dtype):
if isinstance(other, Series):
if len(self) != len(other):
raise ValueError("Length of Series must be same for comparison")
le = Series([j <= other[i] for i, j in enumerate(self)], "bool")
elif self.dtype == float and isinstance(other, Number) or isinstance(other, self.dtype):
le = Series([i <= other for i in self.data], "bool")
else:
raise Exception(f"Cannot compare type {self.dtype.__name__} to {type(other).__name__}")
@ -201,14 +231,21 @@ class Series(UserList):
if isinstance(other, (str, datetime.datetime, datetime.date)):
other = _parse_date(other)
if self.dtype == float and isinstance(other, Number) or isinstance(other, self.dtype):
if isinstance(other, Series):
if len(self) != len(other):
raise ValueError("Length of Series must be same for comparison")
eq = Series([j == other[i] for i, j in enumerate(self)], "bool")
elif self.dtype == float and isinstance(other, Number) or isinstance(other, self.dtype):
eq = Series([i == other for i in self.data], "bool")
else:
raise Exception(f"Cannot compare type {self.dtype.__name__} to {type(other).__name__}")
return eq
class TimeSeriesCore(UserDict):
@Mapping.register
class TimeSeriesCore:
"""Defines the core building blocks of a TimeSeries object"""
def __init__(
@ -237,9 +274,9 @@ class TimeSeriesCore(UserDict):
ts_data = _preprocess_timeseries(ts_data, date_format=date_format)
super().__init__(dict(ts_data))
self.data = dict(ts_data)
if len(self.data) != len(ts_data):
print("Warning: The input data contains duplicate dates which have been ignored.")
warnings.warn("The input data contains duplicate dates which have been ignored.")
self.frequency: Frequency = getattr(AllFrequencies, frequency)
self.iter_num: int = -1
self._dates: list = None
@ -364,6 +401,41 @@ class TimeSeriesCore(UserDict):
raise TypeError(f"Invalid type {repr(type(key).__name__)} for slicing.")
def __gt__(self, other):
if isinstance(other, Number):
data = {k: v > other for k, v in self.data.items()}
if isinstance(other, TimeSeriesCore):
if self.dates != other.dates:
raise ValueError(
"Only objects with same set of dates can be compared.\n"
"Hint: use TimeSeries.sync() method to sync dates of two TimeSeries objects."
)
data = {dt: val > other[dt][1] for dt, val in self.data.items()}
if isinstance(other, Series):
if Series.dtype != float:
raise TypeError("Only Series of type float can be used for comparison")
if len(self) != len(other):
raise ValueError("Length of series does not match length of object")
data = {dt: val > other[i] for i, (dt, val) in enumerate(self.data.items())}
return self.__class__(data, frequency=self.frequency.symbol)
@date_parser(1)
def __setitem__(self, key: str | datetime.datetime, value: Number) -> None:
if not isinstance(value, Number):
raise TypeError("Only numerical values can be stored in TimeSeries")
if key in self.data:
self.data[key] = value
else:
self.data.update({key: value})
self.data = dict(sorted(self.data.items()))
def __iter__(self):
self.n = 0
return self
@ -376,9 +448,12 @@ class TimeSeriesCore(UserDict):
self.n += 1
return key, self.data[key]
def __len__(self):
return len(self.data)
@date_parser(1)
def __contains__(self, key: object) -> bool:
return super().__contains__(key)
return key in self.data
@date_parser(1)
def get(self, date: str | datetime.datetime, default=None, closest=None):

View File

@ -476,7 +476,7 @@ class TimeSeries(TimeSeriesCore):
ValueError: If frequency string is outside valid values
Also see:
--------
---------
TimeSeries.calculate_rolling_returns()
"""
@ -583,7 +583,38 @@ class TimeSeries(TimeSeriesCore):
to_frequency: Literal["D", "W", "M", "Q", "H"],
method: Literal["ffill", "bfill"],
skip_weekends: bool = False,
eomonth: bool = False,
) -> TimeSeries:
"""Expand a time series to a higher frequency.
Parameters
----------
to_frequency : "D", "W", "M", "Q", "H"
Frequency to which the TimeSeries will be expanded.
Must be higher than the current frequency of the TimeSeries.
method : ffill | bfill
Method to be used to fill missing values.
skip_weekends : bool, optional
Whether weekends should be skipped while expanding to daily.
Will be used only if to_frequency is D
eomonth: bool, optional
Whether dates should be end of month dates when frequency is monthly or lower.
Will be used only if to_frequency is M, Q, or H
Returns
-------
TimeSeries
Returns an object of TimeSeries class
Raises
------
ValueError
* If Frequency cannot be recognised
* If to_frequency is same or lower than the current frequency
"""
try:
to_frequency: Frequency = getattr(AllFrequencies, to_frequency)
except AttributeError:
@ -597,6 +628,7 @@ class TimeSeries(TimeSeriesCore):
self.end_date,
frequency=to_frequency.symbol,
skip_weekends=skip_weekends,
eomonth=eomonth,
ensure_coverage=True,
)
@ -606,12 +638,88 @@ class TimeSeries(TimeSeriesCore):
return output_ts
def sync(self, other: TimeSeries, fill_method: Literal["ffill", "bfill"] = "ffill"):
def shrink(
self,
to_frequency: Literal["W", "M", "Q", "H", "Y"],
method: Literal["ffill", "bfill"],
skip_weekends: bool = False,
eomonth: bool = False,
) -> TimeSeries:
"""Shrink a time series to a lower frequency.
Parameters
----------
to_frequency : "W", "M", "Q", "H", "Y"
Frequency to which the TimeSeries will be shrunk.
Must be lower than the current frequency of the TimeSeries.
method : ffill | bfill
Method to be used to fill missing values.
skip_weekends : bool, optional
Whether weekends should be skipped while shrinking to daily.
Will be used only if to_frequency is D
eomonth: bool, optional
Whether dates should be end of month dates when frequency is monthly or lower.
Will be used only if to_frequency is M, Q, H, or Y
Returns
-------
TimeSeries
Returns an object of TimeSeries class
Raises
------
ValueError
* If Frequency cannot be recognised
* If to_frequency is same or 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")
new_dates = create_date_series(
self.start_date,
self.end_date,
frequency=to_frequency.symbol,
skip_weekends=skip_weekends,
eomonth=eomonth,
ensure_coverage=True,
)
closest: str = "previous" if method == "ffill" else "next"
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
def sync(self, other: TimeSeries, fill_method: Literal["ffill", "bfill"] = "ffill") -> TimeSeries:
"""Synchronize two TimeSeries objects
This will ensure that both time series have the same frequency and same set of dates.
The frequency will be set to the higher of the two objects.
Dates will be taken from the class on which the method is called.
Parameters:
-----------
other: TimeSeries
Another object of TimeSeries class whose dates need to be syncronized
fill_method: ffill | bfill, default ffill
Method to use to fill missing values in time series when syncronizing
Returns:
--------
Returns another object of TimeSeries class
Raises:
--------
Raises TypeError if the other object is not of TimeSeries class
"""
if not isinstance(other, TimeSeries):
@ -620,11 +728,11 @@ class TimeSeries(TimeSeriesCore):
if self.frequency.days < other.frequency.days:
other = other.expand(to_frequency=self.frequency.symbol, method=fill_method)
if self.frequency.days > other.frequency.days:
self = self.expand(to_frequency=other.frequency.symbol, method=fill_method)
other = other.shrink(to_frequency=other.frequency.symbol, method=fill_method)
new_other = {}
new_other: dict = {}
closest = "previous" if fill_method == "ffill" else "next"
for dt in self.dates:
closest = "previous" if fill_method == "ffill" else "next"
if dt in other:
new_other[dt] = other[dt][1]
else:

View File

@ -13,9 +13,29 @@ class FincalOptions:
get_closest: str = "exact"
def _parse_date(date: str, date_format: str = None):
"""Parses date and handles errors"""
# print(date, date_format)
def _parse_date(date: str, date_format: str = None) -> datetime.datetime:
"""Parses date and handles errors
Parameters:
-----------
date: str | datetime.date
The date to be parsed.
If the date passed is already a datetime object, it will return it unprocessed.
date_format: str, default None
The format of the date string in datetime.strftime friendly format.
If format is None, format in FincalOptions.date_format will be used.
Returns:
--------
Returns a datetime.datetime object.
Raises:
-------
TypeError: If the is not a date-like string
ValueError: If the date could not be parsed with the given format
"""
if isinstance(date, (datetime.datetime, datetime.date)):
return datetime.datetime.fromordinal(date.toordinal())
@ -37,18 +57,53 @@ def _preprocess_timeseries(
| Mapping[str | datetime.datetime, float],
date_format: str,
) -> List[Tuple[datetime.datetime, float]]:
"""Converts any type of list to the correct type"""
"""Converts any type of list to the TimeSeries friendly format.
This function is internally called by the __init__ function of the TimeSeriesCore class
The TimeSeries class can internally process a list of Tuples.
However, users have the option of passing a variety of types.
This function preprocesses the data and converts it into the relevant format.
If the data is a dictionary, it will be converted using .items() iteration.
If the data is not a dictionary or a list, it will raise an error.
If the data is of list type:
* If the first item is also of list type, it will be parsed as a list of lists
* If the first item is a dictionary with one key, then key will be parsed as date
* If the first item is a dictionary with two keys, then first key will be date and second will be value
* If the first element is of another type, it will raise an error
The final return value is sorted by date
Parameters:
-----------
Data:
The data for the time series. Can be a dictionary, a list of tuples, or a list of dictionaries.
date_format: str
The format of the date in strftime friendly format.
Returns:
-----------
Returns a list of Tuples where the first element of each tuple is of datetime.datetime class
and the second element is of float class
Raises:
--------
TypeError: If the data is not in a format which can be parsed.
"""
if isinstance(data, Mapping):
current_data: List[tuple] = [(k, v) for k, v in data.items()]
return _preprocess_timeseries(current_data, date_format)
# If data is not a dictionary or list, it cannot be parsed
if not isinstance(data, Sequence):
raise TypeError("Could not parse the data")
if isinstance(data[0], Sequence):
return sorted([(_parse_date(i, date_format), j) for i, j in data])
return sorted([(_parse_date(i, date_format), float(j)) for i, j in data])
# If first element is not a dictionary or tuple, it cannot be parsed
if not isinstance(data[0], Mapping):
raise TypeError("Could not parse the data")

54
test.py
View File

@ -1,35 +1,29 @@
# type: ignore
import datetime
import time
import pandas as pd
# from fincal.core import FincalOptions
from fincal.fincal import TimeSeries
df = pd.read_csv('test_files/nav_history_daily.csv')
df = df.sort_values(by=['amfi_code', 'date']) # type: ignore
data_list = [(i.date, i.nav) for i in df[df.amfi_code == 118825].itertuples()]
data = [
("2022-01-01", 10),
("2022-01-02", 12),
("2022-01-03", 14),
("2022-01-04", 16),
("2022-01-06", 18),
("2022-01-07", 20),
("2022-01-09", 22),
("2022-01-10", 24),
("2022-01-11", 26),
("2022-01-13", 28),
("2022-01-14", 30),
("2022-01-15", 32),
("2022-01-16", 34),
]
ts = TimeSeries(data, frequency="D")
print(ts)
start = time.time()
ts_data = TimeSeries(data_list, frequency='M')
print(f"Instantiation took {round((time.time() - start)*1000, 2)} ms")
# ts_data.fill_missing_days()
start = time.time()
# ts_data.calculate_returns(as_on=datetime.datetime(2022, 1, 4), closest='next', years=1)
rr = ts_data.calculate_rolling_returns(datetime.datetime(2015, 1, 1),
datetime.datetime(2022, 1, 21),
frequency='M',
as_on_match='next',
prior_match='previous',
closest='previous',
years=1)
data = [("2022-01-01", 220), ("2022-01-08", 230), ("2022-01-15", 240)]
ts2 = TimeSeries(data, frequency="W")
print(ts2)
# ffill_data = ts_data.bfill()
print(f"Calculation took {round((time.time() - start)*1000, 2)} ms")
rr.sort()
for i in rr[:10]:
synced_ts = ts.sync(ts2)
print("---------\n")
for i in synced_ts:
print(i)
# print(ffill_data)
# print(ts_data)
# print(repr(ts_data))

View File

@ -1,58 +1,52 @@
import pandas as pd
import time
from fincal.fincal import TimeSeries, create_date_series
from fincal.fincal import TimeSeries
dfd = pd.read_csv("test_files/nav_history_daily - Copy.csv")
dfd = dfd[dfd["amfi_code"] == 118825].reset_index(drop=True)
ts = TimeSeries([(i.date, i.nav) for i in dfd.itertuples()], frequency="D")
repr(ts)
# print(ts[['2022-01-31', '2021-05-28']])
# start = time.time()
# dfd = pd.read_csv("test_files/msft.csv") # , dtype=dict(nav=str))
# # dfd = dfd[dfd["amfi_code"] == 118825].reset_index(drop=True)
# print("instantiation took", round((time.time() - start) * 1000, 2), "ms")
# ts = TimeSeries([(i.date, i.nav) for i in dfd.itertuples()], frequency="D")
# print(repr(ts))
# rr = ts.calculate_rolling_returns(from_date='2021-01-01', to_date='2022-01-01', frequency='D', interval_type='days', interval_value=30, compounding=False)
start = time.time()
# mdd = ts.max_drawdown()
# print(mdd)
# print("max drawdown calc took", round((time.time() - start) * 1000, 2), "ms")
# # print(ts[['2022-01-31', '2021-05-28']])
# 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")
# rr = ts.calculate_rolling_returns(
# "2020-02-01",
# "2021-01-01",
# if_not_found="nan",
# compounding=False,
# interval_type="months",
# interval_value=1,
# as_on_match="exact",
# )
# from_date='2021-01-01',
# to_date='2022-01-01',
# frequency='D',
# interval_type='days',
# interval_value=30,
# compounding=False
# )
# for i in rr:
data = [
("2022-01-01", 10),
# ("2022-01-08", 12),
("2022-01-15", 14),
("2022-01-22", 16)
# ("2020-02-07", 18),
# ("2020-02-14", 20),
# ("2020-02-21", 22),
# ("2020-02-28", 24),
# ("2020-03-01", 26),
# ("2020-03-01", 28),
# ("2020-03-01", 30),
# ("2020-03-01", 32),
# ("2021-03-01", 34),
]
ts = TimeSeries(data, "W")
# ts_expanded = ts.expand("D", "ffill", skip_weekends=True)
# for i in ts_expanded:
# print(i)
# returns = ts.calculate_returns(
# "2020-04-25",
# return_actual_date=True,
# closest_max_days=15,
# compounding=True,
# interval_type="days",
# interval_value=90,
# closest="previous",
# if_not_found="fail",
# )
print(ts.get("2022-01-01"))
# print(returns)
volatility = ts.volatility(start_date="2018-01-01", end_date="2021-01-01")
print(volatility)
print(ts.ffill())

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,139 @@
amfi_code,date,nav
118825,01-11-2021,87.925
119528,02-11-2021,378.51
118825,02-11-2021,87.885
119528,03-11-2021,377.79
118825,03-11-2021,87.553
119528,08-11-2021,383.13
118825,08-11-2021,88.743
119528,09-11-2021,383.06
118825,09-11-2021,88.793
119528,10-11-2021,382.71
118825,10-11-2021,88.723
118825,10-11-2021,88.78
119528,11-11-2021,379.28
118825,11-11-2021,88.205
119528,12-11-2021,383.94
118825,12-11-2021,89.025
119528,15-11-2021,383.31
118825,15-11-2021,89.182
119528,16-11-2021,381.08
118825,16-11-2021,88.569
119528,17-11-2021,379.17
118825,17-11-2021,88.09
119528,18-11-2021,375.09
118825,18-11-2021,87.202
119528,22-11-2021,368.16
118825,22-11-2021,85.382
119528,23-11-2021,370.64
118825,23-11-2021,85.978
119528,24-11-2021,369.91
118825,24-11-2021,85.635
119528,25-11-2021,371.33
118825,25-11-2021,86.212
119528,26-11-2021,360.66
118825,26-11-2021,83.748
119528,29-11-2021,360.05
118825,29-11-2021,83.523
119528,30-11-2021,359.8
118825,30-11-2021,83.475
119528,01-12-2021,362.35
118825,01-12-2021,84.269
119528,02-12-2021,366.09
118825,02-12-2021,85.105
119528,03-12-2021,363.11
118825,03-12-2021,84.507
119528,06-12-2021,357.21
118825,06-12-2021,83.113
119528,07-12-2021,362.63
118825,07-12-2021,84.429
119528,08-12-2021,368.73
118825,08-12-2021,85.935
119528,09-12-2021,369.49
118825,09-12-2021,86.045
119528,10-12-2021,369.44
118825,10-12-2021,86.058
119528,13-12-2021,367.6
118825,13-12-2021,85.632
119528,14-12-2021,366.36
118825,14-12-2021,85.502
119528,15-12-2021,364.34
118825,15-12-2021,84.989
119528,16-12-2021,363.73
118825,16-12-2021,84.972
119528,17-12-2021,358.17
118825,17-12-2021,83.83
119528,20-12-2021,349.98
118825,20-12-2021,81.817
119528,21-12-2021,353.71
118825,21-12-2021,82.746
119528,22-12-2021,357.93
118825,22-12-2021,83.776
119528,23-12-2021,360.68
118825,23-12-2021,84.297
119528,24-12-2021,359.11
118825,24-12-2021,83.903
119528,27-12-2021,360.71
118825,27-12-2021,84.227
119528,28-12-2021,363.81
118825,28-12-2021,85.044
119528,29-12-2021,363.2
118825,29-12-2021,85.03
119528,30-12-2021,363.31
118825,30-12-2021,85.047
119528,31-12-2021,366.98
118825,31-12-2021,85.759
119528,03-01-2022,371.76
118825,03-01-2022,87.111
119528,04-01-2022,374.22
118825,04-01-2022,87.804
119528,05-01-2022,376.31
118825,05-01-2022,88.162
119528,06-01-2022,373.64
118825,06-01-2022,87.541
119528,07-01-2022,374.68
118825,07-01-2022,87.818
119528,10-01-2022,378.47
118825,10-01-2022,88.622
119528,11-01-2022,379.34
118825,11-01-2022,88.678
119528,12-01-2022,382.86
118825,12-01-2022,89.332
119528,13-01-2022,383.68
118825,13-01-2022,89.553
119528,14-01-2022,384.02
118825,14-01-2022,89.729
119528,17-01-2022,384.36
118825,17-01-2022,89.733
119528,18-01-2022,380
118825,18-01-2022,88.781
119528,19-01-2022,377.24
118825,19-01-2022,88.059
119528,20-01-2022,374.45
118825,20-01-2022,87.361
119528,21-01-2022,369.86
118825,21-01-2022,86.22
119528,24-01-2022,361.01
118825,24-01-2022,83.907
119528,25-01-2022,364.63
118825,25-01-2022,84.763
119528,27-01-2022,361.95
118825,27-01-2022,83.876
119528,28-01-2022,361.91
118825,28-01-2022,83.829
119528,31-01-2022,367.31
118825,31-01-2022,85.18
119528,04-02-2022,371.01
118825,04-02-2022,86.079
119528,07-02-2022,365.04
118825,07-02-2022,84.867
119528,08-02-2022,365.74
118825,08-02-2022,84.945
119528,09-02-2022,369.85
118825,09-02-2022,85.977
119528,10-02-2022,372.29
118825,10-02-2022,86.5
119528,11-02-2022,366.91
118825,11-02-2022,85.226
119528,14-02-2022,355.47
118825,14-02-2022,82.533
1 amfi_code date nav
2 118825 01-11-2021 87.925
3 119528 02-11-2021 378.51
4 118825 02-11-2021 87.885
5 119528 03-11-2021 377.79
6 118825 03-11-2021 87.553
7 119528 08-11-2021 383.13
8 118825 08-11-2021 88.743
9 119528 09-11-2021 383.06
10 118825 09-11-2021 88.793
11 119528 10-11-2021 382.71
12 118825 10-11-2021 88.723
13 118825 10-11-2021 88.78
14 119528 11-11-2021 379.28
15 118825 11-11-2021 88.205
16 119528 12-11-2021 383.94
17 118825 12-11-2021 89.025
18 119528 15-11-2021 383.31
19 118825 15-11-2021 89.182
20 119528 16-11-2021 381.08
21 118825 16-11-2021 88.569
22 119528 17-11-2021 379.17
23 118825 17-11-2021 88.09
24 119528 18-11-2021 375.09
25 118825 18-11-2021 87.202
26 119528 22-11-2021 368.16
27 118825 22-11-2021 85.382
28 119528 23-11-2021 370.64
29 118825 23-11-2021 85.978
30 119528 24-11-2021 369.91
31 118825 24-11-2021 85.635
32 119528 25-11-2021 371.33
33 118825 25-11-2021 86.212
34 119528 26-11-2021 360.66
35 118825 26-11-2021 83.748
36 119528 29-11-2021 360.05
37 118825 29-11-2021 83.523
38 119528 30-11-2021 359.8
39 118825 30-11-2021 83.475
40 119528 01-12-2021 362.35
41 118825 01-12-2021 84.269
42 119528 02-12-2021 366.09
43 118825 02-12-2021 85.105
44 119528 03-12-2021 363.11
45 118825 03-12-2021 84.507
46 119528 06-12-2021 357.21
47 118825 06-12-2021 83.113
48 119528 07-12-2021 362.63
49 118825 07-12-2021 84.429
50 119528 08-12-2021 368.73
51 118825 08-12-2021 85.935
52 119528 09-12-2021 369.49
53 118825 09-12-2021 86.045
54 119528 10-12-2021 369.44
55 118825 10-12-2021 86.058
56 119528 13-12-2021 367.6
57 118825 13-12-2021 85.632
58 119528 14-12-2021 366.36
59 118825 14-12-2021 85.502
60 119528 15-12-2021 364.34
61 118825 15-12-2021 84.989
62 119528 16-12-2021 363.73
63 118825 16-12-2021 84.972
64 119528 17-12-2021 358.17
65 118825 17-12-2021 83.83
66 119528 20-12-2021 349.98
67 118825 20-12-2021 81.817
68 119528 21-12-2021 353.71
69 118825 21-12-2021 82.746
70 119528 22-12-2021 357.93
71 118825 22-12-2021 83.776
72 119528 23-12-2021 360.68
73 118825 23-12-2021 84.297
74 119528 24-12-2021 359.11
75 118825 24-12-2021 83.903
76 119528 27-12-2021 360.71
77 118825 27-12-2021 84.227
78 119528 28-12-2021 363.81
79 118825 28-12-2021 85.044
80 119528 29-12-2021 363.2
81 118825 29-12-2021 85.03
82 119528 30-12-2021 363.31
83 118825 30-12-2021 85.047
84 119528 31-12-2021 366.98
85 118825 31-12-2021 85.759
86 119528 03-01-2022 371.76
87 118825 03-01-2022 87.111
88 119528 04-01-2022 374.22
89 118825 04-01-2022 87.804
90 119528 05-01-2022 376.31
91 118825 05-01-2022 88.162
92 119528 06-01-2022 373.64
93 118825 06-01-2022 87.541
94 119528 07-01-2022 374.68
95 118825 07-01-2022 87.818
96 119528 10-01-2022 378.47
97 118825 10-01-2022 88.622
98 119528 11-01-2022 379.34
99 118825 11-01-2022 88.678
100 119528 12-01-2022 382.86
101 118825 12-01-2022 89.332
102 119528 13-01-2022 383.68
103 118825 13-01-2022 89.553
104 119528 14-01-2022 384.02
105 118825 14-01-2022 89.729
106 119528 17-01-2022 384.36
107 118825 17-01-2022 89.733
108 119528 18-01-2022 380
109 118825 18-01-2022 88.781
110 119528 19-01-2022 377.24
111 118825 19-01-2022 88.059
112 119528 20-01-2022 374.45
113 118825 20-01-2022 87.361
114 119528 21-01-2022 369.86
115 118825 21-01-2022 86.22
116 119528 24-01-2022 361.01
117 118825 24-01-2022 83.907
118 119528 25-01-2022 364.63
119 118825 25-01-2022 84.763
120 119528 27-01-2022 361.95
121 118825 27-01-2022 83.876
122 119528 28-01-2022 361.91
123 118825 28-01-2022 83.829
124 119528 31-01-2022 367.31
125 118825 31-01-2022 85.18
126 119528 04-02-2022 371.01
127 118825 04-02-2022 86.079
128 119528 07-02-2022 365.04
129 118825 07-02-2022 84.867
130 119528 08-02-2022 365.74
131 118825 08-02-2022 84.945
132 119528 09-02-2022 369.85
133 118825 09-02-2022 85.977
134 119528 10-02-2022 372.29
135 118825 10-02-2022 86.5
136 119528 11-02-2022 366.91
137 118825 11-02-2022 85.226
138 119528 14-02-2022 355.47
139 118825 14-02-2022 82.533

View File

@ -0,0 +1,11 @@
amfi_code,date,nav
118825,31-03-2021,70.69
118825,30-04-2021,70.39
118825,31-05-2021,74.85
118825,30-07-2021,78.335
118825,31-08-2021,83.691
118825,30-09-2021,86.128
118825,29-10-2021,86.612
118825,30-11-2021,83.475
118825,31-01-2022,85.18
118825,17-02-2022,84.33
1 amfi_code date nav
2 118825 31-03-2021 70.69
3 118825 30-04-2021 70.39
4 118825 31-05-2021 74.85
5 118825 30-07-2021 78.335
6 118825 31-08-2021 83.691
7 118825 30-09-2021 86.128
8 118825 29-10-2021 86.612
9 118825 30-11-2021 83.475
10 118825 31-01-2022 85.18
11 118825 17-02-2022 84.33

View File

@ -1,3 +1,7 @@
amfi_code,date,nav
118825,31-03-2021,70.69
118825,30-04-2021,70.39

1 amfi_code date nav
1 amfi_code date nav
2 118825 31-03-2021 70.69
3 118825 30-04-2021 70.39
4 118825 31-05-2021 74.85
5 amfi_code 118825 date 30-07-2021 nav 78.335
6 118825 31-03-2021 31-08-2021 70.69 83.691
7 118825 30-04-2021 30-09-2021 70.39 86.128

View File

@ -3,36 +3,23 @@
{
"cell_type": "code",
"execution_count": 1,
"id": "3f7938c0-98e3-43b8-86e8-4f000cda7ce5",
"id": "e1ecfa55",
"metadata": {},
"outputs": [],
"source": [
"import datetime\n",
"import pandas as pd\n",
"\n",
"from fincal.fincal import TimeSeries\n",
"from fincal.core import Series"
"import fincal as fc"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "4b8ccd5f-dfff-4202-82c4-f66a30c122b6",
"id": "ccac3896",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: total: 125 ms\n",
"Wall time: 99 ms\n"
]
},
{
"data": {
"text/plain": [
"[(datetime.datetime(2022, 1, 31, 0, 0), 310.980011),\n",
" (datetime.datetime(2021, 5, 28, 0, 0), 249.67999300000002)]"
"fincal.fincal.TimeSeries"
]
},
"execution_count": 2,
@ -41,207 +28,45 @@
}
],
"source": [
"%%time\n",
"dfd = pd.read_csv('test_files/msft.csv')\n",
"# dfd = dfd[dfd['amfi_code'] == 118825].reset_index(drop=True)\n",
"ts = TimeSeries([(i.date, i.nav) for i in dfd.itertuples()], frequency='D')\n",
"repr(ts)\n",
"ts[['2022-01-31', '2021-05-28']]"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "ffd9665d",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(datetime.datetime(2022, 1, 31, 0, 0), 310.980011)"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"ts['2022-01-31']"
"fc.TimeSeries"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "086d4377-d1b1-4e51-84c0-39dee28ef75e",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: total: 15.6 ms\n",
"Wall time: 16 ms\n"
]
},
{
"data": {
"text/plain": [
"TimeSeries([(datetime.datetime(2022, 1, 3, 0, 0), 334.75),\n",
"\t (datetime.datetime(2022, 1, 4, 0, 0), 329.01001),\n",
"\t (datetime.datetime(2022, 1, 5, 0, 0), 316.380005)\n",
"\t ...\n",
"\t (datetime.datetime(2022, 2, 16, 0, 0), 299.5),\n",
"\t (datetime.datetime(2022, 2, 17, 0, 0), 290.730011),\n",
"\t (datetime.datetime(2022, 2, 18, 0, 0), 287.929993)], frequency='D')"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"%%time\n",
"s = ts.dates >= '2022-01-01'\n",
"ts[s]"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "e815edc9-3746-4192-814e-bd27b2771a0c",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: total: 15.6 ms\n",
"Wall time: 4 ms\n"
]
},
{
"data": {
"text/plain": [
"[(datetime.datetime(1992, 2, 19, 0, 0), 2.398438),\n",
" (datetime.datetime(1992, 2, 20, 0, 0), 2.447917),\n",
" (datetime.datetime(1992, 2, 21, 0, 0), 2.385417),\n",
" (datetime.datetime(1992, 2, 24, 0, 0), 2.3932290000000003),\n",
" (datetime.datetime(1992, 2, 25, 0, 0), 2.411458),\n",
" (datetime.datetime(1992, 2, 26, 0, 0), 2.541667),\n",
" (datetime.datetime(1992, 2, 27, 0, 0), 2.601563),\n",
" (datetime.datetime(1992, 2, 28, 0, 0), 2.572917),\n",
" (datetime.datetime(1992, 3, 2, 0, 0), 2.5625),\n",
" (datetime.datetime(1992, 3, 3, 0, 0), 2.567708)]"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"%%time\n",
"ts.iloc[:10]"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "dc469722-c816-4b57-8d91-7a3b865f86be",
"metadata": {
"tags": []
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: total: 297 ms\n",
"Wall time: 290 ms\n"
]
}
],
"source": [
"%%time\n",
"from_date = datetime.date(1994, 1, 1)\n",
"to_date = datetime.date(2022, 1, 1)\n",
"# print(ts.calculate_returns(to_date, years=7))\n",
"rr = ts.calculate_rolling_returns(from_date, to_date)"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "e5d357b4-4fe5-4a0a-8107-0ab6828d7c41",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"TimeSeries([(datetime.datetime(1994, 1, 3, 0, 0), -0.06149359306648605),\n",
"\t (datetime.datetime(1994, 1, 4, 0, 0), -0.05433177603118022),\n",
"\t (datetime.datetime(1994, 1, 5, 0, 0), -0.04913276300578029)\n",
"\t ...\n",
"\t (datetime.datetime(2021, 12, 29, 0, 0), 0.5255410267822715),\n",
"\t (datetime.datetime(2021, 12, 30, 0, 0), 0.5306749265370103),\n",
"\t (datetime.datetime(2021, 12, 31, 0, 0), 0.5120942811985818)], frequency='D')"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"rr"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "4bad2efa",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Series([1.0, 2.0, 3.0, 4.0, 5.0])"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"sr = Series([1, 2, 3, 4, 5], 'number')\n",
"sr"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "adceda69",
"id": "a54bfbdf",
"metadata": {},
"outputs": [],
"source": [
"from fincal.fincal import TimeSeries\n",
"import datetime\n",
"ts = TimeSeries(data = [('2021-01-01', 220), ('2021-02-01', 230), ('2021-03-01', 240)], frequency='M')"
"data = [\n",
" (\"2022-01-01\", 10),\n",
" (\"2022-01-02\", 12),\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",
" # (\"2022-01-10\", 24),\n",
" # (\"2022-01-11\", 26),\n",
" # (\"2022-01-12\", 28),\n",
" # (\"2023-01-01\", 30),\n",
" # (\"2023-01-02\", 32),\n",
" # (\"2023-01-03\", 34),\n",
"]"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "68cf9f8c",
"id": "fcc5f8f1",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(datetime.datetime(2021, 2, 1, 0, 0), 0.045454545454545414)"
"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')"
]
},
"execution_count": 5,
@ -250,38 +75,294 @@
}
],
"source": [
"ts.calculate_returns('2021-02-05', interval_type='months', interval_value=1, compounding=False)"
"ts = fc.TimeSeries(data, 'M')\n",
"ts"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "a583347f",
"metadata": {},
"outputs": [],
"source": [
"D = {'a': 1, 'b': 2}"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "f79ac787",
"execution_count": 7,
"id": "c9e9cb1b",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"dict_keys(['a', 'b'])"
"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')"
]
},
"execution_count": 10,
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"D.keys()"
"ts['2022-01-04'] = 15\n",
"ts"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "8e812756",
"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"
}
],
"source": [
"ts.expand('W', 'ffill')"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "55918da9-2df6-4773-9ca0-e19b52c3ece2",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"TimeSeries([(datetime.datetime(2022, 1, 1, 0, 0), 10),\n",
"\t(datetime.datetime(2022, 4, 1, 0, 0), 28),\n",
"\t(datetime.datetime(2022, 7, 1, 0, 0), 28),\n",
"\t(datetime.datetime(2022, 10, 1, 0, 0), 28),\n",
"\t(datetime.datetime(2023, 1, 1, 0, 0), 30)], frequency='Q')"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"ts.shrink('Q', 'ffill')"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "36eefec7-7dbf-4a28-ac50-2e502d9d6864",
"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)]"
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "39bd8598-ab0f-4c81-8428-ad8248e686d3",
"metadata": {},
"outputs": [],
"source": [
"week_ts = fc.TimeSeries(weekly_data, 'W')"
]
},
{
"cell_type": "code",
"execution_count": 22,
"id": "d64dd3c6-4295-4301-90e4-5c74ea23c4af",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"(datetime.datetime(2017, 1, 1, 0, 0), 67)\n",
"(datetime.datetime(2017, 2, 1, 0, 0), 85)\n",
"(datetime.datetime(2017, 3, 1, 0, 0), 76)\n",
"(datetime.datetime(2017, 4, 1, 0, 0), 78)\n",
"(datetime.datetime(2017, 5, 1, 0, 0), 65)\n",
"(datetime.datetime(2017, 6, 1, 0, 0), 74)\n"
]
}
],
"source": [
"for i in week_ts.shrink('M', 'ffill', skip_weekends=True):\n",
" print(i)"
]
},
{
"cell_type": "code",
"execution_count": 23,
"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"
]
}
],
"source": [
"week_ts.sync(ts)"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "4755aea3-3655-4651-91d2-8e54c24303bc",
"metadata": {},
"outputs": [],
"source": [
"import fincal as fc"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "bd9887b3-d98a-4c80-8f95-ef7b7f19ded4",
"metadata": {},
"outputs": [
{
"name": "stdout",
"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"
]
}
],
"source": [
"%%time\n",
"ts = fc.read_csv('test_files/msft.csv', frequency='D')"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "b7c176d4-d89f-4bda-9d67-75463eb90468",
"metadata": {},
"outputs": [
{
"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, 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",
"(datetime.datetime(2022, 2, 17, 0, 0), 290.730011)\n",
"(datetime.datetime(2022, 2, 18, 0, 0), 287.929993)\n"
]
}
],
"source": [
"for i in ts.tail(8):\n",
" print(i)"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "69c57754-a6fb-4881-9359-ba17c7fb8be5",
"metadata": {},
"outputs": [
{
"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"
]
}
],
"source": [
"%%time\n",
"ts['2022-02-12'] = 295"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "7aa02023-406e-4700-801c-c06390ddf914",
"metadata": {},
"outputs": [
{
"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"
]
},
{
"data": {
"text/plain": [
"{'start_date': datetime.datetime(1999, 12, 27, 0, 0),\n",
" 'end_date': datetime.datetime(2009, 3, 9, 0, 0),\n",
" 'drawdown': -0.7456453305351521}"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"%%time\n",
"ts.max_drawdown()"
]
}
],
@ -301,7 +382,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.3"
"version": "3.10.2"
}
},
"nbformat": 4,

View File

@ -179,6 +179,33 @@ class TestSlicing:
assert len(ts_slice) == 2
class TestSetitem:
data = [("2021-01-01", 220), ("2021-01-04", 230), ("2021-03-07", 240)]
def test_setitem(self):
ts = TimeSeriesCore(self.data, frequency="D")
assert len(ts) == 3
ts["2021-01-02"] = 225
assert len(ts) == 4
assert ts["2021-01-02"][1] == 225
ts["2021-01-02"] = 227.6
assert len(ts) == 4
assert ts["2021-01-02"][1] == 227.6
def test_errors(self):
ts = TimeSeriesCore(self.data, frequency="D")
with pytest.raises(TypeError):
ts["2021-01-03"] = "abc"
with pytest.raises(NotImplementedError):
ts.iloc[4] = 4
with pytest.raises(ValueError):
ts["abc"] = 12
class TestTimeSeriesCoreHeadTail:
data = [
("2021-01-01", 220),

View File

@ -5,10 +5,14 @@ from typing import List
import pytest
from dateutil.relativedelta import relativedelta
from fincal.core import AllFrequencies, Frequency
from fincal import (
AllFrequencies,
FincalOptions,
Frequency,
TimeSeries,
create_date_series,
)
from fincal.exceptions import DateNotFoundError
from fincal.fincal import TimeSeries, create_date_series
from fincal.utils import FincalOptions
def create_prices(s0: float, mu: float, sigma: float, num_prices: int) -> list:
@ -507,3 +511,18 @@ class TestDrawdown:
"drawdown": -0.2584760499552089,
}
assert mdd == expeced_response
class TestSync:
def test_weekly_to_daily(self):
daily_data = create_test_data(AllFrequencies.D, num=15)
weekly_data = create_test_data(AllFrequencies.W, num=3)
daily_ts = TimeSeries(daily_data, frequency="D")
weekly_ts = TimeSeries(weekly_data, frequency="W")
synced_weekly_ts = daily_ts.sync(weekly_ts)
assert len(daily_ts) == len(synced_weekly_ts)
assert synced_weekly_ts.frequency == AllFrequencies.D
assert "2017-01-02" in synced_weekly_ts
assert synced_weekly_ts["2017-01-02"][1] == synced_weekly_ts["2017-01-01"][1]