/* * 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.Drawing; using QuantConnect.Interfaces; using System.Collections.Generic; using QuantConnect.Data.Auxiliary; namespace QuantConnect.Securities.Positions { /// /// Helper method to sample portfolio margin chart /// public static class PortfolioMarginChart { private static string PortfolioMarginTooltip = "{SERIES_NAME}: {VALUE}%"; private static string PortfolioMarginIndexName = "Margin Used (%)"; private static readonly int _portfolioMarginSeriesCount = Configuration.Config.GetInt("portfolio-margin-series-count", 5); /// /// Helper method to add the portfolio margin series into the given chart /// public static void AddSample(Chart portfolioChart, PortfolioState portfolioState, IMapFileProvider mapFileProvider, DateTime currentTime) { if (portfolioState == null || portfolioState.PositionGroups == null) { return; } var topSeries = new HashSet(_portfolioMarginSeriesCount); foreach (var positionGroup in portfolioState.PositionGroups .OrderByDescending(x => x.PortfolioValuePercentage) .DistinctBy(x => GetPositionGroupName(x, mapFileProvider, currentTime)) .Take(_portfolioMarginSeriesCount)) { topSeries.Add(positionGroup.Name); } Series others = null; ChartPoint currentOthers = null; foreach (var positionGroup in portfolioState.PositionGroups) { var name = GetPositionGroupName(positionGroup, mapFileProvider, currentTime); if (topSeries.Contains(name)) { var series = GetOrAddSeries(portfolioChart, name, Color.Empty); series.AddPoint(new ChartPoint(portfolioState.Time, positionGroup.PortfolioValuePercentage * 100)); continue; } others ??= GetOrAddSeries(portfolioChart, "OTHERS", Color.Gray); var value = positionGroup.PortfolioValuePercentage * 100; if (currentOthers != null && currentOthers.Time == portfolioState.Time) { // we aggregate currentOthers.y += value; } else { currentOthers = new ChartPoint(portfolioState.Time, value); others.AddPoint(currentOthers); } } foreach (var series in portfolioChart.Series.Values) { // let's add a null point for the series which have no value for this time var lastPoint = series.Values.LastOrDefault() as ChartPoint; if (lastPoint == null || lastPoint.Time != portfolioState.Time && lastPoint.Y.HasValue) { series.AddPoint(new ChartPoint(portfolioState.Time, null)); } } } private static string GetPositionGroupName(PositionGroupState positionGroup, IMapFileProvider mapFileProvider, DateTime currentTime) { if (positionGroup.Positions.Count == 0) { return string.Empty; } if (string.IsNullOrEmpty(positionGroup.Name)) { positionGroup.Name = string.Join(", ", positionGroup.Positions.Select(x => { if (mapFileProvider == null) { return x.Symbol.Value; } return GetMappedSymbol(mapFileProvider, x.Symbol, currentTime).Value; })); } return positionGroup.Name; } private static Series GetOrAddSeries(Chart portfolioChart, string seriesName, Color color) { if (!portfolioChart.Series.TryGetValue(seriesName, out var series)) { series = portfolioChart.Series[seriesName] = new Series(seriesName, SeriesType.StackedArea, 0, "%") { Color = color, Tooltip = PortfolioMarginTooltip, IndexName = PortfolioMarginIndexName, ScatterMarkerSymbol = ScatterMarkerSymbol.None }; } return (Series)series; } private static Symbol GetMappedSymbol(IMapFileProvider mapFileProvider, Symbol symbol, DateTime referenceTime) { if (symbol.RequiresMapping()) { var mapFileResolver = mapFileProvider.Get(AuxiliaryDataKey.Create(symbol)); if (mapFileResolver.Any()) { var mapFile = mapFileResolver.ResolveMapFile(symbol); if (mapFile.Any()) { symbol = symbol.UpdateMappedSymbol(mapFile.GetMappedSymbol(referenceTime.Date, symbol.Value)); } } } return symbol; } /// /// Helper method to set the tooltip values after we've sampled and filter series with a single value /// public static void RemoveSinglePointSeries(Chart portfolioChart) { // let's remove series which have a single value, since it's a area chart they can't be drawn portfolioChart.Series = portfolioChart.Series.Values .Where(x => { var notNullPointsCount = 0; foreach (var point in x.Values.OfType()) { if (point != null && point.Y.HasValue) { notNullPointsCount++; if (notNullPointsCount > 1) { return true; } } } return notNullPointsCount > 1; }) .ToDictionary(x => x.Name, x => x); } } }