Skip to content

Commit

Permalink
Revert parts of GH PR 8395 to fix 0DTE index options
Browse files Browse the repository at this point in the history
  • Loading branch information
jhonabreul committed Nov 18, 2024
1 parent fa1ef4f commit b20be77
Show file tree
Hide file tree
Showing 13 changed files with 182 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public override void OnData(Slice slice)
/// <summary>
/// Data Points count of all timeslices of algorithm
/// </summary>
public override long DataPoints => 350;
public override long DataPoints => 356;

/// <summary>
/// Data Points count of the algorithm history
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public class BasicTemplateIndexOptionsHourlyAlgorithm : BasicTemplateIndexOption
/// <summary>
/// Data Points count of all timeslices of algorithm
/// </summary>
public override long DataPoints => 1263;
public override long DataPoints => 1269;

/// <summary>
/// Data Points count of the algorithm history
Expand Down
18 changes: 9 additions & 9 deletions Algorithm.CSharp/BasicTemplateSPXWeeklyIndexOptionsAlgorithm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,29 +123,29 @@ public override void OnOrderEvent(OrderEvent orderEvent)
{"Total Orders", "5"},
{"Average Win", "0%"},
{"Average Loss", "-0.69%"},
{"Compounding Annual Return", "55.039%"},
{"Compounding Annual Return", "54.478%"},
{"Drawdown", "0.400%"},
{"Expectancy", "-0.5"},
{"Start Equity", "1000000"},
{"End Equity", "1006025"},
{"Net Profit", "0.602%"},
{"Sharpe Ratio", "3.01"},
{"Sharpe Ratio", "2.62"},
{"Sortino Ratio", "0"},
{"Probabilistic Sharpe Ratio", "62.865%"},
{"Probabilistic Sharpe Ratio", "63.221%"},
{"Loss Rate", "50%"},
{"Win Rate", "50%"},
{"Profit-Loss Ratio", "0"},
{"Alpha", "0.249"},
{"Beta", "-0.033"},
{"Alpha", "0.067"},
{"Beta", "-0.013"},
{"Annual Standard Deviation", "0.004"},
{"Annual Variance", "0"},
{"Information Ratio", "-99.414"},
{"Tracking Error", "0.072"},
{"Treynor Ratio", "-0.382"},
{"Information Ratio", "-50.808"},
{"Tracking Error", "0.086"},
{"Treynor Ratio", "-0.725"},
{"Total Fees", "$0.00"},
{"Estimated Strategy Capacity", "$580000.00"},
{"Lowest Capacity Asset", "SPXW 31K54PVWHUJHQ|SPX 31"},
{"Portfolio Turnover", "0.48%"},
{"Portfolio Turnover", "0.40%"},
{"OrderListHash", "07a085baedb37bb7c8d460558ea77e88"}
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public override void OnEndOfAlgorithm()
/// <summary>
/// Data Points count of all timeslices of algorithm
/// </summary>
public long DataPoints => 104;
public long DataPoints => 106;

/// <summary>
/// Data Points count of the algorithm history
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,15 @@ public override void Initialize()
// BlackScholes model supports European style options
option.PriceModel = OptionPriceModels.BlackScholes();

SetWarmup(8, Resolution.Daily);
SetWarmup(7, Resolution.Daily);

Init(option, optionStyleIsSupported: true);
}

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

/// <summary>
/// Data Points count of the algorithm history
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,15 @@ public override void Initialize()
// BaroneAdesiWhaley model does not support European style options
option.PriceModel = OptionPriceModels.BaroneAdesiWhaley();

SetWarmup(8, Resolution.Daily);
SetWarmup(7, Resolution.Daily);

Init(option, optionStyleIsSupported: false);
}

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

/// <summary>
/// Data Points count of the algorithm history
Expand Down
103 changes: 103 additions & 0 deletions Algorithm.CSharp/ZeroDTEIndexOptionsRegressionAlgorithm.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect.Interfaces;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Securities;
using QuantConnect.Data;

namespace QuantConnect.Algorithm.CSharp
{
/// <summary>
/// Algorithm asserting that options are selected every day and that selection for 0DTE contracts works as expected,
/// always including the contracts that expire the same date the option chain belongs to.
/// </summary>
public class ZeroDTEIndexOptionsRegressionAlgorithm : ZeroDTEOptionsRegressionAlgorithm
{
public override void Initialize()
{
SetStartDate(2021, 01, 15);
SetEndDate(2021, 01, 15);
SetCash(100000);

var index = AddIndex("SPX");
var indexOption = AddIndexOption(index.Symbol);

indexOption.SetFilter(u => u.IncludeWeeklys().Expiration(0, 0));
_option = indexOption;

// use the underlying equity as the benchmark
SetBenchmark(index.Symbol);

_selectionDays = new List<DateTime>()
{
new DateTime(2021, 01, 15),
};
}

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

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

/// <summary>
/// Final status of the algorithm
/// </summary>
public override 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 override Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string>
{
{"Total Orders", "0"},
{"Average Win", "0%"},
{"Average Loss", "0%"},
{"Compounding Annual Return", "0%"},
{"Drawdown", "0%"},
{"Expectancy", "0"},
{"Start Equity", "100000"},
{"End Equity", "100000"},
{"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", "$0.00"},
{"Estimated Strategy Capacity", "$0"},
{"Lowest Capacity Asset", ""},
{"Portfolio Turnover", "0%"},
{"OrderListHash", "d41d8cd98f00b204e9800998ecf8427e"}
};
}
}
61 changes: 41 additions & 20 deletions Algorithm.CSharp/ZeroDTEOptionsRegressionAlgorithm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
using QuantConnect.Interfaces;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Securities;
using QuantConnect.Securities.Option;

namespace QuantConnect.Algorithm.CSharp
{
Expand All @@ -29,10 +30,13 @@ namespace QuantConnect.Algorithm.CSharp
/// </summary>
public class ZeroDTEOptionsRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
{
private List<DateTime> _selectionDays;
protected List<DateTime> _selectionDays;
private int _currentSelectionDayIndex;

private int _previouslyAddedContracts;
private bool _selectionChecked;

protected Option _option;

public override void Initialize()
{
Expand All @@ -44,6 +48,7 @@ public override void Initialize()
var option = AddOption(equity.Symbol);

option.SetFilter(u => u.IncludeWeeklys().Expiration(0, 0));
_option = option;

// use the underlying equity as the benchmark
SetBenchmark(equity.Symbol);
Expand All @@ -62,27 +67,43 @@ public override void Initialize()

public override void OnSecuritiesChanged(SecurityChanges changes)
{
// We expect selection every trading day
if (Time.Date != _selectionDays[_currentSelectionDayIndex++])
var exchangeTime = UtcTime.ConvertFromUtc(_option.Exchange.TimeZone);

// Selection happens at midnight
if (exchangeTime.TimeOfDay == TimeSpan.Zero)
{
throw new RegressionTestException($"Unexpected date. Expected {_selectionDays[_currentSelectionDayIndex]} but was {Time.Date}");
}
_selectionChecked = true;

var addedOptions = changes.AddedSecurities.Where(x => x.Symbol.SecurityType == SecurityType.Option && !x.Symbol.IsCanonical()).ToList();
// We expect selection every trading day
if (Time.Date != _selectionDays[_currentSelectionDayIndex++])
{
throw new RegressionTestException($"Unexpected date. Expected {_selectionDays[_currentSelectionDayIndex]} but was {Time.Date}");
}

if (addedOptions.Count == 0)
{
throw new RegressionTestException("No options were added");
}
var addedOptions = changes.AddedSecurities.Where(x => x.Symbol.SecurityType == _option.Symbol.SecurityType && !x.Symbol.IsCanonical()).ToList();

var removedOptions = changes.RemovedSecurities.Where(x => x.Symbol.SecurityType == SecurityType.Option && !x.Symbol.IsCanonical()).ToList();
if (addedOptions.Count == 0)
{
throw new RegressionTestException("No options were added");
}

// Since we are selecting only 0DTE contracts, they must be deselected that same day
if (removedOptions.Count != _previouslyAddedContracts)
var removedOptions = changes.RemovedSecurities.Where(x => x.Symbol.SecurityType == _option.Symbol.SecurityType && !x.Symbol.IsCanonical()).ToList();

// Since we are selecting only 0DTE contracts, they must be deselected that same day
if (removedOptions.Count != _previouslyAddedContracts)
{
throw new RegressionTestException($"Unexpected number of removed contracts. Expected {_previouslyAddedContracts} but was {removedOptions.Count}");
}
_previouslyAddedContracts = addedOptions.Count;
}
}

public override void OnEndOfAlgorithm()
{
if (!_selectionChecked)
{
throw new RegressionTestException($"Unexpected number of removed contracts. Expected {_previouslyAddedContracts} but was {removedOptions.Count}");
throw new RegressionTestException("Selection was not checked");
}
_previouslyAddedContracts = addedOptions.Count;
}

/// <summary>
Expand All @@ -93,27 +114,27 @@ public override void OnSecuritiesChanged(SecurityChanges changes)
/// <summary>
/// This is used by the regression test system to indicate which languages this algorithm is written in.
/// </summary>
public List<Language> Languages { get; } = new() { Language.CSharp };
public virtual List<Language> Languages { get; } = new() { Language.CSharp };

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

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

/// <summary>
/// Final status of the algorithm
/// </summary>
public AlgorithmStatus AlgorithmStatus => AlgorithmStatus.Completed;
public virtual 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>
public virtual Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string>
{
{"Total Orders", "0"},
{"Average Win", "0%"},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@ def initialize(self):
# BlackScholes model supports European style options
option.price_model = OptionPriceModels.black_scholes()

self.set_warmup(8, Resolution.DAILY)
self.set_warmup(7, Resolution.DAILY)

self.init(option, option_style_is_supported=True)
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@ def initialize(self):
# BaroneAdesiWhaley model does not support European style options
option.price_model = OptionPriceModels.barone_adesi_whaley()

self.set_warmup(8, Resolution.DAILY)
self.set_warmup(7, Resolution.DAILY)

self.init(option, option_style_is_supported=False)
2 changes: 1 addition & 1 deletion Common/Data/UniverseSelection/OptionUniverse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ public override BaseData Reader(SubscriptionDataConfig config, StreamReader stre
CacheSymbol(key, symbol);
}

return new OptionUniverse(date.ConvertTo(config.DataTimeZone, config.ExchangeTimeZone), symbol, remainingLine);
return new OptionUniverse(date, symbol, remainingLine);
}

/// <summary>
Expand Down
13 changes: 12 additions & 1 deletion Common/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,18 @@ public static MarketHoursDatabase.Entry GetEntry(this MarketHoursDatabase market
return entry;
}

return marketHoursDatabase.GetEntry(symbol.ID.Market, symbol, symbol.ID.SecurityType);
var result = marketHoursDatabase.GetEntry(symbol.ID.Market, symbol, symbol.ID.SecurityType);

// For the OptionUniverse type, the exchange and data time zones are set to the same value (exchange tz).
// This is not actual options data, just option chains/universe selection, so we don't want any offsets
// between the exchange and data time zones.
// If the MHDB were data type dependent as well, this would be taken care in there.
if (result != null && dataTypes.Any(dataType => dataType == typeof(OptionUniverse)))
{
result = new MarketHoursDatabase.Entry(result.ExchangeHours.TimeZone, result.ExchangeHours);
}

return result;
}

/// <summary>
Expand Down
12 changes: 7 additions & 5 deletions Engine/DataFeeds/BacktestingChainProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,16 +105,18 @@ protected IEnumerable<Symbol> GetSymbols(Symbol canonicalSymbol, DateTime date)

private IEnumerable<Symbol> GetOptionSymbols(Symbol canonicalSymbol, DateTime date)
{
IHistoryProvider historyProvider = Composer.Instance.GetPart<IHistoryProvider>();
var historyProvider = Composer.Instance.GetPart<IHistoryProvider>();
var optionUniverseType = typeof(OptionUniverse);
var marketHoursDataBase = MarketHoursDatabase.FromDataFolder();
var marketHoursEntry = marketHoursDataBase.GetEntry(canonicalSymbol.ID.Market, canonicalSymbol, canonicalSymbol.SecurityType);
var marketHoursEntry = marketHoursDataBase.GetEntry(canonicalSymbol, new[] { optionUniverseType });

date = date.Date;
var previousTradingDate = Time.GetStartTimeForTradeBars(marketHoursEntry.ExchangeHours, date, Time.OneDay, 1,
extendedMarketHours: false, marketHoursEntry.DataTimeZone);
var request = new HistoryRequest(
previousTradingDate.ConvertToUtc(marketHoursEntry.ExchangeHours.TimeZone),
date.ConvertToUtc(marketHoursEntry.ExchangeHours.TimeZone),
typeof(OptionUniverse),
previousTradingDate,
date.AddDays(1),
optionUniverseType,
canonicalSymbol,
Resolution.Daily,
marketHoursEntry.ExchangeHours,
Expand Down

0 comments on commit b20be77

Please sign in to comment.