Files
quantconnect--lean/Common/Util/LeanData.cs
Gerardo Salazar eb1181f5f7 Adds Futures Options Asset Class w/ IB Support (#4928)
* 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
2020-12-02 21:49:59 -03:00

1011 lines
47 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.Globalization;
using System.IO;
using System.Linq;
using NodaTime;
using QuantConnect.Data;
using QuantConnect.Data.Auxiliary;
using QuantConnect.Data.Market;
using QuantConnect.Logging;
using QuantConnect.Securities;
using QuantConnect.Securities.Future;
using static QuantConnect.StringExtensions;
namespace QuantConnect.Util
{
/// <summary>
/// Provides methods for generating lean data file content
/// </summary>
public static class LeanData
{
/// <summary>
/// The different <see cref="SecurityType"/> used for data paths
/// </summary>
/// <remarks>This includes 'alternative'</remarks>
public static IReadOnlyList<string> SecurityTypeAsDataPath => Enum.GetNames(typeof(SecurityType))
.Select(x => x.ToLowerInvariant()).Union(new[] { "alternative" }).ToList();
/// <summary>
/// Converts the specified base data instance into a lean data file csv line.
/// This method takes into account the fake that base data instances typically
/// are time stamped in the exchange time zone, but need to be written to disk
/// in the data time zone.
/// </summary>
public static string GenerateLine(IBaseData data, Resolution resolution, DateTimeZone exchangeTimeZone, DateTimeZone dataTimeZone)
{
var clone = data.Clone();
clone.Time = data.Time.ConvertTo(exchangeTimeZone, dataTimeZone);
return GenerateLine(clone, clone.Symbol.ID.SecurityType, resolution);
}
/// <summary>
/// Converts the specified base data instance into a lean data file csv line
/// </summary>
public static string GenerateLine(IBaseData data, SecurityType securityType, Resolution resolution)
{
var milliseconds = data.Time.TimeOfDay.TotalMilliseconds.ToString(CultureInfo.InvariantCulture);
var longTime = data.Time.ToStringInvariant(DateFormat.TwelveCharacter);
switch (securityType)
{
case SecurityType.Equity:
switch (resolution)
{
case Resolution.Tick:
var tick = (Tick) data;
if (tick.TickType == TickType.Trade)
{
return ToCsv(milliseconds, Scale(tick.LastPrice), tick.Quantity, tick.Exchange, tick.SaleCondition, tick.Suspicious ? "1" : "0");
}
if (tick.TickType == TickType.Quote)
{
return ToCsv(milliseconds, Scale(tick.BidPrice), tick.BidSize, Scale(tick.AskPrice), tick.AskSize, tick.Exchange, tick.SaleCondition, tick.Suspicious ? "1" : "0");
}
break;
case Resolution.Minute:
case Resolution.Second:
var tradeBar = data as TradeBar;
if (tradeBar != null)
{
return ToCsv(milliseconds, Scale(tradeBar.Open), Scale(tradeBar.High), Scale(tradeBar.Low), Scale(tradeBar.Close), tradeBar.Volume);
}
var quoteBar = data as QuoteBar;
if (quoteBar != null)
{
return ToCsv(milliseconds,
ToScaledCsv(quoteBar.Bid), quoteBar.LastBidSize,
ToScaledCsv(quoteBar.Ask), quoteBar.LastAskSize);
}
break;
case Resolution.Hour:
case Resolution.Daily:
var bigTradeBar = data as TradeBar;
if (bigTradeBar != null)
{
return ToCsv(longTime, Scale(bigTradeBar.Open), Scale(bigTradeBar.High), Scale(bigTradeBar.Low), Scale(bigTradeBar.Close), bigTradeBar.Volume);
}
var bigQuoteBar = data as QuoteBar;
if (bigQuoteBar != null)
{
return ToCsv(longTime,
ToScaledCsv(bigQuoteBar.Bid), bigQuoteBar.LastBidSize,
ToScaledCsv(bigQuoteBar.Ask), bigQuoteBar.LastAskSize);
}
break;
}
break;
case SecurityType.Crypto:
switch (resolution)
{
case Resolution.Tick:
var tick = data as Tick;
if (tick == null)
{
throw new ArgumentException("Cryto tick could not be created", nameof(data));
}
if (tick.TickType == TickType.Trade)
{
return ToCsv(milliseconds, tick.LastPrice, tick.Quantity);
}
if (tick.TickType == TickType.Quote)
{
return ToCsv(milliseconds, tick.BidPrice, tick.BidSize, tick.AskPrice, tick.AskSize);
}
throw new ArgumentException("Cryto tick could not be created");
case Resolution.Second:
case Resolution.Minute:
var quoteBar = data as QuoteBar;
if (quoteBar != null)
{
return ToCsv(milliseconds,
ToNonScaledCsv(quoteBar.Bid), quoteBar.LastBidSize,
ToNonScaledCsv(quoteBar.Ask), quoteBar.LastAskSize);
}
var tradeBar = data as TradeBar;
if (tradeBar != null)
{
return ToCsv(milliseconds, tradeBar.Open, tradeBar.High, tradeBar.Low, tradeBar.Close, tradeBar.Volume);
}
throw new ArgumentException("Cryto minute/second bar could not be created", nameof(data));
case Resolution.Hour:
case Resolution.Daily:
var bigQuoteBar = data as QuoteBar;
if (bigQuoteBar != null)
{
return ToCsv(longTime,
ToNonScaledCsv(bigQuoteBar.Bid), bigQuoteBar.LastBidSize,
ToNonScaledCsv(bigQuoteBar.Ask), bigQuoteBar.LastAskSize);
}
var bigTradeBar = data as TradeBar;
if (bigTradeBar != null)
{
return ToCsv(longTime,
bigTradeBar.Open,
bigTradeBar.High,
bigTradeBar.Low,
bigTradeBar.Close,
bigTradeBar.Volume);
}
throw new ArgumentException("Cryto hour/daily bar could not be created", nameof(data));
}
break;
case SecurityType.Forex:
case SecurityType.Cfd:
switch (resolution)
{
case Resolution.Tick:
var tick = data as Tick;
if (tick == null)
{
throw new ArgumentException("Expected data of type 'Tick'", nameof(data));
}
return ToCsv(milliseconds, tick.BidPrice, tick.AskPrice);
case Resolution.Second:
case Resolution.Minute:
var bar = data as QuoteBar;
if (bar == null)
{
throw new ArgumentException("Expected data of type 'QuoteBar'", nameof(data));
}
return ToCsv(milliseconds,
ToNonScaledCsv(bar.Bid), bar.LastBidSize,
ToNonScaledCsv(bar.Ask), bar.LastAskSize);
case Resolution.Hour:
case Resolution.Daily:
var bigBar = data as QuoteBar;
if (bigBar == null)
{
throw new ArgumentException("Expected data of type 'QuoteBar'", nameof(data));
}
return ToCsv(longTime,
ToNonScaledCsv(bigBar.Bid), bigBar.LastBidSize,
ToNonScaledCsv(bigBar.Ask), bigBar.LastAskSize);
}
break;
case SecurityType.Option:
switch (resolution)
{
case Resolution.Tick:
var tick = (Tick)data;
if (tick.TickType == TickType.Trade)
{
return ToCsv(milliseconds,
Scale(tick.LastPrice), tick.Quantity, tick.Exchange, tick.SaleCondition, tick.Suspicious ? "1" : "0");
}
if (tick.TickType == TickType.Quote)
{
return ToCsv(milliseconds,
Scale(tick.BidPrice), tick.BidSize, Scale(tick.AskPrice), tick.AskSize, tick.Exchange, tick.Suspicious ? "1" : "0");
}
if (tick.TickType == TickType.OpenInterest)
{
return ToCsv(milliseconds, tick.Value);
}
break;
case Resolution.Second:
case Resolution.Minute:
// option and future data can be quote or trade bars
var quoteBar = data as QuoteBar;
if (quoteBar != null)
{
return ToCsv(milliseconds,
ToScaledCsv(quoteBar.Bid), quoteBar.LastBidSize,
ToScaledCsv(quoteBar.Ask), quoteBar.LastAskSize);
}
var tradeBar = data as TradeBar;
if (tradeBar != null)
{
return ToCsv(milliseconds,
Scale(tradeBar.Open), Scale(tradeBar.High), Scale(tradeBar.Low), Scale(tradeBar.Close), tradeBar.Volume);
}
var openInterest = data as OpenInterest;
if (openInterest != null)
{
return ToCsv(milliseconds, openInterest.Value);
}
break;
case Resolution.Hour:
case Resolution.Daily:
// option and future data can be quote or trade bars
var bigQuoteBar = data as QuoteBar;
if (bigQuoteBar != null)
{
return ToCsv(longTime,
ToScaledCsv(bigQuoteBar.Bid), bigQuoteBar.LastBidSize,
ToScaledCsv(bigQuoteBar.Ask), bigQuoteBar.LastAskSize);
}
var bigTradeBar = data as TradeBar;
if (bigTradeBar != null)
{
return ToCsv(longTime, ToScaledCsv(bigTradeBar), bigTradeBar.Volume);
}
var bigOpenInterest = data as OpenInterest;
if (bigOpenInterest != null)
{
return ToCsv(milliseconds, bigOpenInterest.Value);
}
break;
default:
throw new ArgumentOutOfRangeException(nameof(resolution), resolution, null);
}
break;
case SecurityType.FutureOption:
switch (resolution)
{
case Resolution.Tick:
var tick = (Tick)data;
if (tick.TickType == TickType.Trade)
{
return ToCsv(milliseconds,
tick.LastPrice, tick.Quantity, tick.Exchange, tick.SaleCondition, tick.Suspicious ? "1" : "0");
}
if (tick.TickType == TickType.Quote)
{
return ToCsv(milliseconds,
tick.BidPrice, tick.BidSize, tick.AskPrice, tick.AskSize, tick.Exchange, tick.Suspicious ? "1" : "0");
}
if (tick.TickType == TickType.OpenInterest)
{
return ToCsv(milliseconds, tick.Value);
}
break;
case Resolution.Second:
case Resolution.Minute:
// option and future data can be quote or trade bars
var quoteBar = data as QuoteBar;
if (quoteBar != null)
{
return ToCsv(milliseconds,
ToNonScaledCsv(quoteBar.Bid), quoteBar.LastBidSize,
ToNonScaledCsv(quoteBar.Ask), quoteBar.LastAskSize);
}
var tradeBar = data as TradeBar;
if (tradeBar != null)
{
return ToCsv(milliseconds,
tradeBar.Open, tradeBar.High, tradeBar.Low, tradeBar.Close, tradeBar.Volume);
}
var openInterest = data as OpenInterest;
if (openInterest != null)
{
return ToCsv(milliseconds, openInterest.Value);
}
break;
case Resolution.Hour:
case Resolution.Daily:
// option and future data can be quote or trade bars
var bigQuoteBar = data as QuoteBar;
if (bigQuoteBar != null)
{
return ToCsv(longTime,
ToNonScaledCsv(bigQuoteBar.Bid), bigQuoteBar.LastBidSize,
ToNonScaledCsv(bigQuoteBar.Ask), bigQuoteBar.LastAskSize);
}
var bigTradeBar = data as TradeBar;
if (bigTradeBar != null)
{
return ToCsv(longTime, ToNonScaledCsv(bigTradeBar), bigTradeBar.Volume);
}
var bigOpenInterest = data as OpenInterest;
if (bigOpenInterest != null)
{
return ToCsv(milliseconds, bigOpenInterest.Value);
}
break;
default:
throw new ArgumentOutOfRangeException(nameof(resolution), resolution, null);
}
break;
case SecurityType.Future:
switch (resolution)
{
case Resolution.Tick:
var tick = (Tick)data;
if (tick.TickType == TickType.Trade)
{
return ToCsv(milliseconds,
tick.LastPrice, tick.Quantity, tick.Exchange, tick.SaleCondition, tick.Suspicious ? "1": "0");
}
if (tick.TickType == TickType.Quote)
{
return ToCsv(milliseconds,
tick.BidPrice, tick.BidSize, tick.AskPrice, tick.AskSize, tick.Exchange, tick.Suspicious ? "1" : "0");
}
if (tick.TickType == TickType.OpenInterest)
{
return ToCsv(milliseconds, tick.Value);
}
break;
case Resolution.Second:
case Resolution.Minute:
// option and future data can be quote or trade bars
var quoteBar = data as QuoteBar;
if (quoteBar != null)
{
return ToCsv(milliseconds,
ToNonScaledCsv(quoteBar.Bid), quoteBar.LastBidSize,
ToNonScaledCsv(quoteBar.Ask), quoteBar.LastAskSize);
}
var tradeBar = data as TradeBar;
if (tradeBar != null)
{
return ToCsv(milliseconds,
tradeBar.Open, tradeBar.High, tradeBar.Low, tradeBar.Close, tradeBar.Volume);
}
var openInterest = data as OpenInterest;
if (openInterest != null)
{
return ToCsv(milliseconds, openInterest.Value);
}
break;
case Resolution.Hour:
case Resolution.Daily:
// option and future data can be quote or trade bars
var bigQuoteBar = data as QuoteBar;
if (bigQuoteBar != null)
{
return ToCsv(longTime,
ToNonScaledCsv(bigQuoteBar.Bid), bigQuoteBar.LastBidSize,
ToNonScaledCsv(bigQuoteBar.Ask), bigQuoteBar.LastAskSize);
}
var bigTradeBar = data as TradeBar;
if (bigTradeBar != null)
{
return ToCsv(longTime, ToNonScaledCsv(bigTradeBar), bigTradeBar.Volume);
}
var bigOpenInterest = data as OpenInterest;
if (bigOpenInterest != null)
{
return ToCsv(longTime, bigOpenInterest.Value);
}
break;
default:
throw new ArgumentOutOfRangeException(nameof(resolution), resolution, null);
}
break;
default:
throw new ArgumentOutOfRangeException(nameof(securityType), securityType, null);
}
throw new NotImplementedException(Invariant(
$"LeanData.GenerateLine has not yet been implemented for security type: {securityType} at resolution: {resolution}"
));
}
/// <summary>
/// Gets the data type required for the specified combination of resolution and tick type
/// </summary>
/// <param name="resolution">The resolution, if Tick, the Type returned is always Tick</param>
/// <param name="tickType">The <see cref="TickType"/> that primarily dictates the type returned</param>
/// <returns>The Type used to create a subscription</returns>
public static Type GetDataType(Resolution resolution, TickType tickType)
{
if (resolution == Resolution.Tick) return typeof(Tick);
if (tickType == TickType.OpenInterest) return typeof(OpenInterest);
if (tickType == TickType.Quote) return typeof(QuoteBar);
return typeof(TradeBar);
}
/// <summary>
/// Determines if the Type is a 'common' type used throughout lean
/// This method is helpful in creating <see cref="SubscriptionDataConfig"/>
/// </summary>
/// <param name="baseDataType">The Type to check</param>
/// <returns>A bool indicating whether the type is of type <see cref="TradeBar"/>
/// <see cref="QuoteBar"/> or <see cref="OpenInterest"/></returns>
public static bool IsCommonLeanDataType(Type baseDataType)
{
if (baseDataType == typeof(TradeBar) ||
baseDataType == typeof(QuoteBar) ||
baseDataType == typeof(OpenInterest))
{
return true;
}
return false;
}
/// <summary>
/// Generates the full zip file path rooted in the <paramref name="dataDirectory"/>
/// </summary>
public static string GenerateZipFilePath(string dataDirectory, Symbol symbol, DateTime date, Resolution resolution, TickType tickType)
{
return Path.Combine(dataDirectory, GenerateRelativeZipFilePath(symbol, date, resolution, tickType));
}
/// <summary>
/// Generates the full zip file path rooted in the <paramref name="dataDirectory"/>
/// </summary>
public static string GenerateZipFilePath(string dataDirectory, string symbol, SecurityType securityType, string market, DateTime date, Resolution resolution)
{
return Path.Combine(dataDirectory, GenerateRelativeZipFilePath(symbol, securityType, market, date, resolution));
}
/// <summary>
/// Generates the relative zip directory for the specified symbol/resolution
/// </summary>
public static string GenerateRelativeZipFileDirectory(Symbol symbol, Resolution resolution)
{
var isHourOrDaily = resolution == Resolution.Hour || resolution == Resolution.Daily;
var securityType = symbol.SecurityType.SecurityTypeToLower();
var market = symbol.ID.Market.ToLowerInvariant();
var res = resolution.ResolutionToLower();
var directory = Path.Combine(securityType, market, res);
switch (symbol.ID.SecurityType)
{
case SecurityType.Base:
case SecurityType.Equity:
case SecurityType.Forex:
case SecurityType.Cfd:
case SecurityType.Crypto:
return !isHourOrDaily ? Path.Combine(directory, symbol.Value.ToLowerInvariant()) : directory;
case SecurityType.Option:
// options uses the underlying symbol for pathing.
return !isHourOrDaily ? Path.Combine(directory, symbol.Underlying.Value.ToLowerInvariant()) : directory;
case SecurityType.FutureOption:
// For futures options, we use the canonical option ticker plus the underlying's expiry
// since it can differ from the underlying's ticker. We differ from normal futures
// because the option chain can be extraordinarily large compared to equity option chains.
var futureOptionPath = Path.Combine(symbol.ID.Symbol, symbol.Underlying.ID.Date.ToStringInvariant(DateFormat.EightCharacter))
.ToLowerInvariant();
return !isHourOrDaily ? Path.Combine(directory, futureOptionPath) : directory;
case SecurityType.Future:
return !isHourOrDaily ? Path.Combine(directory, symbol.ID.Symbol.ToLowerInvariant()) : directory;
case SecurityType.Commodity:
default:
throw new ArgumentOutOfRangeException();
}
}
/// <summary>
/// Generates relative factor file paths for equities
/// </summary>
public static string GenerateRelativeFactorFilePath(Symbol symbol)
{
return Path.Combine(Globals.DataFolder,
"equity",
symbol.ID.Market,
"factor_files",
symbol.Value.ToLowerInvariant() + ".csv");
}
/// <summary>
/// Generates the relative zip file path rooted in the /Data directory
/// </summary>
public static string GenerateRelativeZipFilePath(Symbol symbol, DateTime date, Resolution resolution, TickType tickType)
{
return Path.Combine(GenerateRelativeZipFileDirectory(symbol, resolution), GenerateZipFileName(symbol, date, resolution, tickType));
}
/// <summary>
/// Generates the relative zip file path rooted in the /Data directory
/// </summary>
public static string GenerateRelativeZipFilePath(string symbol, SecurityType securityType, string market, DateTime date, Resolution resolution)
{
var directory = Path.Combine(securityType.SecurityTypeToLower(), market.ToLowerInvariant(), resolution.ResolutionToLower());
if (resolution != Resolution.Daily && resolution != Resolution.Hour)
{
directory = Path.Combine(directory, symbol.ToLowerInvariant());
}
return Path.Combine(directory, GenerateZipFileName(symbol, securityType, date, resolution));
}
/// <summary>
/// Generate's the zip entry name to hold the specified data.
/// </summary>
public static string GenerateZipEntryName(Symbol symbol, DateTime date, Resolution resolution, TickType tickType)
{
var formattedDate = date.ToStringInvariant(DateFormat.EightCharacter);
var isHourOrDaily = resolution == Resolution.Hour || resolution == Resolution.Daily;
switch (symbol.ID.SecurityType)
{
case SecurityType.Base:
case SecurityType.Equity:
case SecurityType.Forex:
case SecurityType.Cfd:
case SecurityType.Crypto:
if (resolution == Resolution.Tick && symbol.SecurityType == SecurityType.Equity)
{
return Invariant($"{formattedDate}_{symbol.Value.ToLowerInvariant()}_{tickType}_{resolution}.csv");
}
if (isHourOrDaily)
{
return $"{symbol.Value.ToLowerInvariant()}.csv";
}
return Invariant($"{formattedDate}_{symbol.Value.ToLowerInvariant()}_{resolution.ResolutionToLower()}_{tickType.TickTypeToLower()}.csv");
case SecurityType.Option:
// We want the future option ticker as the lookup name inside the ZIP file
var optionPath = symbol.Underlying.Value.ToLowerInvariant();
if (isHourOrDaily)
{
return string.Join("_",
optionPath,
tickType.TickTypeToLower(),
symbol.ID.OptionStyle.ToLower(),
symbol.ID.OptionRight.ToLower(),
Scale(symbol.ID.StrikePrice),
symbol.ID.Date.ToStringInvariant(DateFormat.EightCharacter)
) + ".csv";
}
return string.Join("_",
formattedDate,
optionPath,
resolution.ResolutionToLower(),
tickType.TickTypeToLower(),
symbol.ID.OptionStyle.ToLower(),
symbol.ID.OptionRight.ToLower(),
Scale(symbol.ID.StrikePrice),
symbol.ID.Date.ToStringInvariant(DateFormat.EightCharacter)
) + ".csv";
case SecurityType.FutureOption:
// We want the future option ticker as the lookup name inside the ZIP file
var futureOptionPath = symbol.ID.Symbol.ToLowerInvariant();
if (isHourOrDaily)
{
return string.Join("_",
futureOptionPath,
tickType.TickTypeToLower(),
symbol.ID.OptionStyle.ToLower(),
symbol.ID.OptionRight.ToLower(),
Scale(symbol.ID.StrikePrice),
symbol.ID.Date.ToStringInvariant(DateFormat.EightCharacter)
) + ".csv";
}
return string.Join("_",
formattedDate,
futureOptionPath,
resolution.ResolutionToLower(),
tickType.TickTypeToLower(),
symbol.ID.OptionStyle.ToLower(),
symbol.ID.OptionRight.ToLower(),
Scale(symbol.ID.StrikePrice),
symbol.ID.Date.ToStringInvariant(DateFormat.EightCharacter)
) + ".csv";
case SecurityType.Future:
var expiryDate = symbol.ID.Date;
var monthsToAdd = FuturesExpiryUtilityFunctions.GetDeltaBetweenContractMonthAndContractExpiry(symbol.ID.Symbol, expiryDate.Date);
var contractYearMonth = expiryDate.AddMonths(monthsToAdd).ToStringInvariant(DateFormat.YearMonth);
if (isHourOrDaily)
{
return string.Join("_",
symbol.ID.Symbol.ToLowerInvariant(),
tickType.TickTypeToLower(),
contractYearMonth,
expiryDate.ToStringInvariant(DateFormat.EightCharacter)
) + ".csv";
}
return string.Join("_",
formattedDate,
symbol.ID.Symbol.ToLowerInvariant(),
resolution.ResolutionToLower(),
tickType.TickTypeToLower(),
contractYearMonth,
expiryDate.ToStringInvariant(DateFormat.EightCharacter)
) + ".csv";
case SecurityType.Commodity:
default:
throw new ArgumentOutOfRangeException();
}
}
/// <summary>
/// Generates the zip file name for the specified date of data.
/// </summary>
public static string GenerateZipFileName(Symbol symbol, DateTime date, Resolution resolution, TickType tickType)
{
var tickTypeString = tickType.TickTypeToLower();
var formattedDate = date.ToStringInvariant(DateFormat.EightCharacter);
var isHourOrDaily = resolution == Resolution.Hour || resolution == Resolution.Daily;
switch (symbol.ID.SecurityType)
{
case SecurityType.Base:
case SecurityType.Equity:
case SecurityType.Forex:
case SecurityType.Cfd:
if (isHourOrDaily)
{
return $"{symbol.Value.ToLowerInvariant()}.zip";
}
return $"{formattedDate}_{tickTypeString}.zip";
case SecurityType.Crypto:
if (isHourOrDaily)
{
return $"{symbol.Value.ToLowerInvariant()}_{tickTypeString}.zip";
}
return $"{formattedDate}_{tickTypeString}.zip";
case SecurityType.Option:
if (isHourOrDaily)
{
var optionPath = symbol.Underlying.Value.ToLowerInvariant();
return $"{optionPath}_{tickTypeString}_{symbol.ID.OptionStyle.ToLower()}.zip";
}
return $"{formattedDate}_{tickTypeString}_{symbol.ID.OptionStyle.ToLower()}.zip";
case SecurityType.FutureOption:
if (isHourOrDaily)
{
var futureOptionPath = symbol.ID.Symbol.ToLowerInvariant();
return $"{futureOptionPath}_{tickTypeString}_{symbol.ID.OptionStyle.ToLower()}.zip";
}
return $"{formattedDate}_{tickTypeString}_{symbol.ID.OptionStyle.ToLower()}.zip";
case SecurityType.Future:
if (isHourOrDaily)
{
return $"{symbol.ID.Symbol.ToLowerInvariant()}_{tickTypeString}.zip";
}
return $"{formattedDate}_{tickTypeString}.zip";
case SecurityType.Commodity:
default:
throw new ArgumentOutOfRangeException();
}
}
/// <summary>
/// Creates the zip file name for a QC zip data file
/// </summary>
public static string GenerateZipFileName(string symbol, SecurityType securityType, DateTime date, Resolution resolution, TickType? tickType = null)
{
if (resolution == Resolution.Hour || resolution == Resolution.Daily)
{
return $"{symbol.ToLowerInvariant()}.zip";
}
var zipFileName = date.ToStringInvariant(DateFormat.EightCharacter);
tickType = tickType ?? (securityType == SecurityType.Forex || securityType == SecurityType.Cfd ? TickType.Quote : TickType.Trade);
var suffix = Invariant($"_{tickType.Value.TickTypeToLower()}.zip");
return zipFileName + suffix;
}
/// <summary>
/// Gets the tick type most commonly associated with the specified security type
/// </summary>
/// <param name="securityType">The security type</param>
/// <returns>The most common tick type for the specified security type</returns>
public static TickType GetCommonTickType(SecurityType securityType)
{
if (securityType == SecurityType.Forex || securityType == SecurityType.Cfd || securityType == SecurityType.Crypto)
{
return TickType.Quote;
}
return TickType.Trade;
}
/// <summary>
/// Creates a symbol from the specified zip entry name
/// </summary>
/// <param name="symbol">The root symbol of the output symbol</param>
/// <param name="resolution">The resolution of the data source producing the zip entry name</param>
/// <param name="zipEntryName">The zip entry name to be parsed</param>
/// <returns>A new symbol representing the zip entry name</returns>
public static Symbol ReadSymbolFromZipEntry(Symbol symbol, Resolution resolution, string zipEntryName)
{
var isHourlyOrDaily = resolution == Resolution.Hour || resolution == Resolution.Daily;
var parts = zipEntryName.Replace(".csv", string.Empty).Split('_');
switch (symbol.ID.SecurityType)
{
case SecurityType.Option:
case SecurityType.FutureOption:
if (isHourlyOrDaily)
{
var style = (OptionStyle)Enum.Parse(typeof(OptionStyle), parts[2], true);
var right = (OptionRight)Enum.Parse(typeof(OptionRight), parts[3], true);
var strike = Parse.Decimal(parts[4]) / 10000m;
var expiry = Parse.DateTimeExact(parts[5], DateFormat.EightCharacter);
return Symbol.CreateOption(symbol.Underlying, symbol.ID.Market, style, right, strike, expiry);
}
else
{
var style = (OptionStyle)Enum.Parse(typeof(OptionStyle), parts[4], true);
var right = (OptionRight)Enum.Parse(typeof(OptionRight), parts[5], true);
var strike = Parse.Decimal(parts[6]) / 10000m;
var expiry = DateTime.ParseExact(parts[7], DateFormat.EightCharacter, CultureInfo.InvariantCulture);
return Symbol.CreateOption(symbol.Underlying, symbol.ID.Market, style, right, strike, expiry);
}
case SecurityType.Future:
if (isHourlyOrDaily)
{
var expiryYearMonth = Parse.DateTimeExact(parts[2], DateFormat.YearMonth);
var futureExpiryFunc = FuturesExpiryFunctions.FuturesExpiryFunction(symbol);
var futureExpiry = futureExpiryFunc(expiryYearMonth);
return Symbol.CreateFuture(parts[0], symbol.ID.Market, futureExpiry);
}
else
{
var expiryYearMonth = Parse.DateTimeExact(parts[4], DateFormat.YearMonth);
var futureExpiryFunc = FuturesExpiryFunctions.FuturesExpiryFunction(symbol);
var futureExpiry = futureExpiryFunc(expiryYearMonth);
return Symbol.CreateFuture(parts[1], symbol.ID.Market, futureExpiry);
}
default:
throw new NotImplementedException(Invariant(
$"ReadSymbolFromZipEntry is not implemented for {symbol.ID.SecurityType} {symbol.ID.Market} {resolution}"
));
}
}
/// <summary>
/// Scale and convert the resulting number to deci-cents int.
/// </summary>
private static long Scale(decimal value)
{
return (long)(value*10000);
}
/// <summary>
/// Create a csv line from the specified arguments
/// </summary>
private static string ToCsv(params object[] args)
{
// use culture neutral formatting for decimals
for (var i = 0; i < args.Length; i++)
{
var value = args[i];
if (value is decimal)
{
args[i] = ((decimal) value).Normalize();
}
}
return string.Join(",", args);
}
/// <summary>
/// Creates a scaled csv line for the bar, if null fills in empty strings
/// </summary>
private static string ToScaledCsv(IBar bar)
{
if (bar == null)
{
return ToCsv(string.Empty, string.Empty, string.Empty, string.Empty);
}
return ToCsv(Scale(bar.Open), Scale(bar.High), Scale(bar.Low), Scale(bar.Close));
}
/// <summary>
/// Creates a non scaled csv line for the bar, if null fills in empty strings
/// </summary>
private static string ToNonScaledCsv(IBar bar)
{
if (bar == null)
{
return ToCsv(string.Empty, string.Empty, string.Empty, string.Empty);
}
return ToCsv(bar.Open, bar.High, bar.Low, bar.Close);
}
/// <summary>
/// Get the <see cref="TickType"/> for common Lean data types.
/// If not a Lean common data type, return a TickType of Trade.
/// </summary>
/// <param name="type">A Type used to determine the TickType</param>
/// <param name="securityType">The SecurityType used to determine the TickType</param>
/// <returns>A TickType corresponding to the type</returns>
public static TickType GetCommonTickTypeForCommonDataTypes(Type type, SecurityType securityType)
{
if (type == typeof(TradeBar))
{
return TickType.Trade;
}
if (type == typeof(QuoteBar))
{
return TickType.Quote;
}
if (type == typeof(OpenInterest))
{
return TickType.OpenInterest;
}
if (type == typeof(ZipEntryName))
{
return TickType.Quote;
}
if (type == typeof(Tick))
{
if (securityType == SecurityType.Forex ||
securityType == SecurityType.Cfd ||
securityType == SecurityType.Crypto)
{
return TickType.Quote;
}
}
return TickType.Trade;
}
/// <summary>
/// Matches a data path security type with the <see cref="SecurityType"/>
/// </summary>
/// <remarks>This includes 'alternative'</remarks>
/// <param name="securityType">The data path security type</param>
/// <returns>The matching security type for the given data path</returns>
public static SecurityType ParseDataSecurityType(string securityType)
{
if (securityType.Equals("alternative", StringComparison.InvariantCultureIgnoreCase))
{
return SecurityType.Base;
}
return (SecurityType) Enum.Parse(typeof(SecurityType), securityType, true);
}
/// <summary>
/// Parses file name into a <see cref="Security"/> and DateTime
/// </summary>
/// <param name="fileName">File name to be parsed</param>
/// <param name="symbol">The symbol as parsed from the fileName</param>
/// <param name="date">Date of data in the file path. Only returned if the resolution is lower than Hourly</param>
/// <param name="resolution">The resolution of the symbol as parsed from the filePath</param>
public static bool TryParsePath(string fileName, out Symbol symbol, out DateTime date, out Resolution resolution)
{
symbol = null;
resolution = Resolution.Daily;
date = default(DateTime);
var pathSeparators = new[] { '/', '\\' };
try
{
// Removes file extension
fileName = fileName.Replace(fileName.GetExtension(), "");
// remove any relative file path
while (fileName.First() == '.' || pathSeparators.Any(x => x == fileName.First()))
{
fileName = fileName.Remove(0, 1);
}
// split path into components
var info = fileName.Split(pathSeparators, StringSplitOptions.RemoveEmptyEntries).ToList();
// find where the useful part of the path starts - i.e. the securityType
var startIndex = info.FindIndex(x => SecurityTypeAsDataPath.Contains(x.ToLowerInvariant()));
var securityType = ParseDataSecurityType(info[startIndex]);
var market = Market.USA;
string ticker;
if (securityType == SecurityType.Base)
{
if (!Enum.TryParse(info[startIndex + 2], true, out resolution))
{
resolution = Resolution.Daily;
}
// the last part of the path is the file name
var fileNameNoPath = info[info.Count - 1].Split('_').First();
if (!DateTime.TryParseExact(fileNameNoPath,
DateFormat.EightCharacter,
DateTimeFormatInfo.InvariantInfo,
DateTimeStyles.None,
out date))
{
// if parsing the date failed we assume filename is ticker
ticker = fileNameNoPath;
}
else
{
// ticker must be the previous part of the path
ticker = info[info.Count - 2];
}
}
else
{
resolution = (Resolution)Enum.Parse(typeof(Resolution), info[startIndex + 2], true);
// Gather components used to create the security
market = info[startIndex + 1];
ticker = info[startIndex + 3];
// If resolution is Daily or Hour, we do not need to set the date and tick type
if (resolution < Resolution.Hour)
{
date = Parse.DateTimeExact(info[startIndex + 4].Substring(0, 8), DateFormat.EightCharacter);
}
if (securityType == SecurityType.Crypto)
{
ticker = ticker.Split('_').First();
}
}
symbol = Symbol.Create(ticker, securityType, market);
}
catch (Exception ex)
{
Log.Error($"LeanData.TryParsePath(): Error encountered while parsing the path {fileName}. Error: {ex.GetBaseException()}");
return false;
}
return true;
}
}
}