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,直连正常
493 lines
14 KiB
Python
493 lines
14 KiB
Python
# Copyright 2019 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.
|
|
|
|
|
|
import datetime
|
|
from collections.abc import Callable
|
|
from functools import partial
|
|
from itertools import chain
|
|
from zoneinfo import ZoneInfo
|
|
import pandas as pd
|
|
from pandas.tseries.holiday import (
|
|
Holiday,
|
|
nearest_workday,
|
|
next_monday,
|
|
previous_friday,
|
|
)
|
|
|
|
from .common_holidays import european_labour_day, new_years_day
|
|
from .exchange_calendar import (
|
|
FRIDAY,
|
|
MONDAY,
|
|
THURSDAY,
|
|
TUESDAY,
|
|
ExchangeCalendar,
|
|
HolidayCalendar,
|
|
)
|
|
from .lunisolar_holidays import (
|
|
chinese_lunar_new_year_dates,
|
|
dragon_boat_festival_dates,
|
|
mid_autumn_festival_dates,
|
|
qingming_festival_dates,
|
|
)
|
|
|
|
ONE_DAY = datetime.timedelta(1)
|
|
|
|
|
|
def check_after_2013(dt: datetime.datetime) -> bool:
|
|
"""Query if a date has a year component later than 2013."""
|
|
return dt.year > 2013
|
|
|
|
|
|
def check_between_2013_2024(dt: datetime.datetime) -> bool:
|
|
"""Query if a date has a year component from 2014 thr 2023."""
|
|
return 2024 > dt.year > 2013
|
|
|
|
|
|
def before_chinese_new_year_offset(holidays):
|
|
"""Offset holidays that fall on a weekend to the previous friday.
|
|
|
|
Can be used to offset holidays that fall before Chinese New Year.
|
|
"""
|
|
return pd.to_datetime(holidays.map(lambda d: previous_friday(d)))
|
|
|
|
|
|
def chinese_new_year_offset(holidays):
|
|
"""Offset holidays that fall on a weekend to the next monday.
|
|
|
|
Can be used to offset holidays that fall after Chinese New Year.
|
|
"""
|
|
return pd.to_datetime(holidays.map(lambda d: next_monday(d)))
|
|
|
|
|
|
def evalute_tomb_sweeping_weekday_extra(dt: pd.DatetimeIndex) -> pd.DatetimeIndex:
|
|
"""Evaluate extra tomb sweeping holdiays.
|
|
|
|
Evalutes extra holidays when tomb sweeping day falls on a weekday.
|
|
|
|
Reference: Taiwan Implementation Measures for Memorial Days and Holidays 1.5.2.1
|
|
https://law.moj.gov.tw/LawClass/LawAll.aspx?pcode=D0020033
|
|
"""
|
|
dts = []
|
|
for d in dt:
|
|
if d.year > 2012 and d.month == 4 and d.day == 4:
|
|
if datetime.datetime(d.year, 4, 4).weekday() == THURSDAY:
|
|
dts.append(datetime.datetime(d.year, 4, 5))
|
|
else:
|
|
dts.append(datetime.datetime(d.year, 4, 3))
|
|
return pd.to_datetime(dts)
|
|
|
|
|
|
def nearest_weekday_from_2014(dt: datetime.datetime) -> datetime.datetime:
|
|
"""Return nearest weekday to a date that's later than 2013.
|
|
|
|
If date is earlier than 2014 then returns `dt` as is.
|
|
"""
|
|
return dt if dt.year < 2014 else nearest_workday(dt)
|
|
|
|
|
|
def holidays_nearest_weekday_from_2014(
|
|
holidays: pd.DatetimeIndex,
|
|
) -> pd.DatetimeIndex:
|
|
"""Map `holidays` to nearest weekdays.
|
|
|
|
holidays that are weekdays map to themselves.
|
|
holidays before 2014 map to themselves.
|
|
"""
|
|
return pd.to_datetime([nearest_weekday_from_2014(d) for d in holidays])
|
|
|
|
|
|
def nearest_weekday_2014_thr_2023(dt: datetime.datetime) -> datetime.datetime:
|
|
"""Return nearest weekday to a date from 2014 thr 2023.
|
|
|
|
If date is of another year then returns `dt` as received.
|
|
"""
|
|
return nearest_workday(dt) if 2024 > dt.year > 2013 else dt
|
|
|
|
|
|
def manual_nearest_weekday_2014_thr_2023(holidays):
|
|
"""Map holidays from 2013 thr 2023 to nearest weekday.
|
|
|
|
holidays that are weekdays map to themselves.
|
|
holidays earlier than 2014 or later than 2024 map to themselves.
|
|
|
|
Can be used to implement nearest weekday observance rule for Chinese
|
|
lunar calendar holidays from 2013 through 2023.
|
|
"""
|
|
return pd.to_datetime(holidays.map(lambda d: nearest_weekday_2014_thr_2023(d)))
|
|
|
|
|
|
def manual_extra_days_07_thr_2023(holidays):
|
|
"""Return holidays to make up a four day weekend.
|
|
|
|
Can be used to implement the four day weekend rule observed for Chinese
|
|
lunar calendar holidays from 2007 through 2023.
|
|
"""
|
|
friday_extras = [
|
|
d + ONE_DAY
|
|
for d in holidays
|
|
if d.weekday() == THURSDAY and 2024 > d.year > 2006
|
|
]
|
|
|
|
monday_extras = [
|
|
d - ONE_DAY for d in holidays if d.weekday() == TUESDAY and 2024 > d.year > 2006
|
|
]
|
|
|
|
return pd.to_datetime(friday_extras + monday_extras)
|
|
|
|
|
|
def bridge_mon(
|
|
dt: datetime.datetime, checker: Callable = check_after_2013
|
|
) -> datetime.datetime | None:
|
|
"""Return previous Monday if date is a Tueday, otherwise None.
|
|
|
|
Parameters
|
|
----------
|
|
dt
|
|
Date to query.
|
|
|
|
checker
|
|
Callable to implement additional checks. Should take a single
|
|
parameter as a datetime.datetime and return a bool. `bridge_mon`
|
|
will return None if `checker` returns False when passed `dt`.
|
|
|
|
By default will check year of `dt` is later than 2013.
|
|
"""
|
|
dt -= ONE_DAY
|
|
return dt if (dt.weekday() == MONDAY and checker(dt)) else None
|
|
|
|
|
|
def bridge_fri(
|
|
dt: datetime.datetime, checker: Callable = check_after_2013
|
|
) -> datetime.datetime | None:
|
|
"""Return following Friday if a date is a Thursday, otherwise None.
|
|
|
|
Parameters
|
|
----------
|
|
dt
|
|
Date to query.
|
|
|
|
checker
|
|
Callable to implement additional checks. Should take a single
|
|
parameter as a datetime.datetime and return a bool. `bridge_fri`
|
|
will return None if `checker` returns False when passed `dt`.
|
|
|
|
By default will check year of `dt` is later than 2013.
|
|
"""
|
|
dt += ONE_DAY
|
|
return dt if (dt.weekday() == FRIDAY and checker(dt)) else None
|
|
|
|
|
|
bridge_mon_2014_thr_2023 = partial(bridge_mon, checker=check_between_2013_2024)
|
|
bridge_fri_2014_thr_2023 = partial(bridge_fri, checker=check_between_2013_2024)
|
|
|
|
NewYearsDay = new_years_day(observance=nearest_weekday_from_2014)
|
|
NewYearsDayExtraMon = new_years_day(observance=bridge_mon)
|
|
NewYearsDayExtraFri = new_years_day(observance=bridge_fri_2014_thr_2023)
|
|
|
|
PeaceMemorialDay = Holiday(
|
|
"Peace Memorial Day", month=2, day=28, observance=nearest_weekday_from_2014
|
|
)
|
|
PeaceMemorialDayExtraMon = Holiday(
|
|
"Peace Memorial Day extra Monday",
|
|
month=2,
|
|
day=28,
|
|
observance=bridge_mon_2014_thr_2023,
|
|
)
|
|
PeaceMemorialDayExtraFri = Holiday(
|
|
"Peace Memorial Day extra Friday",
|
|
month=2,
|
|
day=28,
|
|
observance=bridge_fri_2014_thr_2023,
|
|
)
|
|
|
|
|
|
WomenAndChildrensDay = Holiday(
|
|
"Women and Children's Day",
|
|
month=4,
|
|
day=4,
|
|
start_date="2011",
|
|
observance=nearest_weekday_from_2014,
|
|
)
|
|
WomenAndChildrensDayExtraMon = Holiday(
|
|
"Women and Children's Day extra Monday",
|
|
month=4,
|
|
day=4,
|
|
start_date="2011",
|
|
observance=bridge_mon_2014_thr_2023,
|
|
)
|
|
WomenAndChildrensDayExtraFri = Holiday(
|
|
"Women and Children's Day extra Friday",
|
|
month=4,
|
|
day=4,
|
|
start_date="2011",
|
|
observance=bridge_fri_2014_thr_2023,
|
|
)
|
|
|
|
|
|
LabourDay = european_labour_day(observance=nearest_weekday_from_2014)
|
|
|
|
TeachersDay = Holiday(
|
|
"Teachers' Day", month=9, day=28, start_date="2025", observance=nearest_workday
|
|
)
|
|
|
|
NationalDay = Holiday(
|
|
"National Day of the Republic of China",
|
|
month=10,
|
|
day=10,
|
|
observance=nearest_weekday_from_2014,
|
|
)
|
|
NationalDayExtraMon = Holiday(
|
|
"National Day of the Republic of China extra Monday",
|
|
month=10,
|
|
day=10,
|
|
observance=bridge_mon_2014_thr_2023,
|
|
)
|
|
NationalDayExtraFri = Holiday(
|
|
"National Day of the Republic of China extra Friday",
|
|
month=10,
|
|
day=10,
|
|
observance=bridge_fri_2014_thr_2023,
|
|
)
|
|
|
|
TaiwanRestorationDay = Holiday(
|
|
"Taiwan Restoration Day",
|
|
month=10,
|
|
day=25,
|
|
start_date="2025",
|
|
observance=nearest_workday,
|
|
)
|
|
|
|
ConstitutionDay = Holiday(
|
|
"Constitution Day",
|
|
month=12,
|
|
day=25,
|
|
start_date="2025",
|
|
)
|
|
|
|
chinese_new_year = chinese_new_year_offset(chinese_lunar_new_year_dates)
|
|
|
|
chinese_new_years_eve = before_chinese_new_year_offset(
|
|
chinese_new_year - ONE_DAY,
|
|
)
|
|
|
|
chinese_new_years_eve_2 = before_chinese_new_year_offset(
|
|
chinese_new_years_eve - ONE_DAY,
|
|
)
|
|
|
|
chinese_new_year_2 = chinese_new_year_offset(
|
|
chinese_new_year + ONE_DAY,
|
|
)
|
|
|
|
chinese_new_year_3 = chinese_new_year_offset(
|
|
chinese_new_year_2 + ONE_DAY,
|
|
)
|
|
|
|
tomb_sweeping_day = manual_nearest_weekday_2014_thr_2023(qingming_festival_dates)
|
|
tomb_sweeping_day_weekday_extras = evalute_tomb_sweeping_weekday_extra(
|
|
qingming_festival_dates
|
|
)
|
|
tomb_sweeping_day_weekend_extras = holidays_nearest_weekday_from_2014(
|
|
qingming_festival_dates
|
|
)
|
|
|
|
dragon_boat_festival = manual_nearest_weekday_2014_thr_2023(dragon_boat_festival_dates)
|
|
dragon_boat_festival_weekday_extras = manual_extra_days_07_thr_2023(
|
|
dragon_boat_festival
|
|
)
|
|
dragon_boat_festival_weekend_extras = holidays_nearest_weekday_from_2014(
|
|
dragon_boat_festival_dates
|
|
)
|
|
|
|
mid_autumn_festival = manual_nearest_weekday_2014_thr_2023(
|
|
mid_autumn_festival_dates,
|
|
)
|
|
mid_autumn_festival_weekday_extras = manual_extra_days_07_thr_2023(mid_autumn_festival)
|
|
mid_autumn_festival_weekend_extras = holidays_nearest_weekday_from_2014(
|
|
mid_autumn_festival_dates
|
|
)
|
|
|
|
# Taiwan takes multiple days off before and after chinese new year,
|
|
# and sometimes it is unclear precisely which days will be holidays.
|
|
chinese_new_year_extras = pd.to_datetime(
|
|
[
|
|
"2002-02-07",
|
|
"2002-02-15",
|
|
"2003-01-29",
|
|
"2004-01-19",
|
|
"2005-02-04",
|
|
"2006-02-02",
|
|
"2007-02-22",
|
|
"2007-02-23",
|
|
"2008-02-04",
|
|
"2009-01-29",
|
|
"2009-01-30",
|
|
"2010-02-18",
|
|
"2010-02-19",
|
|
"2011-01-31",
|
|
"2012-01-26",
|
|
"2012-01-27",
|
|
"2013-02-14",
|
|
"2013-02-15",
|
|
"2014-01-28",
|
|
"2015-02-16",
|
|
"2016-02-11",
|
|
"2016-02-12",
|
|
"2017-01-25",
|
|
"2018-02-13",
|
|
"2019-01-31",
|
|
"2019-02-08",
|
|
"2020-01-21",
|
|
"2020-01-22",
|
|
"2021-02-08",
|
|
"2021-02-09",
|
|
"2022-01-27",
|
|
"2023-01-26",
|
|
"2023-01-27",
|
|
"2024-02-06",
|
|
"2024-02-07",
|
|
"2025-01-23",
|
|
"2025-01-24",
|
|
"2026-02-20",
|
|
]
|
|
)
|
|
|
|
# Some abnormal observances of regularly observed holidays.
|
|
extra_holidays = pd.to_datetime(
|
|
[
|
|
"2026-02-12", # No trading
|
|
"2021-04-02", # Women And Childrens Day
|
|
"2020-04-02", # Tomb Sweeping Day
|
|
"2016-04-05", # Tomb Sweeping Day
|
|
"2012-12-31", # New Year's Eve
|
|
"2012-02-27", # Peace Memorial Day
|
|
"2009-01-02", # New Year's Day
|
|
"2006-10-09", # National Day
|
|
"2005-09-01", # Bank Holiday
|
|
"2018-04-06", # Tomb Sweeping Day, https://www.dgpa.gov.tw/information?uid=41&pid=7488
|
|
"2007-04-06", # Tomb Sweeping Day
|
|
]
|
|
)
|
|
|
|
typhoons = pd.to_datetime(
|
|
[
|
|
"2024-10-03",
|
|
"2024-10-02",
|
|
"2024-07-25",
|
|
"2024-07-24",
|
|
"2023-08-03",
|
|
"2019-09-30",
|
|
"2019-08-09",
|
|
"2016-09-28",
|
|
"2016-09-27",
|
|
"2016-07-08",
|
|
"2015-09-29",
|
|
"2015-07-10",
|
|
"2014-07-23",
|
|
"2013-08-21",
|
|
"2012-08-02",
|
|
"2009-08-07",
|
|
"2008-09-29",
|
|
"2008-07-28",
|
|
"2007-09-18",
|
|
"2005-08-05",
|
|
"2005-07-18",
|
|
"2004-10-25",
|
|
"2004-08-25",
|
|
"2004-08-24",
|
|
"2002-09-06",
|
|
]
|
|
)
|
|
|
|
|
|
class XTAIExchangeCalendar(ExchangeCalendar):
|
|
"""
|
|
Exchange calendar for the Taiwan Stock Exchange Corporation (XTAI).
|
|
https://www.twse.com.tw/en/trading/holiday.html
|
|
|
|
Open Time: 9:00 AM, CST
|
|
Close Time: 1:30 PM, CST
|
|
|
|
Regularly-Observed Holidays:
|
|
- New Year's Day
|
|
- Chinese New Year's Eve
|
|
- Chinese New Year
|
|
- Peace Memorial Day (Feb 28)
|
|
- Women and Children's Day (Apr 4)
|
|
- Tomb Sweeping Day (Lunar Calendar)
|
|
- Labour Day (May 1)
|
|
- Dragon Boat Festival (Lunar Calendar)
|
|
- Mid-Autumn Festival (Lunar Calendar)
|
|
- National Day of the Republic of China (Oct 10)
|
|
|
|
|
|
Early Closes:
|
|
- None
|
|
"""
|
|
|
|
name = "XTAI"
|
|
|
|
tz = ZoneInfo("Asia/Taipei")
|
|
|
|
open_times = ((None, datetime.time(9)),)
|
|
|
|
close_times = ((None, datetime.time(13, 30)),)
|
|
|
|
@property
|
|
def regular_holidays(self):
|
|
return HolidayCalendar(
|
|
[
|
|
NewYearsDay,
|
|
NewYearsDayExtraMon,
|
|
NewYearsDayExtraFri,
|
|
PeaceMemorialDay,
|
|
PeaceMemorialDayExtraMon,
|
|
PeaceMemorialDayExtraFri,
|
|
WomenAndChildrensDay,
|
|
WomenAndChildrensDayExtraMon,
|
|
WomenAndChildrensDayExtraFri,
|
|
LabourDay,
|
|
TeachersDay,
|
|
NationalDay,
|
|
NationalDayExtraMon,
|
|
NationalDayExtraFri,
|
|
TaiwanRestorationDay,
|
|
ConstitutionDay,
|
|
]
|
|
)
|
|
|
|
@property
|
|
def adhoc_holidays(self):
|
|
return list(
|
|
chain(
|
|
extra_holidays,
|
|
typhoons,
|
|
chinese_new_years_eve,
|
|
chinese_new_years_eve_2,
|
|
chinese_new_year,
|
|
chinese_new_year_2,
|
|
chinese_new_year_3,
|
|
chinese_new_year_extras,
|
|
tomb_sweeping_day,
|
|
tomb_sweeping_day_weekday_extras,
|
|
tomb_sweeping_day_weekend_extras,
|
|
dragon_boat_festival,
|
|
dragon_boat_festival_weekday_extras,
|
|
dragon_boat_festival_weekend_extras,
|
|
mid_autumn_festival,
|
|
mid_autumn_festival_weekday_extras,
|
|
mid_autumn_festival_weekend_extras,
|
|
)
|
|
)
|