"""Declaring types used throughout the project."""
from __future__ import annotations
import datetime as dt
from enum import StrEnum
from pprint import pformat
from typing import (
TYPE_CHECKING,
Annotated,
ClassVar,
Literal,
Self,
TypeAlias,
TypeVar,
)
from annotated_types import MinLen
from numpy import datetime64
from pandas import Series, Timestamp
from pydantic import BaseModel, Field, StringConstraints
if TYPE_CHECKING:
from pandas import Series as _Series
SeriesFloat = _Series[float]
else:
SeriesFloat = Series
__all__ = ["ValueType"]
SeriesOrFloat_co = TypeVar("SeriesOrFloat_co", float, SeriesFloat, covariant=True)
CountryStringType = Annotated[
str,
StringConstraints(
strip_whitespace=True,
pattern=r"^[A-Z]{2}$",
to_upper=True,
min_length=2,
max_length=2,
strict=True,
),
]
CountrySetType: TypeAlias = Annotated[set[CountryStringType], MinLen(1)]
CountriesType: TypeAlias = CountrySetType | CountryStringType
[docs]
class Countries(BaseModel):
"""Declare Countries."""
countryinput: CountriesType
CurrencyStringType = Annotated[
str,
StringConstraints(
pattern=r"^[A-Z]{3}$",
to_upper=True,
min_length=3,
max_length=3,
strict=True,
strip_whitespace=True,
),
]
[docs]
class Currency(BaseModel):
"""Declare Currency."""
ccy: CurrencyStringType
DateStringType = Annotated[
str,
StringConstraints(
pattern=r"^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$",
strip_whitespace=True,
strict=True,
min_length=10,
max_length=10,
),
]
DateListType: TypeAlias = Annotated[list[DateStringType], MinLen(1)]
ValueListType: TypeAlias = Annotated[list[float], MinLen(1)]
DaysInYearType = Annotated[int, Field(strict=True, ge=1, le=366)]
DateType = str | dt.date | dt.datetime | datetime64 | Timestamp
PlotlyConfigType = (
str
| int
| float
| bool
| list[str]
| dict[str, str | int | float | bool | list[str]]
)
PlotlyLayoutType = dict[str, PlotlyConfigType]
CaptorLogoType = dict[str, str | float]
LiteralJsonOutput = Literal["values", "tsdf"]
LiteralTrunc = Literal["before", "after", "both"]
LiteralLinePlotMode = (
Literal[
"lines",
"markers",
"lines+markers",
"lines+text",
"markers+text",
"lines+markers+text",
]
| None
)
LiteralHowMerge = Literal["outer", "inner"]
LiteralQuantileInterp = Literal["linear", "lower", "higher", "midpoint", "nearest"]
LiteralBizDayFreq = Literal["B", "BME", "BQE", "BYE"]
LiteralPandasReindexMethod = (
Literal["pad", "ffill", "backfill", "bfill", "nearest"] | None
)
LiteralNanMethod = Literal["fill", "drop"]
LiteralCaptureRatio = Literal["up", "down", "both"]
LiteralBarPlotMode = Literal["stack", "group", "overlay", "relative"]
LiteralPlotlyOutput = Literal["file", "div"]
LiteralPlotlyJSlib = Literal[True, False, "cdn"]
LiteralPlotlyHistogramPlotType = Literal["bars", "lines"]
LiteralPlotlyHistogramBarMode = Literal["stack", "group", "overlay", "relative"]
LiteralPlotlyHistogramCurveType = Literal["normal", "kde"]
LiteralPlotlyHistogramHistNorm = Literal[
"percent",
"probability",
"density",
"probability density",
]
LiteralPortfolioWeightings = Literal[
"eq_weights", "inv_vol", "max_div", "min_vol_overweight"
]
LiteralMinimizeMethods = Literal[
"SLSQP",
"Nelder-Mead",
"Powell",
"CG",
"BFGS",
"Newton-CG",
"L-BFGS-B",
"TNC",
"COBYLA",
"trust-constr",
"dogleg",
"trust-ncg",
"trust-exact",
"trust-krylov",
]
LiteralSeriesProps = Literal[
"value_ret",
"geo_ret",
"arithmetic_ret",
"vol",
"downside_deviation",
"ret_vol_ratio",
"sortino_ratio",
"kappa3_ratio",
"z_score",
"skew",
"kurtosis",
"positive_share",
"var_down",
"cvar_down",
"vol_from_var",
"worst",
"worst_month",
"max_drawdown_cal_year",
"max_drawdown",
"max_drawdown_date",
"first_idx",
"last_idx",
"length",
"span_of_days",
"yearfrac",
"periods_in_a_year",
"autocorr",
"partial_autocorr",
]
LiteralFrameProps = Literal[
"value_ret",
"geo_ret",
"arithmetic_ret",
"autocorr",
"vol",
"downside_deviation",
"ret_vol_ratio",
"sortino_ratio",
"kappa3_ratio",
"z_score",
"skew",
"kurtosis",
"positive_share",
"var_down",
"cvar_down",
"vol_from_var",
"worst",
"worst_month",
"max_drawdown",
"max_drawdown_date",
"max_drawdown_cal_year",
"first_indices",
"last_indices",
"lengths_of_items",
"span_of_days_all",
]
[docs]
class PropertiesList(list[str]):
"""Base class for allowed property arguments definition."""
allowed_strings: ClassVar[set[str]] = {
"value_ret",
"geo_ret",
"arithmetic_ret",
"vol",
"downside_deviation",
"ret_vol_ratio",
"sortino_ratio",
"kappa3_ratio",
"omega_ratio",
"z_score",
"skew",
"kurtosis",
"positive_share",
"var_down",
"cvar_down",
"vol_from_var",
"worst",
"worst_month",
"max_drawdown",
"max_drawdown_date",
"max_drawdown_cal_year",
}
def _validate(self: Self) -> None:
"""Validate the string input of the all_properties method."""
seen = set()
invalids = set()
duplicates = set()
msg = ""
for item in self:
if item not in self.allowed_strings:
invalids.add(item)
if item in seen:
duplicates.add(item)
seen.add(item)
if len(invalids) != 0:
msg += (
f"Invalid string(s): {list(invalids)}.\nAllowed strings are:"
f"\n{pformat(self.allowed_strings)}\n"
)
if len(duplicates) != 0:
msg += f"Duplicate string(s): {list(duplicates)}."
if len(msg) != 0:
raise PropertiesInputValidationError(msg)
[docs]
class OpenTimeSeriesPropertiesList(PropertiesList):
"""Allowed property arguments for the OpenTimeSeries class."""
allowed_strings: ClassVar[set[str]] = PropertiesList.allowed_strings | {
"first_idx",
"last_idx",
"length",
"span_of_days",
"yearfrac",
"periods_in_a_year",
"autocorr",
"partial_autocorr",
}
[docs]
def __init__(
self: Self,
*args: LiteralSeriesProps,
) -> None:
"""Property arguments for the OpenTimeSeries class."""
super().__init__(args)
self._validate()
[docs]
class OpenFramePropertiesList(PropertiesList):
"""Allowed property arguments for the OpenFrame class."""
allowed_strings: ClassVar[set[str]] = PropertiesList.allowed_strings | {
"autocorr",
"first_indices",
"last_indices",
"lengths_of_items",
"span_of_days_all",
}
[docs]
def __init__(self: Self, *args: LiteralFrameProps) -> None:
"""Property arguments for the OpenFrame class."""
super().__init__(args)
self._validate()
[docs]
class ValueType(StrEnum):
"""Enum types of OpenTimeSeries to identify the output."""
EWMA_VOL = "EWMA volatility"
EWMA_VAR = "EWMA VaR"
PRICE = "Price(Close)"
RTRN = "Return(Total)"
RELRTRN = "Relative return"
ROLLBETA = "Beta"
ROLLCORR = "Rolling correlation"
ROLLCVAR = "Rolling CVaR"
ROLLINFORATIO = "Information Ratio"
ROLLRTRN = "Rolling returns"
ROLLVAR = "Rolling VaR"
ROLLVOL = "Rolling volatility"
[docs]
class MixedValuetypesError(Exception):
"""Raised when provided timeseries valuetypes are not the same."""
[docs]
class AtLeastOneFrameError(Exception):
"""Raised when none of the possible frame inputs is provided."""
[docs]
class DateAlignmentError(Exception):
"""Raised when date input is not aligned with existing range."""
[docs]
class NumberOfItemsAndLabelsNotSameError(Exception):
"""Raised when number of labels is not matching the number of timeseries."""
[docs]
class InitialValueZeroError(Exception):
"""Raised when a calculation cannot be performed due to initial value(s) zero."""
[docs]
class CountriesNotStringNorListStrError(Exception):
"""Raised when countries argument is not provided in correct format."""
[docs]
class MarketsNotStringNorListStrError(Exception):
"""Raised when markets argument is not provided in correct format."""
[docs]
class TradingDaysNotAboveZeroError(Exception):
"""Raised when trading days argument is not above zero."""
[docs]
class BothStartAndEndError(Exception):
"""Raised when both start and end dates are provided."""
[docs]
class NoWeightsError(Exception):
"""Raised when no weights are provided to function where necessary."""
[docs]
class LabelsNotUniqueError(Exception):
"""Raised when provided label names are not unique."""
[docs]
class MergingResultedInEmptyError(Exception):
"""Raised when a merge resulted in an empty DataFrame."""
[docs]
class IncorrectArgumentComboError(Exception):
"""Raised when correct combination of arguments is not provided."""
[docs]
class ResampleDataLossError(Exception):
"""Raised when user attempts to run resample_to_business_period_ends on returns."""
class WeightsNotProvidedError(Exception):
"""Raised when weights are not provided."""
class MultipleCurrenciesError(Exception):
"""Raised when multiple currencies are provided."""
class PortfolioItemsNotWithinFrameError(Exception):
"""Raised when portfolio items are not within frame."""
class MaxDiversificationNaNError(Exception):
"""Raised when max_div weight strategy produces NaN values."""
class MaxDiversificationNegativeWeightsError(Exception):
"""Raised when max_div weight strategy produces negative weights."""