style patterns

This commit is contained in:
Miha Kralj
2026-01-25 16:01:45 -08:00
parent 2836f253c4
commit e59665c8f0
399 changed files with 6892 additions and 1323 deletions
+14 -2
View File
@@ -59,7 +59,9 @@ public class AdxTests
var bars = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
for (int i = 0; i < 50; i++)
{
adx.Update(bars[i]);
}
var originalValue = adx.Last;
@@ -110,7 +112,10 @@ public class AdxTests
for (int i = 0; i < bars.Count; i++)
{
adx.Update(bars[i]);
if (adx.IsHot) break;
if (adx.IsHot)
{
break;
}
}
Assert.True(adx.IsHot);
@@ -124,7 +129,9 @@ public class AdxTests
var bars = gbm.Fetch(50, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
for (int i = 0; i < 40; i++)
{
adx.Update(bars[i]);
}
var nanBar = new TBar(DateTime.UtcNow, double.NaN, double.NaN, double.NaN, double.NaN, 100);
var result = adx.Update(nanBar);
@@ -140,7 +147,9 @@ public class AdxTests
var bars = gbm.Fetch(50, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
for (int i = 0; i < 40; i++)
{
adx.Update(bars[i]);
}
var infBar = new TBar(DateTime.UtcNow, double.PositiveInfinity, double.PositiveInfinity, 0, 100, 100);
var result = adx.Update(infBar);
@@ -161,7 +170,10 @@ public class AdxTests
// 2. Streaming Mode
var streamAdx = new Adx(14);
for (int i = 0; i < bars.Count; i++)
{
streamAdx.Update(bars[i]);
}
double streamResult = streamAdx.Last.Value;
Assert.Equal(expected, streamResult, 9);
@@ -235,4 +247,4 @@ public class AdxTests
Assert.Throws<ArgumentException>(() => new Adx(0));
Assert.Throws<ArgumentException>(() => new Adx(-1));
}
}
}
+66 -16
View File
@@ -92,7 +92,9 @@ public sealed class Adx : ITValuePublisher
public Adx(int period)
{
if (period <= 0)
{
throw new ArgumentException("Period must be greater than 0", nameof(period));
}
_period = period;
_decay = (period - 1.0) / period;
@@ -177,14 +179,17 @@ public sealed class Adx : ITValuePublisher
double prevClose = double.IsFinite(_prevBar.Close) ? _prevBar.Close : high;
double prevHigh = double.IsFinite(_prevBar.High) ? _prevBar.High : high;
double prevLow = double.IsFinite(_prevBar.Low) ? _prevBar.Low : low;
double hl = high - low;
double hpc = Math.Abs(high - prevClose);
double lpc = Math.Abs(low - prevClose);
double tr = Math.Max(hl, Math.Max(hpc, lpc));
// Guard TR against non-finite values
if (!double.IsFinite(tr)) tr = 0;
if (!double.IsFinite(tr))
{
tr = 0;
}
// Calculate DM using guarded values
double dmPlus = 0;
@@ -193,14 +198,25 @@ public sealed class Adx : ITValuePublisher
double downMove = prevLow - low;
// Guard moves against non-finite values
if (!double.IsFinite(upMove)) upMove = 0;
if (!double.IsFinite(downMove)) downMove = 0;
if (!double.IsFinite(upMove))
{
upMove = 0;
}
if (!double.IsFinite(downMove))
{
downMove = 0;
}
if (upMove > downMove && upMove > 0)
{
dmPlus = upMove;
}
if (downMove > upMove && downMove > 0)
{
dmMinus = downMove;
}
if (isNew)
{
@@ -251,8 +267,15 @@ public sealed class Adx : ITValuePublisher
}
// Guard against NaN/Infinity in DI calculations
if (!double.IsFinite(diPlus)) diPlus = 0;
if (!double.IsFinite(diMinus)) diMinus = 0;
if (!double.IsFinite(diPlus))
{
diPlus = 0;
}
if (!double.IsFinite(diMinus))
{
diMinus = 0;
}
double diSum = diPlus + diMinus;
if (diSum > 1e-10)
@@ -261,7 +284,10 @@ public sealed class Adx : ITValuePublisher
}
// Guard against NaN/Infinity in DX calculation
if (!double.IsFinite(dx)) dx = 0;
if (!double.IsFinite(dx))
{
dx = 0;
}
// Smooth DX to get ADX
if (_dxSamples < _period)
@@ -281,17 +307,34 @@ public sealed class Adx : ITValuePublisher
}
// Final guard on ADX
if (!double.IsFinite(_adx)) _adx = _p_adx;
if (!double.IsFinite(_adx))
{
_adx = _p_adx;
}
}
// Ensure all outputs are finite; if not, use previous values or 0
if (!double.IsFinite(diPlus)) diPlus = double.IsFinite(DiPlus.Value) ? DiPlus.Value : 0;
if (!double.IsFinite(diMinus)) diMinus = double.IsFinite(DiMinus.Value) ? DiMinus.Value : 0;
if (!double.IsFinite(diPlus))
{
diPlus = double.IsFinite(DiPlus.Value) ? DiPlus.Value : 0;
}
if (!double.IsFinite(diMinus))
{
diMinus = double.IsFinite(DiMinus.Value) ? DiMinus.Value : 0;
}
// Final guard on ADX output - ensure we always return a finite value
double finalAdx = _adx;
if (!double.IsFinite(finalAdx)) finalAdx = _p_adx;
if (!double.IsFinite(finalAdx)) finalAdx = 0;
if (!double.IsFinite(finalAdx))
{
finalAdx = _p_adx;
}
if (!double.IsFinite(finalAdx))
{
finalAdx = 0;
}
DiPlus = new TValue(input.Time, diPlus);
DiMinus = new TValue(input.Time, diMinus);
@@ -309,7 +352,10 @@ public sealed class Adx : ITValuePublisher
public TSeries Update(TBarSeries source)
{
if (source.Count == 0) return new TSeries([], []);
if (source.Count == 0)
{
return new TSeries([], []);
}
var len = source.Count;
var v = new double[len];
@@ -450,7 +496,11 @@ public sealed class Adx : ITValuePublisher
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TSeries Batch(TBarSeries source, int period)
{
if (source.Count == 0) return new TSeries([], []);
if (source.Count == 0)
{
return new TSeries([], []);
}
var len = source.Count;
var v = new double[len];
Calculate(source.High.Values, source.Low.Values, source.Close.Values, period, v);
@@ -464,4 +514,4 @@ public sealed class Adx : ITValuePublisher
return new TSeries(tList, [.. v]);
}
}
}
+13 -3
View File
@@ -54,7 +54,9 @@ public sealed class Adxr : ITValuePublisher
public Adxr(int period)
{
if (period <= 0)
{
throw new ArgumentException("Period must be greater than 0", nameof(period));
}
_period = period;
Name = $"Adxr({period})";
@@ -126,7 +128,10 @@ public sealed class Adxr : ITValuePublisher
public TSeries Update(TBarSeries source)
{
if (source.Count == 0) return new TSeries([], []);
if (source.Count == 0)
{
return new TSeries([], []);
}
int len = source.Count;
var v = new double[len];
@@ -205,14 +210,19 @@ public sealed class Adxr : ITValuePublisher
finally
{
if (rentedAdx != null)
{
ArrayPool<double>.Shared.Return(rentedAdx);
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TSeries Batch(TBarSeries source, int period)
{
if (source.Count == 0) return new TSeries([], []);
if (source.Count == 0)
{
return new TSeries([], []);
}
int len = source.Count;
var v = new double[len];
@@ -228,4 +238,4 @@ public sealed class Adxr : ITValuePublisher
return new TSeries(tList, [.. v]);
}
}
}
+2
View File
@@ -388,7 +388,9 @@ public class AmatTests
for (int i = warmup; i < source.Length; i++)
{
if (Math.Abs(tseriesResult[i].Value - trend[i]) < 0.01)
{
matched++;
}
}
// At least 95% of values after warmup should match
double matchRate = (double)matched / (source.Length - warmup);
+2 -2
View File
@@ -373,7 +373,7 @@ public sealed class AmatValidationTests : IDisposable
double trendMatchRate = (double)trendMatchCount / totalCount;
double strengthMatchRate = (double)strengthMatchCount / totalCount;
Assert.True(trendMatchRate > 0.95, $"Expected >95% trend match rate after warmup, got {trendMatchRate:P2}");
Assert.True(strengthMatchRate > 0.95, $"Expected >95% strength match rate after warmup, got {strengthMatchRate:P2}");
@@ -437,4 +437,4 @@ public sealed class AmatValidationTests : IDisposable
_output.WriteLine($"Period combination ({fastPeriod}, {slowPeriod}) validated: Trend={amat.Last.Value}, Strength={amat.Strength.Value:F2}%");
}
}
}
+51 -4
View File
@@ -123,11 +123,19 @@ public sealed class Amat : ITValuePublisher, IDisposable
public Amat(int fastPeriod = 10, int slowPeriod = 50)
{
if (fastPeriod <= 0)
{
throw new ArgumentException("Fast period must be greater than 0", nameof(fastPeriod));
}
if (slowPeriod <= 0)
{
throw new ArgumentException("Slow period must be greater than 0", nameof(slowPeriod));
}
if (fastPeriod >= slowPeriod)
{
throw new ArgumentException("Fast period must be less than slow period", nameof(fastPeriod));
}
_fastAlpha = 2.0 / (fastPeriod + 1);
_slowAlpha = 2.0 / (slowPeriod + 1);
@@ -314,7 +322,10 @@ public sealed class Amat : ITValuePublisher, IDisposable
/// <returns>Series of trend values</returns>
public TSeries Update(TSeries source)
{
if (source.Count == 0) return [];
if (source.Count == 0)
{
return [];
}
int len = source.Count;
var t = new List<long>(len);
@@ -342,7 +353,10 @@ public sealed class Amat : ITValuePublisher, IDisposable
private static double GetCompensatedValue(double ema, double e, bool isCompensated)
{
if (isCompensated || e <= COMPENSATOR_THRESHOLD)
{
return ema;
}
return ema / (1.0 - e);
}
@@ -358,7 +372,9 @@ public sealed class Amat : ITValuePublisher, IDisposable
e *= decay;
if (!isHot && e <= COVERAGE_THRESHOLD)
{
isHot = true;
}
if (e <= COMPENSATOR_THRESHOLD)
{
@@ -391,18 +407,35 @@ public sealed class Amat : ITValuePublisher, IDisposable
int fastPeriod = 10, int slowPeriod = 50)
{
if (source.Length != trend.Length)
{
throw new ArgumentException("Source and trend must have the same length", nameof(trend));
}
if (source.Length != strength.Length)
{
throw new ArgumentException("Source and strength must have the same length", nameof(strength));
}
if (fastPeriod <= 0)
{
throw new ArgumentException("Fast period must be greater than 0", nameof(fastPeriod));
}
if (slowPeriod <= 0)
{
throw new ArgumentException("Slow period must be greater than 0", nameof(slowPeriod));
}
if (fastPeriod >= slowPeriod)
{
throw new ArgumentException("Fast period must be less than slow period", nameof(fastPeriod));
}
int len = source.Length;
if (len == 0) return;
if (len == 0)
{
return;
}
double fastAlpha = 2.0 / (fastPeriod + 1);
double slowAlpha = 2.0 / (slowPeriod + 1);
@@ -483,16 +516,30 @@ public sealed class Amat : ITValuePublisher, IDisposable
int fastPeriod = 10, int slowPeriod = 50)
{
if (source.Length != trend.Length)
{
throw new ArgumentException("Source and trend must have the same length", nameof(trend));
}
if (fastPeriod <= 0)
{
throw new ArgumentException("Fast period must be greater than 0", nameof(fastPeriod));
}
if (slowPeriod <= 0)
{
throw new ArgumentException("Slow period must be greater than 0", nameof(slowPeriod));
}
if (fastPeriod >= slowPeriod)
{
throw new ArgumentException("Fast period must be less than slow period", nameof(fastPeriod));
}
int len = source.Length;
if (len == 0) return;
if (len == 0)
{
return;
}
double fastAlpha = 2.0 / (fastPeriod + 1);
double slowAlpha = 2.0 / (slowPeriod + 1);
@@ -575,4 +622,4 @@ public sealed class Amat : ITValuePublisher, IDisposable
var amat = new Amat(fastPeriod, slowPeriod);
return amat.Update(source);
}
}
}
+11 -3
View File
@@ -67,7 +67,9 @@ public sealed class Aroon : ITValuePublisher
public Aroon(int period)
{
if (period <= 0)
{
throw new ArgumentException("Period must be greater than 0", nameof(period));
}
_period = period;
Name = $"Aroon({period})";
@@ -163,7 +165,10 @@ public sealed class Aroon : ITValuePublisher
public TSeries Update(TBarSeries source)
{
if (source.Count == 0) return new TSeries([], []);
if (source.Count == 0)
{
return new TSeries([], []);
}
int len = source.Count;
var v = new double[len];
@@ -288,7 +293,10 @@ public sealed class Aroon : ITValuePublisher
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TSeries Batch(TBarSeries source, int period)
{
if (source.Count == 0) return new TSeries([], []);
if (source.Count == 0)
{
return new TSeries([], []);
}
int len = source.Count;
var v = new double[len];
@@ -304,4 +312,4 @@ public sealed class Aroon : ITValuePublisher
return new TSeries(tList, [.. v]);
}
}
}
+11 -3
View File
@@ -56,7 +56,9 @@ public sealed class AroonOsc : ITValuePublisher
public AroonOsc(int period)
{
if (period <= 0)
{
throw new ArgumentException("Period must be greater than 0", nameof(period));
}
_period = period;
Name = $"AroonOsc({period})";
@@ -148,7 +150,10 @@ public sealed class AroonOsc : ITValuePublisher
public TSeries Update(TBarSeries source)
{
if (source.Count == 0) return new TSeries([], []);
if (source.Count == 0)
{
return new TSeries([], []);
}
int len = source.Count;
var v = new double[len];
@@ -189,7 +194,10 @@ public sealed class AroonOsc : ITValuePublisher
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TSeries Batch(TBarSeries source, int period)
{
if (source.Count == 0) return new TSeries([], []);
if (source.Count == 0)
{
return new TSeries([], []);
}
int len = source.Count;
var v = new double[len];
@@ -205,4 +213,4 @@ public sealed class AroonOsc : ITValuePublisher
return new TSeries(tList, [.. v]);
}
}
}
+9
View File
@@ -68,7 +68,9 @@ public class DmxTests
var bars = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
for (int i = 0; i < 50; i++)
{
dmx.Update(bars[i]);
}
var originalValue = dmx.Last;
@@ -114,7 +116,9 @@ public class DmxTests
var bars = gbm.Fetch(50, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
for (int i = 0; i < 30; i++)
{
dmx.Update(bars[i]);
}
var nanBar = new TBar(DateTime.UtcNow, double.NaN, double.NaN, double.NaN, double.NaN, 100);
var result = dmx.Update(nanBar);
@@ -130,7 +134,9 @@ public class DmxTests
var bars = gbm.Fetch(50, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
for (int i = 0; i < 30; i++)
{
dmx.Update(bars[i]);
}
var infBar = new TBar(DateTime.UtcNow, double.PositiveInfinity, double.PositiveInfinity, 0, 100, 100);
var result = dmx.Update(infBar);
@@ -151,7 +157,10 @@ public class DmxTests
// 2. Streaming Mode
var streamDmx = new Dmx(14);
for (int i = 0; i < bars.Count; i++)
{
streamDmx.Update(bars[i]);
}
double streamResult = streamDmx.Last.Value;
Assert.Equal(expected, streamResult, 9);
+19 -1
View File
@@ -109,10 +109,14 @@ public sealed class Dmx : ITValuePublisher
double downMove = _prevBar.Low - input.Low;
if (upMove > downMove && upMove > 0)
{
dmPlusRaw = upMove;
}
if (downMove > upMove && downMove > 0)
{
dmMinusRaw = downMove;
}
double tr1 = input.High - input.Low;
double tr2 = Math.Abs(input.High - _prevBar.Close);
@@ -147,7 +151,9 @@ public sealed class Dmx : ITValuePublisher
{
int count = source.Count;
if (count == 0)
{
return [];
}
var t = new List<long>(count);
var v = new List<double>(count);
@@ -182,13 +188,19 @@ public sealed class Dmx : ITValuePublisher
{
int len = high.Length;
if (len == 0)
{
return;
}
if (low.Length != len || close.Length != len || destination.Length != len)
{
throw new ArgumentException("All input spans must have the same length", nameof(destination));
}
if (period <= 0)
{
throw new ArgumentException("Period must be greater than zero.", nameof(period));
}
// Use single ArrayPool rent with slicing for better cache locality and fewer allocations
// Need 6 buffers of len each: dmPlus, dmMinus, tr, dmPlusSmooth, dmMinusSmooth, trSmooth
@@ -238,10 +250,14 @@ public sealed class Dmx : ITValuePublisher
double dmMinusRaw = 0.0;
if (upMove > downMove && upMove > 0.0)
{
dmPlusRaw = upMove;
}
if (downMove > upMove && downMove > 0.0)
{
dmMinusRaw = downMove;
}
double tr1 = h - l;
double tr2 = Math.Abs(h - pc);
@@ -275,7 +291,9 @@ public sealed class Dmx : ITValuePublisher
finally
{
if (rented != null)
{
ArrayPool<double>.Shared.Return(rented);
}
}
}
@@ -284,4 +302,4 @@ public sealed class Dmx : ITValuePublisher
var dmx = new Dmx(period);
return dmx.Update(source);
}
}
}
+1 -1
View File
@@ -265,4 +265,4 @@ public sealed class Super : ITValuePublisher
var indicator = new Super(period, multiplier);
return indicator.Update(source);
}
}
}