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

407 lines
13 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
import functools
from zoneinfo import ZoneInfo
import pandas as pd
from pandas.tseries.holiday import Holiday
from pandas.tseries.offsets import CustomBusinessDay
from .exchange_calendar import HolidayCalendar
from .precomputed_exchange_calendar import PrecomputedExchangeCalendar
from .xkrx_holidays import (
krx_regular_holiday_rules,
precomputed_krx_holidays,
precomputed_csat_days,
)
from .pandas_extensions.offsets import MultipleWeekmaskCustomBusinessDay
from .pandas_extensions.korean_holiday import next_business_day
class XKRXExchangeCalendar(PrecomputedExchangeCalendar):
"""
Calendar for the Korea exchange, and the primary calendar for
the country of South Korea.
Open Time: 9:00 AM, KST (Korean Standard Time)
Close Time: 3:30 PM, KST (Korean Standard Time)
NOTE: Korea observes Standard Time year-round.
Due to the complexity around the Korean holidays, we are hardcoding
a list of holidays covering 1986-2019, inclusive.
Regularly-Observed Holidays:
- Seollal (New Year's Day)
- Independence Movement Day
- Labor Day
- Buddha's Birthday
- Memorial Day
- Provincial Election Day
- Liberation Day
- Chuseok (Korean Thanksgiving)
- National Foundation Day
- Christmas Day
- End of Year Holiday
NOTE: Hangeul Day became a national holiday in 2013
- Hangeul Proclamation Day
"""
name = "XKRX"
tz = ZoneInfo("Asia/Seoul")
# KRX schedule change history
# https://blog.naver.com/daishin_blog/220724111002
# 1956-03-03: 0930~1130, 1330~1530
# 1978-04-??: 1000~1200, 1330~1530
# 1986-04-??: 0940~1200, 1320~1520
# 1987-03-??: 0940~1140, 1320~1520
# 1995-01-01: 0930~1130, 1300~1500
# 1998-12-07: 0900~1200, 1300~1500
# 2000-05-22: 0900~1500
# 2016-08-01: 0900~1530
# Break time disappears since 2000-05-22
# https://www.donga.com/news/Economy/article/all/20000512/7534650/1
# Closing time became 30mins late since 2016-08-01
# https://biz.chosun.com/site/data/html_dir/2016/07/24/2016072400309.html
open_times = (
(None, time(9, 30)),
(pd.Timestamp("1978-04-01"), time(10, 0)),
(pd.Timestamp("1986-04-01"), time(9, 40)),
(pd.Timestamp("1995-01-01"), time(9, 30)),
(pd.Timestamp("1998-12-07"), time(9, 0)),
)
break_start_times = (
(None, time(11, 30)),
(pd.Timestamp("1978-04-01"), time(12, 0)),
(pd.Timestamp("1987-03-01"), time(11, 40)),
(pd.Timestamp("1995-01-01"), time(11, 30)),
(pd.Timestamp("1998-12-07"), time(12, 0)),
(pd.Timestamp("2000-05-22"), None),
)
break_end_times = (
(None, time(13, 30)),
(pd.Timestamp("1986-04-01"), time(13, 20)),
(pd.Timestamp("1995-01-01"), time(13, 0)),
(pd.Timestamp("2000-05-22"), None),
)
close_times = (
(None, time(15, 30)),
(pd.Timestamp("1986-04-01"), time(15, 20)),
(pd.Timestamp("1995-01-01"), time(15, 0)),
(pd.Timestamp("2016-08-01"), time(15, 30)),
)
# Saterday became holiday since 1998-12-07
# https://www.hankyung.com/finance/article/1998080301961
weekmask = "1111100"
@property
def special_weekmasks(self):
"""
Returns
-------
list: List of (date, date, str) tuples that represent special
weekmasks that applies between dates.
"""
return [
(None, pd.Timestamp("1998-12-06"), "1111110"),
]
@classmethod
def precomputed_holidays(cls) -> list[pd.Timestamp]:
return precomputed_krx_holidays.tolist()
@classmethod
def _earliest_precomputed_year(cls) -> int:
return 1956
@classmethod
def _latest_precomputed_year(cls) -> int:
return 2050
# KRX regular and precomputed adhoc holidays
@property
def regular_holidays(self):
return HolidayCalendar(krx_regular_holiday_rules)
# The first business day of each year:
# opening schedule is delayed by an hour.
@property
def special_offsets(self):
"""
Returns
-------
list:
List of (timedelta, timedelta, timedelta, timedelta,
AbstractHolidayCalendar) tuples that represent, respectively,
special open, break_start, break_end, close offsets and
corresponding HolidayCalendars.
"""
return [
(
pd.Timedelta(1, unit="h"),
None,
None,
None,
HolidayCalendar(
[
Holiday(
"First Business Day of Year",
month=1,
day=1,
observance=next_business_day,
)
]
),
),
]
# Every year's CSAT day, all schedules are delayed by:
# before 1998-11-18: 30 minutes
# after 1998-11-18: 1 hour
@property
def special_offsets_adhoc(
self,
) -> list[
tuple[pd.Timedelta, pd.Timedelta, pd.Timedelta, pd.Timedelta, pd.DatetimeIndex]
]:
"""
Returns
-------
list: List of (timedelta, timedelta, timedelta, timedelta, DatetimeIndex) tuples
that represent special open, break_start, break_end, close offsets
and corresponding DatetimeIndexes.
"""
return [
(
pd.Timedelta(30, unit="m"),
pd.Timedelta(30, unit="m"),
pd.Timedelta(30, unit="m"),
pd.Timedelta(30, unit="m"),
precomputed_csat_days[
precomputed_csat_days.slice_indexer("1993-08-20", "1998-11-17")
],
),
(
pd.Timedelta(1, unit="h"),
pd.Timedelta(1, unit="h"),
pd.Timedelta(1, unit="h"),
pd.Timedelta(1, unit="h"),
precomputed_csat_days[
precomputed_csat_days.slice_indexer("1998-11-18", None)
],
),
]
def _adjust_special_offsets(
self,
session_labels: pd.DatetimeIndex,
standard_times: pd.DatetimeIndex | None,
offsets: tuple[pd.Timedelta, HolidayCalendar],
ad_hoc_offsets: tuple[pd.Timedelta, pd.DatetimeIndex],
start_date: pd.Timestamp,
end_date: pd.Timestamp,
strict: bool = False,
):
# Short circuit when nothing to apply.
if standard_times is None or not len(standard_times):
return standard_times
len_m, len_oc = len(session_labels), len(standard_times)
if len_m != len_oc:
raise ValueError(
"Found misaligned dates while building calendar.\nExpected"
" session_labels to be the same length as open_or_closes but,\n"
f"len(session_labels)={len_m}, len(open_or_closes)={len_oc}"
)
regular = []
for offset, calendar in offsets:
days = calendar.holidays(start_date, end_date)
series = pd.Series(index=days, data=offset)
regular.append(series)
ad_hoc = []
for offset, datetimes in ad_hoc_offsets:
series = pd.Series(index=datetimes, data=offset)
ad_hoc.append(series)
merged = regular + ad_hoc
if not merged:
return pd.Series([], dtype="timedelta64[ns]")
result = pd.concat(merged).sort_index()
offsets = result.loc[(result.index >= start_date) & (result.index <= end_date)]
# Find the array indices corresponding to each special date.
indexer = session_labels.get_indexer(offsets.index)
# -1 indicates that no corresponding entry was found. If any -1s are
# present, then we have special dates that doesn't correspond to any
# trading day.
if -1 in indexer and strict:
bad_dates = list(offsets.index[indexer == -1])
raise ValueError(f"Special dates {bad_dates} are not trading days.")
special_opens_or_closes = standard_times[indexer] + offsets
# Short circuit when nothing to apply.
if not len(special_opens_or_closes):
return standard_times
srs = standard_times.to_series()
srs.iloc[indexer] = special_opens_or_closes
return pd.DatetimeIndex(srs)
def apply_special_offsets(
self,
session_labels: pd.DatetimeIndex,
start: pd.Timestamp,
end: pd.Timestamp,
):
"""Evaluate and overwrite special offsets."""
_special_offsets = self.special_offsets
_special_offsets_adhoc = self.special_offsets_adhoc
_special_open_offsets = [
(t[0], t[-1]) for t in _special_offsets if t[0] is not None
]
_special_open_offsets_adhoc = [
(t[0], t[-1]) for t in _special_offsets_adhoc if t[0] is not None
]
_special_break_start_offsets = [
(t[1], t[-1]) for t in _special_offsets if t[1] is not None
]
_special_break_start_offsets_adhoc = [
(t[1], t[-1]) for t in _special_offsets_adhoc if t[1] is not None
]
_special_break_end_offsets = [
(t[2], t[-1]) for t in _special_offsets if t[2] is not None
]
_special_break_end_offsets_adhoc = [
(t[2], t[-1]) for t in _special_offsets_adhoc if t[2] is not None
]
_special_close_offsets = [
(t[3], t[-1]) for t in _special_offsets if t[3] is not None
]
_special_close_offsets_adhoc = [
(t[3], t[-1]) for t in _special_offsets_adhoc if t[3] is not None
]
self._opens = self._adjust_special_offsets(
session_labels,
self._opens,
_special_open_offsets,
_special_open_offsets_adhoc,
start,
end,
)
self._break_starts = self._adjust_special_offsets(
session_labels,
self._break_starts,
_special_break_start_offsets,
_special_break_start_offsets_adhoc,
start,
end,
)
self._break_ends = self._adjust_special_offsets(
session_labels,
self._break_ends,
_special_break_end_offsets,
_special_break_end_offsets_adhoc,
start,
end,
)
self._closes = self._adjust_special_offsets(
session_labels,
self._closes,
_special_close_offsets,
_special_close_offsets_adhoc,
start,
end,
)
@functools.cached_property
def day(self):
if self.special_weekmasks:
return MultipleWeekmaskCustomBusinessDay(
holidays=self.adhoc_holidays,
calendar=self.regular_holidays,
weekmask=self.weekmask,
weekmasks=self.special_weekmasks,
)
return CustomBusinessDay(
holidays=self.adhoc_holidays,
calendar=self.regular_holidays,
weekmask=self.weekmask,
)
class PrecomputedXKRXExchangeCalendar(PrecomputedExchangeCalendar):
"""
Calendar for the Korea exchange, and the primary calendar for
the country of South Korea.
https://global.krx.co.kr/contents/GLB/05/0501/0501110000/GLB0501110000.jsp
Open Time: 9:00 AM, KST (Korean Standard Time)
Close Time: 3:30 PM, KST (Korean Standard Time)
NOTE: Korea observes Standard Time year-round.
Due to the complexity around the Korean holidays, we are hardcoding
a list of holidays covering 1986-2019, inclusive.
Regularly-Observed Holidays:
- Seollal (New Year's Day)
- Independence Movement Day
- Labor Day
- Buddha's Birthday
- Memorial Day
- Provincial Election Day
- Liberation Day
- Chuseok (Korean Thanksgiving)
- National Foundation Day
- Christmas Day
- End of Year Holiday
NOTE: Hangeul Day became a national holiday in 2013
- Hangeul Proclamation Day
"""
name = "XKRX"
tz = ZoneInfo("Asia/Seoul")
open_times = ((None, time(9)),)
close_times = ((None, time(15, 30)),)
@classmethod
def precomputed_holidays(cls):
return precomputed_krx_holidays