chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:02:50 +08:00
commit 0fc60fdcb1
5008 changed files with 910633 additions and 0 deletions
@@ -0,0 +1,192 @@
/*
* 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.IO;
using QuantConnect.Python;
using QuantConnect.Securities;
using QuantConnect.Util;
namespace QuantConnect.Data.UniverseSelection
{
/// <summary>
/// Represents a chain universe.
/// Intended as a base for options and futures universe data.
/// </summary>
public abstract class BaseChainUniverseData : BaseDataCollection, IChainUniverseData
{
/// <summary>
/// Csv line to get the values from
/// </summary>
/// <remarks>We keep the properties as they are in the csv file to reduce memory usage (strings vs decimals)</remarks>
protected string CsvLine { get; }
/// <summary>
/// The security identifier of the option symbol
/// </summary>
[PandasIgnore]
public SecurityIdentifier ID => Symbol.ID;
/// <summary>
/// Price of the security
/// </summary>
[PandasIgnore]
public override decimal Value => Close;
/// <summary>
/// Open price of the security
/// </summary>
public decimal Open
{
get
{
// Parse the values every time to avoid keeping them in memory
return CsvLine.GetDecimalFromCsv(0);
}
}
/// <summary>
/// High price of the security
/// </summary>
public decimal High
{
get
{
return CsvLine.GetDecimalFromCsv(1);
}
}
/// <summary>
/// Low price of the security
/// </summary>
public decimal Low
{
get
{
return CsvLine.GetDecimalFromCsv(2);
}
}
/// <summary>
/// Close price of the security
/// </summary>
public decimal Close
{
get
{
return CsvLine.GetDecimalFromCsv(3);
}
}
/// <summary>
/// Volume value of the security
/// </summary>
public decimal Volume
{
get
{
return CsvLine.GetDecimalFromCsv(4);
}
}
/// <summary>
/// Open interest value
/// </summary>
public virtual decimal OpenInterest
{
get
{
return CsvLine.GetDecimalFromCsv(5);
}
}
/// <summary>
/// Time that the data became available to use
/// </summary>
public override DateTime EndTime
{
get { return Time + QuantConnect.Time.OneDay; }
set { Time = value - QuantConnect.Time.OneDay; }
}
/// <summary>
/// Creates a new instance of the <see cref="BaseChainUniverseData"/> class
/// </summary>
protected BaseChainUniverseData()
{
}
/// <summary>
/// Creates a new instance of the <see cref="BaseChainUniverseData"/> class
/// </summary>
protected BaseChainUniverseData(DateTime date, Symbol symbol, string csv)
: base(date, date, symbol, null, null)
{
CsvLine = csv;
}
/// <summary>
/// Creates a new instance of the <see cref="BaseChainUniverseData"/> class as a copy of the given instance
/// </summary>
protected BaseChainUniverseData(BaseChainUniverseData other)
: base(other)
{
CsvLine = other.CsvLine;
}
/// <summary>
/// Return the URL string source of the file. This will be converted to a stream
/// </summary>
/// <param name="config">Configuration object</param>
/// <param name="date">Date of this source file</param>
/// <param name="isLiveMode">true if we're in live mode, false for backtesting mode</param>
/// <returns>String URL of source file.</returns>
public override SubscriptionDataSource GetSource(SubscriptionDataConfig config, DateTime date, bool isLiveMode)
{
var path = GetUniverseFullFilePath(config.Symbol, date);
return new SubscriptionDataSource(path, SubscriptionTransportMedium.LocalFile, FileFormat.FoldingCollection);
}
/// <summary>
/// Generates the file path for a universe data file based on the given symbol and date.
/// Optionally, creates the directory if it does not exist.
/// </summary>
/// <param name="symbol">The financial symbol for which the universe file is generated.</param>
/// <param name="date">The date associated with the universe file.</param>
/// <returns>The full file path to the universe data file.</returns>
public static string GetUniverseFullFilePath(Symbol symbol, DateTime date)
{
return Path.Combine(LeanData.GenerateUniversesDirectory(Globals.DataFolder, symbol), $"{date:yyyyMMdd}.csv");
}
/// <summary>
/// Gets the default resolution for this data and security type
/// </summary>
/// <remarks>This is a method and not a property so that python
/// custom data types can override it</remarks>
public override Resolution DefaultResolution()
{
return Resolution.Daily;
}
/// <summary>
/// Gets the symbol of the option
/// </summary>
public Symbol ToSymbol()
{
return Symbol;
}
}
}
@@ -0,0 +1,223 @@
/*
* 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.Linq;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using QuantConnect.Python;
namespace QuantConnect.Data.UniverseSelection
{
/// <summary>
/// This type exists for transport of data as a single packet
/// </summary>
public class BaseDataCollection : BaseData, IEnumerable<BaseData>
{
private static int _universeCount;
private DateTime _endTime;
/// <summary>
/// The associated underlying price data if any
/// </summary>
[PandasNonExpandable]
public BaseData Underlying { get; set; }
/// <summary>
/// Gets or sets the contracts selected by the universe
/// </summary>
[PandasIgnore]
public HashSet<Symbol> FilteredContracts { get; set; }
/// <summary>
/// Gets the data list
/// </summary>
public List<BaseData> Data { get; set; }
/// <summary>
/// Gets or sets the end time of this data
/// </summary>
[PandasIgnore]
public override DateTime EndTime
{
get
{
if (_endTime == default)
{
// to be user friendly let's return Time if not set, like BaseData does
return Time;
}
return _endTime;
}
set
{
_endTime = value;
}
}
/// <summary>
/// Initializes a new default instance of the <see cref="BaseDataCollection"/> c;ass
/// </summary>
public BaseDataCollection()
: this(DateTime.MinValue, Symbol.Empty)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="BaseDataCollection"/> class
/// </summary>
/// <param name="time">The time of this data</param>
/// <param name="symbol">A common identifier for all data in this packet</param>
/// <param name="data">The data to add to this collection</param>
public BaseDataCollection(DateTime time, Symbol symbol, IEnumerable<BaseData> data = null)
: this(time, time, symbol, data)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="BaseDataCollection"/> class
/// </summary>
/// <param name="time">The start time of this data</param>
/// <param name="endTime">The end time of this data</param>
/// <param name="symbol">A common identifier for all data in this packet</param>
/// <param name="data">The data to add to this collection</param>
/// <param name="underlying">The associated underlying price data if any</param>
/// <param name="filteredContracts">The contracts selected by the universe</param>
public BaseDataCollection(DateTime time, DateTime endTime, Symbol symbol, IEnumerable<BaseData> data = null, BaseData underlying = null, HashSet<Symbol> filteredContracts = null)
: this(time, endTime, symbol, data != null ? data.ToList() : new List<BaseData>(), underlying, filteredContracts)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="BaseDataCollection"/> class
/// </summary>
/// <param name="time">The start time of this data</param>
/// <param name="endTime">The end time of this data</param>
/// <param name="symbol">A common identifier for all data in this packet</param>
/// <param name="data">The data to add to this collection</param>
/// <param name="underlying">The associated underlying price data if any</param>
/// <param name="filteredContracts">The contracts selected by the universe</param>
public BaseDataCollection(DateTime time, DateTime endTime, Symbol symbol, List<BaseData> data, BaseData underlying, HashSet<Symbol> filteredContracts)
: this(time, endTime, symbol, underlying, filteredContracts)
{
if (data != null && data.Count == 1 && data[0] is BaseDataCollection collection && collection.Data != null && collection.Data.Count > 0)
{
// we were given a base data collection, let's be nice and fetch it's data if it has any
Data = collection.Data;
}
else
{
Data = data ?? new List<BaseData>();
}
}
/// <summary>
/// Helper method to create an instance without setting the data list
/// </summary>
protected BaseDataCollection(DateTime time, DateTime endTime, Symbol symbol, BaseData underlying, HashSet<Symbol> filteredContracts)
{
Symbol = symbol;
Time = time;
_endTime = endTime;
Underlying = underlying;
FilteredContracts = filteredContracts;
}
/// <summary>
/// Copy constructor for <see cref="BaseDataCollection"/>
/// </summary>
/// <param name="other">The base data collection being copied</param>
public BaseDataCollection(BaseDataCollection other)
: this(other.Time, other.EndTime, other.Symbol, other.Underlying, other.FilteredContracts)
{
Data = other.Data;
}
/// <summary>
/// Creates the universe symbol for the target market
/// </summary>
/// <returns>The universe symbol to use</returns>
public virtual Symbol UniverseSymbol(string market = null)
{
market ??= QuantConnect.Market.USA;
var ticker = $"{GetType().Name}-{market}-{Interlocked.Increment(ref _universeCount):D10}-{Guid.NewGuid()}";
return Symbol.Create(ticker, SecurityType.Base, market, baseDataType: GetType());
}
/// <summary>
/// Indicates whether this contains data that should be stored in the security cache
/// </summary>
/// <returns>Whether this contains data that should be stored in the security cache</returns>
public override bool ShouldCacheToSecurity()
{
if (Data == null || Data.Count == 0)
{
return true;
}
// if we hold the same data type we are, else we ask underlying type
return Data[0].GetType() == GetType() || Data[0].ShouldCacheToSecurity();
}
/// <summary>
/// Adds a new data point to this collection
/// </summary>
/// <param name="newDataPoint">The new data point to add</param>
public virtual void Add(BaseData newDataPoint)
{
Data.Add(newDataPoint);
}
/// <summary>
/// Adds a new data points to this collection
/// </summary>
/// <param name="newDataPoints">The new data points to add</param>
public virtual void AddRange(IEnumerable<BaseData> newDataPoints)
{
Data.AddRange(newDataPoints);
}
/// <summary>
/// Return a new instance clone of this object, used in fill forward
/// </summary>
/// <remarks>
/// This base implementation uses reflection to copy all public fields and properties
/// </remarks>
/// <returns>A clone of the current object</returns>
public override BaseData Clone()
{
return new BaseDataCollection(this);
}
/// <summary>
/// Returns an IEnumerator for this enumerable Object. The enumerator provides
/// a simple way to access all the contents of a collection.
/// </summary>
public IEnumerator<BaseData> GetEnumerator()
{
return (Data ?? Enumerable.Empty<BaseData>()).GetEnumerator();
}
/// <summary>
/// Returns an IEnumerator for this enumerable Object. The enumerator provides
/// a simple way to access all the contents of a collection.
/// </summary>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
@@ -0,0 +1,113 @@
/*
* 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.Globalization;
using QuantConnect.Interfaces;
using QuantConnect.Data.Fundamental;
using System.Runtime.CompilerServices;
namespace QuantConnect.Data.UniverseSelection
{
/// <summary>
/// Base fundamental data provider
/// </summary>
public class BaseFundamentalDataProvider : IFundamentalDataProvider
{
/// <summary>
/// True if live trading
/// </summary>
public bool LiveMode { get; set; }
/// <summary>
/// THe data provider instance to use
/// </summary>
protected IDataProvider DataProvider { get; set; }
/// <summary>
/// Initializes the service
/// </summary>
/// <param name="dataProvider">The data provider instance to use</param>
/// <param name="liveMode">True if running in live mode</param>
public virtual void Initialize(IDataProvider dataProvider, bool liveMode)
{
LiveMode = liveMode;
DataProvider = dataProvider;
}
/// <summary>
/// Will fetch the requested fundamental information for the requested time and symbol
/// </summary>
/// <typeparam name="T">The expected data type</typeparam>
/// <param name="time">The time to request this data for</param>
/// <param name="securityIdentifier">The security identifier</param>
/// <param name="name">The name of the fundamental property</param>
/// <returns>The fundamental information</returns>
public virtual T Get<T>(DateTime time, SecurityIdentifier securityIdentifier, FundamentalProperty name)
{
throw new NotImplementedException();
}
/// <summary>
/// Get's the default value for the given T type
/// </summary>
/// <typeparam name="T">The expected T type</typeparam>
/// <returns>The default value</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T GetDefault<T>()
{
if (typeof(T) == typeof(double))
{
return (T)Convert.ChangeType(double.NaN, typeof(T), CultureInfo.InvariantCulture);
}
else if (typeof(T) == typeof(decimal))
{
return (T)Convert.ChangeType(decimal.Zero, typeof(T), CultureInfo.InvariantCulture);
}
return default;
}
/// <summary>
/// True if the given value is none
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsNone(object value) => IsNone(value?.GetType(), value);
/// <summary>
/// True if the given value is none
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsNone(Type type, object value)
{
if (type == null || value == null)
{
return true;
}
else if(type == typeof(double))
{
return ((double)value).IsNaNOrInfinity();
}
else if (type == typeof(decimal))
{
return default(decimal) == (decimal)value;
}
else if (type == typeof(DateTime))
{
return default(DateTime) == (DateTime)value;
}
return false;
}
}
}
@@ -0,0 +1,135 @@
/*
* 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.Linq;
using System.Globalization;
namespace QuantConnect.Data.UniverseSelection
{
/// <summary>
/// Defines summary information about a single symbol for a given date
/// </summary>
public class CoarseFundamental : BaseData
{
/// <summary>
/// Gets the market for this symbol
/// </summary>
public string Market => Symbol.ID.Market;
/// <summary>
/// Gets the day's dollar volume for this symbol
/// </summary>
public virtual double DollarVolume { get; }
/// <summary>
/// Gets the day's total volume
/// </summary>
public virtual long Volume { get; }
/// <summary>
/// Returns whether the symbol has fundamental data for the given date
/// </summary>
public virtual bool HasFundamentalData { get; }
/// <summary>
/// Gets the price factor for the given date
/// </summary>
public virtual decimal PriceFactor { get; } = 1;
/// <summary>
/// Gets the split factor for the given date
/// </summary>
public virtual decimal SplitFactor { get; } = 1;
/// <summary>
/// Gets the combined factor used to create adjusted prices from raw prices
/// </summary>
public decimal PriceScaleFactor => PriceFactor * SplitFactor;
/// <summary>
/// Gets the split and dividend adjusted price
/// </summary>
public decimal AdjustedPrice => Price * PriceScaleFactor;
/// <summary>
/// The end time of this data.
/// </summary>
public override DateTime EndTime
{
get { return Time + QuantConnect.Time.OneDay; }
set { Time = value - QuantConnect.Time.OneDay; }
}
/// <summary>
/// Gets the raw price
/// </summary>
public override decimal Price => Value;
/// <summary>
/// Initializes a new instance of the <see cref="CoarseFundamental"/> class
/// </summary>
public CoarseFundamental()
{
}
/// <summary>
/// Return the URL string source of the file. This will be converted to a stream
/// </summary>
/// <param name="config">Configuration object</param>
/// <param name="date">Date of this source file</param>
/// <param name="isLiveMode">true if we're in live mode, false for backtesting mode</param>
/// <returns>String URL of source file.</returns>
public override SubscriptionDataSource GetSource(SubscriptionDataConfig config, DateTime date, bool isLiveMode)
{
throw new InvalidOperationException($"Coarse type is obsolete, please use {nameof(Fundamental)}");
}
/// <summary>
/// Reader converts each line of the data source into BaseData objects. Each data type creates its own factory method, and returns a new instance of the object
/// each time it is called.
/// </summary>
/// <param name="config">Subscription data config setup object</param>
/// <param name="line">Line of the source document</param>
/// <param name="date">Date of the requested data</param>
/// <param name="isLiveMode">true if we're in live mode, false for backtesting mode</param>
/// <returns>Instance of the T:BaseData object generated by this line of the CSV</returns>
public override BaseData Reader(SubscriptionDataConfig config, string line, DateTime date, bool isLiveMode)
{
throw new InvalidOperationException($"Coarse type is obsolete, please use {nameof(Fundamental)}");
}
/// <summary>
/// Converts a given fundamental data point into row format
/// </summary>
public static string ToRow(CoarseFundamental coarse)
{
// sid,symbol,close,volume,dollar volume,has fundamental data,price factor,split factor
var values = new object[]
{
coarse.Symbol.ID,
coarse.Symbol.Value,
coarse.Value,
coarse.Volume,
coarse.DollarVolume,
coarse.HasFundamentalData,
coarse.PriceFactor,
coarse.SplitFactor
};
return string.Join(",", values.Select(s => Convert.ToString(s, CultureInfo.InvariantCulture)));
}
}
}
@@ -0,0 +1,198 @@
/*
* 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.IO;
using System.Collections.Generic;
using QuantConnect.Data.Fundamental;
namespace QuantConnect.Data.UniverseSelection
{
/// <summary>
/// Coarse base fundamental data provider
/// </summary>
public class CoarseFundamentalDataProvider : BaseFundamentalDataProvider
{
private DateTime _date;
private readonly Dictionary<SecurityIdentifier, CoarseFundamental> _coarseFundamental = new();
/// <summary>
/// Will fetch the requested fundamental information for the requested time and symbol
/// </summary>
/// <typeparam name="T">The expected data type</typeparam>
/// <param name="time">The time to request this data for</param>
/// <param name="securityIdentifier">The security identifier</param>
/// <param name="name">The name of the fundamental property</param>
/// <returns>The fundamental information</returns>
public override T Get<T>(DateTime time, SecurityIdentifier securityIdentifier, FundamentalProperty name)
{
var enumName = Enum.GetName(name);
lock (_coarseFundamental)
{
if (time == _date)
{
return GetProperty<T>(securityIdentifier, enumName);
}
_date = time;
var path = Path.Combine(Globals.DataFolder, "equity", "usa", "fundamental", "coarse", $"{time:yyyyMMdd}.csv");
var fileStream = DataProvider.Fetch(path);
if (fileStream == null)
{
return GetDefault<T>();
}
_coarseFundamental.Clear();
using (var reader = new StreamReader(fileStream))
{
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
var coarse = Read(line, time);
if (coarse != null)
{
_coarseFundamental[coarse.Symbol.ID] = coarse;
}
}
}
return GetProperty<T>(securityIdentifier, enumName);
}
}
/// <summary>
/// Reads the given line and returns a CoarseFundamentalSource with the information within it
/// </summary>
public static CoarseFundamentalSource Read(string line, DateTime date)
{
try
{
var csv = line.Split(',');
var coarse = new CoarseFundamentalSource
{
Symbol = new Symbol(SecurityIdentifier.Parse(csv[0]), csv[1]),
Time = date,
Value = csv[2].ToDecimal(),
VolumeSetter = csv[3].ToInt64(),
DollarVolumeSetter = (double)csv[4].ToDecimal()
};
if (csv.Length > 5)
{
coarse.HasFundamentalDataSetter = csv[5].ConvertInvariant<bool>();
}
if (csv.Length > 7)
{
coarse.PriceFactorSetter = csv[6].ToDecimal();
coarse.SplitFactorSetter = csv[7].ToDecimal();
}
return coarse;
}
catch (Exception)
{
return null;
}
}
private dynamic GetProperty<T>(SecurityIdentifier securityIdentifier, string property)
{
if (!_coarseFundamental.TryGetValue(securityIdentifier, out var coarse))
{
return GetDefault<T>();
}
switch (property)
{
case nameof(CoarseFundamental.Price):
return coarse.Price;
case nameof(CoarseFundamental.Value):
return coarse.Value;
case nameof(CoarseFundamental.Market):
return coarse.Market;
case nameof(CoarseFundamental.Volume):
return coarse.Volume;
case nameof(CoarseFundamental.PriceFactor):
return coarse.PriceFactor;
case nameof(CoarseFundamental.SplitFactor):
return coarse.SplitFactor;
case nameof(CoarseFundamental.DollarVolume):
return coarse.DollarVolume;
case nameof(CoarseFundamental.HasFundamentalData):
return false;
}
return GetDefault<T>();
}
/// <summary>
/// Coarse fundamental with setters
/// </summary>
public class CoarseFundamentalSource : CoarseFundamental
{
/// <summary>
/// Property to set the volume of the Coarse Fundamental
/// </summary>
public long VolumeSetter { get; init; }
/// <summary>
/// Property to set the dollar volume of the Coarse Fundamental
/// </summary>
public double DollarVolumeSetter { get; init; }
/// <summary>
/// Property to set the price factor of the Coarse Fundamental
/// </summary>
public decimal PriceFactorSetter { get; set; } = 1;
/// <summary>
/// Property to set the split factor of the Coarse Fundamental
/// </summary>
public decimal SplitFactorSetter { get; set; } = 1;
/// <summary>
/// Property to indicate if the Coarse Fundamental has fundamental data
/// </summary>
public bool HasFundamentalDataSetter { get; set; }
/// <summary>
/// Gets the day's dollar volume for this symbol
/// </summary>
public override double DollarVolume => DollarVolumeSetter;
/// <summary>
/// Gets the day's total volume
/// </summary>
public override long Volume => VolumeSetter;
/// <summary>
/// Returns whether the symbol has fundamental data for the given date
/// </summary>
public override bool HasFundamentalData => HasFundamentalDataSetter;
/// <summary>
/// Gets the price factor for the given date
/// </summary>
public override decimal PriceFactor => PriceFactorSetter;
/// <summary>
/// Gets the split factor for the given date
/// </summary>
public override decimal SplitFactor => SplitFactorSetter;
}
}
}
@@ -0,0 +1,104 @@
/*
* 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.Linq;
using Python.Runtime;
using System.Collections.Generic;
using QuantConnect.Util;
namespace QuantConnect.Data.UniverseSelection
{
/// <summary>
/// Defines a universe that reads coarse us equity data
/// </summary>
public class CoarseFundamentalUniverse : Universe
{
private readonly Func<IEnumerable<CoarseFundamental>, IEnumerable<Symbol>> _selector;
/// <summary>
/// Initializes a new instance of the <see cref="CoarseFundamentalUniverse"/> class
/// </summary>
/// <param name="universeSettings">The settings used for new subscriptions generated by this universe</param>
/// <param name="selector">Returns the symbols that should be included in the universe</param>
public CoarseFundamentalUniverse(UniverseSettings universeSettings, Func<IEnumerable<CoarseFundamental>, IEnumerable<Symbol>> selector)
: base(CreateConfiguration(FundamentalUniverseFactory.SymbolFactory.UniverseSymbol()))
{
UniverseSettings = universeSettings;
_selector = selector;
}
/// <summary>
/// Initializes a new instance of the <see cref="CoarseFundamentalUniverse"/> class
/// </summary>
/// <param name="universeSettings">The settings used for new subscriptions generated by this universe</param>
/// <param name="selector">Returns the symbols that should be included in the universe</param>
public CoarseFundamentalUniverse(UniverseSettings universeSettings, PyObject selector)
: this(FundamentalUniverseFactory.SymbolFactory.UniverseSymbol(), universeSettings, selector)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CoarseFundamentalUniverse"/> class
/// </summary>
/// <param name="symbol">Defines the symbol to use for this universe</param>
/// <param name="universeSettings">The settings used for new subscriptions generated by this universe</param>
/// <param name="selector">Returns the symbols that should be included in the universe</param>
public CoarseFundamentalUniverse(Symbol symbol, UniverseSettings universeSettings, Func<IEnumerable<CoarseFundamental>, IEnumerable<Symbol>> selector)
: base(CreateConfiguration(symbol))
{
UniverseSettings = universeSettings;
_selector = selector;
}
/// <summary>
/// Initializes a new instance of the <see cref="CoarseFundamentalUniverse"/> class
/// </summary>
/// <param name="symbol">Defines the symbol to use for this universe</param>
/// <param name="universeSettings">The settings used for new subscriptions generated by this universe</param>
/// <param name="selector">Returns the symbols that should be included in the universe</param>
public CoarseFundamentalUniverse(Symbol symbol, UniverseSettings universeSettings, PyObject selector)
: base(CreateConfiguration(symbol))
{
UniverseSettings = universeSettings;
Func<IEnumerable<CoarseFundamental>, object> func;
if (selector.TrySafeAs(out func))
{
_selector = func.ConvertToUniverseSelectionSymbolDelegate();
}
}
/// <summary>
/// Performs universe selection using the data specified
/// </summary>
/// <param name="utcTime">The current utc time</param>
/// <param name="data">The symbols to remain in the universe</param>
/// <returns>The data that passes the filter</returns>
public override IEnumerable<Symbol> SelectSymbols(DateTime utcTime, BaseDataCollection data)
{
return _selector(new CastingEnumerable<BaseData, CoarseFundamental>(data.Data));
}
/// <summary>
/// Creates a <see cref="CoarseFundamental"/> subscription configuration for the US-equity market
/// </summary>
/// <param name="symbol">The symbol used in the returned configuration</param>
/// <returns>A coarse fundamental subscription configuration with the specified symbol</returns>
public static SubscriptionDataConfig CreateConfiguration(Symbol symbol)
{
return FundamentalUniverseFactory.CreateConfiguration(symbol);
}
}
}
@@ -0,0 +1,157 @@
/*
* 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 Python.Runtime;
namespace QuantConnect.Data.UniverseSelection
{
/// <summary>
/// ConstituentsUniverse allows to perform universe selection based on an
/// already preselected set of <see cref="Symbol"/>.
/// </summary>
/// <remarks>Using this class allows a performance improvement, since there is no
/// runtime logic computation required for selecting the <see cref="Symbol"/></remarks>
public class ConstituentsUniverse<T> : FuncUniverse<T>
where T : BaseData
{
/// <summary>
/// Creates a new instance of the <see cref="ConstituentsUniverse"/>
/// </summary>
/// <param name="symbol">The universe symbol</param>
/// <param name="universeSettings">The universe settings to use</param>
/// <param name="constituentsFilter">User-provided function to filter constituents universe with</param>
public ConstituentsUniverse(
Symbol symbol,
UniverseSettings universeSettings,
Func<IEnumerable<T>, IEnumerable<Symbol>> constituentsFilter = null)
: this(new SubscriptionDataConfig(typeof(T),
symbol,
Resolution.Daily,
TimeZones.NewYork,
TimeZones.NewYork,
false,
false,
true,
true),
universeSettings,
constituentsFilter)
{
}
/// <summary>
/// Creates a new instance of the <see cref="ConstituentsUniverse"/>
/// </summary>
/// <param name="symbol">The universe symbol</param>
/// <param name="universeSettings">The universe settings to use</param>
/// <param name="constituentsFilter">User-provided function to filter constituents universe with</param>
public ConstituentsUniverse(
Symbol symbol,
UniverseSettings universeSettings,
PyObject constituentsFilter = null)
: this(symbol, universeSettings, constituentsFilter.ConvertPythonUniverseFilterFunction<T>())
{
}
/// <summary>
/// Creates a new instance of the <see cref="ConstituentsUniverse"/>
/// </summary>
/// <param name="subscriptionDataConfig">The universe configuration to use</param>
/// <param name="universeSettings">The universe settings to use</param>
/// <param name="constituentsFilter">User-provided function to filter constituents universe with</param>
public ConstituentsUniverse(
SubscriptionDataConfig subscriptionDataConfig,
UniverseSettings universeSettings,
Func<IEnumerable<T>, IEnumerable<Symbol>> constituentsFilter = null)
: base(subscriptionDataConfig,
universeSettings,
constituentsFilter ?? (constituents =>
{
var symbols = constituents.Select(baseData => baseData.Symbol).ToList();
// for performance, just compare to Symbol.None if we have 1 Symbol
if (symbols.Count == 1 && symbols[0] == Symbol.None)
{
// no symbol selected
return Enumerable.Empty<Symbol>();
}
return symbols;
}))
{
if (!subscriptionDataConfig.IsCustomData)
{
throw new InvalidOperationException($"{typeof(T).Name} {nameof(SubscriptionDataConfig)}" +
$" only supports custom data property set to 'true'");
}
}
/// <summary>
/// Constituent universe for a Python function
/// </summary>
/// <param name="subscriptionDataConfig">The universe configuration to use</param>
/// <param name="universeSettings">The universe settings to use</param>
/// <param name="constituentsFilter">User-provided function to filter constituents universe with</param>
public ConstituentsUniverse(
SubscriptionDataConfig subscriptionDataConfig,
UniverseSettings universeSettings,
PyObject constituentsFilter = null)
: this(subscriptionDataConfig, universeSettings, constituentsFilter.ConvertPythonUniverseFilterFunction<T>())
{
}
}
/// <summary>
/// ConstituentsUniverse allows to perform universe selection based on an
/// already preselected set of <see cref="Symbol"/>.
/// </summary>
/// <remarks>Using this class allows a performance improvement, since there is no
/// runtime logic computation required for selecting the <see cref="Symbol"/></remarks>
public class ConstituentsUniverse : ConstituentsUniverse<ConstituentsUniverseData>
{
/// <summary>
/// Creates a new instance of the <see cref="ConstituentsUniverse"/>
/// </summary>
/// <param name="symbol">The universe symbol</param>
/// <param name="universeSettings">The universe settings to use</param>
/// <param name="filterFunc">The constituents filter function</param>
public ConstituentsUniverse(Symbol symbol, UniverseSettings universeSettings, Func<IEnumerable<ConstituentsUniverseData>, IEnumerable<Symbol>> filterFunc)
: base(symbol, universeSettings, filterFunc)
{
}
/// <summary>
/// Creates a new instance of the <see cref="ConstituentsUniverse"/>
/// </summary>
/// <param name="symbol">The universe symbol</param>
/// <param name="universeSettings">The universe settings to use</param>
public ConstituentsUniverse(Symbol symbol, UniverseSettings universeSettings)
: base(symbol, universeSettings, (Func<IEnumerable<ConstituentsUniverseData>, IEnumerable<Symbol>>)null)
{
}
/// <summary>
/// Creates a new instance of the <see cref="ConstituentsUniverse"/>
/// </summary>
/// <param name="symbol">The universe symbol</param>
/// <param name="universeSettings">The universe settings to use</param>
/// <param name="filterFunc">The constituents filter function</param>
public ConstituentsUniverse(Symbol symbol, UniverseSettings universeSettings, PyObject filterFunc)
: base(symbol, universeSettings, filterFunc)
{
}
}
}
@@ -0,0 +1,139 @@
/*
* 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.IO;
using System.Collections.Generic;
namespace QuantConnect.Data.UniverseSelection
{
/// <summary>
/// Custom base data class used for <see cref="ConstituentsUniverse"/>
/// </summary>
public class ConstituentsUniverseData : BaseData
{
/// <summary>
/// Initializes a new instance of the <see cref="CoarseFundamental"/> class
/// </summary>
public ConstituentsUniverseData()
{
DataType = MarketDataType.Auxiliary;
}
/// <summary>
/// The end time of this data.
/// </summary>
public override DateTime EndTime
{
get { return Time + QuantConnect.Time.OneDay; }
set { Time = value - QuantConnect.Time.OneDay; }
}
/// <summary>
/// Return the URL string source of the file. This will be converted to a stream
/// </summary>
/// <param name="config">Configuration object</param>
/// <param name="date">Date of this source file</param>
/// <param name="isLiveMode">true if we're in live mode, false for backtesting mode</param>
/// <returns>String URL of source file.</returns>
public override SubscriptionDataSource GetSource(SubscriptionDataConfig config, DateTime date, bool isLiveMode)
{
var universe = config.Symbol.ID.Symbol.Substring(config.MappedSymbol.LastIndexOf('-') + 1);
if (isLiveMode)
{
date = date.AddDays(-1);
}
var path = Path.Combine(Globals.DataFolder,
config.SecurityType.SecurityTypeToLower(),
config.Market,
"universes",
config.Resolution.ResolutionToLower(),
universe,
$"{date:yyyyMMdd}.csv");
return new SubscriptionDataSource(path, SubscriptionTransportMedium.LocalFile, FileFormat.Csv);
}
/// <summary>
/// Reader converts each line of the data source into BaseData objects. Each data type creates its own factory method, and returns a new instance of the object
/// each time it is called.
/// </summary>
/// <param name="config">Subscription data config setup object</param>
/// <param name="line">Line of the source document</param>
/// <param name="date">Date of the requested data</param>
/// <param name="isLiveMode">true if we're in live mode, false for backtesting mode</param>
/// <returns>Instance of the T:BaseData object generated by this line of the CSV</returns>
public override BaseData Reader(SubscriptionDataConfig config, string line, DateTime date, bool isLiveMode)
{
try
{
var csv = line.Split(',');
var preselected = new ConstituentsUniverseData
{
Symbol = new Symbol(SecurityIdentifier.Parse(csv[1]), csv[0]),
Time = isLiveMode ? date.AddDays(-1) : date
};
return preselected;
}
catch
{
return null;
}
}
/// <summary>
/// Indicates that the data set is expected to be sparse
/// </summary>
/// <remarks>Relies on the <see cref="Symbol"/> property value</remarks>
/// <remarks>This is a method and not a property so that python
/// custom data types can override it</remarks>
/// <returns>True if the data set represented by this type is expected to be sparse</returns>
public override bool IsSparseData()
{
return true;
}
/// <summary>
/// Indicates if there is support for mapping
/// </summary>
/// <returns>True indicates mapping should be used</returns>
public override bool RequiresMapping()
{
return false;
}
/// <summary>
/// Gets the default resolution for this data and security type
/// </summary>
/// <remarks>This is a method and not a property so that python
/// custom data types can override it</remarks>
public override Resolution DefaultResolution()
{
return Resolution.Daily;
}
/// <summary>
/// Gets the supported resolution for this data and security type
/// </summary>
/// <remarks>Relies on the <see cref="Symbol"/> property value</remarks>
/// <remarks>This is a method and not a property so that python
/// custom data types can override it</remarks>
public override List<Resolution> SupportedResolutions()
{
return DailyResolution;
}
}
}
@@ -0,0 +1,164 @@
/*
* 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.Linq;
using QuantConnect.Util;
using QuantConnect.Interfaces;
using QuantConnect.Securities;
using System.Collections.Generic;
using QuantConnect.Data.Auxiliary;
namespace QuantConnect.Data.UniverseSelection
{
/// <summary>
/// Continuous contract universe selection that based on the requested mapping mode will select each symbol
/// </summary>
public class ContinuousContractUniverse : Universe, ITimeTriggeredUniverse
{
private readonly IMapFileProvider _mapFileProvider;
private readonly SubscriptionDataConfig _config;
private readonly Security _security;
private readonly bool _liveMode;
private Symbol _currentSymbol;
private string _mappedSymbol;
/// <summary>
/// True if this universe filter can run async in the data stack
/// TODO: see IContinuousSecurity.Mapped
/// </summary>
public override bool Asynchronous => false;
/// <summary>
/// Creates a new instance
/// </summary>
public ContinuousContractUniverse(Security security, UniverseSettings universeSettings, bool liveMode, SubscriptionDataConfig universeConfig)
: base(universeConfig)
{
_security = security;
_liveMode = liveMode;
UniverseSettings = universeSettings;
_mapFileProvider = Composer.Instance.GetPart<IMapFileProvider>();
_config = new SubscriptionDataConfig(Configuration, dataMappingMode: UniverseSettings.DataMappingMode, symbol: _security.Symbol.Canonical);
}
/// <summary>
/// Performs universe selection based on the symbol mapping
/// </summary>
/// <param name="utcTime">The current utc time</param>
/// <param name="data">Empty data</param>
/// <returns>The symbols to use</returns>
public override IEnumerable<Symbol> SelectSymbols(DateTime utcTime, BaseDataCollection data)
{
yield return _security.Symbol.Canonical;
var mapFile = _mapFileProvider.ResolveMapFile(_config);
var mappedSymbol = mapFile.GetMappedSymbol(utcTime.ConvertFromUtc(_security.Exchange.TimeZone), dataMappingMode: _config.DataMappingMode);
if (!string.IsNullOrEmpty(mappedSymbol) && mappedSymbol != _mappedSymbol)
{
if (_currentSymbol != null)
{
// let's emit the old and new for the mapping date
yield return _currentSymbol;
}
_mappedSymbol = mappedSymbol;
_currentSymbol = _security.Symbol.Canonical
.UpdateMappedSymbol(mappedSymbol, Configuration.ContractDepthOffset)
.Underlying;
}
if (_currentSymbol != null)
{
// TODO: this won't work with async universe selection
((IContinuousSecurity)_security).Mapped = _currentSymbol;
yield return _currentSymbol;
}
}
/// <summary>
/// Gets the subscription requests to be added for the specified security
/// </summary>
/// <param name="security">The security to get subscriptions for</param>
/// <param name="currentTimeUtc">The current time in utc. This is the frontier time of the algorithm</param>
/// <param name="maximumEndTimeUtc">The max end time</param>
/// <param name="subscriptionService">Instance which implements <see cref="ISubscriptionDataConfigService"/> interface</param>
/// <returns>All subscriptions required by this security</returns>
public override IEnumerable<SubscriptionRequest> GetSubscriptionRequests(Security security,
DateTime currentTimeUtc,
DateTime maximumEndTimeUtc,
ISubscriptionDataConfigService subscriptionService)
{
var configs = AddConfigurations(subscriptionService, UniverseSettings, security.Symbol);
return configs.Select(config => new SubscriptionRequest(isUniverseSubscription: false,
universe: this,
security: security,
configuration: new SubscriptionDataConfig(config, isInternalFeed: config.IsInternalFeed || config.TickType == TickType.OpenInterest),
startTimeUtc: currentTimeUtc,
endTimeUtc: maximumEndTimeUtc));
}
/// <summary>
/// Each tradeable day of the future we trigger a new selection.
/// Allows use to select the current contract
/// </summary>
public IEnumerable<DateTime> GetTriggerTimes(DateTime startTimeUtc, DateTime endTimeUtc, MarketHoursDatabase marketHoursDatabase)
{
var startTimeLocal = startTimeUtc.ConvertFromUtc(_security.Exchange.TimeZone);
var endTimeLocal = endTimeUtc.ConvertFromUtc(_security.Exchange.TimeZone);
return Time.EachTradeableDay(_security, startTimeLocal, endTimeLocal, Configuration.ExtendedMarketHours)
// in live trading selection happens on start see 'DataQueueFuturesChainUniverseDataCollectionEnumerator'
.Where(tradeableDay => _liveMode || tradeableDay >= startTimeLocal)
// in live trading we delay selection so that we make sure auxiliary data is ready
.Select(time => _liveMode ? time.Add(Time.LiveAuxiliaryDataOffset) : time);
}
/// <summary>
/// Helper method to add and get the required configurations associated with a continuous universe
/// </summary>
public static List<SubscriptionDataConfig> AddConfigurations(ISubscriptionDataConfigService subscriptionService, UniverseSettings universeSettings, Symbol symbol)
{
List<SubscriptionDataConfig> configs = new(universeSettings.SubscriptionDataTypes.Count);
foreach (var pair in universeSettings.SubscriptionDataTypes)
{
configs.AddRange(subscriptionService.Add(symbol,
universeSettings.Resolution,
universeSettings.FillForward,
universeSettings.ExtendedMarketHours,
dataNormalizationMode: universeSettings.DataNormalizationMode,
// we need to provider the data types we want, else since it's canonical it would assume the default ZipEntry type used in universe chain
subscriptionDataTypes: new List<Tuple<Type, TickType>> { pair },
dataMappingMode: universeSettings.DataMappingMode,
contractDepthOffset: (uint)Math.Abs(universeSettings.ContractDepthOffset),
// open interest is internal and the underlying mapped contracts of the continuous canonical
isInternalFeed: !symbol.IsCanonical() || pair.Item2 == TickType.OpenInterest));
}
return configs;
}
/// <summary>
/// Creates a continuous universe symbol
/// </summary>
/// <param name="symbol">The associated symbol</param>
/// <returns>A symbol for a continuous universe of the specified symbol</returns>
public static Symbol CreateSymbol(Symbol symbol)
{
var ticker = $"qc-universe-continuous-{symbol.ID.Market.ToLowerInvariant()}-{symbol.SecurityType}-{symbol.ID.Symbol}";
return UniverseExtensions.CreateSymbol(symbol.SecurityType, symbol.ID.Market, ticker);
}
}
}
@@ -0,0 +1,122 @@
/*
* 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 QuantConnect.Data.Market;
namespace QuantConnect.Data.UniverseSelection;
/// <summary>
/// Represents derivative market data including trade and open interest information.
/// </summary>
public class DerivativeUniverseData
{
private readonly Symbol _symbol;
private decimal _open;
private decimal _high;
private decimal _low;
private decimal _close;
private decimal _volume;
private decimal? _openInterest;
/// <summary>
/// Initializes a new instance of <see cref="DerivativeUniverseData"/> using open interest data.
/// </summary>
/// <param name="openInterest">The open interest data.</param>
public DerivativeUniverseData(OpenInterest openInterest)
{
_symbol = openInterest.Symbol;
_openInterest = openInterest.Value;
}
/// <summary>
/// Initializes a new instance of <see cref="DerivativeUniverseData"/> using trade bar data.
/// </summary>
/// <param name="tradeBar">The trade bar data.</param>
public DerivativeUniverseData(TradeBar tradeBar)
{
_symbol = tradeBar.Symbol;
_open = tradeBar.Open;
_high = tradeBar.High;
_low = tradeBar.Low;
_close = tradeBar.Close;
_volume = tradeBar.Volume;
}
/// <summary>
/// Initializes a new instance of <see cref="DerivativeUniverseData"/> using quote bar data.
/// </summary>
/// <param name="quoteBar">The quote bar data.</param>
public DerivativeUniverseData(QuoteBar quoteBar)
{
_symbol = quoteBar.Symbol;
_open = quoteBar.Open;
_high = quoteBar.High;
_low = quoteBar.Low;
_close = quoteBar.Close;
}
/// <summary>
/// Updates the instance with new trade bar data.
/// </summary>
/// <param name="tradeBar">The new trade bar data.</param>
/// <exception cref="ArgumentNullException">Thrown when tradeBar is null.</exception>
public void UpdateByTradeBar(TradeBar tradeBar)
{
// If price data has already been initialized (likely from a QuoteBar)
if (_open != 0 || _high != 0 || _low != 0 || _close != 0)
{
_volume = tradeBar.Volume;
return;
}
_open = tradeBar.Open;
_high = tradeBar.High;
_low = tradeBar.Low;
_close = tradeBar.Close;
}
/// <summary>
/// Updates the instance with new quote bar data.
/// </summary>
/// <param name="quoteBar">The new quote bar data.</param>
public void UpdateByQuoteBar(QuoteBar quoteBar)
{
_open = quoteBar.Open;
_high = quoteBar.High;
_low = quoteBar.Low;
_close = quoteBar.Close;
}
/// <summary>
/// Updates the instance with new open interest data.
/// </summary>
/// <param name="openInterest">The new open interest data.</param>
/// <exception cref="ArgumentNullException">Thrown when openInterest is null.</exception>
public void UpdateByOpenInterest(OpenInterest openInterest)
{
_openInterest = openInterest.Value;
}
/// <summary>
/// Converts the current data to a CSV format string.
/// </summary>
/// <returns>A CSV formatted string representing the data.</returns>
public string ToCsv()
{
return OptionUniverse.ToCsv(_symbol, _open, _high, _low, _close, _volume, _openInterest, null, NullGreeks.Instance);
}
}
@@ -0,0 +1,210 @@
/*
* 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 NodaTime;
using QuantConnect.Util;
namespace QuantConnect.Data.UniverseSelection
{
/// <summary>
/// ETF Constituent data
/// </summary>
[Obsolete("'ETFConstituentData' was renamed to 'ETFConstituentUniverse'")]
public class ETFConstituentData : ETFConstituentUniverse { }
/// <summary>
/// ETF constituent data
/// </summary>
public class ETFConstituentUniverse : BaseDataCollection
{
/// <summary>
/// Time of the previous ETF constituent data update
/// </summary>
public DateTime? LastUpdate { get; set; }
/// <summary>
/// The percentage of the ETF allocated to this constituent
/// </summary>
public decimal? Weight { get; set; }
/// <summary>
/// Number of shares held in the ETF
/// </summary>
public decimal? SharesHeld { get; set; }
/// <summary>
/// Market value of the current asset held in U.S. dollars
/// </summary>
public decimal? MarketValue { get; set; }
/// <summary>
/// Period of the data
/// </summary>
public TimeSpan Period { get; set; } = TimeSpan.FromDays(1);
/// <summary>
/// Time that the data became available to use
/// </summary>
public override DateTime EndTime
{
get { return Time + Period; }
set { Time = value - Period; }
}
/// <summary>
/// Return the URL string source of the file. This will be converted to a stream
/// </summary>
/// <param name="config">Configuration object</param>
/// <param name="date">Date of this source file</param>
/// <param name="isLiveMode">true if we're in live mode, false for backtesting mode</param>
/// <returns>String URL of source file.</returns>
public override SubscriptionDataSource GetSource(SubscriptionDataConfig config, DateTime date, bool isLiveMode)
{
return new SubscriptionDataSource(
Path.Combine(
Globals.DataFolder,
config.SecurityType.SecurityTypeToLower(),
config.Market,
"universes",
"etf",
config.Symbol.Underlying.Value.ToLowerInvariant(),
$"{date:yyyyMMdd}.csv"),
SubscriptionTransportMedium.LocalFile,
FileFormat.FoldingCollection);
}
/// <summary>
/// Reader converts each line of the data source into BaseData objects. Each data type creates its own factory method, and returns a new instance of the object
/// each time it is called.
/// </summary>
/// <param name="config">Subscription data config setup object</param>
/// <param name="line">Line of the source document</param>
/// <param name="date">Date of the requested data</param>
/// <param name="isLiveMode">true if we're in live mode, false for backtesting mode</param>
/// <returns>Instance of the T:BaseData object generated by this line of the CSV</returns>
public override BaseData Reader(SubscriptionDataConfig config, string line, DateTime date, bool isLiveMode)
{
if (string.IsNullOrEmpty(line))
{
return null;
}
var split = line.Split(',');
var symbol = new Symbol(SecurityIdentifier.Parse(split[1]), split[0]);
var lastUpdateDate = Parse.TryParseExact(split[2], "yyyyMMdd", DateTimeStyles.None, out var lastUpdateDateParsed)
? lastUpdateDateParsed
: (DateTime?)null;
var weighting = split[3].IsNullOrEmpty()
? (decimal?)null
: Parse.Decimal(split[3], NumberStyles.Any);
var sharesHeld = split[4].IsNullOrEmpty()
? (decimal?)null
: Parse.Decimal(split[4], NumberStyles.Any);
var marketValue = split[5].IsNullOrEmpty()
? (decimal?)null
: Parse.Decimal(split[5], NumberStyles.Any);
return new ETFConstituentUniverse
{
LastUpdate = lastUpdateDate,
Weight = weighting,
SharesHeld = sharesHeld,
MarketValue = marketValue,
Symbol = symbol,
Time = date
};
}
/// <summary>
/// Indicates if there is support for mapping
/// </summary>
/// <returns>True indicates mapping should be used</returns>
public override bool RequiresMapping()
{
return true;
}
/// <summary>
/// Creates a copy of the instance
/// </summary>
/// <returns>Clone of the instance</returns>
public override BaseData Clone()
{
return new ETFConstituentUniverse
{
LastUpdate = LastUpdate,
Weight = Weight,
SharesHeld = SharesHeld,
MarketValue = MarketValue,
Symbol = Symbol,
Time = Time,
Data = Data
};
}
/// <summary>
/// Indicates that the data set is expected to be sparse
/// </summary>
/// <remarks>Relies on the <see cref="Symbol"/> property value</remarks>
/// <remarks>This is a method and not a property so that python
/// custom data types can override it</remarks>
/// <returns>True if the data set represented by this type is expected to be sparse</returns>
public override bool IsSparseData()
{
return true;
}
/// <summary>
/// Gets the default resolution for this data and security type
/// </summary>
/// <remarks>
/// This is a method and not a property so that python
/// custom data types can override it.
/// </remarks>
public override Resolution DefaultResolution()
{
return Resolution.Daily;
}
/// <summary>
/// Gets the supported resolution for this data and security type
/// </summary>
/// <remarks>Relies on the <see cref="Symbol"/> property value</remarks>
/// <remarks>This is a method and not a property so that python
/// custom data types can override it</remarks>
public override List<Resolution> SupportedResolutions()
{
return DailyResolution;
}
/// <summary>
/// Specifies the data time zone for this data type. This is useful for custom data types
/// </summary>
/// <remarks>Will throw <see cref="InvalidOperationException"/> for security types
/// other than <see cref="SecurityType.Base"/></remarks>
/// <returns>The <see cref="DateTimeZone"/> of this data type</returns>
public override DateTimeZone DataTimeZone()
{
return TimeZones.Utc;
}
}
}
@@ -0,0 +1,72 @@
/*
* 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 System.Threading;
using Python.Runtime;
namespace QuantConnect.Data.UniverseSelection
{
/// <summary>
/// Creates a universe based on an ETF's holdings at a given date
/// </summary>
public class ETFConstituentsUniverseFactory : ConstituentsUniverse<ETFConstituentUniverse>
{
private static int _universeCount;
private const string _etfConstituentsUniverseIdentifier = "qc-universe-etf-constituents";
/// <summary>
/// Creates a new universe for the constituents of the ETF provided as <paramref name="symbol"/>
/// </summary>
/// <param name="symbol">The ETF to load constituents for</param>
/// <param name="universeSettings">Universe settings</param>
/// <param name="constituentsFilter">The filter function used to filter out ETF constituents from the universe</param>
public ETFConstituentsUniverseFactory(Symbol symbol, UniverseSettings universeSettings, Func<IEnumerable<ETFConstituentUniverse>, IEnumerable<Symbol>> constituentsFilter = null)
: base(CreateConstituentUniverseETFSymbol(symbol), universeSettings, constituentsFilter ?? (constituents => constituents.Select(c => c.Symbol)))
{
}
/// <summary>
/// Creates a new universe for the constituents of the ETF provided as <paramref name="symbol"/>
/// </summary>
/// <param name="symbol">The ETF to load constituents for</param>
/// <param name="universeSettings">Universe settings</param>
/// <param name="constituentsFilter">The filter function used to filter out ETF constituents from the universe</param>
public ETFConstituentsUniverseFactory(Symbol symbol, UniverseSettings universeSettings, PyObject constituentsFilter)
: this(symbol, universeSettings, constituentsFilter.ConvertPythonUniverseFilterFunction<ETFConstituentUniverse>())
{
}
/// <summary>
/// Creates a universe Symbol for constituent ETFs
/// </summary>
/// <param name="compositeSymbol">The Symbol of the ETF</param>
/// <returns>Universe Symbol with ETF set as underlying</returns>
private static Symbol CreateConstituentUniverseETFSymbol(Symbol compositeSymbol)
{
var universeTicker = $"{_etfConstituentsUniverseIdentifier}-{Interlocked.Increment(ref _universeCount):D10}-{Guid.NewGuid()}";
return new Symbol(
SecurityIdentifier.GenerateConstituentIdentifier(
universeTicker,
compositeSymbol.SecurityType,
compositeSymbol.ID.Market),
universeTicker,
compositeSymbol);
}
}
}
@@ -0,0 +1,63 @@
/*
* 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 Python.Runtime;
using System.Collections.Generic;
using QuantConnect.Data.Fundamental;
namespace QuantConnect.Data.UniverseSelection
{
/// <summary>
/// Provides a universe that can be filtered with a <see cref="FineFundamental"/> selection function
/// </summary>
public class FineFundamentalFilteredUniverse : SelectSymbolsUniverseDecorator
{
/// <summary>
/// The universe that will be used for fine universe selection
/// </summary>
public FineFundamentalUniverse FineFundamentalUniverse { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="FineFundamentalFilteredUniverse"/> class
/// </summary>
/// <param name="universe">The universe to be filtered</param>
/// <param name="fineSelector">The fine selection function</param>
public FineFundamentalFilteredUniverse(Universe universe, Func<IEnumerable<FineFundamental>, IEnumerable<Symbol>> fineSelector)
: base(universe, universe.SelectSymbols)
{
if (universe is CoarseFundamentalUniverse && universe.UniverseSettings.Asynchronous.HasValue && universe.UniverseSettings.Asynchronous.Value)
{
throw new ArgumentException("Asynchronous universe setting is not supported for coarse & fine selections, please use the new Fundamental single pass selection");
}
FineFundamentalUniverse = new FineFundamentalUniverse(universe.UniverseSettings, fineSelector);
FineFundamentalUniverse.SelectionChanged += (sender, args) => OnSelectionChanged(((SelectionEventArgs) args).CurrentSelection);
}
/// <summary>
/// Initializes a new instance of the <see cref="FineFundamentalFilteredUniverse"/> class
/// </summary>
/// <param name="universe">The universe to be filtered</param>
/// <param name="fineSelector">The fine selection function</param>
public FineFundamentalFilteredUniverse(Universe universe, PyObject fineSelector)
: base(universe, universe.SelectSymbols)
{
var func = fineSelector.SafeAs<Func< IEnumerable<FineFundamental>, object>>();
FineFundamentalUniverse = new FineFundamentalUniverse(universe.UniverseSettings, func.ConvertToUniverseSelectionSymbolDelegate());
FineFundamentalUniverse.SelectionChanged += (sender, args) => OnSelectionChanged(((SelectionEventArgs)args).CurrentSelection);
}
}
}
@@ -0,0 +1,77 @@
/*
* 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.Linq;
using System.Collections.Generic;
using QuantConnect.Data.Fundamental;
using QuantConnect.Util;
namespace QuantConnect.Data.UniverseSelection
{
/// <summary>
/// Defines a universe that reads fine us equity data
/// </summary>
public class FineFundamentalUniverse : Universe
{
private readonly Func<IEnumerable<FineFundamental>, IEnumerable<Symbol>> _selector;
/// <summary>
/// Initializes a new instance of the <see cref="FineFundamentalUniverse"/> class
/// </summary>
/// <param name="universeSettings">The settings used for new subscriptions generated by this universe</param>
/// <param name="selector">Returns the symbols that should be included in the universe</param>
public FineFundamentalUniverse(UniverseSettings universeSettings, Func<IEnumerable<FineFundamental>, IEnumerable<Symbol>> selector)
: base(CreateConfiguration(FundamentalUniverseFactory.SymbolFactory.UniverseSymbol()))
{
UniverseSettings = universeSettings;
_selector = selector;
}
/// <summary>
/// Initializes a new instance of the <see cref="FineFundamentalUniverse"/> class
/// </summary>
/// <param name="symbol">Defines the symbol to use for this universe</param>
/// <param name="universeSettings">The settings used for new subscriptions generated by this universe</param>
/// <param name="selector">Returns the symbols that should be included in the universe</param>
public FineFundamentalUniverse(Symbol symbol, UniverseSettings universeSettings, Func<IEnumerable<FineFundamental>, IEnumerable<Symbol>> selector)
: base(CreateConfiguration(symbol))
{
UniverseSettings = universeSettings;
_selector = selector;
}
/// <summary>
/// Performs universe selection using the data specified
/// </summary>
/// <param name="utcTime">The current utc time</param>
/// <param name="data">The symbols to remain in the universe</param>
/// <returns>The data that passes the filter</returns>
public override IEnumerable<Symbol> SelectSymbols(DateTime utcTime, BaseDataCollection data)
{
return _selector(new CastingEnumerable<BaseData, FineFundamental>(data.Data));
}
/// <summary>
/// Creates a <see cref="FineFundamental"/> subscription configuration for the US-equity market
/// </summary>
/// <param name="symbol">The symbol used in the returned configuration</param>
/// <returns>A fine fundamental subscription configuration with the specified symbol</returns>
public static SubscriptionDataConfig CreateConfiguration(Symbol symbol)
{
return FundamentalUniverseFactory.CreateConfiguration(symbol);
}
}
}
@@ -0,0 +1,97 @@
/*
* 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 Python.Runtime;
using QuantConnect.Util;
namespace QuantConnect.Data.UniverseSelection
{
/// <summary>
/// Provides a functional implementation of <see cref="Universe"/>
/// </summary>
/// <typeparam name="T">The BaseData type to provide to the user defined universe filter</typeparam>
public class FuncUniverse<T> : Universe
where T : BaseData
{
private readonly Func<IEnumerable<T>, IEnumerable<Symbol>> _universeSelector;
/// <summary>
/// Initializes a new instance of the <see cref="FuncUniverse{T}"/> class
/// </summary>
/// <param name="configuration">The configuration used to resolve the data for universe selection</param>
/// <param name="universeSettings">The settings used for new subscriptions generated by this universe</param>
/// <param name="universeSelector">Returns the symbols that should be included in the universe</param>
public FuncUniverse(SubscriptionDataConfig configuration, UniverseSettings universeSettings, Func<IEnumerable<T>, IEnumerable<Symbol>> universeSelector)
: base(configuration)
{
_universeSelector = universeSelector;
_universeSelector ??= (dataPoints) => dataPoints.Select(x => x.Symbol);
UniverseSettings = universeSettings;
}
/// <summary>
/// Initializes a new instance of the <see cref="FuncUniverse{T}"/> class for a filter function loaded from Python
/// </summary>
/// <param name="configuration">The configuration used to resolve the data for universe selection</param>
/// <param name="universeSettings">The settings used for new subscriptions generated by this universe</param>
/// <param name="universeSelector">Function that returns the symbols that should be included in the universe</param>
public FuncUniverse(SubscriptionDataConfig configuration, UniverseSettings universeSettings, PyObject universeSelector)
: this(configuration, universeSettings, universeSelector.ConvertPythonUniverseFilterFunction<T>())
{
}
/// <summary>
/// Performs an initial, coarse filter
/// </summary>
/// <param name="utcTime">The current utc time</param>
/// <param name="data">The coarse fundamental data</param>
/// <returns>The data that passes the filter</returns>
public override IEnumerable<Symbol> SelectSymbols(DateTime utcTime, BaseDataCollection data)
{
return _universeSelector(new CastingEnumerable<BaseData, T>(data.Data));
}
}
/// <summary>
/// Provides a functional implementation of <see cref="Universe"/>
/// </summary>
public class FuncUniverse : FuncUniverse<BaseData>
{
/// <summary>
/// Initializes a new instance of the <see cref="FuncUniverse"/> class
/// </summary>
/// <param name="configuration">The configuration used to resolve the data for universe selection</param>
/// <param name="universeSettings">The settings used for new subscriptions generated by this universe</param>
/// <param name="universeSelector">Returns the symbols that should be included in the universe</param>
public FuncUniverse(SubscriptionDataConfig configuration, UniverseSettings universeSettings, Func<IEnumerable<BaseData>, IEnumerable<Symbol>> universeSelector)
: base(configuration, universeSettings, universeSelector)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="FuncUniverse"/> class
/// </summary>
/// <param name="configuration">The configuration used to resolve the data for universe selection</param>
/// <param name="universeSettings">The settings used for new subscriptions generated by this universe</param>
/// <param name="universeSelector">Returns the symbols that should be included in the universe</param>
public FuncUniverse(SubscriptionDataConfig configuration, UniverseSettings universeSettings, PyObject universeSelector)
: base(configuration, universeSettings, universeSelector)
{
}
}
}
@@ -0,0 +1,58 @@
/*
* 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 Python.Runtime;
using System.Collections.Generic;
using QuantConnect.Data.Fundamental;
namespace QuantConnect.Data.UniverseSelection
{
/// <summary>
/// Provides a universe that can be filtered with a <see cref="Fundamental.Fundamental"/> selection function
/// </summary>
public class FundamentalFilteredUniverse : SelectSymbolsUniverseDecorator
{
/// <summary>
/// The universe that will be used for fine universe selection
/// </summary>
public FundamentalUniverseFactory FundamentalUniverse { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="FundamentalFilteredUniverse"/> class
/// </summary>
/// <param name="universe">The universe to be filtered</param>
/// <param name="fundamentalSelector">The fundamental selection function</param>
public FundamentalFilteredUniverse(Universe universe, Func<IEnumerable<Fundamental.Fundamental>, IEnumerable<Symbol>> fundamentalSelector)
: base(universe, universe.SelectSymbols)
{
FundamentalUniverse = Fundamental.FundamentalUniverse.USA(fundamentalSelector, universe.UniverseSettings);
FundamentalUniverse.SelectionChanged += (sender, args) => OnSelectionChanged(((SelectionEventArgs)args).CurrentSelection);
}
/// <summary>
/// Initializes a new instance of the <see cref="FundamentalFilteredUniverse"/> class
/// </summary>
/// <param name="universe">The universe to be filtered</param>
/// <param name="fundamentalSelector">The fundamental selection function</param>
public FundamentalFilteredUniverse(Universe universe, PyObject fundamentalSelector)
: base(universe, universe.SelectSymbols)
{
var func = fundamentalSelector.SafeAs<Func<IEnumerable<Fundamental.Fundamental>, object>>();
FundamentalUniverse = Fundamental.FundamentalUniverse.USA(func.ConvertToUniverseSelectionSymbolDelegate(), universe.UniverseSettings);
FundamentalUniverse.SelectionChanged += (sender, args) => OnSelectionChanged(((SelectionEventArgs)args).CurrentSelection);
}
}
}
@@ -0,0 +1,87 @@
/*
* 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 QuantConnect.Util;
using QuantConnect.Interfaces;
using QuantConnect.Configuration;
using QuantConnect.Data.Fundamental;
namespace QuantConnect.Data.UniverseSelection
{
/// <summary>
/// Fundamental data provider service
/// </summary>
public static class FundamentalService
{
private static IFundamentalDataProvider _fundamentalDataProvider;
/// <summary>
/// Initializes the service
/// </summary>
/// <param name="dataProvider">The data provider instance to use</param>
/// <param name="liveMode">True if running in live mode</param>
public static void Initialize(IDataProvider dataProvider, bool liveMode)
{
Initialize(dataProvider, Config.Get("fundamental-data-provider", nameof(CoarseFundamentalDataProvider)), liveMode);
}
/// <summary>
/// Initializes the service
/// </summary>
/// <param name="dataProvider">The data provider instance to use</param>
/// <param name="fundamentalDataProvider">The fundamental data provider</param>
/// <param name="liveMode">True if running in live mode</param>
public static void Initialize(IDataProvider dataProvider, string fundamentalDataProvider, bool liveMode)
{
Initialize(dataProvider, Composer.Instance.GetExportedValueByTypeName<IFundamentalDataProvider>(fundamentalDataProvider), liveMode);
}
/// <summary>
/// Initializes the service
/// </summary>
/// <param name="dataProvider">The data provider instance to use</param>
/// <param name="fundamentalDataProvider">The fundamental data provider</param>
/// <param name="liveMode">True if running in live mode</param>
public static void Initialize(IDataProvider dataProvider, IFundamentalDataProvider fundamentalDataProvider, bool liveMode)
{
_fundamentalDataProvider = fundamentalDataProvider;
_fundamentalDataProvider.Initialize(dataProvider, liveMode);
}
/// <summary>
/// Will fetch the requested fundamental information for the requested time and symbol
/// </summary>
/// <typeparam name="T">The expected data type</typeparam>
/// <param name="time">The time to request this data for</param>
/// <param name="symbol">The symbol instance</param>
/// <param name="name">The name of the fundamental property</param>
/// <returns>The fundamental information</returns>
public static T Get<T>(DateTime time, Symbol symbol, FundamentalProperty name) => Get<T>(time, symbol.ID, name);
/// <summary>
/// Will fetch the requested fundamental information for the requested time and symbol
/// </summary>
/// <typeparam name="T">The expected data type</typeparam>
/// <param name="time">The time to request this data for</param>
/// <param name="securityIdentifier">The security identifier</param>
/// <param name="name">The name of the fundamental property</param>
/// <returns>The fundamental information</returns>
public static T Get<T>(DateTime time, SecurityIdentifier securityIdentifier, FundamentalProperty name)
{
return _fundamentalDataProvider.Get<T>(time.Date, securityIdentifier, name);
}
}
}
@@ -0,0 +1,112 @@
/*
* 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.Linq;
using Python.Runtime;
using System.Collections.Generic;
using QuantConnect.Data.Fundamental;
using QuantConnect.Util;
namespace QuantConnect.Data.UniverseSelection
{
/// <summary>
/// Defines a universe that reads fundamental us equity data
/// </summary>
public class FundamentalUniverseFactory : Universe
{
internal static readonly FundamentalUniverse SymbolFactory = new();
private readonly Func<IEnumerable<Fundamental.Fundamental>, IEnumerable<Symbol>> _selector;
/// <summary>
/// Initializes a new instance of the <see cref="FundamentalUniverseFactory"/> class
/// </summary>
/// <param name="market">The target market</param>
/// <param name="universeSettings">The settings used for new subscriptions generated by this universe</param>
/// <param name="selector">Returns the symbols that should be included in the universe</param>
public FundamentalUniverseFactory(string market, UniverseSettings universeSettings, Func<IEnumerable<Fundamental.Fundamental>, IEnumerable<Symbol>> selector)
: this(SymbolFactory.UniverseSymbol(market), universeSettings, selector)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="FundamentalUniverseFactory"/> class
/// </summary>
/// <param name="market">The target market</param>
/// <param name="universeSettings">The settings used for new subscriptions generated by this universe</param>
/// <param name="selector">Returns the symbols that should be included in the universe</param>
public FundamentalUniverseFactory(string market, UniverseSettings universeSettings, PyObject selector)
: this(market, universeSettings, selector.SafeAs<Func<IEnumerable<Fundamental.Fundamental>, object>>())
{
}
/// <summary>
/// Initializes a new instance of the <see cref="FundamentalUniverseFactory"/> class
/// </summary>
/// <param name="market">The target market</param>
/// <param name="universeSettings">The settings used for new subscriptions generated by this universe</param>
/// <param name="selector">Returns the symbols that should be included in the universe</param>
public FundamentalUniverseFactory(string market, UniverseSettings universeSettings, Func<IEnumerable<Fundamental.Fundamental>, object> selector)
: this(market, universeSettings, selector.ConvertToUniverseSelectionSymbolDelegate())
{
}
/// <summary>
/// Initializes a new instance of the <see cref="FundamentalUniverseFactory"/> class
/// </summary>
/// <param name="symbol">Defines the symbol to use for this universe</param>
/// <param name="universeSettings">The settings used for new subscriptions generated by this universe</param>
/// <param name="selector">Returns the symbols that should be included in the universe</param>
public FundamentalUniverseFactory(Symbol symbol, UniverseSettings universeSettings, Func<IEnumerable<Fundamental.Fundamental>, IEnumerable<Symbol>> selector)
: base(CreateConfiguration(symbol))
{
UniverseSettings = universeSettings;
_selector = selector;
}
/// <summary>
/// Performs universe selection using the data specified
/// </summary>
/// <param name="utcTime">The current utc time</param>
/// <param name="data">The symbols to remain in the universe</param>
/// <returns>The data that passes the filter</returns>
public override IEnumerable<Symbol> SelectSymbols(DateTime utcTime, BaseDataCollection data)
{
return _selector(new CastingEnumerable<BaseData, Fundamental.Fundamental>(data.Data));
}
/// <summary>
/// Creates a <see cref="Fundamental.Fundamental"/> subscription configuration for the US-equity market
/// </summary>
/// <param name="symbol">The symbol used in the returned configuration</param>
/// <returns>A fundamental subscription configuration with the specified symbol</returns>
public static SubscriptionDataConfig CreateConfiguration(Symbol symbol)
{
return new SubscriptionDataConfig(typeof(Fundamental.FundamentalUniverse),
symbol: symbol,
resolution: Resolution.Daily,
dataTimeZone: TimeZones.NewYork,
exchangeTimeZone: TimeZones.NewYork,
fillForward: false,
extendedHours: false,
isInternalFeed: true,
isCustom: false,
tickType: null,
isFilteredSubscription: false
);
}
}
}
@@ -0,0 +1,168 @@
/*
* 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.IO;
using QuantConnect.Util;
using System.Collections.Generic;
using QuantConnect.Securities.Future;
using System.Runtime.CompilerServices;
namespace QuantConnect.Data.UniverseSelection
{
/// <summary>
/// Represents a universe of futures data
/// </summary>
public class FutureUniverse : BaseChainUniverseData
{
/// <summary>
/// Cache for the symbols to avoid creating them multiple times
/// </summary>
/// <remarks>Key: securityType, market, ticker, expiry</remarks>
private static readonly Dictionary<(SecurityType, string, string, DateTime), Symbol> _symbolsCache = new();
/// <summary>
/// Creates a new instance of the <see cref="FutureUniverse"/> class
/// </summary>
public FutureUniverse()
{
}
/// <summary>
/// Creates a new instance of the <see cref="FutureUniverse"/> class
/// </summary>
public FutureUniverse(DateTime date, Symbol symbol, string csv)
: base(date, symbol, csv)
{
}
/// <summary>
/// Creates a new instance of the <see cref="FutureUniverse"/> class as a copy of the given instance
/// </summary>
public FutureUniverse(FutureUniverse other)
: base(other)
{
}
/// <summary>
/// Reader converts each line of the data source into BaseData objects. Each data type creates its own factory method, and returns a new instance of the object
/// each time it is called.
/// </summary>
/// <param name="config">Subscription data config setup object</param>
/// <param name="stream">Stream reader of the source document</param>
/// <param name="date">Date of the requested data</param>
/// <param name="isLiveMode">true if we're in live mode, false for backtesting mode</param>
/// <returns>Instance of the T:BaseData object generated by this line of the CSV</returns>
[StubsIgnore]
public override BaseData Reader(SubscriptionDataConfig config, StreamReader stream, DateTime date, bool isLiveMode)
{
if (stream == null || stream.EndOfStream)
{
return null;
}
if (stream.Peek() == '#')
{
// Skip header
stream.ReadLine();
return null;
}
var futureContractMonth = stream.GetDateTime(DateFormat.YearMonth);
var cacheKey = (config.SecurityType, config.Market, config.Symbol.ID.Symbol, futureContractMonth);
if (!TryGetCachedSymbol(cacheKey, out var symbol))
{
var expiry = FuturesExpiryUtilityFunctions.GetFutureExpirationFromContractMonth(config.Symbol, futureContractMonth);
symbol = Symbol.CreateFuture(config.Symbol.ID.Symbol, config.Symbol.ID.Market, expiry);
CacheSymbol(cacheKey, symbol);
}
return new FutureUniverse(date, symbol, stream.ReadLine());
}
/// <summary>
/// Creates a copy of the instance
/// </summary>
/// <returns>Clone of the instance</returns>
public override BaseData Clone()
{
return new FutureUniverse(this);
}
/// <summary>
/// Gets the default resolution for this data and security type
/// </summary>
/// <remarks>This is a method and not a property so that python
/// custom data types can override it</remarks>
public override Resolution DefaultResolution()
{
return Resolution.Daily;
}
/// <summary>
/// Implicit conversion into <see cref="Symbol"/>
/// </summary>
/// <param name="data">The option universe data to be converted</param>
#pragma warning disable CA2225 // Operator overloads have named alternates
public static implicit operator Symbol(FutureUniverse data)
#pragma warning restore CA2225 // Operator overloads have named alternates
{
return data.Symbol;
}
/// <summary>
/// Gets the CSV string representation of this universe entry
/// </summary>
public static string ToCsv(Symbol symbol, decimal open, decimal high, decimal low, decimal close, decimal volume, decimal? openInterest)
{
var contractMonth = FuturesExpiryUtilityFunctions.GetFutureContractMonth(symbol);
return $"{contractMonth.ToString(DateFormat.YearMonth)},{open},{high},{low},{close},{volume},{openInterest}";
}
/// <summary>
/// Gets the CSV header string for this universe entry
/// </summary>
public static string CsvHeader => "expiry,open,high,low,close,volume,open_interest";
/// <summary>
/// Tries to get a symbol from the cache
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected static bool TryGetCachedSymbol((SecurityType, string, string, DateTime) key, out Symbol symbol)
{
lock (_symbolsCache)
{
return _symbolsCache.TryGetValue(key, out symbol);
}
}
/// <summary>
/// Caches a symbol
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected static void CacheSymbol((SecurityType, string, string, DateTime) key, Symbol symbol)
{
lock (_symbolsCache)
{
// limit the cache size to help with memory usage
if (_symbolsCache.Count >= 100000)
{
_symbolsCache.Clear();
}
_symbolsCache.TryAdd(key, symbol);
}
}
}
}
@@ -0,0 +1,94 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect.Securities;
using QuantConnect.Securities.Future;
using QuantConnect.Util;
namespace QuantConnect.Data.UniverseSelection
{
/// <summary>
/// Defines a universe for a single futures chain
/// </summary>
public class FuturesChainUniverse : Universe
{
/// <summary>
/// True if this universe filter can run async in the data stack
/// </summary>
public override bool Asynchronous
{
get
{
if (UniverseSettings.Asynchronous.HasValue)
{
return UniverseSettings.Asynchronous.Value;
}
return Future.ContractFilter.Asynchronous;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="FuturesChainUniverse"/> class
/// </summary>
/// <param name="future">The canonical future chain security</param>
/// <param name="universeSettings">The universe settings to be used for new subscriptions</param>
public FuturesChainUniverse(Future future,
UniverseSettings universeSettings)
: base(future.SubscriptionDataConfig)
{
Future = future;
UniverseSettings = universeSettings;
}
/// <summary>
/// The canonical future chain security
/// </summary>
public Future Future { get; }
/// <summary>
/// Gets the settings used for subscriptons added for this universe
/// </summary>
public override UniverseSettings UniverseSettings
{
set
{
if (value != null)
{
// make sure data mode is raw
base.UniverseSettings = new UniverseSettings(value) { DataNormalizationMode = DataNormalizationMode.Raw };
}
}
}
/// <summary>
/// Performs universe selection using the data specified
/// </summary>
/// <param name="utcTime">The current utc time</param>
/// <param name="data">The symbols to remain in the universe</param>
/// <returns>The data that passes the filter</returns>
public override IEnumerable<Symbol> SelectSymbols(DateTime utcTime, BaseDataCollection data)
{
var localEndTime = utcTime.ConvertFromUtc(Future.Exchange.TimeZone);
var availableContracts = new CastingEnumerable<BaseData, FutureUniverse>(data.Data);
var results = Future.ContractFilter.Filter(new FutureFilterUniverse(availableContracts, localEndTime));
return results.Select(x => x.Symbol);
}
}
}
@@ -0,0 +1,61 @@
/*
* 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 QuantConnect.Securities;
namespace QuantConnect.Data.UniverseSelection
{
/// <summary>
/// Provides a universe decoration that replaces the implementation of <see cref="GetSubscriptionRequests"/>
/// </summary>
public class GetSubscriptionRequestsUniverseDecorator : UniverseDecorator
{
private readonly GetSubscriptionRequestsDelegate _getRequests;
/// <summary>
/// Delegate type for the <see cref="GetSubscriptionRequests"/> method
/// </summary>
/// <param name="security">The security to get subscription requests for</param>
/// <param name="currentTimeUtc">The current utc frontier time</param>
/// <param name="maximumEndTimeUtc"></param>
/// <returns>The subscription requests for the security to be given to the data feed</returns>
public delegate IEnumerable<SubscriptionRequest> GetSubscriptionRequestsDelegate(Security security, DateTime currentTimeUtc, DateTime maximumEndTimeUtc);
/// <summary>
/// Initializes a new instance of the <see cref="GetSubscriptionRequestsUniverseDecorator"/> class
/// </summary>
/// <param name="universe">The universe to be decorated</param>
/// <param name="getRequests"></param>
public GetSubscriptionRequestsUniverseDecorator(Universe universe, GetSubscriptionRequestsDelegate getRequests)
: base(universe)
{
_getRequests = getRequests;
}
/// <summary>
/// Gets the subscription requests to be added for the specified security
/// </summary>
/// <param name="security">The security to get subscriptions for</param>
/// <param name="currentTimeUtc">The current time in utc. This is the frontier time of the algorithm</param>
/// <param name="maximumEndTimeUtc">The max end time</param>
/// <returns>All subscriptions required by this security</returns>
public override IEnumerable<SubscriptionRequest> GetSubscriptionRequests(Security security, DateTime currentTimeUtc, DateTime maximumEndTimeUtc)
{
return _getRequests(security, currentTimeUtc, maximumEndTimeUtc);
}
}
}
@@ -0,0 +1,44 @@
/*
* 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 QuantConnect.Data.Fundamental;
using QuantConnect.Interfaces;
namespace QuantConnect.Data.UniverseSelection
{
/// <summary>
///
/// </summary>
public interface IFundamentalDataProvider
{
/// <summary>
/// Initializes the service
/// </summary>
/// <param name="dataProvider">The data provider instance to use</param>
/// <param name="liveMode">True if running in live mode</param>
void Initialize(IDataProvider dataProvider, bool liveMode);
/// <summary>
/// Will fetch the requested fundamental information for the requested time and symbol
/// </summary>
/// <typeparam name="T">The expected data type</typeparam>
/// <param name="time">The time to request this data for</param>
/// <param name="securityIdentifier">The security identifier</param>
/// <param name="name">The name of the fundamental property</param>
/// <returns>The fundamental information</returns>
T Get<T>(DateTime time, SecurityIdentifier securityIdentifier, FundamentalProperty name);
}
}
@@ -0,0 +1,35 @@
/*
* 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 QuantConnect.Securities;
namespace QuantConnect.Data.UniverseSelection
{
/// <summary>
/// A universe implementing this interface will NOT use it's SubscriptionDataConfig to generate data
/// that is used to 'pulse' the universe selection function -- instead, the times output by
/// GetTriggerTimes are used to 'pulse' the universe selection function WITHOUT data.
/// </summary>
public interface ITimeTriggeredUniverse
{
/// <summary>
/// Returns an enumerator that defines when this user defined universe will be invoked
/// </summary>
/// <returns>An enumerator of DateTime that defines when this universe will be invoked</returns>
IEnumerable<DateTime> GetTriggerTimes(DateTime startTimeUtc, DateTime endTimeUtc, MarketHoursDatabase marketHoursDatabase);
}
}
@@ -0,0 +1,162 @@
/*
* 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.Linq;
using QuantConnect.Interfaces;
using QuantConnect.Securities;
using System.Collections.Generic;
using QuantConnect.Securities.Option;
using QuantConnect.Util;
namespace QuantConnect.Data.UniverseSelection
{
/// <summary>
/// Defines a universe for a single option chain
/// </summary>
public class OptionChainUniverse : Universe
{
private readonly OptionFilterUniverse _optionFilterUniverse;
// as an array to make it easy to prepend to selected symbols
private readonly Symbol[] _underlyingSymbol;
/// <summary>
/// True if this universe filter can run async in the data stack
/// </summary>
public override bool Asynchronous
{
get
{
if (UniverseSettings.Asynchronous.HasValue)
{
return UniverseSettings.Asynchronous.Value;
}
return Option.ContractFilter.Asynchronous;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="OptionChainUniverse"/> class
/// </summary>
/// <param name="option">The canonical option chain security</param>
/// <param name="universeSettings">The universe settings to be used for new subscriptions</param>
public OptionChainUniverse(Option option,
UniverseSettings universeSettings)
: base(option.SubscriptionDataConfig)
{
Option = option;
UniverseSettings = universeSettings;
_underlyingSymbol = new[] { Option.Symbol.Underlying };
_optionFilterUniverse = new OptionFilterUniverse(Option);
}
/// <summary>
/// The canonical option chain security
/// </summary>
public Option Option { get; }
/// <summary>
/// Gets the settings used for subscriptons added for this universe
/// </summary>
public override UniverseSettings UniverseSettings
{
set
{
if (value != null)
{
// make sure data mode is raw
base.UniverseSettings = new UniverseSettings(value) { DataNormalizationMode = DataNormalizationMode.Raw };
}
}
}
/// <summary>
/// Performs universe selection using the data specified
/// </summary>
/// <param name="utcTime">The current utc time</param>
/// <param name="data">The symbols to remain in the universe</param>
/// <returns>The data that passes the filter</returns>
public override IEnumerable<Symbol> SelectSymbols(DateTime utcTime, BaseDataCollection data)
{
var localEndTime = utcTime.ConvertFromUtc(Option.Exchange.TimeZone);
// we will only update unique strikes when there is an exchange date change
_optionFilterUniverse.Refresh(new CastingEnumerable<BaseData, OptionUniverse>(data.Data), data.Underlying, localEndTime);
var results = Option.ContractFilter.Filter(_optionFilterUniverse);
// always prepend the underlying symbol
return _underlyingSymbol.Concat(results.Select(x => x.Symbol));
}
/// <summary>
/// Adds the specified security to this universe
/// </summary>
/// <param name="utcTime">The current utc date time</param>
/// <param name="security">The security to be added</param>
/// <param name="isInternal">True if internal member</param>
/// <returns>True if the security was successfully added,
/// false if the security was already in the universe</returns>
internal override bool AddMember(DateTime utcTime, Security security, bool isInternal)
{
// never add members to disposed universes
if (DisposeRequested)
{
return false;
}
if (Securities.ContainsKey(security.Symbol))
{
return false;
}
// method take into account the case, when the option has experienced an adjustment
// we update member reference in this case
if (Securities.Any(x => x.Value.Security == security))
{
Member member;
Securities.TryRemove(security.Symbol, out member);
}
return Securities.TryAdd(security.Symbol, new Member(utcTime, security, isInternal));
}
/// <summary>
/// Gets the subscription requests to be added for the specified security
/// </summary>
/// <param name="security">The security to get subscriptions for</param>
/// <param name="currentTimeUtc">The current time in utc. This is the frontier time of the algorithm</param>
/// <param name="maximumEndTimeUtc">The max end time</param>
/// <param name="subscriptionService">Instance which implements <see cref="ISubscriptionDataConfigService"/> interface</param>
/// <returns>All subscriptions required by this security</returns>
public override IEnumerable<SubscriptionRequest> GetSubscriptionRequests(Security security, DateTime currentTimeUtc, DateTime maximumEndTimeUtc,
ISubscriptionDataConfigService subscriptionService)
{
if (Option.Symbol.Underlying == security.Symbol)
{
Option.Underlying = security;
security.SetDataNormalizationMode(DataNormalizationMode.Raw);
}
else
{
// set the underlying security and pricing model from the canonical security
var option = (Option)security;
option.PriceModel = Option.PriceModel;
}
return base.GetSubscriptionRequests(security, currentTimeUtc, maximumEndTimeUtc, subscriptionService);
}
}
}
@@ -0,0 +1,313 @@
/*
* 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.IO;
using System.Runtime.CompilerServices;
using QuantConnect.Data.Market;
using QuantConnect.Python;
using QuantConnect.Util;
namespace QuantConnect.Data.UniverseSelection
{
/// <summary>
/// Represents a universe of options data
/// </summary>
public class OptionUniverse : BaseChainUniverseData
{
/// <summary>
/// Cache for the symbols to avoid creating them multiple times
/// </summary>
/// <remarks>Key: securityType, market, ticker, expiry, strike, right</remarks>
private static readonly Dictionary<(SecurityType, string, string, DateTime, decimal, OptionRight), Symbol> _symbolsCache = new();
private const int StartingGreeksCsvIndex = 7;
/// <summary>
/// Open interest value of the option
/// </summary>
public override decimal OpenInterest
{
get
{
ThrowIfNotAnOption(nameof(OpenInterest));
return base.OpenInterest;
}
}
/// <summary>
/// Implied volatility value of the option
/// </summary>
public decimal ImpliedVolatility
{
get
{
ThrowIfNotAnOption(nameof(ImpliedVolatility));
return CsvLine.GetDecimalFromCsv(6);
}
}
/// <summary>
/// Greeks values of the option
/// </summary>
public Greeks Greeks
{
get
{
ThrowIfNotAnOption(nameof(Greeks));
return new PreCalculatedGreeks(CsvLine);
}
}
/// <summary>
/// Creates a new instance of the <see cref="OptionUniverse"/> class
/// </summary>
public OptionUniverse()
{
}
/// <summary>
/// Creates a new instance of the <see cref="OptionUniverse"/> class
/// </summary>
public OptionUniverse(DateTime date, Symbol symbol, string csv)
: base(date, symbol, csv)
{
}
/// <summary>
/// Creates a new instance of the <see cref="OptionUniverse"/> class as a copy of the given instance
/// </summary>
public OptionUniverse(OptionUniverse other)
: base(other)
{
}
/// <summary>
/// Reader converts each line of the data source into BaseData objects. Each data type creates its own factory method, and returns a new instance of the object
/// each time it is called.
/// </summary>
/// <param name="config">Subscription data config setup object</param>
/// <param name="stream">Stream reader of the source document</param>
/// <param name="date">Date of the requested data</param>
/// <param name="isLiveMode">true if we're in live mode, false for backtesting mode</param>
/// <returns>Instance of the T:BaseData object generated by this line of the CSV</returns>
[StubsIgnore]
public override BaseData Reader(SubscriptionDataConfig config, StreamReader stream, DateTime date, bool isLiveMode)
{
if (stream == null || stream.EndOfStream)
{
return null;
}
var firstChar = (char)stream.Peek();
if (firstChar == '#')
{
// Skip header
stream.ReadLine();
return null;
}
Symbol symbol;
if (!char.IsDigit(firstChar))
{
// This is the underlying line
symbol = config.Symbol.Underlying;
// Skip the first 3 cells, expiry, strike and right, which will be empty for the underlying
stream.GetChar();
stream.GetChar();
stream.GetChar();
}
else
{
var expiry = stream.GetDateTime("yyyyMMdd");
var strike = stream.GetDecimal();
var right = char.ToUpperInvariant(stream.GetChar()) == 'C' ? OptionRight.Call : OptionRight.Put;
var targetOption = config.Symbol.SecurityType != SecurityType.IndexOption ? null : config.Symbol.ID.Symbol;
var cacheKey = (config.SecurityType, config.Market, targetOption ?? config.Symbol.Underlying.Value, expiry, strike, right);
if (!TryGetCachedSymbol(cacheKey, out symbol))
{
symbol = Symbol.CreateOption(config.Symbol.Underlying, targetOption, config.Symbol.ID.Market,
config.Symbol.SecurityType.DefaultOptionStyle(), right, strike, expiry);
CacheSymbol(cacheKey, symbol);
}
}
return new OptionUniverse(date, symbol, stream.ReadLine());
}
/// <summary>
/// Adds a new data point to this collection.
/// If the data point is for the underlying, it will be stored in the <see cref="BaseDataCollection.Underlying"/> property.
/// </summary>
/// <param name="newDataPoint">The new data point to add</param>
public override void Add(BaseData newDataPoint)
{
if (newDataPoint is BaseChainUniverseData optionUniverseDataPoint)
{
if (optionUniverseDataPoint.Symbol.HasUnderlying)
{
optionUniverseDataPoint.Underlying = Underlying;
base.Add(optionUniverseDataPoint);
}
else
{
Underlying = optionUniverseDataPoint;
foreach (BaseChainUniverseData data in Data)
{
data.Underlying = optionUniverseDataPoint;
}
}
}
}
/// <summary>
/// Creates a copy of the instance
/// </summary>
/// <returns>Clone of the instance</returns>
public override BaseData Clone()
{
return new OptionUniverse(this);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static string GetOptionSymbolCsv(Symbol symbol)
{
if (!symbol.SecurityType.IsOption())
{
return ",,";
}
return $"{symbol.ID.Date:yyyyMMdd},{symbol.ID.StrikePrice},{(symbol.ID.OptionRight == OptionRight.Call ? 'C' : 'P')}";
}
/// <summary>
/// Gets the CSV string representation of this universe entry
/// </summary>
public static string ToCsv(Symbol symbol, decimal open, decimal high, decimal low, decimal close, decimal volume, decimal? openInterest,
decimal? impliedVolatility, Greeks greeks)
{
if (symbol.SecurityType == SecurityType.Future || symbol.SecurityType == SecurityType.FutureOption)
{
return $"{GetOptionSymbolCsv(symbol)},{open},{high},{low},{close},{volume},{openInterest}";
}
return $"{GetOptionSymbolCsv(symbol)},{open},{high},{low},{close},{volume},"
+ $"{openInterest},{impliedVolatility},{greeks?.Delta},{greeks?.Gamma},{greeks?.Vega},{greeks?.ThetaPerDay},{greeks?.Rho}";
}
/// <summary>
/// Implicit conversion into <see cref="Symbol"/>
/// </summary>
/// <param name="data">The option universe data to be converted</param>
#pragma warning disable CA2225 // Operator overloads have named alternates
public static implicit operator Symbol(OptionUniverse data)
#pragma warning restore CA2225 // Operator overloads have named alternates
{
return data.Symbol;
}
/// <summary>
/// Gets the CSV header string for this universe entry
/// </summary>
public static string CsvHeader(SecurityType securityType)
{
// FOPs don't have greeks
if (securityType == SecurityType.FutureOption || securityType == SecurityType.Future)
{
return "expiry,strike,right,open,high,low,close,volume,open_interest";
}
return "expiry,strike,right,open,high,low,close,volume,open_interest,implied_volatility,delta,gamma,vega,theta,rho";
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ThrowIfNotAnOption(string propertyName)
{
if (!Symbol.SecurityType.IsOption())
{
throw new InvalidOperationException($"{propertyName} is only available for options.");
}
}
/// <summary>
/// Pre-calculated greeks lazily parsed from csv line.
/// It parses the greeks values from the csv line only when they are requested to avoid holding decimals in memory.
/// </summary>
private class PreCalculatedGreeks : Greeks
{
private readonly string _csvLine;
public override decimal Delta => _csvLine.GetDecimalFromCsv(StartingGreeksCsvIndex);
public override decimal Gamma => _csvLine.GetDecimalFromCsv(StartingGreeksCsvIndex + 1);
public override decimal Vega => _csvLine.GetDecimalFromCsv(StartingGreeksCsvIndex + 2);
public override decimal Theta => GetSafeTheta(_csvLine.GetDecimalFromCsv(StartingGreeksCsvIndex + 3));
public override decimal Rho => _csvLine.GetDecimalFromCsv(StartingGreeksCsvIndex + 4);
[PandasIgnore]
public override decimal Lambda => decimal.Zero;
/// <summary>
/// Initializes a new default instance of the <see cref="PreCalculatedGreeks"/> class
/// </summary>
public PreCalculatedGreeks(string csvLine)
{
_csvLine = csvLine;
}
/// <summary>
/// Gets a string representation of the greeks values
/// </summary>
public override string ToString()
{
return $"D: {Delta}, G: {Gamma}, V: {Vega}, T: {Theta}, R: {Rho}";
}
}
/// <summary>
/// Tries to get a symbol from the cache
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected static bool TryGetCachedSymbol((SecurityType, string, string, DateTime, decimal, OptionRight) key, out Symbol symbol)
{
lock (_symbolsCache)
{
return _symbolsCache.TryGetValue(key, out symbol);
}
}
/// <summary>
/// Caches a symbol
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected static void CacheSymbol((SecurityType, string, string, DateTime, decimal, OptionRight) key, Symbol symbol)
{
lock (_symbolsCache)
{
// limit the cache size to help with memory usage
if (_symbolsCache.Count >= 500000)
{
_symbolsCache.Clear();
}
_symbolsCache.TryAdd(key, symbol);
}
}
}
}
+59
View File
@@ -0,0 +1,59 @@
/*
* 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.Linq;
using QuantConnect.Scheduling;
using System.Collections.Generic;
namespace QuantConnect.Data.UniverseSelection
{
/// <summary>
/// Entity in charge of managing a schedule
/// </summary>
public class Schedule
{
private IDateRule _dateRule;
/// <summary>
/// True if this schedule is set
/// </summary>
public bool Initialized => _dateRule != null;
/// <summary>
/// Set a <see cref="IDateRule"/> for this schedule
/// </summary>
public void On(IDateRule dateRule)
{
_dateRule = dateRule;
}
/// <summary>
/// Gets the current schedule for a given start time
/// </summary>
public IEnumerable<DateTime> Get(DateTime startTime, DateTime endTime)
{
return _dateRule?.GetDates(startTime, endTime) ?? Enumerable.Empty<DateTime>();
}
/// <summary>
/// Creates a new instance holding the same schedule if any
/// </summary>
public Schedule Clone()
{
return new Schedule { _dateRule = _dateRule };
}
}
}
@@ -0,0 +1,177 @@
/*
* 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 NodaTime;
using Python.Runtime;
using QuantConnect.Data.Market;
using QuantConnect.Scheduling;
using QuantConnect.Securities;
using System;
using System.Collections.Generic;
namespace QuantConnect.Data.UniverseSelection
{
/// <summary>
/// Defines a user that is fired based on a specified <see cref="IDateRule"/> and <see cref="ITimeRule"/>
/// </summary>
public class ScheduledUniverse : Universe, ITimeTriggeredUniverse
{
private readonly IDateRule _dateRule;
private readonly ITimeRule _timeRule;
private readonly Func<DateTime, IEnumerable<Symbol>> _selector;
/// <summary>
/// Initializes a new instance of the <see cref="ScheduledUniverse"/> class
/// </summary>
/// <param name="timeZone">The time zone the date/time rules are in</param>
/// <param name="dateRule">Date rule defines what days the universe selection function will be invoked</param>
/// <param name="timeRule">Time rule defines what times on each day selected by date rule the universe selection function will be invoked</param>
/// <param name="selector">Selector function accepting the date time firing time and returning the universe selected symbols</param>
/// <param name="settings">Universe settings for subscriptions added via this universe, null will default to algorithm's universe settings</param>
public ScheduledUniverse(DateTimeZone timeZone, IDateRule dateRule, ITimeRule timeRule, Func<DateTime, IEnumerable<Symbol>> selector, UniverseSettings settings = null)
: base(CreateConfiguration(timeZone, dateRule, timeRule))
{
_dateRule = dateRule;
_timeRule = timeRule;
_selector = selector;
UniverseSettings = settings;
}
/// <summary>
/// Initializes a new instance of the <see cref="ScheduledUniverse"/> class
/// </summary>
/// <param name="dateRule">Date rule defines what days the universe selection function will be invoked</param>
/// <param name="timeRule">Time rule defines what times on each day selected by date rule the universe selection function will be invoked</param>
/// <param name="selector">Selector function accepting the date time firing time and returning the universe selected symbols</param>
/// <param name="settings">Universe settings for subscriptions added via this universe, null will default to algorithm's universe settings</param>
public ScheduledUniverse(IDateRule dateRule, ITimeRule timeRule, Func<DateTime, IEnumerable<Symbol>> selector, UniverseSettings settings = null)
: this(TimeZones.Utc, dateRule, timeRule, selector, settings)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ScheduledUniverse"/> class
/// </summary>
/// <param name="timeZone">The time zone the date/time rules are in</param>
/// <param name="dateRule">Date rule defines what days the universe selection function will be invoked</param>
/// <param name="timeRule">Time rule defines what times on each day selected by date rule the universe selection function will be invoked</param>
/// <param name="selector">Selector function accepting the date time firing time and returning the universe selected symbols</param>
/// <param name="settings">Universe settings for subscriptions added via this universe, null will default to algorithm's universe settings</param>
public ScheduledUniverse(DateTimeZone timeZone, IDateRule dateRule, ITimeRule timeRule, PyObject selector, UniverseSettings settings = null)
: base(CreateConfiguration(timeZone, dateRule, timeRule))
{
selector.TrySafeAs<Func<DateTime, object>>(out var func);
_dateRule = dateRule;
_timeRule = timeRule;
_selector = func.ConvertSelectionSymbolDelegate();
UniverseSettings = settings;
}
/// <summary>
/// Initializes a new instance of the <see cref="ScheduledUniverse"/> class
/// </summary>
/// <param name="dateRule">Date rule defines what days the universe selection function will be invoked</param>
/// <param name="timeRule">Time rule defines what times on each day selected by date rule the universe selection function will be invoked</param>
/// <param name="selector">Selector function accepting the date time firing time and returning the universe selected symbols</param>
/// <param name="settings">Universe settings for subscriptions added via this universe, null will default to algorithm's universe settings</param>
public ScheduledUniverse(IDateRule dateRule, ITimeRule timeRule, PyObject selector, UniverseSettings settings = null)
: this(TimeZones.Utc, dateRule, timeRule, selector, settings)
{
}
/// <summary>
/// Performs universe selection using the data specified
/// </summary>
/// <param name="utcTime">The current utc time</param>
/// <param name="data">The symbols to remain in the universe</param>
/// <returns>The data that passes the filter</returns>
public override IEnumerable<Symbol> SelectSymbols(DateTime utcTime, BaseDataCollection data)
{
return _selector(DateTime.SpecifyKind(utcTime, DateTimeKind.Unspecified));
}
/// <summary>
/// Get an enumerator of UTC DateTimes that defines when this universe will be invoked,
/// only including times within [startTimeUtc, endTimeUtc], both bounds inclusive.
/// </summary>
/// <param name="startTimeUtc">The start time of the range in UTC</param>
/// <param name="endTimeUtc">The end time of the range in UTC</param>
/// <returns>An enumerator of UTC DateTimes that defines when this universe will be invoked</returns>
public IEnumerable<DateTime> GetTriggerTimes(DateTime startTimeUtc, DateTime endTimeUtc, MarketHoursDatabase marketHoursDatabase)
{
var startTimeLocal = startTimeUtc.ConvertFromUtc(Configuration.ExchangeTimeZone);
var endTimeLocal = endTimeUtc.ConvertFromUtc(Configuration.ExchangeTimeZone);
// define date/time rule enumerable
var dates = _dateRule.GetDates(startTimeLocal, endTimeLocal);
var times = _timeRule.CreateUtcEventTimes(dates).GetEnumerator();
// Make sure and filter out any times before our start time
// GH #5440
do
{
if (!times.MoveNext())
{
times.Dispose();
yield break;
}
}
while (times.Current < startTimeUtc);
// Start yielding times
do
{
if (times.Current > endTimeUtc)
{
times.Dispose();
yield break;
}
yield return times.Current;
}
while (times.MoveNext());
times.Dispose();
}
private static SubscriptionDataConfig CreateConfiguration(DateTimeZone timeZone, IDateRule dateRule, ITimeRule timeRule)
{
// remove forbidden characters
var ticker = $"{dateRule.Name}_{timeRule.Name}";
foreach (var c in SecurityIdentifier.InvalidSymbolCharacters)
{
ticker = ticker.Replace(c.ToStringInvariant(), "_");
}
var symbol = Symbol.Create(ticker, SecurityType.Base, QuantConnect.Market.USA);
var config = new SubscriptionDataConfig(typeof(Tick),
symbol: symbol,
resolution: Resolution.Daily,
dataTimeZone: timeZone,
exchangeTimeZone: timeZone,
fillForward: false,
extendedHours: false,
isInternalFeed: true,
isCustom: false,
tickType: null,
isFilteredSubscription: false
);
// force always open hours so we don't inadvertently mess with the scheduled firing times
MarketHoursDatabase.FromDataFolder()
.SetEntryAlwaysOpen(config.Market, config.Symbol.Value, config.SecurityType, config.ExchangeTimeZone);
return config;
}
}
}
@@ -0,0 +1,286 @@
/*
* 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.Linq;
using QuantConnect.Securities;
using System.Collections.Generic;
namespace QuantConnect.Data.UniverseSelection
{
/// <summary>
/// Defines the additions and subtractions to the algorithm's security subscriptions
/// </summary>
public class SecurityChanges
{
/// <summary>
/// Gets an instance that represents no changes have been made
/// </summary>
public static readonly SecurityChanges None = new (Enumerable.Empty<Security>(), Enumerable.Empty<Security>(),
Enumerable.Empty<Security>(), Enumerable.Empty<Security>());
private readonly IReadOnlySet<Security> _addedSecurities;
private readonly IReadOnlySet<Security> _removedSecurities;
private readonly IReadOnlySet<Security> _internalAddedSecurities;
private readonly IReadOnlySet<Security> _internalRemovedSecurities;
/// <summary>
/// Gets the total count of added and removed securities
/// </summary>
public int Count => _addedSecurities.Count + _removedSecurities.Count
+ _internalAddedSecurities.Count + _internalRemovedSecurities.Count;
/// <summary>
/// True will filter out custom securities from the
/// <see cref="AddedSecurities"/> and <see cref="RemovedSecurities"/> properties
/// </summary>
/// <remarks>This allows us to filter but also to disable
/// the filtering if desired</remarks>
public bool FilterCustomSecurities { get; set; }
/// <summary>
/// True will filter out internal securities from the
/// <see cref="AddedSecurities"/> and <see cref="RemovedSecurities"/> properties
/// </summary>
/// <remarks>This allows us to filter but also to disable
/// the filtering if desired</remarks>
public bool FilterInternalSecurities { get; set; }
/// <summary>
/// Gets the symbols that were added by universe selection
/// </summary>
/// <remarks>Will use <see cref="FilterCustomSecurities"/> value
/// to determine if custom securities should be filtered</remarks>
/// <remarks>Will use <see cref="FilterInternalSecurities"/> value
/// to determine if internal securities should be filtered</remarks>
public IReadOnlyList<Security> AddedSecurities => GetFilteredList(_addedSecurities,
!FilterInternalSecurities ? _internalAddedSecurities : null);
/// <summary>
/// Gets the symbols that were removed by universe selection. This list may
/// include symbols that were removed, but are still receiving data due to
/// existing holdings or open orders
/// </summary>
/// <remarks>Will use <see cref="FilterCustomSecurities"/> value
/// to determine if custom securities should be filtered</remarks>
/// <remarks>Will use <see cref="FilterInternalSecurities"/> value
/// to determine if internal securities should be filtered</remarks>
public IReadOnlyList<Security> RemovedSecurities => GetFilteredList(_removedSecurities,
!FilterInternalSecurities ? _internalRemovedSecurities : null);
/// <summary>
/// Initializes a new instance of the <see cref="SecurityChanges"/> class
/// </summary>
/// <param name="additions">Added symbols list</param>
/// <param name="removals">Removed symbols list</param>
/// <param name="internalAdditions">Internal added symbols list</param>
/// <param name="internalRemovals">Internal removed symbols list</param>
private SecurityChanges(IEnumerable<Security> additions, IEnumerable<Security> removals,
IEnumerable<Security> internalAdditions, IEnumerable<Security> internalRemovals)
{
_addedSecurities = additions.ToHashSet();
_removedSecurities = removals.ToHashSet();
_internalAddedSecurities = internalAdditions.ToHashSet();
_internalRemovedSecurities = internalRemovals.ToHashSet();
}
/// <summary>
/// Initializes a new instance of the <see cref="SecurityChanges"/> class
/// as a shallow clone of a given instance, sharing the same collections
/// </summary>
/// <param name="changes">The instance to clone</param>
public SecurityChanges(SecurityChanges changes)
{
_addedSecurities = changes._addedSecurities;
_removedSecurities = changes._removedSecurities;
_internalAddedSecurities = changes._internalAddedSecurities;
_internalRemovedSecurities = changes._internalRemovedSecurities;
}
/// <summary>
/// Combines the results of two <see cref="SecurityChanges"/>
/// </summary>
/// <param name="left">The left side of the operand</param>
/// <param name="right">The right side of the operand</param>
/// <returns>Adds the additions together and removes any removals found in the additions, that is, additions take precedence</returns>
public static SecurityChanges operator +(SecurityChanges left, SecurityChanges right)
{
// common case is adding something to nothing, shortcut these to prevent linqness
if (left == None || left.Count == 0) return right;
if (right == None || right.Count == 0) return left;
var additions = Merge(left._addedSecurities, right._addedSecurities);
var internalAdditions = Merge(left._internalAddedSecurities, right._internalAddedSecurities);
var removals = Merge(left._removedSecurities, right._removedSecurities,
security => !additions.Contains(security) && !internalAdditions.Contains(security));
var internalRemovals = Merge(left._internalRemovedSecurities, right._internalRemovedSecurities,
security => !additions.Contains(security) && !internalAdditions.Contains(security));
return new SecurityChanges(additions, removals, internalAdditions, internalRemovals);
}
/// <summary>
/// Initializes a new instance of the <see cref="SecurityChanges"/> class all none internal
/// </summary>
/// <param name="additions">Added symbols list</param>
/// <param name="removals">Removed symbols list</param>
/// <param name="internalAdditions">Internal added symbols list</param>
/// <param name="internalRemovals">Internal removed symbols list</param>
/// <remarks>Useful for testing</remarks>
public static SecurityChanges Create(IReadOnlyCollection<Security> additions, IReadOnlyCollection<Security> removals,
IReadOnlyCollection<Security> internalAdditions, IReadOnlyCollection<Security> internalRemovals)
{
// return None if there's no changes, otherwise return what we've modified
return additions?.Count + removals?.Count + internalAdditions?.Count + internalRemovals?.Count > 0
? new SecurityChanges(additions, removals, internalAdditions, internalRemovals)
: None;
}
#region Overrides of Object
/// <summary>
/// Returns a string that represents the current object.
/// </summary>
/// <returns>
/// A string that represents the current object.
/// </returns>
/// <filterpriority>2</filterpriority>
public override string ToString()
{
if (Count == 0)
{
return "SecurityChanges: None";
}
var added = string.Empty;
if (AddedSecurities.Count != 0)
{
added = $" Added: {string.Join(",", AddedSecurities.Select(x => x.Symbol.ID))}";
}
var removed = string.Empty;
if (RemovedSecurities.Count != 0)
{
removed = $" Removed: {string.Join(",", RemovedSecurities.Select(x => x.Symbol.ID))}";
}
return $"SecurityChanges: {added}{removed}";
}
#endregion
/// <summary>
/// Helper method to filter added and removed securities based on current settings
/// </summary>
private IReadOnlyList<Security> GetFilteredList(IReadOnlySet<Security> source, IReadOnlySet<Security> secondSource = null)
{
IEnumerable<Security> enumerable = source;
if (secondSource != null && secondSource.Count > 0)
{
enumerable = enumerable.Union(secondSource);
}
return enumerable.Where(kvp => !FilterCustomSecurities || kvp.Type != SecurityType.Base)
.Select(kvp => kvp)
.OrderBy(security => security.Symbol.Value)
.ToList();
}
/// <summary>
/// Helper method that will merge two security sets, taken into account an optional filter
/// </summary>
/// <returns>Will return merged set</returns>
private static HashSet<Security> Merge(IReadOnlyCollection<Security> left, IReadOnlyCollection<Security> right, Func<Security, bool> filter = null)
{
// if right is emtpy we just use left
IEnumerable<Security> result = left;
if (right.Count != 0)
{
if (left.Count == 0)
{
// left is emtpy so let's just use right
result = right;
}
else
{
// merge, both are not empty
result = result.Concat(right);
}
}
if (filter != null)
{
result = result.Where(filter.Invoke);
}
return new HashSet<Security>(result);
}
}
/// <summary>
/// Helper method to create security changes
/// </summary>
public class SecurityChangesConstructor
{
private readonly List<Security> _internalAdditions = new();
private readonly List<Security> _internalRemovals = new();
private readonly List<Security> _additions = new();
private readonly List<Security> _removals = new();
/// <summary>
/// Inserts a security addition change
/// </summary>
public void Add(Security security, bool isInternal)
{
if (isInternal)
{
_internalAdditions.Add(security);
}
else
{
_additions.Add(security);
}
}
/// <summary>
/// Inserts a security removal change
/// </summary>
public void Remove(Security security, bool isInternal)
{
if (isInternal)
{
_internalRemovals.Add(security);
}
else
{
_removals.Add(security);
}
}
/// <summary>
/// Get the current security changes clearing state
/// </summary>
public SecurityChanges Flush()
{
var result = SecurityChanges.Create(_additions, _removals, _internalAdditions, _internalRemovals);
_internalAdditions.Clear();
_removals.Clear();
_internalRemovals.Clear();
_additions.Clear();
return result;
}
}
}
@@ -0,0 +1,58 @@
/*
* 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;
namespace QuantConnect.Data.UniverseSelection
{
/// <summary>
/// Provides a univese decoration that replaces the implementation of <see cref="SelectSymbols"/>
/// </summary>
public class SelectSymbolsUniverseDecorator : UniverseDecorator
{
private readonly SelectSymbolsDelegate _selectSymbols;
/// <summary>
/// Delegate type for the <see cref="SelectSymbols"/> method
/// </summary>
/// <param name="utcTime">The current utc frontier time</param>
/// <param name="data">The universe selection data</param>
/// <returns>The symbols selected by the universe</returns>
public delegate IEnumerable<Symbol> SelectSymbolsDelegate(DateTime utcTime, BaseDataCollection data);
/// <summary>
/// Initializes a new instance of the <see cref="SelectSymbolsUniverseDecorator"/> class
/// </summary>
/// <param name="universe">The universe to be decorated</param>
/// <param name="selectSymbols">The new implementation of <see cref="SelectSymbols"/></param>
public SelectSymbolsUniverseDecorator(Universe universe, SelectSymbolsDelegate selectSymbols)
: base(universe)
{
_selectSymbols = selectSymbols;
}
/// <summary>
/// Performs universe selection using the data specified
/// </summary>
/// <param name="utcTime">The current utc time</param>
/// <param name="data">The symbols to remain in the universe</param>
/// <returns>The data that passes the filter</returns>
public override IEnumerable<Symbol> SelectSymbols(DateTime utcTime, BaseDataCollection data)
{
return _selectSymbols(utcTime, data);
}
}
}
@@ -0,0 +1,101 @@
/*
* 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 QuantConnect.Securities;
namespace QuantConnect.Data.UniverseSelection
{
/// <summary>
/// Defines the parameters required to add a subscription to a data feed.
/// </summary>
public class SubscriptionRequest : BaseDataRequest
{
/// <summary>
/// Gets true if the subscription is a universe
/// </summary>
public bool IsUniverseSubscription { get; }
/// <summary>
/// Gets the universe this subscription resides in
/// </summary>
public Universe Universe { get; }
/// <summary>
/// Gets the security. This is the destination of data for non-internal subscriptions.
/// </summary>
public Security Security { get; }
/// <summary>
/// Gets the subscription configuration. This defines how/where to read the data.
/// </summary>
public SubscriptionDataConfig Configuration { get; }
/// <summary>
/// Gets the tradable days specified by this request, in the security's data time zone
/// </summary>
public override IEnumerable<DateTime> TradableDaysInDataTimeZone => Time.EachTradeableDayInTimeZone(ExchangeHours,
StartTimeLocal,
EndTimeLocal,
Configuration.DataTimeZone,
Configuration.ExtendedMarketHours);
/// <summary>
/// Initializes a new instance of the <see cref="SubscriptionRequest"/> class
/// </summary>
public SubscriptionRequest(bool isUniverseSubscription,
Universe universe,
Security security,
SubscriptionDataConfig configuration,
DateTime startTimeUtc,
DateTime endTimeUtc)
: base(startTimeUtc, endTimeUtc, security.Exchange.Hours, configuration.TickType, configuration.IsCustomData, configuration.Type)
{
IsUniverseSubscription = isUniverseSubscription;
Universe = universe;
Security = security;
Configuration = configuration;
// open interest data comes in once a day before market open,
// make the subscription start from midnight and use always open exchange
if (Configuration.TickType == TickType.OpenInterest)
{
StartTimeUtc = StartTimeUtc.ConvertFromUtc(ExchangeHours.TimeZone).Date.ConvertToUtc(ExchangeHours.TimeZone);
}
}
/// <summary>
/// Initializes a new instance of the <see cref="SubscriptionRequest"/> class
/// </summary>
public SubscriptionRequest(SubscriptionRequest template,
bool? isUniverseSubscription = null,
Universe universe = null,
Security security = null,
SubscriptionDataConfig configuration = null,
DateTime? startTimeUtc = null,
DateTime? endTimeUtc = null
)
: this(isUniverseSubscription ?? template.IsUniverseSubscription,
universe ?? template.Universe,
security ?? template.Security,
configuration ?? template.Configuration,
startTimeUtc ?? template.StartTimeUtc,
endTimeUtc ?? template.EndTimeUtc
)
{
}
}
}
+469
View File
@@ -0,0 +1,469 @@
/*
* 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;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using QuantConnect.Interfaces;
using QuantConnect.Securities;
using QuantConnect.Util;
namespace QuantConnect.Data.UniverseSelection
{
/// <summary>
/// Provides a base class for all universes to derive from.
/// </summary>
public abstract class Universe : IDisposable
{
/// <summary>
/// Used to round the members time in universe <see cref="CanRemoveMember"/>, this is
/// done because we can not guarantee exact selection time in live mode, see GH issue 3287
/// </summary>
private TimeSpan? _minimumTimeInUniverseRoundingInterval;
/// <summary>
/// Gets a value indicating that no change to the universe should be made
/// </summary>
public static readonly UnchangedUniverse Unchanged = UnchangedUniverse.Instance;
private HashSet<Symbol> _previousSelections;
/// <summary>
/// Gets the internal security collection used to define membership in this universe
/// </summary>
public virtual ConcurrentDictionary<Symbol, Member> Securities
{
get;
private set;
}
/// <summary>
/// The currently selected symbol set
/// </summary>
/// <remarks>This set might be different than <see cref="Securities"/> which might hold members that are no longer selected
/// but have not been removed yet, this can be because they have some open position, orders, haven't completed the minimum time in universe</remarks>
public HashSet<Symbol> Selected
{
get; set;
} = [];
/// <summary>
/// True if this universe filter can run async in the data stack
/// </summary>
public virtual bool Asynchronous
{
get
{
if (UniverseSettings.Asynchronous.HasValue)
{
return UniverseSettings.Asynchronous.Value;
}
return false;
}
set
{
UniverseSettings.Asynchronous = value;
}
}
/// <summary>
/// Event fired when the universe selection has changed
/// </summary>
public event EventHandler SelectionChanged;
/// <summary>
/// Gets the security type of this universe
/// </summary>
public SecurityType SecurityType => Configuration.SecurityType;
/// <summary>
/// Gets the market of this universe
/// </summary>
public string Market => Configuration.Market;
/// <summary>
/// Gets the symbol of this universe
/// </summary>
public Symbol Symbol => Configuration.Symbol;
/// <summary>
/// Gets the data type of this universe
/// </summary>
public Type DataType => Configuration.Type;
/// <summary>
/// Flag indicating if disposal of this universe has been requested
/// </summary>
public virtual bool DisposeRequested
{
get;
protected set;
}
/// <summary>
/// Gets the settings used for subscriptions added for this universe
/// </summary>
public virtual UniverseSettings UniverseSettings
{
get; set;
}
/// <summary>
/// Gets the configuration used to get universe data
/// </summary>
public virtual SubscriptionDataConfig Configuration
{
get; private set;
}
/// <summary>
/// Gets the current listing of members in this universe. Modifications
/// to this dictionary do not change universe membership.
/// </summary>
public Dictionary<Symbol, Security> Members
{
get { return Securities.Select(x => x.Value.Security).ToDictionary(x => x.Symbol); }
}
/// <summary>
/// Initializes a new instance of the <see cref="Universe"/> class
/// </summary>
/// <param name="config">The configuration used to source data for this universe</param>
protected Universe(SubscriptionDataConfig config)
{
_previousSelections = new HashSet<Symbol>();
Securities = new ConcurrentDictionary<Symbol, Member>();
Configuration = config;
}
/// <summary>
/// Determines whether or not the specified security can be removed from
/// this universe. This is useful to prevent securities from being taken
/// out of a universe before the algorithm has had enough time to make
/// decisions on the security
/// </summary>
/// <param name="utcTime">The current utc time</param>
/// <param name="security">The security to check if its ok to remove</param>
/// <returns>True if we can remove the security, false otherwise</returns>
public virtual bool CanRemoveMember(DateTime utcTime, Security security)
{
// can always remove securities after dispose requested
if (DisposeRequested)
{
return true;
}
// can always remove delisted securities from the universe
if (security.IsDelisted)
{
return true;
}
Member member;
if (Securities.TryGetValue(security.Symbol, out member))
{
if (_minimumTimeInUniverseRoundingInterval == null)
{
// lets set _minimumTimeInUniverseRoundingInterval once
_minimumTimeInUniverseRoundingInterval = UniverseSettings.MinimumTimeInUniverse;
AdjustMinimumTimeInUniverseRoundingInterval();
}
var timeInUniverse = utcTime - member.Added;
if (timeInUniverse.Round(_minimumTimeInUniverseRoundingInterval.Value)
>= UniverseSettings.MinimumTimeInUniverse)
{
return true;
}
}
return false;
}
/// <summary>
/// Performs universe selection using the data specified
/// </summary>
/// <param name="utcTime">The current utc time</param>
/// <param name="data">The symbols to remain in the universe</param>
/// <returns>The data that passes the filter</returns>
public IEnumerable<Symbol> PerformSelection(DateTime utcTime, BaseDataCollection data)
{
// select empty set of symbols after dispose requested
if (DisposeRequested)
{
OnSelectionChanged();
return Enumerable.Empty<Symbol>();
}
var selections = data.FilteredContracts;
if (selections == null)
{
// only trigger selection if it hasn't already been run
var result = SelectSymbols(utcTime, data);
if (ReferenceEquals(result, Unchanged))
{
data.FilteredContracts = _previousSelections;
return Unchanged;
}
selections = result.ToHashSet();
data.FilteredContracts = selections;
}
var hasDiffs = _previousSelections.AreDifferent(selections);
_previousSelections = selections;
if (!hasDiffs)
{
return Unchanged;
}
OnSelectionChanged(selections);
return selections;
}
/// <summary>
/// Performs universe selection using the data specified
/// </summary>
/// <param name="utcTime">The current utc time</param>
/// <param name="data">The symbols to remain in the universe</param>
/// <returns>The data that passes the filter</returns>
public abstract IEnumerable<Symbol> SelectSymbols(DateTime utcTime, BaseDataCollection data);
/// <summary>
/// Creates and configures a security for the specified symbol
/// </summary>
/// <param name="symbol">The symbol of the security to be created</param>
/// <param name="algorithm">The algorithm instance</param>
/// <param name="marketHoursDatabase">The market hours database</param>
/// <param name="symbolPropertiesDatabase">The symbol properties database</param>
/// <returns>The newly initialized security object</returns>
/// <obsolete>The CreateSecurity won't be called</obsolete>
[Obsolete("CreateSecurity is obsolete and will not be called. The system will create the required Securities based on selected symbols")]
public virtual Security CreateSecurity(Symbol symbol, IAlgorithm algorithm, MarketHoursDatabase marketHoursDatabase, SymbolPropertiesDatabase symbolPropertiesDatabase)
{
throw new InvalidOperationException("CreateSecurity is obsolete and should not be called." +
"The system will create the required Securities based on selected symbols");
}
/// <summary>
/// Gets the subscription requests to be added for the specified security
/// </summary>
/// <param name="security">The security to get subscriptions for</param>
/// <param name="currentTimeUtc">The current time in utc. This is the frontier time of the algorithm</param>
/// <param name="maximumEndTimeUtc">The max end time</param>
/// <returns>All subscriptions required by this security</returns>
[Obsolete("This overload is obsolete and will not be called. It was not capable of creating new SubscriptionDataConfig due to lack of information")]
public virtual IEnumerable<SubscriptionRequest> GetSubscriptionRequests(Security security, DateTime currentTimeUtc, DateTime maximumEndTimeUtc)
{
throw new InvalidOperationException("This overload is obsolete and should not be called." +
"It was not capable of creating new SubscriptionDataConfig due to lack of information");
}
/// <summary>
/// Gets the subscription requests to be added for the specified security
/// </summary>
/// <param name="security">The security to get subscriptions for</param>
/// <param name="currentTimeUtc">The current time in utc. This is the frontier time of the algorithm</param>
/// <param name="maximumEndTimeUtc">The max end time</param>
/// <param name="subscriptionService">Instance which implements <see cref="ISubscriptionDataConfigService"/> interface</param>
/// <returns>All subscriptions required by this security</returns>
public virtual IEnumerable<SubscriptionRequest> GetSubscriptionRequests(Security security,
DateTime currentTimeUtc,
DateTime maximumEndTimeUtc,
ISubscriptionDataConfigService subscriptionService)
{
var result = subscriptionService.Add(security.Symbol,
UniverseSettings.Resolution,
UniverseSettings.FillForward,
UniverseSettings.ExtendedMarketHours,
dataNormalizationMode: UniverseSettings.DataNormalizationMode,
subscriptionDataTypes: UniverseSettings.SubscriptionDataTypes,
dataMappingMode: UniverseSettings.DataMappingMode,
contractDepthOffset: (uint)Math.Abs(UniverseSettings.ContractDepthOffset));
return result.Select(config => new SubscriptionRequest(isUniverseSubscription: false,
universe: this,
security: security,
configuration: config,
startTimeUtc: currentTimeUtc,
endTimeUtc: maximumEndTimeUtc));
}
/// <summary>
/// Determines whether or not the specified symbol is currently a member of this universe
/// </summary>
/// <param name="symbol">The symbol whose membership is to be checked</param>
/// <returns>True if the specified symbol is part of this universe, false otherwise</returns>
public bool ContainsMember(Symbol symbol)
{
return Securities.ContainsKey(symbol);
}
/// <summary>
/// Adds the specified security to this universe
/// </summary>
/// <param name="utcTime">The current utc date time</param>
/// <param name="security">The security to be added</param>
/// <param name="isInternal">True if internal member</param>
/// <returns>True if the security was successfully added,
/// false if the security was already in the universe</returns>
internal virtual bool AddMember(DateTime utcTime, Security security, bool isInternal)
{
// never add members to disposed universes
if (DisposeRequested)
{
return false;
}
if (security.IsDelisted)
{
return false;
}
return Securities.TryAdd(security.Symbol, new Member(utcTime, security, isInternal));
}
/// <summary>
/// Tries to remove the specified security from the universe. This
/// will first check to verify that we can remove the security by
/// calling the <see cref="CanRemoveMember"/> function.
/// </summary>
/// <param name="utcTime">The current utc time</param>
/// <param name="security">The security to be removed</param>
/// <returns>True if the security was successfully removed, false if
/// we're not allowed to remove or if the security didn't exist</returns>
internal virtual bool RemoveMember(DateTime utcTime, Security security)
{
if (CanRemoveMember(utcTime, security))
{
Member member;
return Securities.TryRemove(security.Symbol, out member);
}
return false;
}
/// <summary>
/// Marks this universe as disposed and ready to remove all child subscriptions
/// </summary>
public virtual void Dispose()
{
DisposeRequested = true;
}
/// <summary>
/// Event invocator for the <see cref="SelectionChanged"/> event
/// </summary>
/// <param name="selection">The current universe selection</param>
protected void OnSelectionChanged(HashSet<Symbol> selection = null)
{
SelectionChanged?.Invoke(this, new SelectionEventArgs(selection ?? new HashSet<Symbol>()));
}
/// <summary>
/// Provides a value to indicate that no changes should be made to the universe.
/// This value is intended to be returned by reference via <see cref="Universe.SelectSymbols"/>
/// </summary>
public sealed class UnchangedUniverse : IEnumerable<string>, IEnumerable<Symbol>
{
/// <summary>
/// Read-only instance of the <see cref="UnchangedUniverse"/> value
/// </summary>
public static readonly UnchangedUniverse Instance = new UnchangedUniverse();
private UnchangedUniverse() { }
IEnumerator<Symbol> IEnumerable<Symbol>.GetEnumerator() { yield break; }
IEnumerator<string> IEnumerable<string>.GetEnumerator() { yield break; }
IEnumerator IEnumerable.GetEnumerator() { yield break; }
}
/// <summary>
/// Will adjust the <see cref="_minimumTimeInUniverseRoundingInterval"/>
/// so rounding is performed as expected
/// </summary>
private void AdjustMinimumTimeInUniverseRoundingInterval()
{
if (_minimumTimeInUniverseRoundingInterval >= Time.OneDay)
{
_minimumTimeInUniverseRoundingInterval = Time.OneDay;
}
else if (_minimumTimeInUniverseRoundingInterval >= Time.OneHour)
{
_minimumTimeInUniverseRoundingInterval = Time.OneHour;
}
else if (_minimumTimeInUniverseRoundingInterval >= Time.OneMinute)
{
_minimumTimeInUniverseRoundingInterval = Time.OneMinute;
}
else if (_minimumTimeInUniverseRoundingInterval >= Time.OneSecond)
{
_minimumTimeInUniverseRoundingInterval = Time.OneSecond;
}
}
/// <summary>
/// Member of the Universe
/// </summary>
public sealed class Member
{
/// <summary>
/// DateTime when added
/// </summary>
public DateTime Added { get; init; }
/// <summary>
/// The security that was added
/// </summary>
public Security Security { get; init; }
/// <summary>
/// True if the security was added as internal by this universe
/// </summary>
public bool IsInternal { get; init; }
/// <summary>
/// Initialize a new member for the universe
/// </summary>
/// <param name="added">DateTime added</param>
/// <param name="security">Security to add</param>
/// <param name="isInternal">True if internal member</param>
public Member(DateTime added, Security security, bool isInternal)
{
Added = added;
Security = security;
IsInternal = isInternal;
}
}
/// <summary>
/// Event fired when the universe selection changes
/// </summary>
public class SelectionEventArgs : EventArgs
{
/// <summary>
/// The current universe selection
/// </summary>
public HashSet<Symbol> CurrentSelection { get; }
/// <summary>
/// Creates a new instance
/// </summary>
public SelectionEventArgs(HashSet<Symbol> currentSelection)
{
CurrentSelection = currentSelection;
}
}
}
}
@@ -0,0 +1,156 @@
/*
* 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.Concurrent;
using System.Collections.Generic;
using System.Linq;
using QuantConnect.Interfaces;
using QuantConnect.Securities;
namespace QuantConnect.Data.UniverseSelection
{
/// <summary>
/// Provides an implementation of <see cref="UniverseSelection.Universe"/> that redirects all calls to a
/// wrapped (or decorated) universe. This provides scaffolding for other decorators who
/// only need to override one or two methods.
/// </summary>
/// <remarks> Requires special handling due to `this != this.Universe`
/// <see cref="GetSubscriptionRequests(Security, DateTime, DateTime, ISubscriptionDataConfigService)"/></remarks>
public abstract class UniverseDecorator : Universe
{
/// <summary>
/// The decorated universe instance
/// </summary>
protected Universe Universe { get; init; }
/// <summary>
/// Gets the settings used for subscriptions added for this universe
/// </summary>
public override UniverseSettings UniverseSettings
{
get
{
return Universe.UniverseSettings;
}
set
{
Universe.UniverseSettings = value;
}
}
/// <summary>
/// Gets the internal security collection used to define membership in this universe
/// </summary>
public override ConcurrentDictionary<Symbol, Member> Securities
{
get { return Universe.Securities; }
}
/// <summary>
/// Initializes a new instance of the <see cref="UniverseDecorator"/> class
/// </summary>
/// <param name="universe">The decorated universe. All overridable methods delegate to this instance.</param>
protected UniverseDecorator(Universe universe)
: base(universe.Configuration)
{
Universe = universe;
}
/// <summary>
/// Gets the subscription requests to be added for the specified security
/// </summary>
/// <param name="security">The security to get subscriptions for</param>
/// <param name="currentTimeUtc">The current time in utc. This is the frontier time of the algorithm</param>
/// <param name="maximumEndTimeUtc">The max end time</param>
/// <returns>All subscriptions required by this security</returns>
[Obsolete("This overload is obsolete and will not be called. It was not capable of creating new SubscriptionDataConfig due to lack of information")]
public override IEnumerable<SubscriptionRequest> GetSubscriptionRequests(Security security, DateTime currentTimeUtc, DateTime maximumEndTimeUtc)
{
return Universe.GetSubscriptionRequests(security, currentTimeUtc, maximumEndTimeUtc);
}
/// <summary>
/// Gets the subscription requests to be added for the specified security
/// </summary>
/// <param name="security">The security to get subscriptions for</param>
/// <param name="currentTimeUtc">The current time in utc. This is the frontier time of the algorithm</param>
/// <param name="maximumEndTimeUtc">The max end time</param>
/// <param name="subscriptionService">Instance which implements <see cref="ISubscriptionDataConfigService"/> interface</param>
/// <returns>All subscriptions required by this security</returns>
public override IEnumerable<SubscriptionRequest> GetSubscriptionRequests(Security security,
DateTime currentTimeUtc,
DateTime maximumEndTimeUtc,
ISubscriptionDataConfigService subscriptionService)
{
var result = Universe.GetSubscriptionRequests(
security,
currentTimeUtc,
maximumEndTimeUtc,
subscriptionService).ToList();
for (var i = 0; i < result.Count; i++)
{
// This is required because the system tracks which universe
// is requesting to add or remove each SubscriptionRequest.
// UniverseDecorator is a special case because
// `this != UniverseDecorator.Universe`
result[i] =
new SubscriptionRequest(result[i], universe: this);
}
return result;
}
/// <summary>
/// Determines whether or not the specified security can be removed from
/// this universe. This is useful to prevent securities from being taken
/// out of a universe before the algorithm has had enough time to make
/// decisions on the security
/// </summary>
/// <param name="utcTime">The current utc time</param>
/// <param name="security">The security to check if its ok to remove</param>
/// <returns>True if we can remove the security, false otherwise</returns>
public override bool CanRemoveMember(DateTime utcTime, Security security)
{
return Universe.CanRemoveMember(utcTime, security);
}
/// <summary>
/// Creates and configures a security for the specified symbol
/// </summary>
/// <param name="symbol">The symbol of the security to be created</param>
/// <param name="algorithm">The algorithm instance</param>
/// <param name="marketHoursDatabase">The market hours database</param>
/// <param name="symbolPropertiesDatabase">The symbol properties database</param>
/// <returns>The newly initialized security object</returns>
[Obsolete("CreateSecurity is obsolete and will not be called. The system will create the required Securities based on selected symbols")]
public override Security CreateSecurity(Symbol symbol, IAlgorithm algorithm, MarketHoursDatabase marketHoursDatabase, SymbolPropertiesDatabase symbolPropertiesDatabase)
{
return Universe.CreateSecurity(symbol, algorithm, marketHoursDatabase, symbolPropertiesDatabase);
}
/// <summary>
/// Performs universe selection using the data specified
/// </summary>
/// <param name="utcTime">The current utc time</param>
/// <param name="data">The symbols to remain in the universe</param>
/// <returns>The data that passes the filter</returns>
public override IEnumerable<Symbol> SelectSymbols(DateTime utcTime, BaseDataCollection data)
{
return Universe.SelectSymbols(utcTime, data);
}
}
}
@@ -0,0 +1,226 @@
/*
* 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.IO;
using System.Linq;
using QuantConnect.Logging;
using QuantConnect.Data.Market;
using System.Collections.Generic;
namespace QuantConnect.Data.UniverseSelection
{
/// <summary>
/// Provides extension methods for the <see cref="Universe"/> class
/// </summary>
public static class UniverseExtensions
{
/// <summary>
/// Creates a new universe that logically is the result of wiring the two universes together such that
/// the first will produce subscriptions for the second and the second will only select on data that has
/// passed the first.
///
/// NOTE: The <paramref name="first"/> and <paramref name="second"/> universe instances provided
/// to this method should not be manually added to the algorithm.
/// </summary>
/// <param name="first">The first universe in this 'chain'</param>
/// <param name="second">The second universe in this 'chain'</param>
/// <param name="configurationPerSymbol">True if each symbol as its own configuration, false otherwise</param>
/// <returns>A new universe that can be added to the algorithm that represents invoking the first universe
/// and then the second universe using the outputs of the first. </returns>
public static Universe ChainedTo(this Universe first, Universe second, bool configurationPerSymbol)
{
var prefilteredSecond = second.PrefilterUsing(first);
return new GetSubscriptionRequestsUniverseDecorator(first, (security, currentTimeUtc, maximumEndTimeUtc) =>
{
return first.GetSubscriptionRequests(security, currentTimeUtc, maximumEndTimeUtc).Select(request => new SubscriptionRequest(
template: request,
isUniverseSubscription: true,
universe: prefilteredSecond,
security: security,
configuration: configurationPerSymbol ? new SubscriptionDataConfig(prefilteredSecond.Configuration, symbol: security.Symbol) : prefilteredSecond.Configuration,
startTimeUtc: currentTimeUtc - prefilteredSecond.Configuration.Resolution.ToTimeSpan(),
endTimeUtc: currentTimeUtc.AddSeconds(-1)
));
});
}
/// <summary>
/// Creates a new universe that restricts the universe selection data to symbols that passed the
/// first universe's selection critera
///
/// NOTE: The <paramref name="second"/> universe instance provided to this method should not be manually
/// added to the algorithm. The <paramref name="first"/> should still be manually (assuming no other changes).
/// </summary>
/// <param name="second">The universe to be filtere</param>
/// <param name="first">The universe providing the set of symbols used for filtered</param>
/// <returns>A new universe that can be added to the algorithm that represents invoking the second
/// using the selections from the first as a filter.</returns>
public static Universe PrefilterUsing(this Universe second, Universe first)
{
return new SelectSymbolsUniverseDecorator(second, (utcTime, data) =>
{
var clone = (BaseDataCollection)data.Clone();
clone.Data = clone.Data.Where(d => first.ContainsMember(d.Symbol)).ToList();
return second.SelectSymbols(utcTime, clone);
});
}
/// <summary>
/// Creates a universe symbol
/// </summary>
/// <param name="securityType">The security</param>
/// <param name="market">The market</param>
/// <param name="ticker">The Universe ticker</param>
/// <returns>A symbol for user defined universe of the specified security type and market</returns>
public static Symbol CreateSymbol(SecurityType securityType, string market, string ticker)
{
// TODO looks like we can just replace this for Symbol.Create?
SecurityIdentifier sid;
switch (securityType)
{
case SecurityType.Base:
sid = SecurityIdentifier.GenerateBase(null, ticker, market);
break;
case SecurityType.Equity:
sid = SecurityIdentifier.GenerateEquity(SecurityIdentifier.DefaultDate, ticker, market);
break;
case SecurityType.Option:
var underlying = SecurityIdentifier.GenerateEquity(SecurityIdentifier.DefaultDate, ticker, market);
sid = SecurityIdentifier.GenerateOption(SecurityIdentifier.DefaultDate, underlying, market, 0, 0, 0);
break;
case SecurityType.FutureOption:
var underlyingFuture = SecurityIdentifier.GenerateFuture(SecurityIdentifier.DefaultDate, ticker, market);
sid = SecurityIdentifier.GenerateOption(SecurityIdentifier.DefaultDate, underlyingFuture, market, 0, 0, 0);
break;
case SecurityType.IndexOption:
var underlyingIndex = SecurityIdentifier.GenerateIndex(ticker, market);
sid = SecurityIdentifier.GenerateOption(SecurityIdentifier.DefaultDate, underlyingIndex, market, 0, 0, OptionStyle.European);
break;
case SecurityType.Forex:
sid = SecurityIdentifier.GenerateForex(ticker, market);
break;
case SecurityType.Cfd:
sid = SecurityIdentifier.GenerateCfd(ticker, market);
break;
case SecurityType.Index:
sid = SecurityIdentifier.GenerateIndex(ticker, market);
break;
case SecurityType.Future:
sid = SecurityIdentifier.GenerateFuture(SecurityIdentifier.DefaultDate, ticker, market);
break;
case SecurityType.Crypto:
sid = SecurityIdentifier.GenerateCrypto(ticker, market);
break;
case SecurityType.CryptoFuture:
sid = SecurityIdentifier.GenerateCryptoFuture(SecurityIdentifier.DefaultDate, ticker, market);
break;
case SecurityType.Commodity:
default:
throw new NotImplementedException($"The specified security type is not implemented yet: {securityType}");
}
return new Symbol(sid, ticker);
}
/// <summary>
/// Processes the universe download based on parameters.
/// </summary>
/// <param name="dataDownloader">The data downloader instance.</param>
/// <param name="universeDownloadParameters">The parameters for universe downloading.</param>
public static void RunUniverseDownloader(IDataDownloader dataDownloader, DataUniverseDownloaderGetParameters universeDownloadParameters)
{
var universeDataBySymbol = new Dictionary<Symbol, DerivativeUniverseData>();
foreach (var (processingDate, universeDownloaderParameters) in universeDownloadParameters.CreateDataDownloaderGetParameters())
{
universeDataBySymbol.Clear();
foreach (var downloaderParameters in universeDownloaderParameters)
{
Log.Debug($"{nameof(UniverseExtensions)}.{nameof(RunUniverseDownloader)}:Generating universe for {downloaderParameters.Symbol} on {processingDate:yyyy/MM/dd}");
var historyData = dataDownloader.Get(downloaderParameters);
if (historyData == null)
{
Log.Debug($"{nameof(UniverseExtensions)}.{nameof(RunUniverseDownloader)}: No data available for the following parameters: {universeDownloadParameters}");
continue;
}
foreach (var baseData in historyData)
{
switch (baseData)
{
case TradeBar tradeBar:
if (!universeDataBySymbol.TryAdd(tradeBar.Symbol, new(tradeBar)))
{
universeDataBySymbol[tradeBar.Symbol].UpdateByTradeBar(tradeBar);
}
break;
case OpenInterest openInterest:
if (!universeDataBySymbol.TryAdd(openInterest.Symbol, new(openInterest)))
{
universeDataBySymbol[openInterest.Symbol].UpdateByOpenInterest(openInterest);
}
break;
case QuoteBar quoteBar:
if (!universeDataBySymbol.TryAdd(quoteBar.Symbol, new(quoteBar)))
{
universeDataBySymbol[quoteBar.Symbol].UpdateByQuoteBar(quoteBar);
}
break;
default:
throw new InvalidOperationException($"{nameof(UniverseExtensions)}.{nameof(RunUniverseDownloader)}: Unexpected data type encountered.");
}
}
}
if (universeDataBySymbol.Count == 0)
{
continue;
}
using var writer = new StreamWriter(universeDownloadParameters.GetUniverseFileName(processingDate));
writer.WriteLine($"#{OptionUniverse.CsvHeader}");
// Write option data, sorted by contract type (Call/Put), strike price, expiration date, and then by full ID
foreach (var universeData in universeDataBySymbol
.OrderBy(x => x.Key.Underlying != null)
.ThenBy(d => d.Key.SecurityType.IsOption() ? d.Key.ID.OptionRight : 0)
.ThenBy(d => d.Key.SecurityType.IsOption() ? d.Key.ID.StrikePrice : 0)
.ThenBy(d => d.Key.ID.Date)
.ThenBy(d => d.Key.ID))
{
writer.WriteLine(universeData.Value.ToCsv());
}
Log.Trace($"{nameof(UniverseExtensions)}.{nameof(RunUniverseDownloader)}:Generated for {universeDownloadParameters.Symbol} on {processingDate:yyyy/MM/dd} with {universeDataBySymbol.Count} entries");
}
}
}
}
@@ -0,0 +1,122 @@
/*
* 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.Concurrent;
using System.Collections.Generic;
using Python.Runtime;
using QuantConnect.Interfaces;
using QuantConnect.Python;
using QuantConnect.Securities;
namespace QuantConnect.Data.UniverseSelection
{
/// <summary>
/// Provides an implementation of <see cref="Universe"/> that wraps a <see cref="PyObject"/> object
/// </summary>
public class UniversePythonWrapper : Universe
{
private readonly BasePythonWrapper<Universe> _model;
/// <summary>
/// Gets the settings used for subscriptions added for this universe
/// </summary>
public override UniverseSettings UniverseSettings
{
get
{
return _model.GetProperty<UniverseSettings>(nameof(UniverseSettings));
}
set
{
_model.SetProperty(nameof(UniverseSettings), value);
}
}
/// <summary>
/// Flag indicating if disposal of this universe has been requested
/// </summary>
public override bool DisposeRequested
{
get
{
return _model.GetProperty<bool>(nameof(DisposeRequested));
}
protected set
{
_model.SetProperty(nameof(DisposeRequested), value);
}
}
/// <summary>
/// Gets the configuration used to get universe data
/// </summary>
public override SubscriptionDataConfig Configuration
{
get
{
return _model.GetProperty<SubscriptionDataConfig>(nameof(Configuration));
}
}
/// <summary>
/// Gets the internal security collection used to define membership in this universe
/// </summary>
public override ConcurrentDictionary<Symbol, Member> Securities
{
get
{
return _model.GetProperty<ConcurrentDictionary<Symbol, Member>>(nameof(Securities));
}
}
/// <summary>
/// Initializes a new instance of the <see cref="UniversePythonWrapper"/> class
/// </summary>
public UniversePythonWrapper(PyObject universe) : base(null)
{
_model = new BasePythonWrapper<Universe>(universe, false);
}
/// <summary>
/// Performs universe selection using the data specified
/// </summary>
/// <param name="utcTime">The current utc time</param>
/// <param name="data">The symbols to remain in the universe</param>
/// <returns>The data that passes the filter</returns>
public override IEnumerable<Symbol> SelectSymbols(DateTime utcTime, BaseDataCollection data)
{
return _model.InvokeMethodAndEnumerate<Symbol>(nameof(SelectSymbols), utcTime, data);
}
/// <summary>
/// Gets the subscription requests to be added for the specified security
/// </summary>
/// <param name="security">The security to get subscriptions for</param>
/// <param name="currentTimeUtc">The current time in utc. This is the frontier time of the algorithm</param>
/// <param name="maximumEndTimeUtc">The max end time</param>
/// <param name="subscriptionService">Instance which implements <see cref="ISubscriptionDataConfigService"/> interface</param>
/// <returns>All subscriptions required by this security</returns>
public override IEnumerable<SubscriptionRequest> GetSubscriptionRequests(Security security, DateTime currentTimeUtc, DateTime maximumEndTimeUtc,
ISubscriptionDataConfigService subscriptionService)
{
var requests = _model.InvokeMethodAndEnumerate<SubscriptionRequest>(nameof(GetSubscriptionRequests), security, currentTimeUtc,
maximumEndTimeUtc, subscriptionService);
foreach (var subscriptionRequest in requests)
{
yield return new SubscriptionRequest(subscriptionRequest, universe: this);
}
}
}
}
@@ -0,0 +1,140 @@
/*
* 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 QuantConnect.Scheduling;
using System.Collections.Generic;
namespace QuantConnect.Data.UniverseSelection
{
/// <summary>
/// Defines settings required when adding a subscription
/// </summary>
public class UniverseSettings
{
/// <summary>
/// The resolution to be used
/// </summary>
public Resolution Resolution { get; set; }
/// <summary>
/// The leverage to be used
/// </summary>
public decimal Leverage { get; set; }
/// <summary>
/// True to fill data forward, false otherwise
/// </summary>
public bool FillForward { get; set; }
/// <summary>
/// If configured, will be used to determine universe selection schedule and filter or skip selection data
/// that does not fit the schedule
/// </summary>
public Schedule Schedule { get; set; }
/// <summary>
/// True to allow extended market hours data, false otherwise
/// </summary>
public bool ExtendedMarketHours { get; set; }
/// <summary>
/// Defines the minimum amount of time a security must be in
/// the universe before being removed.
/// </summary>
/// <remarks>When selection takes place, the actual members time in the universe
/// will be rounded based on this TimeSpan, so that relative small differences do not
/// cause an unexpected behavior <see cref="Universe.CanRemoveMember"/></remarks>
public TimeSpan MinimumTimeInUniverse { get; set; }
/// <summary>
/// Defines how universe data is normalized before being send into the algorithm
/// </summary>
public DataNormalizationMode DataNormalizationMode { get; set; }
/// <summary>
/// Defines how universe data is mapped together
/// </summary>
/// <remarks>This is particular useful when generating continuous futures</remarks>
public DataMappingMode DataMappingMode { get; set; }
/// <summary>
/// The continuous contract desired offset from the current front month.
/// For example, 0 (default) will use the front month, 1 will use the back month contra
/// </summary>
public int ContractDepthOffset { get; set; }
/// <summary>
/// Allows a universe to specify which data types to add for a selected symbol
/// </summary>
public List<Tuple<Type, TickType>> SubscriptionDataTypes { get; set; }
/// <summary>
/// True if universe selection can run asynchronous
/// </summary>
public bool? Asynchronous { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="UniverseSettings"/> class
/// </summary>
/// <param name="resolution">The resolution</param>
/// <param name="leverage">The leverage to be used</param>
/// <param name="fillForward">True to fill data forward, false otherwise</param>
/// <param name="extendedMarketHours">True to allow extended market hours data, false otherwise</param>
/// <param name="minimumTimeInUniverse">Defines the minimum amount of time a security must remain in the universe before being removed</param>
/// <param name="dataNormalizationMode">Defines how universe data is normalized before being send into the algorithm</param>
/// <param name="dataMappingMode">The contract mapping mode to use for the security</param>
/// <param name="contractDepthOffset">The continuous contract desired offset from the current front month.
/// For example, 0 (default) will use the front month, 1 will use the back month contract</param>
/// <param name="asynchronous">True if universe selection can run asynchronous</param>
/// <param name="selectionDateRule">If provided, will be used to determine universe selection schedule</param>
public UniverseSettings(Resolution resolution, decimal leverage, bool fillForward, bool extendedMarketHours, TimeSpan minimumTimeInUniverse, DataNormalizationMode dataNormalizationMode = DataNormalizationMode.Adjusted,
DataMappingMode dataMappingMode = DataMappingMode.OpenInterest, int contractDepthOffset = 0, bool? asynchronous = null, IDateRule selectionDateRule = null)
{
Resolution = resolution;
Leverage = leverage;
FillForward = fillForward;
DataMappingMode = dataMappingMode;
ContractDepthOffset = contractDepthOffset;
ExtendedMarketHours = extendedMarketHours;
MinimumTimeInUniverse = minimumTimeInUniverse;
DataNormalizationMode = dataNormalizationMode;
Asynchronous = asynchronous;
Schedule = new Schedule();
if (selectionDateRule != null)
{
Schedule.On(selectionDateRule);
}
}
/// <summary>
/// Initializes a new instance of the <see cref="UniverseSettings"/> class
/// </summary>
public UniverseSettings(UniverseSettings universeSettings)
{
Resolution = universeSettings.Resolution;
Leverage = universeSettings.Leverage;
FillForward = universeSettings.FillForward;
DataMappingMode = universeSettings.DataMappingMode;
ContractDepthOffset = universeSettings.ContractDepthOffset;
ExtendedMarketHours = universeSettings.ExtendedMarketHours;
MinimumTimeInUniverse = universeSettings.MinimumTimeInUniverse;
DataNormalizationMode = universeSettings.DataNormalizationMode;
SubscriptionDataTypes = universeSettings.SubscriptionDataTypes;
Asynchronous = universeSettings.Asynchronous;
Schedule = universeSettings.Schedule.Clone();
}
}
}
@@ -0,0 +1,292 @@
/*
* 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.Linq;
using QuantConnect.Util;
using QuantConnect.Interfaces;
using QuantConnect.Securities;
using System.Collections.Generic;
using System.Collections.Specialized;
namespace QuantConnect.Data.UniverseSelection
{
/// <summary>
/// Represents the universe defined by the user's algorithm. This is
/// the default universe where manually added securities live by
/// market/security type. They can also be manually generated and
/// can be configured to fire on certain interval and will always
/// return the internal list of symbols.
/// </summary>
public class UserDefinedUniverse : Universe, INotifyCollectionChanged, ITimeTriggeredUniverse
{
private readonly TimeSpan _interval;
private readonly HashSet<SubscriptionDataConfig> _subscriptionDataConfigs = new HashSet<SubscriptionDataConfig>();
private readonly HashSet<Symbol> _symbols = new HashSet<Symbol>();
// `UniverseSelection.RemoveSecurityFromUniverse()` will query us at `GetSubscriptionRequests()` to get the `SubscriptionDataConfig` and remove it from the DF
// and we need to return the config even after the call to `Remove()`
private readonly HashSet<SubscriptionDataConfig> _pendingRemovedConfigs = new HashSet<SubscriptionDataConfig>();
private readonly Func<DateTime, IEnumerable<Symbol>> _selector;
private readonly object _lock = new ();
/// <summary>
/// Event fired when a symbol is added or removed from this universe
/// </summary>
public event NotifyCollectionChangedEventHandler CollectionChanged;
/// <summary>
/// Gets the interval of this user defined universe
/// </summary>
public TimeSpan Interval
{
get { return _interval; }
}
/// <summary>
/// Initializes a new instance of the <see cref="UserDefinedUniverse"/> class
/// </summary>
/// <param name="configuration">The configuration used to resolve the data for universe selection</param>
/// <param name="universeSettings">The settings used for new subscriptions generated by this universe</param>
/// <param name="interval">The interval at which selection should be performed</param>
/// <param name="symbols">The initial set of symbols in this universe</param>
public UserDefinedUniverse(SubscriptionDataConfig configuration, UniverseSettings universeSettings, TimeSpan interval, IEnumerable<Symbol> symbols)
: base(configuration)
{
_interval = interval;
_symbols = symbols.ToHashSet();
UniverseSettings = universeSettings;
// the selector Func will be the union of the provided symbols and the added symbols or subscriptions data configurations
_selector = time => {
lock(_lock)
{
return _subscriptionDataConfigs.Select(x => x.Symbol).Union(_symbols).ToHashSet();
}
};
}
/// <summary>
/// Initializes a new instance of the <see cref="UserDefinedUniverse"/> class
/// </summary>
/// <param name="configuration">The configuration used to resolve the data for universe selection</param>
/// <param name="universeSettings">The settings used for new subscriptions generated by this universe</param>
/// <param name="interval">The interval at which selection should be performed</param>
/// <param name="selector">Universe selection function invoked for each time returned via GetTriggerTimes.
/// The function parameter is a DateTime in the time zone of configuration.ExchangeTimeZone</param>
public UserDefinedUniverse(SubscriptionDataConfig configuration, UniverseSettings universeSettings, TimeSpan interval, Func<DateTime, IEnumerable<string>> selector)
: base(configuration)
{
_interval = interval;
UniverseSettings = universeSettings;
_selector = time =>
{
var selectSymbolsResult = selector(time.ConvertFromUtc(Configuration.ExchangeTimeZone));
// if we received an unchaged then short circuit the symbol creation and return it directly
if (ReferenceEquals(selectSymbolsResult, Unchanged)) return Unchanged;
return selectSymbolsResult.Select(sym => Symbol.Create(sym, Configuration.SecurityType, Configuration.Market));
};
}
/// <summary>
/// Creates a user defined universe symbol
/// </summary>
/// <param name="securityType">The security</param>
/// <param name="market">The market</param>
/// <returns>A symbol for user defined universe of the specified security type and market</returns>
public static Symbol CreateSymbol(SecurityType securityType, string market)
{
var ticker = $"qc-universe-userdefined-{market.ToLowerInvariant()}-{securityType}";
return UniverseExtensions.CreateSymbol(securityType, market, ticker);
}
/// <summary>
/// Adds the specified <see cref="Symbol"/> to this universe
/// </summary>
/// <param name="symbol">The symbol to be added to this universe</param>
/// <returns>True if the symbol was added, false if it was already present</returns>
public bool Add(Symbol symbol)
{
var added = false;
lock (_lock)
{
// let's not call the event having the lock if we don't need too
added = _symbols.Add(symbol);
}
if (added)
{
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, symbol));
return true;
}
return false;
}
/// <summary>
/// Adds the specified <see cref="SubscriptionDataConfig"/> to this universe
/// </summary>
/// <param name="subscriptionDataConfig">The subscription data configuration to be added to this universe</param>
/// <returns>True if the subscriptionDataConfig was added, false if it was already present</returns>
public bool Add(SubscriptionDataConfig subscriptionDataConfig)
{
var added = false;
lock (_lock)
{
// let's not call the event having the lock if we don't need too
added = _subscriptionDataConfigs.Add(subscriptionDataConfig);
}
if (added)
{
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, subscriptionDataConfig.Symbol));
return true;
}
return false;
}
/// <summary>
/// Removes the specified <see cref="Symbol"/> from this universe
/// </summary>
/// <param name="symbol">The symbol to be removed</param>
/// <returns>True if the symbol was removed, false if the symbol was not present</returns>
public bool Remove(Symbol symbol)
{
if (RemoveAndKeepTrack(symbol))
{
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, symbol));
return true;
}
return false;
}
/// <summary>
/// Returns the symbols defined by the user for this universe
/// </summary>
/// <param name="utcTime">The current utc time</param>
/// <param name="data">The symbols to remain in the universe</param>
/// <returns>The data that passes the filter</returns>
public override IEnumerable<Symbol> SelectSymbols(DateTime utcTime, BaseDataCollection data)
{
return _selector(utcTime);
}
/// <summary>
/// Returns an enumerator that defines when this user defined universe will be invoked
/// </summary>
/// <returns>An enumerator of DateTime that defines when this universe will be invoked</returns>
public virtual IEnumerable<DateTime> GetTriggerTimes(DateTime startTimeUtc, DateTime endTimeUtc, MarketHoursDatabase marketHoursDatabase)
{
var exchangeHours = marketHoursDatabase.GetExchangeHours(Configuration);
var localStartTime = startTimeUtc.ConvertFromUtc(exchangeHours.TimeZone);
var localEndTime = endTimeUtc.ConvertFromUtc(exchangeHours.TimeZone);
var first = true;
foreach (var dateTime in LinqExtensions.Range(localStartTime, localEndTime, dt => dt + Interval))
{
if (first)
{
yield return dateTime;
first = false;
}
else if (exchangeHours.IsOpen(dateTime, dateTime + Interval, Configuration.ExtendedMarketHours))
{
yield return dateTime;
}
}
}
/// <summary>
/// Event invocator for the <see cref="CollectionChanged"/> event
/// </summary>
/// <param name="e">The notify collection changed event arguments</param>
protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
CollectionChanged?.Invoke(this, e);
}
/// <summary>
/// Gets the subscription requests to be added for the specified security
/// </summary>
/// <param name="security">The security to get subscriptions for</param>
/// <param name="currentTimeUtc">The current time in utc. This is the frontier time of the algorithm</param>
/// <param name="maximumEndTimeUtc">The max end time</param>
/// <param name="subscriptionService">Instance which implements <see cref="ISubscriptionDataConfigService"/> interface</param>
/// <returns>All subscriptions required by this security</returns>
public override IEnumerable<SubscriptionRequest> GetSubscriptionRequests(Security security,
DateTime currentTimeUtc,
DateTime maximumEndTimeUtc,
ISubscriptionDataConfigService subscriptionService)
{
List<SubscriptionDataConfig> result;
lock (_lock)
{
result = _subscriptionDataConfigs.Where(x => x.Symbol == security.Symbol).ToList();
if (!result.Any())
{
result = _pendingRemovedConfigs.Where(x => x.Symbol == security.Symbol).ToList();
if (result.Any())
{
_pendingRemovedConfigs.RemoveWhere(x => x.Symbol == security.Symbol);
}
else
{
result = base.GetSubscriptionRequests(security, currentTimeUtc, maximumEndTimeUtc, subscriptionService).Select(x => x.Configuration).ToList();
// we create subscription data configs ourselves, add the configs
_subscriptionDataConfigs.UnionWith(result);
}
}
}
return result.Select(config => new SubscriptionRequest(isUniverseSubscription: false,
universe: this,
security: security,
configuration: config,
startTimeUtc: currentTimeUtc,
endTimeUtc: maximumEndTimeUtc));
}
/// <summary>
/// Tries to remove the specified security from the universe.
/// </summary>
/// <param name="utcTime">The current utc time</param>
/// <param name="security">The security to be removed</param>
/// <returns>True if the security was successfully removed, false if
/// we're not allowed to remove or if the security didn't exist</returns>
internal override bool RemoveMember(DateTime utcTime, Security security)
{
if (base.RemoveMember(utcTime, security))
{
RemoveAndKeepTrack(security.Symbol);
return true;
}
return false;
}
private bool RemoveAndKeepTrack(Symbol symbol)
{
lock (_lock)
{
var toBeRemoved = _subscriptionDataConfigs.Where(x => x.Symbol == symbol).ToList();
var removedSymbol = _symbols.Remove(symbol);
if (removedSymbol || toBeRemoved.Any())
{
_subscriptionDataConfigs.RemoveWhere(x => x.Symbol == symbol);
_pendingRemovedConfigs.UnionWith(toBeRemoved);
return true;
}
return false;
}
}
}
}