.. code:: python import sys sys.path.append("../") from datetime import time import pandas as pd import pandas_market_calendars as mcal Calendars ========= Basic Usage ----------- Setup new exchange calendar ~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code:: python nyse = mcal.get_calendar('NYSE') Get the time zone .. code:: python nyse.tz.zone .. parsed-literal:: 'America/New_York' Get the AbstractHolidayCalendar object .. code:: python holidays = nyse.holidays() holidays.holidays[-5:] .. parsed-literal:: (numpy.datetime64('2200-06-19'), numpy.datetime64('2200-07-04'), numpy.datetime64('2200-09-01'), numpy.datetime64('2200-11-27'), numpy.datetime64('2200-12-25')) View the available information on regular market times .. code:: python print(nyse.regular_market_times) # more on this under the 'Customizations' heading .. parsed-literal:: ProtectedDict( {'pre': ((None, datetime.time(4, 0)),), 'market_open': ((None, datetime.time(10, 0)), ('1985-01-01', datetime.time(9, 30))), 'market_close': ((None, datetime.time(15, 0)), ('1952-09-29', datetime.time(15, 30)), ('1974-01-01', datetime.time(16, 0))), 'post': ((None, datetime.time(20, 0)),)} ) Exchange open valid business days ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Get the valid open exchange business dates between a start and end date. Note that Dec 26 (Christmas), Jan 2 (New Years) and all weekends are missing .. code:: python nyse.valid_days(start_date='2016-12-20', end_date='2017-01-10') .. parsed-literal:: DatetimeIndex(['2016-12-20 00:00:00+00:00', '2016-12-21 00:00:00+00:00', '2016-12-22 00:00:00+00:00', '2016-12-23 00:00:00+00:00', '2016-12-27 00:00:00+00:00', '2016-12-28 00:00:00+00:00', '2016-12-29 00:00:00+00:00', '2016-12-30 00:00:00+00:00', '2017-01-03 00:00:00+00:00', '2017-01-04 00:00:00+00:00', '2017-01-05 00:00:00+00:00', '2017-01-06 00:00:00+00:00', '2017-01-09 00:00:00+00:00', '2017-01-10 00:00:00+00:00'], dtype='datetime64[ns, UTC]', freq=None) Schedule ~~~~~~~~ .. code:: python schedule = nyse.schedule(start_date='2016-12-30', end_date='2017-01-10') schedule .. raw:: html
market_open market_close
2016-12-30 2016-12-30 14:30:00+00:00 2016-12-30 21:00:00+00:00
2017-01-03 2017-01-03 14:30:00+00:00 2017-01-03 21:00:00+00:00
2017-01-04 2017-01-04 14:30:00+00:00 2017-01-04 21:00:00+00:00
2017-01-05 2017-01-05 14:30:00+00:00 2017-01-05 21:00:00+00:00
2017-01-06 2017-01-06 14:30:00+00:00 2017-01-06 21:00:00+00:00
2017-01-09 2017-01-09 14:30:00+00:00 2017-01-09 21:00:00+00:00
2017-01-10 2017-01-10 14:30:00+00:00 2017-01-10 21:00:00+00:00
.. code:: python # with early closes early = nyse.schedule(start_date='2012-07-01', end_date='2012-07-10') early .. raw:: html
market_open market_close
2012-07-02 2012-07-02 13:30:00+00:00 2012-07-02 20:00:00+00:00
2012-07-03 2012-07-03 13:30:00+00:00 2012-07-03 17:00:00+00:00
2012-07-05 2012-07-05 13:30:00+00:00 2012-07-05 20:00:00+00:00
2012-07-06 2012-07-06 13:30:00+00:00 2012-07-06 20:00:00+00:00
2012-07-09 2012-07-09 13:30:00+00:00 2012-07-09 20:00:00+00:00
2012-07-10 2012-07-10 13:30:00+00:00 2012-07-10 20:00:00+00:00
.. code:: python # including pre and post-market extended = nyse.schedule(start_date='2012-07-01', end_date='2012-07-10', start="pre", end="post") extended .. raw:: html
pre market_open market_close post
2012-07-02 2012-07-02 08:00:00+00:00 2012-07-02 13:30:00+00:00 2012-07-02 20:00:00+00:00 2012-07-03 00:00:00+00:00
2012-07-03 2012-07-03 08:00:00+00:00 2012-07-03 13:30:00+00:00 2012-07-03 17:00:00+00:00 2012-07-03 17:00:00+00:00
2012-07-05 2012-07-05 08:00:00+00:00 2012-07-05 13:30:00+00:00 2012-07-05 20:00:00+00:00 2012-07-06 00:00:00+00:00
2012-07-06 2012-07-06 08:00:00+00:00 2012-07-06 13:30:00+00:00 2012-07-06 20:00:00+00:00 2012-07-07 00:00:00+00:00
2012-07-09 2012-07-09 08:00:00+00:00 2012-07-09 13:30:00+00:00 2012-07-09 20:00:00+00:00 2012-07-10 00:00:00+00:00
2012-07-10 2012-07-10 08:00:00+00:00 2012-07-10 13:30:00+00:00 2012-07-10 20:00:00+00:00 2012-07-11 00:00:00+00:00
.. code:: python # specific market times # CAVEAT: Looking at 2012-07-03, you can see that times will NOT be adjusted to special_opens/sepcial_closes # if market_open/market_close are not requested specific = nyse.schedule(start_date='2012-07-01', end_date='2012-07-10', market_times= ["post", "market_open"]) # this order will be kept specific .. raw:: html
post market_open
2012-07-02 2012-07-03 00:00:00+00:00 2012-07-02 13:30:00+00:00
2012-07-03 2012-07-04 00:00:00+00:00 2012-07-03 13:30:00+00:00
2012-07-05 2012-07-06 00:00:00+00:00 2012-07-05 13:30:00+00:00
2012-07-06 2012-07-07 00:00:00+00:00 2012-07-06 13:30:00+00:00
2012-07-09 2012-07-10 00:00:00+00:00 2012-07-09 13:30:00+00:00
2012-07-10 2012-07-11 00:00:00+00:00 2012-07-10 13:30:00+00:00
Get early closes ~~~~~~~~~~~~~~~~ .. code:: python nyse.early_closes(schedule=early) .. raw:: html
market_open market_close
2012-07-03 2012-07-03 13:30:00+00:00 2012-07-03 17:00:00+00:00
.. code:: python nyse.early_closes(schedule=extended) .. raw:: html
pre market_open market_close post
2012-07-03 2012-07-03 08:00:00+00:00 2012-07-03 13:30:00+00:00 2012-07-03 17:00:00+00:00 2012-07-03 17:00:00+00:00
Open at time ~~~~~~~~~~~~ Test to see if a given timestamp is during market open hours. (You can find more on this under the ‘Advanced open_at_time’ header) .. code:: python nyse.open_at_time(early, pd.Timestamp('2012-07-03 12:00', tz='America/New_York')) .. parsed-literal:: True .. code:: python nyse.open_at_time(early, pd.Timestamp('2012-07-03 16:00', tz='America/New_York')) .. parsed-literal:: False Other market times will also be considered .. code:: python nyse.open_at_time(extended, pd.Timestamp('2012-07-05 18:00', tz='America/New_York')) .. parsed-literal:: True but can be ignored by setting only_rth = True .. code:: python nyse.open_at_time(extended, pd.Timestamp('2012-07-05 18:00', tz='America/New_York'), only_rth = True) .. parsed-literal:: False Customizations ============== The simplest way to customize the market times of a calendar is by passing datetime.time objects to the constructor, which will modify the open and/or close of *regular trading hours*. .. code:: python cal = mcal.get_calendar('NYSE', open_time=time(10, 0), close_time=time(14, 30)) print('open, close: %s, %s' % (cal.open_time, cal.close_time)) .. parsed-literal:: open, close: 10:00:00, 14:30:00 More advanced customizations can be done after initialization or by inheriting from the closest MarketCalendar class, which requires an explanation of market times… Market times ------------ Market times are moments in a trading day that are contained in the ``regular_market_times`` attribute, for example: .. code:: python print("The original NYSE calendar: \n", nyse.regular_market_times) .. parsed-literal:: The original NYSE calendar: ProtectedDict( {'pre': ((None, datetime.time(4, 0)),), 'market_open': ((None, datetime.time(10, 0)), ('1985-01-01', datetime.time(9, 30))), 'market_close': ((None, datetime.time(15, 0)), ('1952-09-29', datetime.time(15, 30)), ('1974-01-01', datetime.time(16, 0))), 'post': ((None, datetime.time(20, 0)),)} ) NYSE’s regular trading hours are referenced by “market_open” and “market_close”, but NYSE also has extended hours, which are referenced by “pre” and “post”. *The attribute ``regular_market_times`` has these requirements:* - It needs to be a dictionary - Each market_time needs one entry - Regular open must be “market_open”, regular close must be “market_close”. - If there is a break, there must be a “break_start” and a “break_end”. - only ONE break is currently supported. - One tuple for each market_time, containing at least one tuple: - Each nested tuple needs at least two items: ``(first_date_used, time[, offset])``. - The first tuple’s date should be None, marking the start. In every tuple thereafter this is the date when ``time`` was first used. - Optionally (assumed to be zero, when not present), a positive or negative integer, representing an offset in number of days. - Dates need to be in ascending order, None coming first. E.g.: .. code:: python print(nyse.get_time("market_close", all_times= True)) # all_times = False only returns current .. parsed-literal:: ((None, datetime.time(15, 0)), ('1952-09-29', datetime.time(15, 30)), ('1974-01-01', datetime.time(16, 0))) The first known close was 3pm, which changed on 1952-09-29 to 3:30pm, which changed on 1974-01-01 to 4pm. The dates are the first dates that the new time was used. Customizing after initialization ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ There are three methods that allow customizing the ``regular_market_times`` of a MarketCalendar instance: \* ``.change_time(market_time, times)`` \* ``.add_time(market_time, times)`` \* ``.remove_time(market_time)`` .. code:: python cal = mcal.get_calendar("NYSE") cal.change_time("market_open", time(10,30)) print('open, close: %s, %s' % (cal.open_time, cal.close_time)) print("\nThe 'market_open' information is entirely replaced:\n", cal.regular_market_times) .. parsed-literal:: open, close: 10:30:00, 16:00:00 The 'market_open' information is entirely replaced: ProtectedDict( {'pre': ((None, datetime.time(4, 0)),), 'market_open': ((None, datetime.time(10, 30)),), 'market_close': ((None, datetime.time(15, 0)), ('1952-09-29', datetime.time(15, 30)), ('1974-01-01', datetime.time(16, 0))), 'post': ((None, datetime.time(20, 0)),)} ) .. code:: python cal.remove_time("post") cal.add_time("new_post", time(19)) print(cal.regular_market_times) .. parsed-literal:: ProtectedDict( {'pre': ((None, datetime.time(4, 0)),), 'market_open': ((None, datetime.time(10, 30)),), 'market_close': ((None, datetime.time(15, 0)), ('1952-09-29', datetime.time(15, 30)), ('1974-01-01', datetime.time(16, 0))), 'new_post': ((None, datetime.time(19, 0)),)} ) .. code:: python cal.remove_time("pre") cal.remove_time("new_post") The methods ``.add_time`` and ``.change_time`` also accept the time information in these formats: .. code:: python cal.add_time("just_time", time(10)) cal.add_time("with_offset", (time(10), -1)) cal.add_time("changes_and_offset", ((None, time(17)), ("2009-12-28", time(11), -2))) print(cal.regular_market_times) .. parsed-literal:: ProtectedDict( {'market_open': ((None, datetime.time(10, 30)),), 'market_close': ((None, datetime.time(15, 0)), ('1952-09-29', datetime.time(15, 30)), ('1974-01-01', datetime.time(16, 0))), 'just_time': ((None, datetime.time(10, 0)),), 'with_offset': ((None, datetime.time(10, 0), -1),), 'changes_and_offset': ((None, datetime.time(17, 0)), ('2009-12-28', datetime.time(11, 0), -2))} ) CAVEATS: ~~~~~~~~ FIRST ^^^^^ | *Internally, an order of market_times is detected based on their current time*. | Because of the offsets in “with_offset” and “changes_and_offset”, the columns in a schedule are in the following order: .. code:: python cal.schedule("2009-12-23", "2009-12-29", market_times= "all") .. raw:: html
changes_and_offset with_offset just_time market_open market_close
2009-12-23 2009-12-23 22:00:00+00:00 2009-12-22 15:00:00+00:00 2009-12-23 15:00:00+00:00 2009-12-23 15:30:00+00:00 2009-12-23 21:00:00+00:00
2009-12-24 2009-12-24 18:00:00+00:00 2009-12-23 15:00:00+00:00 2009-12-24 15:00:00+00:00 2009-12-24 15:30:00+00:00 2009-12-24 18:00:00+00:00
2009-12-28 2009-12-26 16:00:00+00:00 2009-12-27 15:00:00+00:00 2009-12-28 15:00:00+00:00 2009-12-28 15:30:00+00:00 2009-12-28 21:00:00+00:00
2009-12-29 2009-12-27 16:00:00+00:00 2009-12-28 15:00:00+00:00 2009-12-29 15:00:00+00:00 2009-12-29 15:30:00+00:00 2009-12-29 21:00:00+00:00
On 2009-12-23 changes_and_offset doesn’t seem to be in the right order, but as of 2009-12-28 it is. Passing a list to ``market_times``, allows you to keep a custom order: .. code:: python cal.schedule("2009-12-23", "2009-12-29", market_times= ["with_offset", "market_open", "market_close", "changes_and_offset"]) .. raw:: html
with_offset market_open market_close changes_and_offset
2009-12-23 2009-12-22 15:00:00+00:00 2009-12-23 15:30:00+00:00 2009-12-23 21:00:00+00:00 2009-12-23 22:00:00+00:00
2009-12-24 2009-12-23 15:00:00+00:00 2009-12-24 15:30:00+00:00 2009-12-24 18:00:00+00:00 2009-12-24 18:00:00+00:00
2009-12-28 2009-12-27 15:00:00+00:00 2009-12-28 15:30:00+00:00 2009-12-28 21:00:00+00:00 2009-12-26 16:00:00+00:00
2009-12-29 2009-12-28 15:00:00+00:00 2009-12-29 15:30:00+00:00 2009-12-29 21:00:00+00:00 2009-12-27 16:00:00+00:00
SECOND ^^^^^^ | *Special closes of market_closes will override all later times, special opens of market_opens will override all earlier times*. | In the prior schedule, 2009-12-24 is a special market_close, which was enforced in the changes_and_offset column. Providing ``False`` or ``None`` to the ``force_special_times`` keyword argument, changes this behaviour: .. code:: python # False - will only adjust the columns itself (changes_and_offset left alone, market_close adjusted) cal.schedule("2009-12-23", "2009-12-28", market_times= ["changes_and_offset", "market_close"], force_special_times= False) .. raw:: html
changes_and_offset market_close
2009-12-23 2009-12-23 22:00:00+00:00 2009-12-23 21:00:00+00:00
2009-12-24 2009-12-24 22:00:00+00:00 2009-12-24 18:00:00+00:00
2009-12-28 2009-12-26 16:00:00+00:00 2009-12-28 21:00:00+00:00
.. code:: python # None - will not adjust any column (both are left alone) cal.schedule("2009-12-23", "2009-12-28", market_times= ["changes_and_offset", "market_close"], force_special_times= None) .. raw:: html
changes_and_offset market_close
2009-12-23 2009-12-23 22:00:00+00:00 2009-12-23 21:00:00+00:00
2009-12-24 2009-12-24 22:00:00+00:00 2009-12-24 21:00:00+00:00
2009-12-28 2009-12-26 16:00:00+00:00 2009-12-28 21:00:00+00:00
Inheriting from a MarketCalendar ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You get even more control over a calendar (or help this package by contributing a calendar) by inheriting from a MarketCalendar class. The following three sections cover: :: * Setting special times for market_times * Setting interruptions * How to make sure open_at_time works Special Times ^^^^^^^^^^^^^ Any market_time in regular_market_times can have special times, which are looked for in two properties: :: special_{market_time}_adhoc same format as special_opens_adhoc, which is the same as special_market_open_adhoc special_{market_time} same format as special_opens, which is the same as special_market_open .. code:: python # For example, CFEExchangeCalendar only has the regular trading hours for the futures exchange (8:30 - 15:15). # If you want to use the equity options exchange (8:30 - 15:00), including the order acceptance time at 7:30, and # some special cases when the order acceptance time was different, do this: from pandas_market_calendars.exchange_calendar_cboe import CFEExchangeCalendar class DemoOptionsCalendar(CFEExchangeCalendar): # Inherit what doesn't need to change name = "Demo_Options" aliases = [name] regular_market_times = {**CFEExchangeCalendar.regular_market_times, # unpack the parent's regular_market_times "order_acceptance": ((None, time(7,30)),), # add your market time of interest "market_close": ((None, time(15)),)} # overwrite the market time you want to change @property def special_order_acceptance_adhoc(self): # include special cases return [(time(8,30), ["2000-12-27", "2001-12-27"])] .. code:: python options = mcal.get_calendar("Demo_Options") print(options.regular_market_times) .. parsed-literal:: ProtectedDict( {'market_open': ((None, datetime.time(8, 30)),), 'market_close': ((None, datetime.time(15, 0)),), 'order_acceptance': ((None, datetime.time(7, 30)),)} ) .. code:: python schedule = options.schedule("2000-12-22", "2000-12-28", start= "order_acceptance") schedule .. raw:: html
order_acceptance market_open market_close
2000-12-22 2000-12-22 13:30:00+00:00 2000-12-22 14:30:00+00:00 2000-12-22 21:00:00+00:00
2000-12-26 2000-12-26 13:30:00+00:00 2000-12-26 14:30:00+00:00 2000-12-26 21:00:00+00:00
2000-12-27 2000-12-27 14:30:00+00:00 2000-12-27 14:30:00+00:00 2000-12-27 21:00:00+00:00
2000-12-28 2000-12-28 13:30:00+00:00 2000-12-28 14:30:00+00:00 2000-12-28 21:00:00+00:00
Dec 25th is filtered out already because it is inherited from the CFEExchangeCalendar, and the special case on 2000-12-27 is also integrated Interruptions ^^^^^^^^^^^^^ MarketCalendar subclasses also support interruptions, which can be defined in the ``interruptions`` property. To view interruptions, you can use the ``interruptions_df`` property or set ``interruptions= True`` when calling ``schedule``. .. code:: python class InterruptionsDemo(DemoOptionsCalendar): @property def interruptions(self): return [ ("2002-02-03", (time(11), -1), time(11, 2)), ("2010-01-11", time(11), (time(11, 1), 1)), ("2010-01-13", time(9, 59), time(10), time(10, 29), time(10, 30)), ("2011-01-10", time(11), time(11, 1))] .. code:: python cal = InterruptionsDemo() .. code:: python cal.interruptions_df .. raw:: html
interruption_start_1 interruption_end_1 interruption_start_2 interruption_end_2
2002-02-03 2002-02-02 17:00:00+00:00 2002-02-03 17:02:00+00:00 NaT NaT
2010-01-11 2010-01-11 17:00:00+00:00 2010-01-12 17:01:00+00:00 NaT NaT
2010-01-13 2010-01-13 15:59:00+00:00 2010-01-13 16:00:00+00:00 2010-01-13 16:29:00+00:00 2010-01-13 16:30:00+00:00
2011-01-10 2011-01-10 17:00:00+00:00 2011-01-10 17:01:00+00:00 NaT NaT
.. code:: python sched = cal.schedule("2010-01-09", "2010-01-15", interruptions= True) sched .. raw:: html
market_open market_close interruption_start_1 interruption_end_1 interruption_start_2 interruption_end_2
2010-01-11 2010-01-11 14:30:00+00:00 2010-01-11 21:00:00+00:00 2010-01-11 17:00:00+00:00 2010-01-12 17:01:00+00:00 NaT NaT
2010-01-12 2010-01-12 14:30:00+00:00 2010-01-12 21:00:00+00:00 NaT NaT NaT NaT
2010-01-13 2010-01-13 14:30:00+00:00 2010-01-13 21:00:00+00:00 2010-01-13 15:59:00+00:00 2010-01-13 16:00:00+00:00 2010-01-13 16:29:00+00:00 2010-01-13 16:30:00+00:00
2010-01-14 2010-01-14 14:30:00+00:00 2010-01-14 21:00:00+00:00 NaT NaT NaT NaT
2010-01-15 2010-01-15 14:30:00+00:00 2010-01-15 21:00:00+00:00 NaT NaT NaT NaT
.. code:: python def is_open(c, s, *dates): for t in dates: print("open on", t, ":", c.open_at_time(s, t)) Advanced open_at_time ^^^^^^^^^^^^^^^^^^^^^ ``MarketCalendar.open_at_time`` uses the class attribute ``open_close_map`` to determine if a market_time opens or closes the market. It will also look for the ‘interruption\_’ prefix in the columns to respect interruptions. Here you can see that MarketCalendar.open_at_time respects interruptions (the last two timestamps): .. code:: python is_open(cal, sched, "2010-01-12 14:00:00", "2010-01-12 14:35:00","2010-01-13 15:59:00","2010-01-13 16:30:00") .. parsed-literal:: open on 2010-01-12 14:00:00 : False open on 2010-01-12 14:35:00 : True open on 2010-01-13 15:59:00 : False open on 2010-01-13 16:30:00 : True In the ``DemoOptionsCalendar``, we did not specify what order_acceptance means for the market, which will not allow open_at_time to work. .. code:: python sched = cal.schedule("2010-01-09", "2010-01-15", start= "order_acceptance", interruptions= True) try: cal.open_at_time(sched, "2010-01-12") except ValueError as e: print(e) .. parsed-literal:: You seem to be using a schedule that isn't based on the market_times, or includes market_times that are not represented in the open_close_map. .. code:: python # These are the defaults that every MarketCalendar has, which is still missing order_accpetance. print(cal.open_close_map) .. parsed-literal:: ProtectedDict( {'market_open': True, 'market_close': False, 'break_start': False, 'break_end': True, 'pre': True, 'post': False} ) To correct the calendar we should include the following: .. code:: python class OpenCloseDemo(InterruptionsDemo): open_close_map = {**CFEExchangeCalendar.open_close_map, "order_acceptance": True} cal = OpenCloseDemo() sched = cal.schedule("2010-01-09", "2010-01-15", start= "order_acceptance", interruptions= True) sched .. raw:: html
order_acceptance market_open market_close interruption_start_1 interruption_end_1 interruption_start_2 interruption_end_2
2010-01-11 2010-01-11 13:30:00+00:00 2010-01-11 14:30:00+00:00 2010-01-11 21:00:00+00:00 2010-01-11 17:00:00+00:00 2010-01-12 17:01:00+00:00 NaT NaT
2010-01-12 2010-01-12 13:30:00+00:00 2010-01-12 14:30:00+00:00 2010-01-12 21:00:00+00:00 NaT NaT NaT NaT
2010-01-13 2010-01-13 13:30:00+00:00 2010-01-13 14:30:00+00:00 2010-01-13 21:00:00+00:00 2010-01-13 15:59:00+00:00 2010-01-13 16:00:00+00:00 2010-01-13 16:29:00+00:00 2010-01-13 16:30:00+00:00
2010-01-14 2010-01-14 13:30:00+00:00 2010-01-14 14:30:00+00:00 2010-01-14 21:00:00+00:00 NaT NaT NaT NaT
2010-01-15 2010-01-15 13:30:00+00:00 2010-01-15 14:30:00+00:00 2010-01-15 21:00:00+00:00 NaT NaT NaT NaT
Now we can see that not only interruptions (last two) but also order_acceptance (first) is respected .. code:: python is_open(cal, sched, "2010-01-11 13:35:00", "2010-01-12 14:35:00", "2010-01-13 15:59:00", "2010-01-13 16:30:00") .. parsed-literal:: open on 2010-01-11 13:35:00 : True open on 2010-01-12 14:35:00 : True open on 2010-01-13 15:59:00 : False open on 2010-01-13 16:30:00 : True You can even change this dynamically, using the ``opens`` keyword in ``.change_time`` and ``.add_time`` .. code:: python cal.change_time("order_acceptance", cal["order_acceptance"], opens= False) is_open(cal, sched, "2010-01-11 13:35:00", "2010-01-12 14:35:00", "2010-01-13 15:59:00", "2010-01-13 16:30:00") .. parsed-literal:: open on 2010-01-11 13:35:00 : False open on 2010-01-12 14:35:00 : True open on 2010-01-13 15:59:00 : False open on 2010-01-13 16:30:00 : True .. code:: python cal.change_time("order_acceptance", cal["order_acceptance"], opens= True) cal.add_time("order_closed", time(8), opens= False) sched = cal.schedule("2010-01-09", "2010-01-15", start= "order_acceptance") sched .. raw:: html
order_acceptance order_closed market_open market_close
2010-01-11 2010-01-11 13:30:00+00:00 2010-01-11 14:00:00+00:00 2010-01-11 14:30:00+00:00 2010-01-11 21:00:00+00:00
2010-01-12 2010-01-12 13:30:00+00:00 2010-01-12 14:00:00+00:00 2010-01-12 14:30:00+00:00 2010-01-12 21:00:00+00:00
2010-01-13 2010-01-13 13:30:00+00:00 2010-01-13 14:00:00+00:00 2010-01-13 14:30:00+00:00 2010-01-13 21:00:00+00:00
2010-01-14 2010-01-14 13:30:00+00:00 2010-01-14 14:00:00+00:00 2010-01-14 14:30:00+00:00 2010-01-14 21:00:00+00:00
2010-01-15 2010-01-15 13:30:00+00:00 2010-01-15 14:00:00+00:00 2010-01-15 14:30:00+00:00 2010-01-15 21:00:00+00:00
.. code:: python is_open(cal, sched, "2010-01-11 13:35:00", "2010-01-11 14:15:00", "2010-01-11 14:35:00") .. parsed-literal:: open on 2010-01-11 13:35:00 : True open on 2010-01-11 14:15:00 : False open on 2010-01-11 14:35:00 : True Extra Usage =========== Checking for special times -------------------------- *The following functions respect varying times in regular_market_times* These will only check market_close/market_open columns for early/late times .. code:: python options.early_closes(schedule), options.late_opens(schedule) .. parsed-literal:: (Empty DataFrame Columns: [order_acceptance, market_open, market_close] Index: [], Empty DataFrame Columns: [order_acceptance, market_open, market_close] Index: []) The ``is_different`` method uses the name of the series passed to it, to determine which rows are not equal to the regular market times, and return a boolean Series .. code:: python schedule[options.is_different(schedule["order_acceptance"])] .. raw:: html
order_acceptance market_open market_close
2000-12-27 2000-12-27 14:30:00+00:00 2000-12-27 14:30:00+00:00 2000-12-27 21:00:00+00:00
You can also pass ``pd.Series.lt/ -.gt / -.ge / etc.`` for more control over the comparison .. code:: python schedule[options.is_different(schedule["order_acceptance"], pd.Series.lt)] .. raw:: html
order_acceptance market_open market_close
.. code:: python schedule[options.is_different(schedule["order_acceptance"], pd.Series.ge)] .. raw:: html
order_acceptance market_open market_close
2000-12-22 2000-12-22 13:30:00+00:00 2000-12-22 14:30:00+00:00 2000-12-22 21:00:00+00:00
2000-12-26 2000-12-26 13:30:00+00:00 2000-12-26 14:30:00+00:00 2000-12-26 21:00:00+00:00
2000-12-27 2000-12-27 14:30:00+00:00 2000-12-27 14:30:00+00:00 2000-12-27 21:00:00+00:00
2000-12-28 2000-12-28 13:30:00+00:00 2000-12-28 14:30:00+00:00 2000-12-28 21:00:00+00:00
Checking custom times ~~~~~~~~~~~~~~~~~~~~~ .. code:: python options.has_custom # order_acceptance is not considered custom because it is hardcoded into the class .. parsed-literal:: False .. code:: python options.add_time("post", time(17)) .. code:: python options.has_custom, options.is_custom("market_open"), options.is_custom("post") .. parsed-literal:: (True, False, True) Get the regular time on a certain date ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code:: python nyse.open_time, nyse.close_time # these always refer to the current time of market_open/market_close .. parsed-literal:: (datetime.time(9, 30, tzinfo=), datetime.time(16, 0, tzinfo=)) .. code:: python nyse.get_time("post"), nyse.get_time("pre") # these also refer to the current time .. parsed-literal:: (datetime.time(20, 0, tzinfo=), datetime.time(4, 0, tzinfo=)) .. code:: python # open_time_on looks for market_open, close_time_on looks for market_close and get_time_on looks for the provided market time nyse.open_time_on("1950-01-01"), nyse.get_time_on("market_close", "1960-01-01") .. parsed-literal:: (datetime.time(10, 0, tzinfo=), datetime.time(15, 30, tzinfo=)) Special Methods ~~~~~~~~~~~~~~~ .. code:: python nyse["market_open"] # gets the current time .. parsed-literal:: datetime.time(9, 30, tzinfo=) .. code:: python nyse["market_open", "all"] # gets all times .. parsed-literal:: ((None, datetime.time(10, 0)), ('1985-01-01', datetime.time(9, 30))) .. code:: python nyse["market_open", "1950-01-01"] # gets the time on a certain date .. parsed-literal:: datetime.time(10, 0, tzinfo=) This tries to *add* a time, which will fail if it already exists. In that case ``.change_time`` is the explicit alternative. .. code:: python nyse["new_post"] = time(20) nyse["new_post"] .. parsed-literal:: datetime.time(20, 0, tzinfo=) .. code:: python try: nyse["post"] = time(19) except AssertionError as e: print(e) .. parsed-literal:: post is already in regular_market_times: ['pre', 'market_open', 'market_close', 'post', 'new_post'] Array of special times ~~~~~~~~~~~~~~~~~~~~~~ .. code:: python options.special_dates("order_acceptance", "2000-12-22", "2001-12-28") .. parsed-literal:: 2000-12-27 2000-12-27 14:30:00+00:00 2001-12-27 2001-12-27 14:30:00+00:00 dtype: datetime64[ns, UTC] Handling discontinued times ~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code:: python xkrx = mcal.get_calendar("XKRX") .. parsed-literal:: /opt/hostedtoolcache/Python/3.10.9/x64/lib/python3.10/site-packages/pandas_market_calendars/market_calendar.py:144: UserWarning: ['break_start', 'break_end'] are discontinued, the dictionary `.discontinued_market_times` has the dates on which these were discontinued. The times as of those dates are incorrect, use .remove_time(market_time) to ignore a market_time. warnings.warn(f"{list(discontinued.keys())} are discontinued, the dictionary" .. code:: python xkrx.schedule("2020-01-01", "2020-01-05") .. raw:: html
market_open break_start break_end market_close
2020-01-02 2020-01-02 00:00:00+00:00 2020-01-02 03:00:00+00:00 2020-01-02 04:00:00+00:00 2020-01-02 06:30:00+00:00
2020-01-03 2020-01-03 00:00:00+00:00 2020-01-03 03:00:00+00:00 2020-01-03 04:00:00+00:00 2020-01-03 06:30:00+00:00
.. code:: python xkrx.discontinued_market_times # these are the dates as of which the market time didn't exist anymore .. parsed-literal:: ProtectedDict({'break_start': Timestamp('2000-05-22 00:00:00'), 'break_end': Timestamp('2000-05-22 00:00:00')}) .. code:: python print(xkrx.has_discontinued) xkrx.remove_time("break_start") xkrx.remove_time("break_end") print(xkrx.has_discontinued) .. parsed-literal:: True False .. code:: python xkrx.schedule("2020-01-01", "2020-01-05") .. raw:: html
market_open market_close
2020-01-02 2020-01-02 00:00:00+00:00 2020-01-02 06:30:00+00:00
2020-01-03 2020-01-03 00:00:00+00:00 2020-01-03 06:30:00+00:00
Helpers ======= *schedules with columns other than market_open, break_start, break_end or market_close are not yet supported by the following functions* Date Range ---------- This function will take a schedule DataFrame and return a DatetimeIndex with all timestamps at the frequency given for all of the exchange open dates and times. .. code:: python mcal.date_range(early, frequency='1D') .. parsed-literal:: DatetimeIndex(['2012-07-02 20:00:00+00:00', '2012-07-03 17:00:00+00:00', '2012-07-05 20:00:00+00:00', '2012-07-06 20:00:00+00:00', '2012-07-09 20:00:00+00:00', '2012-07-10 20:00:00+00:00'], dtype='datetime64[ns, UTC]', freq=None) .. code:: python mcal.date_range(early, frequency='1H') .. parsed-literal:: DatetimeIndex(['2012-07-02 14:30:00+00:00', '2012-07-02 15:30:00+00:00', '2012-07-02 16:30:00+00:00', '2012-07-02 17:30:00+00:00', '2012-07-02 18:30:00+00:00', '2012-07-02 19:30:00+00:00', '2012-07-02 20:00:00+00:00', '2012-07-03 14:30:00+00:00', '2012-07-03 15:30:00+00:00', '2012-07-03 16:30:00+00:00', '2012-07-03 17:00:00+00:00', '2012-07-05 14:30:00+00:00', '2012-07-05 15:30:00+00:00', '2012-07-05 16:30:00+00:00', '2012-07-05 17:30:00+00:00', '2012-07-05 18:30:00+00:00', '2012-07-05 19:30:00+00:00', '2012-07-05 20:00:00+00:00', '2012-07-06 14:30:00+00:00', '2012-07-06 15:30:00+00:00', '2012-07-06 16:30:00+00:00', '2012-07-06 17:30:00+00:00', '2012-07-06 18:30:00+00:00', '2012-07-06 19:30:00+00:00', '2012-07-06 20:00:00+00:00', '2012-07-09 14:30:00+00:00', '2012-07-09 15:30:00+00:00', '2012-07-09 16:30:00+00:00', '2012-07-09 17:30:00+00:00', '2012-07-09 18:30:00+00:00', '2012-07-09 19:30:00+00:00', '2012-07-09 20:00:00+00:00', '2012-07-10 14:30:00+00:00', '2012-07-10 15:30:00+00:00', '2012-07-10 16:30:00+00:00', '2012-07-10 17:30:00+00:00', '2012-07-10 18:30:00+00:00', '2012-07-10 19:30:00+00:00', '2012-07-10 20:00:00+00:00'], dtype='datetime64[ns, UTC]', freq=None) Merge schedules --------------- .. code:: python # NYSE Calendar nyse = mcal.get_calendar('NYSE') schedule_nyse = nyse.schedule('2015-12-20', '2016-01-06') schedule_nyse .. raw:: html
market_open market_close
2015-12-21 2015-12-21 14:30:00+00:00 2015-12-21 21:00:00+00:00
2015-12-22 2015-12-22 14:30:00+00:00 2015-12-22 21:00:00+00:00
2015-12-23 2015-12-23 14:30:00+00:00 2015-12-23 21:00:00+00:00
2015-12-24 2015-12-24 14:30:00+00:00 2015-12-24 18:00:00+00:00
2015-12-28 2015-12-28 14:30:00+00:00 2015-12-28 21:00:00+00:00
2015-12-29 2015-12-29 14:30:00+00:00 2015-12-29 21:00:00+00:00
2015-12-30 2015-12-30 14:30:00+00:00 2015-12-30 21:00:00+00:00
2015-12-31 2015-12-31 14:30:00+00:00 2015-12-31 21:00:00+00:00
2016-01-04 2016-01-04 14:30:00+00:00 2016-01-04 21:00:00+00:00
2016-01-05 2016-01-05 14:30:00+00:00 2016-01-05 21:00:00+00:00
2016-01-06 2016-01-06 14:30:00+00:00 2016-01-06 21:00:00+00:00
.. code:: python # LSE Calendar lse = mcal.get_calendar('LSE') schedule_lse = lse.schedule('2015-12-20', '2016-01-06') schedule_lse .. raw:: html
market_open market_close
2015-12-21 2015-12-21 08:00:00+00:00 2015-12-21 16:30:00+00:00
2015-12-22 2015-12-22 08:00:00+00:00 2015-12-22 16:30:00+00:00
2015-12-23 2015-12-23 08:00:00+00:00 2015-12-23 16:30:00+00:00
2015-12-24 2015-12-24 08:00:00+00:00 2015-12-24 12:30:00+00:00
2015-12-29 2015-12-29 08:00:00+00:00 2015-12-29 16:30:00+00:00
2015-12-30 2015-12-30 08:00:00+00:00 2015-12-30 16:30:00+00:00
2015-12-31 2015-12-31 08:00:00+00:00 2015-12-31 12:30:00+00:00
2016-01-04 2016-01-04 08:00:00+00:00 2016-01-04 16:30:00+00:00
2016-01-05 2016-01-05 08:00:00+00:00 2016-01-05 16:30:00+00:00
2016-01-06 2016-01-06 08:00:00+00:00 2016-01-06 16:30:00+00:00
Inner merge ~~~~~~~~~~~ This will find the dates where both the NYSE and LSE are open. Notice that Dec 28th is open for NYSE but not LSE. Also note that some days have a close prior to the open. This function does not currently check for that. .. code:: python mcal.merge_schedules(schedules=[schedule_nyse, schedule_lse], how='inner') .. raw:: html
market_open market_close
2015-12-21 2015-12-21 14:30:00+00:00 2015-12-21 16:30:00+00:00
2015-12-22 2015-12-22 14:30:00+00:00 2015-12-22 16:30:00+00:00
2015-12-23 2015-12-23 14:30:00+00:00 2015-12-23 16:30:00+00:00
2015-12-24 2015-12-24 14:30:00+00:00 2015-12-24 12:30:00+00:00
2015-12-29 2015-12-29 14:30:00+00:00 2015-12-29 16:30:00+00:00
2015-12-30 2015-12-30 14:30:00+00:00 2015-12-30 16:30:00+00:00
2015-12-31 2015-12-31 14:30:00+00:00 2015-12-31 12:30:00+00:00
2016-01-04 2016-01-04 14:30:00+00:00 2016-01-04 16:30:00+00:00
2016-01-05 2016-01-05 14:30:00+00:00 2016-01-05 16:30:00+00:00
2016-01-06 2016-01-06 14:30:00+00:00 2016-01-06 16:30:00+00:00
Outer merge ~~~~~~~~~~~ This will return the dates and times where either the NYSE or the LSE are open .. code:: python mcal.merge_schedules(schedules=[schedule_nyse, schedule_lse], how='outer') .. raw:: html
market_open market_close
2015-12-21 2015-12-21 08:00:00+00:00 2015-12-21 21:00:00+00:00
2015-12-22 2015-12-22 08:00:00+00:00 2015-12-22 21:00:00+00:00
2015-12-23 2015-12-23 08:00:00+00:00 2015-12-23 21:00:00+00:00
2015-12-24 2015-12-24 08:00:00+00:00 2015-12-24 18:00:00+00:00
2015-12-28 2015-12-28 14:30:00+00:00 2015-12-28 21:00:00+00:00
2015-12-29 2015-12-29 08:00:00+00:00 2015-12-29 21:00:00+00:00
2015-12-30 2015-12-30 08:00:00+00:00 2015-12-30 21:00:00+00:00
2015-12-31 2015-12-31 08:00:00+00:00 2015-12-31 21:00:00+00:00
2016-01-04 2016-01-04 08:00:00+00:00 2016-01-04 21:00:00+00:00
2016-01-05 2016-01-05 08:00:00+00:00 2016-01-05 21:00:00+00:00
2016-01-06 2016-01-06 08:00:00+00:00 2016-01-06 21:00:00+00:00
Use holidays in numpy --------------------- This will use your exchange calendar in numpy to add business days .. code:: python import numpy as np cme = mcal.get_calendar("CME_Agriculture") np.busday_offset(dates="2020-05-22", holidays=cme.holidays().holidays, offsets=1) .. parsed-literal:: numpy.datetime64('2020-05-26') Trading Breaks -------------- Some markets have breaks in the day, like the CME Equity Futures markets which are closed from 4:15 - 4:35 (NY) daily. These calendars will have additional columns in the schedule() DataFrame .. code:: python cme = mcal.get_calendar('CME_Equity') schedule = cme.schedule('2020-01-01', '2020-01-04') schedule .. raw:: html
market_open break_start break_end market_close
2020-01-02 2020-01-01 23:00:00+00:00 2020-01-02 21:15:00+00:00 2020-01-02 21:30:00+00:00 2020-01-02 22:00:00+00:00
2020-01-03 2020-01-02 23:00:00+00:00 2020-01-03 21:15:00+00:00 2020-01-03 21:30:00+00:00 2020-01-03 22:00:00+00:00
The date_range() properly accounts for the breaks .. code:: python mcal.date_range(schedule, '5H') .. parsed-literal:: DatetimeIndex(['2020-01-02 04:00:00+00:00', '2020-01-02 09:00:00+00:00', '2020-01-02 14:00:00+00:00', '2020-01-02 19:00:00+00:00', '2020-01-02 21:15:00+00:00', '2020-01-02 22:00:00+00:00', '2020-01-03 04:00:00+00:00', '2020-01-03 09:00:00+00:00', '2020-01-03 14:00:00+00:00', '2020-01-03 19:00:00+00:00', '2020-01-03 21:15:00+00:00', '2020-01-03 22:00:00+00:00'], dtype='datetime64[ns, UTC]', freq=None)