mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-17 10:08:05 +00:00
style patterns
This commit is contained in:
@@ -34,7 +34,9 @@ public class Atan2ValidationTests
|
||||
private static double PineScriptAtan2(double y, double x)
|
||||
{
|
||||
if (y == 0.0 && x == 0.0)
|
||||
{
|
||||
throw new ArgumentException("atan2: Both y and x cannot be zero", nameof(y));
|
||||
}
|
||||
|
||||
double ay = Math.Abs(y);
|
||||
double ax = Math.Abs(x);
|
||||
@@ -50,9 +52,14 @@ public class Atan2ValidationTests
|
||||
}
|
||||
|
||||
if (x < 0.0)
|
||||
{
|
||||
angle = Math.PI - angle;
|
||||
}
|
||||
|
||||
if (y < 0.0)
|
||||
{
|
||||
angle = -angle;
|
||||
}
|
||||
|
||||
return angle;
|
||||
}
|
||||
@@ -303,7 +310,9 @@ public class Atan2ValidationTests
|
||||
|
||||
// Skip origin (angle = 0 with x=1, y=0 is fine, but need to handle numerical zeros)
|
||||
if (Math.Abs(x) < 1e-15 && Math.Abs(y) < 1e-15)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
double dotNet = Math.Atan2(y, x);
|
||||
double pine = PineScriptAtan2(y, x);
|
||||
@@ -348,4 +357,4 @@ public class Atan2ValidationTests
|
||||
|
||||
Assert.True(true); // This is a documentation test
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,10 @@ public class AccelIndicator : Indicator, IWatchlistIndicator
|
||||
|
||||
protected override void OnUpdate(UpdateArgs args)
|
||||
{
|
||||
if (_accel == null || _selector == null) return;
|
||||
if (_accel == null || _selector == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var item = HistoricalData[0, SeekOriginHistory.End];
|
||||
double value = _selector(item);
|
||||
@@ -64,7 +67,9 @@ public class AccelIndicator : Indicator, IWatchlistIndicator
|
||||
{
|
||||
value = _accel!.Last.Value;
|
||||
if (!double.IsFinite(value))
|
||||
{
|
||||
value = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
TValue input = new(time, value);
|
||||
@@ -87,8 +92,16 @@ public class AccelIndicator : Indicator, IWatchlistIndicator
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static IndicatorLineMarker GetMarker(double value)
|
||||
{
|
||||
if (value > 0) return GreenMarker;
|
||||
if (value < 0) return RedMarker;
|
||||
if (value > 0)
|
||||
{
|
||||
return GreenMarker;
|
||||
}
|
||||
|
||||
if (value < 0)
|
||||
{
|
||||
return RedMarker;
|
||||
}
|
||||
|
||||
return GrayMarker;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,11 +254,11 @@ public class AccelTests
|
||||
{
|
||||
var source = new TSeries();
|
||||
var accel = new Accel(source);
|
||||
|
||||
|
||||
source.Add(new TValue(DateTime.UtcNow, 10));
|
||||
source.Add(new TValue(DateTime.UtcNow, 20));
|
||||
source.Add(new TValue(DateTime.UtcNow, 35));
|
||||
|
||||
|
||||
Assert.True(accel.IsHot);
|
||||
Assert.Equal(5, accel.Last.Value); // 35 - 2*20 + 10 = 5
|
||||
}
|
||||
|
||||
+48
-10
@@ -110,7 +110,10 @@ public sealed class Accel : AbstractBase
|
||||
|
||||
public override TSeries Update(TSeries source)
|
||||
{
|
||||
if (source.Count == 0) return [];
|
||||
if (source.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
int len = source.Count;
|
||||
|
||||
@@ -187,16 +190,28 @@ public sealed class Accel : AbstractBase
|
||||
public static void Calculate(ReadOnlySpan<double> source, Span<double> output)
|
||||
{
|
||||
if (source.Length != output.Length)
|
||||
{
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
}
|
||||
|
||||
int len = source.Length;
|
||||
if (len == 0) return;
|
||||
if (len == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// First two elements have insufficient history
|
||||
output[0] = 0.0;
|
||||
if (len == 1) return;
|
||||
if (len == 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
output[1] = 0.0;
|
||||
if (len == 2) return;
|
||||
if (len == 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int i = 2;
|
||||
|
||||
@@ -279,9 +294,20 @@ public sealed class Accel : AbstractBase
|
||||
|
||||
// Handle NaN/Infinity by substitution (find first finite value)
|
||||
double fallback = FindFinite(curr, p1, p2);
|
||||
if (!double.IsFinite(curr)) curr = fallback;
|
||||
if (!double.IsFinite(p1)) p1 = fallback;
|
||||
if (!double.IsFinite(p2)) p2 = fallback;
|
||||
if (!double.IsFinite(curr))
|
||||
{
|
||||
curr = fallback;
|
||||
}
|
||||
|
||||
if (!double.IsFinite(p1))
|
||||
{
|
||||
p1 = fallback;
|
||||
}
|
||||
|
||||
if (!double.IsFinite(p2))
|
||||
{
|
||||
p2 = fallback;
|
||||
}
|
||||
|
||||
// accel = curr - 2*prev1 + prev2
|
||||
output[i] = Math.FusedMultiplyAdd(-2.0, p1, curr + p2);
|
||||
@@ -291,9 +317,21 @@ public sealed class Accel : AbstractBase
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static double FindFinite(double a, double b, double c)
|
||||
{
|
||||
if (double.IsFinite(a)) return a;
|
||||
if (double.IsFinite(b)) return b;
|
||||
if (double.IsFinite(c)) return c;
|
||||
if (double.IsFinite(a))
|
||||
{
|
||||
return a;
|
||||
}
|
||||
|
||||
if (double.IsFinite(b))
|
||||
{
|
||||
return b;
|
||||
}
|
||||
|
||||
if (double.IsFinite(c))
|
||||
{
|
||||
return c;
|
||||
}
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,10 @@ public class ChangeIndicator : Indicator, IWatchlistIndicator
|
||||
|
||||
protected override void OnUpdate(UpdateArgs args)
|
||||
{
|
||||
if (_change == null || _selector == null) return;
|
||||
if (_change == null || _selector == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var item = HistoricalData[0, SeekOriginHistory.End];
|
||||
double value = _selector(item);
|
||||
@@ -76,9 +79,21 @@ public class ChangeIndicator : Indicator, IWatchlistIndicator
|
||||
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
|
||||
private static IndicatorLineMarker GetMarker(double value)
|
||||
{
|
||||
if (!double.IsFinite(value)) return GrayMarker;
|
||||
if (value > 0) return GreenMarker;
|
||||
if (value < 0) return RedMarker;
|
||||
if (!double.IsFinite(value))
|
||||
{
|
||||
return GrayMarker;
|
||||
}
|
||||
|
||||
if (value > 0)
|
||||
{
|
||||
return GreenMarker;
|
||||
}
|
||||
|
||||
if (value < 0)
|
||||
{
|
||||
return RedMarker;
|
||||
}
|
||||
|
||||
return GrayMarker;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,4 +220,4 @@ public class ChangeTests
|
||||
Assert.True(change.IsHot);
|
||||
Assert.NotEqual(0.0, change.Last.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,9 @@ public sealed class Change : AbstractBase
|
||||
public Change(int period = 1)
|
||||
{
|
||||
if (period < 1)
|
||||
{
|
||||
throw new ArgumentException("Period must be >= 1", nameof(period));
|
||||
}
|
||||
|
||||
_period = period;
|
||||
_buffer = new RingBuffer(period + 1);
|
||||
@@ -59,9 +61,13 @@ public sealed class Change : AbstractBase
|
||||
public override TValue Update(TValue input, bool isNew = true)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_p_state = _state;
|
||||
}
|
||||
else
|
||||
{
|
||||
_state = _p_state;
|
||||
}
|
||||
|
||||
double value = double.IsFinite(input.Value) ? input.Value : _state.LastValid;
|
||||
_state = new State(value);
|
||||
@@ -122,11 +128,19 @@ public sealed class Change : AbstractBase
|
||||
public static void Calculate(ReadOnlySpan<double> source, Span<double> output, int period = 1)
|
||||
{
|
||||
if (source.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("Source cannot be empty", nameof(source));
|
||||
}
|
||||
|
||||
if (output.Length < source.Length)
|
||||
{
|
||||
throw new ArgumentException("Output length must be >= source length", nameof(output));
|
||||
}
|
||||
|
||||
if (period < 1)
|
||||
{
|
||||
throw new ArgumentException("Period must be >= 1", nameof(period));
|
||||
}
|
||||
|
||||
// Use ArrayPool for large periods to track past valid values
|
||||
const int StackAllocThreshold = 256;
|
||||
@@ -184,7 +198,9 @@ public sealed class Change : AbstractBase
|
||||
finally
|
||||
{
|
||||
if (pastValidRented != null)
|
||||
{
|
||||
System.Buffers.ArrayPool<double>.Shared.Return(pastValidRented);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,10 @@ public class ExptransIndicator : Indicator, IWatchlistIndicator
|
||||
|
||||
protected override void OnUpdate(UpdateArgs args)
|
||||
{
|
||||
if (_exptrans == null || _selector == null) return;
|
||||
if (_exptrans == null || _selector == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var item = HistoricalData[0, SeekOriginHistory.End];
|
||||
double value = _selector(item);
|
||||
@@ -53,4 +56,4 @@ public class ExptransIndicator : Indicator, IWatchlistIndicator
|
||||
|
||||
LinesSeries[0].SetValue(_exptrans.Last.Value, isHot, ShowColdValues);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,4 +274,4 @@ public class ExptransTests
|
||||
Assert.True(indicator.Last.Value > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,4 +170,4 @@ public class ExptransValidationTests
|
||||
|
||||
Assert.Equal(Math.Pow(expA, n), expNA, 1e-12);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,9 +46,13 @@ public sealed class Exptrans : AbstractBase
|
||||
public override TValue Update(TValue input, bool isNew = true)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_p_state = _state;
|
||||
}
|
||||
else
|
||||
{
|
||||
_state = _p_state;
|
||||
}
|
||||
|
||||
double value = input.Value;
|
||||
double result;
|
||||
@@ -114,9 +118,14 @@ public sealed class Exptrans : AbstractBase
|
||||
public static void Calculate(ReadOnlySpan<double> source, Span<double> output)
|
||||
{
|
||||
if (source.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("Source cannot be empty", nameof(source));
|
||||
}
|
||||
|
||||
if (output.Length < source.Length)
|
||||
{
|
||||
throw new ArgumentException("Output length must be >= source length", nameof(output));
|
||||
}
|
||||
|
||||
double lastValid = 1.0; // exp(0) = 1
|
||||
|
||||
|
||||
@@ -43,7 +43,10 @@ public class HighestIndicator : Indicator, IWatchlistIndicator
|
||||
|
||||
protected override void OnUpdate(UpdateArgs args)
|
||||
{
|
||||
if (_highest == null || _selector == null) return;
|
||||
if (_highest == null || _selector == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var item = HistoricalData[0, SeekOriginHistory.End];
|
||||
double value = _selector(item);
|
||||
|
||||
@@ -297,4 +297,4 @@ public class HighestTests
|
||||
indicator.Update(new TValue(time.AddMinutes(5), 5.0));
|
||||
Assert.Equal(9.0, indicator.Last.Value, Tolerance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,9 @@ public sealed class Highest : AbstractBase
|
||||
public Highest(int period)
|
||||
{
|
||||
if (period < 1)
|
||||
{
|
||||
throw new ArgumentException("Period must be >= 1", nameof(period));
|
||||
}
|
||||
|
||||
_period = period;
|
||||
_buffer = new RingBuffer(period);
|
||||
@@ -58,9 +60,13 @@ public sealed class Highest : AbstractBase
|
||||
public override TValue Update(TValue input, bool isNew = true)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_p_state = _state;
|
||||
}
|
||||
else
|
||||
{
|
||||
_state = _p_state;
|
||||
}
|
||||
|
||||
double value = double.IsFinite(input.Value) ? input.Value : _state.LastValid;
|
||||
_state = new State(value);
|
||||
@@ -112,11 +118,19 @@ public sealed class Highest : AbstractBase
|
||||
public static void Calculate(ReadOnlySpan<double> source, Span<double> output, int period)
|
||||
{
|
||||
if (source.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("Source cannot be empty", nameof(source));
|
||||
}
|
||||
|
||||
if (output.Length < source.Length)
|
||||
{
|
||||
throw new ArgumentException("Output length must be >= source length", nameof(output));
|
||||
}
|
||||
|
||||
if (period < 1)
|
||||
{
|
||||
throw new ArgumentException("Period must be >= 1", nameof(period));
|
||||
}
|
||||
|
||||
int len = source.Length;
|
||||
|
||||
@@ -169,7 +183,11 @@ public sealed class Highest : AbstractBase
|
||||
while (count > 0 && deque[head] <= i - period)
|
||||
{
|
||||
head++;
|
||||
if (head >= capacity) head -= capacity;
|
||||
if (head >= capacity)
|
||||
{
|
||||
head -= capacity;
|
||||
}
|
||||
|
||||
count--;
|
||||
}
|
||||
|
||||
@@ -177,7 +195,11 @@ public sealed class Highest : AbstractBase
|
||||
while (count > 0)
|
||||
{
|
||||
int backIdx = tail - 1;
|
||||
if (backIdx < 0) backIdx += capacity;
|
||||
if (backIdx < 0)
|
||||
{
|
||||
backIdx += capacity;
|
||||
}
|
||||
|
||||
if (values[deque[backIdx]] <= value)
|
||||
{
|
||||
tail = backIdx;
|
||||
@@ -192,7 +214,11 @@ public sealed class Highest : AbstractBase
|
||||
// Add current index at tail
|
||||
deque[tail] = i;
|
||||
tail++;
|
||||
if (tail >= capacity) tail -= capacity;
|
||||
if (tail >= capacity)
|
||||
{
|
||||
tail -= capacity;
|
||||
}
|
||||
|
||||
count++;
|
||||
|
||||
output[i] = values[deque[head]];
|
||||
@@ -201,9 +227,14 @@ public sealed class Highest : AbstractBase
|
||||
finally
|
||||
{
|
||||
if (rentedDeque != null)
|
||||
{
|
||||
System.Buffers.ArrayPool<int>.Shared.Return(rentedDeque);
|
||||
}
|
||||
|
||||
if (rentedValues != null)
|
||||
{
|
||||
System.Buffers.ArrayPool<double>.Shared.Return(rentedValues);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -184,8 +184,8 @@ public class JerkIndicatorTests
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
// Cubic trend: f(x) = x³ has third derivative = 6
|
||||
// Using f(i) = i³, the discrete third differences converge to 6
|
||||
// Cubic trend: f(x) = x³ has third derivative = 6
|
||||
// Using f(i) = i³, the discrete third differences converge to 6
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
double price = 100 + i * i * i; // cubic growth
|
||||
@@ -194,7 +194,7 @@ public class JerkIndicatorTests
|
||||
}
|
||||
|
||||
double lastJerk = indicator.LinesSeries[0].GetValue(0);
|
||||
// For f(x) = x³, discrete third difference = 6
|
||||
// For f(x) = x³, discrete third difference = 6
|
||||
Assert.Equal(6.0, lastJerk, 6);
|
||||
}
|
||||
|
||||
@@ -217,4 +217,4 @@ public class JerkIndicatorTests
|
||||
double lastJerk = indicator.LinesSeries[0].GetValue(0);
|
||||
Assert.Equal(0, lastJerk, 6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,10 @@ public class JerkIndicator : Indicator, IWatchlistIndicator
|
||||
|
||||
protected override void OnUpdate(UpdateArgs args)
|
||||
{
|
||||
if (_jerk == null || _selector == null) return;
|
||||
if (_jerk == null || _selector == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var item = HistoricalData[0, SeekOriginHistory.End];
|
||||
double value = _selector(item);
|
||||
@@ -60,11 +63,18 @@ public class JerkIndicator : Indicator, IWatchlistIndicator
|
||||
double jerk = _jerk.Last.Value;
|
||||
Color color;
|
||||
if (jerk > 0)
|
||||
{
|
||||
color = Color.Green;
|
||||
}
|
||||
else if (jerk < 0)
|
||||
{
|
||||
color = Color.Red;
|
||||
}
|
||||
else
|
||||
{
|
||||
color = Color.Gray;
|
||||
}
|
||||
|
||||
LinesSeries[0].SetMarker(0, new IndicatorLineMarker(color));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,12 +258,12 @@ public class JerkTests
|
||||
{
|
||||
var source = new TSeries();
|
||||
var jerk = new Jerk(source);
|
||||
|
||||
|
||||
source.Add(new TValue(DateTime.UtcNow, 10));
|
||||
source.Add(new TValue(DateTime.UtcNow, 20));
|
||||
source.Add(new TValue(DateTime.UtcNow, 35));
|
||||
source.Add(new TValue(DateTime.UtcNow, 40));
|
||||
|
||||
|
||||
Assert.True(jerk.IsHot);
|
||||
// jerk = 40 - 3*35 + 3*20 - 10 = 40 - 105 + 60 - 10 = -15
|
||||
Assert.Equal(-15, jerk.Last.Value);
|
||||
@@ -304,4 +304,4 @@ public class JerkTests
|
||||
Assert.Equal(jerkResults[i], chainResults[i], precision: 9);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+66
-16
@@ -78,8 +78,8 @@ public sealed class Jerk : AbstractBase
|
||||
|
||||
if (_state.Count >= 3)
|
||||
{
|
||||
// jerk = val - 3*prev1 + 3*prev2 - prev3
|
||||
// Using FMA: val - 3*prev1 + 3*prev2 - prev3
|
||||
// jerk = val - 3*prev1 + 3*prev2 - prev3
|
||||
// Using FMA: val - 3*prev1 + 3*prev2 - prev3
|
||||
// = FMA(-3, prev1, val) + FMA(3, prev2, -prev3)
|
||||
double term1 = Math.FusedMultiplyAdd(-3.0, _state.Prev1, val);
|
||||
double term2 = Math.FusedMultiplyAdd(3.0, _state.Prev2, -_state.Prev3);
|
||||
@@ -127,7 +127,10 @@ public sealed class Jerk : AbstractBase
|
||||
|
||||
public override TSeries Update(TSeries source)
|
||||
{
|
||||
if (source.Count == 0) return [];
|
||||
if (source.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
int len = source.Count;
|
||||
|
||||
@@ -211,18 +214,34 @@ public sealed class Jerk : AbstractBase
|
||||
public static void Calculate(ReadOnlySpan<double> source, Span<double> output)
|
||||
{
|
||||
if (source.Length != output.Length)
|
||||
{
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
}
|
||||
|
||||
int len = source.Length;
|
||||
if (len == 0) return;
|
||||
if (len == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// First three elements have insufficient history
|
||||
output[0] = 0.0;
|
||||
if (len == 1) return;
|
||||
if (len == 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
output[1] = 0.0;
|
||||
if (len == 2) return;
|
||||
if (len == 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
output[2] = 0.0;
|
||||
if (len == 3) return;
|
||||
if (len == 3)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int i = 3;
|
||||
|
||||
@@ -339,10 +358,25 @@ public sealed class Jerk : AbstractBase
|
||||
|
||||
// Handle NaN/Infinity by substitution (find first finite value)
|
||||
double fallback = FindFinite(curr, p1, p2, p3);
|
||||
if (!double.IsFinite(curr)) curr = fallback;
|
||||
if (!double.IsFinite(p1)) p1 = fallback;
|
||||
if (!double.IsFinite(p2)) p2 = fallback;
|
||||
if (!double.IsFinite(p3)) p3 = fallback;
|
||||
if (!double.IsFinite(curr))
|
||||
{
|
||||
curr = fallback;
|
||||
}
|
||||
|
||||
if (!double.IsFinite(p1))
|
||||
{
|
||||
p1 = fallback;
|
||||
}
|
||||
|
||||
if (!double.IsFinite(p2))
|
||||
{
|
||||
p2 = fallback;
|
||||
}
|
||||
|
||||
if (!double.IsFinite(p3))
|
||||
{
|
||||
p3 = fallback;
|
||||
}
|
||||
|
||||
// jerk = curr - 3*prev1 + 3*prev2 - prev3
|
||||
double term1 = Math.FusedMultiplyAdd(-3.0, p1, curr);
|
||||
@@ -354,10 +388,26 @@ public sealed class Jerk : AbstractBase
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static double FindFinite(double a, double b, double c, double d)
|
||||
{
|
||||
if (double.IsFinite(a)) return a;
|
||||
if (double.IsFinite(b)) return b;
|
||||
if (double.IsFinite(c)) return c;
|
||||
if (double.IsFinite(d)) return d;
|
||||
if (double.IsFinite(a))
|
||||
{
|
||||
return a;
|
||||
}
|
||||
|
||||
if (double.IsFinite(b))
|
||||
{
|
||||
return b;
|
||||
}
|
||||
|
||||
if (double.IsFinite(c))
|
||||
{
|
||||
return c;
|
||||
}
|
||||
|
||||
if (double.IsFinite(d))
|
||||
{
|
||||
return d;
|
||||
}
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,4 +134,4 @@ public class LineartransIndicatorTests
|
||||
// Identity transform: 1.0 * 42.5 + 0.0 = 42.5
|
||||
Assert.Equal(42.5, indicator.LinesSeries[0].GetValue(0), 1e-10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,10 @@ public class LineartransIndicator : Indicator, IWatchlistIndicator
|
||||
|
||||
protected override void OnUpdate(UpdateArgs args)
|
||||
{
|
||||
if (_lineartrans == null || _selector == null) return;
|
||||
if (_lineartrans == null || _selector == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var item = HistoricalData[0, SeekOriginHistory.End];
|
||||
double value = _selector(item);
|
||||
@@ -59,4 +62,4 @@ public class LineartransIndicator : Indicator, IWatchlistIndicator
|
||||
|
||||
LinesSeries[0].SetValue(_lineartrans.Last.Value, isHot, ShowColdValues);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,7 +172,9 @@ public class LineartransTests
|
||||
var time = DateTime.UtcNow;
|
||||
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
series.Add(new TValue(time.AddSeconds(i), i * 10.0), true);
|
||||
}
|
||||
|
||||
var result = linear.Update(series);
|
||||
|
||||
@@ -191,7 +193,9 @@ public class LineartransTests
|
||||
var time = DateTime.UtcNow;
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
series.Add(new TValue(time.AddSeconds(i), 10.0 * (i + 1)), true);
|
||||
}
|
||||
|
||||
var result = Lineartrans.Calculate(series, slope: 0.5, intercept: 5.0);
|
||||
|
||||
@@ -257,7 +261,9 @@ public class LineartransTests
|
||||
var streamIndicator = new Lineartrans(slope, intercept);
|
||||
var streamResult = new TSeries();
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
streamResult.Add(streamIndicator.Update(series[i], true), true);
|
||||
}
|
||||
|
||||
// Span
|
||||
var spanOutput = new double[series.Count];
|
||||
@@ -270,4 +276,4 @@ public class LineartransTests
|
||||
Assert.Equal(batchResult[i].Value, spanOutput[i], 1e-10);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,7 +153,9 @@ public class LineartransValidationTests
|
||||
// Create shifted series
|
||||
var shifted = new TSeries();
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
shifted.Add(new TValue(series[i].Time, series[i].Value + offset), true);
|
||||
}
|
||||
|
||||
// a * (x + offset) should equal a*x + a*offset
|
||||
var scaledSum = Lineartrans.Calculate(shifted, a, 0.0);
|
||||
@@ -264,4 +266,4 @@ public class LineartransValidationTests
|
||||
Assert.Equal(expected, result[i].Value, 1e-5);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,9 +38,14 @@ public sealed class Lineartrans : AbstractBase
|
||||
public Lineartrans(double slope = 1.0, double intercept = 0.0)
|
||||
{
|
||||
if (!double.IsFinite(slope))
|
||||
{
|
||||
throw new ArgumentException("Slope must be a finite number", nameof(slope));
|
||||
}
|
||||
|
||||
if (!double.IsFinite(intercept))
|
||||
{
|
||||
throw new ArgumentException("Intercept must be a finite number", nameof(intercept));
|
||||
}
|
||||
|
||||
_slope = slope;
|
||||
_intercept = intercept;
|
||||
@@ -67,9 +72,13 @@ public sealed class Lineartrans : AbstractBase
|
||||
public override TValue Update(TValue input, bool isNew = true)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_p_state = _state;
|
||||
}
|
||||
else
|
||||
{
|
||||
_state = _p_state;
|
||||
}
|
||||
|
||||
double value = input.Value;
|
||||
double result;
|
||||
@@ -129,13 +138,24 @@ public sealed class Lineartrans : AbstractBase
|
||||
double slope = 1.0, double intercept = 0.0)
|
||||
{
|
||||
if (source.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("Source cannot be empty", nameof(source));
|
||||
}
|
||||
|
||||
if (output.Length < source.Length)
|
||||
{
|
||||
throw new ArgumentException("Output length must be >= source length", nameof(output));
|
||||
}
|
||||
|
||||
if (!double.IsFinite(slope))
|
||||
{
|
||||
throw new ArgumentException("Slope must be a finite number", nameof(slope));
|
||||
}
|
||||
|
||||
if (!double.IsFinite(intercept))
|
||||
{
|
||||
throw new ArgumentException("Intercept must be a finite number", nameof(intercept));
|
||||
}
|
||||
|
||||
// Check for non-finite values - if any exist, use scalar path only
|
||||
// Note: For very large arrays, SIMD-based NaN detection could be faster,
|
||||
|
||||
@@ -117,4 +117,4 @@ public class LogtransIndicatorTests
|
||||
Assert.True(double.IsFinite(indicator.LinesSeries[0].GetValue(0)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,10 @@ public class LogtransIndicator : Indicator, IWatchlistIndicator
|
||||
|
||||
protected override void OnUpdate(UpdateArgs args)
|
||||
{
|
||||
if (_logtrans == null || _selector == null) return;
|
||||
if (_logtrans == null || _selector == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var item = HistoricalData[0, SeekOriginHistory.End];
|
||||
double value = _selector(item);
|
||||
@@ -53,4 +56,4 @@ public class LogtransIndicator : Indicator, IWatchlistIndicator
|
||||
|
||||
LinesSeries[0].SetValue(_logtrans.Last.Value, isHot, ShowColdValues);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,4 +258,4 @@ public class LogtransTests
|
||||
// exp(logtrans(x)) should equal x
|
||||
Assert.Equal(original, Math.Exp(logtransResult), Tolerance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,9 +46,13 @@ public sealed class Logtrans : AbstractBase
|
||||
public override TValue Update(TValue input, bool isNew = true)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_p_state = _state;
|
||||
}
|
||||
else
|
||||
{
|
||||
_state = _p_state;
|
||||
}
|
||||
|
||||
// Handle non-positive and non-finite values
|
||||
double value = input.Value;
|
||||
@@ -108,9 +112,14 @@ public sealed class Logtrans : AbstractBase
|
||||
public static void Calculate(ReadOnlySpan<double> source, Span<double> output)
|
||||
{
|
||||
if (source.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("Source cannot be empty", nameof(source));
|
||||
}
|
||||
|
||||
if (output.Length < source.Length)
|
||||
{
|
||||
throw new ArgumentException("Output length must be >= source length", nameof(output));
|
||||
}
|
||||
|
||||
double lastValid = 0.0;
|
||||
|
||||
|
||||
@@ -43,7 +43,10 @@ public class LowestIndicator : Indicator, IWatchlistIndicator
|
||||
|
||||
protected override void OnUpdate(UpdateArgs args)
|
||||
{
|
||||
if (_lowest == null || _selector == null) return;
|
||||
if (_lowest == null || _selector == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var item = HistoricalData[0, SeekOriginHistory.End];
|
||||
double value = _selector(item);
|
||||
|
||||
@@ -33,7 +33,9 @@ public sealed class Lowest : AbstractBase
|
||||
public Lowest(int period)
|
||||
{
|
||||
if (period < 1)
|
||||
{
|
||||
throw new ArgumentException("Period must be >= 1", nameof(period));
|
||||
}
|
||||
|
||||
_period = period;
|
||||
_buffer = new RingBuffer(period);
|
||||
@@ -58,9 +60,13 @@ public sealed class Lowest : AbstractBase
|
||||
public override TValue Update(TValue input, bool isNew = true)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_p_state = _state;
|
||||
}
|
||||
else
|
||||
{
|
||||
_state = _p_state;
|
||||
}
|
||||
|
||||
double value = double.IsFinite(input.Value) ? input.Value : _state.LastValid;
|
||||
_state = new State(value);
|
||||
@@ -112,11 +118,19 @@ public sealed class Lowest : AbstractBase
|
||||
public static void Calculate(ReadOnlySpan<double> source, Span<double> output, int period)
|
||||
{
|
||||
if (source.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("Source cannot be empty", nameof(source));
|
||||
}
|
||||
|
||||
if (output.Length < source.Length)
|
||||
{
|
||||
throw new ArgumentException("Output length must be >= source length", nameof(output));
|
||||
}
|
||||
|
||||
if (period < 1)
|
||||
{
|
||||
throw new ArgumentException("Period must be >= 1", nameof(period));
|
||||
}
|
||||
|
||||
int len = source.Length;
|
||||
|
||||
@@ -162,18 +176,25 @@ public sealed class Lowest : AbstractBase
|
||||
|
||||
// Remove indices outside window
|
||||
while (dequeEnd > dequeStart && deque[dequeStart] <= i - period)
|
||||
{
|
||||
dequeStart++;
|
||||
}
|
||||
|
||||
// Remove larger values from back (use values[] for corrected values)
|
||||
while (dequeEnd > dequeStart && values[deque[dequeEnd - 1]] >= value)
|
||||
{
|
||||
dequeEnd--;
|
||||
}
|
||||
|
||||
// Compact deque if needed
|
||||
if (dequeEnd >= deque.Length)
|
||||
{
|
||||
int count = dequeEnd - dequeStart;
|
||||
for (int j = 0; j < count; j++)
|
||||
{
|
||||
deque[j] = deque[dequeStart + j];
|
||||
}
|
||||
|
||||
dequeStart = 0;
|
||||
dequeEnd = count;
|
||||
}
|
||||
@@ -185,9 +206,14 @@ public sealed class Lowest : AbstractBase
|
||||
finally
|
||||
{
|
||||
if (rentedDeque != null)
|
||||
{
|
||||
System.Buffers.ArrayPool<int>.Shared.Return(rentedDeque);
|
||||
}
|
||||
|
||||
if (rentedValues != null)
|
||||
{
|
||||
System.Buffers.ArrayPool<double>.Shared.Return(rentedValues);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,10 @@ public class MidpointIndicator : Indicator, IWatchlistIndicator
|
||||
|
||||
protected override void OnUpdate(UpdateArgs args)
|
||||
{
|
||||
if (_midpoint == null || _selector == null) return;
|
||||
if (_midpoint == null || _selector == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var item = HistoricalData[0, SeekOriginHistory.End];
|
||||
double value = _selector(item);
|
||||
|
||||
@@ -33,7 +33,9 @@ public sealed class Midpoint : AbstractBase
|
||||
public Midpoint(int period)
|
||||
{
|
||||
if (period < 1)
|
||||
{
|
||||
throw new ArgumentException("Period must be >= 1", nameof(period));
|
||||
}
|
||||
|
||||
_highest = new Highest(period);
|
||||
_lowest = new Lowest(period);
|
||||
@@ -116,11 +118,19 @@ public sealed class Midpoint : AbstractBase
|
||||
public static void Calculate(ReadOnlySpan<double> source, Span<double> output, int period)
|
||||
{
|
||||
if (source.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("Source cannot be empty", nameof(source));
|
||||
}
|
||||
|
||||
if (output.Length < source.Length)
|
||||
{
|
||||
throw new ArgumentException("Output length must be >= source length", nameof(output));
|
||||
}
|
||||
|
||||
if (period < 1)
|
||||
{
|
||||
throw new ArgumentException("Period must be >= 1", nameof(period));
|
||||
}
|
||||
|
||||
int len = source.Length;
|
||||
|
||||
@@ -151,9 +161,14 @@ public sealed class Midpoint : AbstractBase
|
||||
finally
|
||||
{
|
||||
if (rentedHigh != null)
|
||||
{
|
||||
System.Buffers.ArrayPool<double>.Shared.Return(rentedHigh);
|
||||
}
|
||||
|
||||
if (rentedLow != null)
|
||||
{
|
||||
System.Buffers.ArrayPool<double>.Shared.Return(rentedLow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,10 @@ public class NormalizeIndicator : Indicator, IWatchlistIndicator
|
||||
|
||||
protected override void OnUpdate(UpdateArgs args)
|
||||
{
|
||||
if (_normalize == null || _selector == null) return;
|
||||
if (_normalize == null || _selector == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var item = HistoricalData[0, SeekOriginHistory.End];
|
||||
double value = _selector(item);
|
||||
|
||||
@@ -152,7 +152,9 @@ public class NormalizeTests
|
||||
var norm = new Normalize(5);
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
norm.Update(new TValue(DateTime.UtcNow, i * 10));
|
||||
}
|
||||
|
||||
Assert.True(norm.IsHot);
|
||||
|
||||
@@ -198,7 +200,9 @@ public class NormalizeTests
|
||||
var series = _gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
var tseries = new TSeries();
|
||||
foreach (var bar in series)
|
||||
{
|
||||
tseries.Add(new TValue(bar.Time, bar.Close), true);
|
||||
}
|
||||
|
||||
// Static calculation
|
||||
var staticResult = Normalize.Calculate(tseries, 14);
|
||||
@@ -207,7 +211,9 @@ public class NormalizeTests
|
||||
var streamNorm = new Normalize(14);
|
||||
var streamResult = new TSeries();
|
||||
foreach (var bar in series)
|
||||
{
|
||||
streamResult.Add(streamNorm.Update(new TValue(bar.Time, bar.Close)), true);
|
||||
}
|
||||
|
||||
// Compare last 50 values
|
||||
for (int i = 50; i < 100; i++)
|
||||
@@ -261,4 +267,4 @@ public class NormalizeTests
|
||||
var result = norm.Update(new TValue(DateTime.UtcNow, 60));
|
||||
Assert.Equal(0.2, result.Value, 1e-10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,9 @@ public class NormalizeValidationTests
|
||||
double[] values = { 10, 20, 30, 40, 50 };
|
||||
|
||||
foreach (var v in values)
|
||||
{
|
||||
norm.Update(new TValue(DateTime.UtcNow, v));
|
||||
}
|
||||
|
||||
// Max value (50) should normalize to 1.0
|
||||
Assert.Equal(1.0, norm.Last.Value, 1e-10);
|
||||
@@ -55,7 +57,9 @@ public class NormalizeValidationTests
|
||||
double[] values = { 50, 40, 30, 20, 10 };
|
||||
|
||||
foreach (var v in values)
|
||||
{
|
||||
norm.Update(new TValue(DateTime.UtcNow, v));
|
||||
}
|
||||
|
||||
// Min value (10) should normalize to 0.0
|
||||
Assert.Equal(0.0, norm.Last.Value, 1e-10);
|
||||
@@ -98,7 +102,9 @@ public class NormalizeValidationTests
|
||||
|
||||
// All same values
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
norm.Update(new TValue(DateTime.UtcNow, 42.0));
|
||||
}
|
||||
|
||||
// Flat range: should return 0.5
|
||||
Assert.Equal(0.5, norm.Last.Value, 1e-10);
|
||||
@@ -223,7 +229,10 @@ public class NormalizeValidationTests
|
||||
// Mode 2: Batch via Update(TSeries)
|
||||
var tseries = new TSeries();
|
||||
foreach (var bar in series)
|
||||
{
|
||||
tseries.Add(new TValue(bar.Time, bar.Close), true);
|
||||
}
|
||||
|
||||
var results2 = Normalize.Calculate(tseries, period);
|
||||
|
||||
// Mode 3: Static span Calculate
|
||||
@@ -235,7 +244,10 @@ public class NormalizeValidationTests
|
||||
var source = new TSeries();
|
||||
var norm4 = new Normalize(source, period);
|
||||
foreach (var bar in series)
|
||||
{
|
||||
source.Add(new TValue(bar.Time, bar.Close), true);
|
||||
}
|
||||
|
||||
var results4 = norm4.Last.Value;
|
||||
|
||||
// Compare all modes (use last 50 values for stability)
|
||||
@@ -300,4 +312,4 @@ public class NormalizeValidationTests
|
||||
|
||||
Assert.True(norm.IsHot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,9 @@ public sealed class Normalize : AbstractBase
|
||||
public Normalize(int period = 14)
|
||||
{
|
||||
if (period < 1)
|
||||
{
|
||||
throw new ArgumentException("Period must be >= 1", nameof(period));
|
||||
}
|
||||
|
||||
_period = period;
|
||||
_buffer = new RingBuffer(period);
|
||||
@@ -64,7 +66,9 @@ public sealed class Normalize : AbstractBase
|
||||
private static (double min, double max) FindMinMax(ReadOnlySpan<double> values)
|
||||
{
|
||||
if (values.Length == 0)
|
||||
{
|
||||
return (double.MaxValue, double.MinValue);
|
||||
}
|
||||
|
||||
double min = values[0];
|
||||
double max = values[0];
|
||||
@@ -72,8 +76,15 @@ public sealed class Normalize : AbstractBase
|
||||
for (int i = 1; i < values.Length; i++)
|
||||
{
|
||||
double v = values[i];
|
||||
if (v < min) min = v;
|
||||
if (v > max) max = v;
|
||||
if (v < min)
|
||||
{
|
||||
min = v;
|
||||
}
|
||||
|
||||
if (v > max)
|
||||
{
|
||||
max = v;
|
||||
}
|
||||
}
|
||||
|
||||
return (min, max);
|
||||
@@ -83,9 +94,13 @@ public sealed class Normalize : AbstractBase
|
||||
public override TValue Update(TValue input, bool isNew = true)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_p_state = _state;
|
||||
}
|
||||
else
|
||||
{
|
||||
_state = _p_state;
|
||||
}
|
||||
|
||||
double value = input.Value;
|
||||
double result;
|
||||
@@ -151,11 +166,19 @@ public sealed class Normalize : AbstractBase
|
||||
public static void Calculate(ReadOnlySpan<double> source, Span<double> output, int period = 14)
|
||||
{
|
||||
if (source.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("Source cannot be empty", nameof(source));
|
||||
}
|
||||
|
||||
if (output.Length < source.Length)
|
||||
{
|
||||
throw new ArgumentException("Output length must be >= source length", nameof(output));
|
||||
}
|
||||
|
||||
if (period < 1)
|
||||
{
|
||||
throw new ArgumentException("Period must be >= 1", nameof(period));
|
||||
}
|
||||
|
||||
double lastValid = 0.5;
|
||||
|
||||
@@ -181,8 +204,15 @@ public sealed class Normalize : AbstractBase
|
||||
double v = source[j];
|
||||
if (double.IsFinite(v))
|
||||
{
|
||||
if (v < min) min = v;
|
||||
if (v > max) max = v;
|
||||
if (v < min)
|
||||
{
|
||||
min = v;
|
||||
}
|
||||
|
||||
if (v > max)
|
||||
{
|
||||
max = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,10 @@ public class ReluIndicator : Indicator, IWatchlistIndicator
|
||||
|
||||
protected override void OnUpdate(UpdateArgs args)
|
||||
{
|
||||
if (_relu == null || _selector == null) return;
|
||||
if (_relu == null || _selector == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var item = HistoricalData[0, SeekOriginHistory.End];
|
||||
double value = _selector(item);
|
||||
|
||||
@@ -61,9 +61,13 @@ public sealed class Relu : AbstractBase
|
||||
public override TValue Update(TValue input, bool isNew = true)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_p_state = _state;
|
||||
}
|
||||
else
|
||||
{
|
||||
_state = _p_state;
|
||||
}
|
||||
|
||||
double value = input.Value;
|
||||
double result;
|
||||
@@ -85,7 +89,10 @@ public sealed class Relu : AbstractBase
|
||||
|
||||
public override TSeries Update(TSeries source)
|
||||
{
|
||||
if (source.Count == 0) return new TSeries([], []);
|
||||
if (source.Count == 0)
|
||||
{
|
||||
return new TSeries([], []);
|
||||
}
|
||||
|
||||
int len = source.Count;
|
||||
var t = new List<long>(len);
|
||||
@@ -135,9 +142,14 @@ public sealed class Relu : AbstractBase
|
||||
public static void Calculate(ReadOnlySpan<double> source, Span<double> output)
|
||||
{
|
||||
if (source.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("Source cannot be empty", nameof(source));
|
||||
}
|
||||
|
||||
if (output.Length < source.Length)
|
||||
{
|
||||
throw new ArgumentException("Output length must be >= source length", nameof(output));
|
||||
}
|
||||
|
||||
double lastValid = 0.0;
|
||||
int i = 0;
|
||||
@@ -207,4 +219,4 @@ public sealed class Relu : AbstractBase
|
||||
_p_state = default;
|
||||
Last = default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,4 +162,4 @@ public class SigmoidIndicatorTests
|
||||
now = now.AddMinutes(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,10 @@ public class SigmoidIndicator : Indicator, IWatchlistIndicator
|
||||
|
||||
protected override void OnUpdate(UpdateArgs args)
|
||||
{
|
||||
if (_sigmoid == null || _selector == null) return;
|
||||
if (_sigmoid == null || _selector == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var item = HistoricalData[0, SeekOriginHistory.End];
|
||||
double value = _selector(item);
|
||||
|
||||
@@ -219,7 +219,9 @@ public class SigmoidTests
|
||||
var series = new TSeries();
|
||||
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
series.Add(new TValue(DateTime.UtcNow.AddSeconds(i), i - 50), isNew: true);
|
||||
}
|
||||
|
||||
var result = sigmoid.Update(series);
|
||||
|
||||
@@ -232,7 +234,9 @@ public class SigmoidTests
|
||||
var series = new TSeries();
|
||||
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
series.Add(new TValue(DateTime.UtcNow.AddSeconds(i), i - 50), isNew: true);
|
||||
}
|
||||
|
||||
var result = Sigmoid.Calculate(series);
|
||||
|
||||
@@ -274,7 +278,9 @@ public class SigmoidTests
|
||||
double[] source = new double[100];
|
||||
var rng = new Random(42);
|
||||
for (int i = 0; i < source.Length; i++)
|
||||
{
|
||||
source[i] = rng.NextDouble() * 200 - 100;
|
||||
}
|
||||
|
||||
double[] spanOutput = new double[source.Length];
|
||||
Sigmoid.Calculate(source.AsSpan(), spanOutput.AsSpan());
|
||||
@@ -282,10 +288,14 @@ public class SigmoidTests
|
||||
var sigmoid = new Sigmoid();
|
||||
double[] streamOutput = new double[source.Length];
|
||||
for (int i = 0; i < source.Length; i++)
|
||||
{
|
||||
streamOutput[i] = sigmoid.Update(new TValue(DateTime.UtcNow.AddSeconds(i), source[i]), true).Value;
|
||||
}
|
||||
|
||||
for (int i = 0; i < source.Length; i++)
|
||||
{
|
||||
Assert.Equal(streamOutput[i], spanOutput[i], Epsilon);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -315,7 +325,9 @@ public class SigmoidTests
|
||||
sigmoid.Pub += (_, in _) => eventCount++;
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
source.Add(new TValue(DateTime.UtcNow.AddSeconds(i), i), isNew: true);
|
||||
}
|
||||
|
||||
Assert.Equal(10, eventCount);
|
||||
}
|
||||
@@ -351,4 +363,4 @@ public class SigmoidTests
|
||||
Assert.Equal(0.5, result0.Value, Epsilon);
|
||||
Assert.Equal(0.5, result100.Value, Epsilon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,7 +222,9 @@ public class SigmoidValidationTests
|
||||
double[] source = new double[500];
|
||||
var rng = new Random(42);
|
||||
for (int i = 0; i < source.Length; i++)
|
||||
{
|
||||
source[i] = rng.NextDouble() * 200 - 50; // Range [-50, 150]
|
||||
}
|
||||
|
||||
// Span calculation
|
||||
double[] spanOutput = new double[source.Length];
|
||||
@@ -236,4 +238,4 @@ public class SigmoidValidationTests
|
||||
Assert.Equal(spanOutput[i], result.Value, Epsilon);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,9 @@ public sealed class Sigmoid : AbstractBase
|
||||
public Sigmoid(double k = 1.0, double x0 = 0.0)
|
||||
{
|
||||
if (k <= 0)
|
||||
{
|
||||
throw new ArgumentException("Steepness (k) must be positive", nameof(k));
|
||||
}
|
||||
|
||||
_k = k;
|
||||
_x0 = x0;
|
||||
@@ -65,8 +67,16 @@ public sealed class Sigmoid : AbstractBase
|
||||
{
|
||||
double exponent = -k * (x - x0);
|
||||
// Guard against overflow: exp(>709) overflows, exp(<-709) underflows to 0
|
||||
if (exponent > 700) return 0.0; // exp(-700) ≈ 0
|
||||
if (exponent < -700) return 1.0; // 1/(1+0) = 1
|
||||
if (exponent > 700)
|
||||
{
|
||||
return 0.0; // exp(-700) ≈ 0
|
||||
}
|
||||
|
||||
if (exponent < -700)
|
||||
{
|
||||
return 1.0; // 1/(1+0) = 1
|
||||
}
|
||||
|
||||
return 1.0 / (1.0 + Math.Exp(exponent));
|
||||
}
|
||||
|
||||
@@ -74,9 +84,13 @@ public sealed class Sigmoid : AbstractBase
|
||||
public override TValue Update(TValue input, bool isNew = true)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_p_state = _state;
|
||||
}
|
||||
else
|
||||
{
|
||||
_state = _p_state;
|
||||
}
|
||||
|
||||
double value = input.Value;
|
||||
double result;
|
||||
@@ -134,11 +148,19 @@ public sealed class Sigmoid : AbstractBase
|
||||
public static void Calculate(ReadOnlySpan<double> source, Span<double> output, double k = 1.0, double x0 = 0.0)
|
||||
{
|
||||
if (source.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("Source cannot be empty", nameof(source));
|
||||
}
|
||||
|
||||
if (output.Length < source.Length)
|
||||
{
|
||||
throw new ArgumentException("Output length must be >= source length", nameof(output));
|
||||
}
|
||||
|
||||
if (k <= 0)
|
||||
{
|
||||
throw new ArgumentException("Steepness (k) must be positive", nameof(k));
|
||||
}
|
||||
|
||||
double lastValid = 0.5; // Sigmoid(x0) = 0.5
|
||||
int i = 0;
|
||||
|
||||
@@ -41,7 +41,10 @@ public class SlopeIndicator : Indicator, IWatchlistIndicator
|
||||
|
||||
protected override void OnUpdate(UpdateArgs args)
|
||||
{
|
||||
if (_slope == null || _selector == null) return;
|
||||
if (_slope == null || _selector == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var item = HistoricalData[0, SeekOriginHistory.End];
|
||||
double value = _selector(item);
|
||||
@@ -60,11 +63,18 @@ public class SlopeIndicator : Indicator, IWatchlistIndicator
|
||||
double slope = _slope.Last.Value;
|
||||
Color color;
|
||||
if (slope > 0)
|
||||
{
|
||||
color = Color.Green;
|
||||
}
|
||||
else if (slope < 0)
|
||||
{
|
||||
color = Color.Red;
|
||||
}
|
||||
else
|
||||
{
|
||||
color = Color.Gray;
|
||||
}
|
||||
|
||||
LinesSeries[0].SetMarker(0, new IndicatorLineMarker(color));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,10 +241,10 @@ public class SlopeTests
|
||||
{
|
||||
var source = new TSeries();
|
||||
var slope = new Slope(source);
|
||||
|
||||
|
||||
source.Add(new TValue(DateTime.UtcNow, 10));
|
||||
source.Add(new TValue(DateTime.UtcNow, 20));
|
||||
|
||||
|
||||
Assert.True(slope.IsHot);
|
||||
Assert.Equal(10, slope.Last.Value);
|
||||
}
|
||||
|
||||
@@ -98,7 +98,10 @@ public sealed class Slope : AbstractBase
|
||||
|
||||
public override TSeries Update(TSeries source)
|
||||
{
|
||||
if (source.Count == 0) return [];
|
||||
if (source.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
int len = source.Count;
|
||||
ReadOnlySpan<double> sourceValues = source.Values;
|
||||
@@ -155,14 +158,22 @@ public sealed class Slope : AbstractBase
|
||||
public static void Calculate(ReadOnlySpan<double> source, Span<double> output)
|
||||
{
|
||||
if (source.Length != output.Length)
|
||||
{
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
}
|
||||
|
||||
int len = source.Length;
|
||||
if (len == 0) return;
|
||||
if (len == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// First element has no previous - set to 0
|
||||
output[0] = 0.0;
|
||||
if (len == 1) return;
|
||||
if (len == 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int i = 1;
|
||||
|
||||
|
||||
@@ -137,4 +137,4 @@ public class SqrttransIndicatorTests
|
||||
Assert.Equal(expectedRoots[i], indicator.LinesSeries[0].GetValue(0), 1e-10);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,10 @@ public class SqrttransIndicator : Indicator, IWatchlistIndicator
|
||||
|
||||
protected override void OnUpdate(UpdateArgs args)
|
||||
{
|
||||
if (_sqrttrans == null || _selector == null) return;
|
||||
if (_sqrttrans == null || _selector == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var item = HistoricalData[0, SeekOriginHistory.End];
|
||||
double value = _selector(item);
|
||||
@@ -53,4 +56,4 @@ public class SqrttransIndicator : Indicator, IWatchlistIndicator
|
||||
|
||||
LinesSeries[0].SetValue(_sqrttrans.Last.Value, isHot, ShowColdValues);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,4 +324,4 @@ public class SqrttransTests
|
||||
Assert.True(indicator.Last.Value >= 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,4 +236,4 @@ public class SqrttransValidationTests
|
||||
Assert.Equal(Math.Sqrt(largeValues[i]), indicator.Last.Value, Tolerance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,9 +46,13 @@ public sealed class Sqrttrans : AbstractBase
|
||||
public override TValue Update(TValue input, bool isNew = true)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_p_state = _state;
|
||||
}
|
||||
else
|
||||
{
|
||||
_state = _p_state;
|
||||
}
|
||||
|
||||
double value = input.Value;
|
||||
double result;
|
||||
@@ -107,9 +111,14 @@ public sealed class Sqrttrans : AbstractBase
|
||||
public static void Calculate(ReadOnlySpan<double> source, Span<double> output)
|
||||
{
|
||||
if (source.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("Source cannot be empty", nameof(source));
|
||||
}
|
||||
|
||||
if (output.Length < source.Length)
|
||||
{
|
||||
throw new ArgumentException("Output length must be >= source length", nameof(output));
|
||||
}
|
||||
|
||||
double lastValid = 0.0; // sqrt(0) = 0
|
||||
|
||||
|
||||
Reference in New Issue
Block a user