feat(technicals): add technical analysis microservice with indicator engines, pattern detectors, and strategies
This commit is contained in:
@@ -0,0 +1,228 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace FinlyticTechnicals.Timeframe;
|
||||
|
||||
/// <summary>
|
||||
/// Thread-safe, high-performance circular ring buffer with zero allocations on updates.
|
||||
/// Holds a fixed capacity of items (e.g. 500 candles).
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The item type (e.g. CandleDto)</typeparam>
|
||||
public class CircularRingBuffer<T> : IReadOnlyList<T>
|
||||
{
|
||||
private readonly T[] _buffer;
|
||||
private readonly int _capacity;
|
||||
private int _start;
|
||||
private int _count;
|
||||
private readonly ReaderWriterLockSlim _lock = new(LockRecursionPolicy.NoRecursion);
|
||||
|
||||
public CircularRingBuffer(int capacity = 500)
|
||||
{
|
||||
if (capacity <= 0) throw new ArgumentOutOfRangeException(nameof(capacity), "Capacity must be positive.");
|
||||
_capacity = capacity;
|
||||
_buffer = new T[capacity];
|
||||
_start = 0;
|
||||
_count = 0;
|
||||
}
|
||||
|
||||
public int Capacity => _capacity;
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
_lock.EnterReadLock();
|
||||
try { return _count; }
|
||||
finally { _lock.ExitReadLock(); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an item to the buffer. If capacity is reached, the oldest element is overwritten in O(1).
|
||||
/// </summary>
|
||||
public void Add(T item)
|
||||
{
|
||||
_lock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
if (_count < _capacity)
|
||||
{
|
||||
int nextIndex = (_start + _count) % _capacity;
|
||||
_buffer[nextIndex] = item;
|
||||
_count++;
|
||||
}
|
||||
else
|
||||
{
|
||||
_buffer[_start] = item;
|
||||
_start = (_start + 1) % _capacity;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the last (most recent) item in place.
|
||||
/// </summary>
|
||||
public void UpdateLast(T item)
|
||||
{
|
||||
_lock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
if (_count == 0)
|
||||
{
|
||||
_buffer[_start] = item;
|
||||
_count = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
int lastIndex = (_start + _count - 1) % _capacity;
|
||||
_buffer[lastIndex] = item;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the most recent item or default if empty.
|
||||
/// </summary>
|
||||
public T? GetLast()
|
||||
{
|
||||
_lock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
if (_count == 0) return default;
|
||||
int lastIndex = (_start + _count - 1) % _capacity;
|
||||
return _buffer[lastIndex];
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indexer accessing items from oldest (0) to newest (Count - 1).
|
||||
/// </summary>
|
||||
public T this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
_lock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
if (index < 0 || index >= _count)
|
||||
throw new ArgumentOutOfRangeException(nameof(index), "Index out of range.");
|
||||
|
||||
int actualIndex = (_start + index) % _capacity;
|
||||
return _buffer[actualIndex];
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an ordered immutable array snapshot of all elements.
|
||||
/// </summary>
|
||||
public T[] ToArray()
|
||||
{
|
||||
_lock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
if (_count == 0) return Array.Empty<T>();
|
||||
var result = new T[_count];
|
||||
for (int i = 0; i < _count; i++)
|
||||
{
|
||||
int actualIndex = (_start + i) % _capacity;
|
||||
result[i] = _buffer[actualIndex];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates the buffer in bulk with historical data (oldest to newest).
|
||||
/// </summary>
|
||||
public void LoadBulk(IEnumerable<T> items)
|
||||
{
|
||||
_lock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
_start = 0;
|
||||
_count = 0;
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (_count < _capacity)
|
||||
{
|
||||
_buffer[_count] = item;
|
||||
_count++;
|
||||
}
|
||||
else
|
||||
{
|
||||
_buffer[_start] = item;
|
||||
_start = (_start + 1) % _capacity;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
public Enumerator GetEnumerator() => new(this);
|
||||
IEnumerator<T> IEnumerable<T>.GetEnumerator() => new Enumerator(this);
|
||||
IEnumerator IEnumerable.GetEnumerator() => new Enumerator(this);
|
||||
|
||||
public struct Enumerator : IEnumerator<T>
|
||||
{
|
||||
private readonly CircularRingBuffer<T> _buffer;
|
||||
private int _index;
|
||||
private T? _current;
|
||||
|
||||
internal Enumerator(CircularRingBuffer<T> buffer)
|
||||
{
|
||||
_buffer = buffer;
|
||||
_index = 0;
|
||||
_current = default;
|
||||
}
|
||||
|
||||
public readonly T Current => _current!;
|
||||
readonly object? IEnumerator.Current => Current;
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (_index < _buffer.Count)
|
||||
{
|
||||
_current = _buffer[_index];
|
||||
_index++;
|
||||
return true;
|
||||
}
|
||||
_current = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_index = 0;
|
||||
_current = default;
|
||||
}
|
||||
|
||||
public readonly void Dispose() { }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user