# # Copyright 2015 Quantopian, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import annotations import typing if typing.TYPE_CHECKING: import pandas as pd from exchange_calendars import ExchangeCalendar # ruff: noqa: N818 class CalendarError(Exception): msg = None def __init__(self, **kwargs): self.kwargs = kwargs def __str__(self): return self.msg.format(**self.kwargs) __unicode__ = __str__ __repr__ = __str__ class InvalidCalendarName(CalendarError): """ Raised when a calendar with an invalid name is requested. """ msg = "The requested ExchangeCalendar, {calendar_name}, does not exist." class CalendarNameCollision(CalendarError): """ Raised when the static calendar registry already has a calendar with a given name. """ msg = "A calendar with the name {calendar_name} is already registered." class CyclicCalendarAlias(CalendarError): """ Raised when calendar aliases form a cycle. """ msg = "Cycle in calendar aliases: [{cycle}]" class NoSessionsError(CalendarError): """Raised if a requested calendar would have no sessions. NoSessionsError should be raised if `get_calendar` `start` and `end` parameters are passed as dates between and inclusive of which there are no sessions for the given calendar. """ msg = ( "The requested ExchangeCalendar, {calendar_name}, cannot be created as" " there would be no sessions between the requested `start` ('{start}')" " and `end` ('{end}') dates." ) class NotSessionError(ValueError): """Input does not represent a valid session. Raised if parameter expecting a session label receives input that parses correctly (UTC midnight) although is not a session. Parameters ---------- calendar Calendar for which `ts` assumed as a session. ts Timestamp assumed as a session. param_name Name of a parameter that was to receive a session label. """ def __init__(self, calendar: ExchangeCalendar, ts: pd.Timestamp, param_name: str): self.calendar = calendar self.ts = ts self.param_name = param_name def __str__(self) -> str: msg = ( f"Parameter `{self.param_name}` takes a session" f" although received input that parsed to '{self.ts}' which" ) if self.ts < self.calendar.first_session: msg += ( " is earlier than the first session of calendar" f" '{self.calendar.name}' ('{self.calendar.first_session}')." ) elif self.ts > self.calendar.last_session: msg += ( " is later than the last session of calendar" f" '{self.calendar.name}' ('{self.calendar.last_session}')." ) else: msg += f" is not a session of calendar '{self.calendar.name}'." return msg class DateOutOfBounds(ValueError): """A date required to be within sessions' bounds is not. Parameters ---------- calendar Calendar for which `date` required to be within sessions' bounds. date Date required to be within `calendar`'s sessions' bounds. param_name Name of a parameter receiving date. """ def __init__(self, calendar: ExchangeCalendar, date: pd.Timestamp, param_name: str): self.calendar = calendar self.date = date self.param_name = param_name def __str__(self) -> str: msg = f"Parameter `{self.param_name}` receieved as '{self.date}' although" if self.date < self.calendar.first_session: msg += ( " cannot be earlier than the first session of calendar" f" '{self.calendar.name}' ('{self.calendar.first_session}')." ) elif self.date > self.calendar.last_session: msg += ( " cannot be later than the last session of calendar" f" '{self.calendar.name}' ('{self.calendar.last_session}')." ) else: assert ( self.date < self.calendar.first_session or self.date > self.calendar.last_session ) return msg class NotTradingMinuteError(ValueError): """A timestamp assumed as a trading minute is not a trading minute. Parameters ---------- calendar Calendar for which `minute` assumed as a trading minute. minute Minute assumed as a trading minute. param_name Name of a parameter that was to receive a trading minute. """ def __init__( self, calendar: ExchangeCalendar, minute: pd.Timestamp, param_name: str, ): self.calendar = calendar self.minute = minute self.param_name = param_name def __str__(self) -> str: msg = ( f"Parameter `{self.param_name}` takes a trading minute although" f" received input that parsed to '{self.minute}' which" ) if self.minute < self.calendar.first_minute: msg += ( " is earlier than the first trading minute of calendar" f" '{self.calendar.name}' ('{self.calendar.first_session}')." ) elif self.minute > self.calendar.last_minute: msg += ( " is later than the last trading minute of calendar" f" '{self.calendar.name}' ('{self.calendar.last_session}')." ) else: msg += f" is not a trading minute of calendar '{self.calendar.name}'." return msg class MinuteOutOfBounds(ValueError): """A minute required to be within bounds of trading minutes is not. Parameters ---------- calendar Calendar for which `minute` required to be within bounds of trading minutes. minute Minute required to be within bounds of `calendar`'s trading minutes. param_name Name of a parameter receiving `minute`. """ def __init__( self, calendar: ExchangeCalendar, minute: pd.Timestamp, param_name: str ): self.calendar = calendar self.minute = minute self.param_name = param_name def __str__(self) -> str: msg = f"Parameter `{self.param_name}` receieved as '{self.minute}' although" if self.minute < self.calendar.first_minute: msg += ( " cannot be earlier than the first trading minute of calendar" f" '{self.calendar.name}' ('{self.calendar.first_minute}')." ) elif self.minute > self.calendar.last_minute: msg += ( " cannot be later than the last trading minute of calendar" f" '{self.calendar.name}' ('{self.calendar.last_minute}')." ) else: assert ( self.minute < self.calendar.first_minute or self.minute > self.calendar.last_minute ) return msg class RequestedSessionOutOfBounds(ValueError): """The requested session would fall beyond calendar bounds. Parameters ---------- calendar Calendar for which session would be out-of-bounds. too_early True if requested session would be earlier than the first calendar session. False if requested session would be later than the last calendar session. """ def __init__(self, calendar: ExchangeCalendar, too_early: bool): self.calendar = calendar self.adverb = "before" if too_early else "after" self.position = "first" if too_early else "last" self.bound = calendar.first_session if too_early else calendar.last_session def __str__(self) -> str: return ( f"Requested session would fall {self.adverb} the calendar's {self.position}" f" session ('{self.bound}')." ) class RequestedMinuteOutOfBounds(ValueError): """The requested trading minute would fall beyond calendar bounds. Parameters ---------- calendar Calendar for which minute would be out-of-bounds. too_early True if requested minute would be earlier than the first calendar minute. False if requested minute would be later than the last calendar minute. """ def __init__(self, calendar: ExchangeCalendar, too_early: bool): self.calendar = calendar self.adverb = "before" if too_early else "after" self.position = "first" if too_early else "last" self.bound = calendar.first_minute if too_early else calendar.last_minute def __str__(self) -> str: return ( f"Requested minute would fall {self.adverb} the calendar's {self.position}" f" trading minute ('{self.bound}')." ) class IndexOverlapError(ValueError): """Periods implied by indices overlap.""" class IntervalsOverlapError(IndexOverlapError): """Intervals of requested trading index would overlap.""" # pylint: disable=missing-return-type-doc def __str__(self): return ( "Unable to create trading index as intervals would overlap." " This can occur when the frequency is longer than a break or" " the gap between one session's close and the next session's" " open (as reduced by any alignment). To shorten intervals" " that would otherwise overlap either pass `curtail_overlaps`" " as True or pass `force_close` and/or `force_break_close` as True." ) class IndicesOverlapError(IndexOverlapError): """Indices of requested trading index would overlap.""" # pylint: disable=missing-return-type-doc def __str__(self): return ( "Unable to create trading index as an indice would fall to the" " right of (later than) the subsequent indice. This can occur" " when the frequency is longer than a break or the gap between" " one session's close and the next session's open (as reduced" " by any alignment). Consider passing `closed` as `left` or" " passing `force_close` and/or `force_break_close` as True." )