Files
MoFin/venv/lib/python3.12/site-packages/exchange_calendars/exchange_calendar_xhkg.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

463 lines
16 KiB
Python

# Copyright 2018 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 datetime import time, timedelta
from itertools import chain
from zoneinfo import ZoneInfo
import numpy as np
import pandas as pd
import toolz
from pandas.tseries.holiday import EasterMonday, GoodFriday, Holiday, sunday_to_monday
from pandas.tseries.offsets import LastWeekOfMonth, WeekOfMonth
from .common_holidays import (
boxing_day,
christmas,
christmas_eve,
new_years_day,
new_years_eve,
weekend_christmas,
)
from .lunisolar_holidays import (
chinese_buddhas_birthday_dates,
chinese_lunar_new_year_dates,
double_ninth_festival_dates,
dragon_boat_festival_dates,
mid_autumn_festival_dates,
qingming_festival_dates,
)
from .exchange_calendar import (
FRIDAY,
MONDAY,
SATURDAY,
SUNDAY,
THURSDAY,
TUESDAY,
WEDNESDAY,
HolidayCalendar,
)
from .precomputed_exchange_calendar import PrecomputedExchangeCalendar
from .utils.pandas_utils import vectorized_sunday_to_monday
# Useful resources for making changes to this file:
# # /etc/lunisolar
# http://www.math.nus.edu.sg/aslaksen/calendar/cal.pdf
# https://www.hko.gov.hk/gts/time/calendarinfo.htm
# - the almanacs on this page are also useful
weekdays = (MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY)
def process_queen_birthday(dt):
# before 1983
if dt.year in [1974, 1981]:
return dt + pd.DateOffset(weekday=6)
if dt.year < 1983:
return sunday_to_monday(dt)
# after 1983
wom = WeekOfMonth(week=2, weekday=0)
if dt.year in [1983, 1988, 1993, 1994]:
wom = WeekOfMonth(week=1, weekday=0)
if dt.year in [1985]:
wom = WeekOfMonth(week=3, weekday=0)
return dt + wom
LabourDay = Holiday(
name="Labour Day", # 劳动节
month=5,
day=1,
observance=sunday_to_monday,
start_date=pd.Timestamp("1999-05-01"),
)
HKRegionEstablishmentDay = Holiday(
name="Hong Kong Special Region Establishment Day",
month=7,
day=1,
observance=sunday_to_monday,
start_date=pd.Timestamp("1997-07-01"),
)
NationalDay = Holiday(
name="National Day",
month=10,
day=1,
observance=sunday_to_monday,
start_date=pd.Timestamp("1997-07-01"),
)
QueenBirthday = Holiday(
name="Queen's Birthday", # 英女王生日 6月
month=6,
day=10,
observance=process_queen_birthday,
start_date=pd.Timestamp("1983-01-01"),
end_date=pd.Timestamp("1997-06-01"),
)
QueenBirthday2 = Holiday(
name="Queen's Birthday", # 英女王生日 4月
month=4,
day=21,
observance=process_queen_birthday,
start_date=pd.Timestamp("1926-04-21"),
end_date=pd.Timestamp("1983-01-01"),
)
CommemoratingAlliedVictory = Holiday(
name="Commemorating the allied victory", # 重光纪念日 8月最后一个星期一
month=8,
day=20,
offset=LastWeekOfMonth(weekday=0),
start_date=pd.Timestamp("1945-08-30"),
end_date=pd.Timestamp("1997-07-01"),
)
IDontKnow = Holiday(
name="I dont know these days, please tell me", # 8月第一个星期一
month=7,
day=31,
offset=WeekOfMonth(week=0, weekday=0),
start_date=pd.Timestamp("1960-08-01"),
end_date=pd.Timestamp("1983-01-01"),
)
day_after_mid_autumn_festival_dates = mid_autumn_festival_dates + timedelta(1)
HKAdhocClosures = [
# I dont know these days
pd.Timestamp("1970-07-01"),
pd.Timestamp("1971-07-01"),
pd.Timestamp("1973-07-02"),
pd.Timestamp("1974-07-01"),
pd.Timestamp("1975-07-01"),
pd.Timestamp("1976-07-01"),
pd.Timestamp("1977-07-01"),
pd.Timestamp("1979-07-02"),
pd.Timestamp("1980-07-01"),
pd.Timestamp("1981-07-01"),
pd.Timestamp("1982-07-01"),
pd.Timestamp("1971-03-22"),
pd.Timestamp("1971-12-06"),
pd.Timestamp("1971-12-20"),
pd.Timestamp("1975-07-28"),
pd.Timestamp("1985-07-29"),
# Weather related closures
pd.Timestamp("1970-07-16"), # 台风Ruby7003
pd.Timestamp("1970-09-14"), # 台风Georgia7011
pd.Timestamp("1971-07-22"), # 台风Lucy7114
pd.Timestamp("1971-08-31"), # 重光纪念日?
pd.Timestamp("1973-04-16"), # 股灾休市?
pd.Timestamp("1973-07-17"), # 台风Dot7304
pd.Timestamp("1974-04-25"), # 英国女王生日
pd.Timestamp("1975-10-14"), # 台风Elsie7514
pd.Timestamp("1978-07-26"), # 台风Agnes7807
pd.Timestamp("1978-07-27"),
pd.Timestamp("1979-01-26"), # 春节补假
pd.Timestamp("1979-08-02"), # 台风Hope7908
pd.Timestamp("1980-05-21"), # 台风Georgia8004
pd.Timestamp("1980-07-22"), # 台风Joy8007
pd.Timestamp("1981-04-27"), # 英国女王生日
pd.Timestamp("1981-07-06"), # 台风Lynn8106
pd.Timestamp("1981-07-07"),
pd.Timestamp("1981-07-29"), # 查理斯王子与戴安娜婚礼
pd.Timestamp("1983-09-09"), # 台风Ellen8309
pd.Timestamp("1985-06-24"), # 台风Hal8504
pd.Timestamp("1986-04-01"), # 复活节星期一翌日
pd.Timestamp("1986-10-22"), # 英女王伊丽莎白二世访港
pd.Timestamp("1987-10-20"), # 黑色星期一后,休市4天
pd.Timestamp("1987-10-21"),
pd.Timestamp("1987-10-22"),
pd.Timestamp("1987-10-23"),
pd.Timestamp("1988-04-05"), # 清明节翌日
# Timestamp('1988-06-13'), # 英国女王生日
pd.Timestamp("1991-06-18"), # 英国女王生日翌日
pd.Timestamp("1992-07-22"), # 台风Cary9207
# pd.Timestamp('1993-06-14'), # 英国女王生日
pd.Timestamp("1993-09-17"), # 台风Becky9316
pd.Timestamp("1994-06-14"), # 英国女王生日翌日,端午节翌日
pd.Timestamp("1997-06-30"), # 英国女王生日
pd.Timestamp("1997-07-02"), # 香港回归纪念日翌日
pd.Timestamp("1997-08-18"), # 抗战胜利纪念日
pd.Timestamp("1997-10-02"), # 国庆节翌日
pd.Timestamp("1998-08-17"), # 抗战胜利纪念日
pd.Timestamp("1998-10-02"), # 国庆节翌日
pd.Timestamp("1999-04-06"), # 清明节翌日
pd.Timestamp("1999-09-16"), # 台风约克
pd.Timestamp("1999-12-31"), # 千年虫
pd.Timestamp("2001-07-06"), # 台风尤特0104
pd.Timestamp("2001-07-25"), # 台风玉兔0107
# pd.Timestamp(2008-06-25'), # 台风风神0806,上午休市
pd.Timestamp("2008-08-06"), # 台风北冕0809
pd.Timestamp("2008-08-22"), # 台风鹦鹉0810
# pd.Timestamp(2009-09-15'), # 台风巨爵0915,上午休市
pd.Timestamp("2010-04-06"), # 清明节翌日
pd.Timestamp("2011-09-29"), # 台风纳沙1117
# pd.Timestamp(2012-07-24'), # 台风韦森特1208,上午休市
pd.Timestamp("2012-10-02"), # 中秋节补假
# pd.Timestamp(2013-05-22'), # 暴雨,上午休市
pd.Timestamp("2013-08-14"), # 台风尤特1311
# pd.Timestamp(2013-09-23'), # 台风天兔1319,上午休市
# pd.Timestamp(2014-09-16'), # 台风海鸥1415,上午休市
pd.Timestamp("2015-04-07"), # 复活节+清明节补假
# pd.Timestamp(2015-07-09'), # 台风莲花1520,期货夜盘休市
pd.Timestamp("2015-09-03"), # 抗战70周年纪念
# pd.Timestamp(2016-08-01'), # 台风妮妲1604,期货夜盘20:55收市
pd.Timestamp("2016-08-02"), # 台风妮妲1604
pd.Timestamp("2016-10-21"), # 台风海马1622
# pd.Timestamp(2017-06-12'), # 台风苗柏1702,期货夜盘17:35休市
pd.Timestamp("2017-08-23"), # 台风天鸽1713
pd.Timestamp("2020-10-13"), # 台风浪卡2016
pd.Timestamp(
"2021-10-13"
), # https://www.hkex.com.hk/News/Market-Communications/2021/2110132news?sc_lang=en
# In 2022, the mid-autumn festival falls on a Saturday (2022-09-10),
# which means the day after is a Sunday. In 2022, HKSE had a holiday
# on the Monday (2022-09-12). In the past they don't seem to have followed
# this pattern. We'll have to wait and see before we generalise this into a rule.
pd.Timestamp("2022-09-12"),
pd.Timestamp("2023-07-17"), # 8号台风泰利, 全天休市
pd.Timestamp("2024-09-06"), # 八號颱風, 全天休市
]
def boxing_day_obs(dt):
if dt.weekday in (MONDAY, TUESDAY):
return dt + pd.Timedelta(days=1)
return dt
class XHKGExchangeCalendar(PrecomputedExchangeCalendar):
"""
Exchange calendar for the Hong Kong Stock Exchange (XHKG).
Open Time: 9:30 AM, Asia/Hong_Kong
Lunch Break: 12:00 PM - 1:00 PM Asia/Hong_Kong
Close Time: 4:00 PM, Asia/Hong_Kong
Regularly-Observed Holidays:
- New Years Day (observed on monday when Jan 1 is a Sunday)
- Lunar New Year and the following 2 days. If the 3rd day of the lunar year
is a Sunday, then the next Monday is a holiday.
- Ching Ming Festival
- Good Friday
- Easter Monday
- Buddhas Birthday
- Dragon Boat Festival
- Chinese National Day (observed on monday when Oct 1 is a Sunday)
- Day Following Mid-Autumn Festival
- Chung Yeung Festival
- Christmas (observed on nearest weekday to December 25)
- Day after Christmas is observed
Regularly-Observed Early Closes:
- Lunar New Year's Eve
- Christmas Eve
- New Year's Eve
Additional Irregularities:
- Closes frequently for severe weather.
See https://www.hkex.com.hk/Services/Trading-hours-and-Severe-Weather-Arrangements/Trading-Hours/Securities-Market?sc_lang=en
"""
name = "XHKG"
tz = ZoneInfo("Asia/Hong_Kong")
open_times = (
(None, time(10)),
(pd.Timestamp("2011-03-07"), time(9, 30)),
)
break_start_times = ((None, time(12, 0)),)
break_end_times = ((None, time(13, 0)),)
close_times = ((None, time(16)),)
regular_early_close_times = (
(None, time(12, 30)),
(pd.Timestamp("2011-03-07"), time(12, 00)),
)
@classmethod
def precomputed_holidays(cls):
lunisolar_holidays = (
chinese_buddhas_birthday_dates,
chinese_lunar_new_year_dates,
day_after_mid_autumn_festival_dates,
double_ninth_festival_dates,
dragon_boat_festival_dates,
qingming_festival_dates,
)
return lunisolar_holidays # noqa: RET504
@classmethod
def _earliest_precomputed_year(cls) -> int:
return max(map(np.min, cls.precomputed_holidays())).year
@classmethod
def _latest_precomputed_year(cls) -> int:
return min(map(np.max, cls.precomputed_holidays())).year
@property
def regular_holidays(self):
return HolidayCalendar(
[
new_years_day(observance=sunday_to_monday),
GoodFriday,
EasterMonday,
LabourDay,
HKRegionEstablishmentDay,
CommemoratingAlliedVictory,
IDontKnow,
NationalDay,
QueenBirthday,
QueenBirthday2,
christmas(),
weekend_christmas(),
boxing_day(observance=boxing_day_obs),
]
)
@property
def adhoc_holidays(self):
# overrides as inherited from PrecomputedExchangeCalendar
lunar_new_years_eve = (chinese_lunar_new_year_dates - pd.Timedelta(days=1))[
(chinese_lunar_new_year_dates.weekday == SATURDAY)
& (chinese_lunar_new_year_dates.year < 2013)
]
lunar_new_year_2 = chinese_lunar_new_year_dates + pd.Timedelta(days=1)
lunar_new_year_3 = chinese_lunar_new_year_dates + pd.Timedelta(days=2)
lunar_new_year_4 = (chinese_lunar_new_year_dates + pd.Timedelta(days=3))[
# According to the new arrangement under the General Holidays and
# Employment Legislation (Substitution of Holidays)(Amendment)
# Ordinance 2011, when either Lunar New Year's Day, the second day
# of Lunar New Year or the third day of Lunar New Year falls on a
# Sunday, the fourth day of Lunar New Year is designated as a
# statutory and general holiday in substitution.
(
(chinese_lunar_new_year_dates.weekday == SUNDAY)
| (lunar_new_year_2.weekday == SUNDAY)
| (lunar_new_year_3.weekday == SUNDAY)
)
& (chinese_lunar_new_year_dates.year >= 2013)
]
qingming_festival = vectorized_sunday_to_monday(
qingming_festival_dates,
).values.copy() # copy so that array is writeable
years = qingming_festival.astype("M8[Y]")
easter_monday = EasterMonday.dates(years[0], years[-1] + 1)
# qingming gets observed one day later if easter monday is on the same
# day
qingming_festival[qingming_festival == easter_monday] += np.timedelta64(1, "D")
# if the day after the mid autumn festival is October first, which
# conflicts with national day, then national day is observed on the
# second, though we don't encode that in the regular holiday, so
# instead we pretend that the mid autumn festival would be delayed.
mid_autumn_festival = day_after_mid_autumn_festival_dates.values.copy()
mid_autumn_festival[
(day_after_mid_autumn_festival_dates.month == 10)
& (day_after_mid_autumn_festival_dates.day == 1)
] += np.timedelta64(1, "D")
return list(
chain(
lunar_new_years_eve,
chinese_lunar_new_year_dates,
lunar_new_year_2,
lunar_new_year_3,
lunar_new_year_4,
qingming_festival,
vectorized_sunday_to_monday(chinese_buddhas_birthday_dates),
vectorized_sunday_to_monday(dragon_boat_festival_dates),
mid_autumn_festival,
vectorized_sunday_to_monday(double_ninth_festival_dates),
HKAdhocClosures,
)
)
@property
def special_closes(self):
return [
# HK changed their early close time
(
time(12, 30),
HolidayCalendar(
[
new_years_eve(
# Market was close for Y2K instead of closing early
end_date=pd.Timestamp("1999-12-01"),
days_of_week=weekdays,
),
new_years_eve(
start_date=pd.Timestamp("2000-12-01"),
end_date=pd.Timestamp("2011-03-07"),
days_of_week=weekdays,
),
christmas_eve(
end_date=pd.Timestamp("2011-03-07"), days_of_week=weekdays
),
]
),
),
(
time(12, 00),
HolidayCalendar(
[
new_years_eve(
start_date=pd.Timestamp("2011-03-07"),
days_of_week=weekdays,
),
christmas_eve(
start_date=pd.Timestamp("2011-03-07"), days_of_week=weekdays
),
]
),
),
]
@property
def special_closes_adhoc(self):
lunar_new_years_eve = (chinese_lunar_new_year_dates - pd.Timedelta(days=1))[
np.isin(
chinese_lunar_new_year_dates.weekday,
[TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY],
)
& (chinese_lunar_new_year_dates.year >= 2013)
].values
def selection(arr, start, end):
predicates = []
if start is not None:
predicates.append(start.asm8 <= arr)
if end is not None:
predicates.append(arr < end.asm8)
if not predicates:
return arr
return arr[np.all(predicates, axis=0)]
return [
(time, pd.DatetimeIndex(selection(lunar_new_years_eve, start, end)))
for (start, time), (end, _) in toolz.sliding_window(
2,
toolz.concatv(self.regular_early_close_times, [(None, None)]),
)
]