Lean  $LEAN_TAG$
RollingWindow.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;
18 using System.Collections.Generic;
19 using System.Runtime.CompilerServices;
20 using System.Threading;
21 
23 {
24  /// <summary>
25  /// This is a window that allows for list access semantics,
26  /// where this[0] refers to the most recent item in the
27  /// window and this[Count-1] refers to the last item in the window
28  /// </summary>
29  /// <typeparam name="T">The type of data in the window</typeparam>
30  public class RollingWindow<T> : IReadOnlyWindow<T>
31  {
32  // the backing list object used to hold the data
33  private readonly List<T> _list;
34  // read-write lock used for controlling access to the underlying list data structure
35  private readonly ReaderWriterLockSlim _listLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
36  // the most recently removed item from the window (fell off the back)
37  private T _mostRecentlyRemoved;
38  // the total number of samples taken by this indicator
39  private int _samples;
40  // used to locate the last item in the window as an indexer into the _list
41  private int _tail;
42  // the size or capacity of the window
43  private int _size;
44 
45  /// <summary>
46  /// Initializes a new instance of the RollwingWindow class with the specified window size.
47  /// </summary>
48  /// <param name="size">The number of items to hold in the window</param>
49  public RollingWindow(int size)
50  {
51  if (size < 1)
52  {
53  throw new ArgumentException(Messages.RollingWindow.InvalidSize, nameof(size));
54  }
55  _list = new List<T>(size);
56  _size = size;
57  }
58 
59  /// <summary>
60  /// Gets the size of this window
61  /// </summary>
62  public int Size
63  {
64  get
65  {
66  try
67  {
68  _listLock.EnterReadLock();
69  return _size;
70  }
71  finally
72  {
73  _listLock.ExitReadLock();
74  }
75  }
76  set
77  {
78  Resize(value);
79  }
80  }
81 
82  /// <summary>
83  /// Gets the current number of elements in this window
84  /// </summary>
85  public int Count
86  {
87  get
88  {
89  try
90  {
91  _listLock.EnterReadLock();
92  return _list.Count;
93  }
94  finally
95  {
96  _listLock.ExitReadLock();
97  }
98  }
99  }
100 
101  /// <summary>
102  /// Gets the number of samples that have been added to this window over its lifetime
103  /// </summary>
104  public int Samples
105  {
106  get
107  {
108  try
109  {
110  _listLock.EnterReadLock();
111  return _samples;
112  }
113  finally
114  {
115  _listLock.ExitReadLock();
116  }
117  }
118  }
119 
120  /// <summary>
121  /// Gets the most recently removed item from the window. This is the
122  /// piece of data that just 'fell off' as a result of the most recent
123  /// add. If no items have been removed, this will throw an exception.
124  /// </summary>
125  public T MostRecentlyRemoved
126  {
127  get
128  {
129  try
130  {
131  _listLock.EnterReadLock();
132 
133  if (_samples <= _size)
134  {
135  throw new InvalidOperationException(Messages.RollingWindow.NoItemsRemovedYet);
136  }
137  return _mostRecentlyRemoved;
138  }
139  finally
140  {
141  _listLock.ExitReadLock();
142  }
143 
144  }
145  }
146 
147  /// <summary>
148  /// Indexes into this window, where index 0 is the most recently
149  /// entered value
150  /// </summary>
151  /// <param name="i">the index, i</param>
152  /// <returns>the ith most recent entry</returns>
153  public T this [int i]
154  {
155  get
156  {
157  try
158  {
159  _listLock.EnterReadLock();
160 
161  if (i < 0)
162  {
163  throw new ArgumentOutOfRangeException(nameof(i), i, Messages.RollingWindow.IndexOutOfSizeRange);
164  }
165 
166  if (i > _list.Count - 1)
167  {
168  if (i > _size - 1)
169  {
170  _listLock.ExitReadLock();
171  Resize(i + 1);
172  _listLock.EnterReadLock();
173  }
174 
175  return default;
176  }
177 
178  return _list[GetListIndex(i, _list.Count, _tail)];
179  }
180  finally
181  {
182  _listLock.ExitReadLock();
183  }
184  }
185  set
186  {
187  try
188  {
189  _listLock.EnterWriteLock();
190 
191  if (i < 0)
192  {
193  throw new ArgumentOutOfRangeException(nameof(i), i, Messages.RollingWindow.IndexOutOfSizeRange);
194  }
195 
196  if (i > _list.Count - 1)
197  {
198  if (i > _size - 1)
199  {
200  Resize(i + 1);
201  }
202 
203  var count = _list.Count;
204  for (var j = 0; j < i - count + 1; j++)
205  {
206  Add(default);
207  }
208  }
209 
210  _list[GetListIndex(i, _list.Count, _tail)] = value;
211  }
212  finally
213  {
214  _listLock.ExitWriteLock();
215  }
216  }
217  }
218 
219  /// <summary>
220  /// Gets a value indicating whether or not this window is ready, i.e,
221  /// it has been filled to its capacity
222  /// </summary>
223  public bool IsReady
224  {
225  get
226  {
227  try
228  {
229  _listLock.EnterReadLock();
230  return _samples >= _size;
231  }
232  finally
233  {
234  _listLock.ExitReadLock();
235  }
236  }
237  }
238 
239  /// <summary>
240  /// Returns an enumerator that iterates through the collection.
241  /// </summary>
242  /// <returns>
243  /// A <see cref="T:System.Collections.Generic.IEnumerator`1" /> that can be used to iterate through the collection.
244  /// </returns>
245  /// <filterpriority>1</filterpriority>
246  public IEnumerator<T> GetEnumerator()
247  {
248  try
249  {
250  _listLock.EnterReadLock();
251 
252  // we make a copy on purpose so the enumerator isn't tied
253  // to a mutable object, well it is still mutable but out of scope
254  var count = _list.Count;
255  var temp = new T[count];
256  for (int i = 0; i < count; i++)
257  {
258  temp[i] = _list[GetListIndex(i, count, _tail)];
259  }
260 
261  return ((IEnumerable<T>) temp).GetEnumerator();
262  }
263  finally
264  {
265  _listLock.ExitReadLock();
266  }
267 
268  }
269 
270  /// <summary>
271  /// Returns an enumerator that iterates through a collection.
272  /// </summary>
273  /// <returns>
274  /// An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.
275  /// </returns>
276  /// <filterpriority>2</filterpriority>
277  IEnumerator IEnumerable.GetEnumerator()
278  {
279  return GetEnumerator();
280  }
281 
282  /// <summary>
283  /// Adds an item to this window and shifts all other elements
284  /// </summary>
285  /// <param name="item">The item to be added</param>
286  public void Add(T item)
287  {
288  try
289  {
290  _listLock.EnterWriteLock();
291 
292  _samples++;
293  if (_size == _list.Count)
294  {
295  // keep track of what's the last element
296  // so we can reindex on this[ int ]
297  _mostRecentlyRemoved = _list[_tail];
298  _list[_tail] = item;
299  _tail = (_tail + 1) % _size;
300  }
301  else
302  {
303  _list.Add(item);
304  }
305  }
306  finally
307  {
308  _listLock.ExitWriteLock();
309  }
310  }
311 
312  /// <summary>
313  /// Clears this window of all data
314  /// </summary>
315  public void Reset()
316  {
317  try
318  {
319  _listLock.EnterWriteLock();
320 
321  _samples = 0;
322  _list.Clear();
323  _tail = 0;
324  }
325  finally
326  {
327  _listLock.ExitWriteLock();
328  }
329  }
330 
331  [MethodImpl(MethodImplOptions.AggressiveInlining)]
332  private static int GetListIndex(int index, int listCount, int tail)
333  {
334  return (listCount + tail - index - 1) % listCount;
335  }
336 
337  private void Resize(int size)
338  {
339  try
340  {
341  _listLock.EnterWriteLock();
342 
343  _list.EnsureCapacity(size);
344  if (size < _list.Count)
345  {
346  _list.RemoveRange(0, _list.Count - size);
347  }
348  _size = size;
349  }
350  finally
351  {
352  _listLock.ExitWriteLock();
353  }
354  }
355  }
356 }