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,49 @@
/*
* 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.Data;
using QuantConnect.Data.Market;
namespace QuantConnect.Securities.Option
{
/// <summary>
/// Class implements default flat dividend yield curve estimator, implementing <see cref="IQLDividendYieldEstimator"/>.
/// </summary>
public class ConstantQLDividendYieldEstimator : IQLDividendYieldEstimator
{
private readonly double _dividendYield;
/// <summary>
/// Constructor initializes class with constant dividend yield.
/// </summary>
/// <param name="dividendYield"></param>
public ConstantQLDividendYieldEstimator(double dividendYield = 0.00)
{
_dividendYield = dividendYield;
}
/// <summary>
/// Returns current flat estimate of the dividend yield
/// </summary>
/// <param name="security">The option security object</param>
/// <param name="slice">The current data slice. This can be used to access other information
/// available to the algorithm</param>
/// <param name="contract">The option contract to evaluate</param>
/// <returns>The estimate</returns>
public double Estimate(Security security, Slice slice, OptionContract contract)
{
return _dividendYield;
}
}
}
@@ -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 QuantConnect.Data;
using QuantConnect.Data.Market;
namespace QuantConnect.Securities.Option
{
/// <summary>
/// Class implements default flat risk free curve, implementing <see cref="IQLRiskFreeRateEstimator"/>.
/// </summary>
public class ConstantQLRiskFreeRateEstimator : IQLRiskFreeRateEstimator
{
private readonly decimal _riskFreeRate;
/// <summary>
/// Constructor initializes class with risk free rate constant
/// </summary>
/// <param name="riskFreeRate"></param>
public ConstantQLRiskFreeRateEstimator(decimal riskFreeRate = 0.01m)
{
_riskFreeRate = riskFreeRate;
}
/// <summary>
/// Returns current flat estimate of the risk free rate
/// </summary>
/// <param name="security">The option security object</param>
/// <param name="slice">The current data slice. This can be used to access other information
/// available to the algorithm</param>
/// <param name="contract">The option contract to evaluate</param>
/// <returns>The estimate</returns>
public decimal Estimate(Security security, Slice slice, OptionContract contract) => _riskFreeRate;
}
}
@@ -0,0 +1,56 @@
/*
* 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.Data;
using QuantConnect.Data.Market;
namespace QuantConnect.Securities.Option
{
/// <summary>
/// Class implements default underlying constant volatility estimator (<see cref="IQLUnderlyingVolatilityEstimator"/>.), that projects the underlying own volatility
/// model into corresponding option pricing model.
/// </summary>
public class ConstantQLUnderlyingVolatilityEstimator : IQLUnderlyingVolatilityEstimator
{
/// <summary>
/// Indicates whether volatility model has been warmed ot not
/// </summary>
public bool IsReady { get; private set; }
/// <summary>
/// Returns current estimate of the underlying volatility
/// </summary>
/// <param name="security">The option security object</param>
/// <param name="slice">The current data slice. This can be used to access other information
/// available to the algorithm</param>
/// <param name="contract">The option contract to evaluate</param>
/// <returns>The estimate</returns>
public double Estimate(Security security, Slice slice, OptionContract contract)
{
var option = security as Option;
if (option != null &&
option.Underlying != null &&
option.Underlying.VolatilityModel != null &&
option.Underlying.VolatilityModel.Volatility > 0m)
{
IsReady = true;
return (double)option.Underlying.VolatilityModel.Volatility;
}
return 0.0;
}
}
}
@@ -0,0 +1,55 @@
/*
* 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.Data;
using QuantConnect.Data.Market;
namespace QuantConnect.Securities.Option
{
/// <summary>
/// Provides a default implementation of <see cref="IOptionPriceModel"/> that does not compute any
/// greeks and uses the current price for the theoretical price.
/// <remarks>This is a stub implementation until the real models are implemented</remarks>
/// </summary>
public class CurrentPriceOptionPriceModel : OptionPriceModel
{
/// <summary>
/// Creates a new <see cref="OptionPriceModelResult"/> containing the current <see cref="Security.Price"/>
/// and a default, empty instance of first Order <see cref="Greeks"/>
/// </summary>
/// <param name="security">The option security object</param>
/// <param name="slice">The current data slice. This can be used to access other information
/// available to the algorithm</param>
/// <param name="contract">The option contract to evaluate</param>
/// <returns>An instance of <see cref="OptionPriceModelResult"/> containing the theoretical
/// price of the specified option contract</returns>
public override OptionPriceModelResult Evaluate(Security security, Slice slice, OptionContract contract)
{
return new OptionPriceModelResult(security.Price, NullGreeks.Instance);
}
/// <summary>
/// Evaluates the specified option contract to compute a theoretical price, IV and greeks
/// </summary>
/// <param name="parameters">A <see cref="OptionPriceModelParameters"/> object
/// containing the security, slice and contract</param>
/// <returns>An instance of <see cref="OptionPriceModelResult"/> containing the theoretical
/// price of the specified option contract</returns>
public override OptionPriceModelResult Evaluate(OptionPriceModelParameters parameters)
{
return Evaluate(parameters.Security, parameters.Slice, parameters.Contract);
}
}
}
@@ -0,0 +1,148 @@
/*
* 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.Orders;
using QuantConnect.Orders.Fees;
namespace QuantConnect.Securities.Option
{
/// <summary>
/// The option assignment model emulates exercising of short option positions in the portfolio.
/// Simulator implements basic no-arb argument: when time value of the option contract is close to zero
/// it assigns short legs getting profit close to expiration dates in deep ITM positions. User algorithm then receives
/// assignment event from LEAN. Simulator randomly scans for arbitrage opportunities every two hours or so.
/// </summary>
public class DefaultOptionAssignmentModel : IOptionAssignmentModel
{
// when we start simulating assignments prior to expiration
private readonly TimeSpan _priorExpiration;
// we focus only on deep ITM calls and puts
private readonly decimal _requiredInTheMoneyPercent;
/// <summary>
/// Creates a new instance
/// </summary>
/// <param name="requiredInTheMoneyPercent">The percent in the money the option has to be to trigger the option assignment</param>
/// <param name="priorExpiration">For <see cref="OptionStyle.American"/>, the time span prior to expiration were we will try to evaluate option assignment</param>
public DefaultOptionAssignmentModel(decimal requiredInTheMoneyPercent = 0.05m, TimeSpan? priorExpiration = null)
{
_priorExpiration = priorExpiration ?? new TimeSpan(4, 0, 0, 0);
_requiredInTheMoneyPercent = requiredInTheMoneyPercent;
}
/// <summary>
/// Get's the option assignments to generate if any
/// </summary>
/// <param name="parameters">The option assignment parameters data transfer class</param>
/// <returns>The option assignment result</returns>
public virtual OptionAssignmentResult GetAssignment(OptionAssignmentParameters parameters)
{
var option = parameters.Option;
var underlying = parameters.Option.Underlying;
// we take only options that expire soon
if ((option.Symbol.ID.OptionStyle == OptionStyle.American && option.Symbol.ID.Date - option.LocalTime <= _priorExpiration ||
option.Symbol.ID.OptionStyle == OptionStyle.European && option.Symbol.ID.Date.Date == option.LocalTime.Date)
// we take only deep ITM strikes
&& IsDeepInTheMoney(option))
{
// we estimate P/L
var potentialPnL = EstimateArbitragePnL(option, (OptionHolding)option.Holdings, underlying);
if (potentialPnL > 0)
{
return new OptionAssignmentResult(option.Holdings.AbsoluteQuantity, "Simulated option assignment before expiration");
}
}
return OptionAssignmentResult.Null;
}
private bool IsDeepInTheMoney(Option option)
{
var symbol = option.Symbol;
var underlyingPrice = option.Underlying.Close;
// For some options, the price is based on a fraction of the underlying, such as for NQX.
// Therefore, for those options we need to scale the price when comparing it with the
// underlying. For that reason we use option.ScaledStrikePrice instead of
// option.StrikePrice
var result =
symbol.ID.OptionRight == OptionRight.Call
? (underlyingPrice - option.ScaledStrikePrice) / underlyingPrice > _requiredInTheMoneyPercent
: (option.ScaledStrikePrice - underlyingPrice) / underlyingPrice > _requiredInTheMoneyPercent;
return result;
}
private static decimal EstimateArbitragePnL(Option option, OptionHolding holding, Security underlying)
{
// no-arb argument:
// if our long deep ITM position has a large B/A spread and almost no time value, it may be interesting for us
// to exercise the option and close the resulting position in underlying instrument, if we want to exit now.
// User's short option position is our long one.
// In order to sell ITM position we take option bid price as an input
var optionPrice = option.BidPrice;
// we are interested in underlying bid price if we exercise calls and want to sell the underlying immediately.
// we are interested in underlying ask price if we exercise puts
var underlyingPrice = option.Symbol.ID.OptionRight == OptionRight.Call
? underlying.BidPrice
: underlying.AskPrice;
// quantity is normally negative algo's holdings, but since we're modeling the contract holder (counter-party)
// it's negative THEIR holdings. holding.Quantity is negative, so if counter-party exercises, they would reduce holdings
var underlyingQuantity = option.GetExerciseQuantity(holding.Quantity);
// Scenario 1 (base): we just close option position
var marketOrder1 = new MarketOrder(option.Symbol, -holding.Quantity, option.LocalTime.ConvertToUtc(option.Exchange.TimeZone));
var orderFee1 = option.FeeModel.GetOrderFee(
new OrderFeeParameters(option, marketOrder1)).Value.Amount * option.QuoteCurrency.ConversionRate;
var basePnL = (optionPrice - holding.AveragePrice) * -holding.Quantity
* option.QuoteCurrency.ConversionRate
* option.SymbolProperties.ContractMultiplier
- orderFee1;
// Scenario 2 (alternative): we exercise option and then close underlying position
var optionExerciseOrder2 = new OptionExerciseOrder(option.Symbol, (int)holding.AbsoluteQuantity, option.LocalTime.ConvertToUtc(option.Exchange.TimeZone));
var optionOrderFee2 = option.FeeModel.GetOrderFee(
new OrderFeeParameters(option, optionExerciseOrder2)).Value.Amount * option.QuoteCurrency.ConversionRate;
var underlyingOrderFee2Amount = 0m;
// Cash settlements do not open a position for the underlying.
// For Physical Delivery, we calculate the order fee since we have to close the position
if (option.ExerciseSettlement == SettlementType.PhysicalDelivery)
{
var underlyingMarketOrder2 = new MarketOrder(underlying.Symbol, -underlyingQuantity,
underlying.LocalTime.ConvertToUtc(underlying.Exchange.TimeZone));
var underlyingOrderFee2 = underlying.FeeModel.GetOrderFee(
new OrderFeeParameters(underlying, underlyingMarketOrder2)).Value.Amount * underlying.QuoteCurrency.ConversionRate;
underlyingOrderFee2Amount = underlyingOrderFee2;
}
// calculating P/L of the two transactions (exercise option and then close underlying position)
var altPnL = (underlyingPrice - option.ScaledStrikePrice) * underlyingQuantity * underlying.QuoteCurrency.ConversionRate * option.ContractUnitOfTrade
- underlyingOrderFee2Amount
- holding.AveragePrice * holding.AbsoluteQuantity * option.SymbolProperties.ContractMultiplier * option.QuoteCurrency.ConversionRate
- optionOrderFee2;
return altPnL - basePnL;
}
}
}
@@ -0,0 +1,39 @@
/*
* 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.Interfaces;
namespace QuantConnect.Securities.Option
{
/// <summary>
/// An implementation of <see cref="IOptionChainProvider"/> that always returns an empty list of contracts
/// </summary>
public class EmptyOptionChainProvider : IOptionChainProvider
{
/// <summary>
/// Gets the list of option contracts for a given underlying symbol
/// </summary>
/// <param name="symbol">The underlying symbol</param>
/// <param name="date">The date for which to request the option chain (only used in backtesting)</param>
/// <returns>The list of option contracts</returns>
public IEnumerable<Symbol> GetOptionContractList(Symbol symbol, DateTime date)
{
return Enumerable.Empty<Symbol>();
}
}
}
@@ -0,0 +1,47 @@
/*
* 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.Data;
using QuantConnect.Data.Market;
namespace QuantConnect.Securities.Option
{
/// <summary>
/// Class implements Fed's US primary credit rate as risk free rate, implementing <see cref="IQLRiskFreeRateEstimator"/>.
/// </summary>
/// <remarks>
/// Board of Governors of the Federal Reserve System (US), Primary Credit Rate - Historical Dates of Changes and Rates for Federal Reserve District 8: St. Louis [PCREDIT8]
/// retrieved from FRED, Federal Reserve Bank of St. Louis; https://fred.stlouisfed.org/series/PCREDIT8
/// </remarks>
public class FedRateQLRiskFreeRateEstimator : IQLRiskFreeRateEstimator
{
private readonly InterestRateProvider _interestRateProvider = new ();
/// <summary>
/// Returns current flat estimate of the risk free rate
/// </summary>
/// <param name="security">The option security object</param>
/// <param name="slice">The current data slice. This can be used to access other information
/// available to the algorithm</param>
/// <param name="contract">The option contract to evaluate</param>
/// <returns>The estimate</returns>
public decimal Estimate(Security security, Slice slice, OptionContract contract)
{
return slice == null
? InterestRateProvider.DefaultRiskFreeRate
: _interestRateProvider.GetInterestRate(slice.Time.Date);
}
}
}
@@ -0,0 +1,30 @@
/*
* 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.Option
{
/// <summary>
/// The option assignment model emulates exercising of short option positions in the portfolio.
/// </summary>
public interface IOptionAssignmentModel
{
/// <summary>
/// Get's the option assignments to generate if any
/// </summary>
/// <param name="parameters">The option assignment parameters data transfer class</param>
/// <returns>The option assignment result</returns>
public OptionAssignmentResult GetAssignment(OptionAssignmentParameters parameters);
}
}
@@ -0,0 +1,36 @@
/*
* 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.Data;
using QuantConnect.Data.Market;
namespace QuantConnect.Securities.Option
{
/// <summary>
/// Defines a model used to calculate the theoretical price of an option contract.
/// </summary>
public interface IOptionPriceModel
{
/// <summary>
/// Evaluates the specified option contract to compute a theoretical price, IV and greeks
/// </summary>
/// <param name="parameters">A <see cref="OptionPriceModelParameters"/> object
/// containing the security, slice and contract</param>
/// <returns>An instance of <see cref="OptionPriceModelResult"/> containing the theoretical
/// price of the specified option contract</returns>
OptionPriceModelResult Evaluate(OptionPriceModelParameters parameters);
}
}
@@ -0,0 +1,34 @@
/*
* 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.Indicators;
namespace QuantConnect.Securities.Option
{
/// <summary>
/// Provides option price models for option securities
/// </summary>
public interface IOptionPriceModelProvider
{
/// <summary>
/// Gets the option price model for the specified option symbol.
/// If no pricing model is specified, the default option price model for the symbol security type will be returned.
/// </summary>
/// <param name="symbol">The symbol</param>
/// <param name="pricingModelType">The option pricing model type to use</param>
/// <returns>The option price model for the given symbol</returns>
IOptionPriceModel GetOptionPriceModel(Symbol symbol, OptionPricingModelType? pricingModelType = null);
}
}
@@ -0,0 +1,37 @@
/*
* 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.Data;
using QuantConnect.Data.Market;
namespace QuantConnect.Securities.Option
{
/// <summary>
/// Defines QuantLib dividend yield estimator for option pricing model. User may define his own estimators,
/// including those forward and backward looking ones.
/// </summary>
public interface IQLDividendYieldEstimator
{
/// <summary>
/// Returns current estimate of the stock dividend yield
/// </summary>
/// <param name="security">The option security object</param>
/// <param name="slice">The current data slice. This can be used to access other information
/// available to the algorithm</param>
/// <param name="contract">The option contract to evaluate</param>
/// <returns>Dividend yield</returns>
double Estimate(Security security, Slice slice, OptionContract contract);
}
}
@@ -0,0 +1,36 @@
/*
* 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.Data;
using QuantConnect.Data.Market;
namespace QuantConnect.Securities.Option
{
/// <summary>
/// Defines QuantLib risk free rate estimator for option pricing model.
/// </summary>
public interface IQLRiskFreeRateEstimator
{
/// <summary>
/// Returns current estimate of the risk free rate
/// </summary>
/// <param name="security">The option security object</param>
/// <param name="slice">The current data slice. This can be used to access other information
/// available to the algorithm</param>
/// <param name="contract">The option contract to evaluate</param>
/// <returns>Risk free rate</returns>
decimal Estimate(Security security, Slice slice, OptionContract contract);
}
}
@@ -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 QuantConnect.Data;
using QuantConnect.Data.Market;
namespace QuantConnect.Securities.Option
{
/// <summary>
/// Defines QuantLib underlying volatility estimator for option pricing model. User may define his own estimators,
/// including those forward and backward looking ones.
/// </summary>
public interface IQLUnderlyingVolatilityEstimator
{
/// <summary>
/// Returns current estimate of the underlying volatility
/// </summary>
/// <param name="security">The option security object</param>
/// <param name="slice">The current data slice. This can be used to access other information
/// available to the algorithm</param>
/// <param name="contract">The option contract to evaluate</param>
/// <returns>Volatility</returns>
double Estimate(Security security, Slice slice, OptionContract contract);
/// <summary>
/// Indicates whether volatility model is warmed up or no
/// </summary>
bool IsReady { get; }
}
}
@@ -0,0 +1,33 @@
/*
* 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.Option
{
/// <summary>
/// The null option assignment model, that will disable automatic order assignment
/// </summary>
public class NullOptionAssignmentModel : IOptionAssignmentModel
{
/// <summary>
/// Get's the option assignments to generate if any
/// </summary>
/// <param name="parameters">The option assignment parameters data transfer object</param>
/// <returns>The option assignment result</returns>
public OptionAssignmentResult GetAssignment(OptionAssignmentParameters parameters)
{
return OptionAssignmentResult.Null;
}
}
}
+691
View File
@@ -0,0 +1,691 @@
/*
* 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 Python.Runtime;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Interfaces;
using QuantConnect.Orders;
using QuantConnect.Orders.Fees;
using QuantConnect.Orders.Fills;
using QuantConnect.Orders.OptionExercise;
using QuantConnect.Orders.Slippage;
using QuantConnect.Python;
using QuantConnect.Securities.Interfaces;
using QuantConnect.Util;
using System;
using System.Collections.Generic;
namespace QuantConnect.Securities.Option
{
/// <summary>
/// Option Security Object Implementation for Option Assets
/// </summary>
/// <seealso cref="Security"/>
public class Option : Security, IDerivativeSecurity, IOptionPrice
{
/// <summary>
/// The default number of days required to settle an equity sale
/// </summary>
public static int DefaultSettlementDays { get; set; } = 1;
/// <summary>
/// The default time of day for settlement
/// </summary>
public static readonly TimeSpan DefaultSettlementTime = new(6, 0, 0);
/// <summary>
/// Constructor for the option security
/// </summary>
/// <param name="exchangeHours">Defines the hours this exchange is open</param>
/// <param name="quoteCurrency">The cash object that represent the quote currency</param>
/// <param name="config">The subscription configuration for this security</param>
/// <param name="symbolProperties">The symbol properties for this security</param>
/// <param name="currencyConverter">Currency converter used to convert <see cref="CashAmount"/>
/// instances into units of the account currency</param>
/// <param name="registeredTypes">Provides all data types registered in the algorithm</param>
/// <param name="priceModelProvider">The option price model provider</param>
/// <remarks>Used in testing</remarks>
public Option(SecurityExchangeHours exchangeHours,
SubscriptionDataConfig config,
Cash quoteCurrency,
OptionSymbolProperties symbolProperties,
ICurrencyConverter currencyConverter,
IRegisteredSecurityDataTypesProvider registeredTypes,
IOptionPriceModelProvider priceModelProvider = null)
: this(config.Symbol,
quoteCurrency,
symbolProperties,
new OptionExchange(exchangeHours),
new OptionCache(),
new OptionPortfolioModel(),
new ImmediateFillModel(),
new InteractiveBrokersFeeModel(),
NullSlippageModel.Instance,
new ImmediateSettlementModel(),
Securities.VolatilityModel.Null,
new OptionMarginModel(),
new OptionDataFilter(),
new SecurityPriceVariationModel(),
currencyConverter,
registeredTypes,
null,
priceModelProvider)
{
AddData(config);
SetDataNormalizationMode(DataNormalizationMode.Raw);
}
/// <summary>
/// Constructor for the option security
/// </summary>
/// <param name="symbol">The symbol of the security</param>
/// <param name="exchangeHours">Defines the hours this exchange is open</param>
/// <param name="quoteCurrency">The cash object that represent the quote currency</param>
/// <param name="symbolProperties">The symbol properties for this security</param>
/// <param name="currencyConverter">Currency converter used to convert <see cref="CashAmount"/>
/// instances into units of the account currency</param>
/// <param name="registeredTypes">Provides all data types registered in the algorithm</param>
/// <param name="securityCache">Cache to store security information</param>
/// <param name="underlying">Future underlying security</param>
/// <param name="priceModelProvider">The option price model provider</param>
public Option(Symbol symbol,
SecurityExchangeHours exchangeHours,
Cash quoteCurrency,
OptionSymbolProperties symbolProperties,
ICurrencyConverter currencyConverter,
IRegisteredSecurityDataTypesProvider registeredTypes,
SecurityCache securityCache,
Security underlying,
IOptionPriceModelProvider priceModelProvider = null)
: this (symbol,
quoteCurrency,
symbolProperties,
new OptionExchange(exchangeHours),
securityCache,
new OptionPortfolioModel(),
new ImmediateFillModel(),
new InteractiveBrokersFeeModel(),
NullSlippageModel.Instance,
new ImmediateSettlementModel(),
Securities.VolatilityModel.Null,
new OptionMarginModel(),
new OptionDataFilter(),
new SecurityPriceVariationModel(),
currencyConverter,
registeredTypes,
underlying,
priceModelProvider)
{
}
/// <summary>
/// Creates instance of the Option class.
/// </summary>
/// <remarks>
/// Allows for the forwarding of the security configuration to the
/// base Security constructor
/// </remarks>
protected Option(Symbol symbol,
Cash quoteCurrency,
SymbolProperties symbolProperties,
SecurityExchange exchange,
SecurityCache cache,
ISecurityPortfolioModel portfolioModel,
IFillModel fillModel,
IFeeModel feeModel,
ISlippageModel slippageModel,
ISettlementModel settlementModel,
IVolatilityModel volatilityModel,
IBuyingPowerModel buyingPowerModel,
ISecurityDataFilter dataFilter,
IPriceVariationModel priceVariationModel,
ICurrencyConverter currencyConverter,
IRegisteredSecurityDataTypesProvider registeredTypesProvider,
Security underlying,
IOptionPriceModelProvider priceModelProvider
) : base(
symbol,
quoteCurrency,
symbolProperties,
exchange,
cache,
portfolioModel,
fillModel,
feeModel,
slippageModel,
settlementModel,
volatilityModel,
buyingPowerModel,
dataFilter,
priceVariationModel,
currencyConverter,
registeredTypesProvider,
Securities.MarginInterestRateModel.Null
)
{
ExerciseSettlement = SettlementType.PhysicalDelivery;
SetDataNormalizationMode(DataNormalizationMode.Raw);
OptionExerciseModel = new DefaultExerciseModel();
PriceModel = (priceModelProvider ?? QLOptionPriceModelProvider.Instance).GetOptionPriceModel(symbol);
Holdings = new OptionHolding(this, currencyConverter);
_symbolProperties = (OptionSymbolProperties)symbolProperties;
SetFilter(-1, 1, TimeSpan.Zero, TimeSpan.FromDays(35));
Underlying = underlying;
OptionAssignmentModel = new DefaultOptionAssignmentModel();
ScaledStrikePrice = StrikePrice * SymbolProperties.StrikeMultiplier;
}
// save off a strongly typed version of symbol properties
private readonly OptionSymbolProperties _symbolProperties;
/// <summary>
/// Returns true if this is the option chain security, false if it is a specific option contract
/// </summary>
public bool IsOptionChain => Symbol.IsCanonical();
/// <summary>
/// Returns true if this is a specific option contract security, false if it is the option chain security
/// </summary>
public bool IsOptionContract => !Symbol.IsCanonical();
/// <summary>
/// Gets the strike price
/// </summary>
public decimal StrikePrice => Symbol.ID.StrikePrice;
/// <summary>
/// Gets the strike price multiplied by the strike multiplier
/// </summary>
public decimal ScaledStrikePrice
{
get;
private set;
}
/// <summary>
/// Gets the expiration date
/// </summary>
public DateTime Expiry => Symbol.ID.Date;
/// <summary>
/// Gets the right being purchased (call [right to buy] or put [right to sell])
/// </summary>
public OptionRight Right => Symbol.ID.OptionRight;
/// <summary>
/// Gets the option style
/// </summary>
public OptionStyle Style => Symbol.ID.OptionStyle;
/// <summary>
/// Gets the most recent bid price if available
/// </summary>
public override decimal BidPrice => Cache.BidPrice;
/// <summary>
/// Gets the most recent ask price if available
/// </summary>
public override decimal AskPrice => Cache.AskPrice;
/// <summary>
/// When the holder of an equity option exercises one contract, or when the writer of an equity option is assigned
/// an exercise notice on one contract, this unit of trade, usually 100 shares of the underlying security, changes hands.
/// </summary>
public int ContractUnitOfTrade
{
get
{
return _symbolProperties.ContractUnitOfTrade;
}
set
{
_symbolProperties.SetContractUnitOfTrade(value);
}
}
/// <summary>
/// The contract multiplier for the option security
/// </summary>
public int ContractMultiplier
{
get
{
return (int)_symbolProperties.ContractMultiplier;
}
set
{
_symbolProperties.SetContractMultiplier(value);
}
}
/// <summary>
/// Aggregate exercise amount or aggregate contract value. It is the total amount of cash one will pay (or receive) for the shares of the
/// underlying stock if he/she decides to exercise (or is assigned an exercise notice). This amount is not the premium paid or received for an equity option.
/// </summary>
public decimal GetAggregateExerciseAmount()
{
return StrikePrice * ContractMultiplier;
}
/// <summary>
/// Returns the directional quantity of underlying shares that are going to change hands on exercise/assignment of all
/// contracts held by this account, taking into account the contract's <see cref="Right"/> as well as the contract's current
/// <see cref="ContractUnitOfTrade"/>, which may have recently changed due to a split/reverse split in the underlying security.
/// </summary>
/// <remarks>
/// Long option positions result in exercise while short option positions result in assignment. This function uses the term
/// exercise loosely to refer to both situations.
/// </remarks>
public decimal GetExerciseQuantity()
{
// negate Holdings.Quantity to match an equivalent order
return GetExerciseQuantity(-Holdings.Quantity);
}
/// <summary>
/// Returns the directional quantity of underlying shares that are going to change hands on exercise/assignment of the
/// specified <paramref name="exerciseOrderQuantity"/>, taking into account the contract's <see cref="Right"/> as well
/// as the contract's current <see cref="ContractUnitOfTrade"/>, which may have recently changed due to a split/reverse
/// split in the underlying security.
/// </summary>
/// <remarks>
/// Long option positions result in exercise while short option positions result in assignment. This function uses the term
/// exercise loosely to refer to both situations.
/// </remarks>
/// <paramref name="exerciseOrderQuantity">The quantity of contracts being exercised as provided by the <see cref="OptionExerciseOrder"/>.
/// A negative value indicates exercise (we are long and the order quantity is negative to bring us (closer) to zero.
/// A positive value indicates assignment (we are short and the order quantity is positive to bring us (closer) to zero.</paramref>
public decimal GetExerciseQuantity(decimal exerciseOrderQuantity)
{
// when exerciseOrderQuantity > 0 [ we are short ]
// && right == call => we sell to contract holder => negative
// && right == put => we buy from contract holder => positive
// when exerciseOrderQuantity < 0 [ we are long ]
// && right == call => we buy from contract holder => positive
// && right == put => we sell to contract holder => negative
var sign = Right == OptionRight.Call ? -1 : 1;
return sign * exerciseOrderQuantity * ContractUnitOfTrade;
}
/// <summary>
/// Checks if option is eligible for automatic exercise on expiration
/// </summary>
public bool IsAutoExercised(decimal underlyingPrice)
{
return GetIntrinsicValue(underlyingPrice) >= 0.01m;
}
/// <summary>
/// Intrinsic value function of the option
/// </summary>
public decimal GetIntrinsicValue(decimal underlyingPrice)
{
return OptionPayoff.GetIntrinsicValue(underlyingPrice, ScaledStrikePrice, Right);
}
/// <summary>
/// Option payoff function at expiration time
/// </summary>
/// <param name="underlyingPrice">The price of the underlying</param>
/// <returns></returns>
public decimal GetPayOff(decimal underlyingPrice)
{
return OptionPayoff.GetPayOff(underlyingPrice, ScaledStrikePrice, Right);
}
/// <summary>
/// Option out of the money function
/// </summary>
/// <param name="underlyingPrice">The price of the underlying</param>
/// <returns></returns>
public decimal OutOfTheMoneyAmount(decimal underlyingPrice)
{
return Math.Max(0, Right == OptionRight.Call ? ScaledStrikePrice - underlyingPrice : underlyingPrice - ScaledStrikePrice);
}
/// <summary>
/// Specifies if option contract has physical or cash settlement on exercise
/// </summary>
public SettlementType ExerciseSettlement
{
get; set;
}
/// <summary>
/// Gets or sets the underlying security object.
/// </summary>
public Security Underlying
{
get; set;
}
/// <summary>
/// Gets a reduced interface of the underlying security object.
/// </summary>
ISecurityPrice IOptionPrice.Underlying => Underlying;
/// <summary>
/// For this option security object, evaluates the specified option
/// contract to compute a theoretical price, IV and greeks
/// </summary>
/// <param name="slice">The current data slice. This can be used to access other information
/// available to the algorithm</param>
/// <param name="contract">The option contract to evaluate</param>
/// <returns>An instance of <see cref="OptionPriceModelResult"/> containing the theoretical
/// price of the specified option contract</returns>
public OptionPriceModelResult EvaluatePriceModel(Slice slice, OptionContract contract)
{
return PriceModel.Evaluate(new OptionPriceModelParameters(this, slice, contract));
}
/// <summary>
/// Gets or sets the price model for this option security
/// </summary>
public IOptionPriceModel PriceModel
{
get; set;
}
/// <summary>
/// Fill model used to produce fill events for this security
/// </summary>
public IOptionExerciseModel OptionExerciseModel
{
get; set;
}
/// <summary>
/// The automatic option assignment model
/// </summary>
public IOptionAssignmentModel OptionAssignmentModel
{
get; set;
}
/// <summary>
/// When enabled, approximates Greeks if corresponding pricing model didn't calculate exact numbers
/// </summary>
[Obsolete("This property has been deprecated. Please use QLOptionPriceModel.EnableGreekApproximation instead.")]
public bool EnableGreekApproximation
{
get
{
var model = PriceModel as QLOptionPriceModel;
if (model != null)
{
return model.EnableGreekApproximation;
}
return false;
}
set
{
var model = PriceModel as QLOptionPriceModel;
if (model != null)
{
model.EnableGreekApproximation = value;
}
}
}
/// <summary>
/// Gets or sets the contract filter
/// </summary>
public IDerivativeSecurityFilter<OptionUniverse> ContractFilter
{
get; set;
}
/// <summary>
/// Sets the automatic option assignment model
/// </summary>
/// <param name="pyObject">The option assignment model to use</param>
public void SetOptionAssignmentModel(PyObject pyObject)
{
OptionAssignmentModel = PythonUtil.CreateInstanceOrWrapper<IOptionAssignmentModel>(
pyObject,
py => new OptionAssignmentModelPythonWrapper(py)
);
}
/// <summary>
/// Sets the automatic option assignment model
/// </summary>
/// <param name="optionAssignmentModel">The option assignment model to use</param>
public void SetOptionAssignmentModel(IOptionAssignmentModel optionAssignmentModel)
{
OptionAssignmentModel = optionAssignmentModel;
}
/// <summary>
/// Sets the option exercise model
/// </summary>
/// <param name="pyObject">The option exercise model to use</param>
public void SetOptionExerciseModel(PyObject pyObject)
{
OptionExerciseModel = PythonUtil.CreateInstanceOrWrapper<IOptionExerciseModel>(
pyObject,
py => new OptionExerciseModelPythonWrapper(py)
);
}
/// <summary>
/// Sets the option exercise model
/// </summary>
/// <param name="optionExerciseModel">The option exercise model to use</param>
public void SetOptionExerciseModel(IOptionExerciseModel optionExerciseModel)
{
OptionExerciseModel = optionExerciseModel;
}
/// <summary>
/// Sets the <see cref="ContractFilter"/> to a new instance of the filter
/// using the specified min and max strike values. Contracts with expirations further than 35
/// days out will also be filtered.
/// </summary>
/// <param name="minStrike">The min strike rank relative to market price, for example, -1 would put
/// a lower bound of one strike under market price, where a +1 would put a lower bound of one strike
/// over market price</param>
/// <param name="maxStrike">The max strike rank relative to market place, for example, -1 would put
/// an upper bound of on strike under market price, where a +1 would be an upper bound of one strike
/// over market price</param>
public void SetFilter(int minStrike, int maxStrike)
{
SetFilterImp(universe => universe.Strikes(minStrike, maxStrike));
}
/// <summary>
/// Sets the <see cref="ContractFilter"/> to a new instance of the filter
/// using the specified min and max strike and expiration range values
/// </summary>
/// <param name="minExpiry">The minimum time until expiry to include, for example, TimeSpan.FromDays(10)
/// would exclude contracts expiring in less than 10 days</param>
/// <param name="maxExpiry">The maximum time until expiry to include, for example, TimeSpan.FromDays(10)
/// would exclude contracts expiring in more than 10 days</param>
public void SetFilter(TimeSpan minExpiry, TimeSpan maxExpiry)
{
SetFilterImp(universe => universe.Expiration(minExpiry, maxExpiry));
}
/// <summary>
/// Sets the <see cref="ContractFilter"/> to a new instance of the filter
/// using the specified min and max strike and expiration range values
/// </summary>
/// <param name="minStrike">The min strike rank relative to market price, for example, -1 would put
/// a lower bound of one strike under market price, where a +1 would put a lower bound of one strike
/// over market price</param>
/// <param name="maxStrike">The max strike rank relative to market place, for example, -1 would put
/// an upper bound of on strike under market price, where a +1 would be an upper bound of one strike
/// over market price</param>
/// <param name="minExpiry">The minimum time until expiry to include, for example, TimeSpan.FromDays(10)
/// would exclude contracts expiring in less than 10 days</param>
/// <param name="maxExpiry">The maximum time until expiry to include, for example, TimeSpan.FromDays(10)
/// would exclude contracts expiring in more than 10 days</param>
public void SetFilter(int minStrike, int maxStrike, TimeSpan minExpiry, TimeSpan maxExpiry)
{
SetFilterImp(universe => universe
.Strikes(minStrike, maxStrike)
.Expiration(minExpiry, maxExpiry));
}
/// <summary>
/// Sets the <see cref="ContractFilter"/> to a new instance of the filter
/// using the specified min and max strike and expiration range values
/// </summary>
/// <param name="minStrike">The min strike rank relative to market price, for example, -1 would put
/// a lower bound of one strike under market price, where a +1 would put a lower bound of one strike
/// over market price</param>
/// <param name="maxStrike">The max strike rank relative to market place, for example, -1 would put
/// an upper bound of on strike under market price, where a +1 would be an upper bound of one strike
/// over market price</param>
/// <param name="minExpiryDays">The minimum time, expressed in days, until expiry to include, for example, 10
/// would exclude contracts expiring in less than 10 days</param>
/// <param name="maxExpiryDays">The maximum time, expressed in days, until expiry to include, for example, 10
/// would exclude contracts expiring in more than 10 days</param>
public void SetFilter(int minStrike, int maxStrike, int minExpiryDays, int maxExpiryDays)
{
SetFilterImp(universe => universe
.Strikes(minStrike, maxStrike)
.Expiration(minExpiryDays, maxExpiryDays));
}
/// <summary>
/// Sets the <see cref="ContractFilter"/> to a new universe selection function
/// </summary>
/// <param name="universeFunc">new universe selection function</param>
public void SetFilter(Func<OptionFilterUniverse, OptionFilterUniverse> universeFunc)
{
ContractFilter = new FuncSecurityDerivativeFilter<OptionUniverse>(universe =>
{
var optionUniverse = universe as OptionFilterUniverse;
//A null result is allowed: filter methods modify the universe in place,
//returning it is only necessary for chaining
var result = universeFunc(optionUniverse) ?? optionUniverse;
return result.ApplyTypesFilter();
});
ContractFilter.Asynchronous = false;
}
/// <summary>
/// Sets the <see cref="ContractFilter"/> to a new universe selection function
/// </summary>
/// <param name="universeFunc">new universe selection function</param>
public void SetFilter(PyObject universeFunc)
{
ContractFilter = new FuncSecurityDerivativeFilter<OptionUniverse>(universe =>
{
var optionUniverse = universe as OptionFilterUniverse;
using (Py.GIL())
{
PyObject result = (universeFunc as dynamic)(optionUniverse);
//Try to convert it to the possible outcomes and process it
//Must try filter first, if it is a filter and you try and convert it to
//list, TryConvert() with catch an exception. Later Python algo will break on
//this exception because we are using Py.GIL() and it will see the error set
OptionFilterUniverse filter;
List<Symbol> list;
//A None result is allowed: filter methods modify the universe in place,
//returning it is only necessary for chaining
if (result == null || result.IsNone())
{
return optionUniverse.ApplyTypesFilter();
}
if ((result).TryConvert(out filter))
{
optionUniverse = filter;
}
else if ((result).TryConvert(out list))
{
optionUniverse = optionUniverse.WhereContains(list);
}
else
{
throw new ArgumentException($"QCAlgorithm.SetFilter: result type {result.GetPythonType()} from " +
$"filter function is not a valid argument, please return either a OptionFilterUniverse or a list of symbols");
}
}
return optionUniverse.ApplyTypesFilter();
});
ContractFilter.Asynchronous = false;
}
/// <summary>
/// Sets the data normalization mode to be used by this security
/// </summary>
public override void SetDataNormalizationMode(DataNormalizationMode mode)
{
if (mode != DataNormalizationMode.Raw)
{
throw new ArgumentException("DataNormalizationMode.Raw must be used with options");
}
base.SetDataNormalizationMode(mode);
}
private void SetFilterImp(Func<OptionFilterUniverse, OptionFilterUniverse> universeFunc)
{
ContractFilter = new FuncSecurityDerivativeFilter<OptionUniverse>(universe =>
{
var optionUniverse = universe as OptionFilterUniverse;
var result = universeFunc(optionUniverse);
return result.ApplyTypesFilter();
});
}
/// <summary>
/// Sets the option price model
/// </summary>
/// <param name="pyObject">The option price model to use</param>
public void SetPriceModel(PyObject pyObject)
{
PriceModel = PythonUtil.CreateInstanceOrWrapper<IOptionPriceModel>(
pyObject,
py => new OptionPriceModelPythonWrapper(py)
);
}
/// <summary>
/// Sets the option price model
/// </summary>
/// <param name="priceModel">The option price model to use</param>
public void SetPriceModel(IOptionPriceModel priceModel)
{
PriceModel = priceModel;
}
/// <summary>
/// Updates the symbol properties of this security
/// </summary>
internal override void UpdateSymbolProperties(SymbolProperties symbolProperties)
{
if (symbolProperties != null)
{
SymbolProperties = new OptionSymbolProperties(symbolProperties);
}
}
/// <summary>
/// Returns the securities symbol
/// </summary>
public static implicit operator Symbol(Option security) => security.Symbol;
}
}
@@ -0,0 +1,37 @@
/*
* 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.Option
{
/// <summary>
/// The option assignment parameters data transfer class
/// </summary>
public class OptionAssignmentParameters
{
/// <summary>
/// The option to evaluate option assignments for
/// </summary>
public Option Option { get; set; }
/// <summary>
/// Creates a new instance
/// </summary>
/// <param name="option">The target option</param>
public OptionAssignmentParameters(Option option)
{
Option = option;
}
}
}
@@ -0,0 +1,49 @@
/*
* 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.Option
{
/// <summary>
/// Data transfer object class
/// </summary>
public class OptionAssignmentResult
{
/// <summary>
/// No option assignment should take place
/// </summary>
public static OptionAssignmentResult Null { get; } = new OptionAssignmentResult(decimal.Zero, string.Empty);
/// <summary>
/// The amount of option holdings to trigger the assignment for
/// </summary>
public decimal Quantity { get; set; }
/// <summary>
/// The tag that will be used in the order for the option assignment
/// </summary>
public string Tag { get; set; }
/// <summary>
/// Creates a new instance
/// </summary>
/// <param name="quantity">The quantity to assign</param>
/// <param name="tag">The order tag to use</param>
public OptionAssignmentResult(decimal quantity, string tag)
{
Quantity = quantity;
Tag = tag;
}
}
}
+39
View File
@@ -0,0 +1,39 @@
/*
* 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.Data;
namespace QuantConnect.Securities.Option
{
/// <summary>
/// Option specific caching support
/// </summary>
/// <seealso cref="SecurityCache"/>
public class OptionCache : SecurityCache
{
/// <summary>
/// Stores the specified data list in the cache, updating the open interest from any chain universe data
/// </summary>
/// <param name="data">The collection of data to store in this cache</param>
/// <param name="dataType">The data type</param>
public override void StoreData(IReadOnlyList<BaseData> data, Type dataType)
{
UpdateOpenInterest(data);
base.StoreData(data, dataType);
}
}
}
@@ -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.Option
{
/// <summary>
/// Option packet by packet data filtering mechanism for dynamically detecting bad ticks.
/// </summary>
/// <seealso cref="SecurityDataFilter"/>
public class OptionDataFilter : SecurityDataFilter
{
}
}
@@ -0,0 +1,43 @@
/*
* 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.Option
{
/// <summary>
/// Option exchange class - information and helper tools for option exchange properties
/// </summary>
/// <seealso cref="SecurityExchange"/>
public class OptionExchange : SecurityExchange
{
/// <summary>
/// Number of trading days per year for this security, 252.
/// </summary>
/// <remarks>Used for performance statistics to calculate sharpe ratio accurately</remarks>
public override int TradingDaysPerYear
{
get { return 252; }
}
/// <summary>
/// Initializes a new instance of the <see cref="OptionExchange"/> class using the specified
/// exchange hours to determine open/close times
/// </summary>
/// <param name="exchangeHours">Contains the weekly exchange schedule plus holidays</param>
public OptionExchange(SecurityExchangeHours exchangeHours)
: base(exchangeHours)
{
}
}
}
File diff suppressed because it is too large Load Diff
+36
View File
@@ -0,0 +1,36 @@
/*
* 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.Orders;
namespace QuantConnect.Securities.Option
{
/// <summary>
/// Option holdings implementation of the base securities class
/// </summary>
/// <seealso cref="SecurityHolding"/>
public class OptionHolding : SecurityHolding
{
/// <summary>
/// Option Holding Class constructor
/// </summary>
/// <param name="security">The option security being held</param>
/// <param name="currencyConverter">A currency converter instance</param>
public OptionHolding(Option security, ICurrencyConverter currencyConverter)
: base(security, currencyConverter)
{
}
}
}
@@ -0,0 +1,186 @@
/*
* 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.Orders.Fees;
namespace QuantConnect.Securities.Option
{
/// <summary>
/// Represents a simple option margin model.
/// </summary>
/// <remarks>
/// Options are not traded on margin. Margin requirements exist though for those portfolios with short positions.
/// Current implementation covers only single long/naked short option positions.
/// </remarks>
public class OptionMarginModel : SecurityMarginModel
{
// initial margin
private const decimal OptionMarginRequirement = 1;
private const decimal NakedPositionMarginRequirement = 0.1m;
private const decimal EquityOptionNakedPositionMarginRequirementOtm = 0.2m;
private const decimal IndexOptionNakedPositionMarginRequirementOtm = 0.15m;
/// <summary>
/// Initializes a new instance of the <see cref="OptionMarginModel"/>
/// </summary>
/// <param name="requiredFreeBuyingPowerPercent">The percentage used to determine the required unused buying power for the account.</param>
public OptionMarginModel(decimal requiredFreeBuyingPowerPercent = 0)
{
RequiredFreeBuyingPowerPercent = requiredFreeBuyingPowerPercent;
}
/// <summary>
/// Gets the current leverage of the security
/// </summary>
/// <param name="security">The security to get leverage for</param>
/// <returns>The current leverage in the security</returns>
public override decimal GetLeverage(Security security)
{
// Options are not traded on margin
return 1;
}
/// <summary>
/// Sets the leverage for the applicable securities, i.e, options.
/// </summary>
/// <param name="security"></param>
/// <param name="leverage">The new leverage</param>
public override void SetLeverage(Security security, decimal leverage)
{
// Options are leveraged products and different leverage cannot be set by user.
throw new InvalidOperationException("Options are leveraged products and different leverage cannot be set by user");
}
/// <summary>
/// Gets the total margin required to execute the specified order in units of the account currency including fees
/// </summary>
/// <param name="parameters">An object containing the portfolio, the security and the order</param>
/// <returns>The total margin in terms of the currency quoted in the order</returns>
public override InitialMargin GetInitialMarginRequiredForOrder(
InitialMarginRequiredForOrderParameters parameters
)
{
//Get the order value from the non-abstract order classes (MarketOrder, LimitOrder, StopMarketOrder)
//Market order is approximated from the current security price and set in the MarketOrder Method in QCAlgorithm.
var fees = parameters.Security.FeeModel.GetOrderFee(
new OrderFeeParameters(parameters.Security, parameters.Order)
);
var feesInAccountCurrency = parameters.CurrencyConverter.ConvertToAccountCurrency(fees.Value);
var value = parameters.Order.GetValue(parameters.Security);
var orderMargin = value * GetMarginRequirement(parameters.Security, parameters.Order.Quantity, value);
return orderMargin + Math.Sign(orderMargin) * feesInAccountCurrency.Amount;
}
/// <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 provided holdings quantity/cost/value</returns>
public override MaintenanceMargin GetMaintenanceMargin(MaintenanceMarginParameters parameters)
{
// Long options have zero maintenance margin requirement
return parameters.Quantity >= 0 ? 0 : parameters.AbsoluteHoldingsCost * GetMaintenanceMarginRequirement(parameters);
}
/// <summary>
/// The margin that must be held in order to increase the position by the provided quantity
/// </summary>
/// <returns>The initial margin required for the provided security and quantity</returns>
public override InitialMargin GetInitialMarginRequirement(InitialMarginParameters parameters)
{
var security = parameters.Security;
var quantity = parameters.Quantity;
var value = security.QuoteCurrency.ConversionRate
* security.SymbolProperties.ContractMultiplier
* security.Price
* quantity;
// Initial margin requirement for long options is only the premium that is paid upfront
return new OptionInitialMargin(parameters.Quantity >= 0 ? 0 : value * GetMarginRequirement(security, quantity, value), value);
}
/// <summary>
/// The percentage of the holding's absolute cost that must be held in free cash in order to avoid a margin call
/// </summary>
private decimal GetMaintenanceMarginRequirement(MaintenanceMarginParameters parameters)
{
return GetMarginRequirement(parameters.Security, parameters.Quantity, parameters.HoldingsCost);
}
/// <summary>
/// Private method takes option security and its holding and returns required margin. Method considers all short positions naked.
/// </summary>
/// <param name="security">Option security</param>
/// <param name="quantity">Holding quantity</param>
/// <param name="value">Holding value</param>
/// <returns></returns>
private decimal GetMarginRequirement(Security security, decimal quantity, decimal value)
{
var option = (Option)security;
if (value == 0m ||
option.Close == 0m ||
option.StrikePrice == 0m ||
option.Underlying == null ||
option.Underlying.Close == 0m)
{
return 0m;
}
if (value > 0m)
{
return OptionMarginRequirement;
}
var absValue = -value;
var optionProperties = (OptionSymbolProperties)option.SymbolProperties;
var underlying = option.Underlying;
// inferring ratios of the option and its underlying to get underlying security value
var multiplierRatio = underlying.SymbolProperties.ContractMultiplier / optionProperties.ContractMultiplier;
var quantityRatio = optionProperties.ContractUnitOfTrade;
// Some options are based on a fraction of their underlying security value, such as NQX for example. Thus,
// for them we need to scale the underlying value so that the later comparisons made with the option's strike
// value are correct
var priceRatio = (underlying.Close / option.SymbolProperties.StrikeMultiplier) / (absValue / quantityRatio);
var underlyingValueRatio = multiplierRatio * quantityRatio * priceRatio;
// calculating underlying security value less out-of-the-money amount
var amountOTM = option.OutOfTheMoneyAmount(underlying.Close);
var priceRatioOTM = amountOTM / (absValue / quantityRatio);
var underlyingValueRatioOTM = multiplierRatio * quantityRatio * priceRatioOTM;
var strikePriceRatio = option.StrikePrice / (absValue / quantityRatio);
strikePriceRatio = multiplierRatio * quantityRatio * strikePriceRatio;
var nakedMarginRequirement = option.Right == OptionRight.Call
? NakedPositionMarginRequirement * underlyingValueRatio
: NakedPositionMarginRequirement * strikePriceRatio;
var nakedMarginRequirementOtm = security.Type == SecurityType.Option
? EquityOptionNakedPositionMarginRequirementOtm
: IndexOptionNakedPositionMarginRequirementOtm;
return OptionMarginRequirement +
Math.Abs(quantity) * Math.Max(nakedMarginRequirement,
nakedMarginRequirementOtm * underlyingValueRatio - underlyingValueRatioOTM);
}
}
}
@@ -0,0 +1,89 @@
/*
* 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;
namespace QuantConnect.Securities.Option
{
/// <summary>
/// Provides an implementation of <see cref="ISecurityPortfolioModel"/> for options that supports
/// default fills as well as option exercising.
/// </summary>
public class OptionPortfolioModel : SecurityPortfolioModel
{
/// <summary>
/// Performs application of an OrderEvent to the portfolio
/// </summary>
/// <param name="portfolio">The algorithm's portfolio</param>
/// <param name="security">Option security</param>
/// <param name="fill">The order event fill object to be applied</param>
public override void ProcessFill(SecurityPortfolioManager portfolio, Security security, OrderEvent fill)
{
if (fill.Ticket.OrderType == OrderType.OptionExercise)
{
base.ProcessFill(portfolio, portfolio.Securities[fill.Symbol], fill);
// Update the order event message with the P&L
UpdateExerciseOrderEventMessage(security, fill);
}
else
{
// we delegate the call to the base class (default behavior)
base.ProcessFill(portfolio, security, fill);
}
}
private static void UpdateExerciseOrderEventMessage(Security security, OrderEvent fill)
{
var lastTradeProfit = security.Holdings.LastTradeProfit;
var message = "";
if (lastTradeProfit >= 0)
{
message += $". Profit: +{lastTradeProfit.ToStringInvariant()}";
}
else
{
message += $". Loss: {lastTradeProfit.ToStringInvariant()}";
}
fill.Message = fill.Message + message;
}
/// <summary>
/// Helper method to determine the close trade profit
/// </summary>
/// <remarks>For SettlementType.Cash we apply funds and add in the result to the profit</remarks>
protected override ConvertibleCashAmount ProcessCloseTradeProfit(SecurityPortfolioManager portfolio, Security security, OrderEvent fill)
{
var baseResult = base.ProcessCloseTradeProfit(portfolio, security, fill);
var ticket = fill.Ticket;
if (ticket.OrderType == OrderType.OptionExercise && security.Symbol.SecurityType.IsOption())
{
var option = (Option)security;
if (option.ExerciseSettlement == SettlementType.Cash)
{
var underlying = option.Underlying;
var optionQuantity = fill.Ticket.Quantity;
var cashQuantity = -option.GetIntrinsicValue(underlying.Close) * option.ContractUnitOfTrade * optionQuantity;
if (cashQuantity != decimal.Zero)
{
security.SettlementModel.ApplyFunds(new ApplyFundsSettlementModelParameters(portfolio, security, fill.UtcTime, new CashAmount(cashQuantity, option.QuoteCurrency.Symbol), fill));
return new ConvertibleCashAmount(cashQuantity + baseResult.Amount, option.QuoteCurrency);
}
}
}
return baseResult;
}
}
}
@@ -0,0 +1,51 @@
/*
* 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.Data;
using QuantConnect.Data.Market;
namespace QuantConnect.Securities.Option
{
/// <summary>
/// Base class for option price models, computing theoretical price, IV, and Greeks.
/// </summary>
public abstract class OptionPriceModel : IOptionPriceModel
{
/// <summary>
/// Evaluates the specified option contract to compute a theoretical price, IV and greeks
/// </summary>
/// <param name="parameters">A <see cref="OptionPriceModelParameters"/> object
/// containing the security, slice and contract</param>
/// <returns>An instance of <see cref="OptionPriceModelResult"/> containing the theoretical
/// price of the specified option contract</returns>
public abstract OptionPriceModelResult Evaluate(OptionPriceModelParameters parameters);
/// <summary>
/// Evaluates the specified option contract to compute a theoretical price, IV and greeks
/// </summary>
/// <param name="security">The option security object</param>
/// <param name="slice">The current data slice. This can be used to access other information
/// available to the algorithm</param>
/// <param name="contract">The option contract to evaluate</param>
/// <returns>An instance of <see cref="OptionPriceModelResult"/> containing the theoretical
/// price of the specified option contract</returns>
[Obsolete("This method is deprecated. Use Evaluate(OptionPriceModelParameters parameters) instead.")]
public virtual OptionPriceModelResult Evaluate(Security security, Slice slice, OptionContract contract)
{
return Evaluate(new OptionPriceModelParameters(security, slice, contract));
}
}
}
@@ -0,0 +1,54 @@
/*
* 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.Data;
using QuantConnect.Data.Market;
namespace QuantConnect.Securities.Option
{
/// <summary>
/// Defines the parameters for <see cref="IOptionPriceModel.Evaluate"/>
/// </summary>
public class OptionPriceModelParameters
{
/// <summary>
/// Gets the option security object
/// </summary>
public Security Security { get; set; }
/// <summary>
/// Gets the current data slice
/// </summary>
public Slice Slice { get; set; }
/// <summary>
/// Gets the option contract to evaluate
/// </summary>
public OptionContract Contract { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="OptionPriceModelParameters"/> class
/// </summary>
/// <param name="security">The option security object</param>
/// <param name="slice">The current data slice</param>
/// <param name="contract">The option contract to evaluate</param>
public OptionPriceModelParameters(Security security = null, Slice slice = null, OptionContract contract = null)
{
Security = security;
Slice = slice;
Contract = contract;
}
}
}
@@ -0,0 +1,144 @@
/*
* 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 Python.Runtime;
using QuantConnect.Data.Market;
using System;
namespace QuantConnect.Securities.Option
{
/// <summary>
/// Result type for <see cref="IOptionPriceModel.Evaluate"/>
/// </summary>
public class OptionPriceModelResult
{
/// <summary>
/// Represents the zero option price and greeks.
/// </summary>
public static OptionPriceModelResult None { get; } = new(0, NullGreeks.Instance);
private Lazy<decimal> _theoreticalPrice;
private Lazy<Greeks> _greeks;
private Lazy<decimal> _impliedVolatility;
/// <summary>
/// Gets the theoretical price as computed by the <see cref="IOptionPriceModel"/>
/// </summary>
public decimal TheoreticalPrice
{
get
{
return _theoreticalPrice.Value;
}
set
{
_theoreticalPrice = new Lazy<decimal>(() => value, isThreadSafe: false);
}
}
/// <summary>
/// Gets the implied volatility of the option contract
/// </summary>
public decimal ImpliedVolatility
{
get
{
return _impliedVolatility.Value;
}
set
{
_impliedVolatility = new Lazy<decimal>(() => value, isThreadSafe: false);
}
}
/// <summary>
/// Gets the various sensitivities as computed by the <see cref="IOptionPriceModel"/>
/// </summary>
public Greeks Greeks
{
get
{
return _greeks.Value;
}
set
{
_greeks = new Lazy<Greeks>(() => value, isThreadSafe: false);
}
}
/// <summary>
/// Initializes a new instance of the <see cref="OptionPriceModelResult"/> class
/// </summary>
public OptionPriceModelResult()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="OptionPriceModelResult"/> class
/// </summary>
/// <param name="theoreticalPrice">The theoretical price computed by the price model</param>
/// <param name="greeks">The sensitivities (greeks) computed by the price model</param>
public OptionPriceModelResult(decimal theoreticalPrice, Greeks greeks)
: this(() => theoreticalPrice, () => decimal.Zero, () => greeks)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="OptionPriceModelResult"/> class
/// </summary>
/// <param name="theoreticalPrice">The theoretical price computed by the price model</param>
/// <param name="impliedVolatility">The calculated implied volatility</param>
/// <param name="greeks">The sensitivities (greeks) computed by the price model</param>
public OptionPriceModelResult(decimal theoreticalPrice, decimal impliedVolatility, Greeks greeks)
: this(theoreticalPrice, () => impliedVolatility, () => greeks)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="OptionPriceModelResult"/> class with lazy calculations of implied volatility and greeks
/// </summary>
/// <param name="theoreticalPrice">The theoretical price computed by the price model</param>
/// <param name="impliedVolatility">The calculated implied volatility</param>
/// <param name="greeks">The sensitivities (greeks) computed by the price model</param>
public OptionPriceModelResult(decimal theoreticalPrice, Func<decimal> impliedVolatility, Func<Greeks> greeks)
: this(() => theoreticalPrice, impliedVolatility, greeks)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="OptionPriceModelResult"/> class with lazy calculations of implied volatility and greeks
/// </summary>
/// <param name="theoreticalPrice">The theoretical price computed by the price model</param>
/// <param name="impliedVolatility">The calculated implied volatility</param>
/// <param name="greeks">The sensitivities (greeks) computed by the price model</param>
public OptionPriceModelResult(Func<decimal> theoreticalPrice, Func<decimal> impliedVolatility, Func<Greeks> greeks)
{
_theoreticalPrice = new Lazy<decimal>(theoreticalPrice, isThreadSafe: false);
_impliedVolatility = new Lazy<decimal>(impliedVolatility, isThreadSafe: false);
_greeks = new Lazy<Greeks>(greeks, isThreadSafe: false);
}
/// <summary>
/// Initializes a new instance of the <see cref="OptionPriceModelResult"/> class with lazy calculations of implied volatility and greeks
/// </summary>
/// <param name="theoreticalPrice">The theoretical price computed by the price model</param>
/// <param name="impliedVolatility">The calculated implied volatility</param>
/// <param name="greeks">The sensitivities (greeks) computed by the price model</param>
public OptionPriceModelResult(decimal theoreticalPrice, PyObject impliedVolatility, PyObject greeks)
: this(theoreticalPrice, impliedVolatility.SafeAs<Func<decimal>>(), greeks.SafeAs<Func<Greeks>>())
{
}
}
}
@@ -0,0 +1,191 @@
/*
* 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 QLNet;
using System;
using System.Linq;
using Fasterflect;
namespace QuantConnect.Securities.Option
{
using PricingEngineFuncEx = Func<Symbol, GeneralizedBlackScholesProcess, IPricingEngine>;
public static partial class OptionPriceModels
{
/// <summary>
/// Static class contains definitions of major option pricing models that can be used in LEAN,
/// based on QuantLib implementations.
/// </summary>
/// <remarks>
/// To introduce particular model into algorithm add the following line to the algorithm's Initialize() method:
///
/// option.PriceModel = OptionPriceModels.QuantLib.BjerksundStensland(); // Option pricing model of choice
///
/// </remarks>
public static class QuantLib
{
private const int _timeStepsFD = 100;
/// <summary>
/// Creates pricing engine by engine type name.
/// </summary>
/// <param name="priceEngineName">QL price engine name</param>
/// <param name="riskFree">The risk free rate</param>
/// <param name="allowedOptionStyles">List of option styles supported by the pricing model. It defaults to both American and European option styles</param>
/// <returns>New option price model instance of specific engine</returns>
public static IOptionPriceModel Create(string priceEngineName, decimal riskFree, OptionStyle[] allowedOptionStyles = null)
{
var type = AppDomain.CurrentDomain.GetAssemblies()
.Where(a => !a.IsDynamic)
.SelectMany(a => a.GetTypes())
.Where(s => s.Implements(typeof(IPricingEngine)))
.FirstOrDefault(t => t.FullName?.EndsWith(priceEngineName, StringComparison.InvariantCulture) == true);
return new QLOptionPriceModel(process => (IPricingEngine)Activator.CreateInstance(type, process),
riskFreeRateEstimator: new ConstantQLRiskFreeRateEstimator(riskFree),
allowedOptionStyles: allowedOptionStyles);
}
/// <summary>
/// Pricing engine for European vanilla options using analytical formula.
/// QuantLib reference: http://quantlib.org/reference/class_quant_lib_1_1_analytic_european_engine.html
/// </summary>
/// <returns>New option price model instance</returns>
public static IOptionPriceModel BlackScholes()
{
return QLOptionPriceModelProvider.Instance.GetOptionPriceModel(Symbol.Empty, Indicators.OptionPricingModelType.BlackScholes);
}
/// <summary>
/// Barone-Adesi and Whaley pricing engine for American options (1987)
/// QuantLib reference: http://quantlib.org/reference/class_quant_lib_1_1_barone_adesi_whaley_approximation_engine.html
/// </summary>
/// <returns>New option price model instance</returns>
public static IOptionPriceModel BaroneAdesiWhaley()
{
return new QLOptionPriceModel(process => new BaroneAdesiWhaleyApproximationEngine(process),
allowedOptionStyles: new[] { OptionStyle.American });
}
/// <summary>
/// Bjerksund and Stensland pricing engine for American options (1993)
/// QuantLib reference: http://quantlib.org/reference/class_quant_lib_1_1_bjerksund_stensland_approximation_engine.html
/// </summary>
/// <returns>New option price model instance</returns>
public static IOptionPriceModel BjerksundStensland()
{
return new QLOptionPriceModel(process => new BjerksundStenslandApproximationEngine(process),
allowedOptionStyles: new[] { OptionStyle.American });
}
/// <summary>
/// Pricing engine for European vanilla options using integral approach.
/// QuantLib reference: http://quantlib.org/reference/class_quant_lib_1_1_integral_engine.html
/// </summary>
/// <returns>New option price model instance</returns>
public static IOptionPriceModel Integral()
{
return new QLOptionPriceModel(process => new IntegralEngine(process),
allowedOptionStyles: new[] { OptionStyle.European });
}
/// <summary>
/// Pricing engine for European and American options using finite-differences.
/// QuantLib reference: http://quantlib.org/reference/class_quant_lib_1_1_f_d_european_engine.html
/// </summary>
/// <returns>New option price model instance</returns>
public static IOptionPriceModel CrankNicolsonFD()
{
PricingEngineFuncEx pricingEngineFunc = (symbol, process) =>
symbol.ID.OptionStyle == OptionStyle.American
? new FDAmericanEngine(process, _timeStepsFD, _timeStepsFD - 1)
: new FDEuropeanEngine(process, _timeStepsFD, _timeStepsFD - 1);
return new QLOptionPriceModel(pricingEngineFunc);
}
/// <summary>
/// Pricing engine for European and American vanilla options using binomial trees. Jarrow-Rudd model.
/// QuantLib reference: http://quantlib.org/reference/class_quant_lib_1_1_f_d_european_engine.html
/// </summary>
/// <returns>New option price model instance</returns>
public static IOptionPriceModel BinomialJarrowRudd()
{
return new QLOptionPriceModel(process => new BinomialVanillaEngine<JarrowRudd>(process, QLOptionPriceModelProvider.TimeStepsBinomial));
}
/// <summary>
/// Pricing engine for European and American vanilla options using binomial trees. Cox-Ross-Rubinstein(CRR) model.
/// QuantLib reference: http://quantlib.org/reference/class_quant_lib_1_1_f_d_european_engine.html
/// </summary>
/// <returns>New option price model instance</returns>
public static IOptionPriceModel BinomialCoxRossRubinstein()
{
return QLOptionPriceModelProvider.Instance.GetOptionPriceModel(Symbol.Empty, Indicators.OptionPricingModelType.BinomialCoxRossRubinstein);
}
/// <summary>
/// Pricing engine for European and American vanilla options using binomial trees. Additive Equiprobabilities model.
/// QuantLib reference: http://quantlib.org/reference/class_quant_lib_1_1_f_d_european_engine.html
/// </summary>
/// <returns>New option price model instance</returns>
public static IOptionPriceModel AdditiveEquiprobabilities()
{
return new QLOptionPriceModel(process => new BinomialVanillaEngine<AdditiveEQPBinomialTree>(process, QLOptionPriceModelProvider.TimeStepsBinomial));
}
/// <summary>
/// Pricing engine for European and American vanilla options using binomial trees. Trigeorgis model.
/// QuantLib reference: http://quantlib.org/reference/class_quant_lib_1_1_f_d_european_engine.html
/// </summary>
/// <returns>New option price model instance</returns>
public static IOptionPriceModel BinomialTrigeorgis()
{
return new QLOptionPriceModel(process => new BinomialVanillaEngine<Trigeorgis>(process, QLOptionPriceModelProvider.TimeStepsBinomial));
}
/// <summary>
/// Pricing engine for European and American vanilla options using binomial trees. Tian model.
/// QuantLib reference: http://quantlib.org/reference/class_quant_lib_1_1_f_d_european_engine.html
/// </summary>
/// <returns>New option price model instance</returns>
public static IOptionPriceModel BinomialTian()
{
return new QLOptionPriceModel(process => new BinomialVanillaEngine<Tian>(process, QLOptionPriceModelProvider.TimeStepsBinomial));
}
/// <summary>
/// Pricing engine for European and American vanilla options using binomial trees. Leisen-Reimer model.
/// QuantLib reference: http://quantlib.org/reference/class_quant_lib_1_1_f_d_european_engine.html
/// </summary>
/// <returns>New option price model instance</returns>
public static IOptionPriceModel BinomialLeisenReimer()
{
return new QLOptionPriceModel(process => new BinomialVanillaEngine<LeisenReimer>(process, QLOptionPriceModelProvider.TimeStepsBinomial));
}
/// <summary>
/// Pricing engine for European and American vanilla options using binomial trees. Joshi model.
/// QuantLib reference: http://quantlib.org/reference/class_quant_lib_1_1_f_d_european_engine.html
/// </summary>
/// <returns>New option price model instance</returns>
public static IOptionPriceModel BinomialJoshi()
{
return new QLOptionPriceModel(process => new BinomialVanillaEngine<Joshi4>(process, QLOptionPriceModelProvider.TimeStepsBinomial));
}
}
}
}
@@ -0,0 +1,72 @@
/*
* 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.Option
{
/// <summary>
/// Static class contains definitions of major option pricing models that can be used in LEAN
/// </summary>
/// <remarks>
/// To introduce particular model into algorithm add the following line to the algorithm's Initialize() method:
///
/// option.PriceModel = OptionPriceModels.BlackScholes(); // Option pricing model of choice
///
/// </remarks>
public static partial class OptionPriceModels
{
/// <summary>
/// Default option price model provider used by LEAN when creating price models.
/// </summary>
internal static IOptionPriceModelProvider DefaultPriceModelProvider { get; set; }
/// <summary>
/// Null pricing engine that returns the current price as the option theoretical price.
/// It will also set the option Greeks and implied volatility to zero, effectively disabling the pricing.
/// </summary>
public static IOptionPriceModel Null()
{
return new CurrentPriceOptionPriceModel();
}
/// <summary>
/// Pricing engine for Black-Scholes model.
/// </summary>
/// <returns>New option price model instance</returns>
public static IOptionPriceModel BlackScholes()
{
return DefaultPriceModelProvider.GetOptionPriceModel(Symbol.Empty, Indicators.OptionPricingModelType.BlackScholes);
}
/// <summary>
/// Pricing engine for Cox-Ross-Rubinstein (CRR) model.
/// </summary>
/// <returns>New option price model instance</returns>
public static IOptionPriceModel BinomialCoxRossRubinstein()
{
return DefaultPriceModelProvider.GetOptionPriceModel(Symbol.Empty, Indicators.OptionPricingModelType.BinomialCoxRossRubinstein);
}
/// <summary>
/// Pricing engine for forward binomial tree model.
/// </summary>
/// <returns>New option price model instance</returns>
public static IOptionPriceModel ForwardTree()
{
return DefaultPriceModelProvider.GetOptionPriceModel(Symbol.Empty, Indicators.OptionPricingModelType.ForwardTree);
}
}
}
File diff suppressed because it is too large Load Diff
+226
View File
@@ -0,0 +1,226 @@
/*
* 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.Orders;
using System.Collections.Generic;
using System.Linq;
namespace QuantConnect.Securities.Option
{
/// <summary>
/// Option strategy specification class. Describes option strategy and its parameters for trading.
/// </summary>
public class OptionStrategy
{
/// <summary>
/// Option strategy name
/// </summary>
public string Name { get; set; }
/// <summary>
/// The canonical Option symbol of the strategy
/// </summary>
public Symbol CanonicalOption { get; set; }
/// <summary>
/// Underlying symbol of the strategy
/// </summary>
public Symbol Underlying { get; set; }
/// <summary>
/// Option strategy legs
/// </summary>
public List<OptionLegData> OptionLegs { get; set; }
/// <summary>
/// Option strategy underlying legs (usually 0 or 1 legs)
/// </summary>
public List<UnderlyingLegData> UnderlyingLegs { get; set; }
/// <summary>
/// Creates a new instance of <see cref="OptionStrategy"/> with the specified parameters
/// </summary>
/// <param name="name">The strategy name</param>
/// <param name="canonicalSymbol">The canonical option symbol</param>
/// <param name="optionLegs">The option legs data</param>
/// <param name="underlyingLegs">The underlying legs data</param>
public OptionStrategy(string name, Symbol canonicalSymbol, List<OptionLegData> optionLegs = null, List<UnderlyingLegData> underlyingLegs = null)
{
Name = name;
CanonicalOption = canonicalSymbol;
Underlying = canonicalSymbol.Underlying;
OptionLegs = optionLegs ?? new List<OptionLegData>();
UnderlyingLegs = underlyingLegs ?? new List<UnderlyingLegData>();
SetSymbols();
}
/// <summary>
/// Creates a new instance of <see cref="OptionStrategy"/> with default parameters
/// </summary>
public OptionStrategy()
{
OptionLegs = new List<OptionLegData>();
UnderlyingLegs = new List<UnderlyingLegData>();
}
/// <summary>
/// Sets the option legs symbols based on the canonical symbol and the leg data.
/// If the canonical symbol is not set, it will be created using the underlying symbol.
/// </summary>
public void SetSymbols()
{
if (CanonicalOption == null)
{
if (Underlying == null)
{
// Let's be polite and try to get the underlying symbol from the underlying legs as a last resort
var underlyingLeg = UnderlyingLegs.Count > 0 ? UnderlyingLegs[0] : null;
if (underlyingLeg == null || underlyingLeg.Symbol == null)
{
return;
}
Underlying = underlyingLeg.Symbol;
}
CanonicalOption = Symbol.CreateCanonicalOption(Underlying);
}
foreach (var optionLeg in OptionLegs.Where(leg => leg.Symbol == null))
{
var targetOption = CanonicalOption.ID.Symbol;
optionLeg.Symbol = Symbol.CreateOption(Underlying, targetOption, Underlying.ID.Market, CanonicalOption.ID.OptionStyle,
optionLeg.Right, optionLeg.Strike, optionLeg.Expiration);
}
}
/// <summary>
/// Creates a new instance of <see cref="OptionStrategy"/> with the specified name and legs data.
/// The method will try to infer the canonical symbol and underlying symbol from the legs data, but they can also be set manually after the strategy creation.
/// </summary>
public static OptionStrategy Create(string name, IEnumerable<Leg> legs)
{
var underlyingLegs = new List<UnderlyingLegData>();
var optionLegs = new List<OptionLegData>();
Symbol canonicalSymbol = null;
foreach (var leg in legs)
{
if (leg is UnderlyingLegData underlyingLeg)
{
underlyingLegs.Add(underlyingLeg);
}
else if (leg is OptionLegData optionLeg)
{
optionLegs.Add(optionLeg);
if (canonicalSymbol == null)
{
canonicalSymbol = optionLeg.Symbol.Canonical;
}
}
else
{
throw new ArgumentException($"Invalid leg type: {leg.GetType().FullName}");
}
}
return new OptionStrategy(name, canonicalSymbol, optionLegs, underlyingLegs);
}
/// <summary>
/// This class is a POCO containing basic data for the option legs of the strategy
/// </summary>
public class OptionLegData : Leg
{
/// <summary>
/// Option right (type) of the option leg
/// </summary>
public OptionRight Right { get; set; }
/// <summary>
/// Expiration date of the leg
/// </summary>
public DateTime Expiration { get; set; }
/// <summary>
/// Strike price of the leg
/// </summary>
public decimal Strike { get; set; }
/// <summary>
/// Creates a new instance of <see cref="OptionLegData"/> from the specified parameters
/// </summary>
public static OptionLegData Create(int quantity, Symbol symbol, decimal? orderPrice = null)
{
return new OptionLegData
{
Symbol = symbol,
Quantity = quantity,
Expiration = symbol.ID.Date,
OrderPrice = orderPrice,
Right = symbol.ID.OptionRight,
Strike = symbol.ID.StrikePrice
};
}
/// <summary>
/// Returns a string that represents the option leg
/// </summary>
public override string ToString()
{
return $"Leg: {Quantity}. Right: {Right}. Strike: {Strike}. Expiration: {Expiration:yyyyMMdd}";
}
}
/// <summary>
/// This class is a POCO containing basic data for the underlying leg of the strategy
/// </summary>
public class UnderlyingLegData : Leg
{
/// <summary>
/// Creates a new instance of <see cref="UnderlyingLegData"/> for the specified <paramref name="quantity"/> of underlying shares.
/// </summary>
public static UnderlyingLegData Create(int quantity, Symbol symbol, decimal? orderPrice = null)
{
var data = Create(quantity, orderPrice);
data.Symbol = symbol;
return data;
}
/// <summary>
/// Creates a new instance of <see cref="UnderlyingLegData"/> for the specified <paramref name="quantity"/> of underlying shares.
/// </summary>
public static UnderlyingLegData Create(int quantity, decimal? orderPrice = null)
{
return new UnderlyingLegData
{
Quantity = quantity,
OrderPrice = orderPrice
};
}
/// <summary>
/// Returns a string that represents the underlying leg.
/// </summary>
public override string ToString()
{
return Symbol != null ? $"Leg: {Quantity}. {Symbol}" : string.Empty;
}
}
}
}
@@ -0,0 +1,659 @@
/*
* 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.Linq;
using QuantConnect.Orders.Fees;
using QuantConnect.Securities.Positions;
using QuantConnect.Securities.Option.StrategyMatcher;
using System.Collections.Generic;
using QuantConnect.Orders;
namespace QuantConnect.Securities.Option
{
/// <summary>
/// Option strategy buying power model
/// </summary>
/// <remarks>
/// Reference used https://www.interactivebrokers.com/en/index.php?f=26660
/// </remarks>
public class OptionStrategyPositionGroupBuyingPowerModel : PositionGroupBuyingPowerModel
{
private readonly OptionStrategy _optionStrategy;
/// <summary>
/// Creates a new instance for a target option strategy
/// </summary>
/// <param name="optionStrategy">The option strategy to model</param>
public OptionStrategyPositionGroupBuyingPowerModel(OptionStrategy optionStrategy)
{
_optionStrategy = optionStrategy;
}
/// <summary>
/// Gets the margin currently allocated to the specified holding
/// </summary>
/// <param name="parameters">An object containing the security</param>
/// <returns>The maintenance margin required for the </returns>
public override MaintenanceMargin GetMaintenanceMargin(PositionGroupMaintenanceMarginParameters parameters)
{
if (_optionStrategy == null)
{
// we could be liquidating a position
return new MaintenanceMargin(0);
}
else if (_optionStrategy.Name == OptionStrategyDefinitions.ProtectivePut.Name || _optionStrategy.Name == OptionStrategyDefinitions.ProtectiveCall.Name)
{
// Minimum (((10% * Call/Put Strike Price) + Call/Put Out of the Money Amount), Short Stock/Long Maintenance Requirement)
var optionPosition = parameters.PositionGroup.Positions.FirstOrDefault(position => position.Symbol.SecurityType.IsOption());
var underlyingPosition = parameters.PositionGroup.Positions.FirstOrDefault(position => !position.Symbol.SecurityType.IsOption());
var optionSecurity = (Option)parameters.Portfolio.Securities[optionPosition.Symbol];
var underlyingSecurity = parameters.Portfolio.Securities[underlyingPosition.Symbol];
var absOptionQuantity = Math.Abs(optionPosition.Quantity);
var outOfTheMoneyAmount = optionSecurity.OutOfTheMoneyAmount(underlyingSecurity.Price) * optionSecurity.ContractUnitOfTrade * absOptionQuantity;
var underlyingMarginRequired = Math.Abs(underlyingSecurity.BuyingPowerModel.GetMaintenanceMargin(MaintenanceMarginParameters.ForQuantityAtCurrentPrice(
underlyingSecurity, underlyingPosition.Quantity)));
var result = Math.Min(0.1m * optionSecurity.StrikePrice * optionSecurity.ContractUnitOfTrade * absOptionQuantity + outOfTheMoneyAmount, underlyingMarginRequired);
var inAccountCurrency = parameters.Portfolio.CashBook.ConvertToAccountCurrency(result, optionSecurity.QuoteCurrency.Symbol);
return new MaintenanceMargin(inAccountCurrency);
}
else if (_optionStrategy.Name == OptionStrategyDefinitions.CoveredCall.Name)
{
// MAX[In-the-money amount + Margin(long stock evaluated at min(mark price, strike(short call))), min(stock value, max(call value, long stock margin))]
var optionPosition = parameters.PositionGroup.Positions.FirstOrDefault(position => position.Symbol.SecurityType.IsOption());
var underlyingPosition = parameters.PositionGroup.Positions.FirstOrDefault(position => !position.Symbol.SecurityType.IsOption());
var optionSecurity = (Option)parameters.Portfolio.Securities[optionPosition.Symbol];
var underlyingSecurity = parameters.Portfolio.Securities[underlyingPosition.Symbol];
var intrinsicValue = optionSecurity.GetIntrinsicValue(underlyingSecurity.Price);
var inTheMoneyAmount = intrinsicValue * optionSecurity.ContractUnitOfTrade * Math.Abs(optionPosition.Quantity);
var underlyingValue = underlyingSecurity.Holdings.GetQuantityValue(underlyingPosition.Quantity).InAccountCurrency;
var optionValue = optionSecurity.Holdings.GetQuantityValue(optionPosition.Quantity).InAccountCurrency;
// mark price, strike price
var underlyingPriceToEvaluate = Math.Min(underlyingSecurity.Price, optionSecurity.ScaledStrikePrice);
var underlyingHypotheticalValue = underlyingSecurity.Holdings.GetQuantityValue(underlyingPosition.Quantity, underlyingPriceToEvaluate).InAccountCurrency;
var hypotheticalMarginRequired = underlyingSecurity.BuyingPowerModel.GetMaintenanceMargin(
new MaintenanceMarginParameters(underlyingSecurity, underlyingPosition.Quantity, 0, underlyingHypotheticalValue));
var marginRequired = underlyingSecurity.BuyingPowerModel.GetMaintenanceMargin(
new MaintenanceMarginParameters(underlyingSecurity, underlyingPosition.Quantity, 0, underlyingValue));
var secondOperand = Math.Min(underlyingValue, Math.Max(optionValue, marginRequired));
var result = Math.Max(inTheMoneyAmount + hypotheticalMarginRequired, secondOperand);
var inAccountCurrency = parameters.Portfolio.CashBook.ConvertToAccountCurrency(result, optionSecurity.QuoteCurrency.Symbol);
return new MaintenanceMargin(inAccountCurrency);
}
else if (_optionStrategy.Name == OptionStrategyDefinitions.CoveredPut.Name)
{
// Initial Stock Margin Requirement + In the Money Amount
var optionPosition = parameters.PositionGroup.Positions.FirstOrDefault(position => position.Symbol.SecurityType.IsOption());
var underlyingPosition = parameters.PositionGroup.Positions.FirstOrDefault(position => !position.Symbol.SecurityType.IsOption());
var optionSecurity = (Option)parameters.Portfolio.Securities[optionPosition.Symbol];
var underlyingSecurity = parameters.Portfolio.Securities[underlyingPosition.Symbol];
var intrinsicValue = optionSecurity.GetIntrinsicValue(underlyingSecurity.Price);
var inTheMoneyAmount = intrinsicValue * optionSecurity.ContractUnitOfTrade * Math.Abs(optionPosition.Quantity);
var initialMarginRequirement = underlyingSecurity.BuyingPowerModel.GetInitialMarginRequirement(underlyingSecurity, underlyingPosition.Quantity);
var result = Math.Abs(initialMarginRequirement) + inTheMoneyAmount;
var inAccountCurrency = parameters.Portfolio.CashBook.ConvertToAccountCurrency(result, optionSecurity.QuoteCurrency.Symbol);
return new MaintenanceMargin(inAccountCurrency);
}
else if (_optionStrategy.Name == OptionStrategyDefinitions.ProtectiveCollar.Name)
{
// Minimum (((10% * Put Strike Price) + Put Out of the Money Amount), (25% * Call Strike Price))
var putPosition = parameters.PositionGroup.Positions.Single(position =>
position.Symbol.SecurityType.IsOption() && position.Symbol.ID.OptionRight == OptionRight.Put);
var callPosition = parameters.PositionGroup.Positions.Single(position =>
position.Symbol.SecurityType.IsOption() && position.Symbol.ID.OptionRight == OptionRight.Call);
var underlyingPosition = parameters.PositionGroup.Positions.FirstOrDefault(position => !position.Symbol.SecurityType.IsOption());
var putSecurity = (Option)parameters.Portfolio.Securities[putPosition.Symbol];
var callSecurity = (Option)parameters.Portfolio.Securities[callPosition.Symbol];
var underlyingSecurity = parameters.Portfolio.Securities[underlyingPosition.Symbol];
var putMarginRequirement = 0.1m * putSecurity.StrikePrice + putSecurity.OutOfTheMoneyAmount(underlyingSecurity.Price);
var callMarginRequirement = 0.25m * callSecurity.StrikePrice;
// call and put has the exact same number of contracts
var contractUnits = Math.Abs(putPosition.Quantity) * putSecurity.ContractUnitOfTrade;
var result = Math.Min(putMarginRequirement, callMarginRequirement) * contractUnits;
var inAccountCurrency = parameters.Portfolio.CashBook.ConvertToAccountCurrency(result, underlyingSecurity.QuoteCurrency.Symbol);
return new MaintenanceMargin(inAccountCurrency);
}
else if (_optionStrategy.Name == OptionStrategyDefinitions.Conversion.Name)
{
return GetConversionMaintenanceMargin(parameters.PositionGroup, parameters.Portfolio, OptionRight.Call);
}
else if (_optionStrategy.Name == OptionStrategyDefinitions.ReverseConversion.Name)
{
return GetConversionMaintenanceMargin(parameters.PositionGroup, parameters.Portfolio, OptionRight.Put);
}
else if (_optionStrategy.Name == OptionStrategyDefinitions.NakedCall.Name
|| _optionStrategy.Name == OptionStrategyDefinitions.NakedPut.Name)
{
var option = parameters.PositionGroup.Positions.Single();
var security = (Option)parameters.Portfolio.Securities[option.Symbol];
var margin = security.BuyingPowerModel.GetMaintenanceMargin(MaintenanceMarginParameters.ForQuantityAtCurrentPrice(security,
option.Quantity));
return new MaintenanceMargin(margin);
}
else if (_optionStrategy.Name == OptionStrategyDefinitions.BearCallSpread.Name
|| _optionStrategy.Name == OptionStrategyDefinitions.BullCallSpread.Name)
{
var result = GetLongCallShortCallStrikeDifferenceMargin(parameters.PositionGroup.Positions, parameters.Portfolio, parameters.PositionGroup.Quantity);
return new MaintenanceMargin(result);
}
else if (_optionStrategy.Name == OptionStrategyDefinitions.CallCalendarSpread.Name
|| _optionStrategy.Name == OptionStrategyDefinitions.PutCalendarSpread.Name)
{
return new MaintenanceMargin(0);
}
else if (_optionStrategy.Name == OptionStrategyDefinitions.ShortCallCalendarSpread.Name
|| _optionStrategy.Name == OptionStrategyDefinitions.ShortPutCalendarSpread.Name)
{
var shortCall = parameters.PositionGroup.Positions.Single(position => position.Quantity < 0);
var shortCallSecurity = (Option)parameters.Portfolio.Securities[shortCall.Symbol];
var result = shortCallSecurity.BuyingPowerModel.GetMaintenanceMargin(MaintenanceMarginParameters.ForQuantityAtCurrentPrice(
shortCallSecurity, shortCall.Quantity));
return new MaintenanceMargin(result);
}
else if (_optionStrategy.Name == OptionStrategyDefinitions.BearPutSpread.Name
|| _optionStrategy.Name == OptionStrategyDefinitions.BullPutSpread.Name)
{
var result = GetShortPutLongPutStrikeDifferenceMargin(parameters.PositionGroup.Positions, parameters.Portfolio, parameters.PositionGroup.Quantity);
return new MaintenanceMargin(result);
}
else if (_optionStrategy.Name == OptionStrategyDefinitions.Straddle.Name || _optionStrategy.Name == OptionStrategyDefinitions.Strangle.Name)
{
// Margined as two long options: since there is not margin requirements for long options, we return 0
return new MaintenanceMargin(0);
}
else if (_optionStrategy.Name == OptionStrategyDefinitions.ShortStraddle.Name || _optionStrategy.Name == OptionStrategyDefinitions.ShortStrangle.Name)
{
var result = GetShortStraddleStrangleMargin(parameters.PositionGroup, parameters.Portfolio,
(option, quantity) => Math.Abs(option.BuyingPowerModel.GetMaintenanceMargin(
MaintenanceMarginParameters.ForQuantityAtCurrentPrice(option, quantity))));
return new MaintenanceMargin(result);
}
else if (_optionStrategy.Name == OptionStrategyDefinitions.ButterflyCall.Name || _optionStrategy.Name == OptionStrategyDefinitions.ButterflyPut.Name)
{
return new MaintenanceMargin(0);
}
else if (_optionStrategy.Name == OptionStrategyDefinitions.ShortButterflyPut.Name || _optionStrategy.Name == OptionStrategyDefinitions.ShortButterflyCall.Name)
{
var result = GetMiddleAndLowStrikeDifference(parameters.PositionGroup, parameters.Portfolio);
return new MaintenanceMargin(result);
}
else if (_optionStrategy.Name == OptionStrategyDefinitions.IronCondor.Name || _optionStrategy.Name == OptionStrategyDefinitions.IronButterfly.Name ||
_optionStrategy.Name == OptionStrategyDefinitions.ShortIronCondor.Name || _optionStrategy.Name == OptionStrategyDefinitions.ShortIronButterfly.Name)
{
var result = GetShortPutLongPutStrikeDifferenceMargin(parameters.PositionGroup.Positions, parameters.Portfolio, parameters.PositionGroup.Quantity);
return new MaintenanceMargin(result);
}
else if (_optionStrategy.Name == OptionStrategyDefinitions.BoxSpread.Name)
{
return new MaintenanceMargin(0);
}
else if (_optionStrategy.Name == OptionStrategyDefinitions.ShortBoxSpread.Name)
{
// MAX(1.02 x cost to close, Long Call Strike Short Call Strike)
var longCallPosition = parameters.PositionGroup.Positions.Single(
position => position.Quantity > 0 && position.Symbol.ID.OptionRight == OptionRight.Call);
var shortCallPosition = parameters.PositionGroup.Positions.Single(
position => position.Quantity < 0 && position.Symbol.ID.OptionRight == OptionRight.Call);
var longPutPosition = parameters.PositionGroup.Positions.Single(
position => position.Quantity > 0 && position.Symbol.ID.OptionRight == OptionRight.Put);
var shortPutPosition = parameters.PositionGroup.Positions.Single(
position => position.Quantity < 0 && position.Symbol.ID.OptionRight == OptionRight.Put);
var longCallSecurity = (Option)parameters.Portfolio.Securities[longCallPosition.Symbol];
var shortCallSecurity = (Option)parameters.Portfolio.Securities[shortCallPosition.Symbol];
var longPutSecurity = (Option)parameters.Portfolio.Securities[longPutPosition.Symbol];
var shortPutSecurity = (Option)parameters.Portfolio.Securities[shortPutPosition.Symbol];
// commission cost: MAX($1, $0.65/contract * quantity) + bid/ask price
var commissionFees = Math.Max(Math.Abs(longCallPosition.Quantity) * 0.65m, 1m) * 4m; // 4 contracts in total
var orderCosts = shortCallSecurity.AskPrice - longCallSecurity.BidPrice + shortPutSecurity.AskPrice - longPutSecurity.BidPrice;
var multiplier = Math.Abs(longCallPosition.Quantity) * longCallSecurity.ContractUnitOfTrade;
var closeCost = commissionFees + orderCosts * multiplier;
var strikeDifference = longCallPosition.Symbol.ID.StrikePrice - shortCallPosition.Symbol.ID.StrikePrice;
var result = Math.Max(1.02m * closeCost, strikeDifference * multiplier);
var inAccountCurrency = parameters.Portfolio.CashBook.ConvertToAccountCurrency(result, longCallSecurity.QuoteCurrency.Symbol);
return new MaintenanceMargin(inAccountCurrency);
}
else if (_optionStrategy.Name == OptionStrategyDefinitions.JellyRoll.Name
|| _optionStrategy.Name == OptionStrategyDefinitions.ShortJellyRoll.Name)
{
// long calendar spread part has no margin requirement due to same strike
// only the short calendar spread's short option has margin requirement
var furtherExpiry = parameters.PositionGroup.Positions.Max(position => position.Symbol.ID.Date);
var shortCalendarSpreadShortLeg = parameters.PositionGroup.Positions.Single(position =>
position.Quantity < 0 && position.Symbol.ID.Date == furtherExpiry);
var shortCalendarSpreadShortLegSecurity = (Option)parameters.Portfolio.Securities[shortCalendarSpreadShortLeg.Symbol];
var result = Math.Abs(shortCalendarSpreadShortLegSecurity.BuyingPowerModel.GetMaintenanceMargin(
MaintenanceMarginParameters.ForQuantityAtCurrentPrice(shortCalendarSpreadShortLegSecurity, shortCalendarSpreadShortLeg.Quantity)));
return new MaintenanceMargin(result);
}
else if (_optionStrategy.Name == OptionStrategyDefinitions.BearCallLadder.Name)
{
return GetCallLadderMargin(parameters, true);
}
else if (_optionStrategy.Name == OptionStrategyDefinitions.BearPutLadder.Name)
{
return GetPutLadderMargin(parameters, false);
}
else if (_optionStrategy.Name == OptionStrategyDefinitions.BullCallLadder.Name)
{
return GetCallLadderMargin(parameters, false);
}
else if (_optionStrategy.Name == OptionStrategyDefinitions.BullPutLadder.Name)
{
return GetPutLadderMargin(parameters, true);
}
throw new NotImplementedException($"Option strategy {_optionStrategy.Name} margin modeling has yet to be implemented");
}
/// <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</param>
public override InitialMargin GetInitialMarginRequirement(PositionGroupInitialMarginParameters parameters)
{
var result = 0m;
if (_optionStrategy == null)
{
result = 0;
}
else if (_optionStrategy.Name == OptionStrategyDefinitions.ProtectivePut.Name || _optionStrategy.Name == OptionStrategyDefinitions.ProtectiveCall.Name)
{
// Initial Standard Stock Margin Requirement
var underlyingPosition = parameters.PositionGroup.Positions.FirstOrDefault(position => !position.Symbol.SecurityType.IsOption());
var underlyingSecurity = parameters.Portfolio.Securities[underlyingPosition.Symbol];
result = Math.Abs(underlyingSecurity.BuyingPowerModel.GetInitialMarginRequirement(underlyingSecurity, underlyingPosition.Quantity));
result = parameters.Portfolio.CashBook.ConvertToAccountCurrency(result, underlyingSecurity.QuoteCurrency.Symbol);
}
else if (_optionStrategy.Name == OptionStrategyDefinitions.CoveredCall.Name)
{
// Max(Call Value, Long Stock Initial Margin)
var optionPosition = parameters.PositionGroup.Positions.FirstOrDefault(position => position.Symbol.SecurityType.IsOption());
var underlyingPosition = parameters.PositionGroup.Positions.FirstOrDefault(position => !position.Symbol.SecurityType.IsOption());
var optionSecurity = (Option)parameters.Portfolio.Securities[optionPosition.Symbol];
var underlyingSecurity = parameters.Portfolio.Securities[underlyingPosition.Symbol];
var optionValue = Math.Abs(optionSecurity.Holdings.GetQuantityValue(optionPosition.Quantity).InAccountCurrency);
var marginRequired = underlyingSecurity.BuyingPowerModel.GetInitialMarginRequirement(underlyingSecurity, underlyingPosition.Quantity);
// IB charges more than expected, this formula was inferred based on actual requirements see 'CoveredCallInitialMarginRequirementsTestCases'
result = optionValue * 0.8m + marginRequired;
result = parameters.Portfolio.CashBook.ConvertToAccountCurrency(result, optionSecurity.QuoteCurrency.Symbol);
}
else if (_optionStrategy.Name == OptionStrategyDefinitions.CoveredPut.Name)
{
// Initial Stock Margin Requirement + In the Money Amount
result = GetMaintenanceMargin(new PositionGroupMaintenanceMarginParameters(parameters.Portfolio, parameters.PositionGroup));
}
else if (_optionStrategy.Name == OptionStrategyDefinitions.ProtectiveCollar.Name || _optionStrategy.Name == OptionStrategyDefinitions.Conversion.Name)
{
result = GetCollarConversionInitialMargin(parameters.PositionGroup, parameters.Portfolio, OptionRight.Call);
}
else if (_optionStrategy.Name == OptionStrategyDefinitions.ReverseConversion.Name)
{
result = GetCollarConversionInitialMargin(parameters.PositionGroup, parameters.Portfolio, OptionRight.Put);
}
else if (_optionStrategy.Name == OptionStrategyDefinitions.NakedCall.Name
|| _optionStrategy.Name == OptionStrategyDefinitions.NakedPut.Name)
{
var option = parameters.PositionGroup.Positions.Single();
var security = (Option)parameters.Portfolio.Securities[option.Symbol];
var margin = security.BuyingPowerModel.GetInitialMarginRequirement(new InitialMarginParameters(security, option.Quantity));
var optionMargin = margin as OptionInitialMargin;
if (optionMargin != null)
{
return new OptionInitialMargin(Math.Abs(optionMargin.ValueWithoutPremium), optionMargin.Premium);
}
return margin;
}
else if (_optionStrategy.Name == OptionStrategyDefinitions.BearCallSpread.Name
|| _optionStrategy.Name == OptionStrategyDefinitions.BullCallSpread.Name)
{
result = GetLongCallShortCallStrikeDifferenceMargin(parameters.PositionGroup.Positions, parameters.Portfolio, parameters.PositionGroup.Quantity);
}
else if (_optionStrategy.Name == OptionStrategyDefinitions.CallCalendarSpread.Name
|| _optionStrategy.Name == OptionStrategyDefinitions.PutCalendarSpread.Name)
{
result = 0m;
}
else if (_optionStrategy.Name == OptionStrategyDefinitions.ShortCallCalendarSpread.Name
|| _optionStrategy.Name == OptionStrategyDefinitions.ShortPutCalendarSpread.Name)
{
var shortOptionPosition = parameters.PositionGroup.Positions.Single(position => position.Quantity < 0);
var shortOption = (Option)parameters.Portfolio.Securities[shortOptionPosition.Symbol];
result = Math.Abs(shortOption.BuyingPowerModel.GetInitialMarginRequirement(shortOption, shortOptionPosition.Quantity));
}
else if (_optionStrategy.Name == OptionStrategyDefinitions.BearPutSpread.Name
|| _optionStrategy.Name == OptionStrategyDefinitions.BullPutSpread.Name)
{
result = GetShortPutLongPutStrikeDifferenceMargin(parameters.PositionGroup.Positions, parameters.Portfolio, parameters.PositionGroup.Quantity);
}
else if (_optionStrategy.Name == OptionStrategyDefinitions.Straddle.Name || _optionStrategy.Name == OptionStrategyDefinitions.Strangle.Name)
{
// Margined as two long options: since there is not margin requirements for long options, we return 0
result = 0m;
}
else if (_optionStrategy.Name == OptionStrategyDefinitions.ShortStraddle.Name || _optionStrategy.Name == OptionStrategyDefinitions.ShortStrangle.Name)
{
result = GetShortStraddleStrangleMargin(parameters.PositionGroup, parameters.Portfolio,
(option, quantity) => Math.Abs(option.BuyingPowerModel.GetInitialMarginRequirement(option, quantity)));
}
else if (_optionStrategy.Name == OptionStrategyDefinitions.ButterflyCall.Name || _optionStrategy.Name == OptionStrategyDefinitions.ButterflyPut.Name)
{
result = 0m;
}
else if (_optionStrategy.Name == OptionStrategyDefinitions.ShortButterflyPut.Name || _optionStrategy.Name == OptionStrategyDefinitions.ShortButterflyCall.Name)
{
result = GetMiddleAndLowStrikeDifference(parameters.PositionGroup, parameters.Portfolio);
}
else if (_optionStrategy.Name == OptionStrategyDefinitions.IronCondor.Name || _optionStrategy.Name == OptionStrategyDefinitions.IronButterfly.Name ||
_optionStrategy.Name == OptionStrategyDefinitions.ShortIronCondor.Name || _optionStrategy.Name == OptionStrategyDefinitions.ShortIronButterfly.Name)
{
result = GetShortPutLongPutStrikeDifferenceMargin(parameters.PositionGroup.Positions, parameters.Portfolio, parameters.PositionGroup.Quantity);
}
else if (_optionStrategy.Name == OptionStrategyDefinitions.BoxSpread.Name)
{
result = 0m;
}
else if (_optionStrategy.Name == OptionStrategyDefinitions.ShortBoxSpread.Name)
{
result = GetMaintenanceMargin(new PositionGroupMaintenanceMarginParameters(parameters.Portfolio, parameters.PositionGroup));
}
else if (_optionStrategy.Name == OptionStrategyDefinitions.JellyRoll.Name
|| _optionStrategy.Name == OptionStrategyDefinitions.ShortJellyRoll.Name)
{
result = GetMaintenanceMargin(new PositionGroupMaintenanceMarginParameters(parameters.Portfolio, parameters.PositionGroup));
}
else if (_optionStrategy.Name == OptionStrategyDefinitions.BearCallLadder.Name || _optionStrategy.Name == OptionStrategyDefinitions.BearPutLadder.Name
|| _optionStrategy.Name == OptionStrategyDefinitions.BullCallLadder.Name || _optionStrategy.Name == OptionStrategyDefinitions.BullPutLadder.Name)
{
result = GetMaintenanceMargin(new PositionGroupMaintenanceMarginParameters(parameters.Portfolio, parameters.PositionGroup));
}
else
{
throw new NotImplementedException($"Option strategy {_optionStrategy.Name} margin modeling has yet to be implemented");
}
// Add premium to initial margin only when it is positive (the user must pay the premium)
var premium = 0m;
foreach (var position in parameters.PositionGroup.Positions.Where(position => position.Symbol.SecurityType.IsOption()))
{
var option = (Option)parameters.Portfolio.Securities[position.Symbol];
premium += option.Holdings.GetQuantityValue(position.Quantity).InAccountCurrency;
}
return new OptionInitialMargin(result, premium);
}
/// <summary>
/// Gets the total margin required to execute the specified order in units of the account currency including fees
/// </summary>
/// <param name="parameters">An object containing the portfolio, the security and the order</param>
/// <returns>The total margin in terms of the currency quoted in the order</returns>
public override InitialMargin GetInitialMarginRequiredForOrder(PositionGroupInitialMarginForOrderParameters parameters)
{
var security = parameters.Portfolio.Securities[parameters.Order.Symbol];
var fees = security.FeeModel.GetOrderFee(new OrderFeeParameters(security, parameters.Order));
var feesInAccountCurrency = parameters.Portfolio.CashBook.ConvertToAccountCurrency(fees.Value);
var initialMarginRequired = GetInitialMarginRequirement(new PositionGroupInitialMarginParameters(parameters.Portfolio, parameters.PositionGroup));
var feesWithSign = Math.Sign(initialMarginRequired) * feesInAccountCurrency.Amount;
return new InitialMargin(feesWithSign + initialMarginRequired);
}
/// <summary>
/// Gets the initial margin required for the specified contemplated position group.
/// Used by <see cref="QuantConnect.Securities.Positions.PositionGroupBuyingPowerModel.GetReservedBuyingPowerImpact"/> to get the contemplated groups margin.
/// </summary>
protected override decimal GetContemplatedGroupsInitialMargin(SecurityPortfolioManager portfolio, PositionGroupCollection contemplatedGroups,
List<IPosition> ordersPositions)
{
var contemplatedMargin = 0m;
foreach (var contemplatedGroup in contemplatedGroups)
{
// We use the initial margin requirement as the contemplated groups margin in order to ensure
// the available buying power is enough to execute the order.
var initialMargin = contemplatedGroup.BuyingPowerModel.GetInitialMarginRequirement(
new PositionGroupInitialMarginParameters(portfolio, contemplatedGroup));
var optionInitialMargin = initialMargin as OptionInitialMargin;
contemplatedMargin += optionInitialMargin?.ValueWithoutPremium ?? initialMargin;
}
// Now we need to add the premium paid for the order:
// This should always return a single group since it is a single order/combo
var ordersGroups = portfolio.Positions.ResolvePositionGroups(new PositionCollection(ordersPositions));
foreach (var orderGroup in ordersGroups)
{
var initialMargin = orderGroup.BuyingPowerModel.GetInitialMarginRequirement(
new PositionGroupInitialMarginParameters(portfolio, orderGroup));
var optionInitialMargin = initialMargin as OptionInitialMargin;
if (optionInitialMargin != null)
{
// We need to add the premium paid for the order. We use the TotalValue-Value difference instead of Premium
// to add it only when needed -- when it is debited from the account
contemplatedMargin += optionInitialMargin.Value - optionInitialMargin.ValueWithoutPremium;
}
}
return contemplatedMargin;
}
/// <summary>
/// Returns a string that represents the current object.
/// </summary>
/// <returns>A string that represents the current object.</returns>
public override string ToString()
{
return _optionStrategy.Name;
}
/// <summary>
/// Returns the Maximum (Short Put Strike - Long Put Strike, 0)
/// </summary>
private static decimal GetShortPutLongPutStrikeDifferenceMargin(IEnumerable<IPosition> positions, SecurityPortfolioManager portfolio, decimal quantity)
{
var longOption = positions.Single(position => position.Symbol.ID.OptionRight == OptionRight.Put && position.Quantity > 0);
var shortOption = positions.Single(position => position.Symbol.ID.OptionRight == OptionRight.Put && position.Quantity < 0);
var optionSecurity = (Option)portfolio.Securities[longOption.Symbol];
// Maximum (Short Put Strike - Long Put Strike, 0)
var strikeDifference = shortOption.Symbol.ID.StrikePrice - longOption.Symbol.ID.StrikePrice;
var result = Math.Max(strikeDifference * optionSecurity.ContractUnitOfTrade * Math.Abs(quantity), 0);
// convert into account currency
return portfolio.CashBook.ConvertToAccountCurrency(result, optionSecurity.QuoteCurrency.Symbol);
}
/// <summary>
/// Returns the Maximum (Strike Long Call - Strike Short Call, 0)
/// </summary>
private static decimal GetLongCallShortCallStrikeDifferenceMargin(IEnumerable<IPosition> positions, SecurityPortfolioManager portfolio, decimal quantity)
{
var longOption = positions.Single(position => position.Symbol.ID.OptionRight == OptionRight.Call && position.Quantity > 0);
var shortOption = positions.Single(position => position.Symbol.ID.OptionRight == OptionRight.Call && position.Quantity < 0);
var optionSecurity = (Option)portfolio.Securities[longOption.Symbol];
var strikeDifference = longOption.Symbol.ID.StrikePrice - shortOption.Symbol.ID.StrikePrice;
var result = Math.Max(strikeDifference * optionSecurity.ContractUnitOfTrade * Math.Abs(quantity), 0);
// convert into account currency
return portfolio.CashBook.ConvertToAccountCurrency(result, optionSecurity.QuoteCurrency.Symbol);
}
/// <summary>
/// Returns the Maximum (Middle Strike - Lowest Strike, 0)
/// </summary>
private static decimal GetMiddleAndLowStrikeDifference(IPositionGroup positionGroup, SecurityPortfolioManager portfolio)
{
var options = positionGroup.Positions.OrderBy(position => position.Symbol.ID.StrikePrice).ToList();
var lowestCallStrike = options[0].Symbol.ID.StrikePrice;
var middleCallStrike = options[1].Symbol.ID.StrikePrice;
var optionSecurity = (Option)portfolio.Securities[options[0].Symbol];
var strikeDifference = Math.Max((middleCallStrike - lowestCallStrike) * optionSecurity.ContractUnitOfTrade * Math.Abs(positionGroup.Quantity), 0);
// convert into account currency
return portfolio.CashBook.ConvertToAccountCurrency(strikeDifference, optionSecurity.QuoteCurrency.Symbol);
}
/// <summary>
/// Returns the margin for a short straddle or strangle.
/// This is the same for both the initial margin requirement and the maintenance margin.
/// </summary>
private static decimal GetShortStraddleStrangleMargin(IPositionGroup positionGroup, SecurityPortfolioManager portfolio,
Func<Option, decimal, decimal> getOptionMargin)
{
var callOption = positionGroup.Positions.Single(position => position.Symbol.ID.OptionRight == OptionRight.Call);
var callSecurity = (Option)portfolio.Securities[callOption.Symbol];
var callMargin = getOptionMargin(callSecurity, callOption.Quantity);
var putOption = positionGroup.Positions.Single(position => position.Symbol.ID.OptionRight == OptionRight.Put);
var putSecurity = (Option)portfolio.Securities[putOption.Symbol];
var putMargin = getOptionMargin(putSecurity, putOption.Quantity);
var result = 0m;
if (putMargin > callMargin)
{
result = putMargin + callSecurity.Price * callSecurity.ContractUnitOfTrade * Math.Abs(callOption.Quantity);
}
else
{
result = callMargin + putSecurity.Price * putSecurity.ContractUnitOfTrade * Math.Abs(putOption.Quantity);
}
return result;
}
/// <summary>
/// Returns the maintenance margin for a conversion or reverse conversion.
/// </summary>
private static decimal GetConversionMaintenanceMargin(IPositionGroup positionGroup, SecurityPortfolioManager portfolio, OptionRight optionRight)
{
// 10% * Strike Price + Call/Put In the Money Amount
var optionPosition = positionGroup.Positions.Single(position =>
position.Symbol.SecurityType.IsOption() && position.Symbol.ID.OptionRight == optionRight);
var underlyingPosition = positionGroup.Positions.FirstOrDefault(position => !position.Symbol.SecurityType.IsOption());
var optionSecurity = (Option)portfolio.Securities[optionPosition.Symbol];
var underlyingSecurity = portfolio.Securities[underlyingPosition.Symbol];
var marginRequirement = 0.1m * optionSecurity.StrikePrice + optionSecurity.GetIntrinsicValue(underlyingSecurity.Price);
var result = marginRequirement * Math.Abs(optionPosition.Quantity) * optionSecurity.ContractUnitOfTrade;
var inAccountCurrency = portfolio.CashBook.ConvertToAccountCurrency(result, underlyingSecurity.QuoteCurrency.Symbol);
return new MaintenanceMargin(inAccountCurrency);
}
/// <summary>
/// Returns the initial margin requirement for a collar, conversion, or reverse conversion.
/// </summary>
private static decimal GetCollarConversionInitialMargin(IPositionGroup positionGroup, SecurityPortfolioManager portfolio, OptionRight optionRight)
{
// Initial Stock Margin Requirement + In the Money Call/Put Amount
var optionPosition = positionGroup.Positions.Single(position =>
position.Symbol.SecurityType.IsOption() && position.Symbol.ID.OptionRight == optionRight);
var underlyingPosition = positionGroup.Positions.Single(position => !position.Symbol.SecurityType.IsOption());
var optionSecurity = (Option)portfolio.Securities[optionPosition.Symbol];
var underlyingSecurity = portfolio.Securities[underlyingPosition.Symbol];
var intrinsicValue = optionSecurity.GetIntrinsicValue(underlyingSecurity.Price);
var inTheMoneyAmount = intrinsicValue * optionSecurity.ContractUnitOfTrade * Math.Abs(optionPosition.Quantity);
var initialMarginRequirement = underlyingSecurity.BuyingPowerModel.GetInitialMarginRequirement(underlyingSecurity, underlyingPosition.Quantity);
var result = Math.Abs(initialMarginRequirement) + inTheMoneyAmount;
return portfolio.CashBook.ConvertToAccountCurrency(result, optionSecurity.QuoteCurrency.Symbol);
}
/// <summary>
/// Returns the initial/maintenance margin requirement for a call ladder
/// </summary>
private static decimal GetCallLadderMargin(PositionGroupMaintenanceMarginParameters parameters, bool bearCallLadder)
{
var quantity = parameters.PositionGroup.Quantity;
if ((quantity >= 0 && bearCallLadder) || (quantity < 0 && !bearCallLadder))
{
// Bear Call Ladder = Bear Call Spread of 2 lower strike prices + Long Call with the highest strike price (margin: 0)
var callSpread = parameters.PositionGroup.Positions.OrderBy(position => position.Symbol.ID.StrikePrice).Take(2).ToList();
return GetLongCallShortCallStrikeDifferenceMargin(callSpread, parameters.Portfolio, Math.Abs(quantity));
}
else
{
// Bull Call Ladder = Bull Call Spread of 2 lower strike prices (margin: 0) + Short Call with the highest strike price
var shortNakedCall = parameters.PositionGroup.Positions.OrderByDescending(position => position.Symbol.ID.StrikePrice).First();
var security = (Option)parameters.Portfolio.Securities[shortNakedCall.Symbol];
var margin = security.BuyingPowerModel.GetInitialMarginRequirement(new InitialMarginParameters(security, shortNakedCall.Quantity));
return new MaintenanceMargin(Math.Abs(margin));
}
}
/// <summary>
/// Returns the initial/maintenance margin requirement for a put ladder
/// </summary>
private static decimal GetPutLadderMargin(PositionGroupMaintenanceMarginParameters parameters, bool bullPutLadder)
{
var quantity = parameters.PositionGroup.Quantity;
if ((quantity >= 0 && bullPutLadder) || (quantity < 0 && !bullPutLadder))
{
// Bull Put Ladder = Bull Put Spread of 2 higher strike prices + Long Put with the lowest strike price (margin: 0)
var putSpread = parameters.PositionGroup.Positions.OrderByDescending(position => position.Symbol.ID.StrikePrice).Take(2).ToList();
return GetShortPutLongPutStrikeDifferenceMargin(putSpread, parameters.Portfolio, Math.Abs(quantity));
}
else
{
// Bear Put Ladder = Bear Put Spread of 2 higher strike prices (margin: 0) + Short Put with the lowest strike price
var shortNakedPut = parameters.PositionGroup.Positions.OrderBy(position => position.Symbol.ID.StrikePrice).First();
var security = (Option)parameters.Portfolio.Securities[shortNakedPut.Symbol];
var margin = security.BuyingPowerModel.GetInitialMarginRequirement(new InitialMarginParameters(security, shortNakedPut.Quantity));
return new MaintenanceMargin(Math.Abs(margin));
}
}
}
}
+213
View File
@@ -0,0 +1,213 @@
/*
* 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.Runtime.CompilerServices;
using QuantConnect.Securities.Future;
using QuantConnect.Securities.IndexOption;
namespace QuantConnect.Securities.Option
{
/// <summary>
/// Static class contains common utility methods specific to symbols representing the option contracts
/// </summary>
public static class OptionSymbol
{
private static readonly Dictionary<string, byte> _optionExpirationErrorLog = new();
/// <summary>
/// Returns true if the option is a standard contract that expires 3rd Friday of the month
/// </summary>
/// <param name="symbol">Option symbol</param>
/// <returns></returns>
public static bool IsStandardContract(Symbol symbol)
{
return IsStandard(symbol);
}
/// <summary>
/// Returns true if the option is a standard contract that expires 3rd Friday of the month
/// </summary>
/// <param name="symbol">Option symbol</param>
/// <returns></returns>
public static bool IsStandard(Symbol symbol)
{
var date = symbol.ID.Date;
// first we find out the day of week of the first day in the month
var firstDayOfMonth = new DateTime(date.Year, date.Month, 1).DayOfWeek;
// find out the day of first Friday in this month
var firstFriday = firstDayOfMonth == DayOfWeek.Saturday ? 7 : 6 - (int)firstDayOfMonth;
// check if the expiration date is within the week containing 3rd Friday
// we exclude monday, wednesday, and friday weeklys
return firstFriday + 7 + 5 /*sat -> wed */ < date.Day && date.Day < firstFriday + 2 * 7 + 2 /* sat, sun*/;
}
/// <summary>
/// Returns true if the option is a weekly contract that expires on Friday , except 3rd Friday of the month
/// </summary>
/// <param name="symbol">Option symbol</param>
/// <returns></returns>
public static bool IsWeekly(Symbol symbol)
{
return !IsStandard(symbol) && symbol.ID.Date.DayOfWeek == DayOfWeek.Friday;
}
/// <summary>
/// Maps the option ticker to it's underlying
/// </summary>
/// <param name="optionTicker">The option ticker to map</param>
/// <param name="securityType">The security type of the option or underlying</param>
/// <returns>The underlying ticker</returns>
public static string MapToUnderlying(string optionTicker, SecurityType securityType)
{
if (securityType == SecurityType.FutureOption || securityType == SecurityType.Future)
{
return FuturesOptionsSymbolMappings.MapFromOption(optionTicker);
}
else if (securityType == SecurityType.IndexOption || securityType == SecurityType.Index)
{
return IndexOptionSymbol.MapToUnderlying(optionTicker);
}
return optionTicker;
}
/// <summary>
/// Returns the last trading date for the option contract
/// </summary>
/// <param name="symbol">Option symbol</param>
/// <returns></returns>
public static DateTime GetLastDayOfTrading(Symbol symbol)
{
// 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.
// More details: https://www.sec.gov/rules/sro/occ/2013/34-69480.pdf
int daysBefore = 0;
var symbolDateTime = symbol.ID.Date;
if (IsStandard(symbol) &&
symbolDateTime.DayOfWeek == DayOfWeek.Saturday &&
symbolDateTime < new DateTime(2015, 2, 1))
{
daysBefore--;
}
var exchangeHours = MarketHoursDatabase.FromDataFolder()
.GetEntry(symbol.ID.Market, symbol, symbol.SecurityType)
.ExchangeHours;
while (!exchangeHours.IsDateOpen(symbolDateTime.AddDays(daysBefore)))
{
daysBefore--;
}
return symbolDateTime.AddDays(daysBefore).Date;
}
/// <summary>
/// Returns the settlement date time of the option contract.
/// </summary>
/// <param name="symbol">The option contract symbol</param>
/// <returns>The settlement date time</returns>
public static DateTime GetSettlementDateTime(Symbol symbol)
{
if (!TryGetExpirationDateTime(symbol, out var expiryTime, out var exchangeHours))
{
throw new ArgumentException($"The symbol {symbol} is not an option type");
}
// Standard index options are AM-settled, which means they settle on market open of the expiration date.
// Non-standard tickers (e.g. SPXW, RUTW) are always PM-settled, even when expiring on the 3rd Friday.
if (expiryTime.Date == symbol.ID.Date.Date
&& symbol.SecurityType == SecurityType.IndexOption
&& IsStandard(symbol)
&& IndexOptionSymbol.IsAMSettled(symbol))
{
expiryTime = exchangeHours.GetNextMarketOpen(expiryTime.Date, false);
}
// 0DTE PM-settled index options expire at 4:00 PM ET, not the regular 4:15 PM ET (CBOE spec)
else if (expiryTime.Date == symbol.ID.Date.Date
&& symbol.SecurityType == SecurityType.IndexOption
&& !IndexOptionSymbol.IsAMSettled(symbol))
{
expiryTime = expiryTime.Date.Add(TimeSpan.FromHours(15));
}
return expiryTime;
}
/// <summary>
/// Returns true if the option contract is expired at the specified time
/// </summary>
/// <param name="symbol">The option contract symbol</param>
/// <param name="currentTimeUtc">The current time (UTC)</param>
/// <returns>True if the option contract is expired at the specified time, false otherwise</returns>
public static bool IsOptionContractExpired(Symbol symbol, DateTime currentTimeUtc)
{
if (TryGetExpirationDateTime(symbol, out var expiryTime, out var exchangeHours))
{
var currentTime = currentTimeUtc.ConvertFromUtc(exchangeHours.TimeZone);
return currentTime >= expiryTime;
}
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool TryGetExpirationDateTime(Symbol symbol, out DateTime expiryTime, out SecurityExchangeHours exchangeHours)
{
if (!symbol.SecurityType.IsOption())
{
expiryTime = default;
exchangeHours = null;
return false;
}
exchangeHours = MarketHoursDatabase.FromDataFolder().GetExchangeHours(symbol.ID.Market, symbol, symbol.SecurityType);
// Ideally we can calculate expiry on the date of the symbol ID, but if that exchange is not open on that day we
// will consider expired on the last trading day close before this; Example in AddOptionContractExpiresRegressionAlgorithm
var lastTradingDay = exchangeHours.IsDateOpen(symbol.ID.Date)
? symbol.ID.Date
: exchangeHours.GetPreviousTradingDay(symbol.ID.Date);
expiryTime = exchangeHours.GetLastDailyMarketClose(lastTradingDay, false);
// Once bug 6189 was solved in ´GetNextMarketClose()´ there was found possible bugs on some futures symbol.ID.Date or delisting/liquidation handle event.
// Specifically see 'DelistingFutureOptionRegressionAlgorithm' where Symbol.ID.Date: 4/1/2012 00:00 ExpiryTime: 4/2/2012 16:00 for Milk 3 futures options.
// See 'bug-milk-class-3-future-options-expiration' branch. So let's limit the expiry time to up to end of day of expiration
if (expiryTime >= symbol.ID.Date.AddDays(1).Date)
{
lock (_optionExpirationErrorLog)
{
if (symbol.ID.Underlying != null
// let's log this once per underlying and expiration date: avoiding the same log for multiple option contracts with different strikes/rights
&& _optionExpirationErrorLog.TryAdd($"{symbol.ID.Underlying}-{symbol.ID.Date}", 1))
{
Logging.Log.Error($"OptionSymbol.IsOptionContractExpired(): limiting unexpected option expiration time for symbol {symbol.ID}. Symbol.ID.Date {symbol.ID.Date}. ExpiryTime: {expiryTime}");
}
}
expiryTime = symbol.ID.Date.AddDays(1).Date;
}
return true;
}
}
}
@@ -0,0 +1,54 @@
/*
* 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.Option
{
/// <summary>
/// Represents common properties for a specific option contract
/// </summary>
public class OptionSymbolProperties : ContractSymbolProperties
{
/// <summary>
/// When the holder of an equity option exercises one contract, or when the writer of an equity option is assigned
/// an exercise notice on one contract, this unit of trade, usually 100 shares of the underlying security, changes hands.
/// </summary>
public int ContractUnitOfTrade
{
get; protected set;
}
/// <summary>
/// Creates an instance of the <see cref="OptionSymbolProperties"/> class
/// </summary>
public OptionSymbolProperties(string description, string quoteCurrency, decimal contractMultiplier, decimal pipSize, decimal lotSize)
: this(new SymbolProperties(description, quoteCurrency, contractMultiplier, pipSize, lotSize, string.Empty))
{
}
/// <summary>
/// Creates an instance of the <see cref="OptionSymbolProperties"/> class from <see cref="SymbolProperties"/> class
/// </summary>
public OptionSymbolProperties(SymbolProperties properties)
: base(properties)
{
ContractUnitOfTrade = (int)properties.ContractMultiplier;
}
internal void SetContractUnitOfTrade(int unitOfTrade)
{
ContractUnitOfTrade = unitOfTrade;
}
}
}
@@ -0,0 +1,439 @@
/*
* 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 QLNet;
using System;
using System.Linq;
using QuantConnect.Data;
using QuantConnect.Logging;
using QuantConnect.Data.Market;
using System.Collections.Generic;
namespace QuantConnect.Securities.Option
{
using PricingEngineFunc = Func<GeneralizedBlackScholesProcess, IPricingEngine>;
using PricingEngineFuncEx = Func<Symbol, GeneralizedBlackScholesProcess, IPricingEngine>;
/// <summary>
/// Provides QuantLib(QL) implementation of <see cref="IOptionPriceModel"/> to support major option pricing models, available in QL.
/// </summary>
public class QLOptionPriceModel : OptionPriceModel
{
private static readonly OptionStyle[] _defaultAllowedOptionStyles = { OptionStyle.European, OptionStyle.American };
private static readonly IQLUnderlyingVolatilityEstimator _defaultUnderlyingVolEstimator = new ConstantQLUnderlyingVolatilityEstimator();
private static readonly IQLRiskFreeRateEstimator _defaultRiskFreeRateEstimator = new FedRateQLRiskFreeRateEstimator();
private static readonly IQLDividendYieldEstimator _defaultDividendYieldEstimator = new ConstantQLDividendYieldEstimator();
private readonly IQLUnderlyingVolatilityEstimator _underlyingVolEstimator;
private readonly IQLDividendYieldEstimator _dividendYieldEstimator;
private readonly IQLRiskFreeRateEstimator _riskFreeRateEstimator;
private readonly PricingEngineFuncEx _pricingEngineFunc;
/// <summary>
/// When enabled, approximates Greeks if corresponding pricing model didn't calculate exact numbers.
/// The default value is true.
/// </summary>
public bool EnableGreekApproximation { get; set; } = true;
/// <summary>
/// True if volatility model is warmed up, i.e. has generated volatility value different from zero, otherwise false.
/// </summary>
public bool VolatilityEstimatorWarmedUp => _underlyingVolEstimator.IsReady;
/// <summary>
/// List of option styles supported by the pricing model.
/// By default, both American and European option styles are supported.
/// </summary>
public IReadOnlyCollection<OptionStyle> AllowedOptionStyles { get; }
/// <summary>
/// Method constructs QuantLib option price model with necessary estimators of underlying volatility, risk free rate, and underlying dividend yield
/// </summary>
/// <param name="pricingEngineFunc">Function modeled stochastic process, and returns new pricing engine to run calculations for that option</param>
/// <param name="underlyingVolEstimator">The underlying volatility estimator</param>
/// <param name="riskFreeRateEstimator">The risk free rate estimator</param>
/// <param name="dividendYieldEstimator">The underlying dividend yield estimator</param>
/// <param name="allowedOptionStyles">List of option styles supported by the pricing model. It defaults to both American and European option styles</param>
public QLOptionPriceModel(PricingEngineFunc pricingEngineFunc,
IQLUnderlyingVolatilityEstimator underlyingVolEstimator = null,
IQLRiskFreeRateEstimator riskFreeRateEstimator = null,
IQLDividendYieldEstimator dividendYieldEstimator = null,
OptionStyle[] allowedOptionStyles = null)
: this((option, process) => pricingEngineFunc(process), underlyingVolEstimator, riskFreeRateEstimator, dividendYieldEstimator, allowedOptionStyles)
{ }
/// <summary>
/// Method constructs QuantLib option price model with necessary estimators of underlying volatility, risk free rate, and underlying dividend yield
/// </summary>
/// <param name="pricingEngineFunc">Function takes option and modeled stochastic process, and returns new pricing engine to run calculations for that option</param>
/// <param name="underlyingVolEstimator">The underlying volatility estimator</param>
/// <param name="riskFreeRateEstimator">The risk free rate estimator</param>
/// <param name="dividendYieldEstimator">The underlying dividend yield estimator</param>
/// <param name="allowedOptionStyles">List of option styles supported by the pricing model. It defaults to both American and European option styles</param>
public QLOptionPriceModel(PricingEngineFuncEx pricingEngineFunc,
IQLUnderlyingVolatilityEstimator underlyingVolEstimator = null,
IQLRiskFreeRateEstimator riskFreeRateEstimator = null,
IQLDividendYieldEstimator dividendYieldEstimator = null,
OptionStyle[] allowedOptionStyles = null)
{
_pricingEngineFunc = pricingEngineFunc;
_underlyingVolEstimator = underlyingVolEstimator ?? _defaultUnderlyingVolEstimator;
_riskFreeRateEstimator = riskFreeRateEstimator ?? _defaultRiskFreeRateEstimator;
_dividendYieldEstimator = dividendYieldEstimator ?? _defaultDividendYieldEstimator;
AllowedOptionStyles = allowedOptionStyles ?? _defaultAllowedOptionStyles;
}
/// <summary>
/// Evaluates the specified option contract to compute a theoretical price, IV and greeks
/// </summary>
/// <param name="security">The option security object</param>
/// <param name="slice">The current data slice. This can be used to access other information
/// available to the algorithm</param>
/// <param name="contract">The option contract to evaluate</param>
/// <returns>An instance of <see cref="OptionPriceModelResult"/> containing the theoretical
/// price of the specified option contract</returns>
public override OptionPriceModelResult Evaluate(Security security, Slice slice, OptionContract contract)
{
if (!AllowedOptionStyles.Contains(contract.Symbol.ID.OptionStyle))
{
throw new ArgumentException($"{contract.Symbol.ID.OptionStyle} style options are not supported by option price model '{this.GetType().Name}'");
}
try
{
// expired options have no price
if (contract.Time.Date > contract.Expiry.Date)
{
if (Log.DebuggingEnabled)
{
Log.Debug($"QLOptionPriceModel.Evaluate(). Expired {contract.Symbol}. Time > Expiry: {contract.Time.Date} > {contract.Expiry.Date}");
}
return OptionPriceModelResult.None;
}
var dayCounter = new Actual365Fixed();
var securityExchangeHours = security.Exchange.Hours;
var maturityDate = AddDays(contract.Expiry.Date, Option.DefaultSettlementDays, securityExchangeHours);
// Get time until maturity (in year)
var maturity = dayCounter.yearFraction(contract.Time.Date, maturityDate);
if (maturity < 0)
{
if (Log.DebuggingEnabled)
{
Log.Debug($"QLOptionPriceModel.Evaluate(). negative time ({maturity}) given for {contract.Symbol}. Time: {contract.Time.Date}. Maturity {maturityDate}");
}
return OptionPriceModelResult.None;
}
// setting up option pricing parameters
var optionSecurity = (Option)security;
var premium = (double)optionSecurity.Price;
var spot = (double)optionSecurity.Underlying.Price;
if (spot <= 0d || premium <= 0d)
{
if (Log.DebuggingEnabled)
{
Log.Debug($"QLOptionPriceModel.Evaluate(). Non-positive prices for {contract.Symbol}. Premium: {premium}. Underlying price {spot}");
}
return OptionPriceModelResult.None;
}
var calendar = new UnitedStates();
var settlementDate = AddDays(contract.Time.Date, Option.DefaultSettlementDays, securityExchangeHours);
var underlyingQuoteValue = new SimpleQuote(spot);
var dividendYieldValue = new SimpleQuote(_dividendYieldEstimator.Estimate(security, slice, contract));
var dividendYield = new Handle<YieldTermStructure>(new FlatForward(0, calendar, dividendYieldValue, dayCounter));
var riskFreeRateValue = new SimpleQuote((double)_riskFreeRateEstimator.Estimate(security, slice, contract));
var riskFreeRate = new Handle<YieldTermStructure>(new FlatForward(0, calendar, riskFreeRateValue, dayCounter));
// Get discount factor by dividend and risk free rate using the maturity
var dividendDiscount = dividendYield.link.discount(maturity);
var riskFreeDiscount = riskFreeRate.link.discount(maturity);
var forwardPrice = spot * dividendDiscount / riskFreeDiscount;
// Initial guess for volatility by Brenner and Subrahmanyam (1988)
var initialGuess = Math.Sqrt(2 * Math.PI / maturity) * premium / spot;
var underlyingVolEstimate = _underlyingVolEstimator.Estimate(security, slice, contract);
// If the volatility estimator is not ready, we will use initial guess
if (!_underlyingVolEstimator.IsReady)
{
underlyingVolEstimate = initialGuess;
}
var underlyingVolValue = new SimpleQuote(underlyingVolEstimate);
var underlyingVol = new Handle<BlackVolTermStructure>(new BlackConstantVol(0, calendar, new Handle<Quote>(underlyingVolValue), dayCounter));
// preparing stochastic process and payoff functions
var stochasticProcess = new BlackScholesMertonProcess(new Handle<Quote>(underlyingQuoteValue), dividendYield, riskFreeRate, underlyingVol);
var payoff = new PlainVanillaPayoff(contract.Right == OptionRight.Call ? QLNet.Option.Type.Call : QLNet.Option.Type.Put, (double)contract.Strike);
// creating option QL object
var option = contract.Symbol.ID.OptionStyle == OptionStyle.American ?
new VanillaOption(payoff, new AmericanExercise(settlementDate, maturityDate)) :
new VanillaOption(payoff, new EuropeanExercise(maturityDate));
// preparing pricing engine QL object
option.setPricingEngine(_pricingEngineFunc(contract.Symbol, stochasticProcess));
// Setting the evaluation date before running the calculations
var evaluationDate = contract.Time.Date;
SetEvaluationDate(evaluationDate);
// running calculations
var npv = EvaluateOption(option);
BlackCalculator blackCalculator = null;
// Calculate the Implied Volatility
var impliedVol = 0d;
try
{
SetEvaluationDate(evaluationDate);
impliedVol = option.impliedVolatility(premium, stochasticProcess);
}
catch (Exception e)
{
// A Newton-Raphson optimization estimate of the implied volatility
impliedVol = ImpliedVolatilityEstimation(premium, initialGuess, maturity, riskFreeDiscount, forwardPrice, payoff, out blackCalculator);
if (Log.DebuggingEnabled)
{
var referenceDate = underlyingVol.link.referenceDate();
Log.Debug($"QLOptionPriceModel.Evaluate(). Cannot calculate Implied Volatility for {contract.Symbol}. Implied volatility from Newton-Raphson optimization: {impliedVol}. Premium: {premium}. Underlying price: {spot}. Initial guess volatility: {initialGuess}. Maturity: {maturity}. Risk Free: {riskFreeDiscount}. Forward price: {forwardPrice}. Data time: {evaluationDate}. Reference date: {referenceDate}. {e.Message} {e.StackTrace}");
}
}
// Update the Black Vol Term Structure with the Implied Volatility to improve Greek calculation
// We assume that the underlying volatility model does not yield a good estimate and
// other sources, e.g. Interactive Brokers, use the implied volatility to calculate the Greeks
// After this operation, the Theoretical Price (NPV) will match the Premium, so we do not re-evalute
// it and let users compare NPV and the Premium if they wish.
underlyingVolValue.setValue(impliedVol);
// function extracts QL greeks catching exception if greek is not generated by the pricing engine and reevaluates option to get numerical estimate of the seisitivity
decimal tryGetGreekOrReevaluate(Func<double> greek, Func<BlackCalculator, double> black)
{
double result;
var isApproximation = false;
Exception exception = null;
try
{
SetEvaluationDate(evaluationDate);
result = greek();
}
catch (Exception err)
{
exception = err;
if (!EnableGreekApproximation)
{
return 0.0m;
}
if (blackCalculator == null)
{
// Define Black Calculator to calculate Greeks that are not defined by the option object
// Some models do not evaluate all greeks under some circumstances (e.g. low dividend yield)
// We override this restriction to calculate the Greeks directly with the BlackCalculator
var vol = underlyingVol.link.blackVol(maturityDate, (double)contract.Strike);
blackCalculator = CreateBlackCalculator(forwardPrice, riskFreeDiscount, vol, payoff);
}
isApproximation = true;
result = black(blackCalculator);
}
if (result.IsNaNOrInfinity())
{
if (Log.DebuggingEnabled)
{
var referenceDate = underlyingVol.link.referenceDate();
Log.Debug($"QLOptionPriceModel.Evaluate(). NaN or Infinity greek for {contract.Symbol}. Premium: {premium}. Underlying price: {spot}. Initial guess volatility: {initialGuess}. Maturity: {maturity}. Risk Free: {riskFreeDiscount}. Forward price: {forwardPrice}. Implied Volatility: {impliedVol}. Is Approximation? {isApproximation}. Data time: {evaluationDate}. Reference date: {referenceDate}. {exception?.Message} {exception?.StackTrace}");
}
return 0m;
}
var value = result.SafeDecimalCast();
if (value == decimal.Zero && Log.DebuggingEnabled)
{
var referenceDate = underlyingVol.link.referenceDate();
Log.Debug($"QLOptionPriceModel.Evaluate(). Zero-value greek for {contract.Symbol}. Premium: {premium}. Underlying price: {spot}. Initial guess volatility: {initialGuess}. Maturity: {maturity}. Risk Free: {riskFreeDiscount}. Forward price: {forwardPrice}. Implied Volatility: {impliedVol}. Is Approximation? {isApproximation}. Data time: {evaluationDate}. Reference date: {referenceDate}. {exception?.Message} {exception?.StackTrace}");
return value;
}
return value;
}
// producing output with lazy calculations of greeks
return new OptionPriceModelResult(npv, // EvaluateOption ensure it is not NaN or Infinity
() => impliedVol.IsNaNOrInfinity() ? 0m : impliedVol.SafeDecimalCast(),
() => new ModeledGreeks(() => tryGetGreekOrReevaluate(() => option.delta(), (black) => black.delta(spot)),
() => tryGetGreekOrReevaluate(() => option.gamma(), (black) => black.gamma(spot)),
() => tryGetGreekOrReevaluate(() => option.vega(), (black) => black.vega(maturity)) / 100, // per cent
() => tryGetGreekOrReevaluate(() => option.theta(), (black) => black.theta(spot, maturity)),
() => tryGetGreekOrReevaluate(() => option.rho(), (black) => black.rho(maturity)) / 100, // per cent
() => tryGetGreekOrReevaluate(() => option.elasticity(), (black) => black.elasticity(spot))));
}
catch (Exception err)
{
Log.Debug($"QLOptionPriceModel.Evaluate() error: {err.Message} {(Log.DebuggingEnabled ? err.StackTrace : string.Empty)} for {contract.Symbol}");
return OptionPriceModelResult.None;
}
}
/// <summary>
/// Evaluates the specified option contract to compute a theoretical price, IV and greeks
/// </summary>
/// <param name="parameters">A <see cref="OptionPriceModelParameters"/> object
/// containing the security, slice and contract</param>
/// <returns>An instance of <see cref="OptionPriceModelResult"/> containing the theoretical
/// price of the specified option contract</returns>
public override OptionPriceModelResult Evaluate(OptionPriceModelParameters parameters)
{
return Evaluate(parameters.Security, parameters.Slice, parameters.Contract);
}
/// <summary>
/// Runs option evaluation and logs exceptions
/// </summary>
/// <param name="option"></param>
/// <returns></returns>
private static decimal EvaluateOption(VanillaOption option)
{
try
{
var npv = option.NPV();
if (double.IsNaN(npv) ||
double.IsInfinity(npv))
return 0;
// can return negative value in neighborhood of 0
return Math.Max(0, npv).SafeDecimalCast();
}
catch (Exception err)
{
Log.Debug($"QLOptionPriceModel.EvaluateOption() error: {err.Message}");
return 0;
}
}
/// <summary>
/// An implied volatility approximation by Newton-Raphson method. Return 0 if result is not converged
/// </summary>
/// <remarks>
/// Orlando G, Taglialatela G. A review on implied volatility calculation. Journal of Computational and Applied Mathematics. 2017 Aug 15;320:202-20.
/// https://www.sciencedirect.com/science/article/pii/S0377042717300602
/// </remarks>
/// <param name="price">current price of the option</param>
/// <param name="initialGuess">initial guess of the IV</param>
/// <param name="timeTillExpiry">time till option contract expiry</param>
/// <param name="riskFreeDiscount">risk free rate discount factor</param>
/// <param name="forwardPrice">future value of underlying price</param>
/// <param name="payoff">payoff structure of the option contract</param>
/// <param name="black">black calculator instance</param>
/// <returns>implied volatility estimation</returns>
protected double ImpliedVolatilityEstimation(double price, double initialGuess, double timeTillExpiry, double riskFreeDiscount,
double forwardPrice, PlainVanillaPayoff payoff, out BlackCalculator black)
{
// Set up the optimizer
const double tolerance = 1e-3d;
const double lowerBound = 1e-7d;
const double upperBound = 4d;
var iterRemain = 10;
var error = double.MaxValue;
var impliedVolEstimate = initialGuess;
// Set up option calculator
black = CreateBlackCalculator(forwardPrice, riskFreeDiscount, initialGuess, payoff);
while (error > tolerance && iterRemain > 0)
{
var oldImpliedVol = impliedVolEstimate;
// Set up calculator by previous IV estimate to get new theoretical price, vega and IV
black = CreateBlackCalculator(forwardPrice, riskFreeDiscount, oldImpliedVol, payoff);
impliedVolEstimate -= (black.value() - price) / black.vega(timeTillExpiry);
if (impliedVolEstimate < lowerBound)
{
impliedVolEstimate = lowerBound;
}
else if (impliedVolEstimate > upperBound)
{
impliedVolEstimate = upperBound;
}
error = Math.Abs(impliedVolEstimate - oldImpliedVol) / impliedVolEstimate;
iterRemain--;
}
if (iterRemain == 0)
{
if (Log.DebuggingEnabled)
{
Log.Debug("QLOptionPriceModel.ImpliedVolatilityEstimation() error: Implied Volatility approxiation did not converge, returning 0.");
}
return 0d;
}
return impliedVolEstimate;
}
/// <summary>
/// Define Black Calculator to calculate Greeks that are not defined by the option object
/// Some models do not evaluate all greeks under some circumstances (e.g. low dividend yield)
/// We override this restriction to calculate the Greeks directly with the BlackCalculator
/// </summary>
private BlackCalculator CreateBlackCalculator(double forwardPrice, double riskFreeDiscount, double stdDev, PlainVanillaPayoff payoff)
{
return new BlackCalculator(payoff, forwardPrice, stdDev, riskFreeDiscount);
}
private static DateTime AddDays(DateTime date, int days, SecurityExchangeHours marketHours)
{
var forwardDate = date.AddDays(days);
if (!marketHours.IsDateOpen(forwardDate))
{
forwardDate = marketHours.GetNextTradingDay(forwardDate);
}
return forwardDate;
}
/// <summary>
/// Set the evaluation date
/// </summary>
/// <param name="evaluationDate">The current evaluation date</param>
private void SetEvaluationDate(DateTime evaluationDate)
{
if (Settings.evaluationDate().ToDateTime() != evaluationDate)
{
Settings.setEvaluationDate(evaluationDate);
}
}
}
}
@@ -0,0 +1,76 @@
/*
* 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 QLNet;
using QuantConnect.Indicators;
using System;
namespace QuantConnect.Securities.Option
{
/// <summary>
/// Provides option price models for option securities based on QuantLib implementations
/// </summary>
public class QLOptionPriceModelProvider : IOptionPriceModelProvider
{
internal const int TimeStepsBinomial = 100;
/// <summary>
/// Singleton instance of the <see cref="QLOptionPriceModelProvider"/>
/// </summary>
public static QLOptionPriceModelProvider Instance { get; } = new();
private QLOptionPriceModelProvider()
{
}
/// <summary>
/// Gets the option price model for the specified option symbol
/// </summary>
/// <param name="symbol">The symbol</param>
/// <param name="pricingModelType">The option pricing model type to use</param>
/// <returns>The option price model for the given symbol</returns>
public IOptionPriceModel GetOptionPriceModel(Symbol symbol, OptionPricingModelType? pricingModelType = null)
{
if (pricingModelType.HasValue)
{
return GetOptionPriceModel(pricingModelType.Value);
}
return symbol.ID.OptionStyle switch
{
// CRR model has the best accuracy and speed suggested by
// Branka, Zdravka & Tea (2014). Numerical Methods versus Bjerksund and Stensland Approximations for American Options Pricing.
// International Journal of Economics and Management Engineering. 8:4.
// Available via: https://downloads.dxfeed.com/specifications/dxLibOptions/Numerical-Methods-versus-Bjerksund-and-Stensland-Approximations-for-American-Options-Pricing-.pdf
// Also refer to OptionPriceModelTests.MatchesIBGreeksBulk() test,
// we select the most accurate and computational efficient model
OptionStyle.American => GetOptionPriceModel(OptionPricingModelType.BinomialCoxRossRubinstein),
OptionStyle.European => GetOptionPriceModel(OptionPricingModelType.BlackScholes),
_ => throw new ArgumentException("Invalid OptionStyle")
};
}
private static QLOptionPriceModel GetOptionPriceModel(OptionPricingModelType pricingModelType)
{
return pricingModelType switch
{
OptionPricingModelType.BlackScholes => new QLOptionPriceModel(process => new AnalyticEuropeanEngine(process),
allowedOptionStyles: [OptionStyle.European]),
OptionPricingModelType.BinomialCoxRossRubinstein => new QLOptionPriceModel(process => new BinomialVanillaEngine<CoxRossRubinstein>(process, TimeStepsBinomial)),
_ => throw new ArgumentException($"Unsupported pricing model type: {pricingModelType}")
};
}
}
}
@@ -0,0 +1,88 @@
/*
* 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;
namespace QuantConnect.Securities.Option.StrategyMatcher
{
/// <summary>
/// Stub class providing an idea towards an optimal <see cref="IOptionPositionCollectionEnumerator"/> implementation
/// that still needs to be implemented.
/// </summary>
public class AbsoluteRiskOptionPositionCollectionEnumerator : IOptionPositionCollectionEnumerator
{
private readonly Func<Symbol, decimal> _marketPriceProvider;
/// <summary>
/// Intializes a new instance of the <see cref="AbsoluteRiskOptionPositionCollectionEnumerator"/> class
/// </summary>
/// <param name="marketPriceProvider">Function providing the current market price for a provided symbol</param>
public AbsoluteRiskOptionPositionCollectionEnumerator(Func<Symbol, decimal> marketPriceProvider)
{
_marketPriceProvider = marketPriceProvider;
}
/// <summary>
/// Enumerates the provided <paramref name="positions"/>. Positions enumerated first are more
/// likely to be matched than those appearing later in the enumeration.
/// </summary>
public IEnumerable<OptionPosition> Enumerate(OptionPositionCollection positions)
{
if (positions.IsEmpty)
{
yield break;
}
var marketPrice = _marketPriceProvider(positions.Underlying);
var longPositions = new List<OptionPosition>();
var shortPuts = new SortedDictionary<decimal, OptionPosition>();
var shortCalls = new SortedDictionary<decimal, OptionPosition>();
foreach (var position in positions)
{
if (!position.Symbol.HasUnderlying)
{
yield return position;
}
if (position.Quantity > 0)
{
longPositions.Add(position);
}
else
{
switch (position.Right)
{
case OptionRight.Put:
shortPuts.Add(position.Strike, position);
break;
case OptionRight.Call:
shortCalls.Add(position.Strike, position);
break;
default:
throw new ApplicationException(
"The skies are falling, the oceans rising - you're having a bad time"
);
}
}
}
throw new NotImplementedException("This implementation needs to be completed.");
}
}
}
@@ -0,0 +1,82 @@
/*
* 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;
namespace QuantConnect.Securities.Option.StrategyMatcher
{
/// <summary>
/// Provides an implementation of <see cref="IOptionStrategyLegPredicateReferenceValue"/> that represents a constant value.
/// </summary>
public class ConstantOptionStrategyLegPredicateReferenceValue<T> : IOptionStrategyLegPredicateReferenceValue
{
private readonly T _value;
/// <summary>
/// Gets the target of this value
/// </summary>
public PredicateTargetValue Target { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ConstantOptionStrategyLegPredicateReferenceValue{T}"/> class
/// </summary>
/// <param name="value">The constant reference value</param>
/// <param name="target">The value target in relation to the <see cref="OptionPosition"/></param>
public ConstantOptionStrategyLegPredicateReferenceValue(T value, PredicateTargetValue target)
{
_value = value;
Target = target;
}
/// <summary>
/// Returns the constant value provided at initialization
/// </summary>
public object Resolve(IReadOnlyList<OptionPosition> legs)
{
return _value;
}
}
/// <summary>
/// Provides methods for easily creating instances of <see cref="ConstantOptionStrategyLegPredicateReferenceValue{T}"/>
/// </summary>
public static class ConstantOptionStrategyLegReferenceValue
{
/// <summary>
/// Creates a new instance of the <see cref="ConstantOptionStrategyLegPredicateReferenceValue{T}"/> class for
/// the specified <paramref name="value"/>
/// </summary>
public static IOptionStrategyLegPredicateReferenceValue Create(object value)
{
if (value is DateTime)
{
return new ConstantOptionStrategyLegPredicateReferenceValue<DateTime>((DateTime) value, PredicateTargetValue.Expiration);
}
if (value is decimal)
{
return new ConstantOptionStrategyLegPredicateReferenceValue<decimal>((decimal) value, PredicateTargetValue.Strike);
}
if (value is OptionRight)
{
return new ConstantOptionStrategyLegPredicateReferenceValue<OptionRight>((OptionRight) value, PredicateTargetValue.Right);
}
throw new NotSupportedException($"{value?.GetType().GetBetterTypeName()} is not supported.");
}
}
}
@@ -0,0 +1,33 @@
/*
* 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.Option.StrategyMatcher
{
/// <summary>
/// Provides a default implementation of the <see cref="IOptionPositionCollectionEnumerator"/> abstraction.
/// </summary>
public class DefaultOptionPositionCollectionEnumerator : IOptionPositionCollectionEnumerator
{
/// <summary>
/// Enumerates <paramref name="positions"/> according to its default enumerator implementation.
/// </summary>
public IEnumerable<OptionPosition> Enumerate(OptionPositionCollection positions)
{
return positions;
}
}
}
@@ -0,0 +1,35 @@
/*
* 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.Option.StrategyMatcher
{
/// <summary>
/// Provides an implementation of <see cref="IOptionStrategyDefinitionEnumerator"/> that enumerates definitions
/// requiring more leg matches first. This ensures more complex definitions are evaluated before simpler definitions.
/// </summary>
public class DescendingByLegCountOptionStrategyDefinitionEnumerator : IOptionStrategyDefinitionEnumerator
{
/// <summary>
/// Enumerates definitions in descending order of <see cref="OptionStrategyDefinition.LegCount"/>
/// </summary>
public IEnumerable<OptionStrategyDefinition> Enumerate(IReadOnlyList<OptionStrategyDefinition> definitions)
{
return definitions.OrderByDescending(d => d.LegCount);
}
}
}
@@ -0,0 +1,49 @@
/*
* 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;
namespace QuantConnect.Securities.Option.StrategyMatcher
{
/// <summary>
/// Provides a functional implementation of <see cref="IOptionPositionCollectionEnumerator"/>
/// </summary>
public class FunctionalOptionPositionCollectionEnumerator : IOptionPositionCollectionEnumerator
{
private readonly Func<OptionPositionCollection, IEnumerable<OptionPosition>> _enumerate;
/// <summary>
/// Initializes a new instance of the <see cref="FunctionalOptionPositionCollectionEnumerator"/> class
/// </summary>
/// <param name="enumerate"></param>
public FunctionalOptionPositionCollectionEnumerator(
Func<OptionPositionCollection, IEnumerable<OptionPosition>> enumerate
)
{
_enumerate = enumerate;
}
/// <summary>
/// Enumerate the Option Positions Collection
/// </summary>
/// <param name="positions">The positions to enumerate on</param>
/// <returns>Enumerable of Option Positions</returns>
public IEnumerable<OptionPosition> Enumerate(OptionPositionCollection positions)
{
return _enumerate(positions);
}
}
}
@@ -0,0 +1,36 @@
/*
* 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.Option.StrategyMatcher
{
/// <summary>
/// Enumerates an <see cref="OptionPositionCollection"/>. The intent is to evaluate positions that
/// may be more important sooner. Positions appearing earlier in the enumeration are evaluated before
/// positions showing later. This effectively prioritizes individual positions. This should not be
/// used filter filtering, but it could also be used to split a position, for example a position with
/// 10 could be changed to two 5s and they don't need to be enumerated back to-back either. In this
/// way you could prioritize the first 5 and then delay matching of the final 5.
/// </summary>
public interface IOptionPositionCollectionEnumerator
{
/// <summary>
/// Enumerates the provided <paramref name="positions"/>. Positions enumerated first are more
/// likely to be matched than those appearing later in the enumeration.
/// </summary>
IEnumerable<OptionPosition> Enumerate(OptionPositionCollection positions);
}
}
@@ -0,0 +1,31 @@
/*
* 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.Option.StrategyMatcher
{
/// <summary>
/// Enumerates <see cref="OptionStrategyDefinition"/> for the purposes of providing a bias towards definitions
/// that are more favorable to be matched before matching less favorable definitions.
/// </summary>
public interface IOptionStrategyDefinitionEnumerator
{
/// <summary>
/// Enumerates the <paramref name="definitions"/> according to the implementation's own concept of favorability.
/// </summary>
IEnumerable<OptionStrategyDefinition> Enumerate(IReadOnlyList<OptionStrategyDefinition> definitions);
}
}
@@ -0,0 +1,43 @@
/*
* 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.Option.StrategyMatcher
{
/// <summary>
/// When decoding leg predicates, we extract the value we're comparing against
/// If we're comparing against another leg's value (such as legs[0].Strike), then
/// we'll create a OptionStrategyLegPredicateReferenceValue. If we're comparing against a literal/constant value,
/// then we'll create a ConstantOptionStrategyLegPredicateReferenceValue. These reference values are used to slice
/// the <see cref="OptionPositionCollection"/> to only include positions matching the
/// predicate.
/// </summary>
public interface IOptionStrategyLegPredicateReferenceValue
{
/// <summary>
/// Gets the target of this value
/// </summary>
PredicateTargetValue Target { get; }
/// <summary>
/// Resolves the value of the comparand specified in an <see cref="OptionStrategyLegPredicate"/>.
/// For example, the predicate may include ... > legs[0].Strike, and upon evaluation, we need to
/// be able to extract leg[0].Strike for the currently contemplated set of legs adhering to a
/// strategy's definition.
/// </summary>
object Resolve(IReadOnlyList<OptionPosition> legs);
}
}
@@ -0,0 +1,30 @@
/*
* 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.Option.StrategyMatcher
{
/// <summary>
/// Evaluates the provided match to assign an objective score. Higher scores are better.
/// </summary>
public interface IOptionStrategyMatchObjectiveFunction
{
/// <summary>
/// Evaluates the objective function for the provided match solution. Solution with the highest score will be selected
/// as the solution. NOTE: This part of the match has not been implemented as of 2020-11-06 as it's only evaluating the
/// first solution match (MatchOnce).
/// </summary>
decimal ComputeScore(OptionPositionCollection input, OptionStrategyMatch match, OptionPositionCollection unmatched);
}
}
@@ -0,0 +1,34 @@
/*
* 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.Option.StrategyMatcher
{
/// <summary>
/// Provides a default implementation of <see cref="IOptionStrategyDefinitionEnumerator"/> that enumerates
/// definitions according to the order that they were provided to <see cref="OptionStrategyMatcherOptions"/>
/// </summary>
public class IdentityOptionStrategyDefinitionEnumerator : IOptionStrategyDefinitionEnumerator
{
/// <summary>
/// Enumerates the <paramref name="definitions"/> in the same order as provided.
/// </summary>
public IEnumerable<OptionStrategyDefinition> Enumerate(IReadOnlyList<OptionStrategyDefinition> definitions)
{
return definitions;
}
}
}
@@ -0,0 +1,281 @@
/*
* 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.Option.StrategyMatcher
{
/// <summary>
/// Defines a lightweight structure representing a position in an option contract or underlying.
/// This type is heavily utilized by the options strategy matcher and is the parameter type of
/// option strategy definition predicates. Underlying quantities should be represented in lot sizes,
/// which is equal to the quantity of shares divided by the contract's multiplier and then rounded
/// down towards zero (truncate)
/// </summary>
public struct OptionPosition : IEquatable<OptionPosition>
{
/// <summary>
/// Gets a new <see cref="OptionPosition"/> with zero <see cref="Quantity"/>
/// </summary>
public static OptionPosition Empty(Symbol symbol)
=> new OptionPosition(symbol, 0);
/// <summary>
/// Determines whether or not this position has any quantity
/// </summary>
public bool HasQuantity => Quantity != 0;
/// <summary>
/// Determines whether or not this position is for the underlying symbol
/// </summary>
public bool IsUnderlying => !Symbol.HasUnderlying;
/// <summary>
/// Number of contracts held, can be positive or negative
/// </summary>
public int Quantity { get; }
/// <summary>
/// Option contract symbol
/// </summary>
public Symbol Symbol { get; }
/// <summary>
/// Gets the underlying symbol. If this position represents the underlying,
/// then this property is the same as the <see cref="Symbol"/> property
/// </summary>
public Symbol Underlying => IsUnderlying ? Symbol : Symbol.Underlying;
/// <summary>
/// Option contract expiration date
/// </summary>
public DateTime Expiration
{
get
{
if (Symbol.HasUnderlying)
{
return Symbol.ID.Date;
}
throw new InvalidOperationException($"{nameof(Expiration)} is not valid for underlying symbols: {Symbol}");
}
}
/// <summary>
/// Option contract strike price
/// </summary>
public decimal Strike
{
get
{
if (Symbol.HasUnderlying)
{
return Symbol.ID.StrikePrice;
}
throw new InvalidOperationException($"{nameof(Strike)} is not valid for underlying symbols: {Symbol}");
}
}
/// <summary>
/// Option contract right (put/call)
/// </summary>
public OptionRight Right
{
get
{
if (Symbol.HasUnderlying)
{
return Symbol.ID.OptionRight;
}
throw new InvalidOperationException($"{nameof(Right)} is not valid for underlying symbols: {Symbol}");
}
}
/// <summary>
/// Gets whether this position is short/long/none
/// </summary>
public PositionSide Side => (PositionSide) Math.Sign(Quantity);
/// <summary>
/// Initializes a new instance of the <see cref="OptionPosition"/> structure
/// </summary>
/// <param name="symbol">The option contract symbol</param>
/// <param name="quantity">The number of contracts held</param>
public OptionPosition(Symbol symbol, int quantity)
{
Symbol = symbol;
Quantity = quantity;
}
/// <summary>
/// Creates a new <see cref="OptionPosition"/> instance with negative <see cref="Quantity"/>
/// </summary>
public OptionPosition Negate()
{
return new OptionPosition(Symbol, -Quantity);
}
/// <summary>
/// Creates a new <see cref="OptionPosition"/> with this position's <see cref="Symbol"/>
/// and the provided <paramref name="quantity"/>
/// </summary>
public OptionPosition WithQuantity(int quantity)
{
return new OptionPosition(Symbol, quantity);
}
/// <summary>Indicates whether the current object is equal to another object of the same type.</summary>
/// <param name="other">An object to compare with this object.</param>
/// <returns>true if the current object is equal to the <paramref name="other" /> parameter; otherwise, false.</returns>
public bool Equals(OptionPosition other)
{
return Equals(Symbol, other.Symbol) && Quantity == other.Quantity;
}
/// <summary>Indicates whether this instance and a specified object are equal.</summary>
/// <param name="obj">The object to compare with the current instance. </param>
/// <returns>true if <paramref name="obj" /> and this instance are the same type and represent the same value; otherwise, false. </returns>
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
if (obj.GetType() != GetType())
{
return false;
}
return Equals((OptionPosition) obj);
}
/// <summary>Returns the hash code for this instance.</summary>
/// <returns>A 32-bit signed integer that is the hash code for this instance.</returns>
public override int GetHashCode()
{
unchecked
{
return ((Symbol != null ? Symbol.GetHashCode() : 0) * 397) ^ Quantity;
}
}
/// <summary>Returns the fully qualified type name of this instance.</summary>
/// <returns>The fully qualified type name.</returns>
public override string ToString()
{
var s = Quantity == 1 ? "" : "s";
if (Symbol.HasUnderlying)
{
return $"{Quantity} {Right.ToLower()}{s} on {Symbol.Underlying.Value} at ${Strike} expiring on {Expiration:yyyy-MM-dd}";
}
return $"{Quantity} share{s} of {Symbol.Value}";
}
/// <summary>
/// OptionPosition * Operator, will multiple quantity by given factor
/// </summary>
/// <param name="left">OptionPosition to operate on</param>
/// <param name="factor">Factor to multiply by</param>
/// <returns>Resulting OptionPosition</returns>
public static OptionPosition operator *(OptionPosition left, int factor)
{
return new OptionPosition(left.Symbol, factor * left.Quantity);
}
/// <summary>
/// OptionPosition * Operator, will multiple quantity by given factor
/// </summary>
/// <param name="right">OptionPosition to operate on</param>
/// <param name="factor">Factor to multiply by</param>
/// <returns>Resulting OptionPosition</returns>
public static OptionPosition operator *(int factor, OptionPosition right)
{
return new OptionPosition(right.Symbol, factor * right.Quantity);
}
/// <summary>
/// OptionPosition + Operator, will add quantities together if they are for the same symbol.
/// </summary>
/// <returns>Resulting OptionPosition</returns>
public static OptionPosition operator +(OptionPosition left, OptionPosition right)
{
if (!Equals(left.Symbol, right.Symbol))
{
if (left == default(OptionPosition))
{
return right;
}
if (right == default(OptionPosition))
{
return left;
}
throw new InvalidOperationException("Unable to add OptionPosition instances with different symbols");
}
return new OptionPosition(left.Symbol, left.Quantity + right.Quantity);
}
/// <summary>
/// OptionPosition - Operator, will subtract left - right quantities if they are for the same symbol.
/// </summary>
/// <returns>Resulting OptionPosition</returns>
public static OptionPosition operator -(OptionPosition left, OptionPosition right)
{
if (!Equals(left.Symbol, right.Symbol))
{
if (left == default(OptionPosition))
{
// 0 - right
return right.Negate();
}
if (right == default(OptionPosition))
{
// left - 0
return left;
}
throw new InvalidOperationException("Unable to subtract OptionPosition instances with different symbols");
}
return new OptionPosition(left.Symbol, left.Quantity - right.Quantity);
}
/// <summary>
/// Option Position == Operator
/// </summary>
/// <returns>True if they are the same</returns>
public static bool operator ==(OptionPosition left, OptionPosition right)
{
return Equals(left, right);
}
/// <summary>
/// Option Position != Operator
/// </summary>
/// <returns>True if they are not the same</returns>
public static bool operator !=(OptionPosition left, OptionPosition right)
{
return !Equals(left, right);
}
}
}
@@ -0,0 +1,652 @@
/*
* 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.Linq;
using QuantConnect.Util;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using QuantConnect.Securities.Positions;
namespace QuantConnect.Securities.Option.StrategyMatcher
{
/// <summary>
/// Provides indexing of option contracts
/// </summary>
public class OptionPositionCollection : IEnumerable<OptionPosition>
{
/// <summary>
/// Gets an empty instance of <see cref="OptionPositionCollection"/>
/// </summary>
public static OptionPositionCollection Empty { get; } = new OptionPositionCollection(
ImmutableDictionary<Symbol, OptionPosition>.Empty,
ImmutableDictionary<OptionRight, ImmutableHashSet<Symbol>>.Empty,
ImmutableDictionary<PositionSide, ImmutableHashSet<Symbol>>.Empty,
ImmutableSortedDictionary<decimal, ImmutableHashSet<Symbol>>.Empty,
ImmutableSortedDictionary<DateTime, ImmutableHashSet<Symbol>>.Empty
);
private readonly ImmutableDictionary<Symbol, OptionPosition> _positions;
private readonly ImmutableDictionary<OptionRight, ImmutableHashSet<Symbol>> _rights;
private readonly ImmutableDictionary<PositionSide, ImmutableHashSet<Symbol>> _sides;
private readonly ImmutableSortedDictionary<decimal, ImmutableHashSet<Symbol>> _strikes;
private readonly ImmutableSortedDictionary<DateTime, ImmutableHashSet<Symbol>> _expirations;
/// <summary>
/// Gets the underlying security's symbol
/// </summary>
public Symbol Underlying => UnderlyingPosition.Symbol ?? Symbol.Empty;
/// <summary>
/// Gets the total count of unique positions, including the underlying
/// </summary>
public int Count => _positions.Count;
/// <summary>
/// Gets whether or not there's any positions in this collection.
/// </summary>
public bool IsEmpty => _positions.IsEmpty;
/// <summary>
/// Gets the quantity of underlying shares held
/// TODO : Change to UnderlyingLots
/// </summary>
public int UnderlyingQuantity => UnderlyingPosition.Quantity;
/// <summary>
/// Gets the number of unique put contracts held (long or short)
/// </summary>
public int UniquePuts => _rights[OptionRight.Put].Count;
/// <summary>
/// Gets the unique number of expirations
/// </summary>
public int UniqueExpirations => _expirations.Count;
/// <summary>
/// Gets the number of unique call contracts held (long or short)
/// </summary>
public int UniqueCalls => _rights[OptionRight.Call].Count;
/// <summary>
/// Determines if this collection contains a position in the underlying
/// </summary>
public bool HasUnderlying => UnderlyingQuantity != 0;
/// <summary>
/// Gets the <see cref="Underlying"/> position
/// </summary>
public OptionPosition UnderlyingPosition { get; }
/// <summary>
/// Gets all unique strike prices in the collection, in ascending order.
/// </summary>
public IEnumerable<decimal> Strikes => _strikes.Keys;
/// <summary>
/// Gets all unique expiration dates in the collection, in chronological order.
/// </summary>
public IEnumerable<DateTime> Expirations => _expirations.Keys;
/// <summary>
/// Initializes a new instance of the <see cref="OptionPositionCollection"/> class
/// </summary>
/// <param name="positions">All positions</param>
/// <param name="rights">Index of position symbols by option right</param>
/// <param name="sides">Index of position symbols by position side (short/long/none)</param>
/// <param name="strikes">Index of position symbols by strike price</param>
/// <param name="expirations">Index of position symbols by expiration</param>
public OptionPositionCollection(
ImmutableDictionary<Symbol, OptionPosition> positions,
ImmutableDictionary<OptionRight, ImmutableHashSet<Symbol>> rights,
ImmutableDictionary<PositionSide, ImmutableHashSet<Symbol>> sides,
ImmutableSortedDictionary<decimal, ImmutableHashSet<Symbol>> strikes,
ImmutableSortedDictionary<DateTime, ImmutableHashSet<Symbol>> expirations
)
{
_sides = sides;
_rights = rights;
_strikes = strikes;
_positions = positions;
_expirations = expirations;
if (_rights.Count != 2)
{
// ensure we always have both rights indexed, even if empty
ImmutableHashSet<Symbol> value;
if (!_rights.TryGetValue(OptionRight.Call, out value))
{
_rights = _rights.SetItem(OptionRight.Call, ImmutableHashSet<Symbol>.Empty);
}
if (!_rights.TryGetValue(OptionRight.Put, out value))
{
_rights = _rights.SetItem(OptionRight.Put, ImmutableHashSet<Symbol>.Empty);
}
}
if (_sides.Count != 3)
{
// ensure we always have all three sides indexed, even if empty
ImmutableHashSet<Symbol> value;
if (!_sides.TryGetValue(PositionSide.None, out value))
{
_sides = _sides.SetItem(PositionSide.None, ImmutableHashSet<Symbol>.Empty);
}
if (!_sides.TryGetValue(PositionSide.Short, out value))
{
_sides = _sides.SetItem(PositionSide.Short, ImmutableHashSet<Symbol>.Empty);
}
if (!_sides.TryGetValue(PositionSide.Long, out value))
{
_sides = _sides.SetItem(PositionSide.Long, ImmutableHashSet<Symbol>.Empty);
}
}
if (!positions.IsEmpty)
{
// assumption here is that 'positions' includes the underlying equity position and
// ONLY option contracts, so all symbols have the underlying equity symbol embedded
// via the Underlying property, except of course, for the underlying itself.
var underlying = positions.First().Key;
if (underlying.HasUnderlying)
{
underlying = underlying.Underlying;
}
// OptionPosition is struct, so no worry about null ref via .Quantity
var underlyingQuantity = positions.GetValueOrDefault(underlying).Quantity;
UnderlyingPosition = new OptionPosition(underlying, underlyingQuantity);
}
#if DEBUG
var errors = Validate().ToList();
if (errors.Count > 0)
{
throw new ArgumentException("OptionPositionCollection validation failed: "
+ Environment.NewLine + string.Join(Environment.NewLine, errors)
);
}
#endif
}
/// <summary>
/// Determines if a position is held in the specified <paramref name="symbol"/>
/// </summary>
public bool HasPosition(Symbol symbol)
{
OptionPosition position;
return TryGetPosition(symbol, out position) && position.Quantity != 0;
}
/// <summary>
/// Retrieves the <see cref="OptionPosition"/> for the specified <paramref name="symbol"/>
/// if one exists in this collection.
/// </summary>
public bool TryGetPosition(Symbol symbol, out OptionPosition position)
{
return _positions.TryGetValue(symbol, out position);
}
/// <summary>
/// Creates a new <see cref="OptionPositionCollection"/> from the specified enumerable of <paramref name="positions"/>
/// </summary>
public static OptionPositionCollection FromPositions(IEnumerable<OptionPosition> positions)
{
return Empty.AddRange(positions);
}
/// <summary>
/// Creates a new <see cref="OptionPositionCollection"/> from the specified enumerable of <paramref name="positions"/>
/// </summary>
public static OptionPositionCollection FromPositions(IEnumerable<IPosition> positions, decimal contractMultiplier)
{
return Empty.AddRange(positions.Select(position =>
{
var quantity = (int)position.Quantity;
if (position.Symbol.SecurityType.HasOptions())
{
quantity = (int) (quantity / contractMultiplier);
}
return new OptionPosition(position.Symbol, quantity);
}));
}
/// <summary>
/// Creates a new <see cref="OptionPositionCollection"/> from the specified <paramref name="holdings"/>,
/// filtering based on the <paramref name="underlying"/>
/// </summary>
public static OptionPositionCollection Create(Symbol underlying, decimal contractMultiplier, IEnumerable<SecurityHolding> holdings)
{
var positions = Empty;
foreach (var holding in holdings)
{
var symbol = holding.Symbol;
if (!symbol.HasUnderlying)
{
if (symbol == underlying)
{
var underlyingLots = (int) (holding.Quantity / contractMultiplier);
positions = positions.Add(new OptionPosition(symbol, underlyingLots));
}
continue;
}
if (symbol.Underlying != underlying)
{
continue;
}
var position = new OptionPosition(symbol, (int) holding.Quantity);
positions = positions.Add(position);
}
return positions;
}
/// <summary>
/// Creates a new collection that is the result of adding the specified <paramref name="position"/> to this collection.
/// </summary>
public OptionPositionCollection Add(OptionPosition position)
{
if (!position.HasQuantity)
{
// adding nothing doesn't change the collection
return this;
}
var sides = _sides;
var rights = _rights;
var strikes = _strikes;
var positions = _positions;
var expirations = _expirations;
var exists = false;
OptionPosition existing;
var symbol = position.Symbol;
if (positions.TryGetValue(symbol, out existing))
{
exists = true;
position += existing;
}
if (position.HasQuantity)
{
positions = positions.SetItem(symbol, position);
if (!exists && symbol.HasUnderlying)
{
// update indexes when adding a new option contract
sides = sides.Add(position.Side, symbol);
rights = rights.Add(position.Right, symbol);
strikes = strikes.Add(position.Strike, symbol);
positions = positions.SetItem(symbol, position);
expirations = expirations.Add(position.Expiration, symbol);
}
}
else
{
// if the position's quantity went to zero, remove it entirely from the collection when
// removing, be sure to remove strike/expiration indexes. we purposefully keep the rights
// index populated, even with a zero count entry because it's bounded to 2 items (put/call)
positions = positions.Remove(symbol);
if (symbol.HasUnderlying)
{
// keep call/put entries even if goes to zero
var rightsValue = rights[position.Right].Remove(symbol);
rights = rights.SetItem(position.Right, rightsValue);
// keep short/none/long entries even if goes to zero
var sidesValue = sides[position.Side].Remove(symbol);
sides = sides.SetItem(position.Side, sidesValue);
var strikesValue = strikes[position.Strike].Remove(symbol);
strikes = strikesValue.Count > 0
? strikes.SetItem(position.Strike, strikesValue)
: strikes.Remove(position.Strike);
var expirationsValue = expirations[position.Expiration].Remove(symbol);
expirations = expirationsValue.Count > 0
? expirations.SetItem(position.Expiration, expirationsValue)
: expirations.Remove(position.Expiration);
}
}
return new OptionPositionCollection(positions, rights, sides, strikes, expirations);
}
/// <summary>
/// Creates a new collection that is the result of removing the specified <paramref name="position"/>
/// </summary>
public OptionPositionCollection Remove(OptionPosition position)
{
return Add(position.Negate());
}
/// <summary>
/// Creates a new collection that is the result of adding the specified <paramref name="positions"/> to this collection.
/// </summary>
public OptionPositionCollection AddRange(params OptionPosition[] positions)
{
return AddRange((IEnumerable<OptionPosition>) positions);
}
/// <summary>
/// Creates a new collection that is the result of adding the specified <paramref name="positions"/> to this collection.
/// </summary>
public OptionPositionCollection AddRange(IEnumerable<OptionPosition> positions)
{
return positions.Aggregate(this, (current, position) => current + position);
}
/// <summary>
/// Creates a new collection that is the result of removing the specified <paramref name="positions"/>
/// </summary>
public OptionPositionCollection RemoveRange(IEnumerable<OptionPosition> positions)
{
return AddRange(positions.Select(position => position.Negate()));
}
/// <summary>
/// Slices this collection, returning a new collection containing only
/// positions with the specified <paramref name="right"/>
/// </summary>
public OptionPositionCollection Slice(OptionRight right, bool includeUnderlying = true)
{
var rights = _rights.Remove(right.Invert());
var positions = ImmutableDictionary<Symbol, OptionPosition>.Empty;
if (includeUnderlying && HasUnderlying)
{
positions = positions.Add(Underlying, UnderlyingPosition);
}
var sides = ImmutableDictionary<PositionSide, ImmutableHashSet<Symbol>>.Empty;
var strikes = ImmutableSortedDictionary<decimal, ImmutableHashSet<Symbol>>.Empty;
var expirations = ImmutableSortedDictionary<DateTime, ImmutableHashSet<Symbol>>.Empty;
foreach (var symbol in rights.SelectMany(kvp => kvp.Value))
{
var position = _positions[symbol];
sides = sides.Add(position.Side, symbol);
positions = positions.Add(symbol, position);
strikes = strikes.Add(position.Strike, symbol);
expirations = expirations.Add(position.Expiration, symbol);
}
return new OptionPositionCollection(positions, rights, sides, strikes, expirations);
}
/// <summary>
/// Slices this collection, returning a new collection containing only
/// positions with the specified <paramref name="side"/>
/// </summary>
public OptionPositionCollection Slice(PositionSide side, bool includeUnderlying = true)
{
var otherSides = GetOtherSides(side);
var sides = _sides.Remove(otherSides[0]).Remove(otherSides[1]);
var positions = ImmutableDictionary<Symbol, OptionPosition>.Empty;
if (includeUnderlying && HasUnderlying)
{
positions = positions.Add(Underlying, UnderlyingPosition);
}
var rights = ImmutableDictionary<OptionRight, ImmutableHashSet<Symbol>>.Empty;
var strikes = ImmutableSortedDictionary<decimal, ImmutableHashSet<Symbol>>.Empty;
var expirations = ImmutableSortedDictionary<DateTime, ImmutableHashSet<Symbol>>.Empty;
foreach (var symbol in sides.SelectMany(kvp => kvp.Value))
{
var position = _positions[symbol];
rights = rights.Add(position.Right, symbol);
positions = positions.Add(symbol, position);
strikes = strikes.Add(position.Strike, symbol);
expirations = expirations.Add(position.Expiration, symbol);
}
return new OptionPositionCollection(positions, rights, sides, strikes, expirations);
}
/// <summary>
/// Slices this collection, returning a new collection containing only
/// positions matching the specified <paramref name="comparison"/> and <paramref name="strike"/>
/// </summary>
public OptionPositionCollection Slice(BinaryComparison comparison, decimal strike, bool includeUnderlying = true)
{
var strikes = comparison.Filter(_strikes, strike);
if (strikes.IsEmpty)
{
return includeUnderlying && HasUnderlying ? Empty.Add(UnderlyingPosition) : Empty;
}
var positions = ImmutableDictionary<Symbol, OptionPosition>.Empty;
if (includeUnderlying)
{
OptionPosition underlyingPosition;
if (_positions.TryGetValue(Underlying, out underlyingPosition))
{
positions = positions.Add(Underlying, underlyingPosition);
}
}
var sides = ImmutableDictionary<PositionSide, ImmutableHashSet<Symbol>>.Empty;
var rights = ImmutableDictionary<OptionRight, ImmutableHashSet<Symbol>>.Empty;
var expirations = ImmutableSortedDictionary<DateTime, ImmutableHashSet<Symbol>>.Empty;
foreach (var symbol in strikes.SelectMany(kvp => kvp.Value))
{
var position = _positions[symbol];
sides = sides.Add(position.Side, symbol);
positions = positions.Add(symbol, position);
rights = rights.Add(symbol.ID.OptionRight, symbol);
expirations = expirations.Add(symbol.ID.Date, symbol);
}
return new OptionPositionCollection(positions, rights, sides, strikes, expirations);
}
/// <summary>
/// Slices this collection, returning a new collection containing only
/// positions matching the specified <paramref name="comparison"/> and <paramref name="expiration"/>
/// </summary>
public OptionPositionCollection Slice(BinaryComparison comparison, DateTime expiration, bool includeUnderlying = true)
{
var expirations = comparison.Filter(_expirations, expiration);
if (expirations.IsEmpty)
{
return includeUnderlying && HasUnderlying ? Empty.Add(UnderlyingPosition) : Empty;
}
var positions = ImmutableDictionary<Symbol, OptionPosition>.Empty;
if (includeUnderlying)
{
OptionPosition underlyingPosition;
if (_positions.TryGetValue(Underlying, out underlyingPosition))
{
positions = positions.Add(Underlying, underlyingPosition);
}
}
var sides = ImmutableDictionary<PositionSide, ImmutableHashSet<Symbol>>.Empty;
var rights = ImmutableDictionary<OptionRight, ImmutableHashSet<Symbol>>.Empty;
var strikes = ImmutableSortedDictionary<decimal, ImmutableHashSet<Symbol>>.Empty;
foreach (var symbol in expirations.SelectMany(kvp => kvp.Value))
{
var position = _positions[symbol];
sides = sides.Add(position.Side, symbol);
positions = positions.Add(symbol, position);
rights = rights.Add(symbol.ID.OptionRight, symbol);
strikes = strikes.Add(symbol.ID.StrikePrice, symbol);
}
return new OptionPositionCollection(positions, rights, sides, strikes, expirations);
}
/// <summary>
/// Returns the set of <see cref="OptionPosition"/> with the specified <paramref name="symbols"/>
/// </summary>
public IEnumerable<OptionPosition> ForSymbols(IEnumerable<Symbol> symbols)
{
foreach (var symbol in symbols)
{
OptionPosition position;
if (_positions.TryGetValue(symbol, out position))
{
yield return position;
}
}
}
/// <summary>
/// Returns the set of <see cref="OptionPosition"/> with the specified <paramref name="right"/>
/// </summary>
public IEnumerable<OptionPosition> ForRight(OptionRight right)
{
ImmutableHashSet<Symbol> symbols;
return _rights.TryGetValue(right, out symbols)
? ForSymbols(symbols)
: Enumerable.Empty<OptionPosition>();
}
/// <summary>
/// Returns the set of <see cref="OptionPosition"/> with the specified <paramref name="side"/>
/// </summary>
public IEnumerable<OptionPosition> ForSide(PositionSide side)
{
ImmutableHashSet<Symbol> symbols;
return _sides.TryGetValue(side, out symbols)
? ForSymbols(symbols)
: Enumerable.Empty<OptionPosition>();
}
/// <summary>
/// Returns the set of <see cref="OptionPosition"/> with the specified <paramref name="strike"/>
/// </summary>
public IEnumerable<OptionPosition> ForStrike(decimal strike)
{
ImmutableHashSet<Symbol> symbols;
return _strikes.TryGetValue(strike, out symbols)
? ForSymbols(symbols)
: Enumerable.Empty<OptionPosition>();
}
/// <summary>
/// Returns the set of <see cref="OptionPosition"/> with the specified <paramref name="expiration"/>
/// </summary>
public IEnumerable<OptionPosition> ForExpiration(DateTime expiration)
{
ImmutableHashSet<Symbol> symbols;
return _expirations.TryGetValue(expiration, out symbols)
? ForSymbols(symbols)
: Enumerable.Empty<OptionPosition>();
}
/// <summary>Returns a string that represents the current object.</summary>
/// <returns>A string that represents the current object.</returns>
public override string ToString()
{
if (Count == 0)
{
return "Empty";
}
return HasUnderlying
? $"{UnderlyingQuantity} {Underlying.Value}: {_positions.Count - 1} contract positions"
: $"{Underlying.Value}: {_positions.Count} contract positions";
}
/// <summary>Returns an enumerator that iterates through the collection.</summary>
/// <returns>An enumerator that can be used to iterate through the collection.</returns>
public IEnumerator<OptionPosition> GetEnumerator()
{
return _positions.Select(kvp => kvp.Value).GetEnumerator();
}
/// <summary>
/// Validates this collection returning an enumerable of validation errors.
/// This should only be invoked via tests and is automatically invoked via
/// the constructor in DEBUG builds.
/// </summary>
internal IEnumerable<string> Validate()
{
foreach (var kvp in _positions)
{
var position = kvp.Value;
var symbol = position.Symbol;
if (position.Quantity == 0)
{
yield return $"{position}: Quantity == 0";
}
if (!symbol.HasUnderlying)
{
continue;
}
ImmutableHashSet<Symbol> strikes;
if (!_strikes.TryGetValue(position.Strike, out strikes) || !strikes.Contains(symbol))
{
yield return $"{position}: Not indexed by strike price";
}
ImmutableHashSet<Symbol> expirations;
if (!_expirations.TryGetValue(position.Expiration, out expirations) || !expirations.Contains(symbol))
{
yield return $"{position}: Not indexed by expiration date";
}
}
}
private static readonly PositionSide[] OtherSidesForNone = {PositionSide.Short, PositionSide.Long};
private static readonly PositionSide[] OtherSidesForShort = {PositionSide.None, PositionSide.Long};
private static readonly PositionSide[] OtherSidesForLong = {PositionSide.Short, PositionSide.None};
private static PositionSide[] GetOtherSides(PositionSide side)
{
switch (side)
{
case PositionSide.Short: return OtherSidesForShort;
case PositionSide.None: return OtherSidesForNone;
case PositionSide.Long: return OtherSidesForLong;
default:
throw new ArgumentOutOfRangeException(nameof(side), side, null);
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
/// <summary>
/// OptionPositionCollection + Operator
/// </summary>
/// <param name="positions">Collection to add to</param>
/// <param name="position">OptionPosition to add</param>
/// <returns>OptionPositionCollection with the new position added</returns>
public static OptionPositionCollection operator+(OptionPositionCollection positions, OptionPosition position)
{
return positions.Add(position);
}
/// <summary>
/// OptionPositionCollection - Operator
/// </summary>
/// <param name="positions">Collection to remove from</param>
/// <param name="position">OptionPosition to remove</param>
/// <returns>OptionPositionCollection with the position removed</returns>
public static OptionPositionCollection operator-(OptionPositionCollection positions, OptionPosition position)
{
return positions.Remove(position);
}
}
}
@@ -0,0 +1,320 @@
/*
* 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;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Linq.Expressions;
namespace QuantConnect.Securities.Option.StrategyMatcher
{
/// <summary>
/// Provides a definitional object for an <see cref="OptionStrategy"/>. This definition is used to 'match' option
/// positions via <see cref="OptionPositionCollection"/>. The <see cref="OptionStrategyMatcher"/> utilizes a full
/// collection of these definitional objects in order to match an algorithm's option position holdings to the
/// set of strategies in an effort to reduce the total margin required for holding the positions.
/// </summary>
public class OptionStrategyDefinition : IEnumerable<OptionStrategyLegDefinition>
{
/// <summary>
/// Gets the definition's name
/// </summary>
public string Name { get; }
/// <summary>
/// Gets the number of underlying lots required to match this definition. A lot size
/// is equal to the contract's multiplier and is usually equal to 100.
/// </summary>
public int UnderlyingLots { get; }
/// <summary>
/// Gets the option leg definitions. This list does NOT contain a definition for the
/// required underlying lots, due to its simplicity. Instead the required underlying
/// lots are defined via the <see cref="UnderlyingLots"/> property of the definition.
/// </summary>
public IReadOnlyList<OptionStrategyLegDefinition> Legs { get; }
/// <summary>
/// Gets the total number of legs, INCLUDING the underlying leg if applicable. This
/// is used to perform a coarse filter as the minimum number of unique positions in
/// the positions collection.
/// </summary>
public int LegCount => Legs.Count + (UnderlyingLots == 0 ? 0 : 1);
/// <summary>
/// Initializes a new instance of the <see cref="OptionStrategyDefinition"/> class
/// </summary>
/// <param name="name">The definition's name</param>
/// <param name="underlyingLots">The required number of underlying lots</param>
/// <param name="legs">Definitions for each option leg</param>
public OptionStrategyDefinition(string name, int underlyingLots, IEnumerable<OptionStrategyLegDefinition> legs)
{
Name = name;
Legs = legs.ToList();
UnderlyingLots = underlyingLots;
}
/// <summary>
/// Creates the <see cref="OptionStrategy"/> instance using this definition and the provided leg matches
/// </summary>
public OptionStrategy CreateStrategy(IReadOnlyList<OptionStrategyLegDefinitionMatch> legs)
{
return OptionStrategy.Create(Name, Enumerable.Range(0, Math.Min(Legs.Count, legs.Count)).Select(i => Legs[i].CreateLegData(legs[i])));
}
/// <summary>
/// Attempts to match the positions to this definition exactly once, by evaluating the enumerable and
/// taking the first entry matched. If not match is found, then false is returned and <paramref name="match"/>
/// will be null.
/// </summary>
public bool TryMatchOnce(OptionStrategyMatcherOptions options, OptionPositionCollection positions, out OptionStrategyDefinitionMatch match)
{
match = Match(options, positions).FirstOrDefault();
return match != null;
}
/// <summary>
/// Determines all possible matches for this definition using the provided <paramref name="positions"/>.
/// This includes OVERLAPPING matches. It's up to the actual matcher to make decisions based on which
/// matches to accept. This allows the matcher to prioritize matching certain positions over others.
/// </summary>
public IEnumerable<OptionStrategyDefinitionMatch> Match(OptionPositionCollection positions)
{
return Match(OptionStrategyMatcherOptions.ForDefinitions(this), positions);
}
/// <summary>
/// Determines all possible matches for this definition using the provided <paramref name="positions"/>.
/// This includes OVERLAPPING matches. It's up to the actual matcher to make decisions based on which
/// matches to accept. This allows the matcher to prioritize matching certain positions over others.
/// </summary>
public IEnumerable<OptionStrategyDefinitionMatch> Match(
OptionStrategyMatcherOptions options,
OptionPositionCollection positions
)
{
// TODO : Pass OptionStrategyMatcherOptions in and respect applicable options
if (positions.Count < LegCount)
{
return Enumerable.Empty<OptionStrategyDefinitionMatch>();
}
var multiplier = int.MaxValue;
// first check underlying lots has correct sign and sufficient magnitude
var underlyingLotsSign = Math.Sign(UnderlyingLots);
if (underlyingLotsSign != 0)
{
var underlyingPositionSign = Math.Sign(positions.UnderlyingQuantity);
if (underlyingLotsSign != underlyingPositionSign ||
Math.Abs(positions.UnderlyingQuantity) < Math.Abs(UnderlyingLots))
{
return Enumerable.Empty<OptionStrategyDefinitionMatch>();
}
// set multiplier for underlying
multiplier = positions.UnderlyingQuantity / UnderlyingLots;
}
// TODO : Consider add OptionStrategyLegDefinition for underlying for consistency purposes.
// Might want to enforce that it's always the first leg definition as well for easier slicing.
return Match(options,
ImmutableList<OptionStrategyLegDefinitionMatch>.Empty,
ImmutableList<OptionPosition>.Empty,
positions,
multiplier
).Distinct();
}
private IEnumerable<OptionStrategyDefinitionMatch> Match(
OptionStrategyMatcherOptions options,
ImmutableList<OptionStrategyLegDefinitionMatch> legMatches,
ImmutableList<OptionPosition> legPositions,
OptionPositionCollection positions,
int multiplier
)
{
var nextLegIndex = legPositions.Count;
if (nextLegIndex == Legs.Count)
{
if (nextLegIndex > 0)
{
yield return new OptionStrategyDefinitionMatch(this, legMatches, multiplier);
}
}
else if (positions.Count >= LegCount - nextLegIndex)
{
// grab the next leg definition and perform the match, restricting total to configured maximum per leg
var nextLeg = Legs[nextLegIndex];
var maxLegMatch = options.GetMaximumLegMatches(nextLegIndex);
foreach (var legMatch in nextLeg.Match(options, legPositions, positions).Take(maxLegMatch))
{
// add match to the match we're constructing and deduct matched position from positions collection
// we track the min multiplier in line so when we're done, we have the total number of matches for
// the matched set of positions in this 'thread' (OptionStrategy.Quantity)
foreach (var definitionMatch in Match(options,
legMatches.Add(legMatch),
legPositions.Add(legMatch.Position),
positions - legMatch.Position,
Math.Min(multiplier, legMatch.Multiplier)
))
{
yield return definitionMatch;
}
}
}
else
{
// positions.Count < LegsCount indicates a failed match
// could include partial matches, would allow an algorithm to determine if adding a
// new position could help reduce overall margin exposure by completing a strategy
}
}
/// <summary>Returns a string that represents the current object.</summary>
/// <returns>A string that represents the current object.</returns>
public override string ToString()
{
return Name;
}
/// <summary>
/// Factory function for creating definitions
/// </summary>
public static OptionStrategyDefinition Create(string name, int underlyingLots, params OptionStrategyLegDefinition[] legs)
{
return new OptionStrategyDefinition(name, underlyingLots, legs);
}
/// <summary>
/// Factory function for creating definitions
/// </summary>
public static OptionStrategyDefinition Create(string name, params OptionStrategyLegDefinition[] legs)
{
return new OptionStrategyDefinition(name, 0, legs);
}
/// <summary>
/// Factory function for creating definitions
/// </summary>
public static OptionStrategyDefinition Create(string name, params Func<Builder, Builder>[] predicates)
{
return predicates.Aggregate(new Builder(name),
(builder, predicate) => predicate(builder)
).Build();
}
/// <summary>
/// Factory function for creating a call leg definition
/// </summary>
public static OptionStrategyLegDefinition CallLeg(int quantity,
params Expression<Func<IReadOnlyList<OptionPosition>, OptionPosition, bool>>[] predicates
)
{
return OptionStrategyLegDefinition.Create(OptionRight.Call, quantity, predicates);
}
/// <summary>
/// Factory function for creating a put leg definition
/// </summary>
public static OptionStrategyLegDefinition PutLeg(int quantity,
params Expression<Func<IReadOnlyList<OptionPosition>, OptionPosition, bool>>[] predicates
)
{
return OptionStrategyLegDefinition.Create(OptionRight.Put, quantity, predicates);
}
/// <summary>
/// Builder class supporting fluent syntax in constructing <see cref="OptionStrategyDefinition"/>.
/// </summary>
public class Builder
{
private readonly string _name;
private int _underlyingLots;
private List<OptionStrategyLegDefinition> _legs;
/// <summary>
/// Initializes a new instance of the <see cref="Builder"/> class
/// </summary>
public Builder(string name)
{
_name = name;
_legs = new List<OptionStrategyLegDefinition>();
}
/// <summary>
/// Sets the required number of underlying lots
/// </summary>
public Builder WithUnderlyingLots(int lots)
{
if (_underlyingLots != 0)
{
throw new InvalidOperationException("Underlying lots has already been set.");
}
_underlyingLots = lots;
return this;
}
/// <summary>
/// Adds a call leg
/// </summary>
public Builder WithCall(int quantity,
params Expression<Func<IReadOnlyList<OptionPosition>, OptionPosition, bool>>[] predicates
)
{
_legs.Add(OptionStrategyLegDefinition.Create(OptionRight.Call, quantity, predicates));
return this;
}
/// <summary>
/// Adds a put leg
/// </summary>
public Builder WithPut(int quantity,
params Expression<Func<IReadOnlyList<OptionPosition>, OptionPosition, bool>>[] predicates
)
{
_legs.Add(OptionStrategyLegDefinition.Create(OptionRight.Put, quantity, predicates));
return this;
}
/// <summary>
/// Builds the <see cref="OptionStrategyDefinition"/>
/// </summary>
public OptionStrategyDefinition Build()
{
return new OptionStrategyDefinition(_name, _underlyingLots, _legs);
}
}
/// <summary>Returns an enumerator that iterates through the collection.</summary>
/// <returns>An enumerator that can be used to iterate through the collection.</returns>
public IEnumerator<OptionStrategyLegDefinition> GetEnumerator()
{
return Legs.GetEnumerator();
}
/// <summary>Returns an enumerator that iterates through a collection.</summary>
/// <returns>An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.</returns>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
@@ -0,0 +1,209 @@
/*
* 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.Linq;
using System.Collections.Generic;
namespace QuantConnect.Securities.Option.StrategyMatcher
{
/// <summary>
/// Defines a match of <see cref="OptionPosition"/> to a <see cref="OptionStrategyDefinition"/>
/// </summary>
public class OptionStrategyDefinitionMatch : IEquatable<OptionStrategyDefinitionMatch>
{
/// <summary>
/// The <see cref="OptionStrategyDefinition"/> matched
/// </summary>
public OptionStrategyDefinition Definition { get; }
/// <summary>
/// The number of times the definition is able to match the available positions.
/// Since definitions are formed at the 'unit' level, such as having 1 contract,
/// the multiplier defines how many times the definition matched. This multiplier
/// is used to scale the quantity defined in each leg definition when creating the
/// <see cref="OptionStrategy"/> objects.
/// </summary>
public int Multiplier { get; }
/// <summary>
/// The <see cref="OptionStrategyLegDefinitionMatch"/> instances matched to the definition.
/// </summary>
public IReadOnlyList<OptionStrategyLegDefinitionMatch> Legs { get; }
/// <summary>
/// Initializes a new instance of the <see cref="OptionStrategyDefinitionMatch"/> class
/// </summary>
public OptionStrategyDefinitionMatch(
OptionStrategyDefinition definition,
IReadOnlyList<OptionStrategyLegDefinitionMatch> legs,
int multiplier
)
{
Legs = legs;
Multiplier = multiplier;
Definition = definition;
}
/// <summary>
/// Deducts the matched positions from the specified <paramref name="positions"/> taking into account the multiplier
/// </summary>
public OptionPositionCollection RemoveFrom(OptionPositionCollection positions)
{
var optionPositions = Legs.Select(leg => leg.CreateOptionPosition(Multiplier));
if (Definition.UnderlyingLots != 0)
{
optionPositions = optionPositions.Concat(new[]
{
new OptionPosition(Legs[0].Position.Symbol.Underlying, Definition.UnderlyingLots * Multiplier)
});
}
return positions.RemoveRange(optionPositions);
}
/// <summary>
/// Creates the <see cref="OptionStrategy"/> instance this match represents
/// </summary>
public OptionStrategy CreateStrategy()
{
var legs = Legs
// if Definition.UnderlyingLots is not 0, we will create the underlying leg separately
.Where(leg => leg.Position.Symbol.HasUnderlying || Definition.UnderlyingLots == 0)
.Select(leg => leg.CreateOptionStrategyLeg(Multiplier));
if (Definition.UnderlyingLots != 0)
{
legs = legs.Concat([OptionStrategy.UnderlyingLegData.Create(Definition.UnderlyingLots * Multiplier, Legs[0].Position.Underlying)]);
}
return OptionStrategy.Create(Definition.Name, legs);
}
/// <summary>Indicates whether the current object is equal to another object of the same type.</summary>
/// <param name="other">An object to compare with this object.</param>
/// <returns>true if the current object is equal to the <paramref name="other" /> parameter; otherwise, false.</returns>
public bool Equals(OptionStrategyDefinitionMatch other)
{
if (ReferenceEquals(null, other))
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
if (!Equals(Definition, other.Definition))
{
return false;
}
// index legs by OptionPosition so we can do the equality while ignoring ordering
var positions = other.Legs.ToDictionary(leg => leg.Position, leg => leg.Multiplier);
foreach (var leg in other.Legs)
{
int multiplier;
if (!positions.TryGetValue(leg.Position, out multiplier))
{
return false;
}
if (leg.Multiplier != multiplier)
{
return false;
}
}
return true;
}
/// <summary>Determines whether the specified object is equal to the current object.</summary>
/// <param name="obj">The object to compare with the current object. </param>
/// <returns>true if the specified object is equal to the current object; otherwise, false.</returns>
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
if (ReferenceEquals(this, obj))
{
return true;
}
if (obj.GetType() != GetType())
{
return false;
}
return Equals((OptionStrategyDefinitionMatch) obj);
}
/// <summary>Serves as the default hash function. </summary>
/// <returns>A hash code for the current object.</returns>
public override int GetHashCode()
{
unchecked
{
// we want to ensure that the ordering of legs does not impact equality operators in
// pursuit of this, we compute the hash codes of each leg, placing them into an array
// and then sort the array. using the sorted array, aggregates the hash codes
var hashCode = Definition.GetHashCode();
var arr = new int[Legs.Count];
for (int i = 0; i < Legs.Count; i++)
{
arr[i] = Legs[i].GetHashCode();
}
Array.Sort(arr);
for (int i = 0; i < arr.Length; i++)
{
hashCode = (hashCode * 397) ^ arr[i];
}
return hashCode;
}
}
/// <summary>Returns a string that represents the current object.</summary>
/// <returns>A string that represents the current object.</returns>
public override string ToString()
{
return $"{Definition.Name}: {string.Join("|", Legs.Select(leg => leg.Position))}";
}
/// <summary>
/// OptionStrategyDefinitionMatch == Operator
/// </summary>
/// <returns>True if they are the same</returns>
public static bool operator ==(OptionStrategyDefinitionMatch left, OptionStrategyDefinitionMatch right)
{
return Equals(left, right);
}
/// <summary>
/// OptionStrategyDefinitionMatch != Operator
/// </summary>
/// <returns>True if they are not the same</returns>
public static bool operator !=(OptionStrategyDefinitionMatch left, OptionStrategyDefinitionMatch right)
{
return !Equals(left, right);
}
}
}
@@ -0,0 +1,571 @@
/*
* 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.Immutable;
using System.Linq;
using System.Reflection;
namespace QuantConnect.Securities.Option.StrategyMatcher
{
/// <summary>
/// Provides a listing of pre-defined <see cref="OptionStrategyDefinition"/>
/// These definitions are blueprints for <see cref="OptionStrategy"/> instances.
/// Factory functions for those can be found at <see cref="OptionStrategies"/>
/// </summary>
public static class OptionStrategyDefinitions
{
// lazy since 'AllDefinitions' is at top of file and static members are evaluated in order
private static readonly Lazy<ImmutableList<OptionStrategyDefinition>> All
= new Lazy<ImmutableList<OptionStrategyDefinition>>(() =>
typeof(OptionStrategyDefinitions)
.GetProperties(BindingFlags.Public | BindingFlags.Static)
.Where(property => property.PropertyType == typeof(OptionStrategyDefinition))
.Select(property => (OptionStrategyDefinition)property.GetValue(null))
.ToImmutableList()
);
/// <summary>
/// Collection of all OptionStrategyDefinitions
/// </summary>
public static ImmutableList<OptionStrategyDefinition> AllDefinitions
{
get
{
var strategies = All.Value;
return strategies
.SelectMany(optionStrategy => {
// when selling the strategy can get reverted and it's still valid, we need the definition to match against
var inverted = new OptionStrategyDefinition(optionStrategy.Name, optionStrategy.UnderlyingLots * -1,
optionStrategy.Legs.Select(leg => new OptionStrategyLegDefinition(leg.Right, leg.Quantity * -1, leg)));
if (strategies.Any(strategy => strategy.UnderlyingLots == inverted.UnderlyingLots
&& strategy.Legs.Count == inverted.Legs.Count
&& strategy.Legs.All(leg => inverted.Legs.
Any(invertedLeg => invertedLeg.Right == leg.Right
&& leg.Quantity == invertedLeg.Quantity
&& leg.All(predicate => invertedLeg.Any(invertedPredicate => invertedPredicate.ToString() == predicate.ToString()))))))
{
// some strategies inverted have a different name we already know, let's skip those
return new[] { optionStrategy };
}
return new[] { optionStrategy, inverted };
})
.ToImmutableList();
}
}
/// <summary>
/// Hold 1 lot of the underlying and sell 1 call contract
/// </summary>
/// <remarks>Inverse of the <see cref="ProtectiveCall"/></remarks>
public static OptionStrategyDefinition CoveredCall { get; }
= OptionStrategyDefinition.Create("Covered Call", 1,
OptionStrategyDefinition.CallLeg(-1)
);
/// <summary>
/// Hold -1 lot of the underlying and buy 1 call contract
/// </summary>
/// <remarks>Inverse of the <see cref="CoveredCall"/></remarks>
public static OptionStrategyDefinition ProtectiveCall { get; }
= OptionStrategyDefinition.Create("Protective Call", -1,
OptionStrategyDefinition.CallLeg(1)
);
/// <summary>
/// Hold -1 lot of the underlying and sell 1 put contract
/// </summary>
/// <remarks>Inverse of the <see cref="ProtectivePut"/></remarks>
public static OptionStrategyDefinition CoveredPut { get; }
= OptionStrategyDefinition.Create("Covered Put", -1,
OptionStrategyDefinition.PutLeg(-1)
);
/// <summary>
/// Hold 1 lot of the underlying and buy 1 put contract
/// </summary>
/// <remarks>Inverse of the <see cref="CoveredPut"/></remarks>
public static OptionStrategyDefinition ProtectivePut { get; }
= OptionStrategyDefinition.Create("Protective Put", 1,
OptionStrategyDefinition.PutLeg(1)
);
/// <summary>
/// Hold 1 lot of the underlying, sell 1 call contract and buy 1 put contract.
/// The strike price of the short call is below the strike of the long put with the same expiration.
/// </summary>
/// <remarks>Combination of <see cref="CoveredCall"/> and <see cref="ProtectivePut"/></remarks>
public static OptionStrategyDefinition ProtectiveCollar { get; }
= OptionStrategyDefinition.Create("Protective Collar", 1,
OptionStrategyDefinition.CallLeg(-1),
OptionStrategyDefinition.PutLeg(1, (legs, p) => p.Strike < legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration)
);
/// <summary>
/// Hold 1 lot of the underlying, sell 1 call contract and buy 1 put contract.
/// The strike price of the call and put are the same, with the same expiration.
/// </summary>
/// <remarks>A special case of <see cref="ProtectiveCollar"/></remarks>
public static OptionStrategyDefinition Conversion { get; }
= OptionStrategyDefinition.Create("Conversion", 1,
OptionStrategyDefinition.CallLeg(-1),
OptionStrategyDefinition.PutLeg(1, (legs, p) => p.Strike == legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration)
);
/// <summary>
/// Hold 1 lot of the underlying, sell 1 call contract and buy 1 put contract.
/// The strike price of the call and put are the same, with the same expiration.
/// </summary>
/// <remarks>Inverse of <see cref="Conversion"/></remarks>
public static OptionStrategyDefinition ReverseConversion { get; }
= OptionStrategyDefinition.Create("Reverse Conversion", -1,
OptionStrategyDefinition.CallLeg(1),
OptionStrategyDefinition.PutLeg(-1, (legs, p) => p.Strike == legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration)
);
/// <summary>
/// Sell 1 call contract without holding the underlying
/// </summary>
public static OptionStrategyDefinition NakedCall { get; }
= OptionStrategyDefinition.Create("Naked Call",
OptionStrategyDefinition.CallLeg(-1)
);
/// <summary>
/// Sell 1 put contract without holding the underlying
/// </summary>
public static OptionStrategyDefinition NakedPut { get; }
= OptionStrategyDefinition.Create("Naked Put",
OptionStrategyDefinition.PutLeg(-1)
);
/// <summary>
/// Bear Call Spread strategy consists of two calls with the same expiration but different strikes.
/// The strike price of the short call is below the strike of the long call. This is a credit spread.
/// </summary>
public static OptionStrategyDefinition BearCallSpread { get; }
= OptionStrategyDefinition.Create("Bear Call Spread",
OptionStrategyDefinition.CallLeg(-1),
OptionStrategyDefinition.CallLeg(+1, (legs, p) => p.Strike > legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration)
);
/// <summary>
/// Bear Put Spread strategy consists of two puts with the same expiration but different strikes.
/// The strike price of the short put is below the strike of the long put. This is a debit spread.
/// </summary>
public static OptionStrategyDefinition BearPutSpread { get; }
= OptionStrategyDefinition.Create("Bear Put Spread",
OptionStrategyDefinition.PutLeg(1),
OptionStrategyDefinition.PutLeg(-1, (legs, p) => p.Strike < legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration)
);
/// <summary>
/// Bull Call Spread strategy consists of two calls with the same expiration but different strikes.
/// The strike price of the short call is higher than the strike of the long call. This is a debit spread.
/// </summary>
public static OptionStrategyDefinition BullCallSpread { get; }
= OptionStrategyDefinition.Create("Bull Call Spread",
OptionStrategyDefinition.CallLeg(+1),
OptionStrategyDefinition.CallLeg(-1, (legs, p) => p.Strike > legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration)
);
/// <summary>
/// Method creates new Bull Put Spread strategy, that consists of two puts with the same expiration but
/// different strikes. The strike price of the short put is above the strike of the long put. This is a
/// credit spread.
/// </summary>
public static OptionStrategyDefinition BullPutSpread { get; }
= OptionStrategyDefinition.Create("Bull Put Spread",
OptionStrategyDefinition.PutLeg(-1),
OptionStrategyDefinition.PutLeg(+1, (legs, p) => p.Strike < legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration)
);
/// <summary>
/// Straddle strategy is a combination of buying a call and buying a put, both with the same strike price
/// and expiration.
/// </summary>
public static OptionStrategyDefinition Straddle { get; }
= OptionStrategyDefinition.Create("Straddle",
OptionStrategyDefinition.CallLeg(+1),
OptionStrategyDefinition.PutLeg(+1, (legs, p) => p.Strike == legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration)
);
/// <summary>
/// Short Straddle strategy is a combination of selling a call and selling a put, both with the same strike price
/// and expiration.
/// </summary>
/// <remarks>Inverse of the <see cref="Straddle"/></remarks>
public static OptionStrategyDefinition ShortStraddle { get; }
= OptionStrategyDefinition.Create("Short Straddle",
OptionStrategyDefinition.CallLeg(-1),
OptionStrategyDefinition.PutLeg(-1, (legs, p) => p.Strike == legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration)
);
/// <summary>
/// Strangle strategy consists of buying a call option and a put option with the same expiration date.
/// The strike price of the call is above the strike of the put.
/// </summary>
public static OptionStrategyDefinition Strangle { get; }
= OptionStrategyDefinition.Create("Strangle",
OptionStrategyDefinition.CallLeg(+1),
OptionStrategyDefinition.PutLeg(+1, (legs, p) => p.Strike < legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration)
);
/// <summary>
/// Strangle strategy consists of selling a call option and a put option with the same expiration date.
/// The strike price of the call is above the strike of the put.
/// </summary>
/// <remarks>Inverse of the <see cref="Strangle"/></remarks>
public static OptionStrategyDefinition ShortStrangle { get; }
= OptionStrategyDefinition.Create("Short Strangle",
OptionStrategyDefinition.CallLeg(-1),
OptionStrategyDefinition.PutLeg(-1, (legs, p) => p.Strike < legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration)
);
/// <summary>
/// Short Butterfly Call strategy consists of two short calls at a middle strike, and one long call each at a lower
/// and upper strike. The upper and lower strikes must both be equidistant from the middle strike.
/// </summary>
public static OptionStrategyDefinition ButterflyCall { get; }
= OptionStrategyDefinition.Create("Butterfly Call",
OptionStrategyDefinition.CallLeg(+1),
OptionStrategyDefinition.CallLeg(-2, (legs, p) => p.Strike >= legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration),
OptionStrategyDefinition.CallLeg(+1, (legs, p) => p.Strike >= legs[1].Strike,
(legs, p) => p.Expiration == legs[0].Expiration,
(legs, p) => p.Strike - legs[1].Strike == legs[1].Strike - legs[0].Strike)
);
/// <summary>
/// Butterfly Call strategy consists of two long calls at a middle strike, and one short call each at a lower
/// and upper strike. The upper and lower strikes must both be equidistant from the middle strike.
/// </summary>
public static OptionStrategyDefinition ShortButterflyCall { get; }
= OptionStrategyDefinition.Create("Short Butterfly Call",
OptionStrategyDefinition.CallLeg(-1),
OptionStrategyDefinition.CallLeg(+2, (legs, p) => p.Strike >= legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration),
OptionStrategyDefinition.CallLeg(-1, (legs, p) => p.Strike >= legs[1].Strike,
(legs, p) => p.Expiration == legs[0].Expiration,
(legs, p) => p.Strike - legs[1].Strike == legs[1].Strike - legs[0].Strike)
);
/// <summary>
/// Butterfly Put strategy consists of two short puts at a middle strike, and one long put each at a lower and
/// upper strike. The upper and lower strikes must both be equidistant from the middle strike.
/// </summary>
public static OptionStrategyDefinition ButterflyPut { get; }
= OptionStrategyDefinition.Create("Butterfly Put",
OptionStrategyDefinition.PutLeg(+1),
OptionStrategyDefinition.PutLeg(-2, (legs, p) => p.Strike >= legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration),
OptionStrategyDefinition.PutLeg(+1, (legs, p) => p.Strike >= legs[1].Strike,
(legs, p) => p.Expiration == legs[0].Expiration,
(legs, p) => p.Strike - legs[1].Strike == legs[1].Strike - legs[0].Strike)
);
/// <summary>
/// Short Butterfly Put strategy consists of two long puts at a middle strike, and one short put each at a lower and
/// upper strike. The upper and lower strikes must both be equidistant from the middle strike.
/// </summary>
public static OptionStrategyDefinition ShortButterflyPut { get; }
= OptionStrategyDefinition.Create("Short Butterfly Put",
OptionStrategyDefinition.PutLeg(-1),
OptionStrategyDefinition.PutLeg(+2, (legs, p) => p.Strike >= legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration),
OptionStrategyDefinition.PutLeg(-1, (legs, p) => p.Strike >= legs[1].Strike,
(legs, p) => p.Expiration == legs[0].Expiration,
(legs, p) => p.Strike - legs[1].Strike == legs[1].Strike - legs[0].Strike)
);
/// <summary>
/// Call Calendar Spread strategy is a short one call option and long a second call option with a more distant
/// expiration.
/// </summary>
public static OptionStrategyDefinition CallCalendarSpread { get; }
= OptionStrategyDefinition.Create("Call Calendar Spread",
OptionStrategyDefinition.CallLeg(-1),
OptionStrategyDefinition.CallLeg(+1, (legs, p) => p.Strike == legs[0].Strike,
(legs, p) => p.Expiration > legs[0].Expiration)
);
/// <summary>
/// Short Call Calendar Spread strategy is long one call option and short a second call option with a more distant
/// expiration.
/// </summary>
/// <remarks>Inverse of the <see cref="CallCalendarSpread"/></remarks>
public static OptionStrategyDefinition ShortCallCalendarSpread { get; }
= OptionStrategyDefinition.Create("Short Call Calendar Spread",
OptionStrategyDefinition.CallLeg(+1),
OptionStrategyDefinition.CallLeg(-1, (legs, p) => p.Strike == legs[0].Strike,
(legs, p) => p.Expiration > legs[0].Expiration)
);
/// <summary>
/// Put Calendar Spread strategy is a short one put option and long a second put option with a more distant
/// expiration.
/// </summary>
public static OptionStrategyDefinition PutCalendarSpread { get; }
= OptionStrategyDefinition.Create("Put Calendar Spread",
OptionStrategyDefinition.PutLeg(-1),
OptionStrategyDefinition.PutLeg(+1, (legs, p) => p.Strike == legs[0].Strike,
(legs, p) => p.Expiration > legs[0].Expiration)
);
/// <summary>
/// Short Put Calendar Spread strategy is long one put option and short a second put option with a more distant
/// expiration.
/// </summary>
/// <remarks>Inverse of the <see cref="PutCalendarSpread"/></remarks>
public static OptionStrategyDefinition ShortPutCalendarSpread { get; }
= OptionStrategyDefinition.Create("Short Put Calendar Spread",
OptionStrategyDefinition.PutLeg(+1),
OptionStrategyDefinition.PutLeg(-1, (legs, p) => p.Strike == legs[0].Strike,
(legs, p) => p.Expiration > legs[0].Expiration)
);
/// <summary>
/// Iron Butterfly strategy consists of a short ATM call, a short ATM put, a long OTM call, and a long OTM put.
/// The strike spread between ATM and OTM call and put are the same. All at the same expiration date.
/// </summary>
public static OptionStrategyDefinition IronButterfly { get; }
= OptionStrategyDefinition.Create("Iron Butterfly",
OptionStrategyDefinition.PutLeg(-1),
OptionStrategyDefinition.PutLeg(+1, (legs, p) => p.Strike < legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration),
OptionStrategyDefinition.CallLeg(-1, (legs, c) => c.Strike == legs[0].Strike,
(legs, c) => c.Expiration == legs[0].Expiration),
OptionStrategyDefinition.CallLeg(+1, (legs, c) => c.Strike == legs[0].Strike * 2 - legs[1].Strike,
(legs, c) => c.Expiration == legs[0].Expiration)
);
/// <summary>
/// Short Iron Butterfly strategy consists of a long ATM call, a long ATM put, a short OTM call, and a short OTM put.
/// The strike spread between ATM and OTM call and put are the same. All at the same expiration date.
/// </summary>
public static OptionStrategyDefinition ShortIronButterfly { get; }
= OptionStrategyDefinition.Create("Short Iron Butterfly",
OptionStrategyDefinition.PutLeg(+1),
OptionStrategyDefinition.PutLeg(-1, (legs, p) => p.Strike < legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration),
OptionStrategyDefinition.CallLeg(+1, (legs, c) => c.Strike == legs[0].Strike,
(legs, c) => c.Expiration == legs[0].Expiration),
OptionStrategyDefinition.CallLeg(-1, (legs, c) => c.Strike == legs[0].Strike * 2 - legs[1].Strike,
(legs, c) => c.Expiration == legs[0].Expiration)
);
/// <summary>
/// Iron Condor strategy is buying a put, selling a put with a higher strike price, selling a call and buying a call with a higher strike price.
/// All at the same expiration date
/// </summary>
public static OptionStrategyDefinition IronCondor { get; }
= OptionStrategyDefinition.Create("Iron Condor",
OptionStrategyDefinition.PutLeg(+1),
OptionStrategyDefinition.PutLeg(-1, (legs, p) => p.Strike > legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration),
OptionStrategyDefinition.CallLeg(-1, (legs, p) => p.Strike > legs[1].Strike,
(legs, p) => p.Expiration == legs[0].Expiration),
OptionStrategyDefinition.CallLeg(1, (legs, p) => p.Strike > legs[2].Strike,
(legs, p) => p.Expiration == legs[0].Expiration)
);
/// <summary>
/// Short Iron Condor strategy is selling a put, buying a put with a higher strike price, buying a call and selling a call with a higher strike price.
/// All at the same expiration date
/// </summary>
public static OptionStrategyDefinition ShortIronCondor { get; }
= OptionStrategyDefinition.Create("Short Iron Condor",
OptionStrategyDefinition.PutLeg(-1),
OptionStrategyDefinition.PutLeg(+1, (legs, p) => p.Strike > legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration),
OptionStrategyDefinition.CallLeg(+1, (legs, p) => p.Strike > legs[1].Strike,
(legs, p) => p.Expiration == legs[0].Expiration),
OptionStrategyDefinition.CallLeg(-1, (legs, p) => p.Strike > legs[2].Strike,
(legs, p) => p.Expiration == legs[0].Expiration)
);
/// <summary>
/// Long Box Spread strategy is long 1 call and short 1 put with the same strike,
/// while short 1 call and long 1 put with a higher, same strike. All options have the same expiry.
/// expiration.
/// </summary>
public static OptionStrategyDefinition BoxSpread { get; }
= OptionStrategyDefinition.Create("Box Spread",
OptionStrategyDefinition.PutLeg(+1),
OptionStrategyDefinition.PutLeg(-1, (legs, p) => p.Strike < legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration),
OptionStrategyDefinition.CallLeg(+1, (legs, c) => c.Strike == legs[1].Strike,
(legs, c) => c.Expiration == legs[0].Expiration),
OptionStrategyDefinition.CallLeg(-1, (legs, c) => c.Strike == legs[0].Strike,
(legs, c) => c.Expiration == legs[0].Expiration)
);
/// <summary>
/// Short Box Spread strategy is short 1 call and long 1 put with the same strike,
/// while long 1 call and short 1 put with a higher, same strike. All options have the same expiry.
/// expiration.
/// </summary>
public static OptionStrategyDefinition ShortBoxSpread { get; }
= OptionStrategyDefinition.Create("Short Box Spread",
OptionStrategyDefinition.PutLeg(-1),
OptionStrategyDefinition.PutLeg(+1, (legs, p) => p.Strike < legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration),
OptionStrategyDefinition.CallLeg(-1, (legs, c) => c.Strike == legs[1].Strike,
(legs, c) => c.Expiration == legs[0].Expiration),
OptionStrategyDefinition.CallLeg(+1, (legs, c) => c.Strike == legs[0].Strike,
(legs, c) => c.Expiration == legs[0].Expiration)
);
/// <summary>
/// Jelly Roll is short 1 call and long 1 call with the same strike but further expiry, together with
/// long 1 put and short 1 put with the same strike and expiries as calls.
/// </summary>
public static OptionStrategyDefinition JellyRoll { get; }
= OptionStrategyDefinition.Create("Jelly Roll",
OptionStrategyDefinition.CallLeg(-1),
OptionStrategyDefinition.CallLeg(+1, (legs, c) => c.Strike == legs[0].Strike,
(legs, c) => c.Expiration > legs[0].Expiration),
OptionStrategyDefinition.PutLeg(+1, (legs, p) => p.Strike == legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration),
OptionStrategyDefinition.PutLeg(-1, (legs, p) => p.Strike == legs[0].Strike,
(legs, p) => p.Expiration == legs[1].Expiration)
);
/// <summary>
/// Short Jelly Roll is long 1 call and short 1 call with the same strike but further expiry, together with
/// short 1 put and long 1 put with the same strike and expiries as calls.
/// </summary>
public static OptionStrategyDefinition ShortJellyRoll { get; }
= OptionStrategyDefinition.Create("Short Jelly Roll",
OptionStrategyDefinition.CallLeg(+1),
OptionStrategyDefinition.CallLeg(-1, (legs, c) => c.Strike == legs[0].Strike,
(legs, c) => c.Expiration > legs[0].Expiration),
OptionStrategyDefinition.PutLeg(-1, (legs, p) => p.Strike == legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration),
OptionStrategyDefinition.PutLeg(+1, (legs, p) => p.Strike == legs[0].Strike,
(legs, p) => p.Expiration == legs[1].Expiration)
);
/// <summary>
/// Bear Call Ladder strategy is short 1 call and long 2 calls, with ascending strike prices in order,
/// All options have the same expiry.
/// </summary>
public static OptionStrategyDefinition BearCallLadder { get; }
= OptionStrategyDefinition.Create("Bear Call Ladder",
OptionStrategyDefinition.CallLeg(-1),
OptionStrategyDefinition.CallLeg(+1, (legs, c) => c.Strike > legs[0].Strike,
(legs, c) => c.Expiration == legs[0].Expiration),
OptionStrategyDefinition.CallLeg(+1, (legs, c) => c.Strike > legs[1].Strike,
(legs, c) => c.Expiration == legs[0].Expiration)
);
/// <summary>
/// Bear Put Ladder strategy is long 1 put and short 2 puts, with descending strike prices in order,
/// All options have the same expiry.
/// </summary>
public static OptionStrategyDefinition BearPutLadder { get; }
= OptionStrategyDefinition.Create("Bear Put Ladder",
OptionStrategyDefinition.PutLeg(+1),
OptionStrategyDefinition.PutLeg(-1, (legs, p) => p.Strike < legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration),
OptionStrategyDefinition.PutLeg(-1, (legs, p) => p.Strike < legs[1].Strike,
(legs, p) => p.Expiration == legs[0].Expiration)
);
/// <summary>
/// Bull Call Ladder strategy is long 1 call and short 2 calls, with ascending strike prices in order,
/// All options have the same expiry.
/// </summary>
public static OptionStrategyDefinition BullCallLadder { get; }
= OptionStrategyDefinition.Create("Bull Call Ladder",
OptionStrategyDefinition.CallLeg(+1),
OptionStrategyDefinition.CallLeg(-1, (legs, c) => c.Strike > legs[0].Strike,
(legs, c) => c.Expiration == legs[0].Expiration),
OptionStrategyDefinition.CallLeg(-1, (legs, c) => c.Strike > legs[1].Strike,
(legs, c) => c.Expiration == legs[0].Expiration)
);
/// <summary>
/// Bull Put Ladder strategy is short 1 put and long 2 puts, with descending strike prices in order,
/// All options have the same expiry.
/// </summary>
public static OptionStrategyDefinition BullPutLadder { get; }
= OptionStrategyDefinition.Create("Bull Put Ladder",
OptionStrategyDefinition.PutLeg(-1),
OptionStrategyDefinition.PutLeg(+1, (legs, p) => p.Strike < legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration),
OptionStrategyDefinition.PutLeg(+1, (legs, p) => p.Strike < legs[1].Strike,
(legs, p) => p.Expiration == legs[0].Expiration)
);
/// <summary>
/// Call Backspread strategy is short 1 call and long 2 calls, with ascending strike prices in order,
/// both options have the same expiry.
/// </summary>
public static OptionStrategyDefinition CallBackspread { get; }
= OptionStrategyDefinition.Create("Call Backspread",
OptionStrategyDefinition.CallLeg(-1),
OptionStrategyDefinition.CallLeg(+2, (legs, c) => c.Strike > legs[0].Strike,
(legs, c) => c.Expiration == legs[0].Expiration)
);
/// <summary>
/// Put Backspread strategy is short 1 put and long 2 puts, with descending strike prices in order,
/// both options have the same expiry.
/// </summary>
public static OptionStrategyDefinition PutBackspread { get; }
= OptionStrategyDefinition.Create("Put Backspread",
OptionStrategyDefinition.PutLeg(-1),
OptionStrategyDefinition.PutLeg(+2, (legs, p) => p.Strike < legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration)
);
/// <summary>
/// Short Call Backspread strategy is long 1 call and short 2 calls, with ascending strike prices in order,
/// both options have the same expiry.
/// </summary>
public static OptionStrategyDefinition ShortCallBackspread { get; }
= OptionStrategyDefinition.Create("Short Call Backspread",
OptionStrategyDefinition.CallLeg(+1),
OptionStrategyDefinition.CallLeg(-2, (legs, c) => c.Strike > legs[0].Strike,
(legs, c) => c.Expiration == legs[0].Expiration)
);
/// <summary>
/// Short Put Backspread strategy is long 1 put and short 2 puts, with descending strike prices in order,
/// both options have the same expiry.
/// </summary>
public static OptionStrategyDefinition ShortPutBackspread { get; }
= OptionStrategyDefinition.Create("Short Put Backspread",
OptionStrategyDefinition.PutLeg(+1),
OptionStrategyDefinition.PutLeg(-2, (legs, p) => p.Strike < legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration)
);
}
}
@@ -0,0 +1,199 @@
/*
* 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;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
namespace QuantConnect.Securities.Option.StrategyMatcher
{
/// <summary>
/// Defines a single option leg in an option strategy. This definition supports direct
/// match (does position X match the definition) and position collection filtering (filter
/// collection to include matches)
/// </summary>
public class OptionStrategyLegDefinition : IEnumerable<OptionStrategyLegPredicate>
{
private readonly OptionStrategyLegPredicate[] _predicates;
/// <summary>
/// Gets the unit quantity
/// </summary>
public int Quantity { get; }
/// <summary>
/// Gets the contract right
/// </summary>
public OptionRight Right { get; }
/// <summary>
/// Initializes a new instance of the <see cref="OptionStrategyLegDefinition"/> class
/// </summary>
/// <param name="right">The leg's contract right</param>
/// <param name="quantity">The leg's unit quantity</param>
/// <param name="predicates">The conditions a position must meet in order to match this definition</param>
public OptionStrategyLegDefinition(OptionRight right, int quantity, IEnumerable<OptionStrategyLegPredicate> predicates)
{
Right = right;
Quantity = quantity;
_predicates = predicates.ToArray();
}
/// <summary>
/// Yields all possible matches for this leg definition held within the collection of <paramref name="positions"/>
/// </summary>
/// <param name="options">Strategy matcher options guiding matching behaviors</param>
/// <param name="legs">The preceding legs already matched for the parent strategy definition</param>
/// <param name="positions">The remaining, unmatched positions available to be matched against</param>
/// <returns>An enumerable of potential matches</returns>
public IEnumerable<OptionStrategyLegDefinitionMatch> Match(
OptionStrategyMatcherOptions options,
IReadOnlyList<OptionPosition> legs,
OptionPositionCollection positions
)
{
foreach (var position in options.Enumerate(Filter(legs, positions, false)))
{
var multiplier = position.Quantity / Quantity;
if (multiplier != 0)
{
yield return new OptionStrategyLegDefinitionMatch(multiplier,
position.WithQuantity(multiplier * Quantity)
);
}
}
}
/// <summary>
/// Filters the provided <paramref name="positions"/> collection such that any remaining positions are all
/// valid options that match this leg definition instance.
/// </summary>
public OptionPositionCollection Filter(IReadOnlyList<OptionPosition> legs, OptionPositionCollection positions, bool includeUnderlying = true)
{
// first filter down to applicable right
positions = positions.Slice(Right, includeUnderlying);
if (positions.IsEmpty)
{
return positions;
}
// second filter according to the required side
var side = (PositionSide) Math.Sign(Quantity);
positions = positions.Slice(side, includeUnderlying);
if (positions.IsEmpty)
{
return positions;
}
// these are ordered such that indexed filters are performed force and
// opaque/complex predicates follow since they require full table scans
foreach (var predicate in _predicates)
{
positions = predicate.Filter(legs, positions, includeUnderlying);
if (positions.IsEmpty)
{
break;
}
}
// at this point, every position in the positions
// collection is a valid match for this definition
return positions;
}
/// <summary>
/// Creates the appropriate <see cref="Leg"/> for the specified <paramref name="match"/>
/// </summary>
public Leg CreateLegData(OptionStrategyLegDefinitionMatch match)
{
return CreateLegData(
match.Position.Symbol,
match.Position.Quantity / Quantity
);
}
/// <summary>
/// Creates the appropriate <see cref="OptionStrategy.LegData"/> with the specified <paramref name="quantity"/>
/// </summary>
public static Leg CreateLegData(Symbol symbol, int quantity)
{
if (symbol.SecurityType == SecurityType.Option)
{
return OptionStrategy.OptionLegData.Create(quantity, symbol);
}
return OptionStrategy.UnderlyingLegData.Create(quantity);
}
/// <summary>
/// Determines whether or not this leg definition matches the specified <paramref name="position"/>,
/// and if so, what the resulting quantity of the <see cref="OptionStrategy.OptionLegData"/> should be.
/// </summary>
public bool TryMatch(OptionPosition position, out Leg leg)
{
if (Right != position.Right ||
Math.Sign(Quantity) != Math.Sign(position.Quantity))
{
leg = null;
return false;
}
var quantity = position.Quantity / Quantity;
if (quantity == 0)
{
leg = null;
return false;
}
leg = position.Symbol.SecurityType == SecurityType.Option
? (Leg) OptionStrategy.OptionLegData.Create(quantity, position.Symbol)
: OptionStrategy.UnderlyingLegData.Create(quantity);
return true;
}
/// <summary>
/// Creates a new <see cref="OptionStrategyLegDefinition"/> matching the specified parameters
/// </summary>
public static OptionStrategyLegDefinition Create(OptionRight right, int quantity,
IEnumerable<Expression<Func<IReadOnlyList<OptionPosition>, OptionPosition, bool>>> predicates
)
{
return new OptionStrategyLegDefinition(right, quantity,
// sort predicates such that indexed predicates are evaluated first
// this leaves fewer positions to be evaluated by the full table scan
predicates.Select(OptionStrategyLegPredicate.Create).OrderBy(p => p.IsIndexed ? 0 : 1)
);
}
/// <summary>Returns an enumerator that iterates through the collection.</summary>
/// <returns>An enumerator that can be used to iterate through the collection.</returns>
public IEnumerator<OptionStrategyLegPredicate> GetEnumerator()
{
foreach (var predicate in _predicates)
{
yield return predicate;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
@@ -0,0 +1,161 @@
/*
* 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;
using System;
namespace QuantConnect.Securities.Option.StrategyMatcher
{
/// <summary>
/// Defines the item result type of <see cref="OptionStrategyLegDefinition.Match"/>, containing the number of
/// times the leg definition matched the position (<see cref="Multiplier"/>) and applicable portion of the position.
/// </summary>
public struct OptionStrategyLegDefinitionMatch : IEquatable<OptionStrategyLegDefinitionMatch>
{
/// <summary>
/// The number of times the definition is able to match the position. For example,
/// if the definition requires +2 contracts and the algorithm's position has +5
/// contracts, then this multiplier would equal 2.
/// </summary>
public int Multiplier { get; }
/// <summary>
/// The position that was successfully matched with the total quantity matched. For example,
/// if the definition requires +2 contracts and this multiplier equals 2, then this position
/// would have a quantity of 4. This may be different than the remaining/total quantity
/// available in the positions collection.
/// </summary>
public OptionPosition Position { get; }
/// <summary>
/// Initializes a new instance of the <see cref="OptionStrategyLegDefinitionMatch"/> struct
/// </summary>
/// <param name="multiplier">The number of times the positions matched the leg definition</param>
/// <param name="position">The position that matched the leg definition</param>
public OptionStrategyLegDefinitionMatch(int multiplier, OptionPosition position)
{
Position = position;
Multiplier = multiplier;
}
/// <summary>
/// Creates the appropriate type of <see cref="Leg"/> for this matched position
/// </summary>
/// <param name="multiplier">The multiplier to use for creating the leg data. This multiplier will be
/// the minimum multiplier of all legs within a strategy definition match. Each leg defines its own
/// multiplier which is the max matches for that leg and the strategy definition's multiplier is the
/// min of the individual legs.</param>
public Leg CreateOptionStrategyLeg(int multiplier)
{
var quantity = Position.Quantity;
if (Multiplier != multiplier)
{
if (multiplier > Multiplier)
{
throw new ArgumentOutOfRangeException(nameof(multiplier), "Unable to create strategy leg with a larger multiplier than matched.");
}
// back out the unit quantity and scale it up to the requested multiplier
var unit = Position.Quantity / Multiplier;
quantity = unit * multiplier;
}
return Position.IsUnderlying
? (Leg) OptionStrategy.UnderlyingLegData.Create(quantity, Position.Symbol)
: OptionStrategy.OptionLegData.Create(quantity, Position.Symbol);
}
/// <summary>
/// Creates the appropriate <see cref="OptionPosition"/> for this matched position
/// </summary>
/// <param name="multiplier">The multiplier to use for creating the OptionPosition. This multiplier will be
/// the minimum multiplier of all legs within a strategy definition match. Each leg defines its own
/// multiplier which is the max matches for that leg and the strategy definition's multiplier is the
/// min of the individual legs.</param>
public OptionPosition CreateOptionPosition(int multiplier)
{
var quantity = Position.Quantity;
if (Multiplier != multiplier)
{
if (multiplier > Multiplier)
{
throw new ArgumentOutOfRangeException(nameof(multiplier), "Unable to create strategy leg with a larger multiplier than matched.");
}
// back out the unit quantity and scale it up to the requested multiplier
var unit = Position.Quantity / Multiplier;
quantity = unit * multiplier;
}
return new OptionPosition(Position.Symbol, quantity);
}
/// <summary>Indicates whether the current object is equal to another object of the same type.</summary>
/// <param name="other">An object to compare with this object.</param>
/// <returns>true if the current object is equal to the <paramref name="other" /> parameter; otherwise, false.</returns>
public bool Equals(OptionStrategyLegDefinitionMatch other)
{
return Multiplier == other.Multiplier && Position.Equals(other.Position);
}
/// <summary>Indicates whether this instance and a specified object are equal.</summary>
/// <param name="obj">The object to compare with the current instance. </param>
/// <returns>true if <paramref name="obj" /> and this instance are the same type and represent the same value; otherwise, false. </returns>
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
return obj is OptionStrategyLegDefinitionMatch && Equals((OptionStrategyLegDefinitionMatch) obj);
}
/// <summary>Returns the hash code for this instance.</summary>
/// <returns>A 32-bit signed integer that is the hash code for this instance.</returns>
public override int GetHashCode()
{
unchecked
{
return (Multiplier * 397) ^ Position.GetHashCode();
}
}
/// <summary>Returns the fully qualified type name of this instance.</summary>
/// <returns>The fully qualified type name.</returns>
public override string ToString()
{
return $"{Multiplier} Matches|{Position}";
}
/// <summary>
/// OptionStrategyLegDefinitionMatch == Operator
/// </summary>
/// <returns>True if they are equal</returns>
public static bool operator ==(OptionStrategyLegDefinitionMatch left, OptionStrategyLegDefinitionMatch right)
{
return left.Equals(right);
}
/// <summary>
/// OptionStrategyLegDefinitionMatch != Operator
/// </summary>
/// <returns>True if they are not equal</returns>
public static bool operator !=(OptionStrategyLegDefinitionMatch left, OptionStrategyLegDefinitionMatch right)
{
return !left.Equals(right);
}
}
}
@@ -0,0 +1,245 @@
/*
* 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.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using QuantConnect.Util;
namespace QuantConnect.Securities.Option.StrategyMatcher
{
/// <summary>
/// Defines a condition under which a particular <see cref="OptionPosition"/> can be combined with
/// a preceding list of leg (also of type <see cref="OptionPosition"/>) to achieve a particular
/// option strategy.
/// </summary>
public class OptionStrategyLegPredicate
{
private readonly BinaryComparison _comparison;
private readonly IOptionStrategyLegPredicateReferenceValue _reference;
private readonly Func<IReadOnlyList<OptionPosition>, OptionPosition, bool> _predicate;
private readonly Expression<Func<IReadOnlyList<OptionPosition>, OptionPosition, bool>> _expression;
/// <summary>
/// Determines whether or not this predicate is able to utilize <see cref="OptionPositionCollection"/> indexes.
/// </summary>
public bool IsIndexed => _comparison != null && _reference != null;
/// <summary>
/// Initializes a new instance of the <see cref="OptionStrategyLegPredicate"/> class
/// </summary>
/// <param name="comparison">The <see cref="BinaryComparison"/> invoked</param>
/// <param name="reference">The reference value, such as a strike price, encapsulated within the
/// <see cref="IOptionStrategyLegPredicateReferenceValue"/> to enable resolving the value from different potential sets.</param>
/// <param name="predicate">The compiled predicate expression</param>
/// <param name="expression">The predicate expression, from which, all other values were derived.</param>
public OptionStrategyLegPredicate(
BinaryComparison comparison,
IOptionStrategyLegPredicateReferenceValue reference,
Func<IReadOnlyList<OptionPosition>, OptionPosition, bool> predicate,
Expression<Func<IReadOnlyList<OptionPosition>, OptionPosition, bool>> expression
)
{
_reference = reference;
_predicate = predicate;
_comparison = comparison;
_expression = expression;
}
/// <summary>
/// Determines whether or not the provided combination of preceding <paramref name="legs"/>
/// and current <paramref name="position"/> adhere to this predicate's requirements.
/// </summary>
public bool Matches(IReadOnlyList<OptionPosition> legs, OptionPosition position)
{
try
{
return _predicate(legs, position);
}
catch (InvalidOperationException)
{
// attempt to access option SecurityIdentifier values, such as strike, on the underlying
// this simply means we don't match and can safely ignore this exception. now, this does
// somewhat indicate a potential design flaw, but I content that this is better than having
// to manage the underlying position separately throughout the entire matching process.
return false;
}
}
/// <summary>
/// Filters the specified <paramref name="positions"/> by applying this predicate based on the referenced legs.
/// </summary>
public OptionPositionCollection Filter(IReadOnlyList<OptionPosition> legs, OptionPositionCollection positions, bool includeUnderlying)
{
if (!IsIndexed)
{
// if the predicate references non-indexed properties or contains complex/multiple conditions then
// we'll need to do a full table scan. this is not always avoidable, but we should try to avoid it
return OptionPositionCollection.Empty.AddRange(
positions.Where(position => _predicate(legs, position))
);
}
var referenceValue = _reference.Resolve(legs);
switch (_reference.Target)
{
case PredicateTargetValue.Right: return positions.Slice((OptionRight) referenceValue, includeUnderlying);
case PredicateTargetValue.Strike: return positions.Slice(_comparison, (decimal) referenceValue, includeUnderlying);
case PredicateTargetValue.Expiration: return positions.Slice(_comparison, (DateTime) referenceValue, includeUnderlying);
default:
throw new ArgumentOutOfRangeException();
}
}
/// <summary>
/// Gets the underlying <see cref="IOptionStrategyLegPredicateReferenceValue"/> value used by this predicate.
/// </summary>
public IOptionStrategyLegPredicateReferenceValue GetReferenceValue()
{
return _reference;
}
/// <summary>
/// Creates a new <see cref="OptionStrategyLegPredicate"/> from the specified predicate <paramref name="expression"/>
/// </summary>
public static OptionStrategyLegPredicate Create(
Expression<Func<IReadOnlyList<OptionPosition>, OptionPosition, bool>> expression
)
{
// expr must NOT include compound comparisons
// expr is a lambda of one of the following forms:
// (legs, position) => position.{target} {comparison} legs[i].{reference-target}
// (legs, position) => legs[i].{reference-target} {comparison} position.{target}
// (legs, position) => position.{target} {comparison} {literal-reference-target}
// (legs, position) => {literal-reference-target} {comparison} position.{target}
// we want to make the comparison of a common form, specifically:
// position.{target} {comparison} {reference-target}
// this is so when we invoke OptionPositionCollection we have the correct comparison type
// for example, legs[0].Strike > position.Strike
// needs to be inverted into position.Strike < legs[0].Strike
// so we can call OptionPositionCollection.Slice(BinaryComparison.LessThan, legs[0].Strike);
try
{
var legsParameter = expression.Parameters[0];
var positionParameter = expression.Parameters[1];
var binary = expression.OfType<BinaryExpression>().Single(e => e.NodeType.IsBinaryComparison());
var comparison = BinaryComparison.FromExpressionType(binary.NodeType);
var leftReference = CreateReferenceValue(legsParameter, positionParameter, binary.Left);
var rightReference = CreateReferenceValue(legsParameter, positionParameter, binary.Right);
if (leftReference != null && rightReference != null)
{
throw new ArgumentException($"The provided expression is not of the required form: {expression}");
}
// we want the left side to be null, indicating position.{target}
// if not, then we need to flip the comparison operand
var reference = rightReference;
if (rightReference == null)
{
reference = leftReference;
comparison = comparison.FlipOperands();
}
return new OptionStrategyLegPredicate(comparison, reference, expression.Compile(), expression);
}
catch
{
// we can still handle arbitrary predicates, they just require a full search of the positions
// as we're unable to leverage any of the pre-build indexes via Slice methods.
return new OptionStrategyLegPredicate(null, null, expression.Compile(), expression);
}
}
/// <summary>
/// Creates a new <see cref="IOptionStrategyLegPredicateReferenceValue"/> from the specified lambda parameters
/// and expression to be evaluated.
/// </summary>
private static IOptionStrategyLegPredicateReferenceValue CreateReferenceValue(
Expression legsParameter,
Expression positionParameter,
Expression expression
)
{
// if we're referencing the position parameter then this isn't a reference value
// this 'value' is the positions being matched in OptionPositionCollection
// verify the legs parameter doesn't appear in here either
var expressions = expression.AsEnumerable().ToList();
var containsLegParameter = expressions.Any(e => ReferenceEquals(e, legsParameter));
var containsPositionParameter = expressions.Any(e => ReferenceEquals(e, positionParameter));
if (containsPositionParameter)
{
if (containsLegParameter)
{
throw new NotSupportedException("Expressions containing references to both parameters " +
"(legs and positions) on the same side of an equality operator are not supported."
);
}
// this expression is of the form position.Strike/position.Expiration/position.Right
// and as such, is not a reference value, simply return null
return null;
}
if (!containsLegParameter)
{
// this is a literal and we'll attempt to evaluate it.
var value = Expression.Lambda(expression).Compile().DynamicInvoke();
if (value == null)
{
throw new ArgumentNullException($"Failed to evaluate expression literal: {expressions}");
}
return ConstantOptionStrategyLegReferenceValue.Create(value);
}
// we're looking for an array indexer into the legs list
var methodCall = expressions.Single<MethodCallExpression>();
Debug.Assert(methodCall.Method.Name == "get_Item");
// compile and dynamically invoke the argument to get_Item(x) {legs[x]}
var arrayIndex = (int) Expression.Lambda(methodCall.Arguments[0]).Compile().DynamicInvoke();
// and then a member expression denoting the property (target)
var member = expressions.Single<MemberExpression>().Member;
var target = GetPredicateTargetValue(member.Name);
return new OptionStrategyLegPredicateReferenceValue(arrayIndex, target);
}
private static PredicateTargetValue GetPredicateTargetValue(string memberName)
{
switch (memberName)
{
case nameof(OptionPosition.Right): return PredicateTargetValue.Right;
case nameof(OptionPosition.Strike): return PredicateTargetValue.Strike;
case nameof(OptionPosition.Expiration): return PredicateTargetValue.Expiration;
default:
throw new NotImplementedException(
$"Failed to resolve member name '{memberName}' to {nameof(PredicateTargetValue)}"
);
}
}
/// <summary>Returns a string that represents the current object.</summary>
/// <returns>A string that represents the current object.</returns>
public override string ToString()
{
return _expression.ToString();
}
}
}
@@ -0,0 +1,71 @@
/*
* 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;
namespace QuantConnect.Securities.Option.StrategyMatcher
{
/// <summary>
/// Provides an implementation of <see cref="IOptionStrategyLegPredicateReferenceValue"/> that references an option
/// leg from the list of already matched legs by index. The property referenced is defined by <see cref="PredicateTargetValue"/>
/// </summary>
public class OptionStrategyLegPredicateReferenceValue : IOptionStrategyLegPredicateReferenceValue
{
private readonly int _index;
/// <summary>
/// Gets the target of this value
/// </summary>
public PredicateTargetValue Target { get; }
/// <summary>
/// Initializes a new instance of the <see cref="IOptionStrategyLegPredicateReferenceValue"/> class
/// </summary>
/// <param name="index">The legs list index</param>
/// <param name="target">The property value being referenced</param>
public OptionStrategyLegPredicateReferenceValue(int index, PredicateTargetValue target)
{
_index = index;
Target = target;
}
/// <summary>
/// Resolves the value of the comparand specified in an <see cref="OptionStrategyLegPredicate"/>.
/// For example, the predicate may include ... > legs[0].Strike, and upon evaluation, we need to
/// be able to extract leg[0].Strike for the currently contemplated set of legs adhering to a
/// strategy's definition.
/// </summary>
public object Resolve(IReadOnlyList<OptionPosition> legs)
{
if (_index >= legs.Count)
{
throw new InvalidOperationException(
$"OptionStrategyLegPredicateReferenceValue[{_index}] is unable to be resolved. Only {legs.Count} legs were provided."
);
}
var leg = legs[_index];
switch (Target)
{
case PredicateTargetValue.Right: return leg.Right;
case PredicateTargetValue.Strike: return leg.Strike;
case PredicateTargetValue.Expiration: return leg.Expiration;
default:
throw new ArgumentOutOfRangeException();
}
}
}
}
@@ -0,0 +1,40 @@
/*
* 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.Option.StrategyMatcher
{
/// <summary>
/// Defines a complete result from running the matcher on a collection of positions.
/// The matching process will return one these matches for every potential combination
/// of strategies conforming to the search settings and the positions provided.
/// </summary>
public class OptionStrategyMatch
{
/// <summary>
/// The strategies that were matched
/// </summary>
public List<OptionStrategy> Strategies { get; }
/// <summary>
/// Initializes a new instance of the <see cref="OptionStrategyMatch"/> class
/// </summary>
public OptionStrategyMatch(List<OptionStrategy> strategies)
{
Strategies = strategies;
}
}
}
@@ -0,0 +1,74 @@
/*
* 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.Option.StrategyMatcher
{
/// <summary>
/// Matches <see cref="OptionPositionCollection"/> against a collection of <see cref="OptionStrategyDefinition"/>
/// according to the <see cref="OptionStrategyMatcherOptions"/> provided.
/// </summary>
public class OptionStrategyMatcher
{
/// <summary>
/// Specifies options controlling how the matcher operates
/// </summary>
public OptionStrategyMatcherOptions Options { get; }
/// <summary>
/// Initializes a new instance of the <see cref="OptionStrategyMatcher"/> class
/// </summary>
/// <param name="options">Specifies definitions and other options controlling the matcher</param>
public OptionStrategyMatcher(OptionStrategyMatcherOptions options)
{
Options = options;
}
// TODO : Implement matching multiple permutations and using the objective function to select the best solution
/// <summary>
/// Using the definitions provided in <see cref="Options"/>, attempts to match all <paramref name="positions"/>.
/// The resulting <see cref="OptionStrategyMatch"/> presents a single, valid solution for matching as many positions
/// as possible.
/// </summary>
public OptionStrategyMatch MatchOnce(OptionPositionCollection positions)
{
// these definitions are enumerated according to the configured IOptionStrategyDefinitionEnumerator
var strategies = new List<OptionStrategy>();
foreach (var definition in Options.Definitions)
{
// simplest implementation here is to match one at a time, updating positions in between
// a better implementation would be to evaluate all possible matches and make decisions
// prioritizing positions that would require more margin if not matched
OptionStrategyDefinitionMatch match;
while (definition.TryMatchOnce(Options, positions, out match))
{
positions = match.RemoveFrom(positions);
strategies.Add(match.CreateStrategy());
}
if (positions.IsEmpty)
{
break;
}
}
return new OptionStrategyMatch(strategies);
}
}
}
@@ -0,0 +1,258 @@
/*
* 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;
namespace QuantConnect.Securities.Option.StrategyMatcher
{
/// <summary>
/// Defines options that influence how the matcher operates.
/// </summary>
/// <remarks>
/// Many properties in this type are not implemented in the matcher but are provided to document
/// the types of things that can be added to the matcher in the future as necessary. Some of the
/// features contemplated in this class would require updating the various matching/filtering/slicing
/// functions to accept these options, or a particular property. This is the case for the enumerators
/// which would be used to prioritize which positions to try and match first. A great implementation
/// of the <see cref="IOptionPositionCollectionEnumerator"/> would be to yield positions with the
/// highest margin requirements first. At time of writing, the goal is to achieve a workable rev0,
/// and we can later improve the efficiency/optimization of the matching process.
/// </remarks>
public class OptionStrategyMatcherOptions
{
/// <summary>
/// The maximum amount of time spent trying to find an optimal solution.
/// </summary>
public TimeSpan MaximumDuration { get; }
/// <summary>
/// The maximum number of matches to evaluate for the entire portfolio.
/// </summary>
public int MaximumSolutionCount { get; }
/// <summary>
/// Indexed by leg index, defines the max matches to evaluate per leg.
/// For example, MaximumCountPerLeg[1] is the max matches to evaluate
/// for the second leg (index=1).
/// </summary>
public IReadOnlyList<int> MaximumCountPerLeg { get; }
/// <summary>
/// The definitions to be used for matching.
/// </summary>
public IEnumerable<OptionStrategyDefinition> Definitions
=> _definitionEnumerator.Enumerate(_definitions);
/// <summary>
/// Objective function used to compare different match solutions for a given set of positions/definitions
/// </summary>
public IOptionStrategyMatchObjectiveFunction ObjectiveFunction { get; }
private readonly IReadOnlyList<OptionStrategyDefinition> _definitions;
private readonly IOptionPositionCollectionEnumerator _positionEnumerator;
private readonly IOptionStrategyDefinitionEnumerator _definitionEnumerator;
/// <summary>
/// Initializes a new instance of the <see cref="OptionStrategyMatcherOptions"/> class, providing
/// options that control the behavior of the <see cref="OptionStrategyMatcher"/>
/// </summary>
public OptionStrategyMatcherOptions(
IReadOnlyList<OptionStrategyDefinition> definitions,
IReadOnlyList<int> maximumCountPerLeg,
TimeSpan maximumDuration = default(TimeSpan),
int maximumSolutionCount = 100,
IOptionStrategyDefinitionEnumerator definitionEnumerator = null,
IOptionStrategyMatchObjectiveFunction objectiveFunction = null,
IOptionPositionCollectionEnumerator positionEnumerator = null
)
{
if (maximumDuration == default(TimeSpan))
{
maximumDuration = Time.OneMinute;
}
if (definitionEnumerator == null)
{
// by default we want more complex option strategies to have matching priority
definitionEnumerator = new DescendingByLegCountOptionStrategyDefinitionEnumerator();
}
if (objectiveFunction == null)
{
objectiveFunction = new UnmatchedPositionCountOptionStrategyMatchObjectiveFunction();
}
if (positionEnumerator == null)
{
positionEnumerator = new DefaultOptionPositionCollectionEnumerator();
}
_definitions = definitions;
MaximumDuration = maximumDuration;
ObjectiveFunction = objectiveFunction;
MaximumCountPerLeg = maximumCountPerLeg;
_positionEnumerator = positionEnumerator;
_definitionEnumerator = definitionEnumerator;
MaximumSolutionCount = maximumSolutionCount;
}
/// <summary>
/// Gets the maximum number of leg matches to be evaluated. This is to limit evaluating exponential
/// numbers of potential matches as a result of large numbers of unique option positions for the same
/// underlying security.
/// </summary>
public int GetMaximumLegMatches(int legIndex)
{
return MaximumCountPerLeg[legIndex];
}
/// <summary>
/// Enumerates the specified <paramref name="positions"/> according to the configured
/// <see cref="IOptionPositionCollectionEnumerator"/>
/// </summary>
public IEnumerable<OptionPosition> Enumerate(OptionPositionCollection positions)
{
return _positionEnumerator.Enumerate(positions);
}
/// <summary>
/// Creates a new <see cref="OptionStrategyMatcherOptions"/> with the specified <paramref name="definitions"/>,
/// with no limits of maximum matches per leg and default values for the remaining options
/// </summary>
public static OptionStrategyMatcherOptions ForDefinitions(params OptionStrategyDefinition[] definitions)
{
return ForDefinitions(definitions.AsEnumerable());
}
/// <summary>
/// Creates a new <see cref="OptionStrategyMatcherOptions"/> with the specified <paramref name="definitions"/>,
/// with no limits of maximum matches per leg and default values for the remaining options
/// </summary>
public static OptionStrategyMatcherOptions ForDefinitions(IEnumerable<OptionStrategyDefinition> definitions)
{
var maximumCountPerLeg = new[] {int.MaxValue, int.MaxValue, int.MaxValue, int.MaxValue, int.MaxValue};
return new OptionStrategyMatcherOptions(definitions.ToList(), maximumCountPerLeg);
}
/// <summary>
/// Specifies the maximum time provided for obtaining an optimal solution.
/// </summary>
public OptionStrategyMatcherOptions WithMaximumDuration(TimeSpan duration)
{
return new OptionStrategyMatcherOptions(
_definitions,
MaximumCountPerLeg,
duration,
MaximumSolutionCount,
_definitionEnumerator,
ObjectiveFunction,
_positionEnumerator
);
}
/// <summary>
/// Specifies the maximum number of solutions to evaluate via the objective function.
/// </summary>
public OptionStrategyMatcherOptions WithMaximumSolutionCount(int count)
{
return new OptionStrategyMatcherOptions(
_definitions,
MaximumCountPerLeg,
MaximumDuration,
count,
_definitionEnumerator,
ObjectiveFunction,
_positionEnumerator
);
}
/// <summary>
/// Specifies the maximum number of solutions per leg index in a solution. Matching is a recursive
/// process, for example, we'll find a very large number of positions to match the first leg. Matching
/// the second leg we'll see less, and third still even less. This is because each subsequent leg must
/// abide by all the previous legs. This parameter defines how many potential matches to evaluate at
/// each leg. For the first leg, we'll evaluate counts[0] matches. For the second leg we'll evaluate
/// counts[1] matches and so on. By decreasing this parameter we can evaluate more total, complete
/// solutions for the entire portfolio rather than evaluation every single permutation of matches for
/// a particular strategy definition, which grows in absurd exponential fashion as the portfolio grows.
/// </summary>
public OptionStrategyMatcherOptions WithMaximumCountPerLeg(IReadOnlyList<int> counts)
{
return new OptionStrategyMatcherOptions(
_definitions,
counts,
MaximumDuration,
MaximumSolutionCount,
_definitionEnumerator,
ObjectiveFunction,
_positionEnumerator
);
}
/// <summary>
/// Specifies a function used to evaluate how desirable a particular solution is. A good implementation for
/// this would be to minimize the total margin required to hold all of the positions.
/// </summary>
public OptionStrategyMatcherOptions WithObjectiveFunction(IOptionStrategyMatchObjectiveFunction function)
{
return new OptionStrategyMatcherOptions(
_definitions,
MaximumCountPerLeg,
MaximumDuration,
MaximumSolutionCount,
_definitionEnumerator,
function,
_positionEnumerator
);
}
/// <summary>
/// Specifies the order in which definitions are evaluated. Definitions evaluated sooner are more likely to
/// find matches than ones evaluated later.
/// </summary>
public OptionStrategyMatcherOptions WithDefinitionEnumerator(IOptionStrategyDefinitionEnumerator enumerator)
{
return new OptionStrategyMatcherOptions(
_definitions,
MaximumCountPerLeg,
MaximumDuration,
MaximumSolutionCount,
enumerator,
ObjectiveFunction,
_positionEnumerator
);
}
/// <summary>
/// Specifies the order in which positions are evaluated. Positions evaluated sooner are more likely to
/// find matches than ones evaluated later. A good implementation for this is its stand-alone margin required,
/// which would encourage the algorithm to match higher margin positions before matching lower margin positiosn.
/// </summary>
public OptionStrategyMatcherOptions WithPositionEnumerator(IOptionPositionCollectionEnumerator enumerator)
{
return new OptionStrategyMatcherOptions(
_definitions,
MaximumCountPerLeg,
MaximumDuration,
MaximumSolutionCount,
_definitionEnumerator,
ObjectiveFunction,
enumerator
);
}
}
}
@@ -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.
*/
namespace QuantConnect.Securities.Option.StrategyMatcher
{
/// <summary>
/// Specifies the type of value being compared against in a <see cref="OptionStrategyLegPredicate"/>.
/// These values define the limits of what can be filtered and must match available slice methods in
/// <see cref="OptionPositionCollection"/>
/// </summary>
public enum PredicateTargetValue
{
/// <summary>
/// Predicate matches on <see cref="OptionPosition.Right"/> (0)
/// </summary>
Right,
/// <summary>
/// Predicate match on <see cref="OptionPosition.Quantity"/> (1)
/// </summary>
Quantity,
/// <summary>
/// Predicate matches on <see cref="OptionPosition.Strike"/> (2)
/// </summary>
Strike,
/// <summary>
/// Predicate matches on <see cref="OptionPosition.Expiration"/> (3)
/// </summary>
Expiration
}
}
@@ -0,0 +1,44 @@
/*
* 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;
using System.Linq;
namespace QuantConnect.Securities.Option.StrategyMatcher
{
/// <summary>
/// Provides an implementation of <see cref="IOptionStrategyMatchObjectiveFunction"/> that evaluates the number of unmatched
/// positions, in number of contracts, giving precedence to solutions that have fewer unmatched contracts.
/// </summary>
public class UnmatchedPositionCountOptionStrategyMatchObjectiveFunction : IOptionStrategyMatchObjectiveFunction
{
/// <summary>
/// Computes the delta in matched vs unmatched positions, which gives precedence to solutions that match more contracts.
/// </summary>
public decimal ComputeScore(OptionPositionCollection input, OptionStrategyMatch match, OptionPositionCollection unmatched)
{
var value = 0m;
foreach (var strategy in match.Strategies)
{
foreach (var leg in strategy.OptionLegs.Concat<Leg>(strategy.UnderlyingLegs))
{
value += leg.Quantity;
}
}
return value - unmatched.Count;
}
}
}