chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:02:50 +08:00
commit 0fc60fdcb1
5008 changed files with 910633 additions and 0 deletions
@@ -0,0 +1,45 @@
/*
* 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.Collections.Generic;
using Newtonsoft.Json;
using QuantConnect.Util;
namespace QuantConnect.Securities.FutureOption.Api
{
/// <summary>
/// CME Option Chain Quotes API call root response
/// </summary>
public class CMEOptionChainQuotes
{
/// <summary>
/// The future options contracts with/without settlements
/// </summary>
[JsonProperty("optionContractQuotes")]
public List<CMEOptionChainQuoteEntry> Quotes { get; private set; }
}
/// <summary>
/// Option chain entry quotes, containing strike price
/// </summary>
public class CMEOptionChainQuoteEntry
{
/// <summary>
/// Strike price of the future option quote entry
/// </summary>
[JsonProperty("strikePrice"), JsonConverter(typeof(StringDecimalJsonConverter), true)]
public decimal StrikePrice { get; private set; }
}
}
@@ -0,0 +1,141 @@
/*
* 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 Newtonsoft.Json;
using QuantConnect.Util;
namespace QuantConnect.Securities.FutureOption.Api
{
/// <summary>
/// CME options trades, dates, and expiration list API call root response
/// </summary>
/// <remarks>Returned as a List of this class</remarks>
public class CMEOptionsTradeDatesAndExpiration
{
/// <summary>
/// Describes the type of future option this entry is
/// </summary>
[JsonProperty("label")]
public string Label { get; private set; }
/// <summary>
/// Name of the product
/// </summary>
[JsonProperty("name")]
public string Name { get; private set; }
/// <summary>
/// Option type. "AME" for American, "EUR" for European.
/// Note that there are other types such as weekly, but we
/// only support American options for now.
/// </summary>
[JsonProperty("optionType")]
public string OptionType { get; private set; }
/// <summary>
/// Product ID of the option
/// </summary>
[JsonProperty("productId")]
public int ProductId { get; private set; }
/// <summary>
/// Is Daily option
/// </summary>
[JsonProperty("daily")]
public bool Daily { get; private set; }
/// <summary>
/// ???
/// </summary>
[JsonProperty("sto")]
public bool Sto { get; private set; }
/// <summary>
/// Is weekly option
/// </summary>
[JsonProperty("weekly")]
public bool Weekly { get; private set; }
/// <summary>
/// Expirations of the future option
/// </summary>
[JsonProperty("expirations")]
public List<CMEOptionsExpiration> Expirations { get; private set; }
}
/// <summary>
/// Future options Expiration entries. These are useful because we can derive the
/// future chain from this data, since FOP and FUT share a 1-1 expiry code.
/// </summary>
public class CMEOptionsExpiration
{
/// <summary>
/// Date of expiry
/// </summary>
[JsonProperty("label")]
public string Label { get; private set; }
/// <summary>
/// Product ID of the expiring asset (usually future option)
/// </summary>
[JsonProperty("productId")]
public int ProductId { get; private set; }
/// <summary>
/// Contract ID of the asset
/// </summary>
/// <remarks>Used to search settlements for the option chain</remarks>
[JsonProperty("contractId")]
public string ContractId { get; private set; }
/// <summary>
/// Contract month code formatted as [FUTURE_MONTH_LETTER(1)][YEAR(1)]
/// </summary>
[JsonProperty("expiration")]
public CMEOptionExpirationEntry Expiration { get; private set; }
}
/// <summary>
/// Chicago Mercantile Exchange Option Expiration Entry
/// </summary>
public class CMEOptionExpirationEntry
{
/// <summary>
/// Month of expiry
/// </summary>
[JsonProperty("month")]
public int Month { get; private set; }
/// <summary>
/// Year of expiry
/// </summary>
[JsonProperty("year")]
public int Year { get; private set; }
/// <summary>
/// Expiration code (two letter)
/// </summary>
[JsonProperty("code")]
public string Code { get; private set; }
/// <summary>
/// Expiration code (three letter)
/// </summary>
[JsonProperty("twoDigitsCode")]
public string TwoDigitsCode { get; private set; }
}
}
@@ -0,0 +1,98 @@
/*
* 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.Collections.Generic;
using Newtonsoft.Json;
namespace QuantConnect.Securities.FutureOption.Api
{
/// <summary>
/// Product slate API call root response
/// </summary>
public class CMEProductSlateV2ListResponse
{
/// <summary>
/// Products matching the search criteria
/// </summary>
[JsonProperty("products")]
public List<CMEProductSlateV2ListEntry> Products { get; private set; }
}
/// <summary>
/// Product entry describing the asset matching the search criteria
/// </summary>
public class CMEProductSlateV2ListEntry
{
/// <summary>
/// CME ID for the asset
/// </summary>
[JsonProperty("id")]
public int Id { get; private set; }
/// <summary>
/// Name of the product (e.g. E-mini NASDAQ futures)
/// </summary>
[JsonProperty("name")]
public string Name { get; private set; }
/// <summary>
/// Clearing code
/// </summary>
[JsonProperty("clearing")]
public string Clearing { get; private set; }
/// <summary>
/// GLOBEX ticker
/// </summary>
[JsonProperty("globex")]
public string Globex { get; private set; }
/// <summary>
/// Is traded in the GLOBEX venue
/// </summary>
[JsonProperty("globexTraded")]
public bool GlobexTraded { get; private set; }
/// <summary>
/// Venues this asset trades on
/// </summary>
[JsonProperty("venues")]
public string Venues { get; private set; }
/// <summary>
/// Asset type this product is cleared as (i.e. "Futures", "Options")
/// </summary>
[JsonProperty("cleared")]
public string Cleared { get; private set; }
/// <summary>
/// Exchange the asset trades on (i.e. CME, NYMEX, COMEX, CBOT)
/// </summary>
[JsonProperty("exch")]
public string Exchange { get; private set; }
/// <summary>
/// Asset class group ID - describes group of asset class (e.g. equities, agriculture, etc.)
/// </summary>
[JsonProperty("groupId")]
public int GroupId { get; private set; }
/// <summary>
/// More specific ID describing product
/// </summary>
[JsonProperty("subGroupId")]
public int subGroupId { get; private set; }
}
}
@@ -0,0 +1,46 @@
/*
* 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.Collections.Generic;
namespace QuantConnect.Securities.FutureOption
{
/// <summary>
/// Provides a means to get the scaling factor for CME's quotes API
/// </summary>
public class CMEStrikePriceScalingFactors
{
/// <summary>
/// CME's option chain quotes strike price scaling factor
/// </summary>
private static readonly IReadOnlyDictionary<string, decimal> _scalingFactors = new Dictionary<string, decimal>
{
{ "SI", 0.1m },
{ "NG", 5m }
};
/// <summary>
/// Gets the option chain strike price scaling factor for the quote response from CME
/// </summary>
/// <param name="underlyingFuture">Underlying future Symbol to normalize</param>
/// <returns>Scaling factor for the strike price</returns>
public static decimal GetScaleFactor(Symbol underlyingFuture)
{
return _scalingFactors.ContainsKey(underlyingFuture.ID.Symbol)
? _scalingFactors[underlyingFuture.ID.Symbol]
: 1m;
}
}
}
@@ -0,0 +1,75 @@
/*
* 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 QuantConnect.Orders.Fees;
using QuantConnect.Orders.Fills;
using QuantConnect.Orders.Slippage;
using QuantConnect.Securities.Option;
namespace QuantConnect.Securities.FutureOption
{
/// <summary>
/// Futures Options security
/// </summary>
public class FutureOption : Option.Option
{
/// <summary>
/// Constructor for the future option security
/// </summary>
/// <param name="symbol">Symbol of the future option</param>
/// <param name="exchangeHours">Exchange hours of the future option</param>
/// <param name="quoteCurrency">Quoted currency of the future option</param>
/// <param name="symbolProperties">Symbol properties of the future option</param>
/// <param name="currencyConverter">Currency converter</param>
/// <param name="registeredTypes">Provides all data types registered to the algorithm</param>
/// <param name="securityCache">Cache of security objects</param>
/// <param name="underlying">Future underlying security</param>
public FutureOption(Symbol symbol,
SecurityExchangeHours exchangeHours,
Cash quoteCurrency,
OptionSymbolProperties symbolProperties,
ICurrencyConverter currencyConverter,
IRegisteredSecurityDataTypesProvider registeredTypes,
SecurityCache securityCache,
Security underlying)
: base(symbol,
quoteCurrency,
symbolProperties,
new OptionExchange(exchangeHours),
securityCache,
new OptionPortfolioModel(),
new FutureOptionFillModel(),
new InteractiveBrokersFeeModel(),
NullSlippageModel.Instance,
new ImmediateSettlementModel(),
Securities.VolatilityModel.Null,
null,
new OptionDataFilter(),
new SecurityPriceVariationModel(),
currencyConverter,
registeredTypes,
underlying,
null
)
{
BuyingPowerModel = new FuturesOptionsMarginModel(0, this);
}
/// <summary>
/// Returns the securities symbol
/// </summary>
public static implicit operator Symbol(FutureOption security) => security.Symbol;
}
}
@@ -0,0 +1,25 @@
/*
* 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.
*/
namespace QuantConnect.Securities.FutureOption
{
/// <summary>
/// Future option specific caching support
/// </summary>
/// <seealso cref="SecurityCache"/>
public class FutureOptionCache : Option.OptionCache
{
}
}
@@ -0,0 +1,42 @@
/*
* 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;
namespace QuantConnect.Securities.FutureOption
{
/// <summary>
/// Static helper methods to resolve Futures Options Symbol-related tasks.
/// </summary>
public static class FutureOptionSymbol
{
/// <summary>
/// Detects if the future option contract is standard, i.e. not weekly, not short-term, not mid-sized, etc.
/// </summary>
/// <param name="_">Symbol</param>
/// <returns>true</returns>
/// <remarks>
/// We have no way of identifying the type of FOP contract based on the properties contained within the Symbol.
/// </remarks>
public static bool IsStandard(Symbol _) => true;
/// <summary>
/// Gets the last day of trading, aliased to be the Futures options' expiry
/// </summary>
/// <param name="symbol">Futures Options Symbol</param>
/// <returns>Last day of trading date</returns>
public static DateTime GetLastDayOfTrading(Symbol symbol) => symbol.ID.Date.Date;
}
}
@@ -0,0 +1,272 @@
/*
* 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 QuantConnect.Securities.Future;
namespace QuantConnect.Securities.FutureOption
{
/// <summary>
/// Futures options expiry lookup utility class
/// </summary>
public static class FuturesOptionsExpiryFunctions
{
private static readonly Symbol _lo = Symbol.CreateCanonicalOption(Symbol.Create("CL", SecurityType.Future, Market.NYMEX));
private static readonly Symbol _on = Symbol.CreateCanonicalOption(Symbol.Create("NG", SecurityType.Future, Market.NYMEX));
private static readonly Symbol _ozm = Symbol.CreateCanonicalOption(Symbol.Create("ZM", SecurityType.Future, Market.CBOT));
private static readonly Symbol _ozb = Symbol.CreateCanonicalOption(Symbol.Create("ZB", SecurityType.Future, Market.CBOT));
private static readonly Symbol _ozc = Symbol.CreateCanonicalOption(Symbol.Create("ZC", SecurityType.Future, Market.CBOT));
private static readonly Symbol _ozn = Symbol.CreateCanonicalOption(Symbol.Create("ZN", SecurityType.Future, Market.CBOT));
private static readonly Symbol _otn = Symbol.CreateCanonicalOption(Symbol.Create("TN", SecurityType.Future, Market.CBOT));
private static readonly Symbol _oub = Symbol.CreateCanonicalOption(Symbol.Create("UB", SecurityType.Future, Market.CBOT));
private static readonly Symbol _ozo = Symbol.CreateCanonicalOption(Symbol.Create("ZO", SecurityType.Future, Market.CBOT));
private static readonly Symbol _oke = Symbol.CreateCanonicalOption(Symbol.Create("KE", SecurityType.Future, Market.CBOT));
private static readonly Symbol _ozf = Symbol.CreateCanonicalOption(Symbol.Create("ZF", SecurityType.Future, Market.CBOT));
private static readonly Symbol _ozs = Symbol.CreateCanonicalOption(Symbol.Create("ZS", SecurityType.Future, Market.CBOT));
private static readonly Symbol _ozt = Symbol.CreateCanonicalOption(Symbol.Create("ZT", SecurityType.Future, Market.CBOT));
private static readonly Symbol _ozl = Symbol.CreateCanonicalOption(Symbol.Create("ZL", SecurityType.Future, Market.CBOT));
private static readonly Symbol _ozw = Symbol.CreateCanonicalOption(Symbol.Create("ZW", SecurityType.Future, Market.CBOT));
private static readonly Symbol _oym = Symbol.CreateCanonicalOption(Symbol.Create("YM", SecurityType.Future, Market.CBOT));
private static readonly Symbol _hxe = Symbol.CreateCanonicalOption(Symbol.Create("HG", SecurityType.Future, Market.COMEX));
private static readonly Symbol _og = Symbol.CreateCanonicalOption(Symbol.Create("GC", SecurityType.Future, Market.COMEX));
private static readonly Symbol _so = Symbol.CreateCanonicalOption(Symbol.Create("SI", SecurityType.Future, Market.COMEX));
private static readonly Symbol _aud = Symbol.CreateCanonicalOption(Symbol.Create("6A", SecurityType.Future, Market.CME));
private static readonly Symbol _gbu = Symbol.CreateCanonicalOption(Symbol.Create("6B", SecurityType.Future, Market.CME));
private static readonly Symbol _cau = Symbol.CreateCanonicalOption(Symbol.Create("6C", SecurityType.Future, Market.CME));
private static readonly Symbol _euu = Symbol.CreateCanonicalOption(Symbol.Create("6E", SecurityType.Future, Market.CME));
private static readonly Symbol _jpu = Symbol.CreateCanonicalOption(Symbol.Create("6J", SecurityType.Future, Market.CME));
private static readonly Symbol _chu = Symbol.CreateCanonicalOption(Symbol.Create("6S", SecurityType.Future, Market.CME));
private static readonly Symbol _nzd = Symbol.CreateCanonicalOption(Symbol.Create("6N", SecurityType.Future, Market.CME));
private static readonly Symbol _mxn = Symbol.CreateCanonicalOption(Symbol.Create("6M", SecurityType.Future, Market.CME));
private static readonly Symbol _ead = Symbol.CreateCanonicalOption(Symbol.Create("EAD", SecurityType.Future, Market.CME));
private static readonly Symbol _ajy = Symbol.CreateCanonicalOption(Symbol.Create("AJY", SecurityType.Future, Market.CME));
private static readonly Symbol _ane = Symbol.CreateCanonicalOption(Symbol.Create("ANE", SecurityType.Future, Market.CME));
private static readonly Symbol _ecd = Symbol.CreateCanonicalOption(Symbol.Create("ECD", SecurityType.Future, Market.CME));
private static readonly Symbol _le = Symbol.CreateCanonicalOption(Symbol.Create("LE", SecurityType.Future, Market.CME));
private static readonly Symbol _he = Symbol.CreateCanonicalOption(Symbol.Create("HE", SecurityType.Future, Market.CME));
private static readonly Symbol _lbr = Symbol.CreateCanonicalOption(Symbol.Create("LBR", SecurityType.Future, Market.CME));
private static readonly Symbol _lbs = Symbol.CreateCanonicalOption(Symbol.Create("LBS", SecurityType.Future, Market.CME));
private static readonly Symbol _es = Symbol.CreateCanonicalOption(Symbol.Create("ES", SecurityType.Future, Market.CME));
private static readonly Symbol _emd = Symbol.CreateCanonicalOption(Symbol.Create("EMD", SecurityType.Future, Market.CME));
private static readonly Symbol _nq = Symbol.CreateCanonicalOption(Symbol.Create("NQ", SecurityType.Future, Market.CME));
/// <summary>
/// Futures options expiry functions lookup table, keyed by canonical future option Symbol
/// </summary>
private static readonly IReadOnlyDictionary<Symbol, Func<DateTime, DateTime>> _futuresOptionExpiryFunctions = new Dictionary<Symbol, Func<DateTime,DateTime>>
{
// Trading terminates 7 business days before the 26th calendar of the month prior to the contract month. https://www.cmegroup.com/trading/energy/crude-oil/light-sweet-crude_contractSpecs_options.html#optionProductId=190
{_lo, expiryMonth => {
var twentySixthDayOfPreviousMonthFromContractMonth = expiryMonth.AddMonths(-1).AddDays(-(expiryMonth.Day - 1)).AddDays(25);
var holidays = FuturesExpiryUtilityFunctions.GetExpirationHolidays(_lo.ID.Market, _lo.Underlying.ID.Symbol);
return FuturesExpiryUtilityFunctions.AddBusinessDays(twentySixthDayOfPreviousMonthFromContractMonth, -7, holidays);
}},
// Trading terminates on the 4th last business day of the month prior to the contract month (1 business day prior to the expiration of the underlying futures corresponding contract month).
// https://www.cmegroup.com/trading/energy/natural-gas/natural-gas_contractSpecs_options.html
// Although not stated, this follows the same rules as seen in the COMEX markets, but without Fridays. Case: Dec 2020 expiry, Last Trade Date: 24 Nov 2020
{ _on, expiryMonth => FourthLastBusinessDayInPrecedingMonthFromContractMonth(_on.Underlying, expiryMonth, 0, 0, noFridays: false) },
{ _ozb, expiryMonth => FridayBeforeTwoBusinessDaysBeforeEndOfMonth(_ozb.Underlying, expiryMonth) },
{ _ozc, expiryMonth => FridayBeforeTwoBusinessDaysBeforeEndOfMonth(_ozc.Underlying, expiryMonth) },
{ _ozn, expiryMonth => FridayBeforeTwoBusinessDaysBeforeEndOfMonth(_ozn.Underlying, expiryMonth) },
{ _otn, expiryMonth => FridayBeforeTwoBusinessDaysBeforeEndOfMonth(_otn.Underlying, expiryMonth) },
{ _oub, expiryMonth => FridayBeforeTwoBusinessDaysBeforeEndOfMonth(_oub.Underlying, expiryMonth) },
{ _ozo, expiryMonth => FridayBeforeTwoBusinessDaysBeforeEndOfMonth(_ozo.Underlying, expiryMonth) },
{ _oke, expiryMonth => FridayBeforeTwoBusinessDaysBeforeEndOfMonth(_oke.Underlying, expiryMonth) },
{ _ozf, expiryMonth => FridayBeforeTwoBusinessDaysBeforeEndOfMonth(_ozf.Underlying, expiryMonth) },
{ _ozs, expiryMonth => FridayBeforeTwoBusinessDaysBeforeEndOfMonth(_ozs.Underlying, expiryMonth) },
{ _ozt, expiryMonth => FridayBeforeTwoBusinessDaysBeforeEndOfMonth(_ozt.Underlying, expiryMonth) },
{ _ozw, expiryMonth => FridayBeforeTwoBusinessDaysBeforeEndOfMonth(_ozw.Underlying, expiryMonth) },
{ _ozl, expiryMonth => FridayBeforeTwoBusinessDaysBeforeEndOfMonth(_ozl.Underlying, expiryMonth) },
{ _ozm, expiryMonth => FridayBeforeTwoBusinessDaysBeforeEndOfMonth(_ozm.Underlying, expiryMonth) },
{ _hxe, expiryMonth => FourthLastBusinessDayInPrecedingMonthFromContractMonth(_hxe.Underlying, expiryMonth, 12, 0) },
{ _og, expiryMonth => FourthLastBusinessDayInPrecedingMonthFromContractMonth(_og.Underlying, expiryMonth, 12, 30) },
{ _so, expiryMonth => FourthLastBusinessDayInPrecedingMonthFromContractMonth(_so.Underlying, expiryMonth, 12, 25) },
{ _aud, expiryMonth => SecondFridayBeforeThirdWednesdayOfContractMonth(_aud.Underlying, expiryMonth) },
{ _gbu, expiryMonth => SecondFridayBeforeThirdWednesdayOfContractMonth(_gbu.Underlying, expiryMonth) },
{ _cau, expiryMonth => SecondFridayBeforeThirdWednesdayOfContractMonth(_cau.Underlying, expiryMonth) },
{ _euu, expiryMonth => SecondFridayBeforeThirdWednesdayOfContractMonth(_euu.Underlying, expiryMonth) },
{ _jpu, expiryMonth => SecondFridayBeforeThirdWednesdayOfContractMonth(_jpu.Underlying, expiryMonth) },
{ _chu, expiryMonth => SecondFridayBeforeThirdWednesdayOfContractMonth(_chu.Underlying, expiryMonth) },
{ _nzd, expiryMonth => SecondFridayBeforeThirdWednesdayOfContractMonth(_nzd.Underlying, expiryMonth) },
{ _mxn, expiryMonth => SecondFridayBeforeThirdWednesdayOfContractMonth(_mxn.Underlying, expiryMonth) },
{ _ead, expiryMonth => SecondFridayBeforeThirdWednesdayOfContractMonth(_ead.Underlying, expiryMonth) },
{ _ajy, expiryMonth => SecondFridayBeforeThirdWednesdayOfContractMonth(_ajy.Underlying, expiryMonth) },
{ _ane, expiryMonth => SecondFridayBeforeThirdWednesdayOfContractMonth(_ane.Underlying, expiryMonth) },
{ _ecd, expiryMonth => SecondFridayBeforeThirdWednesdayOfContractMonth(_ecd.Underlying, expiryMonth) },
{ _le, expiryMonth => FirstFridayOfContractMonth(_le.Underlying, expiryMonth) },
{ _he, expiryMonth => TenthBusinessDayOfContractMonth(_he.Underlying, expiryMonth) },
{ _lbr, expiryMonth => LastBusinessDayInPrecedingMonthFromContractMonth(_lbr.Underlying, expiryMonth) },
{ _lbs, expiryMonth => LastBusinessDayInPrecedingMonthFromContractMonth(_lbs.Underlying, expiryMonth) },
// even though these FOPs are currently quarterly (as underlying), they had until some serial months. Expiration is the same rule for all
{ _es, expiryMonth => FuturesExpiryUtilityFunctions.ThirdFriday(expiryMonth, _es) },
{ _emd, expiryMonth => FuturesExpiryUtilityFunctions.ThirdFriday(expiryMonth, _emd) },
{ _oym, expiryMonth => FuturesExpiryUtilityFunctions.ThirdFriday(expiryMonth, _oym) },
{ _nq, expiryMonth => FuturesExpiryUtilityFunctions.ThirdFriday(expiryMonth, _nq) },
};
/// <summary>
/// Gets the Futures Options' expiry for the given contract month.
/// </summary>
/// <param name="canonicalFutureOptionSymbol">Canonical Futures Options Symbol. Will be made canonical if not provided a canonical</param>
/// <param name="futureContractMonth">Contract month of the underlying Future</param>
/// <returns>Expiry date/time</returns>
public static DateTime FuturesOptionExpiry(Symbol canonicalFutureOptionSymbol, DateTime futureContractMonth)
{
if (!canonicalFutureOptionSymbol.IsCanonical() || !canonicalFutureOptionSymbol.Underlying.IsCanonical())
{
canonicalFutureOptionSymbol = Symbol.CreateCanonicalOption(
Symbol.Create(canonicalFutureOptionSymbol.Underlying.ID.Symbol,
SecurityType.Future,
canonicalFutureOptionSymbol.Underlying.ID.Market));
}
if (!_futuresOptionExpiryFunctions.TryGetValue(canonicalFutureOptionSymbol, out var expiryFunction))
{
// No definition exists for this FOP. Let's default to futures expiry.
return FuturesExpiryFunctions.FuturesExpiryFunction(canonicalFutureOptionSymbol.Underlying)(futureContractMonth);
}
return expiryFunction(futureContractMonth);
}
/// <summary>
/// Gets the Future Option's expiry from the Future Symbol provided
/// </summary>
/// <param name="futureSymbol">Future (non-canonical) Symbol</param>
/// <param name="canonicalFutureOption">The canonical Future Option Symbol</param>
/// <returns>Future Option Expiry for the Future with the same contract month</returns>
public static DateTime GetFutureOptionExpiryFromFutureExpiry(Symbol futureSymbol, Symbol canonicalFutureOption = null)
{
var futureContractMonth = FuturesExpiryUtilityFunctions.GetFutureContractMonth(futureSymbol);
if (canonicalFutureOption == null)
{
canonicalFutureOption = Symbol.CreateCanonicalOption(
Symbol.Create(futureSymbol.ID.Symbol, SecurityType.Future, futureSymbol.ID.Market));
}
return FuturesOptionExpiry(canonicalFutureOption, futureContractMonth);
}
/// <summary>
/// Expiry function for CBOT Futures Options entries.
/// Returns the Friday before the 2nd last business day of the month preceding the future contract expiry month.
/// </summary>
/// <param name="underlyingFuture">Underlying future symbol</param>
/// <param name="expiryMonth">Expiry month date</param>
/// <returns>Expiry DateTime of the Future Option</returns>
private static DateTime FridayBeforeTwoBusinessDaysBeforeEndOfMonth(Symbol underlyingFuture, DateTime expiryMonth)
{
var holidays = FuturesExpiryUtilityFunctions.GetExpirationHolidays(underlyingFuture.ID.Market, underlyingFuture.ID.Symbol);
var expiryMonthPreceding = expiryMonth.AddMonths(-1).AddDays(-(expiryMonth.Day - 1));
var fridayBeforeSecondLastBusinessDay = FuturesExpiryUtilityFunctions.NthLastBusinessDay(
expiryMonthPreceding,
2,
holidays).AddDays(-1);
while (fridayBeforeSecondLastBusinessDay.DayOfWeek != DayOfWeek.Friday)
{
fridayBeforeSecondLastBusinessDay = FuturesExpiryUtilityFunctions.AddBusinessDays(fridayBeforeSecondLastBusinessDay, -1, holidays);
}
return fridayBeforeSecondLastBusinessDay;
}
/// <summary>
/// For Trading that terminates on the 4th last business day of the month prior to the contract month.
/// If the 4th last business day occurs on a Friday or the day before a holiday, trading terminates on the
/// prior business day. This applies to some NYMEX (with fridays), all COMEX.
/// </summary>
/// <param name="underlyingFuture">Underlying Future Symbol</param>
/// <param name="expiryMonth">Contract expiry month</param>
/// <param name="hour">Hour the contract expires at</param>
/// <param name="minutes">Minute the contract expires at</param>
/// <param name="noFridays">Exclude Friday expiration dates from consideration</param>
/// <returns>Expiry DateTime of the Future Option</returns>
private static DateTime FourthLastBusinessDayInPrecedingMonthFromContractMonth(Symbol underlyingFuture, DateTime expiryMonth, int hour, int minutes, bool noFridays = true)
{
var holidays = FuturesExpiryUtilityFunctions.GetExpirationHolidays(underlyingFuture.ID.Market, underlyingFuture.ID.Symbol);
var expiryMonthPreceding = expiryMonth.AddMonths(-1);
var fourthLastBusinessDay = FuturesExpiryUtilityFunctions.NthLastBusinessDay(expiryMonthPreceding, 4, holidays);
if (noFridays)
{
while (fourthLastBusinessDay.DayOfWeek == DayOfWeek.Friday || holidays.Contains(fourthLastBusinessDay.AddDays(1)))
{
fourthLastBusinessDay = FuturesExpiryUtilityFunctions.AddBusinessDays(fourthLastBusinessDay, -1, holidays);
}
}
return fourthLastBusinessDay.AddHours(hour).AddMinutes(minutes);
}
/// <summary>
/// Expiry function for AUD Future Options expiry.
/// Returns the second Friday before the 3rd Wednesday of contract expiry month, 9am.
/// </summary>
/// <param name="underlyingFuture">Underlying future symbol</param>
/// <param name="expiryMonth">Expiry month date</param>
/// <returns>Expiry DateTime of the Future Option</returns>
private static DateTime SecondFridayBeforeThirdWednesdayOfContractMonth(Symbol underlyingFuture, DateTime expiryMonth)
{
var holidays = FuturesExpiryUtilityFunctions.GetExpirationHolidays(underlyingFuture.ID.Market, underlyingFuture.ID.Symbol);
var thirdWednesday = FuturesExpiryUtilityFunctions.ThirdWednesday(expiryMonth);
var secondFridayBeforeThirdWednesday = thirdWednesday.AddDays(-12);
if (holidays.Contains(secondFridayBeforeThirdWednesday))
{
secondFridayBeforeThirdWednesday = FuturesExpiryUtilityFunctions.AddBusinessDays(secondFridayBeforeThirdWednesday, -1, holidays);
}
return secondFridayBeforeThirdWednesday.AddHours(9);
}
/// <summary>
/// First friday of the contract month
/// </summary>
public static DateTime FirstFridayOfContractMonth(Symbol underlyingFuture, DateTime expiryMonth)
{
var holidays = FuturesExpiryUtilityFunctions.GetExpirationHolidays(underlyingFuture.ID.Market, underlyingFuture.ID.Symbol);
var firstFriday = FuturesExpiryUtilityFunctions.NthFriday(expiryMonth, 1);
if (holidays.Contains(firstFriday))
{
firstFriday = FuturesExpiryUtilityFunctions.AddBusinessDays(firstFriday, -1, holidays);
}
return firstFriday.AddHours(13);
}
/// <summary>
/// Tenth business day of the month
/// </summary>
public static DateTime TenthBusinessDayOfContractMonth(Symbol underlyingFuture, DateTime expiryMonth)
{
var holidays = FuturesExpiryUtilityFunctions.GetExpirationHolidays(underlyingFuture.ID.Market, underlyingFuture.ID.Symbol);
return FuturesExpiryUtilityFunctions.NthBusinessDay(expiryMonth, 10, holidays);
}
/// <summary>
/// Last business day of the month preceding the contract month
/// </summary>
private static DateTime LastBusinessDayInPrecedingMonthFromContractMonth(Symbol underlying, DateTime expiryMonth)
{
var holidays = FuturesExpiryUtilityFunctions.GetExpirationHolidays(underlying.ID.Market, underlying.ID.Symbol);
return FuturesExpiryUtilityFunctions.NthLastBusinessDay(expiryMonth.AddMonths(-1), 1, holidays);
}
}
}
@@ -0,0 +1,165 @@
/*
* 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 QuantConnect.Securities.Future;
namespace QuantConnect.Securities.Option
{
/// <summary>
/// Defines a margin model for future options (an option with a future as its underlying).
/// We re-use the <see cref="FutureMarginModel"/> implementation and multiply its results
/// by 1.5x to simulate the increased margins seen for future options.
/// </summary>
public class FuturesOptionsMarginModel : FutureMarginModel
{
private readonly Option _futureOption;
/// <summary>
/// Initial Overnight margin requirement for the contract effective from the date of change
/// </summary>
public override decimal InitialOvernightMarginRequirement => GetMarginRequirement(_futureOption, base.InitialOvernightMarginRequirement);
/// <summary>
/// Maintenance Overnight margin requirement for the contract effective from the date of change
/// </summary>
public override decimal MaintenanceOvernightMarginRequirement => GetMarginRequirement(_futureOption, base.MaintenanceOvernightMarginRequirement);
/// <summary>
/// Initial Intraday margin for the contract effective from the date of change
/// </summary>
public override decimal InitialIntradayMarginRequirement => GetMarginRequirement(_futureOption, base.InitialIntradayMarginRequirement);
/// <summary>
/// Maintenance Intraday margin requirement for the contract effective from the date of change
/// </summary>
public override decimal MaintenanceIntradayMarginRequirement => GetMarginRequirement(_futureOption, base.MaintenanceIntradayMarginRequirement);
/// <summary>
/// Creates an instance of FutureOptionMarginModel
/// </summary>
/// <param name="requiredFreeBuyingPowerPercent">The percentage used to determine the required unused buying power for the account.</param>
/// <param name="futureOption">Option Security containing a Future security as the underlying</param>
public FuturesOptionsMarginModel(decimal requiredFreeBuyingPowerPercent = 0, Option futureOption = null) : base(requiredFreeBuyingPowerPercent, futureOption?.Underlying)
{
_futureOption = futureOption;
}
/// <summary>
/// Gets the margin currently alloted to the specified holding.
/// </summary>
/// <param name="parameters">An object containing the security</param>
/// <returns>The maintenance margin required for the option</returns>
/// <remarks>
/// We fix the option to 1.5x the maintenance because of its close coupling with the underlying.
/// The option's contract multiplier is 1x, but might be more sensitive to volatility shocks in the long
/// run when it comes to calculating the different market scenarios attempting to simulate VaR, resulting
/// in a margin greater than the underlying's margin.
/// </remarks>
public override MaintenanceMargin GetMaintenanceMargin(MaintenanceMarginParameters parameters)
{
var underlyingRequirement = base.GetMaintenanceMargin(parameters.ForUnderlying(parameters.Quantity));
var positionSide = parameters.Quantity > 0 ? PositionSide.Long : PositionSide.Short;
return GetMarginRequirement(_futureOption, underlyingRequirement, positionSide);
}
/// <summary>
/// The margin that must be held in order to increase the position by the provided quantity
/// </summary>
/// <param name="parameters">An object containing the security and quantity of shares</param>
/// <returns>The initial margin required for the option (i.e. the equity required to enter a position for this option)</returns>
/// <remarks>
/// We fix the option to 1.5x the initial because of its close coupling with the underlying.
/// The option's contract multiplier is 1x, but might be more sensitive to volatility shocks in the long
/// run when it comes to calculating the different market scenarios attempting to simulate VaR, resulting
/// in a margin greater than the underlying's margin.
/// </remarks>
public override InitialMargin GetInitialMarginRequirement(InitialMarginParameters parameters)
{
var underlyingRequirement = base.GetInitialMarginRequirement(parameters.ForUnderlying()).Value;
var positionSide = parameters.Quantity > 0 ? PositionSide.Long : PositionSide.Short;
return new InitialMargin(GetMarginRequirement(_futureOption, underlyingRequirement, positionSide));
}
/// <summary>
/// Get's the margin requirement for a future option based on the underlying future margin requirement and the position side to trade.
/// FOPs margin requirement is an 'S' curve based on the underlying requirement around it's current price, see https://en.wikipedia.org/wiki/Logistic_function
/// </summary>
/// <param name="option">The future option contract to trade</param>
/// <param name="underlyingRequirement">The underlying future associated margin requirement</param>
/// <param name="positionSide">The position side to trade, long by default. This is because short positions require higher margin requirements</param>
public static int GetMarginRequirement(Option option, decimal underlyingRequirement, PositionSide positionSide = PositionSide.Long)
{
var maximumValue = underlyingRequirement;
var curveGrowthRate = -7.8m;
var underlyingPrice = option.Underlying.Price;
// If the underlying price is 0, we can't calculate a margin requirement, so return the underlying requirement.
// This could be removed after GH issue #6523 is resolved.
if (option.Underlying == null || option.Underlying.Price == 0m)
{
return 0;
}
if (positionSide == PositionSide.Short)
{
if (option.Right == OptionRight.Call)
{
// going short the curve growth rate is slower
curveGrowthRate = -4m;
// curve shifted to the right -> causes a margin requirement increase
underlyingPrice *= 1.5m;
}
else
{
// higher max requirements
maximumValue *= 1.25m;
// puts are inverter from calls
curveGrowthRate = 2.4m;
// curve shifted to the left -> causes a margin requirement increase
underlyingPrice *= 0.30m;
}
}
else
{
if (option.Right == OptionRight.Put)
{
// fastest change rate
curveGrowthRate = 9m;
}
else
{
maximumValue *= 1.20m;
}
}
// we normalize the curve growth rate by dividing by the underlyings price
// this way, contracts with different order of magnitude price and strike (like CL & ES) share this logic
var denominator = Math.Pow(Math.E, (double) (-curveGrowthRate * (option.ScaledStrikePrice - underlyingPrice) / underlyingPrice));
if (double.IsInfinity(denominator))
{
return 0;
}
if (denominator.IsNaNOrZero())
{
return (int) maximumValue;
}
return (int) (maximumValue / (1 + denominator).SafeDecimalCast());
}
}
}
@@ -0,0 +1,107 @@
/*
* 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.Collections.Generic;
using System.Linq;
namespace QuantConnect.Securities.Future
{
/// <summary>
/// Provides conversions from a GLOBEX Futures ticker to a GLOBEX Futures Options ticker
/// </summary>
public static class FuturesOptionsSymbolMappings
{
/// <summary>
/// Defines Futures GLOBEX Ticker -> Futures Options GLOBEX Ticker
/// </summary>
private static Dictionary<string, string> _futureToFutureOptionsGLOBEX = new Dictionary<string, string>
{
{ "EH", "OEH" },
{ "KE", "OKE" },
{ "TN", "OTN" },
{ "UB", "OUB" },
{ "YM", "OYM" },
{ "ZB", "OZB" },
{ "ZC", "OZC" },
{ "ZF", "OZF" },
{ "ZL", "OZL" },
{ "ZM", "OZM" },
{ "ZN", "OZN" },
{ "ZO", "OZO" },
{ "ZS", "OZS" },
{ "ZT", "OZT" },
{ "ZW", "OZW" },
{ "RTY", "RTO" },
{ "GC", "OG" },
{ "HG", "HXE" },
{ "SI", "SO" },
{ "CL", "LO" },
{ "HCL", "HCO" },
{ "HO", "OH" },
{ "NG", "ON" },
{ "PA", "PAO" },
{ "PL", "PO" },
{ "RB", "OB" },
{ "YG", "OYG" },
{ "ZG", "OZG" },
{ "ZI", "OZI" },
{ "6A", "ADU" },
{ "6B", "GBU" },
{ "6C", "CAU" },
{ "6E", "EUU" },
{ "6J", "JPU" },
{ "6S", "CHU" }
};
private static Dictionary<string, string> _futureOptionsToFutureGLOBEX = _futureToFutureOptionsGLOBEX
.ToDictionary(kvp => kvp.Value, kvp => kvp.Key);
/// <summary>
/// Returns the futures options ticker for the given futures ticker.
/// </summary>
/// <param name="futureTicker">Future GLOBEX ticker to get Future Option GLOBEX ticker for</param>
/// <returns>Future option ticker. Defaults to future ticker provided if no entry is found</returns>
public static string Map(string futureTicker)
{
futureTicker = futureTicker.ToUpperInvariant();
string result;
if (!_futureToFutureOptionsGLOBEX.TryGetValue(futureTicker, out result))
{
return futureTicker;
}
return result;
}
/// <summary>
/// Maps a futures options ticker to its underlying future's ticker
/// </summary>
/// <param name="futureOptionTicker">Future option ticker to map to the underlying</param>
/// <returns>Future ticker</returns>
public static string MapFromOption(string futureOptionTicker)
{
futureOptionTicker = futureOptionTicker.ToUpperInvariant();
string result;
if (!_futureOptionsToFutureGLOBEX.TryGetValue(futureOptionTicker, out result))
{
return futureOptionTicker;
}
return result;
}
}
}
@@ -0,0 +1,237 @@
/*
* 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]);
}
}
}