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,直连正常
463 lines
16 KiB
Python
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)]),
|
|
)
|
|
]
|