Skip to content

Commit

Permalink
Add multiple option chains api regression algorithms and other minor …
Browse files Browse the repository at this point in the history
…changes
  • Loading branch information
jhonabreul committed Sep 26, 2024
1 parent 3336a7b commit fd88ae2
Show file tree
Hide file tree
Showing 6 changed files with 230 additions and 34 deletions.
143 changes: 143 additions & 0 deletions Algorithm.CSharp/OptionChainsMultipleFullDataRegressionAlgorithm.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/*
* 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
{
/// <summary>
/// Regression algorithm illustrating the usage of the <see cref="QCAlgorithm.OptionChains(IEnumerable{Symbol})"/> 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.
/// </summary>
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.Symbol.ID.Date - Time <= expirySpan &&
contractData.ImpliedVolatility > 0.5m &&
contractData.Delta < 0.5m)
// Get the contract with the latest expiration date
.OrderByDescending(x => x.Symbol.ID.Date)
.First()
.Symbol;
}

public override void OnData(Slice slice)
{
// Do some trading with the selected contract for sample purposes
if (!Portfolio.Invested)
{
MarketOrder(_googOptionContract, 1);
}
else
{
Liquidate();
}
}

/// <summary>
/// This is used by the regression test system to indicate if the open source Lean repository has the required data to run this algorithm.
/// </summary>
public bool CanRunLocally { get; } = true;

/// <summary>
/// This is used by the regression test system to indicate which languages this algorithm is written in.
/// </summary>
public virtual List<Language> Languages { get; } = new() { Language.CSharp, Language.Python };

/// <summary>
/// Data Points count of all timeslices of algorithm
/// </summary>
public long DataPoints => 1059;

/// <summary>
/// Data Points count of the algorithm history
/// </summary>
public int AlgorithmHistoryDataPoints => 2;

/// <summary>
/// Final status of the algorithm
/// </summary>
public AlgorithmStatus AlgorithmStatus => AlgorithmStatus.Completed;

/// <summary>
/// This is used by the regression test system to indicate what the expected statistics are from running the algorithm
/// </summary>
public Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string>
{
{"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"}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,8 @@ public void CheckGreeks(OptionChain contracts)
// Greeks should be valid if they were successfuly accessed for supported option style
if (_optionStyleIsSupported)
{
if (greeks == null)
{
greeks = new ModeledGreeks();
}

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");
}
Expand Down
19 changes: 7 additions & 12 deletions Algorithm.Python/OptionChainFullDataRegressionAlgorithm.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# limitations under the License.

from AlgorithmImports import *
from datetime import timedelta

### <summary>
### Regression algorithm illustrating the usage of the <see cref="QCAlgorithm.OptionChain(Symbol)"/> method
Expand All @@ -30,19 +31,13 @@ def initialize(self):
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 = [
symbol
# Index is a tuple and the first element is the symbol
for (symbol,), contract_data in option_chain.data_frame.iterrows()
if symbol.id.date - self.time <= timedelta(days=10) and contract_data["impliedvolatility"] > 0.5 and contract_data["delta"] < 0.5
]

# Get the contract with the latest expiration date
option_contract = sorted(contracts, key=lambda x: x.id.date, reverse=True)[0]

# Can use the symbol instance to index the data frame
self.debug(f"Option contract data:\n{option_chain.data_frame.loc[(option_contract)]}")
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)
option_contract = contracts.loc[contracts['expiry'].idxmax()].name[0]

self._option_contract = self.add_option_contract(option_contract)

Expand Down
Original file line number Diff line number Diff line change
@@ -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

### <summary>
### Regression algorithm illustrating the usage of the <see cref="QCAlgorithm.OptionChains(IEnumerable{Symbol})"/> 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.
### </summary>
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 getattr(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()
15 changes: 15 additions & 0 deletions Algorithm/QCAlgorithm.Python.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1638,6 +1638,21 @@ public void AddCommand(PyObject type)
};
}


/// <summary>
/// Get the option chains for the specified symbols at the current time (<see cref="Time"/>)
/// </summary>
/// <param name="symbols">
/// The symbols for which the option chain is asked for.
/// It can be either the canonical options or the underlying symbols.
/// </param>
/// <returns>The option chains</returns>
[DocumentationAttribute(AddingData)]
public OptionChains OptionChains(PyObject symbols)
{
return OptionChains(symbols.ConvertToSymbolEnumerable());
}

/// <summary>
/// Gets indicator base type
/// </summary>
Expand Down
17 changes: 1 addition & 16 deletions Algorithm/QCAlgorithm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3382,8 +3382,7 @@ public OptionChains OptionChains(IEnumerable<Symbol> symbols)
var optionCanonicalSymbols = canonicalSymbols.Where(x => x.SecurityType != SecurityType.FutureOption);
var futureOptionCanonicalSymbols = canonicalSymbols.Where(x => x.SecurityType == SecurityType.FutureOption);

// TODO: Resolution.Daily should not be necessary. Remove when GH#8343 is resolved
var optionChainsData = History(optionCanonicalSymbols, 1, Resolution.Daily).GetUniverseData()
var optionChainsData = History(optionCanonicalSymbols, 1).GetUniverseData()
.Select(x => (x.Keys.Single(), x.Values.Single().Cast<OptionUniverse>()));

// TODO: For FOPs, we fall back to the option chain provider until OptionUniverse supports them
Expand Down Expand Up @@ -3411,20 +3410,6 @@ public OptionChains OptionChains(IEnumerable<Symbol> symbols)
return chains;
}

/// <summary>
/// Get the option chains for the specified symbols at the current time (<see cref="Time"/>)
/// </summary>
/// <param name="symbols">
/// The symbols for which the option chain is asked for.
/// It can be either the canonical options or the underlying symbols.
/// </param>
/// <returns>The option chains</returns>
[DocumentationAttribute(AddingData)]
public OptionChains OptionChains(PyObject symbols)
{
return OptionChains(symbols.ConvertToSymbolEnumerable());
}

/// <summary>
/// Register a command type to be used
/// </summary>
Expand Down

0 comments on commit fd88ae2

Please sign in to comment.