quantopian/trading_calendars

XNYS - incorrect start/end times using '.is_open_on_minute' function?

Closed this issue ยท 6 comments

Hi,
I think I may have found an error with the start and end times for XNYS when using .is_open_on_minute to return a boolean.

Reading through the core trading session hours for the NYSE, it seems to me that on a regular trading day the market starts at exactly 9:30:00 EST (inclusive) and is closed at exactly 16:00:00 EST (inclusive). I wrote some code to test some times and I am getting conflicting answers.

Currently,

  • 9:30:00 returns market closed. I think this should be open.
  • 9:30:01 returns market closed. I think this should be open.
  • 9:31:00 is the first time returned as open.
  • 16:00:00 returns market open. I think this should be closed.

My code attached.

Thanks in advance.

import pandas as pd
import pytz
import trading_calendars

NY = 'America/New_York'
us_calendar = trading_calendars.get_calendar('XNYS')

#Market on Friday at 9:30:00 EST
pd_dt1 = pd.Timestamp(year=2020, month=10, day=9, hour=9, minute=30, second=0, tz=pytz.timezone(NY))
open_boolean1 = us_calendar.is_open_on_minute(pd_dt1)
print("Friday at {} EST. open_boolean should be True: {}".format(pd_dt1.strftime('%H:%M:%S'), open_boolean1))

#Market on Friday at 9:30:01 EST
pd_dt2 = pd.Timestamp(year=2020, month=10, day=9, hour=9, minute=30, second=1, tz=pytz.timezone(NY))
open_boolean2 = us_calendar.is_open_on_minute(pd_dt2)
print("Friday at {} EST. open_boolean should be True: {}".format(pd_dt2.strftime('%H:%M:%S'), open_boolean2))

#Market on Friday at 9:31:0 EST
pd_dt3 = pd.Timestamp(year=2020, month=10, day=9, hour=9, minute=31, second=0, tz=pytz.timezone(NY))
open_boolean3 = us_calendar.is_open_on_minute(pd_dt3)
print("Friday at {} EST. open_boolean should be True: {}".format(pd_dt3.strftime('%H:%M:%S'), open_boolean3))

#Market on Friday at 16:00:00 EST
pd_dt4 = pd.Timestamp(year=2020, month=10, day=9, hour=16, minute=0, second=0, tz=pytz.timezone(NY))
open_boolean4 = us_calendar.is_open_on_minute(pd_dt4)
print("Friday at {} EST. open_boolean should be False: {}".format(pd_dt4.strftime('%H:%M:%S'), open_boolean4))

#Market on Friday at 16:00:01 EST
pd_dt5 = pd.Timestamp(year=2020, month=10, day=9, hour=16, minute=0, second=1, tz=pytz.timezone(NY))
open_boolean5 = us_calendar.is_open_on_minute(pd_dt5)
print("Friday at {} EST. open_boolean should be False: {}".format(pd_dt5.strftime('%H:%M:%S'), open_boolean5))

#Market on Saturday at 9:30:00 EST - Should be closed
pd_dt6 = pd.Timestamp(year=2020, month=10, day=10, hour=9, minute=30, second=0, tz=pytz.timezone(NY))
open_boolean6 = us_calendar.is_open_on_minute(pd_dt6)
print("Saturday at {} EST. open_boolean should be False: {}".format(pd_dt6.strftime('%H:%M:%S'), open_boolean6))

I would agree that 9:30:00 and all times between 9:30:00 and 9:31:00 are open for trading. I would agree that trading_calendars should include those times as open. For clarity, the start time as of now is set purposefully at 09:31:00, I assume that is for some logical reason downstream on a system that uses trading_calendars. I would suggest we look to refactor that aspect out so that the calendars can have the proper open times.

As for 16:00:00 I would argue that is also a valid trading time because the NYSE offers the MOC/LOC facility and those trades occur on the 16:00:00 bell.

@rsheftel the MOC/LOC for 16:00:00 logic makes sense to me. I think your assumption is fair and agree on your fix suggestion.

To add to the above. I tried the .open_and_close_for_session command and it seems to have similar/same results as those in my initial query.

import datetime
import trading_calendars

#Get datetime now
today_datetime = datetime.datetime.now()

#Get XNYS calendar
us_calendar = trading_calendars.get_calendar('XNYS')

#Retrieve open and close times for today
us_calendar.open_and_close_for_session(today_datetime.strftime("%Y-%m-%d"))


#Results
#(Timestamp('2020-10-09 13:31:00+0000', tz='UTC'),
# Timestamp('2020-10-09 20:00:00+0000', tz='UTC'))

If you look in the definition of the XNYS class you can see line #164 defines

open_times = ((None, time(9, 31)),)

https://github.com/quantopian/trading_calendars/blob/master/trading_calendars/exchange_calendar_xnys.py

Yep - as @rsheftel the open+1 minute behavior exists as an artifact of when trading_calendars was more tightly interwoven with zipline (https://github.com/quantopian/zipline) and we wanted the market to only indicate as open on the completion of the first minute bar.

I agree that for the majority of use cases this isn't quite the desired behavior. @richafrank and I have talked about changing it in the past, but haven't gotten around to it yet.

I should address this when I go fix up the documentation for trading_calendars though.

I'm closing as this is the currently expected behavior, though I agree it is initially confusing.

@gerrymanoim thanks for confirming that these functions are working as currently intended to. I too agree that it probably isn't the desired behavior in most cases.
Is the plan to change the behavior of this function in the future to reflect the exact opening time or to update the documentation to explain the expected current behavior?

My first plan is to update the docs to clarify the behavior (we have a documentation sprint coming soon).

In the future I'd like to get the actual behavior of this function changed, but that's further away.