Files
quantconnect--lean/Common/Securities/Future/FuturesExpiryUtilityFunctions.cs
2026-07-13 13:02:50 +08:00

423 lines
19 KiB
C#

/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* 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.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using static QuantConnect.StringExtensions;
namespace QuantConnect.Securities.Future
{
/// <summary>
/// Class to implement common functions used in FuturesExpiryFunctions
/// </summary>
public static class FuturesExpiryUtilityFunctions
{
private static readonly MarketHoursDatabase MarketHoursDatabase = MarketHoursDatabase.FromDataFolder();
/// <summary>
/// Get holiday list from the MHDB given the market and the symbol of the security
/// </summary>
/// <param name="market">The market the exchange resides in, i.e, 'usa', 'fxcm', ect...</param>
/// <param name="symbol">The particular symbol being traded</param>s
internal static HashSet<DateTime> GetExpirationHolidays(string market, string symbol)
{
var exchangeHours = MarketHoursDatabase.FromDataFolder()
.GetEntry(market, symbol, SecurityType.Future)
.ExchangeHours;
return exchangeHours.Holidays.Concat(exchangeHours.BankHolidays).ToHashSet();
}
/// <summary>
/// Method to retrieve n^th succeeding/preceding business day for a given day
/// </summary>
/// <param name="time">The current Time</param>
/// <param name="n">Number of business days succeeding current time. Use negative value for preceding business days</param>
/// <param name="holidays">Set of holidays to exclude. These should be sourced from the <see cref="MarketHoursDatabase"/></param>
/// <returns>The date-time after adding n business days</returns>
public static DateTime AddBusinessDays(DateTime time, int n, HashSet<DateTime> holidays)
{
if (n < 0)
{
var businessDays = -n;
var totalDays = 1;
do
{
var previousDay = time.AddDays(-totalDays);
if (!holidays.Contains(previousDay.Date) && previousDay.IsCommonBusinessDay())
{
businessDays--;
}
if (businessDays > 0) totalDays++;
} while (businessDays > 0);
return time.AddDays(-totalDays);
}
else
{
var businessDays = n;
var totalDays = 1;
do
{
var previousDay = time.AddDays(totalDays);
if (!holidays.Contains(previousDay.Date) && previousDay.IsCommonBusinessDay())
{
businessDays--;
}
if (businessDays > 0) totalDays++;
} while (businessDays > 0);
return time.AddDays(totalDays);
}
}
/// <summary>
/// Method to retrieve n^th succeeding/preceding business day for a given day if there was a holiday on that day
/// </summary>
/// <param name="time">The current Time</param>
/// <param name="n">Number of business days succeeding current time. Use negative value for preceding business days</param>
/// <param name="holidayList">Enumerable of holidays to exclude. These should be sourced from the <see cref="MarketHoursDatabase"/></param>
/// <returns>The date-time after adding n business days</returns>
public static DateTime AddBusinessDaysIfHoliday(DateTime time, int n, HashSet<DateTime> holidayList)
{
if (holidayList.Contains(time))
{
return AddBusinessDays(time, n, holidayList);
}
else
{
return time;
}
}
/// <summary>
/// Method to retrieve the n^th last business day of the delivery month.
/// </summary>
/// <param name="time">DateTime for delivery month</param>
/// <param name="n">Number of days</param>
/// <param name="holidayList">Holidays to use while calculating n^th business day. Useful for MHDB entries</param>
/// <returns>Nth Last Business day of the month</returns>
public static DateTime NthLastBusinessDay(DateTime time, int n, IEnumerable<DateTime> holidayList)
{
var daysInMonth = DateTime.DaysInMonth(time.Year, time.Month);
var lastDayOfMonth = new DateTime(time.Year, time.Month, daysInMonth);
var holidays = holidayList.Select(x => x.Date);
if (n > daysInMonth)
{
throw new ArgumentOutOfRangeException(nameof(n), Invariant(
$"Number of days ({n}) is larger than the size of month({daysInMonth})"
));
}
// Count the number of days in the month after the third to last business day
var businessDays = n;
var totalDays = 0;
do
{
var previousDay = lastDayOfMonth.AddDays(-totalDays);
if (NotHoliday(previousDay, holidays) && !holidays.Contains(previousDay))
{
businessDays--;
}
if (businessDays > 0) totalDays++;
} while (businessDays > 0);
return lastDayOfMonth.AddDays(-totalDays);
}
/// <summary>
/// Calculates the n^th business day of the month (includes checking for holidays)
/// </summary>
/// <param name="time">Month to calculate business day for</param>
/// <param name="nthBusinessDay">n^th business day to get</param>
/// <param name="holidayList"> Holidays to not count as business days</param>
/// <returns>Nth business day of the month</returns>
public static DateTime NthBusinessDay(DateTime time, int nthBusinessDay, IEnumerable<DateTime> holidayList)
{
var daysInMonth = DateTime.DaysInMonth(time.Year, time.Month);
var holidays = holidayList.Select(x => x.Date);
if (nthBusinessDay > daysInMonth)
{
throw new ArgumentOutOfRangeException(Invariant(
$"Argument nthBusinessDay (${nthBusinessDay}) is larger than the amount of days in the current month (${daysInMonth})"
));
}
if (nthBusinessDay < 1)
{
throw new ArgumentOutOfRangeException(Invariant(
$"Argument nthBusinessDay (${nthBusinessDay}) is less than one. Provide a number greater than one and less than the days in month"
));
}
var calculatedTime = new DateTime(time.Year, time.Month, 1);
var daysCounted = calculatedTime.IsCommonBusinessDay() ? 1 : 0;
var i = 0;
// Check for holiday up here in case we want the first business day and it is a holiday so that we don't skip over it.
// We also want to make sure that we don't stop on a weekend.
while (daysCounted < nthBusinessDay || holidays.Contains(calculatedTime) || !calculatedTime.IsCommonBusinessDay())
{
// The asset continues trading on days contained within `USHoliday.Dates`, but
// the last trade date is affected by those holidays. We check for
// both MHDB entries and holidays to get accurate business days
if (holidays.Contains(calculatedTime))
{
// Catches edge case where first day is on a friday
if (i == 0 && calculatedTime.DayOfWeek == DayOfWeek.Friday)
{
daysCounted = 0;
}
calculatedTime = calculatedTime.AddDays(1);
if (i != 0 && calculatedTime.IsCommonBusinessDay())
{
daysCounted++;
}
i++;
continue;
}
calculatedTime = calculatedTime.AddDays(1);
if (!holidays.Contains(calculatedTime) && NotHoliday(calculatedTime, holidays))
{
daysCounted++;
}
i++;
}
return calculatedTime;
}
/// <summary>
/// Method to retrieve the 2nd Friday of the given month
/// </summary>
/// <param name="time">Date from the given month</param>
/// <returns>2nd Friday of given month</returns>
public static DateTime SecondFriday(DateTime time) => NthFriday(time, 2);
/// <summary>
/// Method to retrieve the 3rd Friday of the given month
/// </summary>
/// <param name="time">Date from the given month</param>
/// <returns>3rd Friday of given month</returns>
public static DateTime ThirdFriday(DateTime time) => NthFriday(time, 3);
/// <summary>
/// Method to retrieve the Nth Friday of the given month
/// </summary>
/// <param name="time">Date from the given month</param>
/// <param name="n">The order of the Friday in the period</param>
/// <returns>Nth Friday of given month</returns>
public static DateTime NthFriday(DateTime time, int n) => NthWeekday(time, n, DayOfWeek.Friday);
/// <summary>
/// Method to retrieve third Wednesday of the given month (usually Monday).
/// </summary>
/// <param name="time">Date from the given month</param>
/// <returns>Third Wednesday of the given month</returns>
public static DateTime ThirdWednesday(DateTime time) => NthWeekday(time, 3, DayOfWeek.Wednesday);
/// <summary>
/// Method to retrieve the Nth Weekday of the given month
/// </summary>
/// <param name="time">Date from the given month</param>
/// <param name="n">The order of the Weekday in the period</param>
/// <param name="dayOfWeek">The day of the week</param>
/// <returns>Nth Weekday of given month</returns>
public static DateTime NthWeekday(DateTime time, int n, DayOfWeek dayOfWeek)
{
if (n < 1 || n > 5)
{
throw new ArgumentOutOfRangeException(nameof(n), "'n' lower than 1 or greater than 5");
}
var daysInMonth = DateTime.DaysInMonth(time.Year, time.Month);
return (from day in Enumerable.Range(1, daysInMonth)
where new DateTime(time.Year, time.Month, day).DayOfWeek == dayOfWeek
select new DateTime(time.Year, time.Month, day)).ElementAt(n - 1);
}
/// <summary>
/// Method to retrieve the last weekday of any month
/// </summary>
/// <param name="time">Date from the given month</param>
/// <param name="dayOfWeek">the last weekday to be found</param>
/// <returns>Last day of the we</returns>
public static DateTime LastWeekday(DateTime time, DayOfWeek dayOfWeek)
{
var daysInMonth = DateTime.DaysInMonth(time.Year, time.Month);
return (from day in Enumerable.Range(1, daysInMonth).Reverse()
where new DateTime(time.Year, time.Month, day).DayOfWeek == dayOfWeek
select new DateTime(time.Year, time.Month, day)).First();
}
/// <summary>
/// Method to retrieve the last Thursday of any month
/// </summary>
/// <param name="time">Date from the given month</param>
/// <returns>Last Thursday of the given month</returns>
public static DateTime LastThursday(DateTime time) => LastWeekday(time, DayOfWeek.Thursday);
/// <summary>
/// Method to retrieve the last Friday of any month
/// </summary>
/// <param name="time">Date from the given month</param>
/// <returns>Last Friday of the given month</returns>
public static DateTime LastFriday(DateTime time) => LastWeekday(time, DayOfWeek.Friday);
/// <summary>
/// Method to check whether a given time is holiday or not
/// </summary>
/// <param name="time">The DateTime for consideration</param>
/// <param name="holidayList">Enumerable of holidays to exclude. These should be sourced from the <see cref="MarketHoursDatabase"/></param>
/// <returns>True if the time is not a holidays, otherwise returns false</returns>
public static bool NotHoliday(DateTime time, IEnumerable<DateTime> holidayList)
{
return time.IsCommonBusinessDay() && !holidayList.Contains(time.Date);
}
/// <summary>
/// This function takes Thursday as input and returns true if four weekdays preceding it are not Holidays
/// </summary>
/// <param name="thursday">DateTime of a given Thursday</param>
/// <param name="holidayList">Enumerable of holidays to exclude. These should be sourced from the <see cref="MarketHoursDatabase"/></param>
/// <returns>False if DayOfWeek is not Thursday or is not preceded by four weekdays,Otherwise returns True</returns>
public static bool NotPrecededByHoliday(DateTime thursday, IEnumerable<DateTime> holidayList)
{
if (thursday.DayOfWeek != DayOfWeek.Thursday)
{
throw new ArgumentException("Input to NotPrecededByHolidays must be a Thursday");
}
var result = true;
// for Monday, Tuesday and Wednesday
for (var i = 1; i <= 3; i++)
{
if (!NotHoliday(thursday.AddDays(-i), holidayList))
{
result = false;
}
}
// for Friday
if (!NotHoliday(thursday.AddDays(-6), holidayList))
{
result = false;
}
return result;
}
/// <summary>
/// Gets the number of months between the contract month and the expiry date.
/// </summary>
/// <param name="underlying">The future symbol ticker</param>
/// <param name="futureExpiryDate">Expiry date to use to look up contract month delta. Only used for dairy, since we need to lookup its contract month in a pre-defined table.</param>
/// <returns>The number of months between the contract month and the contract expiry</returns>
public static int GetDeltaBetweenContractMonthAndContractExpiry(string underlying, DateTime? futureExpiryDate = null)
{
return ExpiriesPriorMonth.TryGetValue(underlying, out int value) ? value : 0;
}
/// <summary>
/// Helper method to retrieve the futures contract month
/// </summary>
public static DateTime GetFutureContractMonth(Symbol symbol)
{
if (symbol.SecurityType == SecurityType.FutureOption)
{
symbol = symbol.Underlying;
}
var contractExpirationDate = symbol.ID.Date.Date;
var monthsToAdd = GetDeltaBetweenContractMonthAndContractExpiry(symbol.ID.Symbol, contractExpirationDate);
var contractMonth = contractExpirationDate.AddDays(-(contractExpirationDate.Day - 1))
.AddMonths(monthsToAdd);
return contractMonth;
}
/// <summary>
/// Helper method to resolve a future expiration from it's contract month
/// </summary>
public static DateTime GetFutureExpirationFromContractMonth(string symbol, string market, DateTime contractMonth)
{
return GetFutureExpirationFromContractMonth(Symbol.CreateFuture(symbol, market, SecurityIdentifier.DefaultDate), contractMonth);
}
/// <summary>
/// Helper method to resolve a future expiration from it's contract month
/// </summary>
public static DateTime GetFutureExpirationFromContractMonth(Symbol future, DateTime contractMonth)
{
var futureExpiryFunc = FuturesExpiryFunctions.FuturesExpiryFunction(future.Canonical);
var futureExpiry = futureExpiryFunc(contractMonth);
return futureExpiry;
}
/// <summary>
/// This function returns the third Friday of the month, adjusted for holidays and weekends.
/// </summary>
public static DateTime ThirdFriday(DateTime time, Symbol contract)
{
if (contract.ID.SecurityType.IsOption())
{
return ThirdFriday(time, contract.Underlying);
}
var thirdFriday = ThirdFriday(time);
var holidays = GetExpirationHolidays(contract.ID.Market, contract.ID.Symbol);
return AddBusinessDaysIfHoliday(thirdFriday, -1, holidays);
}
/// <summary>
/// Checks if the future contract is expired.
/// </summary>
public static bool IsFutureContractExpired(Symbol symbol, DateTime currentUtcTime, MarketHoursDatabase marketHoursDatabase = null)
{
var exchangeHours = (marketHoursDatabase ?? MarketHoursDatabase).GetExchangeHours(symbol.ID.Market, symbol, symbol.SecurityType);
var currentTimeInExchangeTz = currentUtcTime.ConvertFromUtc(exchangeHours.TimeZone);
if (currentTimeInExchangeTz >= symbol.ID.Date)
{
return true;
}
return false;
}
private static readonly Dictionary<string, int> ExpiriesPriorMonth = new Dictionary<string, int>
{
{ Futures.Energy.ArgusLLSvsWTIArgusTradeMonth, 1 },
{ Futures.Energy.ArgusPropaneSaudiAramco, 1 },
{ Futures.Energy.BrentCrude, 2 },
{ Futures.Energy.BrentLastDayFinancial, 2 },
{ Futures.Energy.CrudeOilWTI, 1 },
{ Futures.Energy.MicroCrudeOilWTI, 1 },
{ Futures.Energy.Gasoline, 1 },
{ Futures.Energy.HeatingOil, 1 },
{ Futures.Energy.MarsArgusVsWTITradeMonth, 1 },
{ Futures.Energy.NaturalGas, 1 },
{ Futures.Energy.NaturalGasHenryHubLastDayFinancial, 1 },
{ Futures.Energy.NaturalGasHenryHubPenultimateFinancial, 1 },
{ Futures.Energy.WTIHoustonArgusVsWTITradeMonth, 1 },
{ Futures.Energy.WTIHoustonCrudeOil, 1 },
{ Futures.Softs.Sugar11, 1 },
{ Futures.Softs.Sugar11CME, 1 }
};
}
}