Market hours: a holiday closes the previous session's post-midnight continuation segment (HKFE futures T+1 session 00:00-03:00), dropping real data
Maintainer thường phản hồi trong vòng 1 ngày
@Sufiyan-MSA đang làm issue này rồi.
Từ ngày 14/10/2026.
Đánh giá
- Độ khó
- 4/5
- Thời gian dự kiến
- 3-5 ngày
- Mức phù hợp với người mới
- 18/100
Hướng nghiên cứu
Start in Common/Securities/SecurityExchangeHours.cs, where GetMarketHours returns ClosedAllDay for a holiday before any segment is checked, and read Common/Securities/LocalMarketHours.cs IsContinuousMarketOpen for the midnight-continuation logic. Reproduce with the Python probe in the issue, comparing History bars for 2020-06-24 and 2020-06-29. The design choice (a general rule versus a per-entry opt-in, and whether IsDateOpen changes) must be settled first. Done means the 2020-06-24 window returns 585 bars and CME holidays are unaffected.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Summary
For a market whose session crosses midnight and is continued by a 00:00-start segment on the next calendar day (HKFE futures: PostMarket 17:15-1.00:00:00 followed by PreMarket 00:00-03:00, the exchange's single After-Hours "T+1" session), SecurityExchangeHours.GetMarketHours returns ClosedAllDay for the whole next day whenever that day is a holiday. The 00:00-03:00 segment is part of the previous trading day's session, so the holiday wipes out the last three hours of a session that the exchange was open for and for which the data files exist. History and the data feed return nothing for that window.
Observed on HSI (Hang Seng Index futures, Market.HKFE): a History request for the 2020-06-24 evening session returns 405 minute quote bars ending 2020-06-25 00:00, while the identical request for 2020-06-29 returns 585 bars ending 2020-06-30 03:00. 2020-06-25 is an HKFE holiday (Tuen Ng Festival); 2020-06-30 is not. The minute file for 2020-06-25 holds 180 quote bars (00:00-02:59) with moving prices for every listed contract, i.e. the exchange traded and the data is there, the engine drops it.
This is the same mechanism family as #9866 (wildcard holidays unioned into every symbol entry, holiday checked before segments) but a different symptom: #9869 keeps a symbol's own early close or late open over a market-wide holiday, which does not help here because HSI has no early close on these dates; the problem is that the holiday is applied to a segment that belongs to the preceding session.
Reproduce
QC Cloud backtest 6bf433cb5ca93b302b6355957cb09a39, LEAN 2.5.0.0.18179, algorithm time zone Asia/Hong_Kong:
from AlgorithmImports import *
from datetime import datetime
class HsiEveningSessionProbe(QCAlgorithm):
def initialize(self):
self.set_start_date(2020, 7, 20)
self.set_end_date(2020, 7, 21)
self.set_cash(100000)
self.set_time_zone("Asia/Hong_Kong")
sid = SecurityIdentifier.parse("HSI XGLILOJXRWEH") # HSI Jul-2020, expiry 2020-07-30
symbol = Symbol(sid, "HSI")
sec = self.add_future_contract(symbol, Resolution.MINUTE, fill_forward=False, extended_market_hours=True)
hours = sec.exchange.hours
for d in [datetime(2020, 6, 24), datetime(2020, 6, 25), datetime(2020, 6, 29), datetime(2020, 6, 30)]:
mh = hours.get_market_hours(d)
self.log(f"MHDB {d:%Y-%m-%d %a}: isDateOpen(ext)={hours.is_date_open(d, True)} closedAllDay={mh.is_closed_all_day} segments={mh}")
for label, start, end in [
("Jun24 evening", datetime(2020, 6, 24, 17, 0), datetime(2020, 6, 25, 4, 0)),
("Jun29 evening", datetime(2020, 6, 29, 17, 0), datetime(2020, 6, 30, 4, 0)),
]:
bars = list(self.history[QuoteBar](symbol, start, end, Resolution.MINUTE, fill_forward=False, extended_market_hours=True))
self.log(f"HIST {label}: {len(bars)} bars" + (f" first_end={bars[0].end_time:%Y-%m-%d %H:%M} last_end={bars[-1].end_time:%Y-%m-%d %H:%M}" if bars else ""))
self.quit("probe done")
Output:
MHDB 2020-06-24 Wed: isDateOpen(ext)=True closedAllDay=False segments=Wednesday: PreMarket: 00:00:00-03:00:00 | PreMarket: 08:45:00-09:15:00 | Market: 09:15:00-12:00:00 | PreMarket: 12:30:00-13:00:00 | Market: 13:00:00-16:30:00 | PostMarket: 17:15:00-1.00:00:00
MHDB 2020-06-25 Thu: isDateOpen(ext)=False closedAllDay=True segments=Closed All Day
MHDB 2020-06-29 Mon: isDateOpen(ext)=True closedAllDay=False segments=Monday: PreMarket: 08:45:00-09:15:00 | Market: 09:15:00-12:00:00 | PreMarket: 12:30:00-13:00:00 | Market: 13:00:00-16:30:00 | PostMarket: 17:15:00-1.00:00:00
MHDB 2020-06-30 Tue: isDateOpen(ext)=True closedAllDay=False segments=Tuesday: PreMarket: 00:00:00-03:00:00 | ... | PostMarket: 17:15:00-1.00:00:00
HIST Jun24 evening: 405 bars first_end=2020-06-24 17:16 last_end=2020-06-25 00:00
HIST Jun29 evening: 585 bars first_end=2020-06-29 17:16 last_end=2020-06-30 03:00
Expected: 585 bars for both windows (405 bars 17:15-24:00 plus 180 bars 00:00-03:00). The cloud data file future/hkfe/minute/hsi/20200625_quote.zip contains exactly those 180 bars per contract (first bid/ask 24196/24241 at 00:00, last 24296/24297 at 02:59 for the 202007 contract), so this is not a data gap.
The same thing happens on every HKFE holiday that follows a normal trading day, e.g. 2020-07-01 (HKSAR Establishment Day), 2020-10-01 (National Day).
Evidence
Data/market-hours/market-hours-database.json:Future-hkfe-HSIhas noholidaysof its own;Future-hkfe-[*]lists6/25/2020,7/1/2020,10/1/2020, ... Both entries define Tuesday-Saturday asPreMarket 00:00:00-03:00:00, ...,PostMarket 17:15:00-1.00:00:00, i.e. the00:00-03:00segment is the tail of the previous day's post-market segment.Common/Util/MarketHoursDatabaseJsonConverter.cs:317-331: the market-wide ([*]) holidays are added to the symbol entry'sholidayDatesunless the symbol has an early close or late open on that date.Common/Securities/SecurityExchangeHours.cs:514-526:GetMarketHoursreturnsLocalMarketHours.ClosedAllDay(dayOfWeek)when the date is in_holidays, before any segment is considered.IsOpen(DateTime, bool)(:177-180) andIsOpen(DateTime, DateTime, bool)(:190-215) go through it, so the 00:00-03:00 window on the holiday is treated as closed.Common/Securities/LocalMarketHours.cs:377-391:IsContinuousMarketOpenalready knows that a segment ending atTime.OneDayfollowed by one starting atTimeSpan.Zeroon the next day is one continuous open period, butGetMarketHoursnever consults it on the holiday path.
Proposed change
In SecurityExchangeHours.GetMarketHours, when localDateTime.Date is a holiday, resolve the previous day's hours (GetMarketHours(localDateTime.Date.AddDays(-1)), which already applies that day's own holiday / early-close state). If its last segment ends at Time.OneDay and the weekday template for the holiday starts with a segment at TimeSpan.Zero, return a LocalMarketHours holding only the leading segments that are contiguous from midnight (here PreMarket 00:00-03:00), instead of ClosedAllDay. Otherwise keep the current behaviour. A preceding holiday or an early close on the eve ends the previous day before midnight and therefore yields ClosedAllDay as today.
Caveat worth a design decision before implementing: the same rule would apply to every market with a midnight-crossing session (CME futures Sunday-Thursday evenings). On a CME holiday whose eve has no early-close row in the database the holiday would become open from 00:00 to the first break of the weekday template, which is right where the exchange really did keep Globex open overnight and wrong where it did not. An alternative with no cross-market effect is an explicit per-entry opt-in (e.g. a continuesPreviousSession flag on the 00:00 segment) or an earlyCloses row on the holiday itself at the end of the continuation (7/1/2020: 03:00:00), which the existing #9869 logic would then honour.
Open questions
- Should
IsDateOpen(date, extendedMarketHours: true)return true for such a holiday (only the continuation trades), or stay false for scheduling purposes with onlyIsOpen/GetMarketHourschanged? - The database is not date-versioned: HKFE's T+1 session ended at 01:00 before mid-2019 and at 03:00 after, so the 2019 and earlier continuations are 60 bars, which the single weekly schedule cannot express. Out of scope here, noting it so the fix is not blamed for it.
Intercom conversation 215476364179934.
- Ngôn ngữ chính
- C#
- Star
- 22.5k
- Fork
- 5.4k
- Merge trung bình
- 22 giờ 6 phút
- Pull request đã merge (30 ngày)
- 81
Chuẩn bị môi trường
Khởi chạy dev container của dự án ngay trên trình duyệt, bằng tài khoản GitHub của bạn.
- Có Dockerfile hoặc tệp Docker Compose
- Có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của QuantConnect/Lean
-
PDF report generation can overwrite HTML when the destination extension is not lowercase .htmlĐang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
QuantConnect/Lean#10082 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
QuantConnect/Lean#10059 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Library Request: highspyĐang mởlibrary-request
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
QuantConnect/Lean#10034 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
QuantConnect/Lean#9965 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
QuantConnect/Lean#9955 ·
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của QuantConnect/Lean
Issue tương tự
-
bug emitter:client:csharp
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 84/100
microsoft/typespec#12572 · 1 bình luận · 1 reaction ·
Maintainer thường phản hồi trong vòng 1 ngày
-
needs-area-label
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
Maintainer thường phản hồi trong vòng 2 ngày
-
AWS Lambda: KeyNotFoundException drops HTTP-triggered transactions when allowAllHeaders is disabledĐang mởbug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 70/100
newrelic/newrelic-dotnet-agent#3949 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
area-networking
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
dotnet/aspnetcore#70545 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 87/100
Maintainer thường phản hồi trong vòng 1 ngày