/*
* 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 QuantConnect.Securities.Future;
namespace QuantConnect.Securities.FutureOption
{
///
/// Creates the underlying Symbol that corresponds to a futures options contract
///
///
/// Because there can exist futures options (FOP) contracts that have an underlying Future
/// that does not have the same contract month as FOPs contract month, we need a way to resolve
/// the underlying Symbol of the FOP to the specific future contract it belongs to.
///
/// Luckily, these FOPs all follow a pattern as to how the underlying is determined. The
/// method will automatically resolve the FOP contract's
/// underlying Future, and will ensure that the rules of the underlying are being followed.
///
/// An example of a contract that this happens to is Gold Futures (FUT=GC, FOP=OG). OG FOPs
/// underlying Symbols are not determined by the contract month of the FOP itself, but rather
/// by the closest contract to it in an even month.
///
/// Examples:
/// OGH21 would have an underlying of GCJ21
/// OGJ21 would have an underlying of GCJ21
/// OGK21 would have an underlying of GCM21
/// OGM21 would have an underlying of GCM21...
///
public static class FuturesOptionsUnderlyingMapper
{
private static readonly Dictionary> _underlyingFuturesOptionsRules = new Dictionary>
{
// CBOT
{ "ZB", (d, ld) => ContractMonthSerialLookupRule(Symbol.Create("ZB", SecurityType.Future, Market.CBOT), d, ld.Value) },
{ "ZC", (d, ld) => ContractMonthSerialLookupRule(Symbol.Create("ZC", SecurityType.Future, Market.CBOT), d, ld.Value) },
{ "ZN", (d, ld) => ContractMonthSerialLookupRule(Symbol.Create("ZN", SecurityType.Future, Market.CBOT), d, ld.Value) },
{ "ZS", (d, ld) => ContractMonthSerialLookupRule(Symbol.Create("ZS", SecurityType.Future, Market.CBOT), d, ld.Value) },
{ "ZT", (d, ld) => ContractMonthSerialLookupRule(Symbol.Create("ZT", SecurityType.Future, Market.CBOT), d, ld.Value) },
{ "ZW", (d, ld) => ContractMonthSerialLookupRule(Symbol.Create("ZW", SecurityType.Future, Market.CBOT), d, ld.Value) },
// COMEX
{ "HG", (d, _) => ContractMonthYearStartThreeMonthsThenEvenOddMonthsSkipRule(d, true) },
{ "SI", (d, _) => ContractMonthYearStartThreeMonthsThenEvenOddMonthsSkipRule(d, true) },
{ "GC", (d, _) => ContractMonthEvenOddMonth(d, false) }
};
///
/// The difference in months for the Futures expiry month minus the Futures Options expiry month. This assumes
/// that the underlying Future follows a 1-1 mapping between the FOP and future, i.e. this will result in incorrect
/// results, but is needed as an intermediate step to resolve the actual expiry.
///
private static readonly IReadOnlyDictionary _futuresOptionsExpiryDelta = new Dictionary
{
{ "ZB", 1 },
{ "ZC", 1 },
{ "ZN", 1 },
{ "ZS", 1 },
{ "ZT", 1 },
{ "ZW", 1 },
{ "HG", 1 },
{ "GC", 1 },
{ "SI", 1 }
};
///
/// Gets the FOP's underlying Future. The underlying Future's contract month might not match
/// the contract month of the Future Option when providing CBOT or COMEX based FOPs contracts to this method.
///
/// Future option ticker
/// Market of the Future Option
/// Expiration date of the future option
/// Date to search the future chain provider with. Optional, but required for CBOT based contracts
/// Symbol if there is an underlying for the FOP, null if there's no underlying found for the Future Option
public static Symbol GetUnderlyingFutureFromFutureOption(string futureOptionTicker, string market, DateTime futureOptionExpiration, DateTime? date = null)
{
var futureTicker = FuturesOptionsSymbolMappings.MapFromOption(futureOptionTicker);
var canonicalFuture = Symbol.Create(futureTicker, SecurityType.Future, market);
// Get the contract month of the FOP to use when searching for the underlying.
// If the FOP and Future share the same contract month, this is reused as the future's
// contract month so that we can resolve the Future's expiry.
var contractMonth = GetFutureContractMonthNoRulesApplied(canonicalFuture, futureOptionExpiration);
if (_underlyingFuturesOptionsRules.ContainsKey(futureTicker))
{
// The provided ticker follows some sort of rule. Let's figure out the underlying's contract month.
var newFutureContractMonth = _underlyingFuturesOptionsRules[futureTicker](contractMonth, date);
if (newFutureContractMonth == null)
{
// This will only happen when we search the Futures chain for a given contract and no
// closest match could be made, i.e. there are no futures in the chain that come after the FOP's
// contract month.
return null;
}
contractMonth = newFutureContractMonth.Value;
}
var futureExpiry = FuturesExpiryFunctions.FuturesExpiryFunction(canonicalFuture)(contractMonth);
return Symbol.CreateFuture(futureTicker, market, futureExpiry);
}
///
/// Searches the futures chain for the next matching futures contract, and resolves the underlying
/// as the closest future we can find during or after the contract month.
///
/// Canonical future Symbol
/// Future option contract month. Note that this is not the expiry of the Future Option.
/// The date that we'll be using to look at the Future chain
/// The underlying future's contract month, or null if no closest contract was found
private static DateTime? ContractMonthSerialLookupRule(Symbol canonicalFutureSymbol, DateTime futureOptionContractMonth, DateTime lookupDate)
{
var futureChain = FuturesListings.ListedContracts(canonicalFutureSymbol.ID.Symbol, lookupDate);
if (futureChain == null)
{
// No matching contract listing rules entry was found
return null;
}
foreach (var future in futureChain.OrderBy(s => s.ID.Date))
{
// Normalize by date first, normalize to a contract month date, then we want to get the contract
// month of the Future contract so we normalize by getting the delta between the expiration
// and the contract month.
var futureContractMonth = future.ID.Date.Date
.AddDays(-future.ID.Date.Day + 1)
.AddMonths(FuturesExpiryUtilityFunctions.GetDeltaBetweenContractMonthAndContractExpiry(future.ID.Symbol, future.ID.Date));
// We want a contract that is either the same as the contract month or greater
if (futureContractMonth < futureOptionContractMonth)
{
continue;
}
return futureContractMonth;
}
// No matching/closest contract was found in the futures chain.
return null;
}
///
/// Searches for the closest future's contract month depending on whether the Future Option's contract month is
/// on an even or odd month.
///
/// Future option contract month. Note that this is not the expiry of the Future Option.
/// True if the Future Option's underlying future contract month is on odd months, false if on even months
/// The underlying Future's contract month
private static DateTime ContractMonthEvenOddMonth(DateTime futureOptionContractMonth, bool oddMonths)
{
var monthEven = futureOptionContractMonth.Month % 2 == 0;
if (oddMonths && monthEven)
{
return futureOptionContractMonth.AddMonths(1);
}
if (!oddMonths && !monthEven)
{
return futureOptionContractMonth.AddMonths(1);
}
return futureOptionContractMonth;
}
///
/// Sets the contract month to the third month for the first 3 months, then begins using the rule.
///
/// Future option contract month. Note that this is not the expiry of the Future Option.
/// True if the Future Option's underlying future contract month is on odd months, false if on even months. Only used for months greater than 3 months
///
private static DateTime ContractMonthYearStartThreeMonthsThenEvenOddMonthsSkipRule(DateTime futureOptionContractMonth, bool oddMonths)
{
if (futureOptionContractMonth.Month <= 3)
{
return new DateTime(futureOptionContractMonth.Year, 3, 1);
}
return ContractMonthEvenOddMonth(futureOptionContractMonth, oddMonths);
}
///
/// Gets the theoretical (i.e. intermediate/naive) future contract month if we assumed a 1-1 mapping
/// between FOPs contract months and Futures contract months, i.e. they share the same contract month.
///
/// Canonical future Symbol
/// Future Option Expiration Date
/// Contract month assuming that the Future Option and Future share the same contract month
public static DateTime GetFutureContractMonthNoRulesApplied(Symbol canonicalFutureSymbol, DateTime futureOptionExpirationDate)
{
var baseOptionExpiryMonthDate = new DateTime(futureOptionExpirationDate.Year, futureOptionExpirationDate.Month, 1);
if (!_futuresOptionsExpiryDelta.ContainsKey(canonicalFutureSymbol.ID.Symbol))
{
// For contracts like CL, they have no expiry delta between the Futures and FOPs, so we hit this path.
// However, it does have a delta between its expiry and contract month, which we adjust here before
// claiming that `baseOptionExpiryMonthDate` is the future's contract month.
var futuresExpiry = FuturesExpiryFunctions.FuturesExpiryFunction(canonicalFutureSymbol)(baseOptionExpiryMonthDate);
var futuresDelta = FuturesExpiryUtilityFunctions.GetDeltaBetweenContractMonthAndContractExpiry(canonicalFutureSymbol.ID.Symbol, futuresExpiry);
return baseOptionExpiryMonthDate.AddMonths(futuresDelta);
}
return baseOptionExpiryMonthDate.AddMonths(_futuresOptionsExpiryDelta[canonicalFutureSymbol.ID.Symbol]);
}
}
}