fa45d8aa5f
- 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,直连正常
579 lines
16 KiB
Python
579 lines
16 KiB
Python
from datetime import date, datetime
|
|
|
|
import pandas as pd
|
|
from pandas._libs.tslibs.conversion import localize_pydatetime
|
|
from pandas.tseries.holiday import Day, Holiday
|
|
from pandas.tseries.offsets import Easter
|
|
from pyluach import dates, hebrewcal
|
|
|
|
try:
|
|
from pandas._libs.tslibs.offsets import apply_wraps
|
|
except ImportError:
|
|
from pandas.tseries.offsets import apply_wraps
|
|
|
|
# Auxiliary functions to get Hebrew dates for holidays observed by TASE for a
|
|
# given Hebrew calendar year. These are just the raw dates with no adjustments
|
|
# applied.
|
|
#
|
|
# Note: pyluach uses the biblical month numbering scheme where the year is
|
|
# incremented when moving from the month of Elul (6) to Tishrei (7). See also
|
|
# https://en.wikipedia.org/wiki/Hebrew_calendar.
|
|
|
|
|
|
def _purim(year):
|
|
"""
|
|
Return the Hebrew date for Purim in the given Hebrew year.
|
|
"""
|
|
# Purim is observed in Adar (12), or Adar II (13) if in a leap year.
|
|
return dates.HebrewDate(year.year, 13 if year.leap else 12, 14)
|
|
|
|
|
|
def _passover(year):
|
|
"""
|
|
Return the Hebrew date for the first day of Passover in the given Hebrew
|
|
year.
|
|
"""
|
|
return dates.HebrewDate(year.year, 1, 15)
|
|
|
|
|
|
def _memorial_day(year):
|
|
"""
|
|
Return the Hebrew date for Memorial Day in the given Hebrew year.
|
|
|
|
If either Memorial Day (MD) or Independence Day (ID) were to interfere with Sabbat,
|
|
including night before, then they are shifted forward or backward to avoid Sabbat.
|
|
ID always celebrated day after MD.
|
|
Wiki: https://en.wikipedia.org/wiki/Yom_HaZikaron#Timing
|
|
Better explanation: https://ph.yhb.org.il/en/05-04-09
|
|
"""
|
|
d = dates.HebrewDate(year.year, 2, 4)
|
|
if d.weekday() == 1:
|
|
# MD on Sunday => shift forward to Monday
|
|
d = dates.HebrewDate(year.year, 2, 5)
|
|
elif d.weekday() == 6:
|
|
# MD on Friday => ID on Sabbat => shift back 2 days
|
|
d = dates.HebrewDate(year.year, 2, 2)
|
|
elif d.weekday() == 5:
|
|
# MD on Thursday => ID night before Sabbat => shift back 1 day
|
|
d = dates.HebrewDate(year.year, 2, 3)
|
|
|
|
return d
|
|
|
|
|
|
def _pentecost(year):
|
|
"""
|
|
Return the Hebrew date for Pentecost in the given Hebrew year.
|
|
"""
|
|
return dates.HebrewDate(year.year, 3, 6)
|
|
|
|
|
|
def _fast_day(year):
|
|
"""
|
|
Return the Hebrew date for Tisha B'Av in the given Hebrew year.
|
|
"""
|
|
return dates.HebrewDate(year.year, 5, 9)
|
|
|
|
|
|
def _new_year(year):
|
|
"""
|
|
Return the Hebrew date for the first day of a new year in the given Hebrew
|
|
year.
|
|
"""
|
|
return dates.HebrewDate(year.year, 7, 1)
|
|
|
|
|
|
def _yom_kippur(year):
|
|
"""
|
|
Return the Hebrew date for Yom Kippur in the given Hebrew year.
|
|
"""
|
|
return dates.HebrewDate(year.year, 7, 10)
|
|
|
|
|
|
def _sukkoth(year):
|
|
"""
|
|
Return the Hebrew date for Sukkoth in the given Hebrew year.
|
|
"""
|
|
return dates.HebrewDate(year.year, 7, 15)
|
|
|
|
|
|
def _simchat_torah(year):
|
|
"""
|
|
Return the Hebrew date for Simchat Torah in the given Hebrew year.
|
|
"""
|
|
return dates.HebrewDate(year.year, 7, 22)
|
|
|
|
|
|
def _hebrew_year(year):
|
|
"""
|
|
Return the Hebrew calendar year that corresponds to 1st January of the
|
|
given Gregorian calendar year.
|
|
|
|
1st January of any Gregorian calendar year, say x, always falls into the
|
|
month of Tevet (10) or Shevat (11) of some Hebrew year f(x). Also, we have
|
|
f(x+1) = f(x) + 1, so that any year in the Gregorian calendar always
|
|
overlaps with two consecutive years in the Hebrew calendar and vice versa.
|
|
"""
|
|
return hebrewcal.Year(dates.GregorianDate(year, 1, 1).to_heb().year)
|
|
|
|
|
|
# Auxilliary functions to calculate Gregorian dates for holidays observed by
|
|
# TASE for a given Gregorian calendar year. Adjustments are also applied.
|
|
|
|
|
|
def purim(year):
|
|
"""
|
|
Return the Gregorian date for Purim in the given Gregorian calendar year.
|
|
"""
|
|
return _purim(_hebrew_year(year)).to_greg()
|
|
|
|
|
|
def passover(year):
|
|
"""
|
|
Return the Gregorian date for the first day of Passover in the given
|
|
Gregorian calendar year.
|
|
"""
|
|
return _passover(_hebrew_year(year)).to_greg()
|
|
|
|
|
|
def memorial_day(year):
|
|
"""
|
|
Return the Gregorian date for Memorial Day in the given Gregorian calendar
|
|
year.
|
|
"""
|
|
|
|
# Regular Memorial Day date.
|
|
d = _memorial_day(_hebrew_year(year)).to_greg()
|
|
|
|
# Reschedule to avoid Sabbath desecration, maybe.
|
|
if d.weekday() == 5:
|
|
# Falls on a Thursday, so Independency Day falls on the Friday.
|
|
# Moved down by one day.
|
|
return d - 1
|
|
if d.weekday() == 6:
|
|
# Falls on a Friday, so Independence Day falls on the Saturday.
|
|
# Moved down by two days.
|
|
return d - 2
|
|
if d.weekday() == 7:
|
|
# Falls on a Saturday, therefore moved up by one day.
|
|
return d + 1
|
|
return d
|
|
|
|
|
|
def pentecost(year):
|
|
"""
|
|
Return the Gregorian date for Pentecost in the given Gregorian calendar
|
|
year.
|
|
"""
|
|
return _pentecost(_hebrew_year(year)).to_greg()
|
|
|
|
|
|
def fast_day(year):
|
|
"""
|
|
Return the Gregorian date for Tisha B'Av in the given Gregorian calendar
|
|
year.
|
|
"""
|
|
d = _fast_day(_hebrew_year(year)).to_greg()
|
|
|
|
# Reschedule if it falls on Sabbath (Saturday), maybe.
|
|
if d.weekday() == 7:
|
|
# Falls on a Saturday, therefore moved up by one day.
|
|
return d + 1
|
|
return d
|
|
|
|
|
|
def new_year(year):
|
|
"""
|
|
Return the Gregorian date for the first day of a new year in the given
|
|
Gregorian calendar year.
|
|
"""
|
|
return _new_year(_hebrew_year(year + 1)).to_greg()
|
|
|
|
|
|
def yom_kippur(year):
|
|
"""
|
|
Return the Gregorian date for Yom Kippur in the given Gregorian calendar
|
|
year.
|
|
"""
|
|
return _yom_kippur(_hebrew_year(year + 1)).to_greg()
|
|
|
|
|
|
def sukkoth(year):
|
|
"""
|
|
Return the Gregorian date for Sukkoth in the given Gregorian calendar year.
|
|
"""
|
|
return _sukkoth(_hebrew_year(year + 1)).to_greg()
|
|
|
|
|
|
def simchat_torah(year):
|
|
"""
|
|
Return the Gregorian date for Simchat Torah in the given Gregorian calendar
|
|
year.
|
|
"""
|
|
return _simchat_torah(_hebrew_year(year + 1)).to_greg()
|
|
|
|
|
|
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
|
|
|
|
|
|
class _HolidayOffset(Easter):
|
|
"""
|
|
Auxiliary class for DateOffset instances for the different holidays.
|
|
"""
|
|
|
|
@property
|
|
def holiday(self):
|
|
"""
|
|
Return the Gregorian date for the holiday in a given Gregorian calendar
|
|
year.
|
|
"""
|
|
|
|
@apply_wraps
|
|
def _apply(self, other):
|
|
current = self.holiday(other.year).to_pydate()
|
|
current = datetime(current.year, current.month, current.day)
|
|
current = localize_pydatetime(current, other.tzinfo)
|
|
|
|
n = self.n
|
|
if n >= 0 and other < current:
|
|
n -= 1
|
|
elif n < 0 and other > current:
|
|
n += 1
|
|
# TODO: Why does this handle the 0 case the opposite of others?
|
|
|
|
# NOTE: self.holiday a dates.GregorianDate so we have to convert to
|
|
# type of other
|
|
new = self.holiday(other.year + n).to_pydate()
|
|
return datetime(
|
|
new.year,
|
|
new.month,
|
|
new.day,
|
|
other.hour,
|
|
other.minute,
|
|
other.second,
|
|
other.microsecond,
|
|
)
|
|
|
|
# backwards compat
|
|
apply = _apply
|
|
|
|
def is_on_offset(self, dt):
|
|
if self.normalize and not _is_normalized(dt):
|
|
return False
|
|
return date(dt.year, dt.month, dt.day) == self.holiday(dt.year).to_pydate()
|
|
|
|
|
|
# DateOffset subclasses for holidays observed by TASE.
|
|
|
|
|
|
class _Purim(_HolidayOffset):
|
|
@property
|
|
def holiday(self):
|
|
return purim
|
|
|
|
|
|
class _Passover(_HolidayOffset):
|
|
@property
|
|
def holiday(self):
|
|
return passover
|
|
|
|
|
|
class _MemorialDay(_HolidayOffset):
|
|
@property
|
|
def holiday(self):
|
|
return memorial_day
|
|
|
|
|
|
class _Pentecost(_HolidayOffset):
|
|
@property
|
|
def holiday(self):
|
|
return pentecost
|
|
|
|
|
|
class _FastDay(_HolidayOffset):
|
|
@property
|
|
def holiday(self):
|
|
return fast_day
|
|
|
|
|
|
class _NewYear(_HolidayOffset):
|
|
@property
|
|
def holiday(self):
|
|
return new_year
|
|
|
|
|
|
class _YomKippur(_HolidayOffset):
|
|
@property
|
|
def holiday(self):
|
|
return yom_kippur
|
|
|
|
|
|
class _YomKippurEveObserved(_HolidayOffset):
|
|
"""
|
|
Custom offset for Yom Kippur Eve with previous_friday observance.
|
|
Applies both the Day(-1) offset and moves to previous Friday if needed.
|
|
"""
|
|
|
|
@property
|
|
def holiday(self):
|
|
return yom_kippur
|
|
|
|
@apply_wraps
|
|
def _apply(self, other):
|
|
current = self.holiday(other.year).to_pydate()
|
|
# Get the day before (Yom Kippur Eve)
|
|
current = current - pd.Timedelta(days=1)
|
|
current = datetime(current.year, current.month, current.day)
|
|
# Apply previous_friday observance (move to previous Friday if on weekend)
|
|
weekday = current.weekday()
|
|
if weekday == 5: # Saturday
|
|
current = current - pd.Timedelta(days=1)
|
|
elif weekday == 6: # Sunday
|
|
current = current - pd.Timedelta(days=2)
|
|
current = localize_pydatetime(current, other.tzinfo)
|
|
|
|
n = self.n
|
|
if n >= 0 and other < current:
|
|
n -= 1
|
|
elif n < 0 and other > current:
|
|
n += 1
|
|
|
|
new = self.holiday(other.year + n).to_pydate()
|
|
# Get the day before (Yom Kippur Eve)
|
|
new = new - pd.Timedelta(days=1)
|
|
new = datetime(new.year, new.month, new.day)
|
|
# Apply previous_friday observance
|
|
weekday = new.weekday()
|
|
if weekday == 5: # Saturday
|
|
new = new - pd.Timedelta(days=1)
|
|
elif weekday == 6: # Sunday
|
|
new = new - pd.Timedelta(days=2)
|
|
|
|
return datetime(
|
|
new.year,
|
|
new.month,
|
|
new.day,
|
|
other.hour,
|
|
other.minute,
|
|
other.second,
|
|
other.microsecond,
|
|
)
|
|
|
|
def is_on_offset(self, dt):
|
|
if self.normalize and not _is_normalized(dt):
|
|
return False
|
|
# Get the expected date and apply the same logic
|
|
expected = self.holiday(dt.year).to_pydate() - pd.Timedelta(days=1)
|
|
expected = date(expected.year, expected.month, expected.day)
|
|
weekday = expected.weekday()
|
|
if weekday == 5: # Saturday
|
|
expected = expected - pd.Timedelta(days=1)
|
|
elif weekday == 6: # Sunday
|
|
expected = expected - pd.Timedelta(days=2)
|
|
return date(dt.year, dt.month, dt.day) == expected.date()
|
|
|
|
|
|
class _Sukkoth(_HolidayOffset):
|
|
@property
|
|
def holiday(self):
|
|
return sukkoth
|
|
|
|
|
|
class _SimchatTorah(_HolidayOffset):
|
|
@property
|
|
def holiday(self):
|
|
return simchat_torah
|
|
|
|
|
|
# Holiday instances for holidays observed by TASE.
|
|
Purim = Holiday("Purim", month=1, day=1, offset=[_Purim()])
|
|
PassoverEve = Holiday("Passover Eve", month=1, day=1, offset=[_Passover(), Day(-1)])
|
|
Passover = Holiday("Passover", month=1, day=1, offset=[_Passover()])
|
|
Passover2Eve = Holiday("Passover II Eve", month=1, day=1, offset=[_Passover(), Day(5)])
|
|
Passover2 = Holiday("Passover II", month=1, day=1, offset=[_Passover(), Day(6)])
|
|
PentecostEve = Holiday("Pentecost Eve", month=1, day=1, offset=[_Pentecost(), Day(-1)])
|
|
Pentecost = Holiday("Pentecost", month=1, day=1, offset=[_Pentecost()])
|
|
FastDay = Holiday("Tisha B'Av", month=1, day=1, offset=[_FastDay()])
|
|
MemorialDay = Holiday("Memorial Day", month=1, day=1, offset=[_MemorialDay()])
|
|
IndependenceDay = Holiday(
|
|
"Independence Day", month=1, day=1, offset=[_MemorialDay(), Day(1)]
|
|
)
|
|
NewYearsEve = Holiday("New Year's Eve", month=1, day=1, offset=[_NewYear(), Day(-1)])
|
|
NewYear = Holiday("New Year", month=1, day=1, offset=[_NewYear()])
|
|
NewYear2 = Holiday("New Year II", month=1, day=1, offset=[_NewYear(), Day(1)])
|
|
YomKippurEve = Holiday("Yom Kippur Eve", month=1, day=1, offset=[_YomKippur(), Day(-1)])
|
|
YomKippurEveObserved = Holiday(
|
|
"Market Holiday",
|
|
month=1,
|
|
day=1,
|
|
offset=[_YomKippurEveObserved()],
|
|
start_date=pd.Timestamp("2026-01-05"),
|
|
)
|
|
YomKippur = Holiday("Yom Kippur", month=1, day=1, offset=[_YomKippur()])
|
|
SukkothEve = Holiday("Sukkoth Eve", month=1, day=1, offset=[_Sukkoth(), Day(-1)])
|
|
Sukkoth = Holiday("Sukkoth", month=1, day=1, offset=[_Sukkoth()])
|
|
SimchatTorahEve = Holiday(
|
|
"Simchat Torah Eve", month=1, day=1, offset=[_SimchatTorah(), Day(-1)]
|
|
)
|
|
SimchatTorah = Holiday("Simchat Torah", month=1, day=1, offset=[_SimchatTorah()])
|
|
|
|
DaysOfWeekBefore2026 = (0, 1, 2, 3, 6)
|
|
DaysOfWeek = (0, 1, 2, 3, 4)
|
|
StartDate = pd.Timestamp("2026-01-05")
|
|
EndDate = pd.Timestamp("2026-01-05")
|
|
|
|
|
|
# Sukkoth interim days - the 3 days following Sukkoth
|
|
SukkothInterimDay1 = Holiday(
|
|
"Sukkoth Interim Day",
|
|
month=1,
|
|
day=1,
|
|
offset=[_Sukkoth(), Day(1)],
|
|
start_date=StartDate,
|
|
days_of_week=DaysOfWeek,
|
|
)
|
|
SukkothInterimDay1Before2026 = Holiday(
|
|
"Sukkoth Interim Day",
|
|
month=1,
|
|
day=1,
|
|
offset=[_Sukkoth(), Day(1)],
|
|
end_date=EndDate,
|
|
days_of_week=DaysOfWeekBefore2026,
|
|
)
|
|
SukkothInterimDay2 = Holiday(
|
|
"Sukkoth Interim Day",
|
|
month=1,
|
|
day=1,
|
|
offset=[_Sukkoth(), Day(2)],
|
|
start_date=StartDate,
|
|
days_of_week=DaysOfWeek,
|
|
)
|
|
SukkothInterimDay2Before2026 = Holiday(
|
|
"Sukkoth Interim Day",
|
|
month=1,
|
|
day=1,
|
|
offset=[_Sukkoth(), Day(2)],
|
|
end_date=EndDate,
|
|
days_of_week=DaysOfWeekBefore2026,
|
|
)
|
|
SukkothInterimDay3 = Holiday(
|
|
"Sukkoth Interim Day",
|
|
month=1,
|
|
day=1,
|
|
offset=[_Sukkoth(), Day(3)],
|
|
start_date=StartDate,
|
|
days_of_week=DaysOfWeek,
|
|
)
|
|
SukkothInterimDay3Before2026 = Holiday(
|
|
"Sukkoth Interim Day",
|
|
month=1,
|
|
day=1,
|
|
offset=[_Sukkoth(), Day(3)],
|
|
end_date=EndDate,
|
|
days_of_week=DaysOfWeekBefore2026,
|
|
)
|
|
SukkothInterimDay4 = Holiday(
|
|
"Sukkoth Interim Day",
|
|
month=1,
|
|
day=1,
|
|
offset=[_Sukkoth(), Day(4)],
|
|
start_date=StartDate,
|
|
days_of_week=DaysOfWeek,
|
|
)
|
|
SukkothInterimDay4Before2026 = Holiday(
|
|
"Sukkoth Interim Day",
|
|
month=1,
|
|
day=1,
|
|
offset=[_Sukkoth(), Day(4)],
|
|
end_date=EndDate,
|
|
days_of_week=DaysOfWeekBefore2026,
|
|
)
|
|
SukkothInterimDay5 = Holiday(
|
|
"Sukkoth Interim Day",
|
|
month=1,
|
|
day=1,
|
|
offset=[_Sukkoth(), Day(5)],
|
|
start_date=StartDate,
|
|
days_of_week=DaysOfWeek,
|
|
)
|
|
SukkothInterimDay5Before2026 = Holiday(
|
|
"Sukkoth Interim Day",
|
|
month=1,
|
|
day=1,
|
|
offset=[_Sukkoth(), Day(5)],
|
|
end_date=EndDate,
|
|
days_of_week=DaysOfWeekBefore2026,
|
|
)
|
|
|
|
# Passover interim days are the days between beginning and end of passover.
|
|
# Any otherwise regular business day in that period becomes an early close day.
|
|
PassoverInterimDay1 = Holiday(
|
|
"Passover Interim Day",
|
|
month=1,
|
|
day=1,
|
|
offset=[_Passover(), Day(1)],
|
|
start_date=StartDate,
|
|
days_of_week=DaysOfWeek,
|
|
)
|
|
PassoverInterimDay1Before2026 = Holiday(
|
|
"Passover Interim Day",
|
|
month=1,
|
|
day=1,
|
|
offset=[_Passover(), Day(1)],
|
|
end_date=EndDate,
|
|
days_of_week=DaysOfWeekBefore2026,
|
|
)
|
|
PassoverInterimDay2 = Holiday(
|
|
"Passover Interim Day",
|
|
month=1,
|
|
day=1,
|
|
offset=[_Passover(), Day(2)],
|
|
start_date=StartDate,
|
|
days_of_week=DaysOfWeek,
|
|
)
|
|
PassoverInterimDay2Before2026 = Holiday(
|
|
"Passover Interim Day",
|
|
month=1,
|
|
day=1,
|
|
offset=[_Passover(), Day(2)],
|
|
end_date=EndDate,
|
|
days_of_week=DaysOfWeekBefore2026,
|
|
)
|
|
PassoverInterimDay3 = Holiday(
|
|
"Passover Interim Day",
|
|
month=1,
|
|
day=1,
|
|
offset=[_Passover(), Day(3)],
|
|
start_date=StartDate,
|
|
days_of_week=DaysOfWeek,
|
|
)
|
|
PassoverInterimDay3Before2026 = Holiday(
|
|
"Passover Interim Day",
|
|
month=1,
|
|
day=1,
|
|
offset=[_Passover(), Day(3)],
|
|
end_date=EndDate,
|
|
days_of_week=DaysOfWeekBefore2026,
|
|
)
|
|
PassoverInterimDay4 = Holiday(
|
|
"Passover Interim Day",
|
|
month=1,
|
|
day=1,
|
|
offset=[_Passover(), Day(4)],
|
|
start_date=StartDate,
|
|
days_of_week=DaysOfWeek,
|
|
)
|
|
PassoverInterimDay4Before2026 = Holiday(
|
|
"Passover Interim Day",
|
|
month=1,
|
|
day=1,
|
|
offset=[_Passover(), Day(4)],
|
|
end_date=EndDate,
|
|
days_of_week=DaysOfWeekBefore2026,
|
|
)
|