Files
quantconnect--lean/Common/Data/Market/OptionContract.cs
T
2026-07-13 13:02:50 +08:00

345 lines
13 KiB
C#

/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Interfaces;
using QuantConnect.Securities;
using QuantConnect.Securities.Option;
using System;
namespace QuantConnect.Data.Market
{
/// <summary>
/// Defines a single option contract at a specific expiration and strike price
/// </summary>
public class OptionContract : BaseContract
{
private IOptionData _optionData = OptionPriceModelResultData.Null;
private readonly SymbolProperties _symbolProperties;
/// <summary>
/// Gets the strike price
/// </summary>
public decimal Strike => Symbol.ID.StrikePrice;
/// <summary>
/// Gets the strike price multiplied by the strike multiplier
/// </summary>
public decimal ScaledStrike => Strike * _symbolProperties.StrikeMultiplier;
/// <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 theoretical price of this option contract as computed by the <see cref="IOptionPriceModel"/>
/// </summary>
public decimal TheoreticalPrice => _optionData.TheoreticalPrice;
/// <summary>
/// Gets the implied volatility of the option contract as computed by the <see cref="IOptionPriceModel"/>
/// </summary>
public decimal ImpliedVolatility => _optionData.ImpliedVolatility;
/// <summary>
/// Gets the greeks for this contract
/// </summary>
public Greeks Greeks => _optionData.Greeks;
/// <summary>
/// Gets the open interest
/// </summary>
public override decimal OpenInterest => _optionData.OpenInterest;
/// <summary>
/// Gets the last price this contract traded at
/// </summary>
public override decimal LastPrice => _optionData.LastPrice;
/// <summary>
/// Gets the last volume this contract traded at
/// </summary>
public override long Volume => _optionData.Volume;
/// <summary>
/// Gets the current bid price
/// </summary>
public override decimal BidPrice => _optionData.BidPrice;
/// <summary>
/// Get the current bid size
/// </summary>
public override long BidSize => _optionData.BidSize;
/// <summary>
/// Gets the ask price
/// </summary>
public override decimal AskPrice => _optionData.AskPrice;
/// <summary>
/// Gets the current ask size
/// </summary>
public override long AskSize => _optionData.AskSize;
/// <summary>
/// Gets the last price the underlying security traded at
/// </summary>
public decimal UnderlyingLastPrice => _optionData.UnderlyingLastPrice;
/// <summary>
/// Initializes a new instance of the <see cref="OptionContract"/> class
/// </summary>
/// <param name="security">The option contract security</param>
public OptionContract(ISecurityPrice security)
: base(security.Symbol)
{
_symbolProperties = security.SymbolProperties;
}
/// <summary>
/// Initializes a new option contract from a given <see cref="OptionUniverse"/> instance
/// </summary>
/// <param name="contractData">The option universe contract data to use as source for this contract</param>
/// <param name="symbolProperties">The contract symbol properties</param>
public OptionContract(OptionUniverse contractData, SymbolProperties symbolProperties)
: base(contractData.Symbol)
{
_symbolProperties = symbolProperties;
_optionData = new OptionUniverseData(contractData);
}
/// <summary>
/// Sets the option price model evaluator function to be used for this contract
/// </summary>
/// <param name="optionPriceModelEvaluator">Function delegate used to evaluate the option price model</param>
internal void SetOptionPriceModel(Func<OptionPriceModelResult> optionPriceModelEvaluator)
{
_optionData = new OptionPriceModelResultData(optionPriceModelEvaluator, _optionData as OptionPriceModelResultData);
}
/// <summary>
/// Creates a <see cref="OptionContract"/>
/// </summary>
/// <param name="baseData"></param>
/// <param name="security">Provides price properties for a <see cref="Security"/></param>
/// <param name="underlying">Last underlying security trade data</param>
/// <returns>Option contract</returns>
public static OptionContract Create(BaseData baseData, ISecurityPrice security, BaseData underlying)
=> Create(baseData.EndTime, security, underlying);
/// <summary>
/// Creates a <see cref="OptionContract"/>
/// </summary>
/// <param name="endTime">local date time this contract's data was last updated</param>
/// <param name="security">provides price properties for a <see cref="Security"/></param>
/// <param name="underlying">last underlying security trade data</param>
/// <returns>Option contract</returns>
public static OptionContract Create(DateTime endTime, ISecurityPrice security, BaseData underlying)
{
var contract = new OptionContract(security)
{
Time = endTime,
};
contract._optionData.SetUnderlying(underlying);
return contract;
}
/// <summary>
/// Creates a new option contract from a given <see cref="OptionUniverse"/> instance,
/// using its data to form a quote bar to source pricing data
/// </summary>
/// <param name="contractData">The option universe contract data to use as source for this contract</param>
/// <param name="symbolProperties">The contract symbol properties</param>
public static OptionContract Create(OptionUniverse contractData, SymbolProperties symbolProperties)
{
var contract = new OptionContract(contractData, symbolProperties)
{
Time = contractData.EndTime,
};
return contract;
}
/// <summary>
/// Implicit conversion into <see cref="Symbol"/>
/// </summary>
/// <param name="contract">The option contract to be converted</param>
public static implicit operator Symbol(OptionContract contract)
{
return contract.Symbol;
}
/// <summary>
/// Updates the option contract with the new data, which can be a <see cref="Tick"/> or <see cref="TradeBar"/> or <see cref="QuoteBar"/>
/// </summary>
internal override void Update(BaseData data)
{
if (data.Symbol.SecurityType.IsOption())
{
_optionData.Update(data);
}
else if (data.Symbol.SecurityType == Symbol.GetUnderlyingFromOptionType(Symbol.SecurityType))
{
_optionData.SetUnderlying(data);
}
}
#region Option Contract Data Handlers
private interface IOptionData
{
decimal LastPrice { get; }
decimal UnderlyingLastPrice { get; }
long Volume { get; }
decimal BidPrice { get; }
long BidSize { get; }
decimal AskPrice { get; }
long AskSize { get; }
decimal OpenInterest { get; }
decimal TheoreticalPrice { get; }
decimal ImpliedVolatility { get; }
Greeks Greeks { get; }
void Update(BaseData data);
void SetUnderlying(BaseData data);
}
/// <summary>
/// Handles option data for a contract from actual price data (trade, quote, open interest) and theoretical price model results
/// </summary>
private class OptionPriceModelResultData : IOptionData
{
public static readonly OptionPriceModelResultData Null = new(() => OptionPriceModelResult.None);
private readonly Lazy<OptionPriceModelResult> _optionPriceModelResult;
private TradeBar _tradeBar;
private QuoteBar _quoteBar;
private OpenInterest _openInterest;
private BaseData _underlying;
public decimal LastPrice => _tradeBar?.Close ?? decimal.Zero;
public decimal UnderlyingLastPrice => _underlying?.Price ?? decimal.Zero;
public long Volume => (long)(_tradeBar?.Volume ?? 0L);
public decimal BidPrice => _quoteBar?.Bid?.Close ?? decimal.Zero;
public long BidSize => (long)(_quoteBar?.LastBidSize ?? 0L);
public decimal AskPrice => _quoteBar?.Ask?.Close ?? decimal.Zero;
public long AskSize => (long)(_quoteBar?.LastAskSize ?? 0L);
public decimal OpenInterest => _openInterest?.Value ?? decimal.Zero;
public decimal TheoreticalPrice => _optionPriceModelResult.Value.TheoreticalPrice;
public decimal ImpliedVolatility => _optionPriceModelResult.Value.ImpliedVolatility;
public Greeks Greeks => _optionPriceModelResult.Value.Greeks;
public OptionPriceModelResultData(Func<OptionPriceModelResult> optionPriceModelEvaluator,
OptionPriceModelResultData previousOptionData = null)
{
_optionPriceModelResult = new(optionPriceModelEvaluator, isThreadSafe: false);
if (previousOptionData != null)
{
_tradeBar = previousOptionData._tradeBar;
_quoteBar = previousOptionData._quoteBar;
_openInterest = previousOptionData._openInterest;
_underlying = previousOptionData._underlying;
}
}
public void Update(BaseData data)
{
switch (data)
{
case TradeBar tradeBar:
_tradeBar = tradeBar;
break;
case QuoteBar quoteBar:
_quoteBar = quoteBar;
break;
case OpenInterest openInterest:
_openInterest = openInterest;
break;
}
}
public void SetUnderlying(BaseData data)
{
_underlying = data;
}
}
/// <summary>
/// Handles option data for a contract from a <see cref="OptionUniverse"/> instance
/// </summary>
private class OptionUniverseData : IOptionData
{
private readonly OptionUniverse _contractData;
public decimal LastPrice => _contractData.Close;
// TODO: Null check required for FOPs: since OptionUniverse does not support FOPs,
// these instances will by "synthetic" and will not have underlying data.
// Can be removed after FOPs are supported by OptionUniverse
public decimal UnderlyingLastPrice => _contractData?.Underlying?.Price ?? decimal.Zero;
public long Volume => (long)_contractData.Volume;
public decimal BidPrice => _contractData.Close;
public long BidSize => 0;
public decimal AskPrice => _contractData.Close;
public long AskSize => 0;
public decimal OpenInterest => _contractData.OpenInterest;
public decimal TheoreticalPrice => decimal.Zero;
public decimal ImpliedVolatility => _contractData.ImpliedVolatility;
public Greeks Greeks => _contractData.Greeks;
public OptionUniverseData(OptionUniverse contractData)
{
_contractData = contractData;
}
public void Update(BaseData data)
{
}
public void SetUnderlying(BaseData data)
{
}
}
#endregion
}
}