Hello! Welcome back.
In our last lesson, we defined the core data models for our system: OptionContract, OptionsChain, and CreditSpreadPosition. This gave us a structured way to represent the financial instruments we'll be trading. Now, we need to bring the dimension of time into our framework, as trading is fundamentally time-sensitive.
Today's learning outcome is to build a utility for managing time-related logic, such as market hours and days-to-expiration (DTE). An automated trading system must be acutely aware of time. It needs to know when the market is open to trade, when it's closed, and it must be able to calculate critical metrics like DTE, which heavily influence option pricing and strategy decisions. We will create a centralized utility to handle all this, making the rest of our framework cleaner and more reliable.
1. Managing Market Hours and Holidays
A common mistake in simple trading systems is assuming market hours are fixed (e.g., 9:30 AM to 4:00 PM ET) and happen every weekday. In reality, markets have holidays, early closes (like the day after Thanksgiving), and other special schedules. Manually tracking this is complex and error-prone.
A much more robust solution is to use a specialized library. pandas-market-calendars is designed for exactly this purpose. It contains the schedules for many global exchanges, including holidays and special open/close times.
Let's explore how to use it.
Calendars - pandas_market_calendars documentation
The documentation for pandas-market-calendars provides clear examples of its main features. We will focus on getting an exchange calendar and querying its schedule.
Please review the following sections: Setup new exchange calendar: Look at how to get a calendar object for a specific exchange like 'NYSE' using mcal.get_calendar('NYSE'). Schedule: Understand how the .schedule() method returns a pandas DataFrame with precise market_open and market_close times for a given date range. Note how it correctly handles holidays. Open at time: See how the .open_at_time() method provides a simple boolean check to see if the market is open at a specific timestamp. Note that timezone-aware timestamps are important here.
Now, let's encapsulate this logic into a dedicated utility class. This aligns with good software design principles: the rest of our application won't need to know about pandas-market-calendars; it will just ask our TimeManager about market status.
We'll create a new file, framework/time_utils.py, for this utility.
# In framework/time_utils.py
import pandas as pd
import pandas_market_calendars as mcal
from datetime import date, datetime
import pytz
class TimeManager:
"""
A utility class to handle market time-related logic.
It provides methods to check market hours, get trading schedules,
and calculate days to expiration (DTE), abstracting away the
details of market calendars and holidays.
"""
def __init__(self, exchange_code: str = 'NYSE'):
"""
Initializes the TimeManager for a specific exchange.
:param exchange_code: The market identifier code (e.g., 'NYSE', 'CBOE').
"""
self.exchange = mcal.get_calendar(exchange_code)
self.market_tz = self.exchange.tz
def is_market_open(self) -> bool:
"""
Checks if the market is currently open.
:return: True if the market is open, False otherwise.
"""
# Get the current time in the market's timezone
now_utc = datetime.utcnow().replace(tzinfo=pytz.utc)
now_market_time = now_utc.astimezone(self.market_tz)
# Get the schedule for today
schedule = self.exchange.schedule(start_date=now_market_time.date(), end_date=now_market_time.date())
if schedule.empty:
return False # Market is closed today (holiday or weekend)
return self.exchange.open_at_time(schedule, now_market_time)
def get_market_schedule(self, start_date: date, end_date: date) -> pd.DataFrame:
"""
Gets the market open and close times for a given date range.
:param start_date: The start date of the period.
:param end_date: The end date of the period.
:return: A pandas DataFrame with market_open and market_close times.
"""
return self.exchange.schedule(start_date=start_date, end_date=end_date)
This class gives us two powerful and simple methods, is_market_open() and get_market_schedule(), that correctly handle all the complexities of the market's calendar.
2. Calculating Days to Expiration (DTE)
Days to Expiration (DTE) is a fundamental metric in options trading. It's the number of days remaining until an option expires.

DTE can be calculated in two common ways:
- Calendar DTE: The total number of calendar days until expiration. This is the most common calculation.
- Trading DTE: The number of remaining trading days until expiration. This is sometimes used for strategies that are sensitive to the number of trading opportunities left.
Let's add a method to our TimeManager to calculate both. We can leverage pandas-market-calendars again to find the number of valid trading days.
# Continuing in framework/time_utils.py
# ... inside the TimeManager class ...
def get_dte(self, expiration_date: date, dte_type: str = 'calendar') -> int:
"""
Calculates the Days to Expiration (DTE) from today.
:param expiration_date: The option's expiration date.
:param dte_type: 'calendar' for calendar days, 'trading' for trading days.
:return: The number of days to expiration.
"""
today = date.today()
if expiration_date < today:
return 0
if dte_type == 'calendar':
return (expiration_date - today).days
elif dte_type == 'trading':
# Get valid trading days between today and expiration
valid_days = self.exchange.valid_days(start_date=today, end_date=expiration_date)
return len(valid_days)
else:
raise ValueError("dte_type must be 'calendar' or 'trading'")
This method cleanly integrates into our TimeManager and uses the library's valid_days function to accurately count trading days, automatically excluding weekends and holidays.
Test your understanding!
In the previous lesson, we created an OptionContract data class with the field lastTradeDateOrContractMonth: str, which stores the expiration date as a string in 'YYYYMMDD' format.
How would you use our new TimeManager instance (tm = TimeManager()) to calculate the calendar DTE for an OptionContract instance named my_contract?
Show answer
First, you need to parse the string date from the contract into a datetime.date object. Then you can pass it to the get_dte method.
from datetime import datetime
# Assume 'my_contract' is an instance of the OptionContract data class
# my_contract.lastTradeDateOrContractMonth = '20241220'
# 1. Parse the string to a datetime object, then get the date part
expiration_date = datetime.strptime(my_contract.lastTradeDateOrContractMonth, '%Y%m%d').date()
# 2. Create a TimeManager instance
tm = TimeManager()
# 3. Call the get_dte method
dte = tm.get_dte(expiration_date, dte_type='calendar')
print(f"The contract has {dte} calendar days to expiration.")
This process of parsing string dates and calculating metrics is a perfect example of why having a dedicated time utility is so valuable.
3. Finding Standard Option Expiration Dates
While options can have various expiration cycles (weeklys, quarterlys), the standard monthly options typically expire on the third Friday of the month. Your trading logic might need to find these dates programmatically, for example, to select contracts that are "45 DTE".

Calculating this isn't trivial because the 3rd Friday can fall on different days of the month (from the 15th to the 21st). While you could write this logic yourself, a more robust approach is to use the python-dateutil library, which excels at complex date and time calculations.
The following Stack Overflow discussion offers several ways to solve this problem. The solutions using dateutil.relativedelta are particularly clean and powerful.
How can I get the 3rd Friday of a month in Python?
This Stack Overflow question, 'How can I get the 3rd Friday of a month in Python?', provides several excellent solutions. We will focus on the most idiomatic and readable one.
Read the answers that use dateutil.relativedelta. Specifically, look at the syntax relativedelta(weekday=FR(3)). This is a very expressive way to specify 'the third Friday'. Understand how it can be used to find the third Friday of any given month.
Let's create a standalone helper function in our time_utils.py file to perform this calculation. It will be a useful addition to our time utility module.
# In framework/time_utils.py, outside the TimeManager class
from dateutil.relativedelta import relativedelta, FR
def get_third_friday(year: int, month: int) -> date:
"""
Calculates the date of the third Friday for a given month and year.
This is the standard expiration date for monthly options.
:param year: The year.
:param month: The month.
:return: A date object for the third Friday of that month.
"""
# Start at the first day of the month
first_day_of_month = date(year, month, 1)
# Use relativedelta to find the 3rd Friday
third_friday = first_day_of_month + relativedelta(weekday=FR(3))
return third_friday
This concise function provides a crucial piece of logic for any options trading system.
Conclusion
In this lesson, we have constructed a vital component of our trading framework: a time utility. By using battle-tested libraries like pandas-market-calendars and python-dateutil, we have built a robust and reliable TimeManager that shields the rest of our system from the complexities of market schedules and date calculations.
Key Takeaways:
- Encapsulate Time Logic: Centralizing all time-related functions into a
TimeManagerclass makes the system cleaner, easier to maintain, and less prone to errors. - Leverage Specialized Libraries: For complex, standardized problems like market calendars (
pandas-market-calendars) and date rules (python-dateutil), using a dedicated library is far superior to building the logic from scratch. - DTE Calculation: You now have a method to accurately calculate DTE in both calendar and trading days, a critical input for many options strategies.
- Finding Expirations: You can now programmatically find standard monthly option expiration dates (the third Friday), enabling more dynamic contract selection logic.
Preview of the Next Lesson:
With our data models defined and our time utility in place, we are now ready to start interacting with the broker to get real data. In the next lesson, we will implement a function to fetch and parse the complete options chain for a given underlying symbol. We will use the OptionContract model from the previous lesson and prepare to use the TimeManager we built today to process the incoming data.