eb1181f5f7
* Adds preliminary universe selection for Future Options
* Fixes scaling issues with Future Options
* Fixes scaling multiplying by 10000x instead of using _scaleFactor
* Fixes scaling for Tick
* Revert changes to Tick since it divides the scaling factor
* Changes stale method name to new method name after rebase
* Fixes selection bugs, adds new methods, and adds unit tests
* Fixes bug where Equity Symbol was created for an underlying
non-equity Symbol, resulting in equity data trying to be loaded
* Adds unit tests covering changes to Tick, QuoteBar, TradeBar and
LeanData
* Adds regression test for AddUniverseOption filter contract selection
for Future Options
* Addresses review - modifies the AddFutureOption signature
* Adds new AddUniverseOptions method overload
* Removes and adds a new unit test
* Misc. modifications to account for new changes
* Fixes bug where futures were loaded using default SID Date
* Refactors and removes unnecessary work
* Fixes regression algorithm, which previously made no trades
* Adds future option data
* Adds the corresponding underlying data, in this case, futures data
to enable usage of future options data
* Replaces data with new data (ES18Z20)
* Improves Future chain filtering and updates regression stats
* Add AddFutureOptionContract API
* Expands regression and unit tests to test in finer detail
* Adds Python regression algorithms for AddFutureOption[Contract] methods
* Adds new unit test for BacktestingOptionChainProvider
* Fixes bug with BacktesingOptionChainProvider where we
attempted to load the Trades option chain first, resulting
in breakage of backwards compatibility and limitation of the
option chain.
* Adds new regression algorithms (Py) to Algorithm.Python project
* Adds FutureOptionMarginBuyingPowerModel
* Modifies code paths used to select margin model
* Adds related unit tests for margin model
* Fixes issue with unit test and MHDB/SPDB lookup for Future Options
* Preliminary regression algorithm testing ITM call/put option buying
* Fixes bug where fee model used did not find non-US market
options fee model. We now use the futures fee model for future
options because IB charges the same commissions per contract
between futures and futures options
* Adds proper regression algorithm for ITM future options expiration
* Pushing broken algorithm for review
* Currently, algorithm does not fill forward, causing
a single future option to not get exercised when it is delisted.
* Adds FutureOptionPutITMExpiryRegressionAlgorithm
* Improves existing regression algorithm for call side
* Fixes bug in existing regression algorithm
* Adds AAPL daily data to advance enumerator for ^^^ fix
* Adds additional future option regression algorithms
* Adds Buy OTM expiration regression algorithms
* Adds Sell ITM/OTM expiration regression algorithms
* Adds missing Python regression algorithms
* Adds remaining Python regression algorithms and fixes issues
* Fixes naming issues and statistics
* Adds short option OTM regression algorithms (Py)
* Add license header and class comments to python algorithms
* Cleans up comments and docstrings
* Create Buy/Sell call intraday regression algo
* Redirects future options symbol properties to futures symbol properties
* Asserts exercise/assignment price and updates stats in regression algos
* Adds new unit test covering changes to SecurityService
* Adds comments and fixes failing test
* Partially fixes future option mis-calculated profit/loss
* Adjusts portfolio model to calculate FOP as a no upfront pay asset class
* Updates regression algorithm statistics
* Begin IB FOP support
* Initial support for FOP IB data streaming, live í¾
* Adds additional functionality to LiveOptionChainProvider
- Allows querying CME API to retrieve option chains for CME products
- Ultimately, it's also the groundwork for the CME
LiveFutureChainProvider
* Edits IDataQueueUniverseProvider interface to provide greater
control to implementors of it
* Misc. bug fixes required to get FOP data streaming through IB
* Adds comments, adds missing rategate call, and cleans up code
* Force exchange for FOP and Futures when no exchange is provided
* Fixes bug with Portfolio modeling across all asset classes
* Adds LiveOptionChainProvider tests for Future Options
* IB brokerage option symbol bug fixes and improvements
* Fixes contract multiplier lookup bug
* Fixes issue where we attempted to subscribe to IB data feed with canonical security
* Adds ES MHDB entry
* Reverts portfolio modeling changes for Futures Options
* Since IB eats into our account's cash balance when
a new FOP contract is purchased, we must model by applying funds
to our cash whenever a new purchase/sell occurs.
If we choose to model FOPs exactly as we do with futures, we
will end up with an invalid TotalPortfolioValue on algorithm
restart. By all means and purposes, FOPs are modeled exactly
the same as equity options with respect to the portfolio.
* Adds comments clarifying portfolio modeling and clarifies
existing portfolio modeling comments with additional context.
* Fixes IB symbol lookup for future options
* Fixes LiveOptionChainProvider looping 5 times per option chain
request, even on success
* Sets OptionChainedUniverseSelectionModel to produce a canonical
future/future option/option Symbol to avoid creating two Symbols
* Adds GLOBEX future option symbol mapping from future -> fop
* Fixes LiveOptionChainProvider loading wrong contract option chains
* Fixes loading of futures options ZIP files when backtesting
* Adds a string -> decimal JSON converter
* Additional fixes/refactoring to the LiveOptionChainProvider
* Adds tests for changes to Symbol and LeanData
* Reverts changes to IB-symbol-map
* Fixes Value for mapped future options tickers
* Fixes Symbol test
* Changes path of future options to future's expiry date
* Extra changes made to remove scaling from writing CSV
* Added method to map from FOP Globex -> FUT Globex
* Fixes MOO and MOC orders for future options
* Note: this order type might not be supported by IB or CME.
* Bug fixes and updates unit tests
* Update regression tests and data format
* Rebase changes
* 1. Multiple bug fixes for LiveOptionChainProvider, reverts IQFeed changes
2. Address review (partial): Code reuse and cleanup
1.
* Modifies check in
`AddFutureOptionShort(Call|Put)ITMExpiryRegressionAlgorithm`
to ensure no buys have negative quantity
* Code reuse changes in IB brokerage
* Bug fix in IB brokerage where we assigned the FOP expiry
as the futures expiry (requires verification)
* Doc changes and adds missing summaries/license banners
* Disposes of HTTP client resources in LiveOptionChainProvider
* Renames classes and adds FutureOption folder in Common/Securities
2.
* We revert back to the quotes API for the option chain,
since the settlement API sometimes had missing strikes.
* Fixes future option expiry being set as future's expiry
in LiveOptionChainProvider
* Fixes bug where wrong option chain was selected because of bad
expiry lookup in the futures expiries returned from CME
* Fixes multiple looping bug in LiveOptionChainProvider
* Adds strike price scaling for LiveOptionChainProvider
* Reverts IQFeed changes and simplifies interface upgrade changes
Some additional challenges we'll have to solve as part of FOPs:
- The `OptionSymbol.IsStandard` method makes the assumption that
weeklies contracts follow the pattern equities follows, which
does not apply to Futures Options
- The Subscription created in:
`OptionChainUniverseSubscriptionEnumeratorFactory`
...adds a Trade config. For illiquid contracts, this
will delay universe selection for the option symbol
until we get a trade. However, if we add a quote config,
the data would instead be loaded based on the first quote
we received from the brokerage.
But since we're currently using a trade config, illiquid
contracts won't start streaming data until it receives a trade.
NOTE: this commit is a WIP to addressing the reviews received in the PR,
but has been committed early for efficiency in the review process
* Fixes regression algorithms and misc. bugs
* Fixes map file lookup for non-equity options
* Adds extra assertion at end of algorithm to ensure no holdings are
left when the algorithm ends.
* Adds FutureOptionSymbol, allowing all contracts through as standard
* Changes SPDB to allow defaulting to underlying future symbol
properties if no entry is found for the given FOP
* Fixes calls to SPDB in SecurityService, IBBrokerage
* Reverts AAPL daily ZIP file to fix majority of regression algorithms
* Adds FOPs symbol properties
* Fixes existing symbol properties for a few futures
* Adds tests for changes to Symbol Properties Database
* Removes string SPDB lookup method
* Updates tests and misc callees of previous method
* Updates all regression tests to use data of already expired contracts
* Adds Futures Options Expiry Functions tests
* Adds required futures data for 2020-01-05
* Address review (partial): Expands test coverage and fixes tests
* Set option chain tests parallelism to fixture only
* Fixes broken test for contract month delta for FuturesOptionsExpiryFunctions
* Changes delisting date logic for Futures Options
* Address review: removes duplicate code, misc code fixes
* Bug fix in MarketHoursDatabase.GetDatabaseSymbolKey() where
we would use the underlying's Symbol for lookup in the MHDB
* Adds missing license banner
* Removes Futures Options entries from MHDB
* Adds new tests
* Adds SecurityType.FutureOption
* Converts any underlying comparisons and uses SecurityType directly
instead for FOP specific behavior
* Extra code modifications to acommodate new SecurityType
* Addresses review: fixes order fee bug on exercise
* Additional bug fixes and adding of SecurityType.FutureOption
* Updates regression algorithms OrderListHash
* Fixes various bugs in IB live implementation
* Fixes bug setting the right contract expiration date for FOP
generated by LiveOptionChainProvider
* Adds new function to FuturesOptionsExpiryFunctions
* Clarifies parameter names better in some functions/methods
* Fixes bugs in IB brokerage for FOPs
* Address review - code cleanup and refactor
* Remove MappingEventProvider, SplitEventProvider, and
DividendEventProvider for Futures Options in
CorporateEventEnumeratorFactory
* Address review: Use MHDB key resolver in SPDB
* Makes regression tests pass and adds comment for expiry issue
* Fixes MHDB lookup on string symbol method
* Adds Futures Options greeks regression algorithm (C# only)
* Adds explanitory comment on MHDB FOP lookup
* Remove python from FutureOptionCallITMGreeksExpiryRegressionAlgorithm
738 lines
36 KiB
C#
738 lines
36 KiB
C#
/*
|
|
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
|
|
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using NodaTime;
|
|
using QuantConnect.Data;
|
|
using QuantConnect.Data.Market;
|
|
using QuantConnect.Interfaces;
|
|
using QuantConnect.Securities;
|
|
using QuantConnect.Util;
|
|
|
|
namespace QuantConnect.Algorithm
|
|
{
|
|
public partial class QCAlgorithm
|
|
{
|
|
/// <summary>
|
|
/// Gets or sets the history provider for the algorithm
|
|
/// </summary>
|
|
public IHistoryProvider HistoryProvider
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets whether or not this algorithm is still warming up
|
|
/// </summary>
|
|
public bool IsWarmingUp
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the warm up period to the specified value
|
|
/// </summary>
|
|
/// <param name="timeSpan">The amount of time to warm up, this does not take into account market hours/weekends</param>
|
|
public void SetWarmup(TimeSpan timeSpan)
|
|
{
|
|
SetWarmUp(timeSpan, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the warm up period to the specified value
|
|
/// </summary>
|
|
/// <param name="timeSpan">The amount of time to warm up, this does not take into account market hours/weekends</param>
|
|
public void SetWarmUp(TimeSpan timeSpan)
|
|
{
|
|
SetWarmup(timeSpan);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the warm up period to the specified value
|
|
/// </summary>
|
|
/// <param name="timeSpan">The amount of time to warm up, this does not take into account market hours/weekends</param>
|
|
/// <param name="resolution">The resolution to request</param>
|
|
public void SetWarmup(TimeSpan timeSpan, Resolution? resolution)
|
|
{
|
|
if (_locked)
|
|
{
|
|
throw new InvalidOperationException("QCAlgorithm.SetWarmup(): This method cannot be used after algorithm initialized");
|
|
}
|
|
|
|
_warmupBarCount = null;
|
|
_warmupTimeSpan = timeSpan;
|
|
_warmupResolution = resolution;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the warm up period to the specified value
|
|
/// </summary>
|
|
/// <param name="timeSpan">The amount of time to warm up, this does not take into account market hours/weekends</param>
|
|
/// <param name="resolution">The resolution to request</param>
|
|
public void SetWarmUp(TimeSpan timeSpan, Resolution? resolution)
|
|
{
|
|
SetWarmup(timeSpan, resolution);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the warm up period by resolving a start date that would send that amount of data into
|
|
/// the algorithm. The highest (smallest) resolution in the securities collection will be used.
|
|
/// For example, if an algorithm has minute and daily data and 200 bars are requested, that would
|
|
/// use 200 minute bars.
|
|
/// </summary>
|
|
/// <param name="barCount">The number of data points requested for warm up</param>
|
|
public void SetWarmup(int barCount)
|
|
{
|
|
SetWarmUp(barCount, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the warm up period by resolving a start date that would send that amount of data into
|
|
/// the algorithm. The highest (smallest) resolution in the securities collection will be used.
|
|
/// For example, if an algorithm has minute and daily data and 200 bars are requested, that would
|
|
/// use 200 minute bars.
|
|
/// </summary>
|
|
/// <param name="barCount">The number of data points requested for warm up</param>
|
|
public void SetWarmUp(int barCount)
|
|
{
|
|
SetWarmup(barCount);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the warm up period by resolving a start date that would send that amount of data into
|
|
/// the algorithm.
|
|
/// </summary>
|
|
/// <param name="barCount">The number of data points requested for warm up</param>
|
|
/// <param name="resolution">The resolution to request</param>
|
|
public void SetWarmup(int barCount, Resolution? resolution)
|
|
{
|
|
if (_locked)
|
|
{
|
|
throw new InvalidOperationException("QCAlgorithm.SetWarmup(): This method cannot be used after algorithm initialized");
|
|
}
|
|
|
|
_warmupTimeSpan = null;
|
|
_warmupBarCount = barCount;
|
|
_warmupResolution = resolution;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the warm up period by resolving a start date that would send that amount of data into
|
|
/// the algorithm.
|
|
/// </summary>
|
|
/// <param name="barCount">The number of data points requested for warm up</param>
|
|
/// <param name="resolution">The resolution to request</param>
|
|
public void SetWarmUp(int barCount, Resolution? resolution)
|
|
{
|
|
SetWarmup(barCount, resolution);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets <see cref="IAlgorithm.IsWarmingUp"/> to false to indicate this algorithm has finished its warm up
|
|
/// </summary>
|
|
public void SetFinishedWarmingUp()
|
|
{
|
|
IsWarmingUp = false;
|
|
|
|
// notify the algorithm
|
|
OnWarmupFinished();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Message for exception that is thrown when the implicit conversion between symbol and string fails
|
|
/// </summary>
|
|
private readonly string _symbolEmptyErrorMessage = "Cannot create history for the given ticker. " +
|
|
"Either explicitly use a symbol object to make the history request " +
|
|
"or ensure the symbol has been added using the AddSecurity() method before making the history request.";
|
|
|
|
/// <summary>
|
|
/// Gets the history requests required for provide warm up data for the algorithm
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public IEnumerable<HistoryRequest> GetWarmupHistoryRequests()
|
|
{
|
|
if (_warmupBarCount.HasValue)
|
|
{
|
|
return CreateBarCountHistoryRequests(Securities.Keys, _warmupBarCount.Value, _warmupResolution);
|
|
}
|
|
if (_warmupTimeSpan.HasValue)
|
|
{
|
|
var end = UtcTime.ConvertFromUtc(TimeZone);
|
|
return CreateDateRangeHistoryRequests(Securities.Keys, end - _warmupTimeSpan.Value, end, _warmupResolution);
|
|
}
|
|
|
|
// if not warmup requested return nothing
|
|
return Enumerable.Empty<HistoryRequest>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the history for all configured securities over the requested span.
|
|
/// This will use the resolution and other subscription settings for each security.
|
|
/// The symbols must exist in the Securities collection.
|
|
/// </summary>
|
|
/// <param name="span">The span over which to request data. This is a calendar span, so take into consideration weekends and such</param>
|
|
/// <param name="resolution">The resolution to request</param>
|
|
/// <returns>An enumerable of slice containing data over the most recent span for all configured securities</returns>
|
|
public IEnumerable<Slice> History(TimeSpan span, Resolution? resolution = null)
|
|
{
|
|
return History(Securities.Keys, Time - span, Time, resolution).Memoize();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the history for all configured securities over the requested span.
|
|
/// This will use the resolution and other subscription settings for each security.
|
|
/// The symbols must exist in the Securities collection.
|
|
/// </summary>
|
|
/// <param name="periods">The number of bars to request</param>
|
|
/// <param name="resolution">The resolution to request</param>
|
|
/// <returns>An enumerable of slice containing data over the most recent span for all configured securities</returns>
|
|
public IEnumerable<Slice> History(int periods, Resolution? resolution = null)
|
|
{
|
|
return History(Securities.Keys, periods, resolution).Memoize();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the historical data for all symbols of the requested type over the requested span.
|
|
/// The symbol's configured values for resolution and fill forward behavior will be used
|
|
/// The symbols must exist in the Securities collection.
|
|
/// </summary>
|
|
/// <param name="span">The span over which to retrieve recent historical data</param>
|
|
/// <param name="resolution">The resolution to request</param>
|
|
/// <returns>An enumerable of slice containing the requested historical data</returns>
|
|
public IEnumerable<DataDictionary<T>> History<T>(TimeSpan span, Resolution? resolution = null)
|
|
where T : IBaseData
|
|
{
|
|
return History<T>(Securities.Keys, span, resolution).Memoize();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the historical data for the specified symbols over the requested span.
|
|
/// The symbols must exist in the Securities collection.
|
|
/// </summary>
|
|
/// <typeparam name="T">The data type of the symbols</typeparam>
|
|
/// <param name="symbols">The symbols to retrieve historical data for</param>
|
|
/// <param name="span">The span over which to retrieve recent historical data</param>
|
|
/// <param name="resolution">The resolution to request</param>
|
|
/// <returns>An enumerable of slice containing the requested historical data</returns>
|
|
public IEnumerable<DataDictionary<T>> History<T>(IEnumerable<Symbol> symbols, TimeSpan span, Resolution? resolution = null)
|
|
where T : IBaseData
|
|
{
|
|
return History<T>(symbols, Time - span, Time, resolution).Memoize();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the historical data for the specified symbols. The exact number of bars will be returned for
|
|
/// each symbol. This may result in some data start earlier/later than others due to when various
|
|
/// exchanges are open. The symbols must exist in the Securities collection.
|
|
/// </summary>
|
|
/// <typeparam name="T">The data type of the symbols</typeparam>
|
|
/// <param name="symbols">The symbols to retrieve historical data for</param>
|
|
/// <param name="periods">The number of bars to request</param>
|
|
/// <param name="resolution">The resolution to request</param>
|
|
/// <returns>An enumerable of slice containing the requested historical data</returns>
|
|
public IEnumerable<DataDictionary<T>> History<T>(IEnumerable<Symbol> symbols, int periods, Resolution? resolution = null)
|
|
where T : IBaseData
|
|
{
|
|
var requests = symbols.Select(x =>
|
|
{
|
|
var config = GetMatchingSubscription(x, typeof(T));
|
|
if (config == null) return null;
|
|
|
|
var exchange = GetExchangeHours(x);
|
|
var res = GetResolution(x, resolution);
|
|
var start = _historyRequestFactory.GetStartTimeAlgoTz(x, periods, res, exchange, config.DataTimeZone);
|
|
return _historyRequestFactory.CreateHistoryRequest(config, start, Time, exchange, res);
|
|
});
|
|
|
|
return History(requests.Where(x => x != null)).Get<T>().Memoize();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the historical data for the specified symbols between the specified dates. The symbols must exist in the Securities collection.
|
|
/// </summary>
|
|
/// <typeparam name="T">The data type of the symbols</typeparam>
|
|
/// <param name="symbols">The symbols to retrieve historical data for</param>
|
|
/// <param name="start">The start time in the algorithm's time zone</param>
|
|
/// <param name="end">The end time in the algorithm's time zone</param>
|
|
/// <param name="resolution">The resolution to request</param>
|
|
/// <returns>An enumerable of slice containing the requested historical data</returns>
|
|
public IEnumerable<DataDictionary<T>> History<T>(IEnumerable<Symbol> symbols, DateTime start, DateTime end, Resolution? resolution = null)
|
|
where T : IBaseData
|
|
{
|
|
var requests = symbols.Select(x =>
|
|
{
|
|
var config = GetMatchingSubscription(x, typeof(T));
|
|
if (config == null) return null;
|
|
|
|
return _historyRequestFactory.CreateHistoryRequest(config, start, end, GetExchangeHours(x), resolution);
|
|
});
|
|
|
|
return History(requests.Where(x => x != null)).Get<T>().Memoize();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the historical data for the specified symbol over the request span. The symbol must exist in the Securities collection.
|
|
/// </summary>
|
|
/// <typeparam name="T">The data type of the symbol</typeparam>
|
|
/// <param name="symbol">The symbol to retrieve historical data for</param>
|
|
/// <param name="span">The span over which to retrieve recent historical data</param>
|
|
/// <param name="resolution">The resolution to request</param>
|
|
/// <returns>An enumerable of slice containing the requested historical data</returns>
|
|
public IEnumerable<T> History<T>(Symbol symbol, TimeSpan span, Resolution? resolution = null)
|
|
where T : IBaseData
|
|
{
|
|
return History<T>(symbol, Time - span, Time, resolution).Memoize();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the historical data for the specified symbol. The exact number of bars will be returned.
|
|
/// The symbol must exist in the Securities collection.
|
|
/// </summary>
|
|
/// <param name="symbol">The symbol to retrieve historical data for</param>
|
|
/// <param name="periods">The number of bars to request</param>
|
|
/// <param name="resolution">The resolution to request</param>
|
|
/// <returns>An enumerable of slice containing the requested historical data</returns>
|
|
public IEnumerable<TradeBar> History(Symbol symbol, int periods, Resolution? resolution = null)
|
|
{
|
|
if (symbol == null) throw new ArgumentException(_symbolEmptyErrorMessage);
|
|
|
|
resolution = GetResolution(symbol, resolution);
|
|
var marketHours = GetMarketHours(symbol);
|
|
var start = _historyRequestFactory.GetStartTimeAlgoTz(symbol, periods, resolution.Value, marketHours.ExchangeHours, marketHours.DataTimeZone);
|
|
|
|
return History(symbol, start, Time, resolution);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the historical data for the specified symbol. The exact number of bars will be returned.
|
|
/// The symbol must exist in the Securities collection.
|
|
/// </summary>
|
|
/// <typeparam name="T">The data type of the symbol</typeparam>
|
|
/// <param name="symbol">The symbol to retrieve historical data for</param>
|
|
/// <param name="periods">The number of bars to request</param>
|
|
/// <param name="resolution">The resolution to request</param>
|
|
/// <returns>An enumerable of slice containing the requested historical data</returns>
|
|
public IEnumerable<T> History<T>(Symbol symbol, int periods, Resolution? resolution = null)
|
|
where T : IBaseData
|
|
{
|
|
if (resolution == Resolution.Tick) throw new ArgumentException("History functions that accept a 'periods' parameter can not be used with Resolution.Tick");
|
|
if (symbol == null) throw new ArgumentException(_symbolEmptyErrorMessage);
|
|
|
|
// verify the types match
|
|
var requestedType = typeof(T);
|
|
var config = GetMatchingSubscription(symbol, requestedType);
|
|
if (config == null)
|
|
{
|
|
var actualType = Securities[symbol].Subscriptions.Select(x => x.Type.Name).DefaultIfEmpty("[None]").FirstOrDefault();
|
|
throw new ArgumentException("The specified security is not of the requested type. Symbol: " + symbol.ToString() + " Requested Type: " + requestedType.Name + " Actual Type: " + actualType);
|
|
}
|
|
|
|
resolution = GetResolution(symbol, resolution);
|
|
var start = _historyRequestFactory.GetStartTimeAlgoTz(symbol, periods, resolution.Value, GetExchangeHours(symbol), config.DataTimeZone);
|
|
return History<T>(symbol, start, Time, resolution).Memoize();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the historical data for the specified symbol between the specified dates. The symbol must exist in the Securities collection.
|
|
/// </summary>
|
|
/// <param name="symbol">The symbol to retrieve historical data for</param>
|
|
/// <param name="start">The start time in the algorithm's time zone</param>
|
|
/// <param name="end">The end time in the algorithm's time zone</param>
|
|
/// <param name="resolution">The resolution to request</param>
|
|
/// <returns>An enumerable of slice containing the requested historical data</returns>
|
|
public IEnumerable<T> History<T>(Symbol symbol, DateTime start, DateTime end, Resolution? resolution = null)
|
|
where T : IBaseData
|
|
{
|
|
if (symbol == null) throw new ArgumentException(_symbolEmptyErrorMessage);
|
|
// verify the types match
|
|
var requestedType = typeof(T);
|
|
var config = GetMatchingSubscription(symbol, requestedType);
|
|
if (config == null)
|
|
{
|
|
var actualType = Securities[symbol].Subscriptions.Select(x => x.Type.Name).DefaultIfEmpty("[None]").FirstOrDefault();
|
|
throw new ArgumentException("The specified security is not of the requested type. Symbol: " + symbol.ToString() + " Requested Type: " + requestedType.Name + " Actual Type: " + actualType);
|
|
}
|
|
|
|
var request = _historyRequestFactory.CreateHistoryRequest(config, start, end, GetExchangeHours(symbol), resolution);
|
|
return History(request).Get<T>(symbol).Memoize();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the historical data for the specified symbol over the request span. The symbol must exist in the Securities collection.
|
|
/// </summary>
|
|
/// <param name="symbol">The symbol to retrieve historical data for</param>
|
|
/// <param name="span">The span over which to retrieve recent historical data</param>
|
|
/// <param name="resolution">The resolution to request</param>
|
|
/// <returns>An enumerable of slice containing the requested historical data</returns>
|
|
public IEnumerable<TradeBar> History(Symbol symbol, TimeSpan span, Resolution? resolution = null)
|
|
{
|
|
return History(symbol, Time - span, Time, resolution);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the historical data for the specified symbol over the request span. The symbol must exist in the Securities collection.
|
|
/// </summary>
|
|
/// <param name="symbol">The symbol to retrieve historical data for</param>
|
|
/// <param name="start">The start time in the algorithm's time zone</param>
|
|
/// <param name="end">The end time in the algorithm's time zone</param>
|
|
/// <param name="resolution">The resolution to request</param>
|
|
/// <returns>An enumerable of slice containing the requested historical data</returns>
|
|
public IEnumerable<TradeBar> History(Symbol symbol, DateTime start, DateTime end, Resolution? resolution = null)
|
|
{
|
|
var securityType = symbol.ID.SecurityType;
|
|
if (securityType == SecurityType.Forex || securityType == SecurityType.Cfd)
|
|
{
|
|
Error("Calling History<TradeBar> method on a Forex or CFD security will return an empty result. Please use the generic version with QuoteBar type parameter.");
|
|
}
|
|
|
|
var resolutionToUse = resolution ?? GetResolution(symbol, resolution);
|
|
if (resolutionToUse == Resolution.Tick)
|
|
{
|
|
throw new InvalidOperationException("Calling History<TradeBar> method with Resolution.Tick will return an empty result." +
|
|
" Please use the generic version with Tick type parameter or provide a list of Symbols to use the Slice history request API.");
|
|
}
|
|
|
|
return History(new[] { symbol }, start, end, resolutionToUse).Get(symbol).Memoize();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the historical data for the specified symbols over the requested span.
|
|
/// The symbol's configured values for resolution and fill forward behavior will be used
|
|
/// The symbols must exist in the Securities collection.
|
|
/// </summary>
|
|
/// <param name="symbols">The symbols to retrieve historical data for</param>
|
|
/// <param name="span">The span over which to retrieve recent historical data</param>
|
|
/// <param name="resolution">The resolution to request</param>
|
|
/// <returns>An enumerable of slice containing the requested historical data</returns>
|
|
public IEnumerable<Slice> History(IEnumerable<Symbol> symbols, TimeSpan span, Resolution? resolution = null)
|
|
{
|
|
return History(symbols, Time - span, Time, resolution).Memoize();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the historical data for the specified symbols. The exact number of bars will be returned for
|
|
/// each symbol. This may result in some data start earlier/later than others due to when various
|
|
/// exchanges are open. The symbols must exist in the Securities collection.
|
|
/// </summary>
|
|
/// <param name="symbols">The symbols to retrieve historical data for</param>
|
|
/// <param name="periods">The number of bars to request</param>
|
|
/// <param name="resolution">The resolution to request</param>
|
|
/// <returns>An enumerable of slice containing the requested historical data</returns>
|
|
public IEnumerable<Slice> History(IEnumerable<Symbol> symbols, int periods, Resolution? resolution = null)
|
|
{
|
|
if (resolution == Resolution.Tick) throw new ArgumentException("History functions that accept a 'periods' parameter can not be used with Resolution.Tick");
|
|
return History(CreateBarCountHistoryRequests(symbols, periods, resolution)).Memoize();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the historical data for the specified symbols between the specified dates. The symbols must exist in the Securities collection.
|
|
/// </summary>
|
|
/// <param name="symbols">The symbols to retrieve historical data for</param>
|
|
/// <param name="start">The start time in the algorithm's time zone</param>
|
|
/// <param name="end">The end time in the algorithm's time zone</param>
|
|
/// <param name="resolution">The resolution to request</param>
|
|
/// <param name="fillForward">True to fill forward missing data, false otherwise</param>
|
|
/// <param name="extendedMarket">True to include extended market hours data, false otherwise</param>
|
|
/// <returns>An enumerable of slice containing the requested historical data</returns>
|
|
public IEnumerable<Slice> History(IEnumerable<Symbol> symbols, DateTime start, DateTime end, Resolution? resolution = null, bool? fillForward = null, bool? extendedMarket = null)
|
|
{
|
|
return History(CreateDateRangeHistoryRequests(symbols, start, end, resolution, fillForward, extendedMarket)).Memoize();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Executes the specified history request
|
|
/// </summary>
|
|
/// <param name="request">the history request to execute</param>
|
|
/// <returns>An enumerable of slice satisfying the specified history request</returns>
|
|
public IEnumerable<Slice> History(HistoryRequest request)
|
|
{
|
|
return History(new[] { request }).Memoize();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Executes the specified history requests
|
|
/// </summary>
|
|
/// <param name="requests">the history requests to execute</param>
|
|
/// <returns>An enumerable of slice satisfying the specified history request</returns>
|
|
public IEnumerable<Slice> History(IEnumerable<HistoryRequest> requests)
|
|
{
|
|
return History(requests, TimeZone).Memoize();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the last known price using the history provider.
|
|
/// Useful for seeding securities with the correct price
|
|
/// </summary>
|
|
/// <param name="security"><see cref="Security"/> object for which to retrieve historical data</param>
|
|
/// <returns>A single <see cref="BaseData"/> object with the last known price</returns>
|
|
public BaseData GetLastKnownPrice(Security security)
|
|
{
|
|
if (security.Symbol.IsCanonical() || HistoryProvider == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var configs = SubscriptionManager.SubscriptionDataConfigService
|
|
.GetSubscriptionDataConfigs(security.Symbol);
|
|
|
|
var dataTimeZone = MarketHoursDatabase
|
|
.GetDataTimeZone(security.Symbol.ID.Market, security.Symbol, security.Symbol.SecurityType);
|
|
|
|
// For speed and memory usage, use Resolution.Minute as the minimum resolution
|
|
var resolution = (Resolution)Math.Max((int)Resolution.Minute, (int)configs.GetHighestResolution());
|
|
var isExtendedMarketHours = configs.IsExtendedMarketHours();
|
|
|
|
// request QuoteBar for Options, Futures, and Futures Options
|
|
var dataType = typeof(BaseData);
|
|
if (security.Type == SecurityType.Option || security.Type == SecurityType.Future || security.Type == SecurityType.FutureOption)
|
|
{
|
|
dataType = LeanData.GetDataType(resolution, TickType.Quote);
|
|
}
|
|
|
|
// Get the config with the largest resolution
|
|
var subscriptionDataConfig = GetMatchingSubscription(security.Symbol, dataType);
|
|
|
|
TickType tickType;
|
|
|
|
if (subscriptionDataConfig == null)
|
|
{
|
|
dataType = typeof(TradeBar);
|
|
tickType = LeanData.GetCommonTickTypeForCommonDataTypes(dataType, security.Type);
|
|
}
|
|
else
|
|
{
|
|
// if subscription resolution is Tick, we also need to update the data type from Tick to TradeBar/QuoteBar
|
|
if (subscriptionDataConfig.Resolution == Resolution.Tick)
|
|
{
|
|
dataType = LeanData.GetDataType(resolution, subscriptionDataConfig.TickType);
|
|
subscriptionDataConfig = new SubscriptionDataConfig(subscriptionDataConfig, dataType, resolution: resolution);
|
|
}
|
|
|
|
dataType = subscriptionDataConfig.Type;
|
|
tickType = subscriptionDataConfig.TickType;
|
|
}
|
|
|
|
Func<int, BaseData> getLastKnownPriceForPeriods = backwardsPeriods =>
|
|
{
|
|
var startTimeUtc = _historyRequestFactory
|
|
.GetStartTimeAlgoTz(security.Symbol, backwardsPeriods, resolution, security.Exchange.Hours, dataTimeZone)
|
|
.ConvertToUtc(_localTimeKeeper.TimeZone);
|
|
|
|
var request = new HistoryRequest(
|
|
startTimeUtc,
|
|
UtcTime,
|
|
dataType,
|
|
security.Symbol,
|
|
resolution,
|
|
security.Exchange.Hours,
|
|
dataTimeZone,
|
|
resolution,
|
|
isExtendedMarketHours,
|
|
configs.IsCustomData(),
|
|
configs.DataNormalizationMode(),
|
|
tickType
|
|
);
|
|
|
|
BaseData result = null;
|
|
History(new List<HistoryRequest> { request })
|
|
.PushThrough(bar =>
|
|
{
|
|
if (!bar.IsFillForward)
|
|
result = bar;
|
|
});
|
|
|
|
return result;
|
|
};
|
|
|
|
var lastKnownPrice = getLastKnownPriceForPeriods(1);
|
|
if (lastKnownPrice != null)
|
|
{
|
|
return lastKnownPrice;
|
|
}
|
|
|
|
// If the first attempt to get the last know price returns null, it maybe the case of an illiquid security.
|
|
// We increase the look-back period for this case accordingly to the resolution to cover 3 trading days
|
|
var periods =
|
|
resolution == Resolution.Daily ? 3 :
|
|
resolution == Resolution.Hour ? 24 : 1440;
|
|
|
|
return getLastKnownPriceForPeriods(periods);
|
|
}
|
|
|
|
private IEnumerable<Slice> History(IEnumerable<HistoryRequest> requests, DateTimeZone timeZone)
|
|
{
|
|
var sentMessage = false;
|
|
// filter out any universe securities that may have made it this far
|
|
var reqs = requests.Where(hr => !UniverseManager.ContainsKey(hr.Symbol)).ToList();
|
|
foreach (var request in reqs)
|
|
{
|
|
// prevent future requests
|
|
if (request.EndTimeUtc > UtcTime)
|
|
{
|
|
request.EndTimeUtc = UtcTime;
|
|
if (request.StartTimeUtc > request.EndTimeUtc)
|
|
{
|
|
request.StartTimeUtc = request.EndTimeUtc;
|
|
}
|
|
if (!sentMessage)
|
|
{
|
|
sentMessage = true;
|
|
Debug("Request for future history modified to end now.");
|
|
}
|
|
}
|
|
}
|
|
|
|
// filter out future data to prevent look ahead bias
|
|
return ((IAlgorithm)this).HistoryProvider.GetHistory(reqs, timeZone);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper method to create history requests from a date range
|
|
/// </summary>
|
|
private IEnumerable<HistoryRequest> CreateDateRangeHistoryRequests(IEnumerable<Symbol> symbols, DateTime startAlgoTz, DateTime endAlgoTz, Resolution? resolution = null, bool? fillForward = null, bool? extendedMarket = null)
|
|
{
|
|
return symbols.Where(x => !x.IsCanonical()).SelectMany(x =>
|
|
{
|
|
var requests = new List<HistoryRequest>();
|
|
|
|
foreach (var config in GetMatchingSubscriptions(x, typeof(BaseData), resolution))
|
|
{
|
|
var request = _historyRequestFactory.CreateHistoryRequest(config, startAlgoTz, endAlgoTz, GetExchangeHours(x), resolution);
|
|
|
|
// apply overrides
|
|
var res = GetResolution(x, resolution);
|
|
if (fillForward.HasValue) request.FillForwardResolution = fillForward.Value ? res : (Resolution?)null;
|
|
if (extendedMarket.HasValue) request.IncludeExtendedMarketHours = extendedMarket.Value;
|
|
|
|
requests.Add(request);
|
|
}
|
|
|
|
return requests;
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper methods to create a history request for the specified symbols and bar count
|
|
/// </summary>
|
|
private IEnumerable<HistoryRequest> CreateBarCountHistoryRequests(IEnumerable<Symbol> symbols, int periods, Resolution? resolution = null)
|
|
{
|
|
return symbols.Where(x => !x.IsCanonical()).SelectMany(x =>
|
|
{
|
|
var res = GetResolution(x, resolution);
|
|
var exchange = GetExchangeHours(x);
|
|
var configs = GetMatchingSubscriptions(x, typeof(BaseData), resolution).ToList();
|
|
if (!configs.Any())
|
|
{
|
|
return Enumerable.Empty<HistoryRequest>();
|
|
}
|
|
|
|
var start = _historyRequestFactory.GetStartTimeAlgoTz(x, periods, res, exchange, configs.First().DataTimeZone);
|
|
var end = Time;
|
|
|
|
return configs.Select(config => _historyRequestFactory.CreateHistoryRequest(config, start, end, exchange, res));
|
|
});
|
|
}
|
|
|
|
private SubscriptionDataConfig GetMatchingSubscription(Symbol symbol, Type type)
|
|
{
|
|
// find the first subscription matching the requested type with a higher resolution than requested
|
|
return GetMatchingSubscriptions(symbol, type).FirstOrDefault();
|
|
}
|
|
|
|
private IEnumerable<SubscriptionDataConfig> GetMatchingSubscriptions(Symbol symbol, Type type, Resolution? resolution = null)
|
|
{
|
|
Security security;
|
|
if (Securities.TryGetValue(symbol, out security))
|
|
{
|
|
// find all subscriptions matching the requested type with a higher resolution than requested
|
|
var matchingSubscriptions = from sub in security.Subscriptions.OrderByDescending(s => s.Resolution)
|
|
where type.IsAssignableFrom(sub.Type)
|
|
select sub;
|
|
|
|
if (resolution.HasValue
|
|
&& (resolution == Resolution.Daily || resolution == Resolution.Hour)
|
|
&& symbol.SecurityType == SecurityType.Equity)
|
|
{
|
|
// for Daily and Hour resolution, for equities, we have to
|
|
// filter out any existing subscriptions that could be of Quote type
|
|
// This could happen if they were Resolution.Minute/Second/Tick
|
|
matchingSubscriptions = matchingSubscriptions.Where(s => s.TickType != TickType.Quote);
|
|
}
|
|
|
|
return matchingSubscriptions;
|
|
}
|
|
else
|
|
{
|
|
var entry = MarketHoursDatabase.GetEntry(symbol.ID.Market, symbol, symbol.ID.SecurityType);
|
|
resolution = GetResolution(symbol, resolution);
|
|
|
|
return SubscriptionManager
|
|
.LookupSubscriptionConfigDataTypes(symbol.SecurityType, resolution.Value, symbol.IsCanonical())
|
|
.Select(x => new SubscriptionDataConfig(
|
|
x.Item1,
|
|
symbol,
|
|
resolution.Value,
|
|
entry.DataTimeZone,
|
|
entry.ExchangeHours.TimeZone,
|
|
UniverseSettings.FillForward,
|
|
UniverseSettings.ExtendedMarketHours,
|
|
true,
|
|
false,
|
|
x.Item2,
|
|
true,
|
|
UniverseSettings.DataNormalizationMode));
|
|
}
|
|
}
|
|
|
|
private SecurityExchangeHours GetExchangeHours(Symbol symbol)
|
|
{
|
|
return GetMarketHours(symbol).ExchangeHours;
|
|
}
|
|
|
|
private MarketHoursDatabase.Entry GetMarketHours(Symbol symbol)
|
|
{
|
|
var hoursEntry = MarketHoursDatabase.GetEntry(symbol.ID.Market, symbol, symbol.ID.SecurityType);
|
|
|
|
// user can override the exchange hours in algorithm, i.e. HistoryAlgorithm
|
|
Security security;
|
|
if (Securities.TryGetValue(symbol, out security))
|
|
{
|
|
return new MarketHoursDatabase.Entry(hoursEntry.DataTimeZone, security.Exchange.Hours);
|
|
}
|
|
|
|
return hoursEntry;
|
|
}
|
|
|
|
private Resolution GetResolution(Symbol symbol, Resolution? resolution)
|
|
{
|
|
Security security;
|
|
if (Securities.TryGetValue(symbol, out security))
|
|
{
|
|
return resolution ?? SubscriptionManager.SubscriptionDataConfigService
|
|
.GetSubscriptionDataConfigs(symbol)
|
|
.GetHighestResolution();
|
|
}
|
|
else
|
|
{
|
|
return resolution ?? UniverseSettings.Resolution;
|
|
}
|
|
}
|
|
}
|
|
}
|