/*
* 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 System.Runtime.CompilerServices;
using Python.Runtime;
using QuantConnect.Data;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Securities.FutureOption;
using QuantConnect.Securities.IndexOption;
using QuantConnect.Securities.Option;
namespace QuantConnect.Securities
{
///
/// Represents options symbols universe used in filtering.
///
public class OptionFilterUniverse : ContractSecurityFilterUniverse
{
private Option.Option _option;
// Fields used in relative strikes filter
private List _uniqueStrikes;
private bool _refreshUniqueStrikes;
private DateTime _lastExchangeDate;
private readonly decimal _underlyingScaleFactor = 1;
///
/// The underlying price data
///
protected BaseData UnderlyingInternal { get; set; }
///
/// The underlying price data
///
public BaseData Underlying
{
get
{
return UnderlyingInternal;
}
}
///
/// Constructs OptionFilterUniverse
///
/// The canonical option chain security
public OptionFilterUniverse(Option.Option option)
{
_option = option;
_underlyingScaleFactor = option.SymbolProperties.StrikeMultiplier;
}
///
/// Constructs OptionFilterUniverse
///
/// Used for testing only
public OptionFilterUniverse(Option.Option option, IEnumerable allData, BaseData underlying, decimal underlyingScaleFactor = 1)
: base(allData, underlying.EndTime)
{
_option = option;
UnderlyingInternal = underlying;
_refreshUniqueStrikes = true;
_underlyingScaleFactor = underlyingScaleFactor;
}
///
/// Refreshes this option filter universe and allows specifying if the exchange date changed from last call
///
/// All data for the option contracts
/// The current underlying last data point
/// The current local time
public void Refresh(IEnumerable allContractsData, BaseData underlying, DateTime localTime)
{
base.Refresh(allContractsData, localTime);
UnderlyingInternal = underlying;
_refreshUniqueStrikes = _lastExchangeDate != localTime.Date;
_lastExchangeDate = localTime.Date;
}
///
/// Determine if the given Option contract symbol is standard
///
/// True if standard
protected override bool IsStandard(Symbol symbol)
{
switch (symbol.SecurityType)
{
case SecurityType.FutureOption:
return FutureOptionSymbol.IsStandard(symbol);
case SecurityType.IndexOption:
return IndexOptionSymbol.IsStandard(symbol);
default:
return OptionSymbol.IsStandard(symbol);
}
}
///
/// Creates a new instance of the data type for the given symbol
///
/// A data instance for the given symbol
protected override OptionUniverse CreateDataInstance(Symbol symbol)
{
return new OptionUniverse()
{
Symbol = symbol,
Time = LocalTime
};
}
///
/// Adjusts the date to the next trading day if the current date is not a trading day, so that expiration filter is properly applied.
/// e.g. Selection for Mondays happen on Friday midnight (Saturday start), so if the minimum time to expiration is, say 0,
/// contracts expiring on Monday would be filtered out if the date is not properly adjusted to the next trading day (Monday).
///
/// The date to be adjusted
/// The adjusted date
protected override DateTime AdjustExpirationReferenceDate(DateTime referenceDate)
{
// Check whether the reference time is a tradable date:
if (!_option.Exchange.Hours.IsDateOpen(referenceDate))
{
referenceDate = _option.Exchange.Hours.GetNextTradingDay(referenceDate);
}
return referenceDate;
}
///
/// Applies filter selecting options contracts based on a range of strikes in relative terms
///
/// The minimum strike relative to the underlying price, for example, -1 would filter out contracts further than 1 strike below market price
/// The maximum strike relative to the underlying price, for example, +1 would filter out contracts further than 1 strike above market price
/// Universe with filter applied
public OptionFilterUniverse Strikes(int minStrike, int maxStrike)
{
if (UnderlyingInternal == null)
{
return this;
}
if (_refreshUniqueStrikes || _uniqueStrikes == null)
{
// Each day we need to recompute the unique strikes list.
_uniqueStrikes = AllSymbols.Select(x => x.ID.StrikePrice)
.Distinct()
.OrderBy(strikePrice => strikePrice)
.ToList();
_refreshUniqueStrikes = false;
}
// find the current price in the list of strikes
// When computing the strike prices we need to take into account
// that some option's strike prices are based on a fraction of
// the underlying. Thus we need to scale the underlying internal
// price so that we can find it among the strike prices
// using BinarySearch() method(as it is used below)
var exactPriceFound = true;
var index = _uniqueStrikes.BinarySearch(UnderlyingInternal.Price / _underlyingScaleFactor);
// Return value of BinarySearch (from MSDN):
// The zero-based index of item in the sorted List, if item is found;
// otherwise, a negative number that is the bitwise complement of the index of the next element that is larger than item
// or, if there is no larger element, the bitwise complement of Count.
if (index < 0)
{
// exact price not found
exactPriceFound = false;
if (index == ~_uniqueStrikes.Count)
{
// there is no greater price, return empty
return Empty();
}
index = ~index;
}
// compute the bounds, no need to worry about rounding and such
var indexMinPrice = index + minStrike;
var indexMaxPrice = index + maxStrike;
if (!exactPriceFound)
{
if (minStrike < 0 && maxStrike > 0)
{
indexMaxPrice--;
}
else if (minStrike > 0)
{
indexMinPrice--;
indexMaxPrice--;
}
}
if (indexMinPrice < 0)
{
indexMinPrice = 0;
}
else if (indexMinPrice >= _uniqueStrikes.Count)
{
// price out of range: return empty
return Empty();
}
if (indexMaxPrice < 0)
{
// price out of range: return empty
return Empty();
}
if (indexMaxPrice >= _uniqueStrikes.Count)
{
indexMaxPrice = _uniqueStrikes.Count - 1;
}
var minPrice = _uniqueStrikes[indexMinPrice];
var maxPrice = _uniqueStrikes[indexMaxPrice];
Data = Data
.Where(data =>
{
var price = data.ID.StrikePrice;
return price >= minPrice && price <= maxPrice;
}
).ToList();
return this;
}
///
/// Sets universe of call options (if any) as a selection
///
/// Universe with filter applied
public OptionFilterUniverse CallsOnly()
{
return Contracts(contracts => contracts.Where(x => x.Symbol.ID.OptionRight == OptionRight.Call));
}
///
/// Sets universe of put options (if any) as a selection
///
/// Universe with filter applied
public OptionFilterUniverse PutsOnly()
{
return Contracts(contracts => contracts.Where(x => x.Symbol.ID.OptionRight == OptionRight.Put));
}
///
/// Sets universe of a single call contract with the closest match to criteria given
///
/// The minimum days till expiry from the current time, closest expiry will be selected
/// The desire strike price distance from the current underlying price
/// Applicable to Naked Call, Covered Call, and Protective Call Option Strategy
/// Universe with filter applied
public OptionFilterUniverse NakedCall(int minDaysTillExpiry = 30, decimal strikeFromAtm = 0)
{
return SingleContract(OptionRight.Call, minDaysTillExpiry, strikeFromAtm);
}
///
/// Sets universe of a single put contract with the closest match to criteria given
///
/// The minimum days till expiry from the current time, closest expiry will be selected
/// The desire strike price distance from the current underlying price
/// Applicable to Naked Put, Covered Put, and Protective Put Option Strategy
/// Universe with filter applied
public OptionFilterUniverse NakedPut(int minDaysTillExpiry = 30, decimal strikeFromAtm = 0)
{
return SingleContract(OptionRight.Put, minDaysTillExpiry, strikeFromAtm);
}
private OptionFilterUniverse SingleContract(OptionRight right, int minDaysTillExpiry = 30, decimal strikeFromAtm = 0)
{
// Select the expiry as the nearest to set days later
var contractsForExpiry = GetContractsForExpiry(AllSymbols, minDaysTillExpiry);
var contracts = contractsForExpiry.Where(x => x.ID.OptionRight == right).ToList();
if (contracts.Count == 0)
{
return Empty();
}
// Select strike price
var strike = GetStrike(contracts, strikeFromAtm);
var selected = contracts.Single(x => x.ID.StrikePrice == strike);
return SymbolList(new List { selected });
}
///
/// Sets universe of 2 call contracts with the same expiry and different strike prices, with closest match to the criteria given
///
/// The minimum days till expiry from the current time, closest expiry will be selected
/// The desire strike price distance from the current underlying price of the higher strike price
/// The desire strike price distance from the current underlying price of the lower strike price
/// Applicable to Bear Call Spread and Bull Call Spread Option Strategy
/// Universe with filter applied
public OptionFilterUniverse CallSpread(int minDaysTillExpiry = 30, decimal higherStrikeFromAtm = 5, decimal? lowerStrikeFromAtm = null)
{
return Spread(OptionRight.Call, minDaysTillExpiry, higherStrikeFromAtm, lowerStrikeFromAtm);
}
///
/// Sets universe of 2 put contracts with the same expiry and different strike prices, with closest match to the criteria given
///
/// The minimum days till expiry from the current time, closest expiry will be selected
/// The desire strike price distance from the current underlying price of the higher strike price
/// The desire strike price distance from the current underlying price of the lower strike price
/// Applicable to Bear Put Spread and Bull Put Spread Option Strategy
/// Universe with filter applied
public OptionFilterUniverse PutSpread(int minDaysTillExpiry = 30, decimal higherStrikeFromAtm = 5, decimal? lowerStrikeFromAtm = null)
{
return Spread(OptionRight.Put, minDaysTillExpiry, higherStrikeFromAtm, lowerStrikeFromAtm);
}
private OptionFilterUniverse Spread(OptionRight right, int minDaysTillExpiry, decimal higherStrikeFromAtm, decimal? lowerStrikeFromAtm = null)
{
if (!lowerStrikeFromAtm.HasValue)
{
lowerStrikeFromAtm = -higherStrikeFromAtm;
}
if (higherStrikeFromAtm <= lowerStrikeFromAtm)
{
throw new ArgumentException("Spread(): strike price arguments must be in descending order, "
+ $"{nameof(higherStrikeFromAtm)}, {nameof(lowerStrikeFromAtm)}");
}
// Select the expiry as the nearest to set days later
var contractsForExpiry = GetContractsForExpiry(AllSymbols, minDaysTillExpiry);
var contracts = contractsForExpiry.Where(x => x.ID.OptionRight == right).ToList();
if (contracts.Count == 0)
{
return Empty();
}
// Select the strike prices with the set spread range
var lowerStrike = GetStrike(contracts, (decimal)lowerStrikeFromAtm);
var lowerStrikeContract = contracts.Single(x => x.ID.StrikePrice == lowerStrike);
var higherStrikeContracts = contracts.Where(x => x.ID.StrikePrice > lowerStrike).ToList();
if (higherStrikeContracts.Count == 0)
{
return Empty();
}
var higherStrike = GetStrike(higherStrikeContracts, higherStrikeFromAtm);
var higherStrikeContract = higherStrikeContracts.Single(x => x.ID.StrikePrice == higherStrike);
return SymbolList(new List { lowerStrikeContract, higherStrikeContract });
}
///
/// Sets universe of 2 call contracts with the same strike price and different expiration dates, with closest match to the criteria given
///
/// The desire strike price distance from the current underlying price
/// The mininum days till expiry of the closer contract from the current time, closest expiry will be selected
/// The mininum days till expiry of the further conrtact from the current time, closest expiry will be selected
/// Applicable to Long and Short Call Calendar Spread Option Strategy
/// Universe with filter applied
public OptionFilterUniverse CallCalendarSpread(decimal strikeFromAtm = 0, int minNearDaysTillExpiry = 30, int minFarDaysTillExpiry = 60)
{
return CalendarSpread(OptionRight.Call, strikeFromAtm, minNearDaysTillExpiry, minFarDaysTillExpiry);
}
///
/// Sets universe of 2 put contracts with the same strike price and different expiration dates, with closest match to the criteria given
///
/// The desire strike price distance from the current underlying price
/// The mininum days till expiry of the closer contract from the current time, closest expiry will be selected
/// The mininum days till expiry of the further conrtact from the current time, closest expiry will be selected
/// Applicable to Long and Short Put Calendar Spread Option Strategy
/// Universe with filter applied
public OptionFilterUniverse PutCalendarSpread(decimal strikeFromAtm = 0, int minNearDaysTillExpiry = 30, int minFarDaysTillExpiry = 60)
{
return CalendarSpread(OptionRight.Put, strikeFromAtm, minNearDaysTillExpiry, minFarDaysTillExpiry);
}
private OptionFilterUniverse CalendarSpread(OptionRight right, decimal strikeFromAtm, int minNearDaysTillExpiry, int minFarDaysTillExpiry)
{
if (minFarDaysTillExpiry <= minNearDaysTillExpiry)
{
throw new ArgumentException("CalendarSpread(): expiry arguments must be in ascending order, "
+ $"{nameof(minNearDaysTillExpiry)}, {nameof(minFarDaysTillExpiry)}");
}
if (minNearDaysTillExpiry < 0)
{
throw new ArgumentException("CalendarSpread(): near expiry argument must be positive.");
}
// Select the set strike
var strike = GetStrike(AllSymbols, strikeFromAtm);
var contracts = AllSymbols.Where(x => x.ID.StrikePrice == strike && x.ID.OptionRight == right).ToList();
// Select the expiries
var nearExpiryContract = GetContractsForExpiry(contracts, minNearDaysTillExpiry).SingleOrDefault();
if (nearExpiryContract == null)
{
return Empty();
}
var furtherContracts = contracts.Where(x => x.ID.Date > nearExpiryContract.ID.Date).ToList();
var farExpiryContract = GetContractsForExpiry(furtherContracts, minFarDaysTillExpiry).SingleOrDefault();
if (farExpiryContract == null)
{
return Empty();
}
return SymbolList(new List { nearExpiryContract, farExpiryContract });
}
///
/// Sets universe of an OTM call contract and an OTM put contract with the same expiry, with closest match to the criteria given
///
/// The minimum days till expiry from the current time, closest expiry will be selected
/// The desire strike price distance from the current underlying price of the OTM call. It must be positive.
/// The desire strike price distance from the current underlying price of the OTM put. It must be negative.
/// Applicable to Long and Short Strangle Option Strategy
/// Universe with filter applied
public OptionFilterUniverse Strangle(int minDaysTillExpiry = 30, decimal callStrikeFromAtm = 5, decimal putStrikeFromAtm = -5)
{
if (callStrikeFromAtm <= 0)
{
throw new ArgumentException($"Strangle(): {nameof(callStrikeFromAtm)} must be positive");
}
if (putStrikeFromAtm >= 0)
{
throw new ArgumentException($"Strangle(): {nameof(putStrikeFromAtm)} must be negative");
}
return CallPutSpread(minDaysTillExpiry, callStrikeFromAtm, putStrikeFromAtm, true);
}
///
/// Sets universe of an ATM call contract and an ATM put contract with the same expiry, with closest match to the criteria given
///
/// The minimum days till expiry from the current time, closest expiry will be selected
/// Applicable to Long and Short Straddle Option Strategy
/// Universe with filter applied
public OptionFilterUniverse Straddle(int minDaysTillExpiry = 30)
{
return CallPutSpread(minDaysTillExpiry, 0, 0);
}
///
/// Sets universe of a call contract and a put contract with the same expiry but lower strike price, with closest match to the criteria given
///
/// The minimum days till expiry from the current time, closest expiry will be selected
/// The desire strike price distance from the current underlying price of the call.
/// The desire strike price distance from the current underlying price of the put.
/// Applicable to Protective Collar Option Strategy
/// Universe with filter applied
public OptionFilterUniverse ProtectiveCollar(int minDaysTillExpiry = 30, decimal callStrikeFromAtm = 5, decimal putStrikeFromAtm = -5)
{
if (callStrikeFromAtm <= putStrikeFromAtm)
{
throw new ArgumentException("ProtectiveCollar(): strike price arguments must be in descending order, "
+ $"{nameof(callStrikeFromAtm)}, {nameof(putStrikeFromAtm)}");
}
var filtered = CallPutSpread(minDaysTillExpiry, callStrikeFromAtm, putStrikeFromAtm);
var callStrike = filtered.Single(x => x.ID.OptionRight == OptionRight.Call).ID.StrikePrice;
var putStrike = filtered.Single(x => x.ID.OptionRight == OptionRight.Put).ID.StrikePrice;
if (callStrike <= putStrike)
{
return Empty();
}
return filtered;
}
///
/// Sets universe of a call contract and a put contract with the same expiry and strike price, with closest match to the criteria given
///
/// The minimum days till expiry from the current time, closest expiry will be selected
/// The desire strike price distance from the current underlying price
/// Applicable to Conversion and Reverse Conversion Option Strategy
/// Universe with filter applied
public OptionFilterUniverse Conversion(int minDaysTillExpiry = 30, decimal strikeFromAtm = 5)
{
return CallPutSpread(minDaysTillExpiry, strikeFromAtm, strikeFromAtm);
}
private OptionFilterUniverse CallPutSpread(int minDaysTillExpiry, decimal callStrikeFromAtm, decimal putStrikeFromAtm, bool otm = false)
{
// Select the expiry as the nearest to set days later
var contracts = GetContractsForExpiry(AllSymbols, minDaysTillExpiry).ToList();
var calls = contracts.Where(x => x.ID.OptionRight == OptionRight.Call).ToList();
var puts = contracts.Where(x => x.ID.OptionRight == OptionRight.Put).ToList();
if (otm)
{
calls = calls.Where(x => x.ID.StrikePrice > Underlying.Price).ToList();
puts = puts.Where(x => x.ID.StrikePrice < Underlying.Price).ToList();
}
if (calls.Count == 0 || puts.Count == 0)
{
return Empty();
}
// Select the strike prices with the set spread range
var callStrike = GetStrike(calls, callStrikeFromAtm);
var call = calls.Single(x => x.ID.StrikePrice == callStrike);
var putStrike = GetStrike(puts, putStrikeFromAtm);
var put = puts.Single(x => x.ID.StrikePrice == putStrike);
// Select the contracts
return SymbolList(new List { call, put });
}
///
/// Sets universe of an ITM call, an ATM call, and an OTM call with the same expiry and equal strike price distance, with closest match to the criteria given
///
/// The minimum days till expiry from the current time, closest expiry will be selected
/// The desire strike price distance of the ITM call and the OTM call from the current underlying price
/// Applicable to Long and Short Call Butterfly Option Strategy
/// Universe with filter applied
public OptionFilterUniverse CallButterfly(int minDaysTillExpiry = 30, decimal strikeSpread = 5)
{
return Butterfly(OptionRight.Call, minDaysTillExpiry, strikeSpread);
}
///
/// Sets universe of an ITM put, an ATM put, and an OTM put with the same expiry and equal strike price distance, with closest match to the criteria given
///
/// The minimum days till expiry from the current time, closest expiry will be selected
/// The desire strike price distance of the ITM put and the OTM put from the current underlying price
/// Applicable to Long and Short Put Butterfly Option Strategy
/// Universe with filter applied
public OptionFilterUniverse PutButterfly(int minDaysTillExpiry = 30, decimal strikeSpread = 5)
{
return Butterfly(OptionRight.Put, minDaysTillExpiry, strikeSpread);
}
private OptionFilterUniverse Butterfly(OptionRight right, int minDaysTillExpiry, decimal strikeSpread)
{
if (strikeSpread <= 0)
{
throw new ArgumentException("ProtectiveCollar(): strikeSpread arguments must be positive");
}
// Select the expiry as the nearest to set days later
var contractsForExpiry = GetContractsForExpiry(AllSymbols, minDaysTillExpiry);
var contracts = contractsForExpiry.Where(x => x.ID.OptionRight == right).ToList();
if (contracts.Count == 0)
{
return Empty();
}
// Select the strike prices with the set spread range
var atmStrike = GetStrike(contracts, 0m);
var lowerStrike = GetStrike(contracts.Where(x => x.ID.StrikePrice < Underlying.Price && x.ID.StrikePrice < atmStrike), -strikeSpread);
var upperStrike = -1m;
if (lowerStrike != decimal.MaxValue)
{
upperStrike = atmStrike * 2 - lowerStrike;
}
// Select the contracts
var filtered = this.Where(x =>
x.ID.Date == contracts[0].ID.Date && x.ID.OptionRight == right &&
(x.ID.StrikePrice == atmStrike || x.ID.StrikePrice == lowerStrike || x.ID.StrikePrice == upperStrike));
if (filtered.Count() != 3)
{
return Empty();
}
return filtered;
}
///
/// Sets universe of an OTM call, an ATM call, an ATM put, and an OTM put with the same expiry and equal strike price distance, with closest match to the criteria given
///
/// The minimum days till expiry from the current time, closest expiry will be selected
/// The desire strike price distance of the OTM call and the OTM put from the current underlying price
/// Applicable to Long and Short Iron Butterfly Option Strategy
/// Universe with filter applied
public OptionFilterUniverse IronButterfly(int minDaysTillExpiry = 30, decimal strikeSpread = 5)
{
if (strikeSpread <= 0)
{
throw new ArgumentException("IronButterfly(): strikeSpread arguments must be positive");
}
// Select the expiry as the nearest to set days later
var contracts = GetContractsForExpiry(AllSymbols, minDaysTillExpiry).ToList();
var calls = contracts.Where(x => x.ID.OptionRight == OptionRight.Call && x.ID.StrikePrice > Underlying.Price).ToList();
var puts = contracts.Where(x => x.ID.OptionRight == OptionRight.Put && x.ID.StrikePrice < Underlying.Price).ToList();
if (calls.Count == 0 || puts.Count == 0)
{
return Empty();
}
// Select the strike prices with the set spread range
var atmStrike = GetStrike(contracts, 0);
var otmCallStrike = GetStrike(calls.Where(x => x.ID.StrikePrice > atmStrike), strikeSpread);
var otmPutStrike = -1m;
if (otmCallStrike != decimal.MaxValue)
{
otmPutStrike = atmStrike * 2 - otmCallStrike;
}
var filtered = this.Where(x =>
x.ID.Date == contracts[0].ID.Date && (
x.ID.StrikePrice == atmStrike ||
(x.ID.OptionRight == OptionRight.Call && x.ID.StrikePrice == otmCallStrike) ||
(x.ID.OptionRight == OptionRight.Put && x.ID.StrikePrice == otmPutStrike)
));
if (filtered.Count() != 4)
{
return Empty();
}
return filtered;
}
///
/// Sets universe of a far-OTM call, a near-OTM call, a near-OTM put, and a far-OTM put with the same expiry
/// and equal strike price distance between both calls and both puts, with closest match to the criteria given
///
/// The minimum days till expiry from the current time, closest expiry will be selected
/// The desire strike price distance of the near-to-expiry call and the near-to-expiry put from the current underlying price
/// The desire strike price distance of the further-to-expiry call and the further-to-expiry put from the current underlying price
/// Applicable to Long and Short Iron Condor Option Strategy
/// Universe with filter applied
public OptionFilterUniverse IronCondor(int minDaysTillExpiry = 30, decimal nearStrikeSpread = 5, decimal farStrikeSpread = 10)
{
if (nearStrikeSpread <= 0 || farStrikeSpread <= 0)
{
throw new ArgumentException("IronCondor(): strike arguments must be positive, "
+ $"{nameof(nearStrikeSpread)}, {nameof(farStrikeSpread)}");
}
if (nearStrikeSpread >= farStrikeSpread)
{
throw new ArgumentException("IronCondor(): strike arguments must be in ascending orders, "
+ $"{nameof(nearStrikeSpread)}, {nameof(farStrikeSpread)}");
}
// Select the expiry as the nearest to set days later
var contracts = GetContractsForExpiry(AllSymbols, minDaysTillExpiry).ToList();
var calls = contracts.Where(x => x.ID.OptionRight == OptionRight.Call && x.ID.StrikePrice > Underlying.Price).ToList();
var puts = contracts.Where(x => x.ID.OptionRight == OptionRight.Put && x.ID.StrikePrice < Underlying.Price).ToList();
if (calls.Count == 0 || puts.Count == 0)
{
return Empty();
}
// Select the strike prices with the set spread range
var nearCallStrike = GetStrike(calls, nearStrikeSpread);
var nearPutStrike = GetStrike(puts, -nearStrikeSpread);
var farCallStrike = GetStrike(calls.Where(x => x.ID.StrikePrice > nearCallStrike), farStrikeSpread);
var farPutStrike = -1m;
if (farCallStrike != decimal.MaxValue)
{
farPutStrike = nearPutStrike - farCallStrike + nearCallStrike;
}
// Select the contracts
var filtered = this.Where(x =>
x.ID.Date == contracts[0].ID.Date && (
(x.ID.OptionRight == OptionRight.Call && x.ID.StrikePrice == nearCallStrike) ||
(x.ID.OptionRight == OptionRight.Put && x.ID.StrikePrice == nearPutStrike) ||
(x.ID.OptionRight == OptionRight.Call && x.ID.StrikePrice == farCallStrike) ||
(x.ID.OptionRight == OptionRight.Put && x.ID.StrikePrice == farPutStrike)
));
if (filtered.Count() != 4)
{
return Empty();
}
return filtered;
}
///
/// Sets universe of an OTM call, an ITM call, an OTM put, and an ITM put with the same expiry with closest match to the criteria given.
/// The OTM call has the same strike as the ITM put, while the same holds for the ITM call and the OTM put
///
/// The minimum days till expiry from the current time, closest expiry will be selected
/// The desire strike price distance of the OTM call and the OTM put from the current underlying price
/// Applicable to Long and Short Box Spread Option Strategy
/// Universe with filter applied
public OptionFilterUniverse BoxSpread(int minDaysTillExpiry = 30, decimal strikeSpread = 5)
{
if (strikeSpread <= 0)
{
throw new ArgumentException($"BoxSpread(): strike arguments must be positive, {nameof(strikeSpread)}");
}
// Select the expiry as the nearest to set days later
var contracts = GetContractsForExpiry(AllSymbols, minDaysTillExpiry).ToList();
if (contracts.Count == 0)
{
return Empty();
}
// Select the strike prices with the set spread range
var higherStrike = GetStrike(contracts.Where(x => x.ID.StrikePrice > Underlying.Price), strikeSpread);
var lowerStrike = GetStrike(contracts.Where(x => x.ID.StrikePrice < higherStrike && x.ID.StrikePrice < Underlying.Price), -strikeSpread);
// Select the contracts
var filtered = this.Where(x =>
(x.ID.StrikePrice == higherStrike || x.ID.StrikePrice == lowerStrike) &&
x.ID.Date == contracts[0].ID.Date);
if (filtered.Count() != 4)
{
return Empty();
}
return filtered;
}
///
/// Sets universe of 2 call and 2 put contracts with the same strike price and 2 expiration dates, with closest match to the criteria given
///
/// The desire strike price distance from the current underlying price
/// The mininum days till expiry of the closer contract from the current time, closest expiry will be selected
/// The mininum days till expiry of the further conrtact from the current time, closest expiry will be selected
/// Applicable to Long and Short Jelly Roll Option Strategy
/// Universe with filter applied
public OptionFilterUniverse JellyRoll(decimal strikeFromAtm = 0, int minNearDaysTillExpiry = 30, int minFarDaysTillExpiry = 60)
{
if (minFarDaysTillExpiry <= minNearDaysTillExpiry)
{
throw new ArgumentException("JellyRoll(): expiry arguments must be in ascending order, "
+ $"{nameof(minNearDaysTillExpiry)}, {nameof(minFarDaysTillExpiry)}");
}
if (minNearDaysTillExpiry < 0)
{
throw new ArgumentException("JellyRoll(): near expiry argument must be positive.");
}
// Select the set strike
var strike = AllSymbols.OrderBy(x => Math.Abs(Underlying.Price - x.ID.StrikePrice + strikeFromAtm))
.First().ID.StrikePrice;
var contracts = AllSymbols.Where(x => x.ID.StrikePrice == strike && x.ID.OptionRight == OptionRight.Call).ToList();
// Select the expiries
var nearExpiryContract = GetContractsForExpiry(contracts, minNearDaysTillExpiry).SingleOrDefault();
if (nearExpiryContract == null)
{
return Empty();
}
var nearExpiry = nearExpiryContract.ID.Date;
var furtherContracts = contracts.Where(x => x.ID.Date > nearExpiryContract.ID.Date).ToList();
var farExpiryContract = GetContractsForExpiry(furtherContracts, minFarDaysTillExpiry).SingleOrDefault();
if (farExpiryContract == null)
{
return Empty();
}
var farExpiry = farExpiryContract.ID.Date;
var filtered = this.Where(x => x.ID.StrikePrice == strike && (x.ID.Date == nearExpiry || x.ID.Date == farExpiry));
if (filtered.Count() != 4)
{
return Empty();
}
return filtered;
}
///
/// Sets universe of 3 call contracts with the same expiry and different strike prices, with closest match to the criteria given
///
/// The minimum days till expiry from the current time, closest expiry will be selected
/// The desire strike price distance from the current underlying price of the higher strike price
/// The desire strike price distance from the current underlying price of the middle strike price
/// The desire strike price distance from the current underlying price of the lower strike price
/// Applicable to Bear Call Ladder and Bull Call Ladder Option Strategy
/// Universe with filter applied
public OptionFilterUniverse CallLadder(int minDaysTillExpiry, decimal higherStrikeFromAtm, decimal middleStrikeFromAtm, decimal lowerStrikeFromAtm)
{
return Ladder(OptionRight.Call, minDaysTillExpiry, higherStrikeFromAtm, middleStrikeFromAtm, lowerStrikeFromAtm);
}
///
/// Sets universe of 3 put contracts with the same expiry and different strike prices, with closest match to the criteria given
///
/// The minimum days till expiry from the current time, closest expiry will be selected
/// The desire strike price distance from the current underlying price of the higher strike price
/// The desire strike price distance from the current underlying price of the middle strike price
/// The desire strike price distance from the current underlying price of the lower strike price
/// Applicable to Bear Put Ladder and Bull Put Ladder Option Strategy
/// Universe with filter applied
public OptionFilterUniverse PutLadder(int minDaysTillExpiry, decimal higherStrikeFromAtm, decimal middleStrikeFromAtm, decimal lowerStrikeFromAtm)
{
return Ladder(OptionRight.Put, minDaysTillExpiry, higherStrikeFromAtm, middleStrikeFromAtm, lowerStrikeFromAtm);
}
///
/// Applies the filter to the universe selecting the contracts with Delta between the given range
///
/// The minimum Delta value
/// The maximum Delta value
/// Universe with filter applied
public OptionFilterUniverse Delta(decimal min, decimal max)
{
ValidateSecurityTypeForSupportedFilters(nameof(Delta));
return this.Where(contractData => contractData.Greeks.Delta >= min && contractData.Greeks.Delta <= max);
}
///
/// Applies the filter to the universe selecting the contracts with Delta between the given range.
/// Alias for
///
/// The minimum Delta value
/// The maximum Delta value
/// Universe with filter applied
public OptionFilterUniverse D(decimal min, decimal max)
{
return Delta(min, max);
}
///
/// Applies the filter to the universe selecting the contracts with Gamma between the given range
///
/// The minimum Gamma value
/// The maximum Gamma value
/// Universe with filter applied
public OptionFilterUniverse Gamma(decimal min, decimal max)
{
ValidateSecurityTypeForSupportedFilters(nameof(Gamma));
return this.Where(contractData => contractData.Greeks.Gamma >= min && contractData.Greeks.Gamma <= max);
}
///
/// Applies the filter to the universe selecting the contracts with Gamma between the given range.
/// Alias for
///
/// The minimum Gamma value
/// The maximum Gamma value
/// Universe with filter applied
public OptionFilterUniverse G(decimal min, decimal max)
{
return Gamma(min, max);
}
///
/// Applies the filter to the universe selecting the contracts with Theta between the given range
///
/// The minimum Theta value
/// The maximum Theta value
/// Universe with filter applied
public OptionFilterUniverse Theta(decimal min, decimal max)
{
ValidateSecurityTypeForSupportedFilters(nameof(Theta));
return this.Where(contractData => contractData.Greeks.Theta >= min && contractData.Greeks.Theta <= max);
}
///
/// Applies the filter to the universe selecting the contracts with Theta between the given range.
/// Alias for
///
/// The minimum Theta value
/// The maximum Theta value
/// Universe with filter applied
public OptionFilterUniverse T(decimal min, decimal max)
{
return Theta(min, max);
}
///
/// Applies the filter to the universe selecting the contracts with Vega between the given range
///
/// The minimum Vega value
/// The maximum Vega value
/// Universe with filter applied
public OptionFilterUniverse Vega(decimal min, decimal max)
{
ValidateSecurityTypeForSupportedFilters(nameof(Vega));
return this.Where(contractData => contractData.Greeks.Vega >= min && contractData.Greeks.Vega <= max);
}
///
/// Applies the filter to the universe selecting the contracts with Vega between the given range.
/// Alias for
///
/// The minimum Vega value
/// The maximum Vega value
/// Universe with filter applied
public OptionFilterUniverse V(decimal min, decimal max)
{
return Vega(min, max);
}
///
/// Applies the filter to the universe selecting the contracts with Rho between the given range
///
/// The minimum Rho value
/// The maximum Rho value
/// Universe with filter applied
public OptionFilterUniverse Rho(decimal min, decimal max)
{
ValidateSecurityTypeForSupportedFilters(nameof(Rho));
return this.Where(contractData => contractData.Greeks.Rho >= min && contractData.Greeks.Rho <= max);
}
///
/// Applies the filter to the universe selecting the contracts with Rho between the given range.
/// Alias for
///
/// The minimum Rho value
/// The maximum Rho value
/// Universe with filter applied
public OptionFilterUniverse R(decimal min, decimal max)
{
return Rho(min, max);
}
///
/// Applies the filter to the universe selecting the contracts with implied volatility between the given range
///
/// The minimum implied volatility value
/// The maximum implied volatility value
/// Universe with filter applied
public OptionFilterUniverse ImpliedVolatility(decimal min, decimal max)
{
ValidateSecurityTypeForSupportedFilters(nameof(ImpliedVolatility));
return this.Where(contractData => contractData.ImpliedVolatility >= min && contractData.ImpliedVolatility <= max);
}
///
/// Applies the filter to the universe selecting the contracts with implied volatility between the given range.
/// Alias for
///
/// The minimum implied volatility value
/// The maximum implied volatility value
/// Universe with filter applied
public OptionFilterUniverse IV(decimal min, decimal max)
{
return ImpliedVolatility(min, max);
}
///
/// Applies the filter to the universe selecting the contracts with open interest between the given range
///
/// The minimum open interest value
/// The maximum open interest value
/// Universe with filter applied
public OptionFilterUniverse OpenInterest(long min, long max)
{
ValidateSecurityTypeForSupportedFilters(nameof(OpenInterest));
return this.Where(contractData => contractData.OpenInterest >= min && contractData.OpenInterest <= max);
}
///
/// Applies the filter to the universe selecting the contracts with open interest between the given range.
/// Alias for
///
/// The minimum open interest value
/// The maximum open interest value
/// Universe with filter applied
public OptionFilterUniverse OI(long min, long max)
{
return OpenInterest(min, max);
}
///
/// Implicitly convert the universe to a list of symbols
///
///
#pragma warning disable CA1002 // Do not expose generic lists
#pragma warning disable CA2225 // Operator overloads have named alternates
public static implicit operator List(OptionFilterUniverse universe)
{
return universe.AllSymbols.ToList();
}
#pragma warning restore CA2225 // Operator overloads have named alternates
#pragma warning restore CA1002 // Do not expose generic lists
private OptionFilterUniverse Ladder(OptionRight right, int minDaysTillExpiry, decimal higherStrikeFromAtm, decimal middleStrikeFromAtm, decimal lowerStrikeFromAtm)
{
if (higherStrikeFromAtm <= lowerStrikeFromAtm || higherStrikeFromAtm <= middleStrikeFromAtm || middleStrikeFromAtm <= lowerStrikeFromAtm)
{
throw new ArgumentException("Ladder(): strike price arguments must be in descending order, "
+ $"{nameof(higherStrikeFromAtm)}, {nameof(middleStrikeFromAtm)}, {nameof(lowerStrikeFromAtm)}");
}
// Select the expiry as the nearest to set days later
var contracts = GetContractsForExpiry(AllSymbols.Where(x => x.ID.OptionRight == right).ToList(), minDaysTillExpiry);
// Select the strike prices with the set ladder range
var lowerStrikeContract = contracts.OrderBy(x => Math.Abs(Underlying.Price - x.ID.StrikePrice + lowerStrikeFromAtm)).First();
var middleStrikeContract = contracts.Where(x => x.ID.StrikePrice > lowerStrikeContract.ID.StrikePrice)
.OrderBy(x => Math.Abs(Underlying.Price - x.ID.StrikePrice + middleStrikeFromAtm)).FirstOrDefault();
if (middleStrikeContract == default)
{
return Empty();
}
var higherStrikeContract = contracts.Where(x => x.ID.StrikePrice > middleStrikeContract.ID.StrikePrice)
.OrderBy(x => Math.Abs(Underlying.Price - x.ID.StrikePrice + higherStrikeFromAtm)).FirstOrDefault();
if (higherStrikeContract == default)
{
return Empty();
}
return this.WhereContains(new List { lowerStrikeContract, middleStrikeContract, higherStrikeContract });
}
///
/// Will provide all contracts that respect a specific expiration filter
///
/// Symbols source to use
/// The desired minimum days till expiry
/// All symbols that respect a single expiration date
private IEnumerable GetContractsForExpiry(IEnumerable symbols, int minDaysTillExpiry)
{
var leastExpiryAccepted = _lastExchangeDate.AddDays(minDaysTillExpiry);
return symbols.Where(x => x.ID.Date >= leastExpiryAccepted)
.GroupBy(x => x.ID.Date)
.OrderBy(x => x.Key)
.FirstOrDefault()
// let's order the symbols too, to guarantee determinism
?.OrderBy(x => x.ID) ?? Enumerable.Empty();
}
///
/// Helper method that will select no contract
///
private OptionFilterUniverse Empty()
{
Data = Enumerable.Empty();
return this;
}
///
/// Helper method that will select the given contract list
///
private OptionFilterUniverse SymbolList(List contracts)
{
AllSymbols = contracts;
return this;
}
private decimal GetStrike(IEnumerable symbols, decimal strikeFromAtm)
{
return symbols.OrderBy(x => Math.Abs(Underlying.Price + strikeFromAtm - x.ID.StrikePrice))
.Select(x => x.ID.StrikePrice)
.DefaultIfEmpty(decimal.MaxValue)
.First();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ValidateSecurityTypeForSupportedFilters(string filterName)
{
if (_option.Symbol.SecurityType == SecurityType.FutureOption)
{
throw new InvalidOperationException($"{filterName} filter is not supported for future options.");
}
}
}
///
/// Extensions for Linq support
///
public static class OptionFilterUniverseEx
{
///
/// Filters universe
///
/// Universe to apply the filter too
/// Bool function to determine which Symbol are filtered
/// Universe with filter applied
public static OptionFilterUniverse Where(this OptionFilterUniverse universe, Func predicate)
{
universe.Data = universe.Data.Where(predicate).ToList();
return universe;
}
///
/// Filters universe
///
/// Universe to apply the filter too
/// Bool function to determine which Symbol are filtered
/// Universe with filter applied
public static OptionFilterUniverse Where(this OptionFilterUniverse universe, PyObject predicate)
{
universe.Data = universe.Data.Where(predicate.ConvertToDelegate>()).ToList();
return universe;
}
///
/// Maps universe
///
/// Universe to apply the filter too
/// Symbol function to determine which Symbols are filtered
/// Universe with filter applied
public static OptionFilterUniverse Select(this OptionFilterUniverse universe, Func mapFunc)
{
universe.AllSymbols = universe.Data.Select(mapFunc).ToList();
return universe;
}
///
/// Maps universe
///
/// Universe to apply the filter too
/// Symbol function to determine which Symbols are filtered
/// Universe with filter applied
public static OptionFilterUniverse Select(this OptionFilterUniverse universe, PyObject mapFunc)
{
return universe.Select(mapFunc.ConvertToDelegate>());
}
///
/// Binds universe
///
/// Universe to apply the filter too
/// Symbol function to determine which Symbols are filtered
/// Universe with filter applied
public static OptionFilterUniverse SelectMany(this OptionFilterUniverse universe, Func> mapFunc)
{
universe.AllSymbols = universe.Data.SelectMany(mapFunc).ToList();
return universe;
}
///
/// Binds universe
///
/// Universe to apply the filter too
/// Symbol function to determine which Symbols are filtered
/// Universe with filter applied
public static OptionFilterUniverse SelectMany(this OptionFilterUniverse universe, PyObject mapFunc)
{
return universe.SelectMany(mapFunc.ConvertToDelegate>>());
}
///
/// Updates universe to only contain the symbols in the list
///
/// Universe to apply the filter too
/// List of Symbols to keep in the Universe
/// Universe with filter applied
public static OptionFilterUniverse WhereContains(this OptionFilterUniverse universe, List filterList)
{
universe.Data = universe.Data.Where(x => filterList.Contains(x)).ToList();
return universe;
}
///
/// Updates universe to only contain the symbols in the list
///
/// Universe to apply the filter too
/// List of Symbols to keep in the Universe
/// Universe with filter applied
public static OptionFilterUniverse WhereContains(this OptionFilterUniverse universe, PyObject filterList)
{
return universe.WhereContains(filterList.ConvertToSymbolEnumerable().ToList());
}
}
}