/*
* 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.Algorithm.Framework.Portfolio;
using QuantConnect.Data.UniverseSelection;
namespace QuantConnect.Algorithm.Framework.Risk
{
///
/// Provides an implementation of that limits
/// the sector exposure to the specified percentage
///
public class MaximumSectorExposureRiskManagementModel : RiskManagementModel
{
private readonly decimal _maximumSectorExposure;
private readonly PortfolioTargetCollection _targetsCollection;
///
/// Initializes a new instance of the class
///
/// The maximum exposure for any sector, defaults to 20% sector exposure.
public MaximumSectorExposureRiskManagementModel(
decimal maximumSectorExposure = 0.20m
)
{
if (maximumSectorExposure <= 0)
{
throw new ArgumentOutOfRangeException("MaximumSectorExposureRiskManagementModel: the maximum sector exposure cannot be a non-positive value.");
}
_maximumSectorExposure = maximumSectorExposure;
_targetsCollection = new PortfolioTargetCollection();
}
///
/// Manages the algorithm's risk at each time step
///
/// The algorithm instance
/// The current portfolio targets to be assessed for risk
public override IEnumerable ManageRisk(QCAlgorithm algorithm, IPortfolioTarget[] targets)
{
var maximumSectorExposureValue = algorithm.Portfolio.TotalPortfolioValue * _maximumSectorExposure;
_targetsCollection.AddRange(targets);
// Group the securities by their sector
var groupBySector = algorithm.UniverseManager.ActiveSecurities
.Where(x => x.Value.Fundamentals != null && x.Value.Fundamentals.HasFundamentalData)
.GroupBy(x => x.Value.Fundamentals.CompanyReference.IndustryTemplateCode);
foreach (var securities in groupBySector)
{
// Compute the sector absolute holdings value
// If the construction model has created a target, we consider that
// value to calculate the security absolute holding value
var sectorAbsoluteHoldingsValue = 0m;
foreach (var security in securities)
{
var absoluteHoldingsValue = security.Value.Holdings.AbsoluteHoldingsValue;
IPortfolioTarget target;
if (_targetsCollection.TryGetValue(security.Value.Symbol, out target))
{
absoluteHoldingsValue = security.Value.Price * Math.Abs(target.Quantity) *
security.Value.SymbolProperties.ContractMultiplier *
security.Value.QuoteCurrency.ConversionRate;
}
sectorAbsoluteHoldingsValue += absoluteHoldingsValue;
}
// If the ratio between the sector absolute holdings value and the maximum sector exposure value
// exceeds the unity, it means we need to reduce each security of that sector by that ratio
// Otherwise, it means that the sector exposure is below the maximum and there is nothing to do.
var ratio = sectorAbsoluteHoldingsValue / maximumSectorExposureValue;
if (ratio > 1)
{
foreach (var security in securities)
{
var quantity = security.Value.Holdings.Quantity;
var symbol = security.Value.Symbol;
IPortfolioTarget target;
if (_targetsCollection.TryGetValue(symbol, out target))
{
quantity = target.Quantity;
}
if (quantity != 0)
{
yield return new PortfolioTarget(symbol, quantity / ratio);
}
}
}
}
}
///
/// 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)
{
var anyFundamentalData = algorithm.ActiveSecurities
.Any(kvp => kvp.Value.Fundamentals != null && kvp.Value.Fundamentals.HasFundamentalData);
if (!anyFundamentalData)
{
throw new Exception("MaximumSectorExposureRiskManagementModel.OnSecuritiesChanged: Please select a portfolio selection model that selects securities with fundamental data.");
}
}
}
}