Files
MoFin/venv/lib/python3.12/site-packages/exchange_calendars/pandas_extensions/offsets.py
T
知微 fa45d8aa5f fix: 小果地址统一node122(兼容LAN+EasyTier)
- health_checklist.json: 192.168.1.122→node122
- ocr_client.py: docstring IP→node122
- docs/market-data-requirements.md: IP→node122
- 所有API调用通过ProxyHandler({})绕过系统代理
  Privoxy对node122:18003返回500,直连正常
2026-06-30 02:56:35 +08:00

348 lines
11 KiB
Python

# https://github.com/pandas-dev/pandas/blob/master/pandas/tseries/offsets.py
# https://github.com/pandas-dev/pandas/blob/master/pandas/_libs/tslibs/offsets.pyx
import contextlib
from datetime import date, datetime, timedelta
import numpy as np
import pandas as pd
import toolz
from dateutil.easter import EASTER_ORTHODOX, easter
from pandas._libs.tslibs.conversion import localize_pydatetime
from pandas._libs.tslibs.offsets import apply_wraps
from pandas.tseries.offsets import BaseOffset, CustomBusinessDay
class CompositeCustomBusinessDay(CustomBusinessDay):
_prefix = "C"
_attributes = (
"n",
"normalize",
"weekmask",
"holidays",
"calendar",
"offset",
"business_days",
)
def __init__(
self,
n=1,
normalize=False,
weekmask="Mon Tue Wed Thu Fri",
holidays=None,
calendar=None,
offset=timedelta(0),
business_days=None,
):
CustomBusinessDay.__init__(
self, n, normalize, weekmask, holidays, calendar, offset
)
self.business_days = business_days
def __setstate__(self, state):
self.business_days = state.pop("business_days")
CustomBusinessDay.__setstate__(self, state)
@property
def business_days(self):
"""
Returns list of tuples of (start_date, end_date, custom_business_day)
which overrides default behavior for the given interval, which starts
from start_date to end_date, inclusive in both sides.
"""
return tuple(self._business_days)
@business_days.setter
def business_days(self, business_days):
self._business_days = []
self._business_days_index = pd.IntervalIndex([], closed="both")
self._business_days_all_index = pd.IntervalIndex([], closed="both")
if business_days is not None:
self._business_days = business_days
business_days_intervals = []
for start_date, end_date, _ in self._business_days:
if start_date is None:
start_date = pd.Timestamp.min # noqa: PLW2901
else:
start_date = pd.Timestamp(start_date) # noqa: PLW2901
if end_date is None:
end_date = pd.Timestamp.max # noqa: PLW2901
else:
end_date = pd.Timestamp(end_date) # noqa: PLW2901
interval = pd.Interval(start_date, end_date, closed="both")
business_days_intervals.append(interval)
self._business_days_index = pd.IntervalIndex(
business_days_intervals, closed="both"
)
business_days_all_intervals = []
right = self._business_days_index[0]
if pd.Timestamp.min < right.left:
interval = pd.Interval(
pd.Timestamp.min, right.left - pd.Timedelta(1, unit="D")
)
business_days_all_intervals.append(interval)
business_days_all_intervals.append(right)
for left, right in toolz.sliding_window(
2, toolz.concatv(self._business_days_index)
):
if pd.Timestamp(
(right.left - left.right).days, unit="D"
) > pd.Timestamp(1, unit="D"):
interval = pd.Interval(
left.right + pd.Timedelta(1, unit="D"),
right.left - pd.Timedelta(1, unit="D"),
)
business_days_all_intervals.append(interval)
business_days_all_intervals.append(right)
left = self._business_days_index[-1]
if left.right < pd.Timestamp.max:
interval = pd.Interval(
left.right + pd.Timedelta(1, unit="D"), pd.Timestamp.max
)
business_days_all_intervals.append(interval)
self._business_days_all_index = pd.IntervalIndex(
business_days_all_intervals, closed="both"
)
def _as_custom_business_day(self):
return CustomBusinessDay(
self.n,
self.normalize,
self.weekmask,
self.holidays,
self.calendar,
self.offset,
)
def _custom_business_day_for(
self, other, n=None, is_edge=False, with_interval=False
):
loc = self._business_days_all_index.get_loc(other)
if is_edge and n is not None:
loc += np.sign(n)
interval = self._business_days_all_index[loc]
try:
loc = self._business_days_index.get_loc(interval.left)
except KeyError:
bday = self._as_custom_business_day()
else:
bday = self._business_days[loc][-1]
if n is not None:
bday = bday.base * n
if with_interval:
return bday, interval
return bday
def _moved(self, from_date, to_date, bday):
return np.busday_count(
np.datetime64(from_date.date()),
np.datetime64(to_date.date()),
busdaycal=bday.calendar,
)
@apply_wraps
def _apply(self, other):
if isinstance(other, datetime):
moved = 0
remaining = self.n - moved
bday, interval = self._custom_business_day_for(
other, remaining, with_interval=True
)
result = bday + other
while not interval.left <= result <= interval.right:
previous_other = other
if result < interval.left:
other = interval.left
elif result > interval.right:
other = interval.right
else:
raise RuntimeError("Should not reach here")
moved += self._moved(previous_other, other, bday)
remaining = self.n - moved
if remaining == 0:
break
bday, interval = self._custom_business_day_for(
other, remaining, is_edge=True, with_interval=True
)
result = bday._apply(other) # noqa: SLF001
return result
return super().apply(other)
# backwards compat
apply = _apply
def is_on_offset(self, dt):
if self.normalize and not _is_normalized(dt):
return False
day64 = _to_dt64D(dt)
bday = self._custom_business_day_for(day64)
return np.is_busday(day64, busdaycal=bday.calendar)
def _is_normalized(dt):
if dt.hour != 0 or dt.minute != 0 or dt.second != 0 or dt.microsecond != 0:
# Regardless of whether dt is datetime vs Timestamp
return False
if isinstance(dt, pd.Timestamp):
return dt.nanosecond == 0
return True
def _to_dt64D(dt): # noqa: N802
# Currently
# > np.datetime64(dt.datetime(2013,5,1),dtype='datetime64[D]')
# numpy.datetime64('2013-05-01T02:00:00.000000+0200')
# Thus astype is needed to cast datetime to datetime64[D]
if getattr(dt, "tzinfo", None) is not None:
# Get the nanosecond timestamp,
# equiv `Timestamp(dt).value` or `dt.timestamp() * 10**9`
dt = pd.Timestamp(dt).value
dt = np.int64(dt).astype("datetime64[ns]")
else:
dt = np.datetime64(dt)
if dt.dtype.name != "datetime64[D]":
dt = dt.astype("datetime64[D]")
return dt
def _get_calendar(weekmask, holidays, calendar):
"""
Generate busdaycalendar
"""
if isinstance(calendar, np.busdaycalendar):
if not holidays:
holidays = tuple(calendar.holidays)
elif not isinstance(holidays, tuple):
holidays = tuple(holidays)
else:
# Trust that calendar.holidays and holidays are
# consistent
pass
# Update weekmask if applicable (added)
calendar = np.busdaycalendar(weekmask, holidays)
return calendar, holidays
if holidays is None:
holidays = []
# Handle non list holidays also (added)
if isinstance(holidays, pd.DatetimeIndex):
holidays = holidays.tolist()
with contextlib.suppress(AttributeError):
holidays = holidays + calendar.holidays().tolist()
holidays = [_to_dt64D(dt) for dt in holidays]
holidays = tuple(sorted(holidays))
kwargs = {"weekmask": weekmask}
if holidays:
kwargs["holidays"] = holidays
busdaycalendar = np.busdaycalendar(**kwargs)
return busdaycalendar, holidays
class MultipleWeekmaskCustomBusinessDay(CompositeCustomBusinessDay):
_prefix = "C"
_attributes = (
"n",
"normalize",
"weekmask",
"holidays",
"calendar",
"offset",
"business_days",
"weekmasks",
)
def __init__(
self,
n=1,
normalize=False,
weekmask="Mon Tue Wed Thu Fri",
holidays=None,
calendar=None,
offset=timedelta(0),
business_days=None,
weekmasks=None,
):
self._weekmasks = weekmasks
if business_days is None and weekmasks is not None:
calendars = [
_get_calendar(weekmask=weekmask, holidays=holidays, calendar=calendar)
for _start_date, _end_date, weekmask in weekmasks
]
business_days = [
(
start_date,
end_date,
CustomBusinessDay(
n, normalize, weekmask, holidays, calendar, offset
),
)
for (start_date, end_date, weekmask), (calendar, holidays) in zip(
weekmasks, calendars, strict=False
)
]
CompositeCustomBusinessDay.__init__(
self, n, normalize, weekmask, holidays, calendar, offset, business_days
)
def __setstate__(self, state):
self.weekmasks = state.pop("weekmasks")
CompositeCustomBusinessDay.__setstate__(self, state)
@property
def weekmasks(self):
return tuple(self._weekmasks)
@weekmasks.setter
def weekmasks(self, weekmasks):
self._weekmasks = weekmasks
class SingleConstructorOffset(BaseOffset):
def __reduce__(self): ...
class OrthodoxEaster(SingleConstructorOffset):
"""
DateOffset for the Orthodox Easter holiday.
"""
def __setstate__(self, state):
self.n = state.pop("n")
self.normalize = state.pop("normalize")
@apply_wraps
def _apply(self, other: datetime) -> datetime:
current_easter = easter(other.year, method=EASTER_ORTHODOX)
current_easter = datetime(
current_easter.year, current_easter.month, current_easter.day
)
current_easter = localize_pydatetime(current_easter, other.tzinfo)
n = self.n
if n >= 0 and other < current_easter:
n -= 1
elif n < 0 and other > current_easter:
n += 1
new = easter(other.year + n, method=EASTER_ORTHODOX)
return datetime(
new.year,
new.month,
new.day,
other.hour,
other.minute,
other.second,
other.microsecond,
)
def is_on_offset(self, dt: datetime) -> bool:
if self.normalize and not _is_normalized(dt):
return False
return date(dt.year, dt.month, dt.day) == easter(
dt.year, method=EASTER_ORTHODOX
)