diff --git a/Algorithm.CSharp/AddOptionContractFromUniverseRegressionAlgorithm.cs b/Algorithm.CSharp/AddOptionContractFromUniverseRegressionAlgorithm.cs
index 88c72be803f0..320fef26f12c 100644
--- a/Algorithm.CSharp/AddOptionContractFromUniverseRegressionAlgorithm.cs
+++ b/Algorithm.CSharp/AddOptionContractFromUniverseRegressionAlgorithm.cs
@@ -117,7 +117,7 @@ public override void OnSecuritiesChanged(SecurityChanges changes)
&& optionContract.ID.OptionStyle == OptionStyle.American);
AddOptionContract(option);
- foreach (var symbol in new[] { option.Symbol, option.Underlying.Symbol })
+ foreach (var symbol in new[] { option.Symbol, option.UnderlyingSymbol })
{
var config = SubscriptionManager.SubscriptionDataConfigService.GetSubscriptionDataConfigs(symbol).ToList();
diff --git a/Algorithm.CSharp/OptionChainsMultipleFullDataRegressionAlgorithm.cs b/Algorithm.CSharp/OptionChainsMultipleFullDataRegressionAlgorithm.cs
new file mode 100644
index 000000000000..0e5484e5f0fc
--- /dev/null
+++ b/Algorithm.CSharp/OptionChainsMultipleFullDataRegressionAlgorithm.cs
@@ -0,0 +1,142 @@
+/*
+ * 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.Data;
+using QuantConnect.Data.Market;
+using QuantConnect.Interfaces;
+using QuantConnect.Securities;
+
+namespace QuantConnect.Algorithm.CSharp
+{
+ ///
+ /// Regression algorithm illustrating the usage of the method
+ /// to get multiple option chains, which contains additional data besides the symbols, including prices, implied volatility and greeks.
+ /// It also shows how this data can be used to filter the contracts based on certain criteria.
+ ///
+ public class OptionChainsMultipleFullDataRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
+ {
+ private Symbol _googOptionContract;
+ private Symbol _spxOptionContract;
+
+ public override void Initialize()
+ {
+ SetStartDate(2015, 12, 24);
+ SetEndDate(2015, 12, 24);
+ SetCash(100000);
+
+ var goog = AddEquity("GOOG").Symbol;
+ var spx = AddIndex("SPX").Symbol;
+
+ var chains = OptionChains(new[] { goog, spx });
+
+ _googOptionContract = GetContract(chains, goog, TimeSpan.FromDays(10));
+ _spxOptionContract = GetContract(chains, spx, TimeSpan.FromDays(60));
+
+ AddOptionContract(_googOptionContract);
+ AddIndexOptionContract(_spxOptionContract);
+ }
+
+ private Symbol GetContract(OptionChains chains, Symbol underlying, TimeSpan expirySpan)
+ {
+ return chains
+ .Where(kvp => kvp.Key.Underlying == underlying)
+ .Select(kvp => kvp.Value)
+ .Single()
+ // Get contracts expiring within a given span, with an implied volatility greater than 0.5 and a delta less than 0.5
+ .Where(contractData => contractData.ID.Date - Time <= expirySpan &&
+ contractData.ImpliedVolatility > 0.5m &&
+ contractData.Greeks.Delta < 0.5m)
+ // Get the contract with the latest expiration date
+ .OrderByDescending(x => x.ID.Date)
+ .First();
+ }
+
+ public override void OnData(Slice slice)
+ {
+ // Do some trading with the selected contract for sample purposes
+ if (!Portfolio.Invested)
+ {
+ MarketOrder(_googOptionContract, 1);
+ }
+ else
+ {
+ Liquidate();
+ }
+ }
+
+ ///
+ /// This is used by the regression test system to indicate if the open source Lean repository has the required data to run this algorithm.
+ ///
+ public bool CanRunLocally { get; } = true;
+
+ ///
+ /// This is used by the regression test system to indicate which languages this algorithm is written in.
+ ///
+ public virtual List Languages { get; } = new() { Language.CSharp, Language.Python };
+
+ ///
+ /// Data Points count of all timeslices of algorithm
+ ///
+ public long DataPoints => 1059;
+
+ ///
+ /// Data Points count of the algorithm history
+ ///
+ public int AlgorithmHistoryDataPoints => 2;
+
+ ///
+ /// Final status of the algorithm
+ ///
+ public AlgorithmStatus AlgorithmStatus => AlgorithmStatus.Completed;
+
+ ///
+ /// This is used by the regression test system to indicate what the expected statistics are from running the algorithm
+ ///
+ public Dictionary ExpectedStatistics => new Dictionary
+ {
+ {"Total Orders", "210"},
+ {"Average Win", "0%"},
+ {"Average Loss", "0%"},
+ {"Compounding Annual Return", "0%"},
+ {"Drawdown", "0%"},
+ {"Expectancy", "0"},
+ {"Start Equity", "100000"},
+ {"End Equity", "96041"},
+ {"Net Profit", "0%"},
+ {"Sharpe Ratio", "0"},
+ {"Sortino Ratio", "0"},
+ {"Probabilistic Sharpe Ratio", "0%"},
+ {"Loss Rate", "0%"},
+ {"Win Rate", "0%"},
+ {"Profit-Loss Ratio", "0"},
+ {"Alpha", "0"},
+ {"Beta", "0"},
+ {"Annual Standard Deviation", "0"},
+ {"Annual Variance", "0"},
+ {"Information Ratio", "0"},
+ {"Tracking Error", "0"},
+ {"Treynor Ratio", "0"},
+ {"Total Fees", "$209.00"},
+ {"Estimated Strategy Capacity", "$0"},
+ {"Lowest Capacity Asset", "GOOCV W6U7PD1F2WYU|GOOCV VP83T1ZUHROL"},
+ {"Portfolio Turnover", "85.46%"},
+ {"OrderListHash", "a7ab1a9e64fe9ba76ea33a40a78a4e3b"}
+ };
+ }
+}
diff --git a/Algorithm.CSharp/OptionPriceModelForOptionStylesBaseRegressionAlgorithm.cs b/Algorithm.CSharp/OptionPriceModelForOptionStylesBaseRegressionAlgorithm.cs
index 13d35e5a6f71..2157c9a7316e 100644
--- a/Algorithm.CSharp/OptionPriceModelForOptionStylesBaseRegressionAlgorithm.cs
+++ b/Algorithm.CSharp/OptionPriceModelForOptionStylesBaseRegressionAlgorithm.cs
@@ -86,7 +86,7 @@ public void CheckGreeks(OptionChain contracts)
foreach (var contract in contracts)
{
- Greeks greeks = new Greeks();
+ Greeks greeks = null;
try
{
greeks = contract.Greeks;
@@ -110,7 +110,8 @@ public void CheckGreeks(OptionChain contracts)
// Greeks should be valid if they were successfuly accessed for supported option style
if (_optionStyleIsSupported)
{
- if (greeks.Delta == 0m && greeks.Gamma == 0m && greeks.Theta == 0m && greeks.Vega == 0m && greeks.Rho == 0m)
+ if (greeks == null ||
+ (greeks.Delta == 0m && greeks.Gamma == 0m && greeks.Theta == 0m && greeks.Vega == 0m && greeks.Rho == 0m))
{
throw new RegressionTestException($"Expected greeks to not be zero simultaneously for {contract.Symbol.Value}, an {_option.Style} style option, using {_option?.PriceModel.GetType().Name}, but they were");
}
diff --git a/Algorithm.CSharp/QuantConnect.Algorithm.CSharp.csproj b/Algorithm.CSharp/QuantConnect.Algorithm.CSharp.csproj
index 9bd55cda326c..75ace3e4ab70 100644
--- a/Algorithm.CSharp/QuantConnect.Algorithm.CSharp.csproj
+++ b/Algorithm.CSharp/QuantConnect.Algorithm.CSharp.csproj
@@ -34,7 +34,7 @@
portable
-
+
diff --git a/Algorithm.Framework/QuantConnect.Algorithm.Framework.csproj b/Algorithm.Framework/QuantConnect.Algorithm.Framework.csproj
index 8f4de2012ea2..cd7e54e89dda 100644
--- a/Algorithm.Framework/QuantConnect.Algorithm.Framework.csproj
+++ b/Algorithm.Framework/QuantConnect.Algorithm.Framework.csproj
@@ -30,7 +30,7 @@
LICENSE
-
+
diff --git a/Algorithm.Python/OptionChainFullDataRegressionAlgorithm.py b/Algorithm.Python/OptionChainFullDataRegressionAlgorithm.py
index 976d69469d01..49330c2b333c 100644
--- a/Algorithm.Python/OptionChainFullDataRegressionAlgorithm.py
+++ b/Algorithm.Python/OptionChainFullDataRegressionAlgorithm.py
@@ -12,6 +12,7 @@
# limitations under the License.
from AlgorithmImports import *
+from datetime import timedelta
###
### Regression algorithm illustrating the usage of the method
@@ -27,14 +28,16 @@ def initialize(self):
goog = self.add_equity("GOOG").symbol
+ option_chain = self.option_chain(goog)
+
+ # Demonstration using data frame:
+ df = option_chain.data_frame
# Get contracts expiring within 10 days, with an implied volatility greater than 0.5 and a delta less than 0.5
- contracts = [
- contract_data
- for contract_data in self.option_chain(goog)
- if contract_data.id.date - self.time <= timedelta(days=10) and contract_data.implied_volatility > 0.5 and contract_data.greeks.delta < 0.5
- ]
- # Get the contract with the latest expiration date
- self._option_contract = sorted(contracts, key=lambda x: x.id.date, reverse=True)[0]
+ contracts = df.loc[(df.expiry <= self.time + timedelta(days=10)) & (df.impliedvolatility > 0.5) & (df.delta < 0.5)]
+
+ # Get the contract with the latest expiration date.
+ # Note: the result of df.loc[] is a series, and its name is a tuple with a single element (contract symbol)
+ self._option_contract = contracts.loc[contracts.expiry.idxmax()].name[0]
self.add_option_contract(self._option_contract)
diff --git a/Algorithm.Python/OptionChainedUniverseSelectionModelRegressionAlgorithm.py b/Algorithm.Python/OptionChainedUniverseSelectionModelRegressionAlgorithm.py
index 0a03690afe09..e800f1fa47e6 100644
--- a/Algorithm.Python/OptionChainedUniverseSelectionModelRegressionAlgorithm.py
+++ b/Algorithm.Python/OptionChainedUniverseSelectionModelRegressionAlgorithm.py
@@ -23,7 +23,7 @@ def initialize(self):
self.set_start_date(2014, 6, 6)
self.set_end_date(2014, 6, 6)
self.set_cash(100000)
-
+
universe = self.add_universe("my-minute-universe-name", lambda time: [ "AAPL", "TWX" ])
self.add_universe_selection(
OptionChainedUniverseSelectionModel(
@@ -34,9 +34,9 @@ def initialize(self):
.expiration(0, 180))
)
)
-
+
def on_data(self, slice):
- if self.portfolio.invested or not (self.is_market_open("AAPL") and self.is_market_open("AAPL")): return
+ if self.portfolio.invested or not (self.is_market_open("AAPL") and self.is_market_open("TWX")): return
values = list(map(lambda x: x.value, filter(lambda x: x.key == "?AAPL" or x.key == "?TWX", slice.option_chains)))
for chain in values:
# we sort the contracts to find at the money (ATM) contract with farthest expiration
diff --git a/Algorithm.Python/OptionChainsMultipleFullDataRegressionAlgorithm.py b/Algorithm.Python/OptionChainsMultipleFullDataRegressionAlgorithm.py
new file mode 100644
index 000000000000..a53e55ff76ff
--- /dev/null
+++ b/Algorithm.Python/OptionChainsMultipleFullDataRegressionAlgorithm.py
@@ -0,0 +1,62 @@
+# 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.
+
+from AlgorithmImports import *
+from datetime import timedelta
+
+###
+### Regression algorithm illustrating the usage of the method
+### to get multiple option chains, which contains additional data besides the symbols, including prices, implied volatility and greeks.
+### It also shows how this data can be used to filter the contracts based on certain criteria.
+###
+class OptionChainsMultipleFullDataRegressionAlgorithm(QCAlgorithm):
+
+ def initialize(self):
+ self.set_start_date(2015, 12, 24)
+ self.set_end_date(2015, 12, 24)
+ self.set_cash(100000)
+
+ goog = self.add_equity("GOOG").symbol
+ spx = self.add_index("SPX").symbol
+
+ chains = self.option_chains([goog, spx])
+
+ self._goog_option_contract = self.get_contract(chains, goog, timedelta(days=10))
+ self._spx_option_contract = self.get_contract(chains, spx, timedelta(days=60))
+
+ self.add_option_contract(self._goog_option_contract)
+ self.add_index_option_contract(self._spx_option_contract)
+
+ def get_contract(self, chains: OptionChains, underlying: Symbol, expiry_span: timedelta) -> Symbol:
+ df = chains.data_frame
+
+ # Index by the requested underlying, by getting all data with canonicals which underlying is the requested underlying symbol:
+ canonicals = df.index.get_level_values('canonical')
+ condition = [canonical for canonical in canonicals if canonical.underlying == underlying]
+ df = df.loc[condition]
+
+ # Get contracts expiring in the next 10 days with an implied volatility greater than 0.5 and a delta less than 0.5
+ contracts = df.loc[(df.expiry <= self.time + expiry_span) & (df.impliedvolatility > 0.5) & (df.delta < 0.5)]
+
+ # Select the contract with the latest expiry date
+ contracts.sort_values(by='expiry', ascending=False, inplace=True)
+
+ # Get the symbol: the resulting series name is a tuple (canonical symbol, contract symbol)
+ return contracts.iloc[0].name[1]
+
+ def on_data(self, data):
+ # Do some trading with the selected contract for sample purposes
+ if not self.portfolio.invested:
+ self.market_order(self._goog_option_contract, 1)
+ else:
+ self.liquidate()
diff --git a/Algorithm.Python/OptionPriceModelForOptionStylesBaseRegressionAlgorithm.py b/Algorithm.Python/OptionPriceModelForOptionStylesBaseRegressionAlgorithm.py
index 37907f3ca573..de43f774c95f 100644
--- a/Algorithm.Python/OptionPriceModelForOptionStylesBaseRegressionAlgorithm.py
+++ b/Algorithm.Python/OptionPriceModelForOptionStylesBaseRegressionAlgorithm.py
@@ -53,7 +53,7 @@ def check_greeks(self, contracts):
self._tried_greeks_calculation = True
for contract in contracts:
- greeks = Greeks()
+ greeks = None
try:
greeks = contract.greeks
@@ -70,9 +70,10 @@ def check_greeks(self, contracts):
# Delta can be {-1, 0, 1} if the price is too wild, rho can be 0 if risk free rate is 0
# Vega can be 0 if the price is very off from theoretical price, Gamma = 0 if Delta belongs to {-1, 1}
if (self._option_style_is_supported
- and ((contract.right == OptionRight.CALL and (greeks.delta < 0.0 or greeks.delta > 1.0 or greeks.rho < 0.0))
- or (contract.right == OptionRight.PUT and (greeks.delta < -1.0 or greeks.delta > 0.0 or greeks.rho > 0.0))
- or greeks.theta == 0.0 or greeks.vega < 0.0 or greeks.gamma < 0.0)):
+ and (greeks is None
+ or ((contract.right == OptionRight.CALL and (greeks.delta < 0.0 or greeks.delta > 1.0 or greeks.rho < 0.0))
+ or (contract.right == OptionRight.PUT and (greeks.delta < -1.0 or greeks.delta > 0.0 or greeks.rho > 0.0))
+ or greeks.theta == 0.0 or greeks.vega < 0.0 or greeks.gamma < 0.0))):
raise Exception(f'Expected greeks to have valid values. Greeks were: Delta: {greeks.delta}, Rho: {greeks.rho}, Theta: {greeks.theta}, Vega: {greeks.vega}, Gamma: {greeks.gamma}')
diff --git a/Algorithm.Python/QuantConnect.Algorithm.Python.csproj b/Algorithm.Python/QuantConnect.Algorithm.Python.csproj
index 5b199146b18e..1713557d4efa 100644
--- a/Algorithm.Python/QuantConnect.Algorithm.Python.csproj
+++ b/Algorithm.Python/QuantConnect.Algorithm.Python.csproj
@@ -39,7 +39,7 @@
-
+
diff --git a/Algorithm/QCAlgorithm.Indicators.cs b/Algorithm/QCAlgorithm.Indicators.cs
index 01a9814d7354..48b22e5813bc 100644
--- a/Algorithm/QCAlgorithm.Indicators.cs
+++ b/Algorithm/QCAlgorithm.Indicators.cs
@@ -498,7 +498,7 @@ public ChandeKrollStop CKS(Symbol symbol, int atrPeriod, decimal atrMult, int pe
InitializeIndicator(indicator, resolution, selector, symbol);
return indicator;
}
-
+
///
/// Creates a new ChaikinMoneyFlow indicator.
///
@@ -4004,7 +4004,9 @@ void consumeLastPoint(IndicatorDataPoint newInputPoint)
indicator.Updated -= callback;
return new IndicatorHistory(indicatorsDataPointsByTime, indicatorsDataPointPerProperty,
- new Lazy(() => PandasConverter.GetIndicatorDataFrame(indicatorsDataPointPerProperty.Select(x => new KeyValuePair>(x.Name, x.Values)))));
+ new Lazy(
+ () => PandasConverter.GetIndicatorDataFrame(indicatorsDataPointPerProperty.Select(x => new KeyValuePair>(x.Name, x.Values))),
+ isThreadSafe: false));
}
private Type GetDataTypeFromSelector(Func selector)
diff --git a/Algorithm/QCAlgorithm.Python.cs b/Algorithm/QCAlgorithm.Python.cs
index 0068de131f04..b3fd794d6a6c 100644
--- a/Algorithm/QCAlgorithm.Python.cs
+++ b/Algorithm/QCAlgorithm.Python.cs
@@ -1639,6 +1639,21 @@ public void AddCommand(PyObject type)
};
}
+
+ ///
+ /// Get the option chains for the specified symbols at the current time ()
+ ///
+ ///
+ /// The symbols for which the option chain is asked for.
+ /// It can be either the canonical options or the underlying symbols.
+ ///
+ /// The option chains
+ [DocumentationAttribute(AddingData)]
+ public OptionChains OptionChains(PyObject symbols)
+ {
+ return OptionChains(symbols.ConvertToSymbolEnumerable());
+ }
+
///
/// Get an authenticated link to execute the given command instance
///
@@ -1770,8 +1785,9 @@ private PyObject TryCleanupCollectionDataFrame(Type dataType, PyObject history)
{
if (!dynamic.empty)
{
- using PyObject columns = dynamic.columns;
- if (columns.As().Contains("data"))
+ using var columns = new PySequence(dynamic.columns);
+ using var dataKey = "data".ToPython();
+ if (columns.Contains(dataKey))
{
history = dynamic["data"];
}
diff --git a/Algorithm/QCAlgorithm.cs b/Algorithm/QCAlgorithm.cs
index c85f52f498be..f65c9e56a8d5 100644
--- a/Algorithm/QCAlgorithm.cs
+++ b/Algorithm/QCAlgorithm.cs
@@ -3356,38 +3356,57 @@ public List Fundamentals(List symbols)
/// The symbol for which the option chain is asked for.
/// It can be either the canonical option or the underlying symbol.
///
- ///
- /// The option chain as an enumerable of ,
- /// each containing the contract symbol along with additional data, including daily price data,
- /// implied volatility and greeks.
- ///
+ /// The option chain
///
/// As of 2024/09/11, future options chain will not contain any additional data (e.g. daily price data, implied volatility and greeks),
/// it will be populated with the contract symbol only. This is expected to change in the future.
///
[DocumentationAttribute(AddingData)]
- public DataHistory OptionChain(Symbol symbol)
+ public OptionChain OptionChain(Symbol symbol)
{
- var canonicalSymbol = GetCanonicalOptionSymbol(symbol);
- IEnumerable optionChain;
+ return OptionChains(new[] { symbol }).Values.SingleOrDefault() ?? new OptionChain(GetCanonicalOptionSymbol(symbol), Time.Date);
+ }
- // TODO: Until future options are supported by OptionUniverse, we need to fall back to the OptionChainProvider for them
- if (canonicalSymbol.SecurityType != SecurityType.FutureOption)
- {
- var history = History(canonicalSymbol, 1);
- optionChain = history?.SingleOrDefault()?.Data?.Cast() ?? Enumerable.Empty();
- }
- else
+ ///
+ /// Get the option chains for the specified symbols at the current time ()
+ ///
+ ///
+ /// The symbols for which the option chain is asked for.
+ /// It can be either the canonical options or the underlying symbols.
+ ///
+ /// The option chains
+ [DocumentationAttribute(AddingData)]
+ public OptionChains OptionChains(IEnumerable symbols)
+ {
+ var canonicalSymbols = symbols.Select(GetCanonicalOptionSymbol).ToList();
+ var optionCanonicalSymbols = canonicalSymbols.Where(x => x.SecurityType != SecurityType.FutureOption);
+ var futureOptionCanonicalSymbols = canonicalSymbols.Where(x => x.SecurityType == SecurityType.FutureOption);
+
+ var optionChainsData = History(optionCanonicalSymbols, 1).GetUniverseData()
+ .Select(x => (x.Keys.Single(), x.Values.Single().Cast()));
+
+ // TODO: For FOPs, we fall back to the option chain provider until OptionUniverse supports them
+ var futureOptionChainsData = futureOptionCanonicalSymbols.Select(symbol =>
{
- optionChain = OptionChainProvider.GetOptionContractList(canonicalSymbol, Time)
+ var optionChainData = OptionChainProvider.GetOptionContractList(symbol, Time)
.Select(contractSymbol => new OptionUniverse()
{
Symbol = contractSymbol,
- EndTime = Time.Date
+ EndTime = Time.Date,
});
+ return (symbol, optionChainData);
+ });
+
+ var time = Time.Date;
+ var chains = new OptionChains(time);
+ foreach (var (symbol, contracts) in optionChainsData.Concat(futureOptionChainsData))
+ {
+ var symbolProperties = SymbolPropertiesDatabase.GetSymbolProperties(symbol.ID.Market, symbol, symbol.SecurityType, AccountCurrency);
+ var optionChain = new OptionChain(symbol, time, contracts, symbolProperties);
+ chains.Add(symbol, optionChain);
}
- return new DataHistory(optionChain, new Lazy(() => PandasConverter.GetDataFrame(optionChain)));
+ return chains;
}
///
diff --git a/Algorithm/QuantConnect.Algorithm.csproj b/Algorithm/QuantConnect.Algorithm.csproj
index 2a59f8f6235b..d2fe1d7621db 100644
--- a/Algorithm/QuantConnect.Algorithm.csproj
+++ b/Algorithm/QuantConnect.Algorithm.csproj
@@ -30,7 +30,7 @@
LICENSE
-
+
diff --git a/AlgorithmFactory/QuantConnect.AlgorithmFactory.csproj b/AlgorithmFactory/QuantConnect.AlgorithmFactory.csproj
index 6a59f8ca8c6d..f92733aaeff6 100644
--- a/AlgorithmFactory/QuantConnect.AlgorithmFactory.csproj
+++ b/AlgorithmFactory/QuantConnect.AlgorithmFactory.csproj
@@ -29,7 +29,7 @@
LICENSE
-
+
diff --git a/Common/Data/IBaseData.cs b/Common/Data/IBaseData.cs
index 728f5f20b724..9eaf56a9ff75 100644
--- a/Common/Data/IBaseData.cs
+++ b/Common/Data/IBaseData.cs
@@ -1,11 +1,11 @@
-/*
+/*
* 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");
+ *
+ * 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.
@@ -21,7 +21,7 @@ namespace QuantConnect.Data
///
/// Base Data Class: Type, Timestamp, Key -- Base Features.
///
- public interface IBaseData
+ public interface IBaseData : ISymbolProvider
{
///
/// Market Data Type of this data - does it come in individual price packets or is it grouped into OHLC.
@@ -31,7 +31,7 @@ MarketDataType DataType
get;
set;
}
-
+
///
/// Time keeper of data -- all data is timeseries based.
///
@@ -49,16 +49,6 @@ DateTime EndTime
get;
set;
}
-
-
- ///
- /// Symbol for underlying Security
- ///
- Symbol Symbol
- {
- get;
- set;
- }
///
@@ -112,7 +102,7 @@ decimal Price
BaseData Reader(SubscriptionDataConfig config, StreamReader stream, DateTime date, bool isLiveMode);
///
- /// Return the URL string source of the file. This will be converted to a stream
+ /// Return the URL string source of the file. This will be converted to a stream
///
/// Type of datafeed we're reqesting - backtest or live
/// Configuration object
diff --git a/Common/Data/ISymbolProvider.cs b/Common/Data/ISymbolProvider.cs
new file mode 100644
index 000000000000..e2a329ee9366
--- /dev/null
+++ b/Common/Data/ISymbolProvider.cs
@@ -0,0 +1,32 @@
+/*
+ * 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.Data
+{
+ ///
+ /// Base data with a symbol
+ ///
+ public interface ISymbolProvider
+ {
+ ///
+ /// Gets the Symbol
+ ///
+ Symbol Symbol
+ {
+ get;
+ set;
+ }
+ }
+}
diff --git a/Common/Data/Market/BaseGreeks.cs b/Common/Data/Market/BaseGreeks.cs
deleted file mode 100644
index 6374d014ed5e..000000000000
--- a/Common/Data/Market/BaseGreeks.cs
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * 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.Data.Market
-{
- ///
- /// Defines the greeks
- ///
- public abstract class BaseGreeks
- {
- ///
- /// Gets the delta.
- ///
- /// Delta measures the rate of change of the option value with respect to changes in
- /// the underlying asset'sprice. (∂V/∂S)
- ///
- ///
- public abstract decimal Delta { get; protected set; }
-
- ///
- /// Gets the gamma.
- ///
- /// Gamma measures the rate of change of Delta with respect to changes in
- /// the underlying asset'sprice. (∂²V/∂S²)
- ///
- ///
- public abstract decimal Gamma { get; protected set; }
-
- ///
- /// Gets the vega.
- ///
- /// Vega measures the rate of change of the option value with respect to changes in
- /// the underlying's volatility. (∂V/∂σ)
- ///
- ///
- public abstract decimal Vega { get; protected set; }
-
- ///
- /// Gets the theta.
- ///
- /// Theta measures the rate of change of the option value with respect to changes in
- /// time. This is commonly known as the 'time decay.' (∂V/∂τ)
- ///
- ///
- public abstract decimal Theta { get; protected set; }
-
- ///
- /// Gets the rho.
- ///
- /// Rho measures the rate of change of the option value with respect to changes in
- /// the risk free interest rate. (∂V/∂r)
- ///
- ///
- public abstract decimal Rho { get; protected set; }
-
- ///
- /// Gets the lambda.
- ///
- /// Lambda is the percentage change in option value per percentage change in the
- /// underlying's price, a measure of leverage. Sometimes referred to as gearing.
- /// (∂V/∂S ✕ S/V)
- ///
- ///
- public abstract decimal Lambda { get; protected set; }
-
- ///
- /// Gets the lambda.
- ///
- /// Lambda is the percentage change in option value per percentage change in the
- /// underlying's price, a measure of leverage. Sometimes referred to as gearing.
- /// (∂V/∂S ✕ S/V)
- ///
- ///
- ///
- /// Alias for required for compatibility with Python when
- /// PEP8 API is used (lambda is a reserved keyword in Python).
- ///
- public virtual decimal Lambda_ => Lambda;
-
- ///
- /// Gets the theta per day.
- ///
- /// Theta measures the rate of change of the option value with respect to changes in
- /// time. This is commonly known as the 'time decay.' (∂V/∂τ)
- ///
- ///
- public virtual decimal ThetaPerDay => Theta / 365m;
- }
-}
diff --git a/Common/Data/Market/Greeks.cs b/Common/Data/Market/Greeks.cs
index fca5b43ec902..216f45c2eed1 100644
--- a/Common/Data/Market/Greeks.cs
+++ b/Common/Data/Market/Greeks.cs
@@ -13,146 +13,89 @@
* limitations under the License.
*/
-using System;
-
namespace QuantConnect.Data.Market
{
///
/// Defines the greeks
///
- public class Greeks : BaseGreeks
+ public abstract class Greeks
{
- private Lazy _delta;
- private Lazy _gamma;
- private Lazy _vega;
- private Lazy _theta;
- private Lazy _rho;
- private Lazy _lambda;
-
- // _deltagamma stores gamma and delta combined and is done
- // for optimization purposes (approximation of delta and gamma is very similar)
- private Lazy> _deltaGamma;
-
- ///
- public override decimal Delta
- {
- get
- {
- return _delta != null ? _delta.Value : _deltaGamma.Value.Item1;
- }
- protected set
- {
- _delta = new Lazy(() => value);
- }
- }
-
- ///
- public override decimal Gamma
- {
- get
- {
- return _gamma != null ? _gamma.Value : _deltaGamma.Value.Item2;
- }
- protected set
- {
- _gamma = new Lazy(() => value);
- }
- }
-
- ///
- public override decimal Vega
- {
- get
- {
- return _vega.Value;
- }
- protected set
- {
- _vega = new Lazy(() => value);
- }
- }
+ ///
+ /// Gets the delta.
+ ///
+ /// Delta measures the rate of change of the option value with respect to changes in
+ /// the underlying asset'sprice. (∂V/∂S)
+ ///
+ ///
+ public abstract decimal Delta { get; }
- ///
- public override decimal Theta
- {
- get
- {
- return _theta.Value;
- }
- protected set
- {
- _theta = new Lazy(() => value);
- }
- }
+ ///
+ /// Gets the gamma.
+ ///
+ /// Gamma measures the rate of change of Delta with respect to changes in
+ /// the underlying asset'sprice. (∂²V/∂S²)
+ ///
+ ///
+ public abstract decimal Gamma { get; }
- ///
- public override decimal Rho
- {
- get
- {
- return _rho.Value;
- }
- protected set
- {
- _rho = new Lazy(() => value);
- }
- }
+ ///
+ /// Gets the vega.
+ ///
+ /// Vega measures the rate of change of the option value with respect to changes in
+ /// the underlying's volatility. (∂V/∂σ)
+ ///
+ ///
+ public abstract decimal Vega { get; }
- ///
- public override decimal Lambda
- {
- get
- {
- return _lambda.Value;
- }
- protected set
- {
- _lambda = new Lazy(() => value);
- }
- }
+ ///
+ /// Gets the theta.
+ ///
+ /// Theta measures the rate of change of the option value with respect to changes in
+ /// time. This is commonly known as the 'time decay.' (∂V/∂τ)
+ ///
+ ///
+ public abstract decimal Theta { get; }
///
- /// Initializes a new default instance of the class
+ /// Gets the rho.
+ ///
+ /// Rho measures the rate of change of the option value with respect to changes in
+ /// the risk free interest rate. (∂V/∂r)
+ ///
///
- public Greeks()
- : this(0m, 0m, 0m, 0m, 0m, 0m)
- {
- }
+ public abstract decimal Rho { get; }
///
- /// Initializes a new instance of the class
+ /// Gets the lambda.
+ ///
+ /// Lambda is the percentage change in option value per percentage change in the
+ /// underlying's price, a measure of leverage. Sometimes referred to as gearing.
+ /// (∂V/∂S ✕ S/V)
+ ///
///
- public Greeks(decimal delta, decimal gamma, decimal vega, decimal theta, decimal rho, decimal lambda)
- {
- Delta = delta;
- Gamma = gamma;
- Vega = vega;
- Theta = theta;
- Rho = rho;
- Lambda = lambda;
- }
+ public abstract decimal Lambda { get; }
+
///
- /// Initializes a new instance of the class
+ /// Gets the lambda.
+ ///
+ /// Lambda is the percentage change in option value per percentage change in the
+ /// underlying's price, a measure of leverage. Sometimes referred to as gearing.
+ /// (∂V/∂S ✕ S/V)
+ ///
///
- public Greeks(Func delta, Func gamma, Func vega, Func theta, Func rho, Func lambda)
- {
- _delta = new Lazy(delta);
- _gamma = new Lazy(gamma);
- _vega = new Lazy(vega);
- _theta = new Lazy(theta);
- _rho = new Lazy(rho);
- _lambda = new Lazy(lambda);
- }
+ ///
+ /// Alias for required for compatibility with Python when
+ /// PEP8 API is used (lambda is a reserved keyword in Python).
+ ///
+ public virtual decimal Lambda_ => Lambda;
+
///
- /// Initializes a new instance of the class
+ /// Gets the theta per day.
+ ///
+ /// Theta measures the rate of change of the option value with respect to changes in
+ /// time. This is commonly known as the 'time decay.' (∂V/∂τ)
+ ///
///
- public Greeks(Func> deltaGamma, Func vega, Func theta, Func rho, Func lambda)
- {
- _deltaGamma = new Lazy>(deltaGamma);
- _vega = new Lazy(vega);
- _theta = new Lazy(theta);
- _rho = new Lazy(rho);
- _lambda = new Lazy(lambda);
- }
+ public virtual decimal ThetaPerDay => Theta / 365m;
}
}
diff --git a/Common/Data/Market/ModeledGreeks.cs b/Common/Data/Market/ModeledGreeks.cs
new file mode 100644
index 000000000000..e1feb8fb5726
--- /dev/null
+++ b/Common/Data/Market/ModeledGreeks.cs
@@ -0,0 +1,75 @@
+/*
+ * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
+ * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+using System;
+
+namespace QuantConnect.Data.Market
+{
+ ///
+ /// Defines the greeks
+ ///
+ internal class ModeledGreeks : Greeks
+ {
+ private Lazy _delta;
+ private Lazy _gamma;
+ private Lazy _vega;
+ private Lazy _theta;
+ private Lazy _rho;
+ private Lazy _lambda;
+
+ ///
+ /// Gets the delta
+ ///
+ public override decimal Delta => _delta.Value;
+
+ ///
+ /// Gets the gamma
+ ///
+ public override decimal Gamma => _gamma.Value;
+
+ ///
+ /// Gets the vega
+ ///
+ public override decimal Vega => _vega.Value;
+
+ ///
+ /// Gets the theta
+ ///
+ public override decimal Theta => _theta.Value;
+
+ ///
+ /// Gets the rho
+ ///
+ public override decimal Rho => _rho.Value;
+
+ ///
+ /// Gets the lambda
+ ///
+ public override decimal Lambda => _lambda.Value;
+
+ ///
+ /// Initializes a new instance of the class
+ ///
+ public ModeledGreeks(Func delta, Func gamma, Func vega, Func theta, Func rho, Func lambda)
+ {
+ _delta = new Lazy(delta, isThreadSafe: false);
+ _gamma = new Lazy(gamma, isThreadSafe: false);
+ _vega = new Lazy(vega, isThreadSafe: false);
+ _theta = new Lazy(theta, isThreadSafe: false);
+ _rho = new Lazy(rho, isThreadSafe: false);
+ _lambda = new Lazy(lambda, isThreadSafe: false);
+ }
+ }
+}
diff --git a/Common/Data/Market/NullGreeks.cs b/Common/Data/Market/NullGreeks.cs
new file mode 100644
index 000000000000..695bbf412880
--- /dev/null
+++ b/Common/Data/Market/NullGreeks.cs
@@ -0,0 +1,62 @@
+/*
+ * 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.Data.Market
+{
+ ///
+ /// Defines greeks that are all zero
+ ///
+ internal class NullGreeks : Greeks
+ {
+ ///
+ /// Singleton instance of
+ ///
+ public static readonly NullGreeks Instance = new NullGreeks();
+
+ ///
+ /// Gets the delta
+ ///
+ public override decimal Delta => decimal.Zero;
+
+ ///
+ /// Gets the gamma
+ ///
+ public override decimal Gamma => decimal.Zero;
+
+ ///
+ /// Gets the vega
+ ///
+ public override decimal Vega => decimal.Zero;
+
+ ///
+ /// Gets the theta
+ ///
+ public override decimal Theta => decimal.Zero;
+
+ ///
+ /// Gets the rho
+ ///
+ public override decimal Rho => decimal.Zero;
+
+ ///
+ /// Gets the lambda
+ ///
+ public override decimal Lambda => decimal.Zero;
+
+ private NullGreeks()
+ {
+ }
+ }
+}
diff --git a/Common/Data/Market/OptionChain.cs b/Common/Data/Market/OptionChain.cs
index b4b54e47e801..8f3d5dc521d4 100644
--- a/Common/Data/Market/OptionChain.cs
+++ b/Common/Data/Market/OptionChain.cs
@@ -17,6 +17,10 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
+using Python.Runtime;
+using QuantConnect.Data.UniverseSelection;
+using QuantConnect.Python;
+using QuantConnect.Securities;
using QuantConnect.Securities.Option;
using QuantConnect.Util;
@@ -29,6 +33,7 @@ namespace QuantConnect.Data.Market
public class OptionChain : BaseData, IEnumerable
{
private readonly Dictionary>> _auxiliaryData = new Dictionary>>();
+ private readonly Lazy _dataframe;
///
/// Gets the most recent trade information for the underlying. This may
@@ -79,12 +84,18 @@ public HashSet FilteredContracts
get; private set;
}
+ ///
+ /// The data frame representation of the option chain
+ ///
+ public PyObject DataFrame => _dataframe.Value;
+
///
/// Initializes a new default instance of the class
///
private OptionChain()
{
DataType = MarketDataType.OptionChain;
+ _dataframe = new Lazy(() => new PandasConverter().GetDataFrame(this, symbolOnlyIndex: true), isThreadSafe: false);
}
///
@@ -93,6 +104,7 @@ private OptionChain()
/// The symbol for this chain.
/// The time of this chain
public OptionChain(Symbol canonicalOptionSymbol, DateTime time)
+ : this()
{
Time = time;
Symbol = canonicalOptionSymbol;
@@ -116,6 +128,7 @@ public OptionChain(Symbol canonicalOptionSymbol, DateTime time)
/// All contracts for this option chain
/// The filtered list of contracts for this option chain
public OptionChain(Symbol canonicalOptionSymbol, DateTime time, BaseData underlying, IEnumerable trades, IEnumerable quotes, IEnumerable contracts, IEnumerable filteredContracts)
+ : this()
{
Time = time;
Underlying = underlying;
@@ -176,6 +189,32 @@ public OptionChain(Symbol canonicalOptionSymbol, DateTime time, BaseData underly
}
}
+ ///
+ /// Initializes a new option chain for a list of contracts as instances
+ ///
+ /// The canonical option symbol
+ /// The time of this chain
+ /// The list of contracts data
+ /// The option symbol properties
+ public OptionChain(Symbol canonicalOptionSymbol, DateTime time, IEnumerable contracts, SymbolProperties symbolProperties)
+ : this(canonicalOptionSymbol, time)
+ {
+ Time = time;
+ Symbol = canonicalOptionSymbol;
+ DataType = MarketDataType.OptionChain;
+
+ Ticks = new Ticks(time);
+ TradeBars = new TradeBars(time);
+ QuoteBars = new QuoteBars(time);
+ Contracts = new OptionContracts(time);
+
+ foreach (var contractData in contracts)
+ {
+ Contracts[contractData.Symbol] = OptionContract.Create(contractData, symbolProperties);
+ Underlying ??= contractData.Underlying;
+ }
+ }
+
///
/// Gets the auxiliary data with the specified type and symbol
///
@@ -315,4 +354,4 @@ internal void AddAuxData(BaseData baseData)
list.Add(baseData);
}
}
-}
\ No newline at end of file
+}
diff --git a/Common/Data/Market/OptionChains.cs b/Common/Data/Market/OptionChains.cs
index c1ad7422af19..109d16aa7a2b 100644
--- a/Common/Data/Market/OptionChains.cs
+++ b/Common/Data/Market/OptionChains.cs
@@ -1,4 +1,4 @@
-/*
+/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
@@ -13,7 +13,11 @@
* limitations under the License.
*/
+using Python.Runtime;
+using QuantConnect.Python;
using System;
+using System.Collections.Generic;
+using System.Linq;
namespace QuantConnect.Data.Market
{
@@ -22,10 +26,15 @@ namespace QuantConnect.Data.Market
///
public class OptionChains : DataDictionary
{
+ private static readonly IEnumerable _indexNames = new[] { "canonical", "symbol" };
+
+ private readonly Lazy _dataframe;
+
///
/// Creates a new instance of the dictionary
///
public OptionChains()
+ : this(default)
{
}
@@ -35,8 +44,14 @@ public OptionChains()
public OptionChains(DateTime time)
: base(time)
{
+ _dataframe = new Lazy(InitializeDataFrame, isThreadSafe: false);
}
+ ///
+ /// The data frame representation of the option chains
+ ///
+ public PyObject DataFrame => _dataframe.Value;
+
///
/// Gets or sets the OptionChain with the specified ticker.
///
@@ -56,5 +71,13 @@ public OptionChains(DateTime time)
/// The Symbol of the element to get or set.
/// Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations
public new OptionChain this[Symbol symbol] { get { return base[symbol]; } set { base[symbol] = value; } }
+
+ private PyObject InitializeDataFrame()
+ {
+ var dataFrames = this.Select(kvp => kvp.Value.DataFrame).ToList();
+ var canonicalSymbols = this.Select(kvp => kvp.Key);
+
+ return PandasConverter.ConcatDataFrames(dataFrames, keys: canonicalSymbols, names: _indexNames);
+ }
}
-}
\ No newline at end of file
+}
diff --git a/Common/Data/Market/OptionContract.cs b/Common/Data/Market/OptionContract.cs
index 1c4bdef042fc..b5b4cd693367 100644
--- a/Common/Data/Market/OptionContract.cs
+++ b/Common/Data/Market/OptionContract.cs
@@ -13,6 +13,7 @@
* limitations under the License.
*/
+using QuantConnect.Data.UniverseSelection;
using QuantConnect.Interfaces;
using QuantConnect.Securities;
using QuantConnect.Securities.Option;
@@ -23,25 +24,28 @@ namespace QuantConnect.Data.Market
///
/// Defines a single option contract at a specific expiration and strike price
///
- public class OptionContract
+ public class OptionContract : ISymbolProvider, ISymbol
{
- private Lazy _optionPriceModelResult = new(() => OptionPriceModelResult.None);
+ private IOptionData _optionData = OptionPriceModelResultData.Null;
+ private readonly SymbolProperties _symbolProperties;
///
/// Gets the option contract's symbol
///
public Symbol Symbol
{
- get; private set;
+ get; set;
}
+ ///
+ /// The security identifier of the option symbol
+ ///
+ public SecurityIdentifier ID => Symbol.ID;
+
///
/// Gets the underlying security's symbol
///
- public Symbol UnderlyingSymbol
- {
- get; private set;
- }
+ public Symbol UnderlyingSymbol => Symbol.Underlying;
///
/// Gets the strike price
@@ -51,11 +55,7 @@ public Symbol UnderlyingSymbol
///
/// Gets the strike price multiplied by the strike multiplier
///
- public decimal ScaledStrike
- {
- get;
- private set;
- }
+ public decimal ScaledStrike => Strike * _symbolProperties.StrikeMultiplier;
///
/// Gets the expiration date
@@ -75,17 +75,17 @@ public decimal ScaledStrike
///
/// Gets the theoretical price of this option contract as computed by the
///
- public decimal TheoreticalPrice => _optionPriceModelResult.Value.TheoreticalPrice;
+ public decimal TheoreticalPrice => _optionData.TheoreticalPrice;
///
/// Gets the implied volatility of the option contract as computed by the
///
- public decimal ImpliedVolatility => _optionPriceModelResult.Value.ImpliedVolatility;
+ public decimal ImpliedVolatility => _optionData.ImpliedVolatility;
///
/// Gets the greeks for this contract
///
- public Greeks Greeks => _optionPriceModelResult.Value.Greeks;
+ public Greeks Greeks => _optionData.Greeks;
///
/// Gets the local date time this contract's data was last updated
@@ -98,77 +98,63 @@ public DateTime Time
///
/// Gets the open interest
///
- public decimal OpenInterest
- {
- get; set;
- }
+ public decimal OpenInterest => _optionData.OpenInterest;
///
/// Gets the last price this contract traded at
///
- public decimal LastPrice
- {
- get; set;
- }
+ public decimal LastPrice => _optionData.LastPrice;
///
/// Gets the last volume this contract traded at
///
- public long Volume
- {
- get; set;
- }
+ public long Volume => _optionData.Volume;
///
/// Gets the current bid price
///
- public decimal BidPrice
- {
- get; set;
- }
+ public decimal BidPrice => _optionData.BidPrice;
///
/// Get the current bid size
///
- public long BidSize
- {
- get; set;
- }
+ public long BidSize => _optionData.BidSize;
///
/// Gets the ask price
///
- public decimal AskPrice
- {
- get; set;
- }
+ public decimal AskPrice => _optionData.AskPrice;
///
/// Gets the current ask size
///
- public long AskSize
- {
- get; set;
- }
+ public long AskSize => _optionData.AskSize;
///
/// Gets the last price the underlying security traded at
///
- public decimal UnderlyingLastPrice
- {
- get; set;
- }
+ public decimal UnderlyingLastPrice => _optionData.UnderlyingLastPrice;
///
/// Initializes a new instance of the class
///
/// The option contract security
- /// The symbol of the underlying security
- public OptionContract(ISecurityPrice security, Symbol underlyingSymbol)
+ public OptionContract(ISecurityPrice security)
{
Symbol = security.Symbol;
- UnderlyingSymbol = underlyingSymbol;
- ScaledStrike = Strike * security.SymbolProperties.StrikeMultiplier;
+ _symbolProperties = security.SymbolProperties;
+ }
+
+ ///
+ /// Initializes a new option contract from a given instance
+ ///
+ /// The option universe contract data to use as source for this contract
+ /// The contract symbol properties
+ public OptionContract(OptionUniverse contractData, SymbolProperties symbolProperties)
+ {
+ Symbol = contractData.Symbol;
+ _symbolProperties = symbolProperties;
+ _optionData = new OptionUniverseData(contractData);
}
///
@@ -177,7 +163,7 @@ public OptionContract(ISecurityPrice security, Symbol underlyingSymbol)
/// Function delegate used to evaluate the option price model
internal void SetOptionPriceModel(Func optionPriceModelEvaluator)
{
- _optionPriceModelResult = new Lazy(optionPriceModelEvaluator);
+ _optionData = new OptionPriceModelResultData(optionPriceModelEvaluator, _optionData as OptionPriceModelResultData);
}
///
@@ -189,37 +175,209 @@ internal void SetOptionPriceModel(Func optionPriceModelE
public override string ToString() => Symbol.Value;
///
- /// Creates a
+ /// Creates a
///
///
- /// provides price properties for a
- /// last price the underlying security traded at
+ /// Provides price properties for a
+ /// Last underlying security trade data
/// Option contract
- public static OptionContract Create(BaseData baseData, ISecurityPrice security, decimal underlyingLastPrice)
- => Create(baseData.Symbol.Underlying, baseData.EndTime, security, underlyingLastPrice);
+ public static OptionContract Create(BaseData baseData, ISecurityPrice security, BaseData underlying)
+ => Create(baseData.EndTime, security, underlying);
///
/// Creates a
///
- /// The symbol of the underlying security
/// local date time this contract's data was last updated
/// provides price properties for a
- /// last price the underlying security traded at
+ /// last underlying security trade data
/// Option contract
- public static OptionContract Create(Symbol underlyingSymbol, DateTime endTime, ISecurityPrice security, decimal underlyingLastPrice)
+ public static OptionContract Create(DateTime endTime, ISecurityPrice security, BaseData underlying)
{
- return new OptionContract(security, underlyingSymbol)
+ var contract = new OptionContract(security)
{
Time = endTime,
- LastPrice = security.Close,
- Volume = (long)security.Volume,
- BidPrice = security.BidPrice,
- BidSize = (long)security.BidSize,
- AskPrice = security.AskPrice,
- AskSize = (long)security.AskSize,
- OpenInterest = security.OpenInterest,
- UnderlyingLastPrice = underlyingLastPrice
};
+ contract._optionData.SetUnderlying(underlying);
+
+ return contract;
+ }
+
+ ///
+ /// Creates a new option contract from a given instance,
+ /// using its data to form a quote bar to source pricing data
+ ///
+ /// The option universe contract data to use as source for this contract
+ /// The contract symbol properties
+ public static OptionContract Create(OptionUniverse contractData, SymbolProperties symbolProperties)
+ {
+ var contract = new OptionContract(contractData, symbolProperties)
+ {
+ Time = contractData.EndTime,
+ };
+
+ return contract;
+ }
+
+ ///
+ /// Implicit conversion into
+ ///
+ /// The option contract to be converted
+ public static implicit operator Symbol(OptionContract contract)
+ {
+ return contract.Symbol;
}
+
+ ///
+ /// Updates the option contract with the new data, which can be a or or
+ ///
+ internal void Update(BaseData data)
+ {
+ if (data.Symbol == Symbol)
+ {
+ _optionData.Update(data);
+ }
+ else if (data.Symbol == UnderlyingSymbol)
+ {
+ _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);
+ }
+
+ ///
+ /// Handles option data for a contract from actual price data (trade, quote, open interest) and theoretical price model results
+ ///
+ private class OptionPriceModelResultData : IOptionData
+ {
+ public static readonly OptionPriceModelResultData Null = new(() => OptionPriceModelResult.None);
+
+ private readonly Lazy _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 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;
+ }
+ }
+
+ ///
+ /// Handles option data for a contract from a instance
+ ///
+ 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
}
}
diff --git a/Common/Data/UniverseSelection/OptionUniverse.cs b/Common/Data/UniverseSelection/OptionUniverse.cs
index 735ae4c92752..dee24e34300c 100644
--- a/Common/Data/UniverseSelection/OptionUniverse.cs
+++ b/Common/Data/UniverseSelection/OptionUniverse.cs
@@ -125,7 +125,7 @@ public decimal ImpliedVolatility
///
/// Greeks values of the option
///
- public BaseGreeks Greeks
+ public Greeks Greeks
{
get
{
@@ -290,7 +290,7 @@ public override Resolution DefaultResolution()
/// Gets the CSV string representation of this universe entry
///
public static string ToCsv(Symbol symbol, decimal open, decimal high, decimal low, decimal close, decimal volume, decimal? openInterest,
- decimal? impliedVolatility, BaseGreeks greeks)
+ decimal? impliedVolatility, Greeks greeks)
{
return $"{symbol.ID},{symbol.Value},{open},{high},{low},{close},{volume},"
+ $"{openInterest},{impliedVolatility},{greeks?.Delta},{greeks?.Gamma},{greeks?.Vega},{greeks?.Theta},{greeks?.Rho}";
@@ -331,51 +331,21 @@ private void ThrowIfNotAnOption(string propertyName)
/// Pre-calculated greeks lazily parsed from csv line.
/// It parses the greeks values from the csv line only when they are requested to avoid holding decimals in memory.
///
- private class PreCalculatedGreeks : BaseGreeks
+ private class PreCalculatedGreeks : Greeks
{
private readonly string _csvLine;
- ///
- public override decimal Delta
- {
- get => _csvLine.GetDecimalFromCsv(StartingGreeksCsvIndex);
- protected set => throw new InvalidOperationException("Delta is read-only.");
- }
+ public override decimal Delta => _csvLine.GetDecimalFromCsv(StartingGreeksCsvIndex);
- ///
- public override decimal Gamma
- {
- get => _csvLine.GetDecimalFromCsv(StartingGreeksCsvIndex + 1);
- protected set => throw new InvalidOperationException("Gamma is read-only.");
- }
+ public override decimal Gamma => _csvLine.GetDecimalFromCsv(StartingGreeksCsvIndex + 1);
- ///
- public override decimal Vega
- {
- get => _csvLine.GetDecimalFromCsv(StartingGreeksCsvIndex + 2);
- protected set => throw new InvalidOperationException("Vega is read-only.");
- }
+ public override decimal Vega => _csvLine.GetDecimalFromCsv(StartingGreeksCsvIndex + 2);
- ///
- public override decimal Theta
- {
- get => _csvLine.GetDecimalFromCsv(StartingGreeksCsvIndex + 3);
- protected set => throw new InvalidOperationException("Theta is read-only.");
- }
+ public override decimal Theta => _csvLine.GetDecimalFromCsv(StartingGreeksCsvIndex + 3);
- ///
- public override decimal Rho
- {
- get => _csvLine.GetDecimalFromCsv(StartingGreeksCsvIndex + 4);
- protected set => throw new InvalidOperationException("Rho is read-only.");
- }
+ public override decimal Rho => _csvLine.GetDecimalFromCsv(StartingGreeksCsvIndex + 4);
- ///
- public override decimal Lambda
- {
- get => decimal.Zero;
- protected set => throw new InvalidOperationException("Lambda is read-only.");
- }
+ public override decimal Lambda => decimal.Zero;
///
/// Initializes a new default instance of the class
@@ -384,6 +354,14 @@ public PreCalculatedGreeks(string csvLine)
{
_csvLine = csvLine;
}
+
+ ///
+ /// Gets a string representation of the greeks values
+ ///
+ public override string ToString()
+ {
+ return $"D: {Delta}, G: {Gamma}, V: {Vega}, T: {Theta}, R: {Rho}";
+ }
}
}
}
diff --git a/Common/PandasMapper.py b/Common/PandasMapper.py
index f20b76fc8aad..c18bd1812a83 100644
--- a/Common/PandasMapper.py
+++ b/Common/PandasMapper.py
@@ -25,31 +25,44 @@
AddReference("QuantConnect.Common")
from QuantConnect import *
+class PandasColumn(str):
+ '''
+ PandasColumn is a wrapper class for a pandas column that allows for the column to be used as a key
+ and properly compared to strings, regardless of whether it's a C# or Python string
+ (since the hash of a C# string and the same Python string are different).
+ '''
+
+ def __new__(cls, key):
+ return super().__new__(cls, key)
+
+ def __eq__(self, other):
+ # We need this since Lean created data frames might contain Symbol objects in the indexes
+ return super().__eq__(other) and type(other) is not Symbol
+
+ def __hash__(self):
+ return super().__hash__()
+
def mapper(key):
'''Maps a Symbol object or a Symbol Ticker (string) to the string representation of
Symbol SecurityIdentifier.If cannot map, returns the object
'''
keyType = type(key)
- if keyType is Symbol:
- return str(key.ID)
+ if keyType is tuple:
+ return tuple(mapper(x) for x in key)
if keyType is str:
- reserved = ['high', 'low', 'open', 'close']
- if key in reserved:
- return key
- kvp = SymbolCache.TryGetSymbol(key, None)
+ kvp = SymbolCache.try_get_symbol(key, None)
if kvp[0]:
- return str(kvp[1].ID)
+ return kvp[1]
+ return key
if keyType is list:
return [mapper(x) for x in key]
- if keyType is tuple:
- return tuple([mapper(x) for x in key])
if keyType is dict:
- return { k: mapper(v) for k, v in key.items()}
+ return {k: mapper(v) for k, v in key.items()}
return key
def wrap_keyerror_function(f):
'''Wraps function f with wrapped_function, used for functions that throw KeyError when not found.
- wrapped_function converts the args / kwargs to use alternative index keys and then calls the function.
+ wrapped_function converts the args / kwargs to use alternative index keys and then calls the function.
If this fails we fall back to the original key and try it as well, if they both fail we throw our error.
'''
def wrapped_function(*args, **kwargs):
@@ -65,14 +78,15 @@ def wrapped_function(*args, **kwargs):
return f(*newargs, **newkwargs)
except KeyError as e:
- mKey = [arg for arg in newargs if isinstance(arg, str)]
+ pass
# Execute original
# Allows for df, Series, etc indexing for keys like 'SPY' if they exist
try:
return f(*args, **kwargs)
except KeyError as e:
- oKey = [arg for arg in args if isinstance(arg, str)]
+ mKey = [str(arg) for arg in newargs if isinstance(arg, str) or isinstance(arg, Symbol)]
+ oKey = [str(arg) for arg in args if isinstance(arg, str) or isinstance(arg, Symbol)]
raise KeyError(f"No key found for either mapped or original key. Mapped Key: {mKey}; Original Key: {oKey}")
wrapped_function.__name__ = f.__name__
@@ -111,7 +125,7 @@ def wrapped_function(*args, **kwargs):
pd.core.indexes.base.Index.get_loc = wrap_keyerror_function(pd.core.indexes.base.Index.get_loc)
# Wrap our DF _getitem__ as well, even though most pathways go through the above functions
-# There are cases like indexing with an array that need to be mapped earlier to stop KeyError from arising
+# There are cases like indexing with an array that need to be mapped earlier to stop KeyError from arising
pd.core.frame.DataFrame.__getitem__ = wrap_keyerror_function(pd.core.frame.DataFrame.__getitem__)
# For older version of pandas we may need to wrap extra functions
@@ -119,7 +133,7 @@ def wrapped_function(*args, **kwargs):
pd.core.indexes.base.Index.get_value = wrap_keyerror_function(pd.core.indexes.base.Index.get_value)
# Special cases where we need to wrap a function that won't throw a keyerror when not found but instead returns true or false
-# Wrap __contains__ to support Python syntax like 'SPY' in DataFrame
+# Wrap __contains__ to support Python syntax like 'SPY' in DataFrame
pd.core.indexes.base.Index.__contains__ = wrap_bool_function(pd.core.indexes.base.Index.__contains__)
# For compatibility with PandasData.cs usage of this module (Previously wrapped classes)
diff --git a/Common/Python/PandasConverter.cs b/Common/Python/PandasConverter.cs
index 1db45d05ce73..a32011e92ab6 100644
--- a/Common/Python/PandasConverter.cs
+++ b/Common/Python/PandasConverter.cs
@@ -33,19 +33,16 @@ public class PandasConverter
private static PyObject _concat;
///
- /// Creates an instance of .
+ /// Initializes the class
///
- public PandasConverter()
+ static PandasConverter()
{
- if (_pandas == null)
+ using (Py.GIL())
{
- using (Py.GIL())
- {
- var pandas = Py.Import("pandas");
- _pandas = pandas;
- // keep it so we don't need to ask for it each time
- _concat = pandas.GetAttr("concat");
- }
+ var pandas = Py.Import("pandas");
+ _pandas = pandas;
+ // keep it so we don't need to ask for it each time
+ _concat = pandas.GetAttr("concat");
}
}
@@ -70,54 +67,34 @@ public PyObject GetDataFrame(IEnumerable data, Type dataType = null)
AddSliceDataTypeDataToDict(slice, requestedTick, requestedTradeBar, requestedQuoteBar, sliceDataDict, ref maxLevels, dataType);
}
- using (Py.GIL())
- {
- if (sliceDataDict.Count == 0)
- {
- return _pandas.DataFrame();
- }
- using var dataFrames = sliceDataDict.Select(x => x.Value.ToPandasDataFrame(maxLevels)).ToPyListUnSafe();
- using var sortDic = Py.kw("sort", true);
- var result = _concat.Invoke(new[] { dataFrames }, sortDic);
-
- foreach (var df in dataFrames)
- {
- df.Dispose();
- }
- return result;
- }
+ return CreateDataFrame(sliceDataDict, maxLevels);
}
///
/// Converts an enumerable of in a pandas.DataFrame
///
/// Enumerable of
+ /// Whether to make the index only the symbol, without time or any other index levels
/// containing a pandas.DataFrame
/// Helper method for testing
- public PyObject GetDataFrame(IEnumerable data)
- where T : IBaseData
+ public PyObject GetDataFrame(IEnumerable data, bool symbolOnlyIndex = false)
+ where T : ISymbolProvider
{
- PandasData sliceData = null;
+ var pandasDataBySymbol = new Dictionary();
+ var maxLevels = 0;
foreach (var datum in data)
{
- if (sliceData == null)
- {
- sliceData = new PandasData(datum);
- }
-
- sliceData.Add(datum);
+ var pandasData = GetPandasDataValue(pandasDataBySymbol, datum.Symbol, datum, ref maxLevels);
+ pandasData.Add(datum);
}
- using (Py.GIL())
- {
- // If sliceData is still null, data is an empty enumerable
- // returns an empty pandas.DataFrame
- if (sliceData == null)
- {
- return _pandas.DataFrame();
- }
- return sliceData.ToPandasDataFrame();
- }
+ return CreateDataFrame(pandasDataBySymbol,
+ // Use 2 instead of maxLevels for backwards compatibility
+ maxLevels: symbolOnlyIndex ? 1 : 2,
+ sort: false,
+ // Multiple data frames (one for each symbol) will be concatenated,
+ // so make sure rows with missing values only are not filtered out before concatenation
+ filterMissingValueColumns: pandasDataBySymbol.Count <= 1);
}
///
@@ -187,9 +164,92 @@ public PyObject GetIndicatorDataFrame(PyObject data)
///
public override string ToString()
{
- return _pandas == null
- ? Messages.PandasConverter.PandasModuleNotImported
- : _pandas.Repr();
+ if (_pandas == null)
+ {
+ return Messages.PandasConverter.PandasModuleNotImported;
+ }
+
+ using (Py.GIL())
+ {
+ return _pandas.Repr();
+ }
+ }
+
+ ///
+ /// Create a data frame by concatenated the resulting data frames from the given data
+ ///
+ private static PyObject CreateDataFrame(Dictionary dataBySymbol, int maxLevels = 2, bool sort = true,
+ bool filterMissingValueColumns = true)
+ {
+ using (Py.GIL())
+ {
+ if (dataBySymbol.Count == 0)
+ {
+ return _pandas.DataFrame();
+ }
+
+ var dataFrames = dataBySymbol.Select(x => x.Value.ToPandasDataFrame(maxLevels, filterMissingValueColumns));
+ var result = ConcatDataFrames(dataFrames, sort: sort, dropna: true);
+
+ foreach (var df in dataFrames)
+ {
+ df.Dispose();
+ }
+
+ return result;
+ }
+ }
+
+ ///
+ /// Concatenates multiple data frames
+ ///
+ /// The data frames to concatenate
+ ///
+ /// Optional new keys for a new multi-index level that would be added
+ /// to index each individual data frame in the resulting one
+ ///
+ /// The optional names of the new index level (and the existing ones if they need to be changed)
+ /// Whether to sort the resulting data frame
+ /// Whether to drop columns containing NA values only (Nan, None, etc)
+ /// A new data frame result from concatenating the input
+ public static PyObject ConcatDataFrames(IEnumerable dataFrames, IEnumerable