/*
* 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.Linq;
using QuantConnect.Data;
using System.Collections.Generic;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Data.Custom.AlphaStreams;
namespace QuantConnect.Algorithm.Framework.Alphas
{
///
/// Alpha model that will handle adding and removing securities from the algorithm based on the current portfolio of the different alphas
///
public sealed class AlphaStreamAlphaModule : AlphaModel
{
private Dictionary> _symbolsPerAlpha = new Dictionary>();
///
/// Initialize new
///
public AlphaStreamAlphaModule(string name = null)
{
Name = name ?? "AlphaStreamAlphaModule";
}
///
/// Updates this alpha model with the latest data from the algorithm.
/// This is called each time the algorithm receives data for subscribed securities
///
/// The algorithm instance
/// The new data available
/// The new insights generated
public override IEnumerable Update(QCAlgorithm algorithm, Slice data)
{
foreach (var portfolioState in data.Get().Values)
{
ProcessPortfolioState(algorithm, portfolioState);
}
return Enumerable.Empty();
}
///
/// Event fired each time the we add/remove securities from the data feed
///
/// The algorithm instance that experienced the change in securities
/// The security additions and removals from the algorithm
public override void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes)
{
changes.FilterCustomSecurities = false;
foreach (var addedSecurity in changes.AddedSecurities)
{
if (addedSecurity.Symbol.IsCustomDataType())
{
if (!_symbolsPerAlpha.ContainsKey(addedSecurity.Symbol))
{
_symbolsPerAlpha[addedSecurity.Symbol] = new HashSet();
}
// warmup alpha state, adding target securities
ProcessPortfolioState(algorithm, addedSecurity.Cache.GetData());
}
}
algorithm.Log($"OnSecuritiesChanged: {changes}");
}
///
/// Will handle adding and removing securities from the algorithm based on the current portfolio of the different alphas
///
private void ProcessPortfolioState(QCAlgorithm algorithm, AlphaStreamsPortfolioState portfolioState)
{
if (portfolioState == null)
{
return;
}
var alphaId = portfolioState.Symbol;
if (!_symbolsPerAlpha.TryGetValue(alphaId, out var currentSymbols))
{
_symbolsPerAlpha[alphaId] = currentSymbols = new HashSet();
}
var newSymbols = new HashSet(currentSymbols.Count);
foreach (var symbol in portfolioState.PositionGroups?.SelectMany(positionGroup => positionGroup.Positions).Select(state => state.Symbol) ?? Enumerable.Empty())
{
// only add it if it's not used by any alpha (already added check)
if (newSymbols.Add(symbol) && !UsedBySomeAlpha(symbol))
{
algorithm.AddSecurity(symbol,
resolution: algorithm.UniverseSettings.Resolution,
extendedMarketHours: algorithm.UniverseSettings.ExtendedMarketHours);
}
}
_symbolsPerAlpha[alphaId] = newSymbols;
foreach (var symbol in currentSymbols.Where(symbol => !UsedBySomeAlpha(symbol)))
{
algorithm.RemoveSecurity(symbol);
}
}
private bool UsedBySomeAlpha(Symbol asset)
{
return _symbolsPerAlpha.Any(pair => pair.Value.Contains(asset));
}
}
}