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