/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using Python.Runtime;
namespace QuantConnect.Indicators
{
///
/// This is generic rolling window.
///
public class RollingWindow : RollingWindow
{
///
/// Initializes a new RollingWindow with the specified size.
///
/// The number of elements to store in the window
public RollingWindow(int size) : base(size)
{
}
}
///
/// This is a window that allows for list access semantics,
/// where this[0] refers to the most recent item in the
/// window and this[Count-1] refers to the last item in the window
///
/// The type of data in the window
public class RollingWindow : IReadOnlyWindow
{
// the backing list object used to hold the data
private readonly List _list;
// read-write lock used for controlling access to the underlying list data structure
private readonly ReaderWriterLockSlim _listLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
// the most recently removed item from the window (fell off the back)
private T _mostRecentlyRemoved;
// the total number of samples taken by this indicator
private int _samples;
// used to locate the last item in the window as an indexer into the _list
private int _tail;
// the size or capacity of the window
private int _size;
///
/// Initializes a new instance of the RollwingWindow class with the specified window size.
///
/// The number of items to hold in the window
public RollingWindow(int size)
{
if (size < 1)
{
throw new ArgumentException(Messages.RollingWindow.InvalidSize, nameof(size));
}
_list = new List(size);
_size = size;
}
///
/// Gets the size of this window
///
public int Size
{
get
{
try
{
_listLock.EnterReadLock();
return _size;
}
finally
{
_listLock.ExitReadLock();
}
}
set
{
Resize(value);
}
}
///
/// Gets the current number of elements in this window
///
public int Count
{
get
{
try
{
_listLock.EnterReadLock();
return _list.Count;
}
finally
{
_listLock.ExitReadLock();
}
}
}
///
/// Gets the number of samples that have been added to this window over its lifetime
///
public int Samples
{
get
{
try
{
_listLock.EnterReadLock();
return _samples;
}
finally
{
_listLock.ExitReadLock();
}
}
}
///
/// Gets the most recently removed item from the window. This is the
/// piece of data that just 'fell off' as a result of the most recent
/// add. If no items have been removed, this will throw an exception.
///
public T MostRecentlyRemoved
{
get
{
try
{
_listLock.EnterReadLock();
if (_samples <= _size)
{
throw new InvalidOperationException(Messages.RollingWindow.NoItemsRemovedYet);
}
return _mostRecentlyRemoved;
}
finally
{
_listLock.ExitReadLock();
}
}
}
///
/// Indexes into this window, where index 0 is the most recently
/// entered value
///
/// the index, i
/// the ith most recent entry
public T this[int i]
{
get
{
try
{
_listLock.EnterReadLock();
if (i < 0)
{
if (_size + i < 0)
{
throw new ArgumentOutOfRangeException(nameof(i), i, Messages.RollingWindow.IndexOutOfSizeRange);
}
i = _size + i;
}
if (i > _list.Count - 1)
{
if (i > _size - 1)
{
_listLock.ExitReadLock();
Resize(i + 1);
_listLock.EnterReadLock();
}
return default;
}
return _list[GetListIndex(i, _list.Count, _tail)];
}
finally
{
_listLock.ExitReadLock();
}
}
set
{
try
{
_listLock.EnterWriteLock();
if (i < 0)
{
if (_size + i < 0)
{
throw new ArgumentOutOfRangeException(nameof(i), i, Messages.RollingWindow.IndexOutOfSizeRange);
}
i = _size + i;
}
if (i > _list.Count - 1)
{
if (i > _size - 1)
{
Resize(i + 1);
}
var count = _list.Count;
for (var j = 0; j < i - count + 1; j++)
{
Add(default);
}
}
_list[GetListIndex(i, _list.Count, _tail)] = value;
}
finally
{
_listLock.ExitWriteLock();
}
}
}
///
/// Gets a value indicating whether or not this window is ready, i.e,
/// it has been filled to its capacity
///
public bool IsReady
{
get
{
try
{
_listLock.EnterReadLock();
return _samples >= _size;
}
finally
{
_listLock.ExitReadLock();
}
}
}
///
/// Returns an enumerator that iterates through the collection.
///
///
/// A that can be used to iterate through the collection.
///
/// 1
public IEnumerator GetEnumerator()
{
try
{
_listLock.EnterReadLock();
// we make a copy on purpose so the enumerator isn't tied
// to a mutable object, well it is still mutable but out of scope
var count = _list.Count;
var temp = new T[count];
for (int i = 0; i < count; i++)
{
temp[i] = _list[GetListIndex(i, count, _tail)];
}
return ((IEnumerable)temp).GetEnumerator();
}
finally
{
_listLock.ExitReadLock();
}
}
///
/// Returns an enumerator that iterates through a collection.
///
///
/// An object that can be used to iterate through the collection.
///
/// 2
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
///
/// Adds an item to this window and shifts all other elements
///
/// The item to be added
public void Add(T item)
{
try
{
_listLock.EnterWriteLock();
_samples++;
if (_size == _list.Count)
{
// keep track of what's the last element
// so we can reindex on this[ int ]
_mostRecentlyRemoved = _list[_tail];
_list[_tail] = item;
_tail = (_tail + 1) % _size;
}
else
{
_list.Add(item);
}
}
finally
{
_listLock.ExitWriteLock();
}
}
///
/// Clears this window of all data
///
public void Reset()
{
try
{
_listLock.EnterWriteLock();
_samples = 0;
_list.Clear();
_tail = 0;
}
finally
{
_listLock.ExitWriteLock();
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetListIndex(int index, int listCount, int tail)
{
return (listCount + tail - index - 1) % listCount;
}
private void Resize(int size)
{
try
{
_listLock.EnterWriteLock();
_list.EnsureCapacity(size);
if (size < _list.Count)
{
_list.RemoveRange(0, _list.Count - size);
}
_size = size;
}
finally
{
_listLock.ExitWriteLock();
}
}
}
}