Lean  $LEAN_TAG$
PortfolioMarginChart.cs
1 /*
2  * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
3  * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  *
15 */
16 
17 using System;
18 using System.Linq;
19 using System.Drawing;
21 using System.Collections.Generic;
23 
25 {
26  /// <summary>
27  /// Helper method to sample portfolio margin chart
28  /// </summary>
29  public static class PortfolioMarginChart
30  {
31  private static string PortfolioMarginTooltip = "{SERIES_NAME}: {VALUE}%";
32  private static string PortfolioMarginIndexName = "Margin Used (%)";
33  private static readonly int _portfolioMarginSeriesCount = Configuration.Config.GetInt("portfolio-margin-series-count", 5);
34 
35  /// <summary>
36  /// Helper method to add the portfolio margin series into the given chart
37  /// </summary>
38  public static void AddSample(Chart portfolioChart, PortfolioState portfolioState, IMapFileProvider mapFileProvider, DateTime currentTime)
39  {
40  if (portfolioState == null || portfolioState.PositionGroups == null)
41  {
42  return;
43  }
44 
45  var topSeries = new HashSet<string>(_portfolioMarginSeriesCount);
46  foreach (var positionGroup in portfolioState.PositionGroups
47  .OrderByDescending(x => x.PortfolioValuePercentage)
48  .DistinctBy(x => GetPositionGroupName(x, mapFileProvider, currentTime))
49  .Take(_portfolioMarginSeriesCount))
50  {
51  topSeries.Add(positionGroup.Name);
52  }
53 
54  Series others = null;
55  ChartPoint currentOthers = null;
56  foreach (var positionGroup in portfolioState.PositionGroups)
57  {
58  var name = GetPositionGroupName(positionGroup, mapFileProvider, currentTime);
59  if (topSeries.Contains(name))
60  {
61  var series = GetOrAddSeries(portfolioChart, name, Color.Empty);
62  series.AddPoint(new ChartPoint(portfolioState.Time, positionGroup.PortfolioValuePercentage * 100));
63  continue;
64  }
65 
66  others ??= GetOrAddSeries(portfolioChart, "OTHERS", Color.Gray);
67  var value = positionGroup.PortfolioValuePercentage * 100;
68  if (currentOthers != null && currentOthers.Time == portfolioState.Time)
69  {
70  // we aggregate
71  currentOthers.y += value;
72  }
73  else
74  {
75  currentOthers = new ChartPoint(portfolioState.Time, value);
76  others.AddPoint(currentOthers);
77  }
78  }
79 
80  foreach (var series in portfolioChart.Series.Values)
81  {
82  // let's add a null point for the series which have no value for this time
83  var lastPoint = series.Values.LastOrDefault() as ChartPoint;
84  if (lastPoint == null || lastPoint.Time != portfolioState.Time && lastPoint.Y.HasValue)
85  {
86  series.AddPoint(new ChartPoint(portfolioState.Time, null));
87  }
88  }
89  }
90 
91  private static string GetPositionGroupName(PositionGroupState positionGroup, IMapFileProvider mapFileProvider, DateTime currentTime)
92  {
93  if (positionGroup.Positions.Count == 0)
94  {
95  return string.Empty;
96  }
97 
98  if (string.IsNullOrEmpty(positionGroup.Name))
99  {
100  positionGroup.Name = string.Join(", ", positionGroup.Positions.Select(x =>
101  {
102  if (mapFileProvider == null)
103  {
104  return x.Symbol.Value;
105  }
106  return GetMappedSymbol(mapFileProvider, x.Symbol, currentTime).Value;
107  }));
108  }
109 
110  return positionGroup.Name;
111  }
112 
113  private static Series GetOrAddSeries(Chart portfolioChart, string seriesName, Color color)
114  {
115  if (!portfolioChart.Series.TryGetValue(seriesName, out var series))
116  {
117  series = portfolioChart.Series[seriesName] = new Series(seriesName, SeriesType.StackedArea, 0, "%")
118  {
119  Color = color,
120  Tooltip = PortfolioMarginTooltip,
121  IndexName = PortfolioMarginIndexName,
123  };
124  }
125  return (Series)series;
126  }
127 
128  private static Symbol GetMappedSymbol(IMapFileProvider mapFileProvider, Symbol symbol, DateTime referenceTime)
129  {
130  if (symbol.RequiresMapping())
131  {
132  var mapFileResolver = mapFileProvider.Get(AuxiliaryDataKey.Create(symbol));
133  if (mapFileResolver.Any())
134  {
135  var mapFile = mapFileResolver.ResolveMapFile(symbol);
136  if (mapFile.Any())
137  {
138  symbol = symbol.UpdateMappedSymbol(mapFile.GetMappedSymbol(referenceTime.Date, symbol.Value));
139  }
140  }
141  }
142  return symbol;
143  }
144 
145  /// <summary>
146  /// Helper method to set the tooltip values after we've sampled and filter series with a single value
147  /// </summary>
148  public static void RemoveSinglePointSeries(Chart portfolioChart)
149  {
150  // let's remove series which have a single value, since it's a area chart they can't be drawn
151  portfolioChart.Series = portfolioChart.Series.Values
152  .Where(x =>
153  {
154  var notNullPointsCount = 0;
155  foreach (var point in x.Values.OfType<ChartPoint>())
156  {
157  if (point != null && point.Y.HasValue)
158  {
159  notNullPointsCount++;
160  if (notNullPointsCount > 1)
161  {
162  return true;
163  }
164  }
165  }
166  return notNullPointsCount > 1;
167  })
168  .ToDictionary(x => x.Name, x => x);
169  }
170  }
171 }