mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-16 01:28:05 +00:00
Refactor and optimize various components of QuanTAlib
- Removed WmaVector class to streamline weighted moving average calculations. - Simplified RingBuffer implementation by removing unnecessary comments and improving clarity. - Enhanced SIMD extensions for better performance and readability. - Updated TBar and TBarSeries classes to improve property calculations and reduce overhead. - Cleaned up TValue struct by removing redundant comments. - Added comprehensive unit tests for IndicatorExtensions and TrimaIndicator to ensure functionality and correctness.
This commit is contained in:
@@ -23,9 +23,9 @@ public sealed class RingBuffer : IEnumerable<double>
|
||||
{
|
||||
private readonly double[] _buffer;
|
||||
private readonly int _capacity;
|
||||
private int _head; // Next write position (also start position when full)
|
||||
private int _count; // Current number of elements
|
||||
private double _sum; // Running sum of all elements
|
||||
private int _head;
|
||||
private int _count;
|
||||
private double _sum;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new RingBuffer with the specified capacity.
|
||||
@@ -114,7 +114,6 @@ public sealed class RingBuffer : IEnumerable<double>
|
||||
get
|
||||
{
|
||||
if (_count == 0) return 0;
|
||||
// When full, _head points to oldest; otherwise start is 0
|
||||
int start = _count == _capacity ? _head : 0;
|
||||
return _buffer[start];
|
||||
}
|
||||
@@ -143,7 +142,6 @@ public sealed class RingBuffer : IEnumerable<double>
|
||||
|
||||
if (_count == _capacity)
|
||||
{
|
||||
// Buffer is full: remove oldest value from sum
|
||||
removed = _buffer[_head];
|
||||
_sum -= removed;
|
||||
}
|
||||
@@ -243,13 +241,11 @@ public sealed class RingBuffer : IEnumerable<double>
|
||||
|
||||
int start = _count == _capacity ? _head : 0;
|
||||
|
||||
// Check if contiguous (no wrap)
|
||||
if (start + _count <= _capacity)
|
||||
{
|
||||
return new ReadOnlySpan<double>(_buffer, start, _count);
|
||||
}
|
||||
|
||||
// Wrapped - need to copy
|
||||
return new ReadOnlySpan<double>(ToArray());
|
||||
}
|
||||
|
||||
|
||||
@@ -112,7 +112,6 @@ public static class SimdExtensions
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check remaining elements with scalar
|
||||
for (; i < span.Length; i++)
|
||||
{
|
||||
if (!double.IsFinite(span[i]))
|
||||
@@ -144,19 +143,16 @@ public static class SimdExtensions
|
||||
int vectorSize = Vector<double>.Count;
|
||||
int i = 0;
|
||||
|
||||
// Process in vector chunks
|
||||
for (; i <= span.Length - vectorSize; i += vectorSize)
|
||||
{
|
||||
var vector = new Vector<double>(span.Slice(i, vectorSize));
|
||||
sum += vector;
|
||||
}
|
||||
|
||||
// Horizontal sum of vector
|
||||
double result = 0.0;
|
||||
for (int j = 0; j < vectorSize; j++)
|
||||
result += sum[j];
|
||||
|
||||
// Process remaining elements
|
||||
for (; i < span.Length; i++)
|
||||
result += span[i];
|
||||
|
||||
@@ -186,14 +182,12 @@ public static class SimdExtensions
|
||||
var minVec = new Vector<double>(span.Slice(0, vectorSize));
|
||||
int i = vectorSize;
|
||||
|
||||
// Process in vector chunks
|
||||
for (; i <= span.Length - vectorSize; i += vectorSize)
|
||||
{
|
||||
var vector = new Vector<double>(span.Slice(i, vectorSize));
|
||||
minVec = Vector.Min(minVec, vector);
|
||||
}
|
||||
|
||||
// Find minimum within vector
|
||||
double result = minVec[0];
|
||||
for (int j = 1; j < vectorSize; j++)
|
||||
{
|
||||
@@ -201,7 +195,6 @@ public static class SimdExtensions
|
||||
result = minVec[j];
|
||||
}
|
||||
|
||||
// Process remaining elements
|
||||
for (; i < span.Length; i++)
|
||||
{
|
||||
if (span[i] < result)
|
||||
@@ -234,14 +227,12 @@ public static class SimdExtensions
|
||||
var maxVec = new Vector<double>(span.Slice(0, vectorSize));
|
||||
int i = vectorSize;
|
||||
|
||||
// Process in vector chunks
|
||||
for (; i <= span.Length - vectorSize; i += vectorSize)
|
||||
{
|
||||
var vector = new Vector<double>(span.Slice(i, vectorSize));
|
||||
maxVec = Vector.Max(maxVec, vector);
|
||||
}
|
||||
|
||||
// Find maximum within vector
|
||||
double result = maxVec[0];
|
||||
for (int j = 1; j < vectorSize; j++)
|
||||
{
|
||||
@@ -249,7 +240,6 @@ public static class SimdExtensions
|
||||
result = maxVec[j];
|
||||
}
|
||||
|
||||
// Process remaining elements
|
||||
for (; i < span.Length; i++)
|
||||
{
|
||||
if (span[i] > result)
|
||||
@@ -285,12 +275,17 @@ public static class SimdExtensions
|
||||
{
|
||||
if (span.Length < 2) return double.NaN;
|
||||
|
||||
// Guard against non-finite inputs
|
||||
if (span.ContainsNonFinite()) return double.NaN;
|
||||
double m;
|
||||
if (mean.HasValue)
|
||||
{
|
||||
if (span.ContainsNonFinite()) return double.NaN;
|
||||
m = mean.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
m = span.AverageSIMD();
|
||||
}
|
||||
|
||||
double m = mean ?? span.AverageSIMD();
|
||||
|
||||
// Guard against non-finite mean (could be passed in or computed from non-finite values)
|
||||
if (!double.IsFinite(m)) return double.NaN;
|
||||
|
||||
if (Vector.IsHardwareAccelerated && span.Length >= Vector<double>.Count)
|
||||
@@ -300,7 +295,6 @@ public static class SimdExtensions
|
||||
int vectorSize = Vector<double>.Count;
|
||||
int i = 0;
|
||||
|
||||
// Process in vector chunks
|
||||
for (; i <= span.Length - vectorSize; i += vectorSize)
|
||||
{
|
||||
var vector = new Vector<double>(span.Slice(i, vectorSize));
|
||||
@@ -308,12 +302,10 @@ public static class SimdExtensions
|
||||
sumSq += diff * diff;
|
||||
}
|
||||
|
||||
// Horizontal sum of vector
|
||||
double result = 0.0;
|
||||
for (int j = 0; j < vectorSize; j++)
|
||||
result += sumSq[j];
|
||||
|
||||
// Process remaining elements
|
||||
for (; i < span.Length; i++)
|
||||
{
|
||||
double diff = span[i] - m;
|
||||
@@ -358,7 +350,6 @@ public static class SimdExtensions
|
||||
var maxVec = minVec;
|
||||
int i = vectorSize;
|
||||
|
||||
// Process in vector chunks
|
||||
for (; i <= span.Length - vectorSize; i += vectorSize)
|
||||
{
|
||||
var vector = new Vector<double>(span.Slice(i, vectorSize));
|
||||
@@ -366,7 +357,6 @@ public static class SimdExtensions
|
||||
maxVec = Vector.Max(maxVec, vector);
|
||||
}
|
||||
|
||||
// Find min/max within vectors
|
||||
double min = minVec[0];
|
||||
double max = maxVec[0];
|
||||
for (int j = 1; j < vectorSize; j++)
|
||||
@@ -375,7 +365,6 @@ public static class SimdExtensions
|
||||
if (maxVec[j] > max) max = maxVec[j];
|
||||
}
|
||||
|
||||
// Process remaining elements
|
||||
for (; i < span.Length; i++)
|
||||
{
|
||||
if (span[i] < min) min = span[i];
|
||||
|
||||
@@ -28,8 +28,8 @@ public readonly struct TBar : IEquatable<TBar>
|
||||
// Computed properties (calculated on demand, no storage overhead)
|
||||
public double HL2 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => (High + Low) * 0.5; }
|
||||
public double OC2 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => (Open + Close) * 0.5; }
|
||||
public double OHL3 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => (Open + High + Low) / 3.0; }
|
||||
public double HLC3 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => (High + Low + Close) / 3.0; }
|
||||
public double OHL3 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => (Open + High + Low) * 0.333333333333333333; }
|
||||
public double HLC3 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => (High + Low + Close) * 0.333333333333333333; }
|
||||
public double OHLC4 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => (Open + High + Low + Close) * 0.25; }
|
||||
public double HLCC4 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => (High + Low + Close + Close) * 0.25; }
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ namespace QuanTAlib;
|
||||
/// </summary>
|
||||
public class TBarSeries : IReadOnlyList<TBar>
|
||||
{
|
||||
// Internal storage: SoA layout
|
||||
protected readonly List<long> _t = new();
|
||||
protected readonly List<double> _o = new();
|
||||
protected readonly List<double> _h = new();
|
||||
@@ -22,7 +21,6 @@ public class TBarSeries : IReadOnlyList<TBar>
|
||||
public string Name { get; set; } = "Bar";
|
||||
public event Action<TBar>? Pub;
|
||||
|
||||
// Public properties are Views into the main data
|
||||
public TSeries Open { get; }
|
||||
public TSeries High { get; }
|
||||
public TSeries Low { get; }
|
||||
@@ -38,7 +36,6 @@ public class TBarSeries : IReadOnlyList<TBar>
|
||||
|
||||
public TBarSeries()
|
||||
{
|
||||
// Initialize views sharing the same Time list but different Value lists
|
||||
Open = new TSeries(_t, _o) { Name = "Open" };
|
||||
High = new TSeries(_t, _h) { Name = "High" };
|
||||
Low = new TSeries(_t, _l) { Name = "Low" };
|
||||
@@ -46,9 +43,6 @@ public class TBarSeries : IReadOnlyList<TBar>
|
||||
Volume = new TSeries(_t, _v) { Name = "Volume" };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor with capacity hint to avoid List growth overhead.
|
||||
/// </summary>
|
||||
public TBarSeries(int capacity)
|
||||
{
|
||||
_t = new List<long>(capacity);
|
||||
@@ -58,7 +52,6 @@ public class TBarSeries : IReadOnlyList<TBar>
|
||||
_c = new List<double>(capacity);
|
||||
_v = new List<double>(capacity);
|
||||
|
||||
// Initialize views sharing the same Time list but different Value lists
|
||||
Open = new TSeries(_t, _o) { Name = "Open" };
|
||||
High = new TSeries(_t, _h) { Name = "High" };
|
||||
Low = new TSeries(_t, _l) { Name = "Low" };
|
||||
|
||||
@@ -11,36 +11,25 @@ namespace QuanTAlib;
|
||||
/// </summary>
|
||||
public class TSeries : IReadOnlyList<TValue>
|
||||
{
|
||||
// Internal storage: SoA layout
|
||||
// We use List<T> for dynamic sizing but access internal arrays via CollectionsMarshal for speed
|
||||
protected readonly List<long> _t;
|
||||
protected readonly List<double> _v;
|
||||
|
||||
public string Name { get; set; } = "Data";
|
||||
|
||||
// Event optimization: Use Action<TValue> to avoid EventArgs allocation
|
||||
// Note: Events are generally discouraged in the hot path of this high-perf design,
|
||||
// but kept for compatibility/chaining.
|
||||
public event Action<TValue>? Pub;
|
||||
|
||||
public TSeries()
|
||||
public TSeries()
|
||||
{
|
||||
_t = new List<long>();
|
||||
_v = new List<double>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor with capacity hint to avoid List growth overhead.
|
||||
/// </summary>
|
||||
public TSeries(int capacity)
|
||||
public TSeries(int capacity)
|
||||
{
|
||||
_t = new List<long>(capacity);
|
||||
_v = new List<double>(capacity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for wrapping existing lists (e.g. from TBarSeries).
|
||||
/// </summary>
|
||||
public TSeries(List<long> time, List<double> values)
|
||||
{
|
||||
_t = time;
|
||||
@@ -105,7 +94,6 @@ public class TSeries : IReadOnlyList<TValue>
|
||||
}
|
||||
else
|
||||
{
|
||||
// Update last bar
|
||||
int lastIdx = _v.Count - 1;
|
||||
_t[lastIdx] = value.Time;
|
||||
_v[lastIdx] = value.Value;
|
||||
@@ -129,7 +117,7 @@ public class TSeries : IReadOnlyList<TValue>
|
||||
foreach (var v in values)
|
||||
{
|
||||
Add(new TValue(t, v), isNew: true);
|
||||
t += TimeSpan.TicksPerMinute; // Dummy time increment
|
||||
t += TimeSpan.TicksPerMinute;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,19 +9,9 @@ namespace QuanTAlib;
|
||||
[SkipLocalsInit]
|
||||
public readonly struct TValue : IEquatable<TValue>
|
||||
{
|
||||
/// <summary>
|
||||
/// Time in ticks (UTC).
|
||||
/// </summary>
|
||||
public readonly long Time;
|
||||
|
||||
/// <summary>
|
||||
/// The value.
|
||||
/// </summary>
|
||||
public readonly double Value;
|
||||
|
||||
/// <summary>
|
||||
/// Convenience property to get DateTime from Ticks.
|
||||
/// </summary>
|
||||
public DateTime AsDateTime => new(Time, DateTimeKind.Utc);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
|
||||
Reference in New Issue
Block a user