Skip to content

Commit

Permalink
Index option chain backwards compatibility (#8395)
Browse files Browse the repository at this point in the history
* Fix option universe

Set right time using exchange time zone when reading.
Remove OptionUniverse market hours hack in GetEntry extension method.

* Minor change
  • Loading branch information
jhonabreul authored Nov 15, 2024
1 parent f7b012a commit fa1ef4f
Show file tree
Hide file tree
Showing 14 changed files with 46 additions and 38 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 => 356;
public override long DataPoints => 350;

/// <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 => 1269;
public override long DataPoints => 1263;

/// <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", "54.478%"},
{"Compounding Annual Return", "55.039%"},
{"Drawdown", "0.400%"},
{"Expectancy", "-0.5"},
{"Start Equity", "1000000"},
{"End Equity", "1006025"},
{"Net Profit", "0.602%"},
{"Sharpe Ratio", "2.62"},
{"Sharpe Ratio", "3.01"},
{"Sortino Ratio", "0"},
{"Probabilistic Sharpe Ratio", "63.221%"},
{"Probabilistic Sharpe Ratio", "62.865%"},
{"Loss Rate", "50%"},
{"Win Rate", "50%"},
{"Profit-Loss Ratio", "0"},
{"Alpha", "0.067"},
{"Beta", "-0.013"},
{"Alpha", "0.249"},
{"Beta", "-0.033"},
{"Annual Standard Deviation", "0.004"},
{"Annual Variance", "0"},
{"Information Ratio", "-50.808"},
{"Tracking Error", "0.086"},
{"Treynor Ratio", "-0.725"},
{"Information Ratio", "-99.414"},
{"Tracking Error", "0.072"},
{"Treynor Ratio", "-0.382"},
{"Total Fees", "$0.00"},
{"Estimated Strategy Capacity", "$580000.00"},
{"Lowest Capacity Asset", "SPXW 31K54PVWHUJHQ|SPX 31"},
{"Portfolio Turnover", "0.40%"},
{"Portfolio Turnover", "0.48%"},
{"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 => 106;
public long DataPoints => 104;

/// <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(7, Resolution.Daily);
SetWarmup(8, Resolution.Daily);

Init(option, optionStyleIsSupported: true);
}

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

/// <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(7, Resolution.Daily);
SetWarmup(8, Resolution.Daily);

Init(option, optionStyleIsSupported: false);
}

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

/// <summary>
/// Data Points count of the algorithm history
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(7, Resolution.DAILY)
self.set_warmup(8, 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(7, Resolution.DAILY)
self.set_warmup(8, 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, symbol, remainingLine);
return new OptionUniverse(date.ConvertTo(config.DataTimeZone, config.ExchangeTimeZone), symbol, remainingLine);
}

/// <summary>
Expand Down
11 changes: 1 addition & 10 deletions Common/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -191,16 +191,7 @@ public static MarketHoursDatabase.Entry GetEntry(this MarketHoursDatabase market
return entry;
}

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

// For option universes, the data time zone is the same as the exchange time zone so that selection
// happens at exchange time regardless of whether there exchange and data time zones are different.
if (result != null && dataTypes.Any(dataType => dataType == typeof(OptionUniverse)))
{
result = new MarketHoursDatabase.Entry(result.ExchangeHours.TimeZone, result.ExchangeHours);
}

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

/// <summary>
Expand Down
5 changes: 2 additions & 3 deletions Engine/DataFeeds/BacktestingChainProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,11 @@ private IEnumerable<Symbol> GetOptionSymbols(Symbol canonicalSymbol, DateTime da
var marketHoursDataBase = MarketHoursDatabase.FromDataFolder();
var marketHoursEntry = marketHoursDataBase.GetEntry(canonicalSymbol.ID.Market, canonicalSymbol, canonicalSymbol.SecurityType);

date = date.Date;
var previousTradingDate = Time.GetStartTimeForTradeBars(marketHoursEntry.ExchangeHours, date, Time.OneDay, 1,
extendedMarketHours: false, marketHoursEntry.DataTimeZone);
var request = new HistoryRequest(
previousTradingDate,
date.AddDays(1),
previousTradingDate.ConvertToUtc(marketHoursEntry.ExchangeHours.TimeZone),
date.ConvertToUtc(marketHoursEntry.ExchangeHours.TimeZone),
typeof(OptionUniverse),
canonicalSymbol,
Resolution.Daily,
Expand Down
7 changes: 6 additions & 1 deletion Engine/DataFeeds/SubscriptionDataReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
using QuantConnect.Data.Custom.Tiingo;
using QuantConnect.Lean.Engine.DataFeeds.Enumerators;
using QuantConnect.Securities;
using QuantConnect.Data.UniverseSelection;

namespace QuantConnect.Lean.Engine.DataFeeds
{
Expand Down Expand Up @@ -320,10 +321,14 @@ public bool MoveNext()
// Advance the time keeper either until the current instance time (to synchronize) or until the source changes.
// Note: use time instead of end time to avoid skipping instances that all have the same timestamps in the same file (e.g. universe data)
var currentSource = _source;
var nextExchangeDate = _config.Resolution == Resolution.Daily && _timeKeeper.IsExchangeBehindData()
var nextExchangeDate = _config.Resolution == Resolution.Daily
&& _timeKeeper.IsExchangeBehindData()
&& !_config.Type.IsAssignableTo(typeof(BaseDataCollection))
// If daily and exchange is behind data, data for date X will have a start time within date X-1,
// so we use the actual date from end time. e.g. a daily bar for Jan15 can have a start time of Jan14 8PM
// (exchange tz 4 hours behind data tz) and end time would be Jan15 8PM.
// This doesn't apply to universe files (BaseDataCollection check) because they are not read in the same way
// price daily files are read: they are read in a collection with end time of X+1. We don't want to skip them or advance time yet.
? instance.EndTime
: instance.Time;
while (_timeKeeper.ExchangeTime < nextExchangeDate && currentSource == _source)
Expand Down
15 changes: 15 additions & 0 deletions Tests/Algorithm/AlgorithmChainsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -142,5 +142,20 @@ def get_option_chain_data_from_dataframe(algorithm, canonical):

CollectionAssert.AreEquivalent(optionContractsSymbols, optionChain);
}

[Test]
public void IndexOptionChainApisAreConsistent()
{
var date = new DateTime(2015, 12, 24);
_algorithm.SetDateTime(date.ConvertToUtc(_algorithm.TimeZone));

var symbol = Symbols.SPX;
var exchange = MarketHoursDatabase.FromDataFolder().GetExchangeHours(symbol.ID.Market, symbol, symbol.SecurityType);

var chainFromAlgorithmApi = _algorithm.OptionChain(symbol).Select(x => x.Symbol).ToList();
var chainFromChainProviderApi = _optionChainProvider.GetOptionContractList(symbol, date.ConvertTo(_algorithm.TimeZone, exchange.TimeZone)).ToList();

CollectionAssert.AreEquivalent(chainFromAlgorithmApi, chainFromChainProviderApi);
}
}
}
8 changes: 3 additions & 5 deletions Tests/Algorithm/AlgorithmHistoryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -596,8 +596,6 @@ public void BarCountHistoryRequestIsCorrectlyBuilt(Resolution? resolution, Langu
Assert.AreEqual(typeof(OptionUniverse), request.DataType);
Assert.IsFalse(request.IncludeExtendedMarketHours);
Assert.IsFalse(request.IsCustomData);
// For OptionUniverse, exchange and data time zones are set to the same value
Assert.AreEqual(request.ExchangeHours.TimeZone, request.DataTimeZone);
}
}

Expand Down Expand Up @@ -3343,10 +3341,10 @@ public override BaseData Reader(SubscriptionDataConfig config, string line, Date
}

/// <summary>
/// Represents custom data with an optional sorting functionality. The <see cref="ExampleCustomDataWithSort"/> class
/// Represents custom data with an optional sorting functionality. The <see cref="ExampleCustomDataWithSort"/> class
/// allows you to specify a static property <seealso cref="CustomDataKey"/>, which defines the name of the custom data source.
/// Sorting can be enabled or disabled by setting the <seealso cref="Sort"/> property.
/// This class overrides <see cref="GetSource(SubscriptionDataConfig, DateTime, bool)"/> to initialize the
/// This class overrides <see cref="GetSource(SubscriptionDataConfig, DateTime, bool)"/> to initialize the
/// <seealso cref="SubscriptionDataSource.Sort"/> property based on the value of <see cref="Sort"/>.
/// </summary>
public class ExampleCustomDataWithSort : BaseData
Expand All @@ -3367,7 +3365,7 @@ public class ExampleCustomDataWithSort : BaseData
public decimal Close { get; set; }

/// <summary>
/// Returns the data source for the subscription. It uses the custom data key and sets sorting based on the
/// Returns the data source for the subscription. It uses the custom data key and sets sorting based on the
/// <see cref="Sort"/> property.
/// </summary>
/// <param name="config">Subscription configuration.</param>
Expand Down

0 comments on commit fa1ef4f

Please sign in to comment.