/*
* 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.Data;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Interfaces;
namespace QuantConnect.Lean.Engine.DataFeeds
{
///
/// Class in charge of handling Leans internal subscriptions
///
public class InternalSubscriptionManager
{
private readonly Dictionary> _subscriptionRequests;
private readonly Resolution _resolution;
private readonly IAlgorithm _algorithm;
///
/// Event fired when a new internal subscription request is to be added
///
public EventHandler Added { get; set; }
///
/// Event fired when an existing internal subscription should be removed
///
public EventHandler Removed { get; set; }
///
/// Creates a new instances
///
/// The associated algorithm
/// The resolution to use for the internal subscriptions
public InternalSubscriptionManager(IAlgorithm algorithm, Resolution resolution)
{
_algorithm = algorithm;
_resolution = resolution;
_subscriptionRequests = new Dictionary>();
}
///
/// Notifies about a removed subscription request
///
/// The removed subscription request
public void AddedSubscriptionRequest(SubscriptionRequest request)
{
if (PreFilter(request))
{
var lowResolution = request.Configuration.Resolution > Resolution.Minute;
List internalRequests;
var existing = _subscriptionRequests.TryGetValue(request.Configuration.Symbol, out internalRequests);
var alreadyInternal = existing && internalRequests.Any(internalRequest => internalRequest.Configuration.Type == request.Configuration.Type
&& request.Configuration.TickType == internalRequest.Configuration.TickType);
if (lowResolution && !alreadyInternal)
{
// low resolution subscriptions we will add internal Resolution.Minute subscriptions
// if we don't already have this symbol added
var config = new SubscriptionDataConfig(request.Configuration, resolution: _resolution, isInternalFeed: true, extendedHours: true, isFilteredSubscription: false);
var startTimeUtc = request.StartTimeUtc;
if (_algorithm.IsWarmingUp)
{
// during warmup in live trading do not add these internal subscription until the algorithm starts
// these subscription are only added for realtime price in low resolution subscriptions which isn't required for warmup
startTimeUtc = DateTime.UtcNow;
}
var internalRequest = new SubscriptionRequest(false, null, request.Security, config, startTimeUtc, request.EndTimeUtc);
if (existing)
{
_subscriptionRequests[request.Configuration.Symbol].Add(internalRequest);
}
else
{
_subscriptionRequests[request.Configuration.Symbol] = new List{ internalRequest };
}
Added?.Invoke(this, internalRequest);
}
else if (!lowResolution && alreadyInternal)
{
_subscriptionRequests.Remove(request.Configuration.Symbol);
// the user added a higher resolution configuration, we can remove the internal we added
foreach (var subscriptionRequest in internalRequests)
{
Removed?.Invoke(this, subscriptionRequest);
}
}
}
}
///
/// Notifies about an added subscription request
///
/// The added subscription request
public void RemovedSubscriptionRequest(SubscriptionRequest request)
{
if (PreFilter(request) && _subscriptionRequests.ContainsKey(request.Configuration.Symbol))
{
var userConfigs = _algorithm.SubscriptionManager.SubscriptionDataConfigService
.GetSubscriptionDataConfigs(request.Configuration.Symbol).ToList();
if (userConfigs.Count == 0 || userConfigs.Any(config => config.Resolution <= Resolution.Minute))
{
var requests = _subscriptionRequests[request.Configuration.Symbol];
_subscriptionRequests.Remove(request.Configuration.Symbol);
// if we had a config and the user no longer has a config for this symbol we remove the internal subscription
foreach (var subscriptionRequest in requests)
{
Removed?.Invoke(this, subscriptionRequest);
}
}
}
}
///
/// True for for live trading, non internal, non universe subscriptions, non custom data subscriptions
///
private bool PreFilter(SubscriptionRequest request)
{
return _algorithm.LiveMode && !request.Configuration.IsInternalFeed && !request.IsUniverseSubscription && !request.Configuration.IsCustomData;
}
}
}