Lean  $LEAN_TAG$
All Classes Namespaces Functions Variables Enumerations Enumerator Properties Events Pages
ReturnsSymbolData.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 System;
17 using System.Collections.Generic;
18 using System.Linq;
20 
22 {
23  /// <summary>
24  /// Contains returns specific to a symbol required for optimization model
25  /// </summary>
26  public class ReturnsSymbolData
27  {
28  private readonly Symbol _symbol;
29  private readonly RateOfChange _roc;
30  private readonly RollingWindow<IndicatorDataPoint> _window;
31 
32  /// <summary>
33  /// The symbol's asset rate of change indicator
34  /// </summary>
35  public RateOfChange ROC { get { return _roc; } }
36 
37  /// <summary>
38  /// Initializes a new instance of the <see cref="ReturnsSymbolData"/> class
39  /// </summary>
40  /// <param name="symbol">The symbol of the data that updates the indicators</param>
41  /// <param name="lookback">Look-back period for the RateOfChange indicator</param>
42  /// <param name="period">Size of rolling window that contains historical RateOfChange</param>
43  public ReturnsSymbolData(Symbol symbol, int lookback, int period)
44  {
45  _symbol = symbol;
46  _roc = new RateOfChange($"{_symbol}.ROC({lookback})", lookback);
47  _window = new RollingWindow<IndicatorDataPoint>(period);
48  _roc.Updated += OnRateOfChangeUpdated;
49  }
50 
51  /// <summary>
52  /// Historical returns
53  /// </summary>
54  public Dictionary<DateTime, double> Returns => _window.ToDictionary(x => x.EndTime, x => (double) x.Value);
55 
56  /// <summary>
57  /// Adds an item to this window and shifts all other elements
58  /// </summary>
59  /// <param name="time">The time associated with the value</param>
60  /// <param name="value">The value to use to update this window</param>
61  public void Add(DateTime time, decimal value)
62  {
63  var item = new IndicatorDataPoint(_symbol, time, value);
64  AddToWindow(item);
65  }
66 
67  /// <summary>
68  /// Updates the state of the RateOfChange with the given value and returns true
69  /// if this indicator is ready, false otherwise
70  /// </summary>
71  /// <param name="time">The time associated with the value</param>
72  /// <param name="value">The value to use to update this indicator</param>
73  /// <returns>True if this indicator is ready, false otherwise</returns>
74  public bool Update(DateTime time, decimal value)
75  {
76  return _roc.Update(time, value);
77  }
78 
79  /// <summary>
80  /// Resets all indicators of this object to its initial state
81  /// </summary>
82  public void Reset()
83  {
84  _roc.Updated -= OnRateOfChangeUpdated;
85  _roc.Reset();
86  _window.Reset();
87  }
88 
89  /// <summary>
90  /// When the RateOfChange is updated, adds the new value to the RollingWindow
91  /// </summary>
92  /// <param name="roc"></param>
93  /// <param name="updated"></param>
94  private void OnRateOfChangeUpdated(object roc, IndicatorDataPoint updated)
95  {
96  if (_roc.IsReady)
97  {
98  AddToWindow(updated);
99  }
100  }
101 
102  private void AddToWindow(IndicatorDataPoint updated)
103  {
104  if (_window.Samples > 0 && _window[0].EndTime == updated.EndTime)
105  {
106  // this could happen with fill forward bars in the history request
107  return;
108  }
109 
110  _window.Add(updated);
111  }
112  }
113 
114  /// <summary>
115  /// Extension methods for <see cref="ReturnsSymbolData"/>
116  /// </summary>
117  public static class ReturnsSymbolDataExtensions
118  {
119  /// <summary>
120  /// Converts a dictionary of <see cref="ReturnsSymbolData"/> keyed by <see cref="Symbol"/> into a matrix
121  /// </summary>
122  /// <param name="symbolData">Dictionary of <see cref="ReturnsSymbolData"/> keyed by <see cref="Symbol"/> to be converted into a matrix</param>
123  /// <param name="symbols">List of <see cref="Symbol"/> to be included in the matrix</param>
124  public static double[,] FormReturnsMatrix(this Dictionary<Symbol, ReturnsSymbolData> symbolData, IEnumerable<Symbol> symbols)
125  {
126  var returnsByDate = (from s in symbols join sd in symbolData on s equals sd.Key select sd.Value.Returns).ToList();
127 
128  // Consolidate by date
129  var alldates = returnsByDate.SelectMany(r => r.Keys).Distinct().ToList();
130 
131  var max = symbolData.Count == 0 ? 0 : symbolData.Max(kvp => kvp.Value.Returns.Count);
132 
133  // Perfect match between the dates in the ReturnsSymbolData objects
134  if (max == alldates.Count)
135  {
136  return Accord.Math.Matrix.Create(alldates
137  // if a return date isn't found for a symbol we use 'double.NaN'
138  .Select(d => returnsByDate.Select(s => s.GetValueOrDefault(d, double.NaN)).ToArray())
139  .Where(r => !r.Select(Math.Abs).Sum().IsNaNOrZero()) // remove empty rows
140  .ToArray());
141  }
142 
143  // If it is not a match, we assume that each index correspond to the same point in time
144  var returnsByIndex = returnsByDate.Select((doubles, i) => doubles.Values.ToArray());
145 
146  return Accord.Math.Matrix.Create(Enumerable.Range(0, max)
147  // there is no guarantee that all symbols have the same amount of returns so we need to check range and use 'double.NaN' if required as above
148  .Select(d => returnsByIndex.Select(s => s.Length < (d + 1) ? double.NaN : s[d]).ToArray())
149  .Where(r => !r.Select(Math.Abs).Sum().IsNaNOrZero()) // remove empty rows
150  .ToArray());
151  }
152  }
153 }