chore: import upstream snapshot with attribution
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}")
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
+88
@@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
+82
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+35
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+49
@@ -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);
|
||||
}
|
||||
}
|
||||
+34
@@ -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
|
||||
}
|
||||
}
|
||||
+44
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user