238 lines
14 KiB
C#
238 lines
14 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 QuantConnect.Securities.Future;
|
|
|
|
namespace QuantConnect.Securities.FutureOption
|
|
{
|
|
/// <summary>
|
|
/// Creates the underlying Symbol that corresponds to a futures options contract
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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 <see cref="GetUnderlyingFutureFromFutureOption"/> 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...
|
|
/// </remarks>
|
|
public static class FuturesOptionsUnderlyingMapper
|
|
{
|
|
private static readonly Dictionary<string, Func<DateTime, DateTime?, DateTime?>> _underlyingFuturesOptionsRules = new Dictionary<string, Func<DateTime, DateTime?, DateTime?>>
|
|
{
|
|
// 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) },
|
|
{ "ZM", (d, ld) => ContractMonthSerialLookupRule(Symbol.Create("ZM", 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) },
|
|
{ "ZL", (d, ld) => ContractMonthSerialLookupRule(Symbol.Create("ZL", SecurityType.Future, Market.CBOT), d, ld.Value) },
|
|
{ "TN", (d, ld) => ContractMonthSerialLookupRule(Symbol.Create("TN", SecurityType.Future, Market.CBOT), d, ld.Value) },
|
|
|
|
// COMEX
|
|
{ "HG", (d, _) => ContractMonthYearStartThreeMonthsThenEvenOddMonthsSkipRule(d, true) },
|
|
{ "SI", (d, _) => ContractMonthYearStartThreeMonthsThenEvenOddMonthsSkipRule(d, true) },
|
|
{ "GC", (d, _) => ContractMonthEvenOddMonth(d, false) },
|
|
|
|
// CME
|
|
{ "6A", (d, ld) => ContractMonthSerialLookupRule(Symbol.Create("6A", SecurityType.Future, Market.CME), d, ld.Value) },
|
|
{ "6B", (d, ld) => ContractMonthSerialLookupRule(Symbol.Create("6B", SecurityType.Future, Market.CME), d, ld.Value) },
|
|
{ "6M", (d, ld) => ContractMonthSerialLookupRule(Symbol.Create("6M", SecurityType.Future, Market.CME), d, ld.Value) },
|
|
{ "6J", (d, ld) => ContractMonthSerialLookupRule(Symbol.Create("6J", SecurityType.Future, Market.CME), d, ld.Value) },
|
|
{ "6E", (d, ld) => ContractMonthSerialLookupRule(Symbol.Create("6E", SecurityType.Future, Market.CME), d, ld.Value) },
|
|
{ "6C", (d, ld) => ContractMonthSerialLookupRule(Symbol.Create("6C", SecurityType.Future, Market.CME), d, ld.Value) },
|
|
};
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
private static readonly IReadOnlyDictionary<string, int> _futuresOptionsExpiryDelta = new Dictionary<string, int>
|
|
{
|
|
{ "ZB", 1 },
|
|
{ "ZC", 1 },
|
|
{ "ZM", 1 },
|
|
{ "ZN", 1 },
|
|
{ "TN", 1 },
|
|
{ "ZS", 1 },
|
|
{ "ZT", 1 },
|
|
{ "ZW", 1 },
|
|
{ "ZL", 1 },
|
|
{ "HG", 1 },
|
|
{ "GC", 1 },
|
|
{ "SI", 1 },
|
|
{ "UB", 1 },
|
|
{ "ZO", 1 },
|
|
{ "KE", 1 },
|
|
{ "ZF", 1 },
|
|
{ "LBR", 1 },
|
|
{ "LBS", 1 }
|
|
};
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="futureOptionTicker">Future option ticker</param>
|
|
/// <param name="market">Market of the Future Option</param>
|
|
/// <param name="futureOptionExpiration">Expiration date of the future option</param>
|
|
/// <param name="date">Date to search the future chain provider with. Optional, but required for CBOT based contracts</param>
|
|
/// <returns>Symbol if there is an underlying for the FOP, null if there's no underlying found for the Future Option</returns>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="canonicalFutureSymbol">Canonical future Symbol</param>
|
|
/// <param name="futureOptionContractMonth">Future option contract month. Note that this is not the expiry of the Future Option.</param>
|
|
/// <param name="lookupDate">The date that we'll be using to look at the Future chain</param>
|
|
/// <returns>The underlying future's contract month, or null if no closest contract was found</returns>
|
|
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 = FuturesExpiryUtilityFunctions.GetFutureContractMonth(future);
|
|
|
|
// 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Searches for the closest future's contract month depending on whether the Future Option's contract month is
|
|
/// on an even or odd month.
|
|
/// </summary>
|
|
/// <param name="futureOptionContractMonth">Future option contract month. Note that this is not the expiry of the Future Option.</param>
|
|
/// <param name="oddMonths">True if the Future Option's underlying future contract month is on odd months, false if on even months</param>
|
|
/// <returns>The underlying Future's contract month</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the contract month to the third month for the first 3 months, then begins using the <see cref="ContractMonthEvenOddMonth"/> rule.
|
|
/// </summary>
|
|
/// <param name="futureOptionContractMonth">Future option contract month. Note that this is not the expiry of the Future Option.</param>
|
|
/// <param name="oddMonths">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</param>
|
|
/// <returns></returns>
|
|
private static DateTime ContractMonthYearStartThreeMonthsThenEvenOddMonthsSkipRule(DateTime futureOptionContractMonth, bool oddMonths)
|
|
{
|
|
if (futureOptionContractMonth.Month <= 3)
|
|
{
|
|
return new DateTime(futureOptionContractMonth.Year, 3, 1);
|
|
}
|
|
|
|
return ContractMonthEvenOddMonth(futureOptionContractMonth, oddMonths);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="canonicalFutureSymbol">Canonical future Symbol</param>
|
|
/// <param name="futureOptionExpirationDate">Future Option Expiration Date</param>
|
|
/// <returns>Contract month assuming that the Future Option and Future share the same contract month</returns>
|
|
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]);
|
|
}
|
|
}
|
|
}
|