Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Algorithm/QCAlgorithm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3513,7 +3513,8 @@ public OptionChains OptionChains(IEnumerable<Symbol> symbols, bool flatten = fal
foreach (var (symbol, contracts) in optionChainsData)
{
var symbolProperties = SymbolPropertiesDatabase.GetSymbolProperties(symbol.ID.Market, symbol, symbol.SecurityType, AccountCurrency);
var optionChain = new OptionChain(symbol, GetTimeInExchangeTimeZone(symbol).Date, contracts, symbolProperties, flatten);
var exchangeHours = MarketHoursDatabase.GetExchangeHours(symbol.ID.Market, symbol, symbol.SecurityType);
var optionChain = new OptionChain(symbol, UtcTime.ConvertFromUtc(exchangeHours.TimeZone).Date, contracts, symbolProperties, exchangeHours, flatten);
chains.Add(symbol, optionChain);
}

Expand Down
2 changes: 1 addition & 1 deletion Common/Data/Market/OptionChain.Filters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,7 @@ public OptionChain PutLadder(int minDaysTillExpiry, decimal higherStrikeFromAtm,
/// </summary>
protected override OptionChainFilterUniverse CreateFilterUniverse()
{
return new OptionChainFilterUniverse(this);
return new OptionChainFilterUniverse(this, _symbolProperties, _exchangeHours);
}

/// <summary>
Expand Down
31 changes: 28 additions & 3 deletions Common/Data/Market/OptionChain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ namespace QuantConnect.Data.Market
/// </summary>
public partial class OptionChain : BaseChain<OptionContract, OptionContracts, OptionChain, OptionChainFilterUniverse>, IOptionContractFilters<OptionChain>
{
private readonly SymbolProperties _symbolProperties;
private readonly SecurityExchangeHours _exchangeHours;

/// <summary>
/// Initializes a new instance of the <see cref="OptionChain"/> class
/// </summary>
Expand All @@ -40,17 +43,35 @@ public OptionChain(Symbol canonicalOptionSymbol, DateTime time, bool flatten = t
{
}

/// <summary>
/// Initializes a new instance of the <see cref="OptionChain"/> class with the option symbol properties and exchange hours,
/// so the filters don't look them up
/// </summary>
/// <param name="canonicalOptionSymbol">The symbol for this chain.</param>
/// <param name="time">The time of this chain</param>
/// <param name="symbolProperties">The option symbol properties</param>
/// <param name="exchangeHours">The option exchange hours</param>
/// <param name="flatten">Whether to flatten the data frame</param>
internal OptionChain(Symbol canonicalOptionSymbol, DateTime time, SymbolProperties symbolProperties, SecurityExchangeHours exchangeHours,
bool flatten = true)
: this(canonicalOptionSymbol, time, flatten)
{
_symbolProperties = symbolProperties;
_exchangeHours = exchangeHours;
}

/// <summary>
/// Initializes a new option chain for a list of contracts as <see cref="OptionUniverse"/> instances
/// </summary>
/// <param name="canonicalOptionSymbol">The canonical option symbol</param>
/// <param name="time">The time of this chain</param>
/// <param name="contracts">The list of contracts data</param>
/// <param name="symbolProperties">The option symbol properties</param>
/// <param name="exchangeHours">The option exchange hours</param>
/// <param name="flatten">Whether to flatten the data frame</param>
public OptionChain(Symbol canonicalOptionSymbol, DateTime time, IEnumerable<OptionUniverse> contracts, SymbolProperties symbolProperties,
bool flatten = true)
: this(canonicalOptionSymbol, time, flatten)
SecurityExchangeHours exchangeHours = null, bool flatten = true)
: this(canonicalOptionSymbol, time, symbolProperties, exchangeHours, flatten)
{
var underlyingSet = false;
foreach (var contractData in contracts)
Expand All @@ -62,7 +83,7 @@ public OptionChain(Symbol canonicalOptionSymbol, DateTime time, IEnumerable<Opti
underlyingSet = true;
}
if (contractData.Symbol.ID.Date.Date < time.Date) continue;
Contracts[contractData.Symbol] = OptionContract.Create(contractData, symbolProperties);
Contracts[contractData.Symbol] = OptionContract.Create(contractData, symbolProperties, exchangeHours);
}
}

Expand All @@ -72,6 +93,8 @@ public OptionChain(Symbol canonicalOptionSymbol, DateTime time, IEnumerable<Opti
private OptionChain(OptionChain other)
: base(other)
{
_symbolProperties = other._symbolProperties;
_exchangeHours = other._exchangeHours;
}

/// <summary>
Expand All @@ -81,6 +104,8 @@ private OptionChain(OptionChain other)
private OptionChain(OptionChain other, IEnumerable<OptionContract> contracts)
: base(other, contracts)
{
_symbolProperties = other._symbolProperties;
_exchangeHours = other._exchangeHours;
}

/// <summary>
Expand Down
35 changes: 30 additions & 5 deletions Common/Data/Market/OptionContract.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

using QuantConnect.Data.UniverseSelection;
using QuantConnect.Interfaces;
using QuantConnect.Python;
using QuantConnect.Securities;
using QuantConnect.Securities.Option;
using System;
Expand All @@ -28,6 +29,8 @@ public class OptionContract : BaseContract
{
private IOptionData _optionData = OptionPriceModelResultData.Null;
private readonly SymbolProperties _symbolProperties;
private readonly SecurityExchangeHours _exchangeHours;
private DateTime? _lastTradingDate;

/// <summary>
/// Gets the strike price
Expand Down Expand Up @@ -105,9 +108,11 @@ public class OptionContract : BaseContract
public decimal UnderlyingLastPrice => _optionData.UnderlyingLastPrice;

/// <summary>
/// The option symbol properties
/// Calendar days from this contract's time until its last trading date: the previous open day for equity options expiring
/// on a Saturday or holiday, see <see cref="OptionSymbol.GetLastDayOfTrading(Symbol)"/>, the expiration date otherwise
/// </summary>
internal SymbolProperties SymbolProperties => _symbolProperties;
[PandasIgnore]
public override int DaysToExpiry => ((_lastTradingDate ??= GetLastTradingDate()) - Time.Date).Days;

/// <summary>
/// Initializes a new instance of the <see cref="OptionContract"/> class
Expand All @@ -117,17 +122,20 @@ public OptionContract(ISecurityPrice security)
: base(security.Symbol)
{
_symbolProperties = security.SymbolProperties;
_exchangeHours = security.Exchange.Hours;
}

/// <summary>
/// Initializes a new option contract from a given <see cref="OptionUniverse"/> instance
/// </summary>
/// <param name="contractData">The option universe contract data to use as source for this contract</param>
/// <param name="symbolProperties">The contract symbol properties</param>
public OptionContract(OptionUniverse contractData, SymbolProperties symbolProperties)
/// <param name="exchangeHours">The contract exchange hours</param>
public OptionContract(OptionUniverse contractData, SymbolProperties symbolProperties, SecurityExchangeHours exchangeHours = null)
: base(contractData.Symbol)
{
_symbolProperties = symbolProperties;
_exchangeHours = exchangeHours;
_optionData = new OptionUniverseData(contractData);
}

Expand Down Expand Up @@ -174,9 +182,10 @@ public static OptionContract Create(DateTime endTime, ISecurityPrice security, B
/// </summary>
/// <param name="contractData">The option universe contract data to use as source for this contract</param>
/// <param name="symbolProperties">The contract symbol properties</param>
public static OptionContract Create(OptionUniverse contractData, SymbolProperties symbolProperties)
/// <param name="exchangeHours">The contract exchange hours</param>
public static OptionContract Create(OptionUniverse contractData, SymbolProperties symbolProperties, SecurityExchangeHours exchangeHours = null)
{
var contract = new OptionContract(contractData, symbolProperties)
var contract = new OptionContract(contractData, symbolProperties, exchangeHours)
{
Time = contractData.EndTime,
};
Expand Down Expand Up @@ -210,6 +219,22 @@ internal override void Update(BaseData data)

#region Option Contract Data Handlers

/// <summary>
/// The previous open day for equity options expiring on a Saturday or a holiday, the expiration date otherwise
/// </summary>
private DateTime GetLastTradingDate()
{
if (Symbol.SecurityType != SecurityType.Option)
{
return Symbol.ID.Date.Date;
}

// equity options were dated on the Saturday after their last trading day until the OCC moved expirations to Friday in 2015
return _exchangeHours == null
? OptionSymbol.GetLastDayOfTrading(Symbol)
: OptionSymbol.GetLastDayOfTrading(Symbol, _exchangeHours);
}

private interface IOptionData
{
decimal LastPrice { get; }
Expand Down
5 changes: 5 additions & 0 deletions Common/Interfaces/ISecurityPrice.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ public interface ISecurityPrice
/// </summary>
SymbolProperties SymbolProperties { get; }

/// <summary>
/// <see cref="SecurityExchange"/> of the symbol
/// </summary>
SecurityExchange Exchange { get; }

/// <summary>
/// Update any security properties based on the latest market data and time
/// </summary>
Expand Down
16 changes: 15 additions & 1 deletion Common/Securities/ContractSecurityFilterUniverse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,16 @@ protected virtual DateTime AdjustExpirationReferenceDate(DateTime referenceDate)
return referenceDate;
}

/// <summary>
/// Gets the date the given contract stops trading, used by the expiration filters. Defaults to the contract expiration date
/// </summary>
/// <param name="contract">The contract</param>
/// <returns>The contract's last trading date</returns>
protected virtual DateTime GetLastTradingDate(TData contract)
{
return contract.Symbol.ID.Date.Date;
}

/// <summary>
/// Applies filter selecting options contracts based on a range of expiration dates relative to the current day
/// </summary>
Expand All @@ -363,7 +373,11 @@ public virtual T Expiration(TimeSpan minExpiry, TimeSpan maxExpiry)
var maxExpiryToDate = referenceDate + maxExpiry;

Data = Data
.Where(data => data.Symbol.ID.Date.Date >= minExpiryToDate && data.Symbol.ID.Date.Date <= maxExpiryToDate)
.Where(contract =>
{
var expiry = GetLastTradingDate(contract);
return expiry >= minExpiryToDate && expiry <= maxExpiryToDate;
})
.ToList();

return (T)this;
Expand Down
12 changes: 5 additions & 7 deletions Common/Securities/Option/OptionChainFilterUniverse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,13 @@ public class OptionChainFilterUniverse : BaseOptionFilterUniverse<OptionChainFil
/// Initializes a new instance of the <see cref="OptionChainFilterUniverse"/> class over the contracts of the given chain
/// </summary>
/// <param name="chain">The option chain to filter</param>
internal OptionChainFilterUniverse(OptionChain chain)
: base(GetContracts(chain), GetUnderlying(chain), chain.ExchangeTime, GetStrikeMultiplier(chain))
/// <param name="symbolProperties">The option symbol properties, if known</param>
/// <param name="exchangeHours">The option exchange hours, looked up in the market hours database when null</param>
internal OptionChainFilterUniverse(OptionChain chain, SymbolProperties symbolProperties, SecurityExchangeHours exchangeHours)
: base(GetContracts(chain), GetUnderlying(chain), chain.ExchangeTime, symbolProperties?.StrikeMultiplier ?? 1)
{
_symbol = chain.Symbol;
_exchangeHours = exchangeHours;
}

/// <summary>
Expand Down Expand Up @@ -91,10 +94,5 @@ private static BaseData GetUnderlying(OptionChain chain)
var underlying = chain.Underlying;
return underlying != null && underlying.Price != 0 ? underlying : null;
}

private static decimal GetStrikeMultiplier(OptionChain chain)
{
return chain.Contracts.Values.FirstOrDefault()?.SymbolProperties?.StrikeMultiplier ?? 1;
}
}
}
42 changes: 41 additions & 1 deletion Common/Securities/Option/OptionFilterUniverse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ public abstract class BaseOptionFilterUniverse<TUniverse, TData> : ContractSecur
private bool _refreshUniqueStrikes;
private DateTime _lastExchangeDate;
private readonly decimal _underlyingScaleFactor = 1;
// the contracts come grouped by expiration, so the last resolved date answers the next contract most of the time
private DateTime _lastExpiry;
private DateTime _lastTradingDate;

/// <summary>
/// The underlying price data
Expand Down Expand Up @@ -157,6 +160,43 @@ protected override DateTime AdjustExpirationReferenceDate(DateTime referenceDate
return referenceDate;
}

/// <summary>
/// Gets the last trading date of the given contract: the previous open day for equity options expiring on a Saturday
/// or a holiday, see <see cref="OptionSymbol.GetLastDayOfTrading(Symbol, SecurityExchangeHours)"/>, the expiration date otherwise
/// </summary>
/// <param name="contract">The contract</param>
/// <returns>The date the contract stops trading</returns>
protected override DateTime GetLastTradingDate(TData contract)
{
return GetLastTradingDate(contract.Symbol);
}

/// <summary>
/// Gets the last trading date of the given contract, see <see cref="GetLastTradingDate(TData)"/>. Uses the universe
/// exchange hours and keeps the last resolved expiration, since every contract in the universe shares them
/// </summary>
/// <param name="symbol">The contract symbol</param>
/// <returns>The date the contract stops trading</returns>
protected DateTime GetLastTradingDate(Symbol symbol)
{
var expiry = symbol.ID.Date.Date;
if (symbol.SecurityType != SecurityType.Option)
{
return expiry;
}

if (expiry != _lastExpiry)
{
// equity options were dated on the Saturday after their last trading day until the OCC moved expirations to Friday in 2015
_lastTradingDate = ExchangeHours == null
? OptionSymbol.GetLastDayOfTrading(symbol)
: OptionSymbol.GetLastDayOfTrading(symbol, ExchangeHours);
_lastExpiry = expiry;
}

return _lastTradingDate;
}

/// <summary>
/// Applies filter selecting options contracts based on a range of strikes in relative terms
/// </summary>
Expand Down Expand Up @@ -1186,7 +1226,7 @@ private TUniverse Ladder(OptionRight right, int minDaysTillExpiry, decimal highe
private IEnumerable<Symbol> GetContractsForExpiry(IEnumerable<Symbol> symbols, int minDaysTillExpiry)
{
var leastExpiryAccepted = _lastExchangeDate.AddDays(minDaysTillExpiry);
return symbols.Where(x => x.ID.Date >= leastExpiryAccepted)
return symbols.Where(x => GetLastTradingDate(x) >= leastExpiryAccepted)
.GroupBy(x => x.ID.Date)
.OrderBy(x => x.Key)
.FirstOrDefault()
Expand Down
18 changes: 14 additions & 4 deletions Common/Securities/Option/OptionSymbol.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,20 @@ public static string MapToUnderlying(string optionTicker, SecurityType securityT
/// <param name="symbol">Option symbol</param>
/// <returns></returns>
public static DateTime GetLastDayOfTrading(Symbol symbol)
{
var exchangeHours = MarketHoursDatabase.FromDataFolder()
.GetEntry(symbol.ID.Market, symbol, symbol.SecurityType)
.ExchangeHours;
return GetLastDayOfTrading(symbol, exchangeHours);
}

/// <summary>
/// Returns the last trading date for the option contract, using the given exchange hours instead of looking them up
/// </summary>
/// <param name="symbol">Option symbol</param>
/// <param name="exchangeHours">The exchange hours of the option</param>
/// <returns></returns>
public static DateTime GetLastDayOfTrading(Symbol symbol, SecurityExchangeHours exchangeHours)
{
// The OCC proposed rule change: starting from 1 Feb 2015 standard monthly contracts
// expire on 3rd Friday, not Saturday following 3rd Friday as it was before.
Expand All @@ -109,10 +123,6 @@ public static DateTime GetLastDayOfTrading(Symbol symbol)
daysBefore--;
}

var exchangeHours = MarketHoursDatabase.FromDataFolder()
.GetEntry(symbol.ID.Market, symbol, symbol.SecurityType)
.ExchangeHours;

while (!exchangeHours.IsDateOpen(symbolDateTime.AddDays(daysBefore)))
{
daysBefore--;
Expand Down
5 changes: 4 additions & 1 deletion Engine/DataFeeds/TimeSliceFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,10 @@ private bool HandleOptionData(DateTime algorithmTime, BaseData baseData, OptionC
if (!optionChains.TryGetValue(canonical, out chain))
{
// the data is already in the exchange time zone, unlike the algorithm time the chain is stamped with
chain = new OptionChain(canonical, algorithmTime) { ExchangeTime = baseData.EndTime };
chain = new OptionChain(canonical, algorithmTime, security.SymbolProperties, security.Exchange.Hours)
{
ExchangeTime = baseData.EndTime
};
optionChains[canonical] = chain;
}

Expand Down
Loading
Loading