Lean  $LEAN_TAG$
Metrics.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 using Deedle;
17 using QuantConnect.Orders;
18 using System;
19 using System.Collections.Generic;
20 using System.Linq;
21 
22 namespace QuantConnect.Report
23 {
24  /// <summary>
25  /// Strategy metrics collection such as usage of funds and asset allocations
26  /// </summary>
27  public static class Metrics
28  {
29  /// <summary>
30  /// Calculates the leverage used from trades. The series used to call this extension function should
31  /// be the equity curve with the associated <see cref="Order"/> objects that go along with it.
32  /// </summary>
33  /// <param name="equityCurve">Equity curve series</param>
34  /// <param name="orders">Orders associated with the equity curve</param>
35  /// <returns>Leverage utilization over time</returns>
36  public static Series<DateTime, double> LeverageUtilization(Series<DateTime, double> equityCurve, List<Order> orders)
37  {
38  if (equityCurve.IsEmpty || orders.Count == 0)
39  {
40  return new Series<DateTime, double>(new DateTime[] { }, new double[] { });
41  }
42 
43  var pointInTimePortfolios = PortfolioLooper.FromOrders(equityCurve, orders)
44  .ToList(); // Required because for some reason our AbsoluteHoldingsValue is multiplied by two whenever we GroupBy on the raw IEnumerable
45 
46  return LeverageUtilization(pointInTimePortfolios);
47  }
48 
49  /// <summary>
50  /// Gets the leverage utilization from a list of <see cref="PointInTimePortfolio"/>
51  /// </summary>
52  /// <param name="portfolios">Point in time portfolios</param>
53  /// <returns>Series of leverage utilization</returns>
54  public static Series<DateTime, double> LeverageUtilization(List<PointInTimePortfolio> portfolios)
55  {
56  var leverage = portfolios.GroupBy(portfolio => portfolio.Time)
57  .Select(group => new KeyValuePair<DateTime, double>(group.Key, (double)group.Last().Leverage))
58  .ToList();
59 
60  // Drop missing because we don't care about the missing values
61  return new Series<DateTime, double>(leverage).DropMissing();
62  }
63 
64  /// <summary>
65  /// Calculates the portfolio's asset allocation percentage over time. The series used to call this extension function should
66  /// be the equity curve with the associated <see cref="Order"/> objects that go along with it.
67  /// </summary>
68  /// <param name="equityCurve">Equity curve series</param>
69  /// <param name="orders">Orders associated with the equity curve</param>
70  /// <returns></returns>
71  public static Series<Symbol, double> AssetAllocations(Series<DateTime, double> equityCurve, List<Order> orders)
72  {
73  if (equityCurve.IsEmpty || orders.Count == 0)
74  {
75  return new Series<Symbol, double>(new Symbol[] { }, new double[] { });
76  }
77 
78  // Convert PointInTimePortfolios to List because for some reason our AbsoluteHoldingsValue is multiplied by two whenever we GroupBy on the raw IEnumerable
79  return AssetAllocations(PortfolioLooper.FromOrders(equityCurve, orders).ToList());
80  }
81 
82  /// <summary>
83  /// Calculates the asset allocation percentage over time.
84  /// </summary>
85  /// <param name="portfolios">Point in time portfolios</param>
86  /// <returns>Series keyed by Symbol containing the percentage allocated to that asset over time</returns>
87  public static Series<Symbol, double> AssetAllocations(List<PointInTimePortfolio> portfolios)
88  {
89  var portfolioHoldings = portfolios.GroupBy(x => x.Time)
90  .Select(kvp => kvp.Last())
91  .ToList();
92 
93  var totalPortfolioValueOverTime = (double)portfolioHoldings.Sum(x => x.Holdings.Sum(y => y.AbsoluteHoldingsValue));
94  var holdingsBySymbolOverTime = new Dictionary<Symbol, double>();
95 
96  foreach (var portfolio in portfolioHoldings)
97  {
98  foreach (var holding in portfolio.Holdings)
99  {
100  if (!holdingsBySymbolOverTime.ContainsKey(holding.Symbol))
101  {
102  holdingsBySymbolOverTime[holding.Symbol] = (double)holding.AbsoluteHoldingsValue;
103  continue;
104  }
105 
106  holdingsBySymbolOverTime[holding.Symbol] = holdingsBySymbolOverTime[holding.Symbol] + (double)holding.AbsoluteHoldingsValue;
107  }
108  }
109 
110  return new Series<Symbol, double>(
111  holdingsBySymbolOverTime.Keys,
112  holdingsBySymbolOverTime.Values.Select(x => x / totalPortfolioValueOverTime).ToList()
113  ).DropMissing();
114  }
115 
116  /// <summary>
117  /// Strategy long/short exposure by asset class
118  /// </summary>
119  /// <param name="equityCurve">Equity curve</param>
120  /// <param name="orders">Orders of the strategy</param>
121  /// <param name="direction">Long or short</param>
122  /// <returns>
123  /// Frame keyed by <see cref="SecurityType"/> and <see cref="OrderDirection"/>.
124  /// Returns a Frame of exposure per asset per direction over time
125  /// </returns>
126  public static Frame<DateTime, Tuple<SecurityType, OrderDirection>> Exposure(Series<DateTime, double> equityCurve, List<Order> orders, OrderDirection direction)
127  {
128  if (equityCurve.IsEmpty || orders.Count == 0)
129  {
130  return Frame.CreateEmpty<DateTime, Tuple<SecurityType, OrderDirection>>();
131  }
132 
133  return Exposure(PortfolioLooper.FromOrders(equityCurve, orders).ToList(), direction);
134  }
135 
136  /// <summary>
137  /// Strategy long/short exposure by asset class
138  /// </summary>
139  /// <param name="portfolios">Point in time portfolios</param>
140  /// <param name="direction">Long or short</param>
141  /// <returns>
142  /// Frame keyed by <see cref="SecurityType"/> and <see cref="OrderDirection"/>.
143  /// Returns a Frame of exposure per asset per direction over time
144  /// </returns>
145  public static Frame<DateTime, Tuple<SecurityType, OrderDirection>> Exposure(List<PointInTimePortfolio> portfolios, OrderDirection direction)
146  {
147  // We want to add all of the holdings by asset class to a mock dataframe that is column keyed by SecurityType with
148  // rows being DateTime and values being the exposure at that given time (as double)
149  var holdingsByAssetClass = new Dictionary<SecurityType, List<KeyValuePair<DateTime, double>>>();
150  var multiplier = direction == OrderDirection.Sell ? -1 : 1;
151 
152  foreach (var portfolio in portfolios)
153  {
154  List<KeyValuePair<DateTime, double>> holdings;
155  if (!holdingsByAssetClass.TryGetValue(portfolio.Order.SecurityType, out holdings))
156  {
157  holdings = new List<KeyValuePair<DateTime, double>>();
158  holdingsByAssetClass[portfolio.Order.SecurityType] = holdings;
159  }
160 
161  var assets = portfolio.Holdings
162  .Where(pointInTimeHoldings => pointInTimeHoldings.Symbol.SecurityType == portfolio.Order.SecurityType)
163  .ToList();
164 
165  if (assets.Count > 0)
166  {
167  // Use the multiplier to flip the holdings value around
168  var sum = (double)assets.Where(pointInTimeHoldings => multiplier * pointInTimeHoldings.HoldingsValue > 0)
169  .Select(pointInTimeHoldings => pointInTimeHoldings.AbsoluteHoldingsValue)
170  .Sum();
171 
172  holdings.Add(new KeyValuePair<DateTime, double>(portfolio.Time, sum / (double)portfolio.TotalPortfolioValue));
173  }
174  }
175 
176  var frame = Frame.CreateEmpty<DateTime, Tuple<SecurityType, OrderDirection>>();
177 
178  foreach (var kvp in holdingsByAssetClass)
179  {
180  // Skip Base asset class since we need it as a special value
181  // (and it can't be traded on either way)
182  if (kvp.Key == SecurityType.Base)
183  {
184  continue;
185  }
186 
187  // Select the last entry of a given time to get accurate results of the portfolio's actual value.
188  // Then, select only the long or short holdings.
189  frame = frame.Join(
190  new Tuple<SecurityType, OrderDirection>(kvp.Key, direction),
191  new Series<DateTime, double>(kvp.Value.GroupBy(x => x.Key).Select(x => x.Last())) * multiplier
192  );
193  }
194 
195  // Equivalent to `pd.fillna(method='ffill').dropna(axis=1, how='all').dropna(how='all')`
196  // First drops any missing SecurityTypes, then drops the rows with missing values
197  // to get rid of any empty data prior to the first value.
198  return frame.FillMissing(Direction.Forward)
199  .DropSparseColumnsAll()
200  .DropSparseRowsAll();
201  }
202  }
203 }