mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-23 13:08:04 +00:00
style patterns
This commit is contained in:
@@ -37,6 +37,10 @@ dotnet_diagnostic.S3236.severity = none
|
|||||||
dotnet_diagnostic.MA0046.severity = none
|
dotnet_diagnostic.MA0046.severity = none
|
||||||
dotnet_diagnostic.MA0003.severity = suggestion
|
dotnet_diagnostic.MA0003.severity = suggestion
|
||||||
|
|
||||||
|
# Require curly braces on control structures
|
||||||
|
csharp_prefer_braces = true:warning
|
||||||
|
dotnet_diagnostic.IDE0011.severity = warning
|
||||||
|
|
||||||
csharp_style_var_for_built_in_types = false:silent
|
csharp_style_var_for_built_in_types = false:silent
|
||||||
csharp_style_var_when_type_is_apparent = true:suggestion
|
csharp_style_var_when_type_is_apparent = true:suggestion
|
||||||
csharp_style_var_elsewhere = false:silent
|
csharp_style_var_elsewhere = false:silent
|
||||||
|
|||||||
@@ -73,7 +73,10 @@ public sealed class AbberIndicator : Indicator, IWatchlistIndicator
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
protected override void OnUpdate(UpdateArgs args)
|
protected override void OnUpdate(UpdateArgs args)
|
||||||
{
|
{
|
||||||
if (HistoricalData.Count == 0 || _abber is null || _selector is null) return;
|
if (HistoricalData.Count == 0 || _abber is null || _selector is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var item = HistoricalData[0, SeekOriginHistory.End];
|
var item = HistoricalData[0, SeekOriginHistory.End];
|
||||||
double value = _selector(item);
|
double value = _selector(item);
|
||||||
|
|||||||
@@ -421,4 +421,4 @@ public sealed class AbberValidationTests(ITestOutputHelper output) : IDisposable
|
|||||||
|
|
||||||
output.WriteLine($"Abber consistency across {periods.Length} periods validated successfully");
|
output.WriteLine($"Abber consistency across {periods.Length} periods validated successfully");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,9 +93,14 @@ public sealed class Abber : ITValuePublisher
|
|||||||
public Abber(int period, double multiplier = 2.0)
|
public Abber(int period, double multiplier = 2.0)
|
||||||
{
|
{
|
||||||
if (period <= 0)
|
if (period <= 0)
|
||||||
|
{
|
||||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||||
|
}
|
||||||
|
|
||||||
if (multiplier <= 0)
|
if (multiplier <= 0)
|
||||||
|
{
|
||||||
throw new ArgumentException("Multiplier must be greater than 0", nameof(multiplier));
|
throw new ArgumentException("Multiplier must be greater than 0", nameof(multiplier));
|
||||||
|
}
|
||||||
|
|
||||||
_period = period;
|
_period = period;
|
||||||
_multiplier = multiplier;
|
_multiplier = multiplier;
|
||||||
@@ -235,7 +240,9 @@ public sealed class Abber : ITValuePublisher
|
|||||||
public (TSeries Middle, TSeries Upper, TSeries Lower) Update(TSeries source)
|
public (TSeries Middle, TSeries Upper, TSeries Lower) Update(TSeries source)
|
||||||
{
|
{
|
||||||
if (source.Count == 0)
|
if (source.Count == 0)
|
||||||
|
{
|
||||||
return (new TSeries([], []), new TSeries([], []), new TSeries([], []));
|
return (new TSeries([], []), new TSeries([], []), new TSeries([], []));
|
||||||
|
}
|
||||||
|
|
||||||
int len = source.Count;
|
int len = source.Count;
|
||||||
var tMiddle = new List<long>(len);
|
var tMiddle = new List<long>(len);
|
||||||
@@ -302,7 +309,10 @@ public sealed class Abber : ITValuePublisher
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void Prime(TSeries source)
|
public void Prime(TSeries source)
|
||||||
{
|
{
|
||||||
if (source.Count == 0) return;
|
if (source.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Reset state
|
// Reset state
|
||||||
_sourceBuffer.Clear();
|
_sourceBuffer.Clear();
|
||||||
@@ -483,13 +493,24 @@ public sealed class Abber : ITValuePublisher
|
|||||||
{
|
{
|
||||||
int len = source.Length;
|
int len = source.Length;
|
||||||
if (middle.Length < len || upper.Length < len || lower.Length < len)
|
if (middle.Length < len || upper.Length < len || lower.Length < len)
|
||||||
|
{
|
||||||
throw new ArgumentException("Output buffers must be at least as long as input", nameof(middle));
|
throw new ArgumentException("Output buffers must be at least as long as input", nameof(middle));
|
||||||
if (period <= 0)
|
}
|
||||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
|
||||||
if (multiplier <= 0)
|
|
||||||
throw new ArgumentException("Multiplier must be greater than 0", nameof(multiplier));
|
|
||||||
|
|
||||||
if (len == 0) return;
|
if (period <= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (multiplier <= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Multiplier must be greater than 0", nameof(multiplier));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Scalar implementation with NaN handling
|
// Scalar implementation with NaN handling
|
||||||
var outputs = new BatchOutputs(middle, upper, lower);
|
var outputs = new BatchOutputs(middle, upper, lower);
|
||||||
@@ -626,7 +647,10 @@ public sealed class Abber : ITValuePublisher
|
|||||||
buffers.Deviation[state.BufferIndex] = deviation;
|
buffers.Deviation[state.BufferIndex] = deviation;
|
||||||
|
|
||||||
state.BufferIndex++;
|
state.BufferIndex++;
|
||||||
if (state.BufferIndex >= period) state.BufferIndex = 0;
|
if (state.BufferIndex >= period)
|
||||||
|
{
|
||||||
|
state.BufferIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
double middle = state.SumSource / period;
|
double middle = state.SumSource / period;
|
||||||
double avgDeviation = state.SumDeviation / period;
|
double avgDeviation = state.SumDeviation / period;
|
||||||
@@ -649,4 +673,4 @@ public sealed class Abber : ITValuePublisher
|
|||||||
var results = abber.Update(source);
|
var results = abber.Update(source);
|
||||||
return (results, abber);
|
return (results, abber);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -189,4 +189,4 @@ public class AccBandsIndicatorTests
|
|||||||
indicator.Factor = 3.5;
|
indicator.Factor = 3.5;
|
||||||
Assert.Equal(3.5, indicator.Factor);
|
Assert.Equal(3.5, indicator.Factor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,10 @@ public sealed class AccBandsIndicator : Indicator, IWatchlistIndicator
|
|||||||
|
|
||||||
protected override void OnUpdate(UpdateArgs args)
|
protected override void OnUpdate(UpdateArgs args)
|
||||||
{
|
{
|
||||||
if (_accBands == null) return;
|
if (_accBands == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var item = HistoricalData[0, SeekOriginHistory.End];
|
var item = HistoricalData[0, SeekOriginHistory.End];
|
||||||
bool isNew = args.IsNewBar();
|
bool isNew = args.IsNewBar();
|
||||||
@@ -78,4 +81,4 @@ public sealed class AccBandsIndicator : Indicator, IWatchlistIndicator
|
|||||||
// Lower band
|
// Lower band
|
||||||
LinesSeries[2].SetValue(_accBands.Lower.Value, isHot, ShowColdValues);
|
LinesSeries[2].SetValue(_accBands.Lower.Value, isHot, ShowColdValues);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,9 +96,14 @@ public sealed class AccBands : ITValuePublisher, IDisposable
|
|||||||
public AccBands(int period, double factor = 2.0)
|
public AccBands(int period, double factor = 2.0)
|
||||||
{
|
{
|
||||||
if (period <= 0)
|
if (period <= 0)
|
||||||
|
{
|
||||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||||
|
}
|
||||||
|
|
||||||
if (factor <= 0)
|
if (factor <= 0)
|
||||||
|
{
|
||||||
throw new ArgumentException("Factor must be greater than 0", nameof(factor));
|
throw new ArgumentException("Factor must be greater than 0", nameof(factor));
|
||||||
|
}
|
||||||
|
|
||||||
_period = period;
|
_period = period;
|
||||||
_factor = factor;
|
_factor = factor;
|
||||||
@@ -125,7 +130,11 @@ public sealed class AccBands : ITValuePublisher, IDisposable
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (_disposed) return;
|
if (_disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
|
|
||||||
if (_source != null)
|
if (_source != null)
|
||||||
@@ -271,7 +280,9 @@ public sealed class AccBands : ITValuePublisher, IDisposable
|
|||||||
public (TSeries Middle, TSeries Upper, TSeries Lower) Update(TBarSeries source)
|
public (TSeries Middle, TSeries Upper, TSeries Lower) Update(TBarSeries source)
|
||||||
{
|
{
|
||||||
if (source.Count == 0)
|
if (source.Count == 0)
|
||||||
|
{
|
||||||
return (new TSeries([], []), new TSeries([], []), new TSeries([], []));
|
return (new TSeries([], []), new TSeries([], []), new TSeries([], []));
|
||||||
|
}
|
||||||
|
|
||||||
int len = source.Count;
|
int len = source.Count;
|
||||||
var tMiddle = new List<long>(len);
|
var tMiddle = new List<long>(len);
|
||||||
@@ -315,7 +326,10 @@ public sealed class AccBands : ITValuePublisher, IDisposable
|
|||||||
// skipcq: CS-R1140
|
// skipcq: CS-R1140
|
||||||
public void Prime(TBarSeries source)
|
public void Prime(TBarSeries source)
|
||||||
{
|
{
|
||||||
if (source.Count == 0) return;
|
if (source.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Reset state
|
// Reset state
|
||||||
_highBuffer.Clear();
|
_highBuffer.Clear();
|
||||||
@@ -336,13 +350,24 @@ public sealed class AccBands : ITValuePublisher, IDisposable
|
|||||||
{
|
{
|
||||||
var bar = source[i];
|
var bar = source[i];
|
||||||
if (double.IsFinite(bar.High) && double.IsNaN(_state.LastValidHigh))
|
if (double.IsFinite(bar.High) && double.IsNaN(_state.LastValidHigh))
|
||||||
|
{
|
||||||
_state.LastValidHigh = bar.High;
|
_state.LastValidHigh = bar.High;
|
||||||
|
}
|
||||||
|
|
||||||
if (double.IsFinite(bar.Low) && double.IsNaN(_state.LastValidLow))
|
if (double.IsFinite(bar.Low) && double.IsNaN(_state.LastValidLow))
|
||||||
|
{
|
||||||
_state.LastValidLow = bar.Low;
|
_state.LastValidLow = bar.Low;
|
||||||
|
}
|
||||||
|
|
||||||
if (double.IsFinite(bar.Close) && double.IsNaN(_state.LastValidClose))
|
if (double.IsFinite(bar.Close) && double.IsNaN(_state.LastValidClose))
|
||||||
|
{
|
||||||
_state.LastValidClose = bar.Close;
|
_state.LastValidClose = bar.Close;
|
||||||
|
}
|
||||||
|
|
||||||
if (!double.IsNaN(_state.LastValidHigh) && !double.IsNaN(_state.LastValidLow) && !double.IsNaN(_state.LastValidClose))
|
if (!double.IsNaN(_state.LastValidHigh) && !double.IsNaN(_state.LastValidLow) && !double.IsNaN(_state.LastValidClose))
|
||||||
|
{
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find valid values in warmup window if not found
|
// Find valid values in warmup window if not found
|
||||||
@@ -352,13 +377,24 @@ public sealed class AccBands : ITValuePublisher, IDisposable
|
|||||||
{
|
{
|
||||||
var bar = source[i];
|
var bar = source[i];
|
||||||
if (double.IsFinite(bar.High) && double.IsNaN(_state.LastValidHigh))
|
if (double.IsFinite(bar.High) && double.IsNaN(_state.LastValidHigh))
|
||||||
|
{
|
||||||
_state.LastValidHigh = bar.High;
|
_state.LastValidHigh = bar.High;
|
||||||
|
}
|
||||||
|
|
||||||
if (double.IsFinite(bar.Low) && double.IsNaN(_state.LastValidLow))
|
if (double.IsFinite(bar.Low) && double.IsNaN(_state.LastValidLow))
|
||||||
|
{
|
||||||
_state.LastValidLow = bar.Low;
|
_state.LastValidLow = bar.Low;
|
||||||
|
}
|
||||||
|
|
||||||
if (double.IsFinite(bar.Close) && double.IsNaN(_state.LastValidClose))
|
if (double.IsFinite(bar.Close) && double.IsNaN(_state.LastValidClose))
|
||||||
|
{
|
||||||
_state.LastValidClose = bar.Close;
|
_state.LastValidClose = bar.Close;
|
||||||
|
}
|
||||||
|
|
||||||
if (!double.IsNaN(_state.LastValidHigh) && !double.IsNaN(_state.LastValidLow) && !double.IsNaN(_state.LastValidClose))
|
if (!double.IsNaN(_state.LastValidHigh) && !double.IsNaN(_state.LastValidLow) && !double.IsNaN(_state.LastValidClose))
|
||||||
|
{
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -578,15 +614,29 @@ public sealed class AccBands : ITValuePublisher, IDisposable
|
|||||||
{
|
{
|
||||||
int len = close.Length;
|
int len = close.Length;
|
||||||
if (high.Length != len || low.Length != len)
|
if (high.Length != len || low.Length != len)
|
||||||
|
{
|
||||||
throw new ArgumentException("High, Low, and Close must have the same length", nameof(high));
|
throw new ArgumentException("High, Low, and Close must have the same length", nameof(high));
|
||||||
if (middle.Length < len || upper.Length < len || lower.Length < len)
|
}
|
||||||
throw new ArgumentException("Output buffers must be at least as long as input", nameof(middle));
|
|
||||||
if (period <= 0)
|
|
||||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
|
||||||
if (factor <= 0)
|
|
||||||
throw new ArgumentException("Factor must be greater than 0", nameof(factor));
|
|
||||||
|
|
||||||
if (len == 0) return;
|
if (middle.Length < len || upper.Length < len || lower.Length < len)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Output buffers must be at least as long as input", nameof(middle));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (period <= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (factor <= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Factor must be greater than 0", nameof(factor));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Scalar implementation with NaN handling
|
// Scalar implementation with NaN handling
|
||||||
var inputs = new BatchInputs(high, low, close);
|
var inputs = new BatchInputs(high, low, close);
|
||||||
@@ -643,13 +693,24 @@ public sealed class AccBands : ITValuePublisher, IDisposable
|
|||||||
for (int k = 0; k < len; k++)
|
for (int k = 0; k < len; k++)
|
||||||
{
|
{
|
||||||
if (double.IsFinite(inputs.High[k]) && double.IsNaN(state.LastValidHigh))
|
if (double.IsFinite(inputs.High[k]) && double.IsNaN(state.LastValidHigh))
|
||||||
|
{
|
||||||
state.LastValidHigh = inputs.High[k];
|
state.LastValidHigh = inputs.High[k];
|
||||||
|
}
|
||||||
|
|
||||||
if (double.IsFinite(inputs.Low[k]) && double.IsNaN(state.LastValidLow))
|
if (double.IsFinite(inputs.Low[k]) && double.IsNaN(state.LastValidLow))
|
||||||
|
{
|
||||||
state.LastValidLow = inputs.Low[k];
|
state.LastValidLow = inputs.Low[k];
|
||||||
|
}
|
||||||
|
|
||||||
if (double.IsFinite(inputs.Close[k]) && double.IsNaN(state.LastValidClose))
|
if (double.IsFinite(inputs.Close[k]) && double.IsNaN(state.LastValidClose))
|
||||||
|
{
|
||||||
state.LastValidClose = inputs.Close[k];
|
state.LastValidClose = inputs.Close[k];
|
||||||
|
}
|
||||||
|
|
||||||
if (!double.IsNaN(state.LastValidHigh) && !double.IsNaN(state.LastValidLow) && !double.IsNaN(state.LastValidClose))
|
if (!double.IsNaN(state.LastValidHigh) && !double.IsNaN(state.LastValidLow) && !double.IsNaN(state.LastValidClose))
|
||||||
|
{
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -660,9 +721,32 @@ public sealed class AccBands : ITValuePublisher, IDisposable
|
|||||||
double l = inputs.Low[i];
|
double l = inputs.Low[i];
|
||||||
double c = inputs.Close[i];
|
double c = inputs.Close[i];
|
||||||
|
|
||||||
if (double.IsFinite(h)) state.LastValidHigh = h; else h = state.LastValidHigh;
|
if (double.IsFinite(h))
|
||||||
if (double.IsFinite(l)) state.LastValidLow = l; else l = state.LastValidLow;
|
{
|
||||||
if (double.IsFinite(c)) state.LastValidClose = c; else c = state.LastValidClose;
|
state.LastValidHigh = h;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
h = state.LastValidHigh;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (double.IsFinite(l))
|
||||||
|
{
|
||||||
|
state.LastValidLow = l;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
l = state.LastValidLow;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (double.IsFinite(c))
|
||||||
|
{
|
||||||
|
state.LastValidClose = c;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
c = state.LastValidClose;
|
||||||
|
}
|
||||||
|
|
||||||
return (h, l, c);
|
return (h, l, c);
|
||||||
}
|
}
|
||||||
@@ -726,7 +810,10 @@ public sealed class AccBands : ITValuePublisher, IDisposable
|
|||||||
buffers.Close[state.BufferIndex] = c;
|
buffers.Close[state.BufferIndex] = c;
|
||||||
|
|
||||||
state.BufferIndex++;
|
state.BufferIndex++;
|
||||||
if (state.BufferIndex >= period) state.BufferIndex = 0;
|
if (state.BufferIndex >= period)
|
||||||
|
{
|
||||||
|
state.BufferIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
WriteBandOutputs(outputs, i, state.SumHigh / period, state.SumLow / period, state.SumClose / period, factor);
|
WriteBandOutputs(outputs, i, state.SumHigh / period, state.SumLow / period, state.SumClose / period, factor);
|
||||||
|
|
||||||
@@ -759,4 +846,4 @@ public sealed class AccBands : ITValuePublisher, IDisposable
|
|||||||
var results = accBands.Update(source);
|
var results = accBands.Update(source);
|
||||||
return (results, accBands);
|
return (results, accBands);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -217,4 +217,4 @@ public class ApchannelIndicatorTests
|
|||||||
double expectedMiddle = (upper + lower) / 2.0;
|
double expectedMiddle = (upper + lower) / 2.0;
|
||||||
Assert.Equal(expectedMiddle, middle, 6); // 6 decimal precision
|
Assert.Equal(expectedMiddle, middle, 6); // 6 decimal precision
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,10 @@ public sealed class ApchannelIndicator : Indicator, IWatchlistIndicator
|
|||||||
|
|
||||||
protected override void OnUpdate(UpdateArgs args)
|
protected override void OnUpdate(UpdateArgs args)
|
||||||
{
|
{
|
||||||
if (_apchannel == null) return;
|
if (_apchannel == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var item = HistoricalData[0, SeekOriginHistory.End];
|
var item = HistoricalData[0, SeekOriginHistory.End];
|
||||||
bool isNew = args.IsNewBar();
|
bool isNew = args.IsNewBar();
|
||||||
@@ -75,4 +78,4 @@ public sealed class ApchannelIndicator : Indicator, IWatchlistIndicator
|
|||||||
// Lower band
|
// Lower band
|
||||||
LinesSeries[2].SetValue(_apchannel.LowerBand, isHot, ShowColdValues);
|
LinesSeries[2].SetValue(_apchannel.LowerBand, isHot, ShowColdValues);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,9 +22,16 @@ public sealed class ApchannelValidationTests : IDisposable
|
|||||||
|
|
||||||
private void Dispose(bool disposing)
|
private void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
if (_disposed) return;
|
if (_disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
if (disposing) _testData?.Dispose();
|
if (disposing)
|
||||||
|
{
|
||||||
|
_testData?.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -83,7 +83,11 @@ public sealed class Apchannel : AbstractBase
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
if (_disposed) return;
|
if (_disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
|
|
||||||
if (disposing && _source != null)
|
if (disposing && _source != null)
|
||||||
@@ -174,7 +178,10 @@ public sealed class Apchannel : AbstractBase
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public TSeries Update(TBarSeries source)
|
public TSeries Update(TBarSeries source)
|
||||||
{
|
{
|
||||||
if (source.Count == 0) return [];
|
if (source.Count == 0)
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
int len = source.Count;
|
int len = source.Count;
|
||||||
var t = new List<long>(len);
|
var t = new List<long>(len);
|
||||||
@@ -207,7 +214,10 @@ public sealed class Apchannel : AbstractBase
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public override TSeries Update(TSeries source)
|
public override TSeries Update(TSeries source)
|
||||||
{
|
{
|
||||||
if (source.Count == 0) return [];
|
if (source.Count == 0)
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
int len = source.Count;
|
int len = source.Count;
|
||||||
var t = new List<long>(len);
|
var t = new List<long>(len);
|
||||||
@@ -234,7 +244,9 @@ public sealed class Apchannel : AbstractBase
|
|||||||
{
|
{
|
||||||
Init();
|
Init();
|
||||||
if (source.Length == 0)
|
if (source.Length == 0)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
long time = DateTime.UtcNow.Ticks;
|
long time = DateTime.UtcNow.Ticks;
|
||||||
long dt = step?.Ticks ?? TimeSpan.TicksPerMinute;
|
long dt = step?.Ticks ?? TimeSpan.TicksPerMinute;
|
||||||
@@ -281,9 +293,15 @@ public sealed class Apchannel : AbstractBase
|
|||||||
int length = sourceHigh.Length;
|
int length = sourceHigh.Length;
|
||||||
|
|
||||||
if (sourceLow.Length != length)
|
if (sourceLow.Length != length)
|
||||||
|
{
|
||||||
throw new ArgumentException("Source arrays must have the same length.", nameof(sourceLow));
|
throw new ArgumentException("Source arrays must have the same length.", nameof(sourceLow));
|
||||||
|
}
|
||||||
|
|
||||||
if (upperBand.Length != length)
|
if (upperBand.Length != length)
|
||||||
|
{
|
||||||
throw new ArgumentException("Upper band array must match source length.", nameof(upperBand));
|
throw new ArgumentException("Upper band array must match source length.", nameof(upperBand));
|
||||||
|
}
|
||||||
|
|
||||||
if (lowerBand.Length != length)
|
if (lowerBand.Length != length)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Lower band array must match source length.", nameof(lowerBand));
|
throw new ArgumentException("Lower band array must match source length.", nameof(lowerBand));
|
||||||
@@ -295,7 +313,9 @@ public sealed class Apchannel : AbstractBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (length == 0)
|
if (length == 0)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
double decay = 1.0 - alpha;
|
double decay = 1.0 - alpha;
|
||||||
|
|
||||||
@@ -324,7 +344,9 @@ public sealed class Apchannel : AbstractBase
|
|||||||
|
|
||||||
// Early return for single-element arrays
|
// Early return for single-element arrays
|
||||||
if (length == 1)
|
if (length == 1)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 1; i < length; i++)
|
for (int i = 1; i < length; i++)
|
||||||
{
|
{
|
||||||
@@ -332,8 +354,15 @@ public sealed class Apchannel : AbstractBase
|
|||||||
double low = sourceLow[i];
|
double low = sourceLow[i];
|
||||||
|
|
||||||
// Handle NaN/Infinity
|
// Handle NaN/Infinity
|
||||||
if (!double.IsFinite(high)) high = lastValidHigh;
|
if (!double.IsFinite(high))
|
||||||
if (!double.IsFinite(low)) low = lastValidLow;
|
{
|
||||||
|
high = lastValidHigh;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!double.IsFinite(low))
|
||||||
|
{
|
||||||
|
low = lastValidLow;
|
||||||
|
}
|
||||||
|
|
||||||
// Use FMA for optimal performance and precision
|
// Use FMA for optimal performance and precision
|
||||||
highEma = Math.FusedMultiplyAdd(decay, highEma, alpha * high);
|
highEma = Math.FusedMultiplyAdd(decay, highEma, alpha * high);
|
||||||
@@ -346,4 +375,4 @@ public sealed class Apchannel : AbstractBase
|
|||||||
lastValidLow = low;
|
lastValidLow = low;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -213,4 +213,4 @@ public class ApzIndicatorTests
|
|||||||
|
|
||||||
Assert.Equal(upperDistance, lowerDistance, 6); // 6 decimal precision
|
Assert.Equal(upperDistance, lowerDistance, 6); // 6 decimal precision
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,10 @@ public sealed class ApzIndicator : Indicator, IWatchlistIndicator
|
|||||||
|
|
||||||
protected override void OnUpdate(UpdateArgs args)
|
protected override void OnUpdate(UpdateArgs args)
|
||||||
{
|
{
|
||||||
if (_apz == null) return;
|
if (_apz == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var item = HistoricalData[0, SeekOriginHistory.End];
|
var item = HistoricalData[0, SeekOriginHistory.End];
|
||||||
bool isNew = args.IsNewBar();
|
bool isNew = args.IsNewBar();
|
||||||
@@ -78,4 +81,4 @@ public sealed class ApzIndicator : Indicator, IWatchlistIndicator
|
|||||||
// Lower band
|
// Lower band
|
||||||
LinesSeries[2].SetValue(_apz.Lower.Value, isHot, ShowColdValues);
|
LinesSeries[2].SetValue(_apz.Lower.Value, isHot, ShowColdValues);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -378,9 +378,14 @@ public sealed class ApzValidationTests : IDisposable
|
|||||||
apzResults.Add(apz.Last.Value);
|
apzResults.Add(apz.Last.Value);
|
||||||
|
|
||||||
if (emaResults.Count == 0)
|
if (emaResults.Count == 0)
|
||||||
|
{
|
||||||
ema = bar.Close;
|
ema = bar.Close;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
ema = alpha * bar.Close + (1 - alpha) * ema;
|
ema = alpha * bar.Close + (1 - alpha) * ema;
|
||||||
|
}
|
||||||
|
|
||||||
emaResults.Add(ema);
|
emaResults.Add(ema);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+82
-10
@@ -116,9 +116,14 @@ public sealed class Apz : ITValuePublisher
|
|||||||
public Apz(int period, double multiplier = 2.0)
|
public Apz(int period, double multiplier = 2.0)
|
||||||
{
|
{
|
||||||
if (period <= 0)
|
if (period <= 0)
|
||||||
|
{
|
||||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||||
|
}
|
||||||
|
|
||||||
if (multiplier <= 0)
|
if (multiplier <= 0)
|
||||||
|
{
|
||||||
throw new ArgumentException("Multiplier must be greater than 0", nameof(multiplier));
|
throw new ArgumentException("Multiplier must be greater than 0", nameof(multiplier));
|
||||||
|
}
|
||||||
|
|
||||||
_period = period;
|
_period = period;
|
||||||
_multiplier = multiplier;
|
_multiplier = multiplier;
|
||||||
@@ -163,19 +168,31 @@ public sealed class Apz : ITValuePublisher
|
|||||||
private (double price, double high, double low) GetValidValues(double price, double high, double low)
|
private (double price, double high, double low) GetValidValues(double price, double high, double low)
|
||||||
{
|
{
|
||||||
if (double.IsFinite(price))
|
if (double.IsFinite(price))
|
||||||
|
{
|
||||||
_state.LastValidPrice = price;
|
_state.LastValidPrice = price;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
price = _state.LastValidPrice;
|
price = _state.LastValidPrice;
|
||||||
|
}
|
||||||
|
|
||||||
if (double.IsFinite(high))
|
if (double.IsFinite(high))
|
||||||
|
{
|
||||||
_state.LastValidHigh = high;
|
_state.LastValidHigh = high;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
high = _state.LastValidHigh;
|
high = _state.LastValidHigh;
|
||||||
|
}
|
||||||
|
|
||||||
if (double.IsFinite(low))
|
if (double.IsFinite(low))
|
||||||
|
{
|
||||||
_state.LastValidLow = low;
|
_state.LastValidLow = low;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
low = _state.LastValidLow;
|
low = _state.LastValidLow;
|
||||||
|
}
|
||||||
|
|
||||||
return (price, high, low);
|
return (price, high, low);
|
||||||
}
|
}
|
||||||
@@ -206,7 +223,9 @@ public sealed class Apz : ITValuePublisher
|
|||||||
adaptiveRange *= compensator;
|
adaptiveRange *= compensator;
|
||||||
|
|
||||||
if (_state.E <= ConvergenceThreshold)
|
if (_state.E <= ConvergenceThreshold)
|
||||||
|
{
|
||||||
_state.IsHot = true;
|
_state.IsHot = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
double bandWidth = _multiplier * adaptiveRange;
|
double bandWidth = _multiplier * adaptiveRange;
|
||||||
@@ -220,9 +239,13 @@ public sealed class Apz : ITValuePublisher
|
|||||||
public TValue Update(TBar input, bool isNew = true)
|
public TValue Update(TBar input, bool isNew = true)
|
||||||
{
|
{
|
||||||
if (isNew)
|
if (isNew)
|
||||||
|
{
|
||||||
_p_state = _state;
|
_p_state = _state;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
_state = _p_state;
|
_state = _p_state;
|
||||||
|
}
|
||||||
|
|
||||||
var (price, high, low) = GetValidValues(input.Close, input.High, input.Low);
|
var (price, high, low) = GetValidValues(input.Close, input.High, input.Low);
|
||||||
|
|
||||||
@@ -237,7 +260,10 @@ public sealed class Apz : ITValuePublisher
|
|||||||
}
|
}
|
||||||
|
|
||||||
double range = high - low;
|
double range = high - low;
|
||||||
if (range < 0) range = 0; // Safety check
|
if (range < 0)
|
||||||
|
{
|
||||||
|
range = 0; // Safety check
|
||||||
|
}
|
||||||
|
|
||||||
var (middle, upper, lower) = Compute(price, range);
|
var (middle, upper, lower) = Compute(price, range);
|
||||||
|
|
||||||
@@ -255,7 +281,9 @@ public sealed class Apz : ITValuePublisher
|
|||||||
public (TSeries Middle, TSeries Upper, TSeries Lower) Update(TBarSeries source)
|
public (TSeries Middle, TSeries Upper, TSeries Lower) Update(TBarSeries source)
|
||||||
{
|
{
|
||||||
if (source.Count == 0)
|
if (source.Count == 0)
|
||||||
|
{
|
||||||
return (new TSeries([], []), new TSeries([], []), new TSeries([], []));
|
return (new TSeries([], []), new TSeries([], []), new TSeries([], []));
|
||||||
|
}
|
||||||
|
|
||||||
int len = source.Count;
|
int len = source.Count;
|
||||||
var tMiddle = new List<long>(len);
|
var tMiddle = new List<long>(len);
|
||||||
@@ -316,7 +344,10 @@ public sealed class Apz : ITValuePublisher
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void Prime(TBarSeries source)
|
public void Prime(TBarSeries source)
|
||||||
{
|
{
|
||||||
if (source.Count == 0) return;
|
if (source.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Reset state
|
// Reset state
|
||||||
_state = State.New();
|
_state = State.New();
|
||||||
@@ -443,15 +474,29 @@ public sealed class Apz : ITValuePublisher
|
|||||||
{
|
{
|
||||||
int len = close.Length;
|
int len = close.Length;
|
||||||
if (high.Length != len || low.Length != len)
|
if (high.Length != len || low.Length != len)
|
||||||
|
{
|
||||||
throw new ArgumentException("Input spans must have the same length", nameof(high));
|
throw new ArgumentException("Input spans must have the same length", nameof(high));
|
||||||
if (outputs.Middle.Length < len || outputs.Upper.Length < len || outputs.Lower.Length < len)
|
}
|
||||||
throw new ArgumentException("Output buffers must be at least as long as input", nameof(outputs));
|
|
||||||
if (period <= 0)
|
|
||||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
|
||||||
if (multiplier <= 0)
|
|
||||||
throw new ArgumentException("Multiplier must be greater than 0", nameof(multiplier));
|
|
||||||
|
|
||||||
if (len == 0) return;
|
if (outputs.Middle.Length < len || outputs.Upper.Length < len || outputs.Lower.Length < len)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Output buffers must be at least as long as input", nameof(outputs));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (period <= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (multiplier <= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Multiplier must be greater than 0", nameof(multiplier));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
CalculateScalarCore(high, low, close, outputs, period, multiplier);
|
CalculateScalarCore(high, low, close, outputs, period, multiplier);
|
||||||
}
|
}
|
||||||
@@ -470,16 +515,29 @@ public sealed class Apz : ITValuePublisher
|
|||||||
{
|
{
|
||||||
int len = close.Length;
|
int len = close.Length;
|
||||||
if (high.Length != len || low.Length != len)
|
if (high.Length != len || low.Length != len)
|
||||||
|
{
|
||||||
throw new ArgumentException("Input spans must have the same length", nameof(high));
|
throw new ArgumentException("Input spans must have the same length", nameof(high));
|
||||||
|
}
|
||||||
|
|
||||||
if (outputs.Middle.Length < len || outputs.Upper.Length < len || outputs.Lower.Length < len)
|
if (outputs.Middle.Length < len || outputs.Upper.Length < len || outputs.Lower.Length < len)
|
||||||
|
{
|
||||||
throw new ArgumentException("Output buffers must be at least as long as input", nameof(outputs));
|
throw new ArgumentException("Output buffers must be at least as long as input", nameof(outputs));
|
||||||
|
}
|
||||||
|
|
||||||
if (period <= 0)
|
if (period <= 0)
|
||||||
|
{
|
||||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||||
|
}
|
||||||
|
|
||||||
if (multiplier <= 0)
|
if (multiplier <= 0)
|
||||||
|
{
|
||||||
throw new ArgumentException("Multiplier must be greater than 0", nameof(multiplier));
|
throw new ArgumentException("Multiplier must be greater than 0", nameof(multiplier));
|
||||||
|
}
|
||||||
|
|
||||||
if (len == 0)
|
if (len == 0)
|
||||||
|
{
|
||||||
return new ScalarState();
|
return new ScalarState();
|
||||||
|
}
|
||||||
|
|
||||||
return CalculateScalarCoreWithState(high, low, close, outputs, period, multiplier);
|
return CalculateScalarCoreWithState(high, low, close, outputs, period, multiplier);
|
||||||
}
|
}
|
||||||
@@ -540,19 +598,31 @@ public sealed class Apz : ITValuePublisher
|
|||||||
|
|
||||||
// Get valid values
|
// Get valid values
|
||||||
if (double.IsFinite(price))
|
if (double.IsFinite(price))
|
||||||
|
{
|
||||||
state.LastValidPrice = price;
|
state.LastValidPrice = price;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
price = state.LastValidPrice;
|
price = state.LastValidPrice;
|
||||||
|
}
|
||||||
|
|
||||||
if (double.IsFinite(h))
|
if (double.IsFinite(h))
|
||||||
|
{
|
||||||
state.LastValidHigh = h;
|
state.LastValidHigh = h;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
h = state.LastValidHigh;
|
h = state.LastValidHigh;
|
||||||
|
}
|
||||||
|
|
||||||
if (double.IsFinite(l))
|
if (double.IsFinite(l))
|
||||||
|
{
|
||||||
state.LastValidLow = l;
|
state.LastValidLow = l;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
l = state.LastValidLow;
|
l = state.LastValidLow;
|
||||||
|
}
|
||||||
|
|
||||||
// Handle first valid value
|
// Handle first valid value
|
||||||
if (double.IsNaN(price))
|
if (double.IsNaN(price))
|
||||||
@@ -585,7 +655,9 @@ public sealed class Apz : ITValuePublisher
|
|||||||
adaptiveRange *= compensator;
|
adaptiveRange *= compensator;
|
||||||
|
|
||||||
if (state.E <= ConvergenceThreshold)
|
if (state.E <= ConvergenceThreshold)
|
||||||
|
{
|
||||||
state.IsHot = true;
|
state.IsHot = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
double bandWidth = multiplier * adaptiveRange;
|
double bandWidth = multiplier * adaptiveRange;
|
||||||
@@ -626,4 +698,4 @@ public sealed class Apz : ITValuePublisher
|
|||||||
var results = apz.Update(source);
|
var results = apz.Update(source);
|
||||||
return (results, apz);
|
return (results, apz);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -189,4 +189,4 @@ public class AtrBandsIndicatorTests
|
|||||||
indicator.Multiplier = 3.5;
|
indicator.Multiplier = 3.5;
|
||||||
Assert.Equal(3.5, indicator.Multiplier);
|
Assert.Equal(3.5, indicator.Multiplier);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,10 @@ public sealed class AtrBandsIndicator : Indicator, IWatchlistIndicator
|
|||||||
|
|
||||||
protected override void OnUpdate(UpdateArgs args)
|
protected override void OnUpdate(UpdateArgs args)
|
||||||
{
|
{
|
||||||
if (_atrBands == null) return;
|
if (_atrBands == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var item = HistoricalData[0, SeekOriginHistory.End];
|
var item = HistoricalData[0, SeekOriginHistory.End];
|
||||||
bool isNew = args.IsNewBar();
|
bool isNew = args.IsNewBar();
|
||||||
@@ -78,4 +81,4 @@ public sealed class AtrBandsIndicator : Indicator, IWatchlistIndicator
|
|||||||
// Lower band
|
// Lower band
|
||||||
LinesSeries[2].SetValue(_atrBands.Lower.Value, isHot, ShowColdValues);
|
LinesSeries[2].SetValue(_atrBands.Lower.Value, isHot, ShowColdValues);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -708,4 +708,4 @@ public class AtrBandsTests
|
|||||||
// Bands should be symmetric around middle
|
// Bands should be symmetric around middle
|
||||||
Assert.Equal(upperDist, lowerDist, 1e-10);
|
Assert.Equal(upperDist, lowerDist, 1e-10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,9 +114,14 @@ public sealed class AtrBands : ITValuePublisher, IDisposable
|
|||||||
public AtrBands(int period, double multiplier = 2.0)
|
public AtrBands(int period, double multiplier = 2.0)
|
||||||
{
|
{
|
||||||
if (period <= 0)
|
if (period <= 0)
|
||||||
|
{
|
||||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||||
|
}
|
||||||
|
|
||||||
if (multiplier <= 0)
|
if (multiplier <= 0)
|
||||||
|
{
|
||||||
throw new ArgumentException("Multiplier must be greater than 0", nameof(multiplier));
|
throw new ArgumentException("Multiplier must be greater than 0", nameof(multiplier));
|
||||||
|
}
|
||||||
|
|
||||||
_period = period;
|
_period = period;
|
||||||
_multiplier = multiplier;
|
_multiplier = multiplier;
|
||||||
@@ -145,7 +150,11 @@ public sealed class AtrBands : ITValuePublisher, IDisposable
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (_disposed) return;
|
if (_disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
|
|
||||||
if (_source is not null)
|
if (_source is not null)
|
||||||
@@ -173,7 +182,9 @@ public sealed class AtrBands : ITValuePublisher, IDisposable
|
|||||||
private double CalculateTrueRange(double high, double low, double prevClose)
|
private double CalculateTrueRange(double high, double low, double prevClose)
|
||||||
{
|
{
|
||||||
if (double.IsNaN(prevClose))
|
if (double.IsNaN(prevClose))
|
||||||
|
{
|
||||||
return high - low;
|
return high - low;
|
||||||
|
}
|
||||||
|
|
||||||
double hl = high - low;
|
double hl = high - low;
|
||||||
double hpc = Math.Abs(high - prevClose);
|
double hpc = Math.Abs(high - prevClose);
|
||||||
@@ -188,9 +199,13 @@ public sealed class AtrBands : ITValuePublisher, IDisposable
|
|||||||
public TValue Update(TBar input, bool isNew = true)
|
public TValue Update(TBar input, bool isNew = true)
|
||||||
{
|
{
|
||||||
if (isNew)
|
if (isNew)
|
||||||
|
{
|
||||||
_p_state = _state;
|
_p_state = _state;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
_state = _p_state;
|
_state = _p_state;
|
||||||
|
}
|
||||||
|
|
||||||
// Get valid values with last-value substitution
|
// Get valid values with last-value substitution
|
||||||
double source = input.Close;
|
double source = input.Close;
|
||||||
@@ -198,10 +213,41 @@ public sealed class AtrBands : ITValuePublisher, IDisposable
|
|||||||
double low = input.Low;
|
double low = input.Low;
|
||||||
double close = input.Close;
|
double close = input.Close;
|
||||||
|
|
||||||
if (double.IsFinite(source)) _state.LastValidSource = source; else source = _state.LastValidSource;
|
if (double.IsFinite(source))
|
||||||
if (double.IsFinite(high)) _state.LastValidHigh = high; else high = _state.LastValidHigh;
|
{
|
||||||
if (double.IsFinite(low)) _state.LastValidLow = low; else low = _state.LastValidLow;
|
_state.LastValidSource = source;
|
||||||
if (double.IsFinite(close)) _state.LastValidClose = close; else close = _state.LastValidClose;
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
source = _state.LastValidSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (double.IsFinite(high))
|
||||||
|
{
|
||||||
|
_state.LastValidHigh = high;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
high = _state.LastValidHigh;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (double.IsFinite(low))
|
||||||
|
{
|
||||||
|
_state.LastValidLow = low;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
low = _state.LastValidLow;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (double.IsFinite(close))
|
||||||
|
{
|
||||||
|
_state.LastValidClose = close;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
close = _state.LastValidClose;
|
||||||
|
}
|
||||||
|
|
||||||
// Handle first valid value initialization
|
// Handle first valid value initialization
|
||||||
if (double.IsNaN(source))
|
if (double.IsNaN(source))
|
||||||
@@ -248,7 +294,9 @@ public sealed class AtrBands : ITValuePublisher, IDisposable
|
|||||||
double width = atr * _multiplier;
|
double width = atr * _multiplier;
|
||||||
|
|
||||||
if (isNew)
|
if (isNew)
|
||||||
|
{
|
||||||
_state.PrevClose = close;
|
_state.PrevClose = close;
|
||||||
|
}
|
||||||
|
|
||||||
Last = new TValue(input.Time, middle);
|
Last = new TValue(input.Time, middle);
|
||||||
Upper = new TValue(input.Time, middle + width);
|
Upper = new TValue(input.Time, middle + width);
|
||||||
@@ -264,7 +312,9 @@ public sealed class AtrBands : ITValuePublisher, IDisposable
|
|||||||
public (TSeries Middle, TSeries Upper, TSeries Lower) Update(TBarSeries source)
|
public (TSeries Middle, TSeries Upper, TSeries Lower) Update(TBarSeries source)
|
||||||
{
|
{
|
||||||
if (source.Count == 0)
|
if (source.Count == 0)
|
||||||
|
{
|
||||||
return (new TSeries([], []), new TSeries([], []), new TSeries([], []));
|
return (new TSeries([], []), new TSeries([], []), new TSeries([], []));
|
||||||
|
}
|
||||||
|
|
||||||
int len = source.Count;
|
int len = source.Count;
|
||||||
var tMiddle = new List<long>(len);
|
var tMiddle = new List<long>(len);
|
||||||
@@ -305,7 +355,10 @@ public sealed class AtrBands : ITValuePublisher, IDisposable
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void Prime(TBarSeries source)
|
public void Prime(TBarSeries source)
|
||||||
{
|
{
|
||||||
if (source.Count == 0) return;
|
if (source.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Reset state
|
// Reset state
|
||||||
_sourceBuffer.Clear();
|
_sourceBuffer.Clear();
|
||||||
@@ -325,11 +378,19 @@ public sealed class AtrBands : ITValuePublisher, IDisposable
|
|||||||
_state.LastValidClose = bar.Close;
|
_state.LastValidClose = bar.Close;
|
||||||
}
|
}
|
||||||
if (double.IsFinite(bar.High) && double.IsNaN(_state.LastValidHigh))
|
if (double.IsFinite(bar.High) && double.IsNaN(_state.LastValidHigh))
|
||||||
|
{
|
||||||
_state.LastValidHigh = bar.High;
|
_state.LastValidHigh = bar.High;
|
||||||
|
}
|
||||||
|
|
||||||
if (double.IsFinite(bar.Low) && double.IsNaN(_state.LastValidLow))
|
if (double.IsFinite(bar.Low) && double.IsNaN(_state.LastValidLow))
|
||||||
|
{
|
||||||
_state.LastValidLow = bar.Low;
|
_state.LastValidLow = bar.Low;
|
||||||
|
}
|
||||||
|
|
||||||
if (!double.IsNaN(_state.LastValidSource) && !double.IsNaN(_state.LastValidHigh) && !double.IsNaN(_state.LastValidLow))
|
if (!double.IsNaN(_state.LastValidSource) && !double.IsNaN(_state.LastValidHigh) && !double.IsNaN(_state.LastValidLow))
|
||||||
|
{
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find valid values in warmup window if not found
|
// Find valid values in warmup window if not found
|
||||||
@@ -396,15 +457,29 @@ public sealed class AtrBands : ITValuePublisher, IDisposable
|
|||||||
{
|
{
|
||||||
int len = input.Close.Length;
|
int len = input.Close.Length;
|
||||||
if (input.High.Length != len || input.Low.Length != len)
|
if (input.High.Length != len || input.Low.Length != len)
|
||||||
|
{
|
||||||
throw new ArgumentException("High, Low, and Close must have the same length", nameof(input));
|
throw new ArgumentException("High, Low, and Close must have the same length", nameof(input));
|
||||||
if (output.Middle.Length < len || output.Upper.Length < len || output.Lower.Length < len)
|
}
|
||||||
throw new ArgumentException("Output buffers must be at least as long as input", nameof(output));
|
|
||||||
if (period <= 0)
|
|
||||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
|
||||||
if (multiplier <= 0)
|
|
||||||
throw new ArgumentException("Multiplier must be greater than 0", nameof(multiplier));
|
|
||||||
|
|
||||||
if (len == 0) return;
|
if (output.Middle.Length < len || output.Upper.Length < len || output.Lower.Length < len)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Output buffers must be at least as long as input", nameof(output));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (period <= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (multiplier <= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Multiplier must be greater than 0", nameof(multiplier));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
CalculateScalarCore(input.High, input.Low, input.Close, output.Middle, output.Upper, output.Lower, period, multiplier);
|
CalculateScalarCore(input.High, input.Low, input.Close, output.Middle, output.Upper, output.Lower, period, multiplier);
|
||||||
}
|
}
|
||||||
@@ -521,9 +596,32 @@ public sealed class AtrBands : ITValuePublisher, IDisposable
|
|||||||
double c = close[i];
|
double c = close[i];
|
||||||
|
|
||||||
// Get valid values
|
// Get valid values
|
||||||
if (double.IsFinite(h)) lastValidHigh = h; else h = lastValidHigh;
|
if (double.IsFinite(h))
|
||||||
if (double.IsFinite(l)) lastValidLow = l; else l = lastValidLow;
|
{
|
||||||
if (double.IsFinite(c)) lastValidClose = c; else c = lastValidClose;
|
lastValidHigh = h;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
h = lastValidHigh;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (double.IsFinite(l))
|
||||||
|
{
|
||||||
|
lastValidLow = l;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
l = lastValidLow;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (double.IsFinite(c))
|
||||||
|
{
|
||||||
|
lastValidClose = c;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
c = lastValidClose;
|
||||||
|
}
|
||||||
|
|
||||||
if (double.IsNaN(c))
|
if (double.IsNaN(c))
|
||||||
{
|
{
|
||||||
@@ -536,7 +634,9 @@ public sealed class AtrBands : ITValuePublisher, IDisposable
|
|||||||
// Calculate True Range
|
// Calculate True Range
|
||||||
double tr;
|
double tr;
|
||||||
if (double.IsNaN(prevClose))
|
if (double.IsNaN(prevClose))
|
||||||
|
{
|
||||||
tr = h - l;
|
tr = h - l;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
double hl = h - l;
|
double hl = h - l;
|
||||||
@@ -591,4 +691,4 @@ public sealed class AtrBands : ITValuePublisher, IDisposable
|
|||||||
var results = atrBands.Update(source);
|
var results = atrBands.Update(source);
|
||||||
return (results, atrBands);
|
return (results, atrBands);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -184,4 +184,4 @@ public class BbandsIndicatorTests
|
|||||||
Assert.True(double.IsFinite(series.GetValue(0)));
|
Assert.True(double.IsFinite(series.GetValue(0)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,4 +72,4 @@ public class BbandsIndicator : Indicator, IWatchlistIndicator
|
|||||||
WidthSeries!.SetValue(bbands.Width.Value, bbands.IsHot, ShowColdValues);
|
WidthSeries!.SetValue(bbands.Width.Value, bbands.IsHot, ShowColdValues);
|
||||||
PercentBSeries!.SetValue(bbands.PercentB.Value, bbands.IsHot, ShowColdValues);
|
PercentBSeries!.SetValue(bbands.PercentB.Value, bbands.IsHot, ShowColdValues);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -318,4 +318,4 @@ public class BbandsTests
|
|||||||
Assert.Equal(batchResult[^1].Value, streamingBbands.Middle.Value, precision: 8);
|
Assert.Equal(batchResult[^1].Value, streamingBbands.Middle.Value, precision: 8);
|
||||||
Assert.Equal(middleArray[^1], streamingBbands.Middle.Value, precision: 8);
|
Assert.Equal(middleArray[^1], streamingBbands.Middle.Value, precision: 8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -372,4 +372,4 @@ public sealed class BbandsValidationTests : IDisposable
|
|||||||
}
|
}
|
||||||
_output.WriteLine("Bbands Batch(TSeries) validated successfully against Ooples");
|
_output.WriteLine("Bbands Batch(TSeries) validated successfully against Ooples");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ public sealed class Bbands : AbstractBase
|
|||||||
{
|
{
|
||||||
step ??= TimeSpan.FromSeconds(1);
|
step ??= TimeSpan.FromSeconds(1);
|
||||||
DateTime startTime = DateTime.UtcNow;
|
DateTime startTime = DateTime.UtcNow;
|
||||||
|
|
||||||
for (int i = 0; i < source.Length; i++)
|
for (int i = 0; i < source.Length; i++)
|
||||||
{
|
{
|
||||||
Update(new TValue(startTime + i * step.Value, source[i]), isNew: true);
|
Update(new TValue(startTime + i * step.Value, source[i]), isNew: true);
|
||||||
@@ -320,4 +320,4 @@ public sealed class Bbands : AbstractBase
|
|||||||
lower[i] = middle[i] - offset;
|
lower[i] = middle[i] - offset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,9 @@ public sealed class DchannelIndicator : Indicator, IWatchlistIndicator
|
|||||||
protected override void OnUpdate(UpdateArgs args)
|
protected override void OnUpdate(UpdateArgs args)
|
||||||
{
|
{
|
||||||
if (_indicator is null)
|
if (_indicator is null)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var item = HistoricalData[0, SeekOriginHistory.End];
|
var item = HistoricalData[0, SeekOriginHistory.End];
|
||||||
bool isNew = args.IsNewBar();
|
bool isNew = args.IsNewBar();
|
||||||
|
|||||||
@@ -42,7 +42,9 @@ public sealed class Dchannel : ITValuePublisher
|
|||||||
public Dchannel(int period)
|
public Dchannel(int period)
|
||||||
{
|
{
|
||||||
if (period <= 0)
|
if (period <= 0)
|
||||||
|
{
|
||||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||||
|
}
|
||||||
|
|
||||||
_period = period;
|
_period = period;
|
||||||
_hBuf = new double[_period];
|
_hBuf = new double[_period];
|
||||||
@@ -74,14 +76,22 @@ public sealed class Dchannel : ITValuePublisher
|
|||||||
private (double high, double low) GetValid(double high, double low)
|
private (double high, double low) GetValid(double high, double low)
|
||||||
{
|
{
|
||||||
if (double.IsFinite(high))
|
if (double.IsFinite(high))
|
||||||
|
{
|
||||||
_state = _state with { LastValidHigh = high };
|
_state = _state with { LastValidHigh = high };
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
high = _state.LastValidHigh;
|
high = _state.LastValidHigh;
|
||||||
|
}
|
||||||
|
|
||||||
if (double.IsFinite(low))
|
if (double.IsFinite(low))
|
||||||
|
{
|
||||||
_state = _state with { LastValidLow = low };
|
_state = _state with { LastValidLow = low };
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
low = _state.LastValidLow;
|
low = _state.LastValidLow;
|
||||||
|
}
|
||||||
|
|
||||||
return (high, low);
|
return (high, low);
|
||||||
}
|
}
|
||||||
@@ -90,15 +100,21 @@ public sealed class Dchannel : ITValuePublisher
|
|||||||
public TValue Update(TBar input, bool isNew = true)
|
public TValue Update(TBar input, bool isNew = true)
|
||||||
{
|
{
|
||||||
if (isNew)
|
if (isNew)
|
||||||
|
{
|
||||||
_p_state = _state;
|
_p_state = _state;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
_state = _p_state;
|
_state = _p_state;
|
||||||
|
}
|
||||||
|
|
||||||
if (isNew)
|
if (isNew)
|
||||||
{
|
{
|
||||||
_index++;
|
_index++;
|
||||||
if (_count < _period)
|
if (_count < _period)
|
||||||
|
{
|
||||||
_count++;
|
_count++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int bufIdx = (int)(_index % _period);
|
int bufIdx = (int)(_index % _period);
|
||||||
@@ -134,7 +150,9 @@ public sealed class Dchannel : ITValuePublisher
|
|||||||
double mid = (top + bot) * 0.5;
|
double mid = (top + bot) * 0.5;
|
||||||
|
|
||||||
if (!IsHot && _count >= _period)
|
if (!IsHot && _count >= _period)
|
||||||
|
{
|
||||||
_state = _state with { IsHot = true };
|
_state = _state with { IsHot = true };
|
||||||
|
}
|
||||||
|
|
||||||
Last = new TValue(input.Time, mid);
|
Last = new TValue(input.Time, mid);
|
||||||
Upper = new TValue(input.Time, top);
|
Upper = new TValue(input.Time, top);
|
||||||
@@ -147,7 +165,9 @@ public sealed class Dchannel : ITValuePublisher
|
|||||||
public (TSeries Middle, TSeries Upper, TSeries Lower) Update(TBarSeries source)
|
public (TSeries Middle, TSeries Upper, TSeries Lower) Update(TBarSeries source)
|
||||||
{
|
{
|
||||||
if (source.Count == 0)
|
if (source.Count == 0)
|
||||||
|
{
|
||||||
return (new TSeries([], []), new TSeries([], []), new TSeries([], []));
|
return (new TSeries([], []), new TSeries([], []), new TSeries([], []));
|
||||||
|
}
|
||||||
|
|
||||||
int len = source.Count;
|
int len = source.Count;
|
||||||
var tMiddle = new List<long>(len);
|
var tMiddle = new List<long>(len);
|
||||||
@@ -191,7 +211,9 @@ public sealed class Dchannel : ITValuePublisher
|
|||||||
Reset();
|
Reset();
|
||||||
|
|
||||||
if (source.Count == 0)
|
if (source.Count == 0)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < source.Count; i++)
|
for (int i = 0; i < source.Count; i++)
|
||||||
{
|
{
|
||||||
@@ -226,14 +248,25 @@ public sealed class Dchannel : ITValuePublisher
|
|||||||
int period)
|
int period)
|
||||||
{
|
{
|
||||||
if (period <= 0)
|
if (period <= 0)
|
||||||
|
{
|
||||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||||
|
}
|
||||||
|
|
||||||
if (high.Length != low.Length)
|
if (high.Length != low.Length)
|
||||||
|
{
|
||||||
throw new ArgumentException("High and Low spans must have the same length", nameof(high));
|
throw new ArgumentException("High and Low spans must have the same length", nameof(high));
|
||||||
|
}
|
||||||
|
|
||||||
if (middle.Length < high.Length || upper.Length < high.Length || lower.Length < high.Length)
|
if (middle.Length < high.Length || upper.Length < high.Length || lower.Length < high.Length)
|
||||||
|
{
|
||||||
throw new ArgumentException("Output spans must be at least as long as inputs", nameof(middle));
|
throw new ArgumentException("Output spans must be at least as long as inputs", nameof(middle));
|
||||||
|
}
|
||||||
|
|
||||||
int len = high.Length;
|
int len = high.Length;
|
||||||
if (len == 0) return;
|
if (len == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
double[] top = ArrayPool<double>.Shared.Rent(len);
|
double[] top = ArrayPool<double>.Shared.Rent(len);
|
||||||
double[] bot = ArrayPool<double>.Shared.Rent(len);
|
double[] bot = ArrayPool<double>.Shared.Rent(len);
|
||||||
@@ -295,4 +328,4 @@ public sealed class Dchannel : ITValuePublisher
|
|||||||
var results = indicator.Update(source);
|
var results = indicator.Update(source);
|
||||||
return (results, indicator);
|
return (results, indicator);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,9 @@ public sealed class DecaychannelIndicator : Indicator, IWatchlistIndicator
|
|||||||
protected override void OnUpdate(UpdateArgs args)
|
protected override void OnUpdate(UpdateArgs args)
|
||||||
{
|
{
|
||||||
if (_indicator is null)
|
if (_indicator is null)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var item = HistoricalData[0, SeekOriginHistory.End];
|
var item = HistoricalData[0, SeekOriginHistory.End];
|
||||||
bool isNew = args.IsNewBar();
|
bool isNew = args.IsNewBar();
|
||||||
|
|||||||
@@ -58,7 +58,9 @@ public sealed class Decaychannel : ITValuePublisher
|
|||||||
public Decaychannel(int period)
|
public Decaychannel(int period)
|
||||||
{
|
{
|
||||||
if (period <= 0)
|
if (period <= 0)
|
||||||
|
{
|
||||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||||
|
}
|
||||||
|
|
||||||
_period = period;
|
_period = period;
|
||||||
_decayLambda = Math.Log(2.0) / period;
|
_decayLambda = Math.Log(2.0) / period;
|
||||||
@@ -98,14 +100,22 @@ public sealed class Decaychannel : ITValuePublisher
|
|||||||
private (double high, double low) GetValid(double high, double low)
|
private (double high, double low) GetValid(double high, double low)
|
||||||
{
|
{
|
||||||
if (double.IsFinite(high))
|
if (double.IsFinite(high))
|
||||||
|
{
|
||||||
_state = _state with { LastValidHigh = high };
|
_state = _state with { LastValidHigh = high };
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
high = _state.LastValidHigh;
|
high = _state.LastValidHigh;
|
||||||
|
}
|
||||||
|
|
||||||
if (double.IsFinite(low))
|
if (double.IsFinite(low))
|
||||||
|
{
|
||||||
_state = _state with { LastValidLow = low };
|
_state = _state with { LastValidLow = low };
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
low = _state.LastValidLow;
|
low = _state.LastValidLow;
|
||||||
|
}
|
||||||
|
|
||||||
return (high, low);
|
return (high, low);
|
||||||
}
|
}
|
||||||
@@ -151,7 +161,9 @@ public sealed class Decaychannel : ITValuePublisher
|
|||||||
{
|
{
|
||||||
int len = Math.Min(_count, _period);
|
int len = Math.Min(_count, _period);
|
||||||
if (len == 0)
|
if (len == 0)
|
||||||
|
{
|
||||||
return (double.NaN, double.NaN);
|
return (double.NaN, double.NaN);
|
||||||
|
}
|
||||||
|
|
||||||
double max = double.MinValue;
|
double max = double.MinValue;
|
||||||
double min = double.MaxValue;
|
double min = double.MaxValue;
|
||||||
@@ -159,13 +171,23 @@ public sealed class Decaychannel : ITValuePublisher
|
|||||||
for (int i = 0; i < len; i++)
|
for (int i = 0; i < len; i++)
|
||||||
{
|
{
|
||||||
int idx = (int)((_index - i) % _period);
|
int idx = (int)((_index - i) % _period);
|
||||||
if (idx < 0) idx += _period;
|
if (idx < 0)
|
||||||
|
{
|
||||||
|
idx += _period;
|
||||||
|
}
|
||||||
|
|
||||||
double h = _hBuf[idx];
|
double h = _hBuf[idx];
|
||||||
double l = _lBuf[idx];
|
double l = _lBuf[idx];
|
||||||
|
|
||||||
if (h > max) max = h;
|
if (h > max)
|
||||||
if (l < min) min = l;
|
{
|
||||||
|
max = h;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (l < min)
|
||||||
|
{
|
||||||
|
min = l;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (max, min);
|
return (max, min);
|
||||||
@@ -183,7 +205,9 @@ public sealed class Decaychannel : ITValuePublisher
|
|||||||
// Now advance to new bar
|
// Now advance to new bar
|
||||||
_index++;
|
_index++;
|
||||||
if (_count < _period)
|
if (_count < _period)
|
||||||
|
{
|
||||||
_count++;
|
_count++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -193,7 +217,9 @@ public sealed class Decaychannel : ITValuePublisher
|
|||||||
// Re-advance to current bar position (we're reprocessing current bar)
|
// Re-advance to current bar position (we're reprocessing current bar)
|
||||||
_index++;
|
_index++;
|
||||||
if (_count < _period)
|
if (_count < _period)
|
||||||
|
{
|
||||||
_count++;
|
_count++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int bufIdx = (int)(_index % _period);
|
int bufIdx = (int)(_index % _period);
|
||||||
@@ -286,7 +312,9 @@ public sealed class Decaychannel : ITValuePublisher
|
|||||||
public (TSeries Middle, TSeries Upper, TSeries Lower) Update(TBarSeries source)
|
public (TSeries Middle, TSeries Upper, TSeries Lower) Update(TBarSeries source)
|
||||||
{
|
{
|
||||||
if (source.Count == 0)
|
if (source.Count == 0)
|
||||||
|
{
|
||||||
return (new TSeries([], []), new TSeries([], []), new TSeries([], []));
|
return (new TSeries([], []), new TSeries([], []), new TSeries([], []));
|
||||||
|
}
|
||||||
|
|
||||||
int len = source.Count;
|
int len = source.Count;
|
||||||
var tMiddle = new List<long>(len);
|
var tMiddle = new List<long>(len);
|
||||||
@@ -329,7 +357,9 @@ public sealed class Decaychannel : ITValuePublisher
|
|||||||
Reset();
|
Reset();
|
||||||
|
|
||||||
if (source.Count == 0)
|
if (source.Count == 0)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < source.Count; i++)
|
for (int i = 0; i < source.Count; i++)
|
||||||
{
|
{
|
||||||
@@ -370,14 +400,25 @@ public sealed class Decaychannel : ITValuePublisher
|
|||||||
int period)
|
int period)
|
||||||
{
|
{
|
||||||
if (period <= 0)
|
if (period <= 0)
|
||||||
|
{
|
||||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||||
|
}
|
||||||
|
|
||||||
if (high.Length != low.Length)
|
if (high.Length != low.Length)
|
||||||
|
{
|
||||||
throw new ArgumentException("High and Low spans must have the same length", nameof(high));
|
throw new ArgumentException("High and Low spans must have the same length", nameof(high));
|
||||||
|
}
|
||||||
|
|
||||||
if (middle.Length < high.Length || upper.Length < high.Length || lower.Length < high.Length)
|
if (middle.Length < high.Length || upper.Length < high.Length || lower.Length < high.Length)
|
||||||
|
{
|
||||||
throw new ArgumentException("Output spans must be at least as long as inputs", nameof(middle));
|
throw new ArgumentException("Output spans must be at least as long as inputs", nameof(middle));
|
||||||
|
}
|
||||||
|
|
||||||
int len = high.Length;
|
int len = high.Length;
|
||||||
if (len == 0) return;
|
if (len == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
double decayLambda = Math.Log(2.0) / period;
|
double decayLambda = Math.Log(2.0) / period;
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,9 @@ public sealed class FcbIndicator : Indicator, IWatchlistIndicator
|
|||||||
protected override void OnUpdate(UpdateArgs args)
|
protected override void OnUpdate(UpdateArgs args)
|
||||||
{
|
{
|
||||||
if (_indicator is null)
|
if (_indicator is null)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var item = HistoricalData[0, SeekOriginHistory.End];
|
var item = HistoricalData[0, SeekOriginHistory.End];
|
||||||
bool isNew = args.IsNewBar();
|
bool isNew = args.IsNewBar();
|
||||||
|
|||||||
+57
-1
@@ -59,7 +59,9 @@ public sealed class Fcb : ITValuePublisher
|
|||||||
public Fcb(int period = 20)
|
public Fcb(int period = 20)
|
||||||
{
|
{
|
||||||
if (period < 1)
|
if (period < 1)
|
||||||
|
{
|
||||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be >= 1.");
|
throw new ArgumentOutOfRangeException(nameof(period), "Period must be >= 1.");
|
||||||
|
}
|
||||||
|
|
||||||
_period = period;
|
_period = period;
|
||||||
WarmupPeriod = period + 2; // Need 2 extra bars for fractal detection
|
WarmupPeriod = period + 2; // Need 2 extra bars for fractal detection
|
||||||
@@ -91,14 +93,22 @@ public sealed class Fcb : ITValuePublisher
|
|||||||
private (double high, double low) GetValid(double high, double low)
|
private (double high, double low) GetValid(double high, double low)
|
||||||
{
|
{
|
||||||
if (double.IsFinite(high))
|
if (double.IsFinite(high))
|
||||||
|
{
|
||||||
_state = _state with { LastValidHigh = high };
|
_state = _state with { LastValidHigh = high };
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
high = _state.LastValidHigh;
|
high = _state.LastValidHigh;
|
||||||
|
}
|
||||||
|
|
||||||
if (double.IsFinite(low))
|
if (double.IsFinite(low))
|
||||||
|
{
|
||||||
_state = _state with { LastValidLow = low };
|
_state = _state with { LastValidLow = low };
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
low = _state.LastValidLow;
|
low = _state.LastValidLow;
|
||||||
|
}
|
||||||
|
|
||||||
return (high, low);
|
return (high, low);
|
||||||
}
|
}
|
||||||
@@ -120,9 +130,13 @@ public sealed class Fcb : ITValuePublisher
|
|||||||
int backIdx = (_hHead + _hCount - 1) % _period;
|
int backIdx = (_hHead + _hCount - 1) % _period;
|
||||||
int bufIdx = _hDeque[backIdx] % _period;
|
int bufIdx = _hDeque[backIdx] % _period;
|
||||||
if (_hBuf[bufIdx] <= value)
|
if (_hBuf[bufIdx] <= value)
|
||||||
|
{
|
||||||
_hCount--;
|
_hCount--;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int tail = (_hHead + _hCount) % _period;
|
int tail = (_hHead + _hCount) % _period;
|
||||||
@@ -145,9 +159,13 @@ public sealed class Fcb : ITValuePublisher
|
|||||||
int backIdx = (_lHead + _lCount - 1) % _period;
|
int backIdx = (_lHead + _lCount - 1) % _period;
|
||||||
int bufIdx = _lDeque[backIdx] % _period;
|
int bufIdx = _lDeque[backIdx] % _period;
|
||||||
if (_lBuf[bufIdx] >= value)
|
if (_lBuf[bufIdx] >= value)
|
||||||
|
{
|
||||||
_lCount--;
|
_lCount--;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int tail = (_lHead + _lCount) % _period;
|
int tail = (_lHead + _lCount) % _period;
|
||||||
@@ -163,7 +181,9 @@ public sealed class Fcb : ITValuePublisher
|
|||||||
_lCount = 0;
|
_lCount = 0;
|
||||||
|
|
||||||
if (_count == 0)
|
if (_count == 0)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
long startLogical = _index - _count + 1;
|
long startLogical = _index - _count + 1;
|
||||||
for (int i = 0; i < _count; i++)
|
for (int i = 0; i < _count; i++)
|
||||||
@@ -199,15 +219,21 @@ public sealed class Fcb : ITValuePublisher
|
|||||||
public TValue Update(TBar input, bool isNew = true)
|
public TValue Update(TBar input, bool isNew = true)
|
||||||
{
|
{
|
||||||
if (isNew)
|
if (isNew)
|
||||||
|
{
|
||||||
_p_state = _state;
|
_p_state = _state;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
_state = _p_state;
|
_state = _p_state;
|
||||||
|
}
|
||||||
|
|
||||||
if (isNew)
|
if (isNew)
|
||||||
{
|
{
|
||||||
_index++;
|
_index++;
|
||||||
if (_count < _period)
|
if (_count < _period)
|
||||||
|
{
|
||||||
_count++;
|
_count++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var (high, low) = GetValid(input.High, input.Low);
|
var (high, low) = GetValid(input.High, input.Low);
|
||||||
@@ -272,7 +298,9 @@ public sealed class Fcb : ITValuePublisher
|
|||||||
double mid = (top + bot) * 0.5;
|
double mid = (top + bot) * 0.5;
|
||||||
|
|
||||||
if (!_state.IsHot && _index + 1 >= WarmupPeriod)
|
if (!_state.IsHot && _index + 1 >= WarmupPeriod)
|
||||||
|
{
|
||||||
_state = _state with { IsHot = true };
|
_state = _state with { IsHot = true };
|
||||||
|
}
|
||||||
|
|
||||||
Last = new TValue(input.Time, mid);
|
Last = new TValue(input.Time, mid);
|
||||||
Upper = new TValue(input.Time, top);
|
Upper = new TValue(input.Time, top);
|
||||||
@@ -285,7 +313,9 @@ public sealed class Fcb : ITValuePublisher
|
|||||||
public (TSeries Middle, TSeries Upper, TSeries Lower) Update(TBarSeries source)
|
public (TSeries Middle, TSeries Upper, TSeries Lower) Update(TBarSeries source)
|
||||||
{
|
{
|
||||||
if (source.Count == 0)
|
if (source.Count == 0)
|
||||||
|
{
|
||||||
return (new TSeries([], []), new TSeries([], []), new TSeries([], []));
|
return (new TSeries([], []), new TSeries([], []), new TSeries([], []));
|
||||||
|
}
|
||||||
|
|
||||||
int len = source.Count;
|
int len = source.Count;
|
||||||
var tMiddle = new List<long>(len);
|
var tMiddle = new List<long>(len);
|
||||||
@@ -329,7 +359,9 @@ public sealed class Fcb : ITValuePublisher
|
|||||||
Reset();
|
Reset();
|
||||||
|
|
||||||
if (source.Count == 0)
|
if (source.Count == 0)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < source.Count; i++)
|
for (int i = 0; i < source.Count; i++)
|
||||||
{
|
{
|
||||||
@@ -349,14 +381,25 @@ public sealed class Fcb : ITValuePublisher
|
|||||||
int period)
|
int period)
|
||||||
{
|
{
|
||||||
if (period < 1)
|
if (period < 1)
|
||||||
|
{
|
||||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be >= 1.");
|
throw new ArgumentOutOfRangeException(nameof(period), "Period must be >= 1.");
|
||||||
|
}
|
||||||
|
|
||||||
if (high.Length != low.Length)
|
if (high.Length != low.Length)
|
||||||
|
{
|
||||||
throw new ArgumentException("High and Low spans must have the same length", nameof(high));
|
throw new ArgumentException("High and Low spans must have the same length", nameof(high));
|
||||||
|
}
|
||||||
|
|
||||||
if (middle.Length < high.Length || upper.Length < high.Length || lower.Length < high.Length)
|
if (middle.Length < high.Length || upper.Length < high.Length || lower.Length < high.Length)
|
||||||
|
{
|
||||||
throw new ArgumentException("Output spans must be at least as long as inputs", nameof(middle));
|
throw new ArgumentException("Output spans must be at least as long as inputs", nameof(middle));
|
||||||
|
}
|
||||||
|
|
||||||
int len = high.Length;
|
int len = high.Length;
|
||||||
if (len == 0) return;
|
if (len == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Allocate buffers for fractal tracking and deques
|
// Allocate buffers for fractal tracking and deques
|
||||||
double[] hBuf = ArrayPool<double>.Shared.Rent(period);
|
double[] hBuf = ArrayPool<double>.Shared.Rent(period);
|
||||||
@@ -389,9 +432,14 @@ public sealed class Fcb : ITValuePublisher
|
|||||||
if (i >= 2)
|
if (i >= 2)
|
||||||
{
|
{
|
||||||
if (h1 > h2 && h1 > h0)
|
if (h1 > h2 && h1 > h0)
|
||||||
|
{
|
||||||
hiFractal = h1;
|
hiFractal = h1;
|
||||||
|
}
|
||||||
|
|
||||||
if (l1 < l2 && l1 < l0)
|
if (l1 < l2 && l1 < l0)
|
||||||
|
{
|
||||||
loFractal = l1;
|
loFractal = l1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int bufIdx = i % period;
|
int bufIdx = i % period;
|
||||||
@@ -410,9 +458,13 @@ public sealed class Fcb : ITValuePublisher
|
|||||||
int backIdx = (hHead + hCount - 1) % period;
|
int backIdx = (hHead + hCount - 1) % period;
|
||||||
int bIdx = hDeque[backIdx] % period;
|
int bIdx = hDeque[backIdx] % period;
|
||||||
if (hBuf[bIdx] <= hiFractal)
|
if (hBuf[bIdx] <= hiFractal)
|
||||||
|
{
|
||||||
hCount--;
|
hCount--;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
int tail = (hHead + hCount) % period;
|
int tail = (hHead + hCount) % period;
|
||||||
hDeque[tail] = i;
|
hDeque[tail] = i;
|
||||||
@@ -429,9 +481,13 @@ public sealed class Fcb : ITValuePublisher
|
|||||||
int backIdx = (lHead + lCount - 1) % period;
|
int backIdx = (lHead + lCount - 1) % period;
|
||||||
int bIdx = lDeque[backIdx] % period;
|
int bIdx = lDeque[backIdx] % period;
|
||||||
if (lBuf[bIdx] >= loFractal)
|
if (lBuf[bIdx] >= loFractal)
|
||||||
|
{
|
||||||
lCount--;
|
lCount--;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
tail = (lHead + lCount) % period;
|
tail = (lHead + lCount) % period;
|
||||||
lDeque[tail] = i;
|
lDeque[tail] = i;
|
||||||
|
|||||||
@@ -49,7 +49,9 @@ public sealed class JbandsIndicator : Indicator, IWatchlistIndicator
|
|||||||
protected override void OnUpdate(UpdateArgs args)
|
protected override void OnUpdate(UpdateArgs args)
|
||||||
{
|
{
|
||||||
if (_indicator is null)
|
if (_indicator is null)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var item = HistoricalData[0, SeekOriginHistory.End];
|
var item = HistoricalData[0, SeekOriginHistory.End];
|
||||||
bool isNew = args.IsNewBar();
|
bool isNew = args.IsNewBar();
|
||||||
|
|||||||
@@ -215,7 +215,9 @@ public class JbandsTests
|
|||||||
var gbm = new GBM(startPrice: 100, mu: 0.01, sigma: 0.1, seed: 123);
|
var gbm = new GBM(startPrice: 100, mu: 0.01, sigma: 0.1, seed: 123);
|
||||||
double[] source = new double[100];
|
double[] source = new double[100];
|
||||||
for (int i = 0; i < source.Length; i++)
|
for (int i = 0; i < source.Length; i++)
|
||||||
|
{
|
||||||
source[i] = gbm.Next().Close;
|
source[i] = gbm.Next().Close;
|
||||||
|
}
|
||||||
|
|
||||||
double[] middle = new double[100];
|
double[] middle = new double[100];
|
||||||
double[] upper = new double[100];
|
double[] upper = new double[100];
|
||||||
|
|||||||
@@ -95,7 +95,9 @@ public class JbandsValidationTests
|
|||||||
var gbm = new GBM(startPrice: 100, mu: 0.01, sigma: 0.1, seed: 777);
|
var gbm = new GBM(startPrice: 100, mu: 0.01, sigma: 0.1, seed: 777);
|
||||||
double[] source = new double[200];
|
double[] source = new double[200];
|
||||||
for (int i = 0; i < source.Length; i++)
|
for (int i = 0; i < source.Length; i++)
|
||||||
|
{
|
||||||
source[i] = gbm.Next().Close;
|
source[i] = gbm.Next().Close;
|
||||||
|
}
|
||||||
|
|
||||||
double[] middle = new double[200];
|
double[] middle = new double[200];
|
||||||
double[] upper = new double[200];
|
double[] upper = new double[200];
|
||||||
|
|||||||
@@ -59,17 +59,28 @@ public sealed class Jbands : ITValuePublisher
|
|||||||
public Jbands(int period, int phase = 0, double power = 0.45)
|
public Jbands(int period, int phase = 0, double power = 0.45)
|
||||||
{
|
{
|
||||||
if (period < 1)
|
if (period < 1)
|
||||||
|
{
|
||||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be >= 1.");
|
throw new ArgumentOutOfRangeException(nameof(period), "Period must be >= 1.");
|
||||||
|
}
|
||||||
|
|
||||||
if (!double.IsFinite(power))
|
if (!double.IsFinite(power))
|
||||||
|
{
|
||||||
throw new ArgumentException("Power must be finite.", nameof(power));
|
throw new ArgumentException("Power must be finite.", nameof(power));
|
||||||
|
}
|
||||||
|
|
||||||
// Phase parameter: maps -100..100 -> 0.5..2.5
|
// Phase parameter: maps -100..100 -> 0.5..2.5
|
||||||
if (phase < -100)
|
if (phase < -100)
|
||||||
|
{
|
||||||
_phaseParam = 0.5;
|
_phaseParam = 0.5;
|
||||||
|
}
|
||||||
else if (phase > 100)
|
else if (phase > 100)
|
||||||
|
{
|
||||||
_phaseParam = 2.5;
|
_phaseParam = 2.5;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
_phaseParam = (phase * 0.01) + 1.5;
|
_phaseParam = (phase * 0.01) + 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
// Length / log / divider parameters from decompiled JMA
|
// Length / log / divider parameters from decompiled JMA
|
||||||
double lengthParam = period < 1.0000000002
|
double lengthParam = period < 1.0000000002
|
||||||
@@ -129,7 +140,10 @@ public sealed class Jbands : ITValuePublisher
|
|||||||
if (!double.IsFinite(value))
|
if (!double.IsFinite(value))
|
||||||
{
|
{
|
||||||
if (_state.Bars == 0)
|
if (_state.Bars == 0)
|
||||||
|
{
|
||||||
return (double.NaN, double.NaN, double.NaN);
|
return (double.NaN, double.NaN, double.NaN);
|
||||||
|
}
|
||||||
|
|
||||||
value = _state.LastPrice;
|
value = _state.LastPrice;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -139,7 +153,9 @@ public sealed class Jbands : ITValuePublisher
|
|||||||
|
|
||||||
_state.Bars++;
|
_state.Bars++;
|
||||||
if (_state.Bars == 1)
|
if (_state.Bars == 1)
|
||||||
|
{
|
||||||
return InitializeFirstBar(value);
|
return InitializeFirstBar(value);
|
||||||
|
}
|
||||||
|
|
||||||
return CalculateJbands(value);
|
return CalculateJbands(value);
|
||||||
}
|
}
|
||||||
@@ -210,8 +226,16 @@ public sealed class Jbands : ITValuePublisher
|
|||||||
{
|
{
|
||||||
double ratio = Math.Max(absValue / refVolatility, 0.0);
|
double ratio = Math.Max(absValue / refVolatility, 0.0);
|
||||||
double d = Math.Pow(ratio, _pExponent);
|
double d = Math.Pow(ratio, _pExponent);
|
||||||
if (d > _logParam) d = _logParam;
|
if (d > _logParam)
|
||||||
if (d < 1.0) d = 1.0;
|
{
|
||||||
|
d = _logParam;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (d < 1.0)
|
||||||
|
{
|
||||||
|
d = 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,7 +290,9 @@ public sealed class Jbands : ITValuePublisher
|
|||||||
public (TSeries Middle, TSeries Upper, TSeries Lower) Update(TSeries source)
|
public (TSeries Middle, TSeries Upper, TSeries Lower) Update(TSeries source)
|
||||||
{
|
{
|
||||||
if (source.Count == 0)
|
if (source.Count == 0)
|
||||||
|
{
|
||||||
return (new TSeries([], []), new TSeries([], []), new TSeries([], []));
|
return (new TSeries([], []), new TSeries([], []), new TSeries([], []));
|
||||||
|
}
|
||||||
|
|
||||||
int len = source.Count;
|
int len = source.Count;
|
||||||
var tMiddle = new List<long>(len);
|
var tMiddle = new List<long>(len);
|
||||||
@@ -319,7 +345,10 @@ public sealed class Jbands : ITValuePublisher
|
|||||||
public void Prime(TSeries source)
|
public void Prime(TSeries source)
|
||||||
{
|
{
|
||||||
Reset();
|
Reset();
|
||||||
if (source.Count == 0) return;
|
if (source.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < source.Count; i++)
|
for (int i = 0; i < source.Count; i++)
|
||||||
{
|
{
|
||||||
@@ -343,13 +372,24 @@ public sealed class Jbands : ITValuePublisher
|
|||||||
double power = 0.45)
|
double power = 0.45)
|
||||||
{
|
{
|
||||||
if (middle.Length != source.Length)
|
if (middle.Length != source.Length)
|
||||||
|
{
|
||||||
throw new ArgumentException("Source and middle must have the same length.", nameof(middle));
|
throw new ArgumentException("Source and middle must have the same length.", nameof(middle));
|
||||||
|
}
|
||||||
|
|
||||||
if (upper.Length != source.Length)
|
if (upper.Length != source.Length)
|
||||||
|
{
|
||||||
throw new ArgumentException("Source and upper must have the same length.", nameof(upper));
|
throw new ArgumentException("Source and upper must have the same length.", nameof(upper));
|
||||||
|
}
|
||||||
|
|
||||||
if (lower.Length != source.Length)
|
if (lower.Length != source.Length)
|
||||||
|
{
|
||||||
throw new ArgumentException("Source and lower must have the same length.", nameof(lower));
|
throw new ArgumentException("Source and lower must have the same length.", nameof(lower));
|
||||||
|
}
|
||||||
|
|
||||||
if (source.Length == 0)
|
if (source.Length == 0)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var jbands = new Jbands(period, phase, power);
|
var jbands = new Jbands(period, phase, power);
|
||||||
for (int i = 0; i < source.Length; i++)
|
for (int i = 0; i < source.Length; i++)
|
||||||
@@ -366,7 +406,9 @@ public sealed class Jbands : ITValuePublisher
|
|||||||
{
|
{
|
||||||
int count = _volBuffer.Count;
|
int count = _volBuffer.Count;
|
||||||
if (count < 16)
|
if (count < 16)
|
||||||
|
{
|
||||||
return fallback;
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
Span<double> sorted = stackalloc double[count];
|
Span<double> sorted = stackalloc double[count];
|
||||||
_volBuffer.CopyTo(sorted);
|
_volBuffer.CopyTo(sorted);
|
||||||
@@ -387,8 +429,15 @@ public sealed class Jbands : ITValuePublisher
|
|||||||
end = drop + slice - 1;
|
end = drop + slice - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (start < 0) start = 0;
|
if (start < 0)
|
||||||
if (end >= count) end = count - 1;
|
{
|
||||||
|
start = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (end >= count)
|
||||||
|
{
|
||||||
|
end = count - 1;
|
||||||
|
}
|
||||||
|
|
||||||
int len = end - start + 1;
|
int len = end - start + 1;
|
||||||
return sorted.Slice(start, len).SumSIMD() / len;
|
return sorted.Slice(start, len).SumSIMD() / len;
|
||||||
|
|||||||
@@ -48,7 +48,9 @@ public sealed class KchannelIndicator : Indicator, IWatchlistIndicator
|
|||||||
protected override void OnUpdate(UpdateArgs args)
|
protected override void OnUpdate(UpdateArgs args)
|
||||||
{
|
{
|
||||||
if (_indicator is null)
|
if (_indicator is null)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var item = HistoricalData[0, SeekOriginHistory.End];
|
var item = HistoricalData[0, SeekOriginHistory.End];
|
||||||
bool isNew = args.IsNewBar();
|
bool isNew = args.IsNewBar();
|
||||||
|
|||||||
@@ -449,7 +449,9 @@ public sealed class KchannelValidationTests : IDisposable
|
|||||||
|
|
||||||
// After warmup, values should be within 5% (warmup methods may differ)
|
// After warmup, values should be within 5% (warmup methods may differ)
|
||||||
if (midPct < 0.05)
|
if (midPct < 0.05)
|
||||||
|
{
|
||||||
closeCount++;
|
closeCount++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,9 +52,14 @@ public sealed class Kchannel : ITValuePublisher
|
|||||||
public Kchannel(int period = 20, double multiplier = 2.0)
|
public Kchannel(int period = 20, double multiplier = 2.0)
|
||||||
{
|
{
|
||||||
if (period < 1)
|
if (period < 1)
|
||||||
|
{
|
||||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be >= 1.");
|
throw new ArgumentOutOfRangeException(nameof(period), "Period must be >= 1.");
|
||||||
|
}
|
||||||
|
|
||||||
if (multiplier <= 0.0)
|
if (multiplier <= 0.0)
|
||||||
|
{
|
||||||
throw new ArgumentOutOfRangeException(nameof(multiplier), "Multiplier must be > 0.");
|
throw new ArgumentOutOfRangeException(nameof(multiplier), "Multiplier must be > 0.");
|
||||||
|
}
|
||||||
|
|
||||||
_period = period;
|
_period = period;
|
||||||
_multiplier = multiplier;
|
_multiplier = multiplier;
|
||||||
@@ -95,19 +100,31 @@ public sealed class Kchannel : ITValuePublisher
|
|||||||
private (double close, double high, double low) GetValid(double close, double high, double low)
|
private (double close, double high, double low) GetValid(double close, double high, double low)
|
||||||
{
|
{
|
||||||
if (double.IsFinite(close))
|
if (double.IsFinite(close))
|
||||||
|
{
|
||||||
_state = _state with { LastValidClose = close };
|
_state = _state with { LastValidClose = close };
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
close = _state.LastValidClose;
|
close = _state.LastValidClose;
|
||||||
|
}
|
||||||
|
|
||||||
if (double.IsFinite(high))
|
if (double.IsFinite(high))
|
||||||
|
{
|
||||||
_state = _state with { LastValidHigh = high };
|
_state = _state with { LastValidHigh = high };
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
high = _state.LastValidHigh;
|
high = _state.LastValidHigh;
|
||||||
|
}
|
||||||
|
|
||||||
if (double.IsFinite(low))
|
if (double.IsFinite(low))
|
||||||
|
{
|
||||||
_state = _state with { LastValidLow = low };
|
_state = _state with { LastValidLow = low };
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
low = _state.LastValidLow;
|
low = _state.LastValidLow;
|
||||||
|
}
|
||||||
|
|
||||||
return (close, high, low);
|
return (close, high, low);
|
||||||
}
|
}
|
||||||
@@ -116,9 +133,13 @@ public sealed class Kchannel : ITValuePublisher
|
|||||||
public TValue Update(TBar input, bool isNew = true)
|
public TValue Update(TBar input, bool isNew = true)
|
||||||
{
|
{
|
||||||
if (isNew)
|
if (isNew)
|
||||||
|
{
|
||||||
_p_state = _state;
|
_p_state = _state;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
_state = _p_state;
|
_state = _p_state;
|
||||||
|
}
|
||||||
|
|
||||||
var (close, high, low) = GetValid(input.Close, input.High, input.Low);
|
var (close, high, low) = GetValid(input.Close, input.High, input.Low);
|
||||||
|
|
||||||
@@ -144,7 +165,9 @@ public sealed class Kchannel : ITValuePublisher
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isNew)
|
if (isNew)
|
||||||
|
{
|
||||||
_state = _state with { Bars = _state.Bars + 1 };
|
_state = _state with { Bars = _state.Bars + 1 };
|
||||||
|
}
|
||||||
|
|
||||||
// EMA with warmup compensation (sum/weight approach)
|
// EMA with warmup compensation (sum/weight approach)
|
||||||
double newSum = Math.FusedMultiplyAdd(_state.EmaSum, 1.0 - _emaAlpha, close * _emaAlpha);
|
double newSum = Math.FusedMultiplyAdd(_state.EmaSum, 1.0 - _emaAlpha, close * _emaAlpha);
|
||||||
@@ -179,7 +202,9 @@ public sealed class Kchannel : ITValuePublisher
|
|||||||
double lower = emaValue - width;
|
double lower = emaValue - width;
|
||||||
|
|
||||||
if (!_state.IsHot && _state.Bars >= WarmupPeriod)
|
if (!_state.IsHot && _state.Bars >= WarmupPeriod)
|
||||||
|
{
|
||||||
_state = _state with { IsHot = true };
|
_state = _state with { IsHot = true };
|
||||||
|
}
|
||||||
|
|
||||||
Last = new TValue(input.Time, emaValue);
|
Last = new TValue(input.Time, emaValue);
|
||||||
Upper = new TValue(input.Time, upper);
|
Upper = new TValue(input.Time, upper);
|
||||||
@@ -192,7 +217,9 @@ public sealed class Kchannel : ITValuePublisher
|
|||||||
public (TSeries Middle, TSeries Upper, TSeries Lower) Update(TBarSeries source)
|
public (TSeries Middle, TSeries Upper, TSeries Lower) Update(TBarSeries source)
|
||||||
{
|
{
|
||||||
if (source.Count == 0)
|
if (source.Count == 0)
|
||||||
|
{
|
||||||
return (new TSeries([], []), new TSeries([], []), new TSeries([], []));
|
return (new TSeries([], []), new TSeries([], []), new TSeries([], []));
|
||||||
|
}
|
||||||
|
|
||||||
int len = source.Count;
|
int len = source.Count;
|
||||||
var tMiddle = new List<long>(len);
|
var tMiddle = new List<long>(len);
|
||||||
@@ -237,7 +264,9 @@ public sealed class Kchannel : ITValuePublisher
|
|||||||
Reset();
|
Reset();
|
||||||
|
|
||||||
if (source.Count == 0)
|
if (source.Count == 0)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < source.Count; i++)
|
for (int i = 0; i < source.Count; i++)
|
||||||
{
|
{
|
||||||
@@ -259,16 +288,30 @@ public sealed class Kchannel : ITValuePublisher
|
|||||||
double multiplier = 2.0)
|
double multiplier = 2.0)
|
||||||
{
|
{
|
||||||
if (period < 1)
|
if (period < 1)
|
||||||
|
{
|
||||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be >= 1.");
|
throw new ArgumentOutOfRangeException(nameof(period), "Period must be >= 1.");
|
||||||
|
}
|
||||||
|
|
||||||
if (multiplier <= 0.0)
|
if (multiplier <= 0.0)
|
||||||
|
{
|
||||||
throw new ArgumentOutOfRangeException(nameof(multiplier), "Multiplier must be > 0.");
|
throw new ArgumentOutOfRangeException(nameof(multiplier), "Multiplier must be > 0.");
|
||||||
|
}
|
||||||
|
|
||||||
if (high.Length != low.Length || high.Length != close.Length)
|
if (high.Length != low.Length || high.Length != close.Length)
|
||||||
|
{
|
||||||
throw new ArgumentException("High, Low, and Close spans must have the same length", nameof(high));
|
throw new ArgumentException("High, Low, and Close spans must have the same length", nameof(high));
|
||||||
|
}
|
||||||
|
|
||||||
if (middle.Length < high.Length || upper.Length < high.Length || lower.Length < high.Length)
|
if (middle.Length < high.Length || upper.Length < high.Length || lower.Length < high.Length)
|
||||||
|
{
|
||||||
throw new ArgumentException("Output spans must be at least as long as inputs", nameof(middle));
|
throw new ArgumentException("Output spans must be at least as long as inputs", nameof(middle));
|
||||||
|
}
|
||||||
|
|
||||||
int len = high.Length;
|
int len = high.Length;
|
||||||
if (len == 0) return;
|
if (len == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
double emaAlpha = 2.0 / (period + 1);
|
double emaAlpha = 2.0 / (period + 1);
|
||||||
double atrAlpha = 1.0 / period;
|
double atrAlpha = 1.0 / period;
|
||||||
|
|||||||
@@ -53,7 +53,9 @@ public sealed class MaenvIndicator : Indicator, IWatchlistIndicator
|
|||||||
protected override void OnUpdate(UpdateArgs args)
|
protected override void OnUpdate(UpdateArgs args)
|
||||||
{
|
{
|
||||||
if (_indicator is null)
|
if (_indicator is null)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var item = HistoricalData[0, SeekOriginHistory.End];
|
var item = HistoricalData[0, SeekOriginHistory.End];
|
||||||
bool isNew = args.IsNewBar();
|
bool isNew = args.IsNewBar();
|
||||||
|
|||||||
@@ -72,9 +72,14 @@ public sealed class Maenv : ITValuePublisher
|
|||||||
public Maenv(int period = 20, double percentage = 1.0, MaenvType maType = MaenvType.EMA)
|
public Maenv(int period = 20, double percentage = 1.0, MaenvType maType = MaenvType.EMA)
|
||||||
{
|
{
|
||||||
if (period < 1)
|
if (period < 1)
|
||||||
|
{
|
||||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be >= 1.");
|
throw new ArgumentOutOfRangeException(nameof(period), "Period must be >= 1.");
|
||||||
|
}
|
||||||
|
|
||||||
if (percentage <= 0.0)
|
if (percentage <= 0.0)
|
||||||
|
{
|
||||||
throw new ArgumentOutOfRangeException(nameof(percentage), "Percentage must be > 0.");
|
throw new ArgumentOutOfRangeException(nameof(percentage), "Percentage must be > 0.");
|
||||||
|
}
|
||||||
|
|
||||||
_period = period;
|
_period = period;
|
||||||
_percentage = percentage;
|
_percentage = percentage;
|
||||||
@@ -142,7 +147,10 @@ public sealed class Maenv : ITValuePublisher
|
|||||||
if (double.IsFinite(value))
|
if (double.IsFinite(value))
|
||||||
{
|
{
|
||||||
if (isNew)
|
if (isNew)
|
||||||
|
{
|
||||||
_state = _state with { LastValid = value };
|
_state = _state with { LastValid = value };
|
||||||
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
return _state.LastValid;
|
return _state.LastValid;
|
||||||
@@ -159,23 +167,35 @@ public sealed class Maenv : ITValuePublisher
|
|||||||
{
|
{
|
||||||
_p_state = _state;
|
_p_state = _state;
|
||||||
if (_smaBuffer != null && _p_smaBuffer != null)
|
if (_smaBuffer != null && _p_smaBuffer != null)
|
||||||
|
{
|
||||||
Array.Copy(_smaBuffer, _p_smaBuffer, _period);
|
Array.Copy(_smaBuffer, _p_smaBuffer, _period);
|
||||||
|
}
|
||||||
|
|
||||||
if (_wmaBuffer != null && _p_wmaBuffer != null)
|
if (_wmaBuffer != null && _p_wmaBuffer != null)
|
||||||
|
{
|
||||||
Array.Copy(_wmaBuffer, _p_wmaBuffer, _period);
|
Array.Copy(_wmaBuffer, _p_wmaBuffer, _period);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_state = _p_state;
|
_state = _p_state;
|
||||||
if (_smaBuffer != null && _p_smaBuffer != null)
|
if (_smaBuffer != null && _p_smaBuffer != null)
|
||||||
|
{
|
||||||
Array.Copy(_p_smaBuffer, _smaBuffer, _period);
|
Array.Copy(_p_smaBuffer, _smaBuffer, _period);
|
||||||
|
}
|
||||||
|
|
||||||
if (_wmaBuffer != null && _p_wmaBuffer != null)
|
if (_wmaBuffer != null && _p_wmaBuffer != null)
|
||||||
|
{
|
||||||
Array.Copy(_p_wmaBuffer, _wmaBuffer, _period);
|
Array.Copy(_p_wmaBuffer, _wmaBuffer, _period);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
double value = GetValid(input.Value, isNew);
|
double value = GetValid(input.Value, isNew);
|
||||||
|
|
||||||
if (isNew)
|
if (isNew)
|
||||||
|
{
|
||||||
_state = _state with { Bars = _state.Bars + 1 };
|
_state = _state with { Bars = _state.Bars + 1 };
|
||||||
|
}
|
||||||
|
|
||||||
double middle = _maType switch
|
double middle = _maType switch
|
||||||
{
|
{
|
||||||
@@ -190,7 +210,9 @@ public sealed class Maenv : ITValuePublisher
|
|||||||
double lower = middle - dist;
|
double lower = middle - dist;
|
||||||
|
|
||||||
if (!_state.IsHot && _state.Bars >= WarmupPeriod)
|
if (!_state.IsHot && _state.Bars >= WarmupPeriod)
|
||||||
|
{
|
||||||
_state = _state with { IsHot = true };
|
_state = _state with { IsHot = true };
|
||||||
|
}
|
||||||
|
|
||||||
Last = new TValue(input.Time, middle);
|
Last = new TValue(input.Time, middle);
|
||||||
Upper = new TValue(input.Time, upper);
|
Upper = new TValue(input.Time, upper);
|
||||||
@@ -203,7 +225,9 @@ public sealed class Maenv : ITValuePublisher
|
|||||||
public (TSeries Middle, TSeries Upper, TSeries Lower) Update(TSeries source)
|
public (TSeries Middle, TSeries Upper, TSeries Lower) Update(TSeries source)
|
||||||
{
|
{
|
||||||
if (source.Count == 0)
|
if (source.Count == 0)
|
||||||
|
{
|
||||||
return (new TSeries([], []), new TSeries([], []), new TSeries([], []));
|
return (new TSeries([], []), new TSeries([], []), new TSeries([], []));
|
||||||
|
}
|
||||||
|
|
||||||
int len = source.Count;
|
int len = source.Count;
|
||||||
var tMiddle = new List<long>(len);
|
var tMiddle = new List<long>(len);
|
||||||
@@ -249,7 +273,10 @@ public sealed class Maenv : ITValuePublisher
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private double CalculateSMA(double value, bool isNew)
|
private double CalculateSMA(double value, bool isNew)
|
||||||
{
|
{
|
||||||
if (_smaBuffer == null) return value;
|
if (_smaBuffer == null)
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate new count (always increment if not full, for both isNew cases)
|
// Calculate new count (always increment if not full, for both isNew cases)
|
||||||
int currentCount = _state.SmaCount;
|
int currentCount = _state.SmaCount;
|
||||||
@@ -323,7 +350,10 @@ public sealed class Maenv : ITValuePublisher
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private double CalculateWMA(double value, bool isNew)
|
private double CalculateWMA(double value, bool isNew)
|
||||||
{
|
{
|
||||||
if (_wmaBuffer == null) return value;
|
if (_wmaBuffer == null)
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate count for this bar (always increment if not full, for both isNew cases)
|
// Calculate count for this bar (always increment if not full, for both isNew cases)
|
||||||
int currentCount = _state.WmaCount;
|
int currentCount = _state.WmaCount;
|
||||||
@@ -334,13 +364,17 @@ public sealed class Maenv : ITValuePublisher
|
|||||||
if (calcCount > 1)
|
if (calcCount > 1)
|
||||||
{
|
{
|
||||||
for (int i = _period - 1; i > 0; i--)
|
for (int i = _period - 1; i > 0; i--)
|
||||||
|
{
|
||||||
_wmaBuffer[i] = _wmaBuffer[i - 1];
|
_wmaBuffer[i] = _wmaBuffer[i - 1];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_wmaBuffer[0] = value;
|
_wmaBuffer[0] = value;
|
||||||
|
|
||||||
// Persist state only for isNew=true
|
// Persist state only for isNew=true
|
||||||
if (isNew)
|
if (isNew)
|
||||||
|
{
|
||||||
_state = _state with { WmaCount = calcCount };
|
_state = _state with { WmaCount = calcCount };
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate WMA
|
// Calculate WMA
|
||||||
double norm = 0.0;
|
double norm = 0.0;
|
||||||
@@ -361,7 +395,9 @@ public sealed class Maenv : ITValuePublisher
|
|||||||
Reset();
|
Reset();
|
||||||
|
|
||||||
if (source.Count == 0)
|
if (source.Count == 0)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < source.Count; i++)
|
for (int i = 0; i < source.Count; i++)
|
||||||
{
|
{
|
||||||
@@ -386,14 +422,25 @@ public sealed class Maenv : ITValuePublisher
|
|||||||
MaenvType maType = MaenvType.EMA)
|
MaenvType maType = MaenvType.EMA)
|
||||||
{
|
{
|
||||||
if (period < 1)
|
if (period < 1)
|
||||||
|
{
|
||||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be >= 1.");
|
throw new ArgumentOutOfRangeException(nameof(period), "Period must be >= 1.");
|
||||||
|
}
|
||||||
|
|
||||||
if (percentage <= 0.0)
|
if (percentage <= 0.0)
|
||||||
|
{
|
||||||
throw new ArgumentOutOfRangeException(nameof(percentage), "Percentage must be > 0.");
|
throw new ArgumentOutOfRangeException(nameof(percentage), "Percentage must be > 0.");
|
||||||
|
}
|
||||||
|
|
||||||
if (middle.Length < source.Length || upper.Length < source.Length || lower.Length < source.Length)
|
if (middle.Length < source.Length || upper.Length < source.Length || lower.Length < source.Length)
|
||||||
|
{
|
||||||
throw new ArgumentException("Output spans must be at least as long as input", nameof(middle));
|
throw new ArgumentException("Output spans must be at least as long as input", nameof(middle));
|
||||||
|
}
|
||||||
|
|
||||||
int len = source.Length;
|
int len = source.Length;
|
||||||
if (len == 0) return;
|
if (len == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
switch (maType)
|
switch (maType)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -41,7 +41,9 @@ public sealed class MmchannelIndicator : Indicator, IWatchlistIndicator
|
|||||||
protected override void OnUpdate(UpdateArgs args)
|
protected override void OnUpdate(UpdateArgs args)
|
||||||
{
|
{
|
||||||
if (_indicator is null)
|
if (_indicator is null)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var item = HistoricalData[0, SeekOriginHistory.End];
|
var item = HistoricalData[0, SeekOriginHistory.End];
|
||||||
bool isNew = args.IsNewBar();
|
bool isNew = args.IsNewBar();
|
||||||
|
|||||||
@@ -41,7 +41,9 @@ public sealed class Mmchannel : ITValuePublisher
|
|||||||
public Mmchannel(int period)
|
public Mmchannel(int period)
|
||||||
{
|
{
|
||||||
if (period <= 0)
|
if (period <= 0)
|
||||||
|
{
|
||||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||||
|
}
|
||||||
|
|
||||||
_period = period;
|
_period = period;
|
||||||
_hBuf = new double[_period];
|
_hBuf = new double[_period];
|
||||||
@@ -73,14 +75,22 @@ public sealed class Mmchannel : ITValuePublisher
|
|||||||
private (double high, double low) GetValid(double high, double low)
|
private (double high, double low) GetValid(double high, double low)
|
||||||
{
|
{
|
||||||
if (double.IsFinite(high))
|
if (double.IsFinite(high))
|
||||||
|
{
|
||||||
_state = _state with { LastValidHigh = high };
|
_state = _state with { LastValidHigh = high };
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
high = _state.LastValidHigh;
|
high = _state.LastValidHigh;
|
||||||
|
}
|
||||||
|
|
||||||
if (double.IsFinite(low))
|
if (double.IsFinite(low))
|
||||||
|
{
|
||||||
_state = _state with { LastValidLow = low };
|
_state = _state with { LastValidLow = low };
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
low = _state.LastValidLow;
|
low = _state.LastValidLow;
|
||||||
|
}
|
||||||
|
|
||||||
return (high, low);
|
return (high, low);
|
||||||
}
|
}
|
||||||
@@ -89,15 +99,21 @@ public sealed class Mmchannel : ITValuePublisher
|
|||||||
public TValue Update(TBar input, bool isNew = true)
|
public TValue Update(TBar input, bool isNew = true)
|
||||||
{
|
{
|
||||||
if (isNew)
|
if (isNew)
|
||||||
|
{
|
||||||
_p_state = _state;
|
_p_state = _state;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
_state = _p_state;
|
_state = _p_state;
|
||||||
|
}
|
||||||
|
|
||||||
if (isNew)
|
if (isNew)
|
||||||
{
|
{
|
||||||
_index++;
|
_index++;
|
||||||
if (_count < _period)
|
if (_count < _period)
|
||||||
|
{
|
||||||
_count++;
|
_count++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int bufIdx = (int)(_index % _period);
|
int bufIdx = (int)(_index % _period);
|
||||||
@@ -132,7 +148,9 @@ public sealed class Mmchannel : ITValuePublisher
|
|||||||
double bot = _minDeque.GetExtremum(_lBuf);
|
double bot = _minDeque.GetExtremum(_lBuf);
|
||||||
|
|
||||||
if (!IsHot && _count >= _period)
|
if (!IsHot && _count >= _period)
|
||||||
|
{
|
||||||
_state = _state with { IsHot = true };
|
_state = _state with { IsHot = true };
|
||||||
|
}
|
||||||
|
|
||||||
// Last returns Upper by default for single-value compatibility
|
// Last returns Upper by default for single-value compatibility
|
||||||
Last = new TValue(input.Time, top);
|
Last = new TValue(input.Time, top);
|
||||||
@@ -146,7 +164,9 @@ public sealed class Mmchannel : ITValuePublisher
|
|||||||
public (TSeries Upper, TSeries Lower) Update(TBarSeries source)
|
public (TSeries Upper, TSeries Lower) Update(TBarSeries source)
|
||||||
{
|
{
|
||||||
if (source.Count == 0)
|
if (source.Count == 0)
|
||||||
|
{
|
||||||
return (new TSeries([], []), new TSeries([], []));
|
return (new TSeries([], []), new TSeries([], []));
|
||||||
|
}
|
||||||
|
|
||||||
int len = source.Count;
|
int len = source.Count;
|
||||||
var tUpper = new List<long>(len);
|
var tUpper = new List<long>(len);
|
||||||
@@ -184,7 +204,9 @@ public sealed class Mmchannel : ITValuePublisher
|
|||||||
Reset();
|
Reset();
|
||||||
|
|
||||||
if (source.Count == 0)
|
if (source.Count == 0)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < source.Count; i++)
|
for (int i = 0; i < source.Count; i++)
|
||||||
{
|
{
|
||||||
@@ -218,14 +240,25 @@ public sealed class Mmchannel : ITValuePublisher
|
|||||||
int period)
|
int period)
|
||||||
{
|
{
|
||||||
if (period <= 0)
|
if (period <= 0)
|
||||||
|
{
|
||||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||||
|
}
|
||||||
|
|
||||||
if (high.Length != low.Length)
|
if (high.Length != low.Length)
|
||||||
|
{
|
||||||
throw new ArgumentException("High and Low spans must have the same length", nameof(high));
|
throw new ArgumentException("High and Low spans must have the same length", nameof(high));
|
||||||
|
}
|
||||||
|
|
||||||
if (upper.Length < high.Length || lower.Length < high.Length)
|
if (upper.Length < high.Length || lower.Length < high.Length)
|
||||||
|
{
|
||||||
throw new ArgumentException("Output spans must be at least as long as inputs", nameof(upper));
|
throw new ArgumentException("Output spans must be at least as long as inputs", nameof(upper));
|
||||||
|
}
|
||||||
|
|
||||||
int len = high.Length;
|
int len = high.Length;
|
||||||
if (len == 0) return;
|
if (len == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Highest.Calculate(high, upper, period);
|
Highest.Calculate(high, upper, period);
|
||||||
Lowest.Calculate(low, lower, period);
|
Lowest.Calculate(low, lower, period);
|
||||||
@@ -261,4 +294,4 @@ public sealed class Mmchannel : ITValuePublisher
|
|||||||
var results = indicator.Update(source);
|
var results = indicator.Update(source);
|
||||||
return (results, indicator);
|
return (results, indicator);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,9 @@ public sealed class PchannelIndicator : Indicator, IWatchlistIndicator
|
|||||||
protected override void OnUpdate(UpdateArgs args)
|
protected override void OnUpdate(UpdateArgs args)
|
||||||
{
|
{
|
||||||
if (_indicator is null)
|
if (_indicator is null)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var item = HistoricalData[0, SeekOriginHistory.End];
|
var item = HistoricalData[0, SeekOriginHistory.End];
|
||||||
bool isNew = args.IsNewBar();
|
bool isNew = args.IsNewBar();
|
||||||
|
|||||||
@@ -18,9 +18,16 @@ public sealed class PchannelValidationTests : IDisposable
|
|||||||
|
|
||||||
private void Dispose(bool disposing)
|
private void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
if (_disposed) return;
|
if (_disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
if (disposing) _testData?.Dispose();
|
if (disposing)
|
||||||
|
{
|
||||||
|
_testData?.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|||||||
@@ -49,7 +49,9 @@ public sealed class Pchannel : ITValuePublisher
|
|||||||
public Pchannel(int period)
|
public Pchannel(int period)
|
||||||
{
|
{
|
||||||
if (period <= 0)
|
if (period <= 0)
|
||||||
|
{
|
||||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||||
|
}
|
||||||
|
|
||||||
_period = period;
|
_period = period;
|
||||||
_hBuf = new double[_period];
|
_hBuf = new double[_period];
|
||||||
@@ -85,14 +87,22 @@ public sealed class Pchannel : ITValuePublisher
|
|||||||
private (double high, double low) GetValid(double high, double low)
|
private (double high, double low) GetValid(double high, double low)
|
||||||
{
|
{
|
||||||
if (double.IsFinite(high))
|
if (double.IsFinite(high))
|
||||||
|
{
|
||||||
_state = _state with { LastValidHigh = high };
|
_state = _state with { LastValidHigh = high };
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
high = _state.LastValidHigh;
|
high = _state.LastValidHigh;
|
||||||
|
}
|
||||||
|
|
||||||
if (double.IsFinite(low))
|
if (double.IsFinite(low))
|
||||||
|
{
|
||||||
_state = _state with { LastValidLow = low };
|
_state = _state with { LastValidLow = low };
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
low = _state.LastValidLow;
|
low = _state.LastValidLow;
|
||||||
|
}
|
||||||
|
|
||||||
return (high, low);
|
return (high, low);
|
||||||
}
|
}
|
||||||
@@ -165,7 +175,9 @@ public sealed class Pchannel : ITValuePublisher
|
|||||||
_lCount = 0;
|
_lCount = 0;
|
||||||
|
|
||||||
if (_count == 0)
|
if (_count == 0)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
long startLogical = _index - _count + 1;
|
long startLogical = _index - _count + 1;
|
||||||
for (int i = 0; i < _count; i++)
|
for (int i = 0; i < _count; i++)
|
||||||
@@ -183,15 +195,21 @@ public sealed class Pchannel : ITValuePublisher
|
|||||||
public TValue Update(TBar input, bool isNew = true)
|
public TValue Update(TBar input, bool isNew = true)
|
||||||
{
|
{
|
||||||
if (isNew)
|
if (isNew)
|
||||||
|
{
|
||||||
_p_state = _state;
|
_p_state = _state;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
_state = _p_state;
|
_state = _p_state;
|
||||||
|
}
|
||||||
|
|
||||||
if (isNew)
|
if (isNew)
|
||||||
{
|
{
|
||||||
_index++;
|
_index++;
|
||||||
if (_count < _period)
|
if (_count < _period)
|
||||||
|
{
|
||||||
_count++;
|
_count++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int bufIdx = (int)(_index % _period);
|
int bufIdx = (int)(_index % _period);
|
||||||
@@ -224,7 +242,9 @@ public sealed class Pchannel : ITValuePublisher
|
|||||||
double mid = (top + bot) * 0.5;
|
double mid = (top + bot) * 0.5;
|
||||||
|
|
||||||
if (!IsHot && _count >= _period)
|
if (!IsHot && _count >= _period)
|
||||||
|
{
|
||||||
_state = _state with { IsHot = true };
|
_state = _state with { IsHot = true };
|
||||||
|
}
|
||||||
|
|
||||||
Last = new TValue(input.Time, mid);
|
Last = new TValue(input.Time, mid);
|
||||||
Upper = new TValue(input.Time, top);
|
Upper = new TValue(input.Time, top);
|
||||||
@@ -237,7 +257,9 @@ public sealed class Pchannel : ITValuePublisher
|
|||||||
public (TSeries Middle, TSeries Upper, TSeries Lower) Update(TBarSeries source)
|
public (TSeries Middle, TSeries Upper, TSeries Lower) Update(TBarSeries source)
|
||||||
{
|
{
|
||||||
if (source.Count == 0)
|
if (source.Count == 0)
|
||||||
|
{
|
||||||
return (new TSeries([], []), new TSeries([], []), new TSeries([], []));
|
return (new TSeries([], []), new TSeries([], []), new TSeries([], []));
|
||||||
|
}
|
||||||
|
|
||||||
int len = source.Count;
|
int len = source.Count;
|
||||||
var tMiddle = new List<long>(len);
|
var tMiddle = new List<long>(len);
|
||||||
@@ -280,7 +302,9 @@ public sealed class Pchannel : ITValuePublisher
|
|||||||
Reset();
|
Reset();
|
||||||
|
|
||||||
if (source.Count == 0)
|
if (source.Count == 0)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < source.Count; i++)
|
for (int i = 0; i < source.Count; i++)
|
||||||
{
|
{
|
||||||
@@ -317,14 +341,25 @@ public sealed class Pchannel : ITValuePublisher
|
|||||||
int period)
|
int period)
|
||||||
{
|
{
|
||||||
if (period <= 0)
|
if (period <= 0)
|
||||||
|
{
|
||||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||||
|
}
|
||||||
|
|
||||||
if (high.Length != low.Length)
|
if (high.Length != low.Length)
|
||||||
|
{
|
||||||
throw new ArgumentException("High and Low spans must have the same length", nameof(high));
|
throw new ArgumentException("High and Low spans must have the same length", nameof(high));
|
||||||
|
}
|
||||||
|
|
||||||
if (middle.Length < high.Length || upper.Length < high.Length || lower.Length < high.Length)
|
if (middle.Length < high.Length || upper.Length < high.Length || lower.Length < high.Length)
|
||||||
|
{
|
||||||
throw new ArgumentException("Output spans must be at least as long as inputs", nameof(middle));
|
throw new ArgumentException("Output spans must be at least as long as inputs", nameof(middle));
|
||||||
|
}
|
||||||
|
|
||||||
int len = high.Length;
|
int len = high.Length;
|
||||||
if (len == 0) return;
|
if (len == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
double[] top = ArrayPool<double>.Shared.Rent(len);
|
double[] top = ArrayPool<double>.Shared.Rent(len);
|
||||||
double[] bot = ArrayPool<double>.Shared.Rent(len);
|
double[] bot = ArrayPool<double>.Shared.Rent(len);
|
||||||
|
|||||||
@@ -50,7 +50,9 @@ public sealed class RegchannelIndicator : Indicator, IWatchlistIndicator
|
|||||||
protected override void OnUpdate(UpdateArgs args)
|
protected override void OnUpdate(UpdateArgs args)
|
||||||
{
|
{
|
||||||
if (_indicator is null)
|
if (_indicator is null)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var item = HistoricalData[0, SeekOriginHistory.End];
|
var item = HistoricalData[0, SeekOriginHistory.End];
|
||||||
bool isNew = args.IsNewBar();
|
bool isNew = args.IsNewBar();
|
||||||
|
|||||||
@@ -83,9 +83,14 @@ public sealed class Regchannel : ITValuePublisher
|
|||||||
public Regchannel(int period = 20, double multiplier = 2.0)
|
public Regchannel(int period = 20, double multiplier = 2.0)
|
||||||
{
|
{
|
||||||
if (period <= 1)
|
if (period <= 1)
|
||||||
|
{
|
||||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than 1.");
|
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than 1.");
|
||||||
|
}
|
||||||
|
|
||||||
if (multiplier <= 0)
|
if (multiplier <= 0)
|
||||||
|
{
|
||||||
throw new ArgumentOutOfRangeException(nameof(multiplier), "Multiplier must be greater than 0.");
|
throw new ArgumentOutOfRangeException(nameof(multiplier), "Multiplier must be greater than 0.");
|
||||||
|
}
|
||||||
|
|
||||||
_period = period;
|
_period = period;
|
||||||
_multiplier = multiplier;
|
_multiplier = multiplier;
|
||||||
@@ -163,13 +168,17 @@ public sealed class Regchannel : ITValuePublisher
|
|||||||
int head = _state.Head;
|
int head = _state.Head;
|
||||||
|
|
||||||
if (count < _period)
|
if (count < _period)
|
||||||
|
{
|
||||||
count++;
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
_buffer[head] = value;
|
_buffer[head] = value;
|
||||||
int newHead = (head + 1) % _period;
|
int newHead = (head + 1) % _period;
|
||||||
|
|
||||||
if (isNew)
|
if (isNew)
|
||||||
|
{
|
||||||
_state = _state with { Head = newHead, Count = count };
|
_state = _state with { Head = newHead, Count = count };
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate linear regression and std dev of residuals
|
// Calculate linear regression and std dev of residuals
|
||||||
if (count <= 1)
|
if (count <= 1)
|
||||||
@@ -241,7 +250,9 @@ public sealed class Regchannel : ITValuePublisher
|
|||||||
double band = _multiplier * stdDev;
|
double band = _multiplier * stdDev;
|
||||||
|
|
||||||
if (!_state.IsHot && count >= WarmupPeriod)
|
if (!_state.IsHot && count >= WarmupPeriod)
|
||||||
|
{
|
||||||
_state = _state with { IsHot = true };
|
_state = _state with { IsHot = true };
|
||||||
|
}
|
||||||
|
|
||||||
_state = _state with { Slope = slope, StdDev = stdDev };
|
_state = _state with { Slope = slope, StdDev = stdDev };
|
||||||
|
|
||||||
@@ -256,7 +267,9 @@ public sealed class Regchannel : ITValuePublisher
|
|||||||
public (TSeries Middle, TSeries Upper, TSeries Lower) Update(TSeries source)
|
public (TSeries Middle, TSeries Upper, TSeries Lower) Update(TSeries source)
|
||||||
{
|
{
|
||||||
if (source.Count == 0)
|
if (source.Count == 0)
|
||||||
|
{
|
||||||
return (new TSeries([], []), new TSeries([], []), new TSeries([], []));
|
return (new TSeries([], []), new TSeries([], []), new TSeries([], []));
|
||||||
|
}
|
||||||
|
|
||||||
int len = source.Count;
|
int len = source.Count;
|
||||||
var tMiddle = new List<long>(len);
|
var tMiddle = new List<long>(len);
|
||||||
@@ -300,7 +313,9 @@ public sealed class Regchannel : ITValuePublisher
|
|||||||
Reset();
|
Reset();
|
||||||
|
|
||||||
if (source.Count == 0)
|
if (source.Count == 0)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < source.Count; i++)
|
for (int i = 0; i < source.Count; i++)
|
||||||
{
|
{
|
||||||
@@ -320,14 +335,25 @@ public sealed class Regchannel : ITValuePublisher
|
|||||||
double multiplier = 2.0)
|
double multiplier = 2.0)
|
||||||
{
|
{
|
||||||
if (period <= 1)
|
if (period <= 1)
|
||||||
|
{
|
||||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than 1.");
|
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than 1.");
|
||||||
|
}
|
||||||
|
|
||||||
if (multiplier <= 0)
|
if (multiplier <= 0)
|
||||||
|
{
|
||||||
throw new ArgumentOutOfRangeException(nameof(multiplier), "Multiplier must be greater than 0.");
|
throw new ArgumentOutOfRangeException(nameof(multiplier), "Multiplier must be greater than 0.");
|
||||||
|
}
|
||||||
|
|
||||||
if (middle.Length < source.Length || upper.Length < source.Length || lower.Length < source.Length)
|
if (middle.Length < source.Length || upper.Length < source.Length || lower.Length < source.Length)
|
||||||
|
{
|
||||||
throw new ArgumentException("Output spans must be at least as long as input", nameof(middle));
|
throw new ArgumentException("Output spans must be at least as long as input", nameof(middle));
|
||||||
|
}
|
||||||
|
|
||||||
int len = source.Length;
|
int len = source.Length;
|
||||||
if (len == 0) return;
|
if (len == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Precompute constants for full period
|
// Precompute constants for full period
|
||||||
double sumXFull = 0.5 * period * (period - 1);
|
double sumXFull = 0.5 * period * (period - 1);
|
||||||
|
|||||||
@@ -50,7 +50,9 @@ public sealed class SdchannelIndicator : Indicator, IWatchlistIndicator
|
|||||||
protected override void OnUpdate(UpdateArgs args)
|
protected override void OnUpdate(UpdateArgs args)
|
||||||
{
|
{
|
||||||
if (_indicator is null)
|
if (_indicator is null)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var item = HistoricalData[0, SeekOriginHistory.End];
|
var item = HistoricalData[0, SeekOriginHistory.End];
|
||||||
bool isNew = args.IsNewBar();
|
bool isNew = args.IsNewBar();
|
||||||
|
|||||||
@@ -83,9 +83,14 @@ public sealed class Sdchannel : ITValuePublisher
|
|||||||
public Sdchannel(int period = 20, double multiplier = 2.0)
|
public Sdchannel(int period = 20, double multiplier = 2.0)
|
||||||
{
|
{
|
||||||
if (period <= 1)
|
if (period <= 1)
|
||||||
|
{
|
||||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than 1.");
|
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than 1.");
|
||||||
|
}
|
||||||
|
|
||||||
if (multiplier <= 0)
|
if (multiplier <= 0)
|
||||||
|
{
|
||||||
throw new ArgumentOutOfRangeException(nameof(multiplier), "Multiplier must be greater than 0.");
|
throw new ArgumentOutOfRangeException(nameof(multiplier), "Multiplier must be greater than 0.");
|
||||||
|
}
|
||||||
|
|
||||||
_period = period;
|
_period = period;
|
||||||
_multiplier = multiplier;
|
_multiplier = multiplier;
|
||||||
@@ -163,13 +168,17 @@ public sealed class Sdchannel : ITValuePublisher
|
|||||||
int head = _state.Head;
|
int head = _state.Head;
|
||||||
|
|
||||||
if (count < _period)
|
if (count < _period)
|
||||||
|
{
|
||||||
count++;
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
_buffer[head] = value;
|
_buffer[head] = value;
|
||||||
int newHead = (head + 1) % _period;
|
int newHead = (head + 1) % _period;
|
||||||
|
|
||||||
if (isNew)
|
if (isNew)
|
||||||
|
{
|
||||||
_state = _state with { Head = newHead, Count = count };
|
_state = _state with { Head = newHead, Count = count };
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate linear regression and std dev of residuals
|
// Calculate linear regression and std dev of residuals
|
||||||
if (count <= 1)
|
if (count <= 1)
|
||||||
@@ -241,7 +250,9 @@ public sealed class Sdchannel : ITValuePublisher
|
|||||||
double band = _multiplier * stdDev;
|
double band = _multiplier * stdDev;
|
||||||
|
|
||||||
if (!_state.IsHot && count >= WarmupPeriod)
|
if (!_state.IsHot && count >= WarmupPeriod)
|
||||||
|
{
|
||||||
_state = _state with { IsHot = true };
|
_state = _state with { IsHot = true };
|
||||||
|
}
|
||||||
|
|
||||||
_state = _state with { Slope = slope, StdDev = stdDev };
|
_state = _state with { Slope = slope, StdDev = stdDev };
|
||||||
|
|
||||||
@@ -256,7 +267,9 @@ public sealed class Sdchannel : ITValuePublisher
|
|||||||
public (TSeries Middle, TSeries Upper, TSeries Lower) Update(TSeries source)
|
public (TSeries Middle, TSeries Upper, TSeries Lower) Update(TSeries source)
|
||||||
{
|
{
|
||||||
if (source.Count == 0)
|
if (source.Count == 0)
|
||||||
|
{
|
||||||
return (new TSeries([], []), new TSeries([], []), new TSeries([], []));
|
return (new TSeries([], []), new TSeries([], []), new TSeries([], []));
|
||||||
|
}
|
||||||
|
|
||||||
int len = source.Count;
|
int len = source.Count;
|
||||||
var tMiddle = new List<long>(len);
|
var tMiddle = new List<long>(len);
|
||||||
@@ -300,7 +313,9 @@ public sealed class Sdchannel : ITValuePublisher
|
|||||||
Reset();
|
Reset();
|
||||||
|
|
||||||
if (source.Count == 0)
|
if (source.Count == 0)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < source.Count; i++)
|
for (int i = 0; i < source.Count; i++)
|
||||||
{
|
{
|
||||||
@@ -320,14 +335,25 @@ public sealed class Sdchannel : ITValuePublisher
|
|||||||
double multiplier = 2.0)
|
double multiplier = 2.0)
|
||||||
{
|
{
|
||||||
if (period <= 1)
|
if (period <= 1)
|
||||||
|
{
|
||||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than 1.");
|
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than 1.");
|
||||||
|
}
|
||||||
|
|
||||||
if (multiplier <= 0)
|
if (multiplier <= 0)
|
||||||
|
{
|
||||||
throw new ArgumentOutOfRangeException(nameof(multiplier), "Multiplier must be greater than 0.");
|
throw new ArgumentOutOfRangeException(nameof(multiplier), "Multiplier must be greater than 0.");
|
||||||
|
}
|
||||||
|
|
||||||
if (middle.Length < source.Length || upper.Length < source.Length || lower.Length < source.Length)
|
if (middle.Length < source.Length || upper.Length < source.Length || lower.Length < source.Length)
|
||||||
|
{
|
||||||
throw new ArgumentException("Output spans must be at least as long as input", nameof(middle));
|
throw new ArgumentException("Output spans must be at least as long as input", nameof(middle));
|
||||||
|
}
|
||||||
|
|
||||||
int len = source.Length;
|
int len = source.Length;
|
||||||
if (len == 0) return;
|
if (len == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Precompute constants for full period
|
// Precompute constants for full period
|
||||||
double sumXFull = 0.5 * period * (period - 1);
|
double sumXFull = 0.5 * period * (period - 1);
|
||||||
|
|||||||
@@ -48,7 +48,9 @@ public sealed class StarchannelIndicator : Indicator, IWatchlistIndicator
|
|||||||
protected override void OnUpdate(UpdateArgs args)
|
protected override void OnUpdate(UpdateArgs args)
|
||||||
{
|
{
|
||||||
if (_indicator is null)
|
if (_indicator is null)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var item = HistoricalData[0, SeekOriginHistory.End];
|
var item = HistoricalData[0, SeekOriginHistory.End];
|
||||||
bool isNew = args.IsNewBar();
|
bool isNew = args.IsNewBar();
|
||||||
|
|||||||
@@ -49,9 +49,14 @@ public sealed class Starchannel : ITValuePublisher
|
|||||||
public Starchannel(int period = 20, double multiplier = 2.0)
|
public Starchannel(int period = 20, double multiplier = 2.0)
|
||||||
{
|
{
|
||||||
if (period < 1)
|
if (period < 1)
|
||||||
|
{
|
||||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be >= 1.");
|
throw new ArgumentOutOfRangeException(nameof(period), "Period must be >= 1.");
|
||||||
|
}
|
||||||
|
|
||||||
if (multiplier <= 0.0)
|
if (multiplier <= 0.0)
|
||||||
|
{
|
||||||
throw new ArgumentOutOfRangeException(nameof(multiplier), "Multiplier must be > 0.");
|
throw new ArgumentOutOfRangeException(nameof(multiplier), "Multiplier must be > 0.");
|
||||||
|
}
|
||||||
|
|
||||||
_period = period;
|
_period = period;
|
||||||
_multiplier = multiplier;
|
_multiplier = multiplier;
|
||||||
@@ -93,19 +98,31 @@ public sealed class Starchannel : ITValuePublisher
|
|||||||
private (double close, double high, double low) GetValid(double close, double high, double low)
|
private (double close, double high, double low) GetValid(double close, double high, double low)
|
||||||
{
|
{
|
||||||
if (double.IsFinite(close))
|
if (double.IsFinite(close))
|
||||||
|
{
|
||||||
_state = _state with { LastValidClose = close };
|
_state = _state with { LastValidClose = close };
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
close = _state.LastValidClose;
|
close = _state.LastValidClose;
|
||||||
|
}
|
||||||
|
|
||||||
if (double.IsFinite(high))
|
if (double.IsFinite(high))
|
||||||
|
{
|
||||||
_state = _state with { LastValidHigh = high };
|
_state = _state with { LastValidHigh = high };
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
high = _state.LastValidHigh;
|
high = _state.LastValidHigh;
|
||||||
|
}
|
||||||
|
|
||||||
if (double.IsFinite(low))
|
if (double.IsFinite(low))
|
||||||
|
{
|
||||||
_state = _state with { LastValidLow = low };
|
_state = _state with { LastValidLow = low };
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
low = _state.LastValidLow;
|
low = _state.LastValidLow;
|
||||||
|
}
|
||||||
|
|
||||||
return (close, high, low);
|
return (close, high, low);
|
||||||
}
|
}
|
||||||
@@ -147,7 +164,9 @@ public sealed class Starchannel : ITValuePublisher
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isNew)
|
if (isNew)
|
||||||
|
{
|
||||||
_state = _state with { Bars = _state.Bars + 1 };
|
_state = _state with { Bars = _state.Bars + 1 };
|
||||||
|
}
|
||||||
|
|
||||||
// SMA: use RingBuffer's running sum
|
// SMA: use RingBuffer's running sum
|
||||||
_smaBuffer.Add(close);
|
_smaBuffer.Add(close);
|
||||||
@@ -179,7 +198,9 @@ public sealed class Starchannel : ITValuePublisher
|
|||||||
double lower = smaValue - width;
|
double lower = smaValue - width;
|
||||||
|
|
||||||
if (!_state.IsHot && _state.Bars >= WarmupPeriod)
|
if (!_state.IsHot && _state.Bars >= WarmupPeriod)
|
||||||
|
{
|
||||||
_state = _state with { IsHot = true };
|
_state = _state with { IsHot = true };
|
||||||
|
}
|
||||||
|
|
||||||
Last = new TValue(input.Time, smaValue);
|
Last = new TValue(input.Time, smaValue);
|
||||||
Upper = new TValue(input.Time, upper);
|
Upper = new TValue(input.Time, upper);
|
||||||
@@ -192,7 +213,9 @@ public sealed class Starchannel : ITValuePublisher
|
|||||||
public (TSeries Middle, TSeries Upper, TSeries Lower) Update(TBarSeries source)
|
public (TSeries Middle, TSeries Upper, TSeries Lower) Update(TBarSeries source)
|
||||||
{
|
{
|
||||||
if (source.Count == 0)
|
if (source.Count == 0)
|
||||||
|
{
|
||||||
return (new TSeries([], []), new TSeries([], []), new TSeries([], []));
|
return (new TSeries([], []), new TSeries([], []), new TSeries([], []));
|
||||||
|
}
|
||||||
|
|
||||||
int len = source.Count;
|
int len = source.Count;
|
||||||
var tMiddle = new List<long>(len);
|
var tMiddle = new List<long>(len);
|
||||||
@@ -237,7 +260,9 @@ public sealed class Starchannel : ITValuePublisher
|
|||||||
Reset();
|
Reset();
|
||||||
|
|
||||||
if (source.Count == 0)
|
if (source.Count == 0)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < source.Count; i++)
|
for (int i = 0; i < source.Count; i++)
|
||||||
{
|
{
|
||||||
@@ -259,16 +284,30 @@ public sealed class Starchannel : ITValuePublisher
|
|||||||
double multiplier = 2.0)
|
double multiplier = 2.0)
|
||||||
{
|
{
|
||||||
if (period < 1)
|
if (period < 1)
|
||||||
|
{
|
||||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be >= 1.");
|
throw new ArgumentOutOfRangeException(nameof(period), "Period must be >= 1.");
|
||||||
|
}
|
||||||
|
|
||||||
if (multiplier <= 0.0)
|
if (multiplier <= 0.0)
|
||||||
|
{
|
||||||
throw new ArgumentOutOfRangeException(nameof(multiplier), "Multiplier must be > 0.");
|
throw new ArgumentOutOfRangeException(nameof(multiplier), "Multiplier must be > 0.");
|
||||||
|
}
|
||||||
|
|
||||||
if (high.Length != low.Length || high.Length != close.Length)
|
if (high.Length != low.Length || high.Length != close.Length)
|
||||||
|
{
|
||||||
throw new ArgumentException("High, Low, and Close spans must have the same length", nameof(high));
|
throw new ArgumentException("High, Low, and Close spans must have the same length", nameof(high));
|
||||||
|
}
|
||||||
|
|
||||||
if (middle.Length < high.Length || upper.Length < high.Length || lower.Length < high.Length)
|
if (middle.Length < high.Length || upper.Length < high.Length || lower.Length < high.Length)
|
||||||
|
{
|
||||||
throw new ArgumentException("Output spans must be at least as long as inputs", nameof(middle));
|
throw new ArgumentException("Output spans must be at least as long as inputs", nameof(middle));
|
||||||
|
}
|
||||||
|
|
||||||
int len = high.Length;
|
int len = high.Length;
|
||||||
if (len == 0) return;
|
if (len == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
double atrAlpha = 1.0 / period;
|
double atrAlpha = 1.0 / period;
|
||||||
|
|
||||||
|
|||||||
@@ -203,4 +203,4 @@ public class StbandsIndicatorTests
|
|||||||
Assert.True(trend == 1 || trend == -1, $"Trend should be +1 or -1, got {trend}");
|
Assert.True(trend == 1 || trend == -1, $"Trend should be +1 or -1, got {trend}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,4 +70,4 @@ public class StbandsIndicator : Indicator, IWatchlistIndicator
|
|||||||
TrendSeries!.SetValue(stbands.Trend.Value, stbands.IsHot, ShowColdValues);
|
TrendSeries!.SetValue(stbands.Trend.Value, stbands.IsHot, ShowColdValues);
|
||||||
WidthSeries!.SetValue(stbands.Width.Value, stbands.IsHot, ShowColdValues);
|
WidthSeries!.SetValue(stbands.Width.Value, stbands.IsHot, ShowColdValues);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -420,4 +420,4 @@ public class StbandsTests
|
|||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(stbands.Upper.Value - stbands.Lower.Value, stbands.Width.Value, precision: 10);
|
Assert.Equal(stbands.Upper.Value - stbands.Lower.Value, stbands.Width.Value, precision: 10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -376,4 +376,4 @@ public sealed class StbandsValidationTests : IDisposable
|
|||||||
$"Higher multiplier should produce wider bands (mult={multipliers[i]})");
|
$"Higher multiplier should produce wider bands (mult={multipliers[i]})");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,11 +175,17 @@ public sealed class Stbands : AbstractBase
|
|||||||
|
|
||||||
// Determine trend
|
// Determine trend
|
||||||
if (close <= finalLower)
|
if (close <= finalLower)
|
||||||
|
{
|
||||||
trend = 1; // Bullish
|
trend = 1; // Bullish
|
||||||
|
}
|
||||||
else if (close >= finalUpper)
|
else if (close >= finalUpper)
|
||||||
|
{
|
||||||
trend = -1; // Bearish
|
trend = -1; // Bearish
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
trend = prevTrend;
|
trend = prevTrend;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update state
|
// Update state
|
||||||
@@ -374,9 +380,13 @@ public sealed class Stbands : AbstractBase
|
|||||||
|
|
||||||
// Determine trend
|
// Determine trend
|
||||||
if (c <= finalLower)
|
if (c <= finalLower)
|
||||||
|
{
|
||||||
currentTrend = 1;
|
currentTrend = 1;
|
||||||
|
}
|
||||||
else if (c >= finalUpper)
|
else if (c >= finalUpper)
|
||||||
|
{
|
||||||
currentTrend = -1;
|
currentTrend = -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
upper[i] = finalUpper;
|
upper[i] = finalUpper;
|
||||||
@@ -385,4 +395,4 @@ public sealed class Stbands : AbstractBase
|
|||||||
prevClose = c;
|
prevClose = c;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -216,4 +216,4 @@ public class UbandsIndicatorTests
|
|||||||
Assert.True(Math.Abs(width - (upper - lower)) < 0.0001,
|
Assert.True(Math.Abs(width - (upper - lower)) < 0.0001,
|
||||||
$"Width ({width}) should equal Upper - Lower ({upper - lower})");
|
$"Width ({width}) should equal Upper - Lower ({upper - lower})");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,4 +68,4 @@ public class UbandsIndicator : Indicator, IWatchlistIndicator
|
|||||||
LowerSeries!.SetValue(ubands.Lower.Value, ubands.IsHot, ShowColdValues);
|
LowerSeries!.SetValue(ubands.Lower.Value, ubands.IsHot, ShowColdValues);
|
||||||
WidthSeries!.SetValue(ubands.Width.Value, ubands.IsHot, ShowColdValues);
|
WidthSeries!.SetValue(ubands.Width.Value, ubands.IsHot, ShowColdValues);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -401,4 +401,4 @@ public class UbandsTests
|
|||||||
Assert.Equal(50, result.Count);
|
Assert.Equal(50, result.Count);
|
||||||
Assert.True(double.IsFinite(result.Last.Value));
|
Assert.True(double.IsFinite(result.Last.Value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -413,4 +413,4 @@ public sealed class UbandsValidationTests : IDisposable
|
|||||||
|
|
||||||
Assert.True(middleVar < sourceVar, "Smoothed signal should have lower variance");
|
Assert.True(middleVar < sourceVar, "Smoothed signal should have lower variance");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -385,4 +385,4 @@ public sealed class Ubands : AbstractBase
|
|||||||
lower[i] = usf - bandOffset;
|
lower[i] = usf - bandOffset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,4 +74,4 @@ public class UchannelIndicator : Indicator, IWatchlistIndicator
|
|||||||
StrSeries!.SetValue(uchannel.STR.Value, uchannel.IsHot, ShowColdValues);
|
StrSeries!.SetValue(uchannel.STR.Value, uchannel.IsHot, ShowColdValues);
|
||||||
WidthSeries!.SetValue(uchannel.Width.Value, uchannel.IsHot, ShowColdValues);
|
WidthSeries!.SetValue(uchannel.Width.Value, uchannel.IsHot, ShowColdValues);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -497,4 +497,4 @@ public class UchannelTests
|
|||||||
Assert.Equal(uchannel1.Upper.Value, uchannel2.Upper.Value, precision: 10);
|
Assert.Equal(uchannel1.Upper.Value, uchannel2.Upper.Value, precision: 10);
|
||||||
Assert.Equal(uchannel1.Lower.Value, uchannel2.Lower.Value, precision: 10);
|
Assert.Equal(uchannel1.Lower.Value, uchannel2.Lower.Value, precision: 10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -436,4 +436,4 @@ public class UchannelValidationTests
|
|||||||
// Should produce same results
|
// Should produce same results
|
||||||
Assert.Equal(valueBefore, valueAfter, precision: 10);
|
Assert.Equal(valueBefore, valueAfter, precision: 10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,6 +87,9 @@ public sealed class Uchannel : AbstractBase
|
|||||||
/// <summary>Gets the channel width (Upper - Lower).</summary>
|
/// <summary>Gets the channel width (Upper - Lower).</summary>
|
||||||
public TValue Width => new(Upper.Time, Upper.Value - Lower.Value);
|
public TValue Width => new(Upper.Time, Upper.Value - Lower.Value);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
/// <param name="strPeriod">Period for smoothing True Range. Must be >= 1.</param>
|
/// <param name="strPeriod">Period for smoothing True Range. Must be >= 1.</param>
|
||||||
/// <param name="centerPeriod">Period for smoothing centerline. Must be >= 1.</param>
|
/// <param name="centerPeriod">Period for smoothing centerline. Must be >= 1.</param>
|
||||||
/// <param name="multiplier">Band multiplier for STR. Must be > 0.</param>
|
/// <param name="multiplier">Band multiplier for STR. Must be > 0.</param>
|
||||||
@@ -403,11 +406,19 @@ public sealed class Uchannel : AbstractBase
|
|||||||
|
|
||||||
int length = close.Length;
|
int length = close.Length;
|
||||||
if (high.Length != length || low.Length != length)
|
if (high.Length != length || low.Length != length)
|
||||||
|
{
|
||||||
throw new ArgumentException("All input arrays must have the same length", nameof(high));
|
throw new ArgumentException("All input arrays must have the same length", nameof(high));
|
||||||
if (upper.Length != length || middle.Length != length || lower.Length != length)
|
}
|
||||||
throw new ArgumentException("Output arrays must match input length", nameof(upper));
|
|
||||||
|
|
||||||
if (length == 0) return;
|
if (upper.Length != length || middle.Length != length || lower.Length != length)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Output arrays must match input length", nameof(upper));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (length == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Compute USF coefficients
|
// Compute USF coefficients
|
||||||
double arg_str = Math.Sqrt(2) * Math.PI / strPeriod;
|
double arg_str = Math.Sqrt(2) * Math.PI / strPeriod;
|
||||||
@@ -503,4 +514,4 @@ public sealed class Uchannel : AbstractBase
|
|||||||
prevClose = c;
|
prevClose = c;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -211,7 +211,7 @@ public class VwapbandsIndicatorTests
|
|||||||
|
|
||||||
// Same prices but different volume distributions
|
// Same prices but different volume distributions
|
||||||
// Process both bars for each indicator
|
// Process both bars for each indicator
|
||||||
|
|
||||||
// Indicator1: high volume on low price, low volume on high price
|
// Indicator1: high volume on low price, low volume on high price
|
||||||
indicator1.HistoricalData.AddBar(now, 100, 102, 98, 100, 10000);
|
indicator1.HistoricalData.AddBar(now, 100, 102, 98, 100, 10000);
|
||||||
indicator1.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
indicator1.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
||||||
@@ -231,4 +231,4 @@ public class VwapbandsIndicatorTests
|
|||||||
// VWAP2 should be higher (weighted toward 110 due to high volume at high price)
|
// VWAP2 should be higher (weighted toward 110 due to high volume at high price)
|
||||||
Assert.True(vwap1 < vwap2, $"VWAP1 ({vwap1}) should be less than VWAP2 ({vwap2}) due to volume weighting");
|
Assert.True(vwap1 < vwap2, $"VWAP1 ({vwap1}) should be less than VWAP2 ({vwap2}) due to volume weighting");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,4 +76,4 @@ public class VwapbandsIndicator : Indicator, IWatchlistIndicator
|
|||||||
Lower2Series!.SetValue(vwapbands.Lower2.Value, vwapbands.IsHot, ShowColdValues);
|
Lower2Series!.SetValue(vwapbands.Lower2.Value, vwapbands.IsHot, ShowColdValues);
|
||||||
WidthSeries!.SetValue(vwapbands.Width.Value, vwapbands.IsHot, ShowColdValues);
|
WidthSeries!.SetValue(vwapbands.Width.Value, vwapbands.IsHot, ShowColdValues);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -602,4 +602,4 @@ public class VwapbandsTests
|
|||||||
Assert.Equal(expectedVwap, vwapbands.Vwap.Value, precision: 10);
|
Assert.Equal(expectedVwap, vwapbands.Vwap.Value, precision: 10);
|
||||||
Assert.True(vwapbands.Vwap.Value < 110, "VWAP should be heavily weighted toward 100");
|
Assert.True(vwapbands.Vwap.Value < 110, "VWAP should be heavily weighted toward 100");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -518,4 +518,4 @@ public sealed class VwapbandsValidationTests : IDisposable
|
|||||||
|
|
||||||
_output.WriteLine("VWAPBANDS static Calculate validated successfully");
|
_output.WriteLine("VWAPBANDS static Calculate validated successfully");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -414,4 +414,4 @@ public sealed class Vwapbands : AbstractBase
|
|||||||
lower2[i] = vwapVal - 2.0 * multiplier * stdev;
|
lower2[i] = vwapVal - 2.0 * multiplier * stdev;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -258,4 +258,4 @@ public class VwapsdIndicatorTests
|
|||||||
Assert.True(Math.Abs(width2 - 2 * width1) < 0.0001,
|
Assert.True(Math.Abs(width2 - 2 * width1) < 0.0001,
|
||||||
$"Width2 ({width2}) should be ~2x Width1 ({width1})");
|
$"Width2 ({width2}) should be ~2x Width1 ({width1})");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,4 +68,4 @@ public class VwapsdIndicator : Indicator, IWatchlistIndicator
|
|||||||
LowerSeries!.SetValue(vwapsd.Lower.Value, vwapsd.IsHot, ShowColdValues);
|
LowerSeries!.SetValue(vwapsd.Lower.Value, vwapsd.IsHot, ShowColdValues);
|
||||||
WidthSeries!.SetValue(vwapsd.Width.Value, vwapsd.IsHot, ShowColdValues);
|
WidthSeries!.SetValue(vwapsd.Width.Value, vwapsd.IsHot, ShowColdValues);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -694,4 +694,4 @@ public class VwapsdTests
|
|||||||
Assert.Equal(400.0, vwapsd.Upper.Value, precision: 10);
|
Assert.Equal(400.0, vwapsd.Upper.Value, precision: 10);
|
||||||
Assert.Equal(-100.0, vwapsd.Lower.Value, precision: 10);
|
Assert.Equal(-100.0, vwapsd.Lower.Value, precision: 10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -610,4 +610,4 @@ public sealed class VwapsdValidationTests : IDisposable
|
|||||||
|
|
||||||
_output.WriteLine("VWAPSD boundary numDevs validation completed");
|
_output.WriteLine("VWAPSD boundary numDevs validation completed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -400,4 +400,4 @@ public sealed class Vwapsd : AbstractBase
|
|||||||
lower[i] = vwapVal - numDevs * stdev;
|
lower[i] = vwapVal - numDevs * stdev;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,9 @@ public abstract class BiInputIndicatorBase : AbstractBase
|
|||||||
protected BiInputIndicatorBase(int period, string name)
|
protected BiInputIndicatorBase(int period, string name)
|
||||||
{
|
{
|
||||||
if (period <= 0)
|
if (period <= 0)
|
||||||
|
{
|
||||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||||
|
}
|
||||||
|
|
||||||
_buffer = new RingBuffer(period);
|
_buffer = new RingBuffer(period);
|
||||||
Name = name;
|
Name = name;
|
||||||
@@ -176,9 +178,13 @@ public abstract class BiInputIndicatorBase : AbstractBase
|
|||||||
double error = ComputeError(actualVal, predictedVal);
|
double error = ComputeError(actualVal, predictedVal);
|
||||||
|
|
||||||
if (isNew)
|
if (isNew)
|
||||||
|
{
|
||||||
ProcessNewBar(error);
|
ProcessNewBar(error);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
ProcessBarCorrection(error);
|
ProcessBarCorrection(error);
|
||||||
|
}
|
||||||
|
|
||||||
double mean = _buffer.Count > 0 ? _state.Sum / _buffer.Count : error;
|
double mean = _buffer.Count > 0 ? _state.Sum / _buffer.Count : error;
|
||||||
double result = PostProcess(mean);
|
double result = PostProcess(mean);
|
||||||
@@ -248,7 +254,9 @@ public abstract class BiInputIndicatorBase : AbstractBase
|
|||||||
BiInputBatchDelegate batchMethod)
|
BiInputBatchDelegate batchMethod)
|
||||||
{
|
{
|
||||||
if (actual.Count != predicted.Count)
|
if (actual.Count != predicted.Count)
|
||||||
|
{
|
||||||
throw new ArgumentException("Actual and predicted series must have the same length", nameof(predicted));
|
throw new ArgumentException("Actual and predicted series must have the same length", nameof(predicted));
|
||||||
|
}
|
||||||
|
|
||||||
int len = actual.Count;
|
int len = actual.Count;
|
||||||
var t = new List<long>(len);
|
var t = new List<long>(len);
|
||||||
@@ -276,8 +284,13 @@ public abstract class BiInputIndicatorBase : AbstractBase
|
|||||||
int period)
|
int period)
|
||||||
{
|
{
|
||||||
if (actual.Length != predicted.Length || actual.Length != output.Length)
|
if (actual.Length != predicted.Length || actual.Length != output.Length)
|
||||||
|
{
|
||||||
throw new ArgumentException("All spans must have the same length", nameof(output));
|
throw new ArgumentException("All spans must have the same length", nameof(output));
|
||||||
|
}
|
||||||
|
|
||||||
if (period <= 0)
|
if (period <= 0)
|
||||||
|
{
|
||||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,9 @@ public sealed class MonotonicDeque
|
|||||||
public MonotonicDeque(int period)
|
public MonotonicDeque(int period)
|
||||||
{
|
{
|
||||||
if (period <= 0)
|
if (period <= 0)
|
||||||
|
{
|
||||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than 0");
|
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than 0");
|
||||||
|
}
|
||||||
|
|
||||||
_period = period;
|
_period = period;
|
||||||
_deque = new int[period];
|
_deque = new int[period];
|
||||||
@@ -155,7 +157,10 @@ public sealed class MonotonicDeque
|
|||||||
public void RebuildMax(double[] buffer, long currentIndex, int count)
|
public void RebuildMax(double[] buffer, long currentIndex, int count)
|
||||||
{
|
{
|
||||||
Reset();
|
Reset();
|
||||||
if (count == 0) return;
|
if (count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
long startLogical = currentIndex - count + 1;
|
long startLogical = currentIndex - count + 1;
|
||||||
for (int i = 0; i < count; i++)
|
for (int i = 0; i < count; i++)
|
||||||
@@ -176,7 +181,10 @@ public sealed class MonotonicDeque
|
|||||||
public void RebuildMin(double[] buffer, long currentIndex, int count)
|
public void RebuildMin(double[] buffer, long currentIndex, int count)
|
||||||
{
|
{
|
||||||
Reset();
|
Reset();
|
||||||
if (count == 0) return;
|
if (count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
long startLogical = currentIndex - count + 1;
|
long startLogical = currentIndex - count + 1;
|
||||||
for (int i = 0; i < count; i++)
|
for (int i = 0; i < count; i++)
|
||||||
@@ -186,4 +194,4 @@ public sealed class MonotonicDeque
|
|||||||
PushMin(logicalIndex, buffer[bufIdx], buffer);
|
PushMin(logicalIndex, buffer[bufIdx], buffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,9 @@ public sealed class RingBuffer : IEnumerable<double>
|
|||||||
public RingBuffer(int capacity)
|
public RingBuffer(int capacity)
|
||||||
{
|
{
|
||||||
if (capacity <= 0)
|
if (capacity <= 0)
|
||||||
|
{
|
||||||
throw new ArgumentException("Capacity must be greater than 0", nameof(capacity));
|
throw new ArgumentException("Capacity must be greater than 0", nameof(capacity));
|
||||||
|
}
|
||||||
|
|
||||||
Capacity = capacity;
|
Capacity = capacity;
|
||||||
_buffer = GC.AllocateArray<double>(capacity, pinned: true);
|
_buffer = GC.AllocateArray<double>(capacity, pinned: true);
|
||||||
@@ -130,7 +132,11 @@ public sealed class RingBuffer : IEnumerable<double>
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (_count == 0) return double.NaN;
|
if (_count == 0)
|
||||||
|
{
|
||||||
|
return double.NaN;
|
||||||
|
}
|
||||||
|
|
||||||
int idx = (_head - 1 + Capacity) % Capacity;
|
int idx = (_head - 1 + Capacity) % Capacity;
|
||||||
return _buffer[idx];
|
return _buffer[idx];
|
||||||
}
|
}
|
||||||
@@ -145,7 +151,11 @@ public sealed class RingBuffer : IEnumerable<double>
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (_count == 0) return double.NaN;
|
if (_count == 0)
|
||||||
|
{
|
||||||
|
return double.NaN;
|
||||||
|
}
|
||||||
|
|
||||||
int start = _count == Capacity ? _head : 0;
|
int start = _count == Capacity ? _head : 0;
|
||||||
return _buffer[start];
|
return _buffer[start];
|
||||||
}
|
}
|
||||||
@@ -224,7 +234,10 @@ public sealed class RingBuffer : IEnumerable<double>
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void UpdateNewest(double value)
|
public void UpdateNewest(double value)
|
||||||
{
|
{
|
||||||
if (_count == 0) return;
|
if (_count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int idx = (_head - 1 + Capacity) % Capacity;
|
int idx = (_head - 1 + Capacity) % Capacity;
|
||||||
double oldValue = _buffer[idx];
|
double oldValue = _buffer[idx];
|
||||||
@@ -283,7 +296,10 @@ public sealed class RingBuffer : IEnumerable<double>
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public ReadOnlySpan<double> GetSpan()
|
public ReadOnlySpan<double> GetSpan()
|
||||||
{
|
{
|
||||||
if (_count == 0) return ReadOnlySpan<double>.Empty;
|
if (_count == 0)
|
||||||
|
{
|
||||||
|
return ReadOnlySpan<double>.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
int start = _count == Capacity ? _head : 0;
|
int start = _count == Capacity ? _head : 0;
|
||||||
|
|
||||||
@@ -332,7 +348,11 @@ public sealed class RingBuffer : IEnumerable<double>
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public double Max()
|
public double Max()
|
||||||
{
|
{
|
||||||
if (_count == 0) return double.NaN;
|
if (_count == 0)
|
||||||
|
{
|
||||||
|
return double.NaN;
|
||||||
|
}
|
||||||
|
|
||||||
return MaxSimd();
|
return MaxSimd();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -342,7 +362,11 @@ public sealed class RingBuffer : IEnumerable<double>
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public double Min()
|
public double Min()
|
||||||
{
|
{
|
||||||
if (_count == 0) return double.NaN;
|
if (_count == 0)
|
||||||
|
{
|
||||||
|
return double.NaN;
|
||||||
|
}
|
||||||
|
|
||||||
return MinSimd();
|
return MinSimd();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -461,7 +485,10 @@ public sealed class RingBuffer : IEnumerable<double>
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public double[] ToArray()
|
public double[] ToArray()
|
||||||
{
|
{
|
||||||
if (_count == 0) return Array.Empty<double>();
|
if (_count == 0)
|
||||||
|
{
|
||||||
|
return Array.Empty<double>();
|
||||||
|
}
|
||||||
|
|
||||||
double[] array = new double[_count];
|
double[] array = new double[_count];
|
||||||
CopyTo(array, 0);
|
CopyTo(array, 0);
|
||||||
@@ -474,7 +501,10 @@ public sealed class RingBuffer : IEnumerable<double>
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void CopyTo(double[] destination, int destinationIndex)
|
public void CopyTo(double[] destination, int destinationIndex)
|
||||||
{
|
{
|
||||||
if (_count == 0) return;
|
if (_count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int start = _count == Capacity ? _head : 0;
|
int start = _count == Capacity ? _head : 0;
|
||||||
|
|
||||||
@@ -497,7 +527,10 @@ public sealed class RingBuffer : IEnumerable<double>
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void CopyTo(Span<double> destination)
|
public void CopyTo(Span<double> destination)
|
||||||
{
|
{
|
||||||
if (_count == 0) return;
|
if (_count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int start = _count == Capacity ? _head : 0;
|
int start = _count == Capacity ? _head : 0;
|
||||||
|
|
||||||
@@ -533,7 +566,9 @@ public sealed class RingBuffer : IEnumerable<double>
|
|||||||
public void CopyFrom(RingBuffer source)
|
public void CopyFrom(RingBuffer source)
|
||||||
{
|
{
|
||||||
if (source.Capacity != Capacity)
|
if (source.Capacity != Capacity)
|
||||||
|
{
|
||||||
throw new ArgumentException("Source buffer must have same capacity", nameof(source));
|
throw new ArgumentException("Source buffer must have same capacity", nameof(source));
|
||||||
|
}
|
||||||
|
|
||||||
Array.Copy(source._buffer, _buffer, Capacity);
|
Array.Copy(source._buffer, _buffer, Capacity);
|
||||||
_head = source._head;
|
_head = source._head;
|
||||||
@@ -632,7 +667,9 @@ public sealed class RingBuffer : IEnumerable<double>
|
|||||||
public bool MoveNext()
|
public bool MoveNext()
|
||||||
{
|
{
|
||||||
if (_index + 1 >= _count)
|
if (_index + 1 >= _count)
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
_index++;
|
_index++;
|
||||||
int bufferIdx = (_start + _index) % _buffer.Capacity;
|
int bufferIdx = (_start + _index) % _buffer.Capacity;
|
||||||
@@ -668,4 +705,4 @@ public sealed class RingBuffer : IEnumerable<double>
|
|||||||
public static bool operator ==(Enumerator left, Enumerator right) => left.Equals(right);
|
public static bool operator ==(Enumerator left, Enumerator right) => left.Equals(right);
|
||||||
public static bool operator !=(Enumerator left, Enumerator right) => !left.Equals(right);
|
public static bool operator !=(Enumerator left, Enumerator right) => !left.Equals(right);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+281
-27
@@ -39,11 +39,15 @@ public static class ErrorHelpers
|
|||||||
Span<double> output)
|
Span<double> output)
|
||||||
{
|
{
|
||||||
if (actual.Length != predicted.Length || actual.Length != output.Length)
|
if (actual.Length != predicted.Length || actual.Length != output.Length)
|
||||||
|
{
|
||||||
throw new ArgumentException(SpanLengthMismatchMessage, nameof(output));
|
throw new ArgumentException(SpanLengthMismatchMessage, nameof(output));
|
||||||
|
}
|
||||||
|
|
||||||
int len = actual.Length;
|
int len = actual.Length;
|
||||||
if (len == 0)
|
if (len == 0)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
double lastValidActual = FindFirstValidValue(actual);
|
double lastValidActual = FindFirstValidValue(actual);
|
||||||
double lastValidPredicted = FindFirstValidValue(predicted);
|
double lastValidPredicted = FindFirstValidValue(predicted);
|
||||||
@@ -53,7 +57,9 @@ public static class ErrorHelpers
|
|||||||
{
|
{
|
||||||
int processedCount = ComputeSignedErrorsSimdWithNaNDetection(actual, predicted, output, ref lastValidActual, ref lastValidPredicted);
|
int processedCount = ComputeSignedErrorsSimdWithNaNDetection(actual, predicted, output, ref lastValidActual, ref lastValidPredicted);
|
||||||
if (processedCount == len)
|
if (processedCount == len)
|
||||||
|
{
|
||||||
return; // All processed via SIMD
|
return; // All processed via SIMD
|
||||||
|
}
|
||||||
// Continue with scalar for remaining elements (NaN was detected)
|
// Continue with scalar for remaining elements (NaN was detected)
|
||||||
ComputeSignedErrorsScalar(actual.Slice(processedCount), predicted.Slice(processedCount), output.Slice(processedCount), lastValidActual, lastValidPredicted);
|
ComputeSignedErrorsScalar(actual.Slice(processedCount), predicted.Slice(processedCount), output.Slice(processedCount), lastValidActual, lastValidPredicted);
|
||||||
return;
|
return;
|
||||||
@@ -74,11 +80,15 @@ public static class ErrorHelpers
|
|||||||
Span<double> output)
|
Span<double> output)
|
||||||
{
|
{
|
||||||
if (actual.Length != predicted.Length || actual.Length != output.Length)
|
if (actual.Length != predicted.Length || actual.Length != output.Length)
|
||||||
|
{
|
||||||
throw new ArgumentException(SpanLengthMismatchMessage, nameof(output));
|
throw new ArgumentException(SpanLengthMismatchMessage, nameof(output));
|
||||||
|
}
|
||||||
|
|
||||||
int len = actual.Length;
|
int len = actual.Length;
|
||||||
if (len == 0)
|
if (len == 0)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
double lastValidActual = FindFirstValidValue(actual);
|
double lastValidActual = FindFirstValidValue(actual);
|
||||||
double lastValidPredicted = FindFirstValidValue(predicted);
|
double lastValidPredicted = FindFirstValidValue(predicted);
|
||||||
@@ -88,7 +98,9 @@ public static class ErrorHelpers
|
|||||||
{
|
{
|
||||||
int processedCount = ComputeAbsoluteErrorsSimdWithNaNDetection(actual, predicted, output, ref lastValidActual, ref lastValidPredicted);
|
int processedCount = ComputeAbsoluteErrorsSimdWithNaNDetection(actual, predicted, output, ref lastValidActual, ref lastValidPredicted);
|
||||||
if (processedCount == len)
|
if (processedCount == len)
|
||||||
|
{
|
||||||
return; // All processed via SIMD
|
return; // All processed via SIMD
|
||||||
|
}
|
||||||
// Continue with scalar for remaining elements (NaN was detected)
|
// Continue with scalar for remaining elements (NaN was detected)
|
||||||
ComputeAbsoluteErrorsScalar(actual.Slice(processedCount), predicted.Slice(processedCount), output.Slice(processedCount), lastValidActual, lastValidPredicted);
|
ComputeAbsoluteErrorsScalar(actual.Slice(processedCount), predicted.Slice(processedCount), output.Slice(processedCount), lastValidActual, lastValidPredicted);
|
||||||
return;
|
return;
|
||||||
@@ -109,11 +121,15 @@ public static class ErrorHelpers
|
|||||||
Span<double> output)
|
Span<double> output)
|
||||||
{
|
{
|
||||||
if (actual.Length != predicted.Length || actual.Length != output.Length)
|
if (actual.Length != predicted.Length || actual.Length != output.Length)
|
||||||
|
{
|
||||||
throw new ArgumentException(SpanLengthMismatchMessage, nameof(output));
|
throw new ArgumentException(SpanLengthMismatchMessage, nameof(output));
|
||||||
|
}
|
||||||
|
|
||||||
int len = actual.Length;
|
int len = actual.Length;
|
||||||
if (len == 0)
|
if (len == 0)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
double lastValidActual = FindFirstValidValue(actual);
|
double lastValidActual = FindFirstValidValue(actual);
|
||||||
double lastValidPredicted = FindFirstValidValue(predicted);
|
double lastValidPredicted = FindFirstValidValue(predicted);
|
||||||
@@ -123,7 +139,9 @@ public static class ErrorHelpers
|
|||||||
{
|
{
|
||||||
int processedCount = ComputeSquaredErrorsSimdWithNaNDetection(actual, predicted, output, ref lastValidActual, ref lastValidPredicted);
|
int processedCount = ComputeSquaredErrorsSimdWithNaNDetection(actual, predicted, output, ref lastValidActual, ref lastValidPredicted);
|
||||||
if (processedCount == len)
|
if (processedCount == len)
|
||||||
|
{
|
||||||
return; // All processed via SIMD
|
return; // All processed via SIMD
|
||||||
|
}
|
||||||
// Continue with scalar for remaining elements (NaN was detected)
|
// Continue with scalar for remaining elements (NaN was detected)
|
||||||
ComputeSquaredErrorsScalar(actual.Slice(processedCount), predicted.Slice(processedCount), output.Slice(processedCount), lastValidActual, lastValidPredicted);
|
ComputeSquaredErrorsScalar(actual.Slice(processedCount), predicted.Slice(processedCount), output.Slice(processedCount), lastValidActual, lastValidPredicted);
|
||||||
return;
|
return;
|
||||||
@@ -145,11 +163,15 @@ public static class ErrorHelpers
|
|||||||
Span<double> output)
|
Span<double> output)
|
||||||
{
|
{
|
||||||
if (actual.Length != predicted.Length || actual.Length != weights.Length || actual.Length != output.Length)
|
if (actual.Length != predicted.Length || actual.Length != weights.Length || actual.Length != output.Length)
|
||||||
|
{
|
||||||
throw new ArgumentException("All spans must have the same length", nameof(output));
|
throw new ArgumentException("All spans must have the same length", nameof(output));
|
||||||
|
}
|
||||||
|
|
||||||
int len = actual.Length;
|
int len = actual.Length;
|
||||||
if (len == 0)
|
if (len == 0)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
double lastValidActual = FindFirstValidValue(actual);
|
double lastValidActual = FindFirstValidValue(actual);
|
||||||
double lastValidPredicted = FindFirstValidValue(predicted);
|
double lastValidPredicted = FindFirstValidValue(predicted);
|
||||||
@@ -165,9 +187,32 @@ public static class ErrorHelpers
|
|||||||
double pred = predicted[i];
|
double pred = predicted[i];
|
||||||
double wgt = weights[i];
|
double wgt = weights[i];
|
||||||
|
|
||||||
if (double.IsFinite(act)) currentValidActual = act; else act = currentValidActual;
|
if (double.IsFinite(act))
|
||||||
if (double.IsFinite(pred)) currentValidPredicted = pred; else pred = currentValidPredicted;
|
{
|
||||||
if (double.IsFinite(wgt)) currentValidWeight = wgt; else wgt = currentValidWeight;
|
currentValidActual = act;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
act = currentValidActual;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (double.IsFinite(pred))
|
||||||
|
{
|
||||||
|
currentValidPredicted = pred;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pred = currentValidPredicted;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (double.IsFinite(wgt))
|
||||||
|
{
|
||||||
|
currentValidWeight = wgt;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
wgt = currentValidWeight;
|
||||||
|
}
|
||||||
|
|
||||||
double diff = act - pred;
|
double diff = act - pred;
|
||||||
output[i] = wgt * diff * diff;
|
output[i] = wgt * diff * diff;
|
||||||
@@ -186,11 +231,15 @@ public static class ErrorHelpers
|
|||||||
double epsilon = 1e-10)
|
double epsilon = 1e-10)
|
||||||
{
|
{
|
||||||
if (actual.Length != predicted.Length || actual.Length != output.Length)
|
if (actual.Length != predicted.Length || actual.Length != output.Length)
|
||||||
|
{
|
||||||
throw new ArgumentException(SpanLengthMismatchMessage, nameof(output));
|
throw new ArgumentException(SpanLengthMismatchMessage, nameof(output));
|
||||||
|
}
|
||||||
|
|
||||||
int len = actual.Length;
|
int len = actual.Length;
|
||||||
if (len == 0)
|
if (len == 0)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
double lastValidActual = FindFirstValidValue(actual);
|
double lastValidActual = FindFirstValidValue(actual);
|
||||||
double lastValidPredicted = FindFirstValidValue(predicted);
|
double lastValidPredicted = FindFirstValidValue(predicted);
|
||||||
@@ -227,11 +276,15 @@ public static class ErrorHelpers
|
|||||||
double epsilon = 1e-10)
|
double epsilon = 1e-10)
|
||||||
{
|
{
|
||||||
if (actual.Length != predicted.Length || actual.Length != output.Length)
|
if (actual.Length != predicted.Length || actual.Length != output.Length)
|
||||||
|
{
|
||||||
throw new ArgumentException(SpanLengthMismatchMessage, nameof(output));
|
throw new ArgumentException(SpanLengthMismatchMessage, nameof(output));
|
||||||
|
}
|
||||||
|
|
||||||
int len = actual.Length;
|
int len = actual.Length;
|
||||||
if (len == 0)
|
if (len == 0)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
double lastValidActual = FindFirstValidValue(actual);
|
double lastValidActual = FindFirstValidValue(actual);
|
||||||
double lastValidPredicted = FindFirstValidValue(predicted);
|
double lastValidPredicted = FindFirstValidValue(predicted);
|
||||||
@@ -244,8 +297,23 @@ public static class ErrorHelpers
|
|||||||
double act = actual[i];
|
double act = actual[i];
|
||||||
double pred = predicted[i];
|
double pred = predicted[i];
|
||||||
|
|
||||||
if (double.IsFinite(act)) currentValidActual = act; else act = currentValidActual;
|
if (double.IsFinite(act))
|
||||||
if (double.IsFinite(pred)) currentValidPredicted = pred; else pred = currentValidPredicted;
|
{
|
||||||
|
currentValidActual = act;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
act = currentValidActual;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (double.IsFinite(pred))
|
||||||
|
{
|
||||||
|
currentValidPredicted = pred;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pred = currentValidPredicted;
|
||||||
|
}
|
||||||
|
|
||||||
double denominator = (Math.Abs(act) + Math.Abs(pred)) / 2.0;
|
double denominator = (Math.Abs(act) + Math.Abs(pred)) / 2.0;
|
||||||
output[i] = denominator < epsilon
|
output[i] = denominator < epsilon
|
||||||
@@ -265,11 +333,15 @@ public static class ErrorHelpers
|
|||||||
Span<double> output)
|
Span<double> output)
|
||||||
{
|
{
|
||||||
if (actual.Length != predicted.Length || actual.Length != output.Length)
|
if (actual.Length != predicted.Length || actual.Length != output.Length)
|
||||||
|
{
|
||||||
throw new ArgumentException(SpanLengthMismatchMessage, nameof(output));
|
throw new ArgumentException(SpanLengthMismatchMessage, nameof(output));
|
||||||
|
}
|
||||||
|
|
||||||
int len = actual.Length;
|
int len = actual.Length;
|
||||||
if (len == 0)
|
if (len == 0)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
double lastValidActual = FindFirstValidValue(actual);
|
double lastValidActual = FindFirstValidValue(actual);
|
||||||
double lastValidPredicted = FindFirstValidValue(predicted);
|
double lastValidPredicted = FindFirstValidValue(predicted);
|
||||||
@@ -282,8 +354,23 @@ public static class ErrorHelpers
|
|||||||
double act = actual[i];
|
double act = actual[i];
|
||||||
double pred = predicted[i];
|
double pred = predicted[i];
|
||||||
|
|
||||||
if (double.IsFinite(act)) currentValidActual = act; else act = currentValidActual;
|
if (double.IsFinite(act))
|
||||||
if (double.IsFinite(pred)) currentValidPredicted = pred; else pred = currentValidPredicted;
|
{
|
||||||
|
currentValidActual = act;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
act = currentValidActual;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (double.IsFinite(pred))
|
||||||
|
{
|
||||||
|
currentValidPredicted = pred;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pred = currentValidPredicted;
|
||||||
|
}
|
||||||
|
|
||||||
double diff = act - pred;
|
double diff = act - pred;
|
||||||
// log(cosh(x)) ≈ |x| - log(2) for large |x|, numerically stable
|
// log(cosh(x)) ≈ |x| - log(2) for large |x|, numerically stable
|
||||||
@@ -303,11 +390,15 @@ public static class ErrorHelpers
|
|||||||
double delta = 1.0)
|
double delta = 1.0)
|
||||||
{
|
{
|
||||||
if (actual.Length != predicted.Length || actual.Length != output.Length)
|
if (actual.Length != predicted.Length || actual.Length != output.Length)
|
||||||
|
{
|
||||||
throw new ArgumentException(SpanLengthMismatchMessage, nameof(output));
|
throw new ArgumentException(SpanLengthMismatchMessage, nameof(output));
|
||||||
|
}
|
||||||
|
|
||||||
int len = actual.Length;
|
int len = actual.Length;
|
||||||
if (len == 0)
|
if (len == 0)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
double lastValidActual = FindFirstValidValue(actual);
|
double lastValidActual = FindFirstValidValue(actual);
|
||||||
double lastValidPredicted = FindFirstValidValue(predicted);
|
double lastValidPredicted = FindFirstValidValue(predicted);
|
||||||
@@ -321,8 +412,23 @@ public static class ErrorHelpers
|
|||||||
double act = actual[i];
|
double act = actual[i];
|
||||||
double pred = predicted[i];
|
double pred = predicted[i];
|
||||||
|
|
||||||
if (double.IsFinite(act)) currentValidActual = act; else act = currentValidActual;
|
if (double.IsFinite(act))
|
||||||
if (double.IsFinite(pred)) currentValidPredicted = pred; else pred = currentValidPredicted;
|
{
|
||||||
|
currentValidActual = act;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
act = currentValidActual;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (double.IsFinite(pred))
|
||||||
|
{
|
||||||
|
currentValidPredicted = pred;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pred = currentValidPredicted;
|
||||||
|
}
|
||||||
|
|
||||||
double diff = act - pred;
|
double diff = act - pred;
|
||||||
double ratio = diff / delta;
|
double ratio = diff / delta;
|
||||||
@@ -345,11 +451,15 @@ public static class ErrorHelpers
|
|||||||
double c = 4.685)
|
double c = 4.685)
|
||||||
{
|
{
|
||||||
if (actual.Length != predicted.Length || actual.Length != output.Length)
|
if (actual.Length != predicted.Length || actual.Length != output.Length)
|
||||||
|
{
|
||||||
throw new ArgumentException(SpanLengthMismatchMessage, nameof(output));
|
throw new ArgumentException(SpanLengthMismatchMessage, nameof(output));
|
||||||
|
}
|
||||||
|
|
||||||
int len = actual.Length;
|
int len = actual.Length;
|
||||||
if (len == 0)
|
if (len == 0)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
double lastValidActual = FindFirstValidValue(actual);
|
double lastValidActual = FindFirstValidValue(actual);
|
||||||
double lastValidPredicted = FindFirstValidValue(predicted);
|
double lastValidPredicted = FindFirstValidValue(predicted);
|
||||||
@@ -363,8 +473,23 @@ public static class ErrorHelpers
|
|||||||
double act = actual[i];
|
double act = actual[i];
|
||||||
double pred = predicted[i];
|
double pred = predicted[i];
|
||||||
|
|
||||||
if (double.IsFinite(act)) currentValidActual = act; else act = currentValidActual;
|
if (double.IsFinite(act))
|
||||||
if (double.IsFinite(pred)) currentValidPredicted = pred; else pred = currentValidPredicted;
|
{
|
||||||
|
currentValidActual = act;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
act = currentValidActual;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (double.IsFinite(pred))
|
||||||
|
{
|
||||||
|
currentValidPredicted = pred;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pred = currentValidPredicted;
|
||||||
|
}
|
||||||
|
|
||||||
double diff = act - pred;
|
double diff = act - pred;
|
||||||
double absDiff = Math.Abs(diff);
|
double absDiff = Math.Abs(diff);
|
||||||
@@ -396,11 +521,15 @@ public static class ErrorHelpers
|
|||||||
double delta = 1.0)
|
double delta = 1.0)
|
||||||
{
|
{
|
||||||
if (actual.Length != predicted.Length || actual.Length != output.Length)
|
if (actual.Length != predicted.Length || actual.Length != output.Length)
|
||||||
|
{
|
||||||
throw new ArgumentException(SpanLengthMismatchMessage, nameof(output));
|
throw new ArgumentException(SpanLengthMismatchMessage, nameof(output));
|
||||||
|
}
|
||||||
|
|
||||||
int len = actual.Length;
|
int len = actual.Length;
|
||||||
if (len == 0)
|
if (len == 0)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
double lastValidActual = FindFirstValidValue(actual);
|
double lastValidActual = FindFirstValidValue(actual);
|
||||||
double lastValidPredicted = FindFirstValidValue(predicted);
|
double lastValidPredicted = FindFirstValidValue(predicted);
|
||||||
@@ -414,8 +543,23 @@ public static class ErrorHelpers
|
|||||||
double act = actual[i];
|
double act = actual[i];
|
||||||
double pred = predicted[i];
|
double pred = predicted[i];
|
||||||
|
|
||||||
if (double.IsFinite(act)) currentValidActual = act; else act = currentValidActual;
|
if (double.IsFinite(act))
|
||||||
if (double.IsFinite(pred)) currentValidPredicted = pred; else pred = currentValidPredicted;
|
{
|
||||||
|
currentValidActual = act;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
act = currentValidActual;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (double.IsFinite(pred))
|
||||||
|
{
|
||||||
|
currentValidPredicted = pred;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pred = currentValidPredicted;
|
||||||
|
}
|
||||||
|
|
||||||
double diff = act - pred;
|
double diff = act - pred;
|
||||||
double absDiff = Math.Abs(diff);
|
double absDiff = Math.Abs(diff);
|
||||||
@@ -438,13 +582,20 @@ public static class ErrorHelpers
|
|||||||
int resyncInterval = 1000)
|
int resyncInterval = 1000)
|
||||||
{
|
{
|
||||||
if (errors.Length != output.Length)
|
if (errors.Length != output.Length)
|
||||||
|
{
|
||||||
throw new ArgumentException("Spans must have the same length", nameof(output));
|
throw new ArgumentException("Spans must have the same length", nameof(output));
|
||||||
|
}
|
||||||
|
|
||||||
if (period <= 0)
|
if (period <= 0)
|
||||||
|
{
|
||||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||||
|
}
|
||||||
|
|
||||||
int len = errors.Length;
|
int len = errors.Length;
|
||||||
if (len == 0)
|
if (len == 0)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
double[]? rented = null;
|
double[]? rented = null;
|
||||||
|
|
||||||
@@ -477,7 +628,10 @@ public static class ErrorHelpers
|
|||||||
buffer[bufferIndex] = error;
|
buffer[bufferIndex] = error;
|
||||||
|
|
||||||
bufferIndex++;
|
bufferIndex++;
|
||||||
if (bufferIndex >= period) bufferIndex = 0;
|
if (bufferIndex >= period)
|
||||||
|
{
|
||||||
|
bufferIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
output[i] = sum / period;
|
output[i] = sum / period;
|
||||||
|
|
||||||
@@ -486,7 +640,11 @@ public static class ErrorHelpers
|
|||||||
{
|
{
|
||||||
tickCount = 0;
|
tickCount = 0;
|
||||||
double recalcSum = 0;
|
double recalcSum = 0;
|
||||||
for (int k = 0; k < period; k++) recalcSum += buffer[k];
|
for (int k = 0; k < period; k++)
|
||||||
|
{
|
||||||
|
recalcSum += buffer[k];
|
||||||
|
}
|
||||||
|
|
||||||
sum = recalcSum;
|
sum = recalcSum;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -511,13 +669,20 @@ public static class ErrorHelpers
|
|||||||
int resyncInterval = 1000)
|
int resyncInterval = 1000)
|
||||||
{
|
{
|
||||||
if (squaredErrors.Length != output.Length)
|
if (squaredErrors.Length != output.Length)
|
||||||
|
{
|
||||||
throw new ArgumentException("Spans must have the same length", nameof(output));
|
throw new ArgumentException("Spans must have the same length", nameof(output));
|
||||||
|
}
|
||||||
|
|
||||||
if (period <= 0)
|
if (period <= 0)
|
||||||
|
{
|
||||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||||
|
}
|
||||||
|
|
||||||
int len = squaredErrors.Length;
|
int len = squaredErrors.Length;
|
||||||
if (len == 0)
|
if (len == 0)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
double[]? rented = null;
|
double[]? rented = null;
|
||||||
|
|
||||||
@@ -550,7 +715,10 @@ public static class ErrorHelpers
|
|||||||
buffer[bufferIndex] = sqError;
|
buffer[bufferIndex] = sqError;
|
||||||
|
|
||||||
bufferIndex++;
|
bufferIndex++;
|
||||||
if (bufferIndex >= period) bufferIndex = 0;
|
if (bufferIndex >= period)
|
||||||
|
{
|
||||||
|
bufferIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
output[i] = Math.Sqrt(sum / period);
|
output[i] = Math.Sqrt(sum / period);
|
||||||
|
|
||||||
@@ -559,7 +727,11 @@ public static class ErrorHelpers
|
|||||||
{
|
{
|
||||||
tickCount = 0;
|
tickCount = 0;
|
||||||
double recalcSum = 0;
|
double recalcSum = 0;
|
||||||
for (int k = 0; k < period; k++) recalcSum += buffer[k];
|
for (int k = 0; k < period; k++)
|
||||||
|
{
|
||||||
|
recalcSum += buffer[k];
|
||||||
|
}
|
||||||
|
|
||||||
sum = recalcSum;
|
sum = recalcSum;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -586,13 +758,20 @@ public static class ErrorHelpers
|
|||||||
int resyncInterval = 1000)
|
int resyncInterval = 1000)
|
||||||
{
|
{
|
||||||
if (weightedSquaredErrors.Length != output.Length || weightedSquaredErrors.Length != weights.Length)
|
if (weightedSquaredErrors.Length != output.Length || weightedSquaredErrors.Length != weights.Length)
|
||||||
|
{
|
||||||
throw new ArgumentException("Spans must have the same length", nameof(output));
|
throw new ArgumentException("Spans must have the same length", nameof(output));
|
||||||
|
}
|
||||||
|
|
||||||
if (period <= 0)
|
if (period <= 0)
|
||||||
|
{
|
||||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||||
|
}
|
||||||
|
|
||||||
int len = weightedSquaredErrors.Length;
|
int len = weightedSquaredErrors.Length;
|
||||||
if (len == 0)
|
if (len == 0)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
double[]? rentedErrors = null;
|
double[]? rentedErrors = null;
|
||||||
double[]? rentedWeights = null;
|
double[]? rentedWeights = null;
|
||||||
@@ -637,7 +816,10 @@ public static class ErrorHelpers
|
|||||||
weightBuffer[bufferIndex] = wgt;
|
weightBuffer[bufferIndex] = wgt;
|
||||||
|
|
||||||
bufferIndex++;
|
bufferIndex++;
|
||||||
if (bufferIndex >= period) bufferIndex = 0;
|
if (bufferIndex >= period)
|
||||||
|
{
|
||||||
|
bufferIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
output[i] = sumWeights > 1e-10 ? Math.Sqrt(sumErrors / sumWeights) : 0.0;
|
output[i] = sumWeights > 1e-10 ? Math.Sqrt(sumErrors / sumWeights) : 0.0;
|
||||||
|
|
||||||
@@ -686,11 +868,15 @@ public static class ErrorHelpers
|
|||||||
Span<double> predictedOut)
|
Span<double> predictedOut)
|
||||||
{
|
{
|
||||||
if (actual.Length != predicted.Length || actual.Length != actualOut.Length || actual.Length != predictedOut.Length)
|
if (actual.Length != predicted.Length || actual.Length != actualOut.Length || actual.Length != predictedOut.Length)
|
||||||
|
{
|
||||||
throw new ArgumentException(SpanLengthMismatchMessage, nameof(predictedOut));
|
throw new ArgumentException(SpanLengthMismatchMessage, nameof(predictedOut));
|
||||||
|
}
|
||||||
|
|
||||||
int len = actual.Length;
|
int len = actual.Length;
|
||||||
if (len == 0)
|
if (len == 0)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
double lastValidActual = FindFirstValidValue(actual);
|
double lastValidActual = FindFirstValidValue(actual);
|
||||||
double lastValidPredicted = FindFirstValidValue(predicted);
|
double lastValidPredicted = FindFirstValidValue(predicted);
|
||||||
@@ -700,8 +886,23 @@ public static class ErrorHelpers
|
|||||||
double act = actual[i];
|
double act = actual[i];
|
||||||
double pred = predicted[i];
|
double pred = predicted[i];
|
||||||
|
|
||||||
if (double.IsFinite(act)) lastValidActual = act; else act = lastValidActual;
|
if (double.IsFinite(act))
|
||||||
if (double.IsFinite(pred)) lastValidPredicted = pred; else pred = lastValidPredicted;
|
{
|
||||||
|
lastValidActual = act;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
act = lastValidActual;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (double.IsFinite(pred))
|
||||||
|
{
|
||||||
|
lastValidPredicted = pred;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pred = lastValidPredicted;
|
||||||
|
}
|
||||||
|
|
||||||
actualOut[i] = act;
|
actualOut[i] = act;
|
||||||
predictedOut[i] = pred;
|
predictedOut[i] = pred;
|
||||||
@@ -717,7 +918,9 @@ public static class ErrorHelpers
|
|||||||
for (int i = 0; i < span.Length; i++)
|
for (int i = 0; i < span.Length; i++)
|
||||||
{
|
{
|
||||||
if (double.IsFinite(span[i]))
|
if (double.IsFinite(span[i]))
|
||||||
|
{
|
||||||
return span[i];
|
return span[i];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
@@ -751,14 +954,18 @@ public static class ErrorHelpers
|
|||||||
// MoveMask returns a bitmask; all-ones means all finite (mask == 0b1111 for 4 doubles)
|
// MoveMask returns a bitmask; all-ones means all finite (mask == 0b1111 for 4 doubles)
|
||||||
int mask = Avx.MoveMask(combined);
|
int mask = Avx.MoveMask(combined);
|
||||||
if (mask != 0b1111)
|
if (mask != 0b1111)
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scalar tail
|
// Scalar tail
|
||||||
for (int i = vectorEnd; i < len; i++)
|
for (int i = vectorEnd; i < len; i++)
|
||||||
{
|
{
|
||||||
if (!double.IsFinite(actual[i]) || !double.IsFinite(predicted[i]))
|
if (!double.IsFinite(actual[i]) || !double.IsFinite(predicted[i]))
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -767,7 +974,9 @@ public static class ErrorHelpers
|
|||||||
for (int i = 0; i < len; i++)
|
for (int i = 0; i < len; i++)
|
||||||
{
|
{
|
||||||
if (!double.IsFinite(actual[i]) || !double.IsFinite(predicted[i]))
|
if (!double.IsFinite(actual[i]) || !double.IsFinite(predicted[i]))
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -890,8 +1099,23 @@ public static class ErrorHelpers
|
|||||||
double act = actual[i];
|
double act = actual[i];
|
||||||
double pred = predicted[i];
|
double pred = predicted[i];
|
||||||
|
|
||||||
if (double.IsFinite(act)) currentValidActual = act; else act = currentValidActual;
|
if (double.IsFinite(act))
|
||||||
if (double.IsFinite(pred)) currentValidPredicted = pred; else pred = currentValidPredicted;
|
{
|
||||||
|
currentValidActual = act;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
act = currentValidActual;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (double.IsFinite(pred))
|
||||||
|
{
|
||||||
|
currentValidPredicted = pred;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pred = currentValidPredicted;
|
||||||
|
}
|
||||||
|
|
||||||
output[i] = act - pred;
|
output[i] = act - pred;
|
||||||
}
|
}
|
||||||
@@ -1099,8 +1323,23 @@ public static class ErrorHelpers
|
|||||||
double act = actual[i];
|
double act = actual[i];
|
||||||
double pred = predicted[i];
|
double pred = predicted[i];
|
||||||
|
|
||||||
if (double.IsFinite(act)) currentValidActual = act; else act = currentValidActual;
|
if (double.IsFinite(act))
|
||||||
if (double.IsFinite(pred)) currentValidPredicted = pred; else pred = currentValidPredicted;
|
{
|
||||||
|
currentValidActual = act;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
act = currentValidActual;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (double.IsFinite(pred))
|
||||||
|
{
|
||||||
|
currentValidPredicted = pred;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pred = currentValidPredicted;
|
||||||
|
}
|
||||||
|
|
||||||
output[i] = Math.Abs(act - pred);
|
output[i] = Math.Abs(act - pred);
|
||||||
}
|
}
|
||||||
@@ -1156,8 +1395,23 @@ public static class ErrorHelpers
|
|||||||
double act = actual[i];
|
double act = actual[i];
|
||||||
double pred = predicted[i];
|
double pred = predicted[i];
|
||||||
|
|
||||||
if (double.IsFinite(act)) currentValidActual = act; else act = currentValidActual;
|
if (double.IsFinite(act))
|
||||||
if (double.IsFinite(pred)) currentValidPredicted = pred; else pred = currentValidPredicted;
|
{
|
||||||
|
currentValidActual = act;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
act = currentValidActual;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (double.IsFinite(pred))
|
||||||
|
{
|
||||||
|
currentValidPredicted = pred;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pred = currentValidPredicted;
|
||||||
|
}
|
||||||
|
|
||||||
double diff = act - pred;
|
double diff = act - pred;
|
||||||
output[i] = diff * diff;
|
output[i] = diff * diff;
|
||||||
@@ -1181,4 +1435,4 @@ public static class ErrorHelpers
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,7 +112,9 @@ public class SimdExtensionsTests
|
|||||||
{
|
{
|
||||||
double[] data = new double[1000];
|
double[] data = new double[1000];
|
||||||
for (int i = 0; i < data.Length; i++)
|
for (int i = 0; i < data.Length; i++)
|
||||||
|
{
|
||||||
data[i] = i + 1.0;
|
data[i] = i + 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
var span = new ReadOnlySpan<double>(data);
|
var span = new ReadOnlySpan<double>(data);
|
||||||
const double expected = 1000.0 * 1001.0 / 2.0;
|
const double expected = 1000.0 * 1001.0 / 2.0;
|
||||||
@@ -992,4 +994,4 @@ public class SimdScalarFallbackTests
|
|||||||
|
|
||||||
Assert.Throws<ArgumentException>(() => SimdExtensions.Subtract(left, right, result));
|
Assert.Throws<ArgumentException>(() => SimdExtensions.Subtract(left, right, result));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+159
-25
@@ -20,7 +20,9 @@ public static class SimdExtensions
|
|||||||
for (int i = 0; i < span.Length; i++)
|
for (int i = 0; i < span.Length; i++)
|
||||||
{
|
{
|
||||||
if (!double.IsFinite(span[i]))
|
if (!double.IsFinite(span[i]))
|
||||||
|
{
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -30,7 +32,10 @@ public static class SimdExtensions
|
|||||||
{
|
{
|
||||||
double scalar = 0.0;
|
double scalar = 0.0;
|
||||||
for (int i = 0; i < span.Length; i++)
|
for (int i = 0; i < span.Length; i++)
|
||||||
|
{
|
||||||
scalar += span[i];
|
scalar += span[i];
|
||||||
|
}
|
||||||
|
|
||||||
return scalar;
|
return scalar;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,13 +43,17 @@ public static class SimdExtensions
|
|||||||
internal static double MinScalar(ReadOnlySpan<double> span)
|
internal static double MinScalar(ReadOnlySpan<double> span)
|
||||||
{
|
{
|
||||||
if (span.Length == 0)
|
if (span.Length == 0)
|
||||||
|
{
|
||||||
throw new ArgumentException("Span must not be empty", nameof(span));
|
throw new ArgumentException("Span must not be empty", nameof(span));
|
||||||
|
}
|
||||||
|
|
||||||
double min = span[0];
|
double min = span[0];
|
||||||
for (int i = 1; i < span.Length; i++)
|
for (int i = 1; i < span.Length; i++)
|
||||||
{
|
{
|
||||||
if (span[i] < min)
|
if (span[i] < min)
|
||||||
|
{
|
||||||
min = span[i];
|
min = span[i];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return min;
|
return min;
|
||||||
}
|
}
|
||||||
@@ -53,13 +62,17 @@ public static class SimdExtensions
|
|||||||
internal static double MaxScalar(ReadOnlySpan<double> span)
|
internal static double MaxScalar(ReadOnlySpan<double> span)
|
||||||
{
|
{
|
||||||
if (span.Length == 0)
|
if (span.Length == 0)
|
||||||
|
{
|
||||||
throw new ArgumentException("Span must not be empty", nameof(span));
|
throw new ArgumentException("Span must not be empty", nameof(span));
|
||||||
|
}
|
||||||
|
|
||||||
double max = span[0];
|
double max = span[0];
|
||||||
for (int i = 1; i < span.Length; i++)
|
for (int i = 1; i < span.Length; i++)
|
||||||
{
|
{
|
||||||
if (span[i] > max)
|
if (span[i] > max)
|
||||||
|
{
|
||||||
max = span[i];
|
max = span[i];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return max;
|
return max;
|
||||||
}
|
}
|
||||||
@@ -69,7 +82,9 @@ public static class SimdExtensions
|
|||||||
{
|
{
|
||||||
// Match VarianceSIMD behavior: return 0.0 for length <= 1 to avoid divide-by-zero
|
// Match VarianceSIMD behavior: return 0.0 for length <= 1 to avoid divide-by-zero
|
||||||
if (span.Length <= 1)
|
if (span.Length <= 1)
|
||||||
|
{
|
||||||
return 0.0;
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
double sumSquares = 0.0;
|
double sumSquares = 0.0;
|
||||||
for (int i = 0; i < span.Length; i++)
|
for (int i = 0; i < span.Length; i++)
|
||||||
@@ -84,14 +99,23 @@ public static class SimdExtensions
|
|||||||
internal static (double Min, double Max) MinMaxScalar(ReadOnlySpan<double> span)
|
internal static (double Min, double Max) MinMaxScalar(ReadOnlySpan<double> span)
|
||||||
{
|
{
|
||||||
if (span.Length == 0)
|
if (span.Length == 0)
|
||||||
|
{
|
||||||
throw new ArgumentException("Span must not be empty", nameof(span));
|
throw new ArgumentException("Span must not be empty", nameof(span));
|
||||||
|
}
|
||||||
|
|
||||||
double scalarMin = span[0];
|
double scalarMin = span[0];
|
||||||
double scalarMax = span[0];
|
double scalarMax = span[0];
|
||||||
for (int i = 1; i < span.Length; i++)
|
for (int i = 1; i < span.Length; i++)
|
||||||
{
|
{
|
||||||
if (span[i] < scalarMin) scalarMin = span[i];
|
if (span[i] < scalarMin)
|
||||||
if (span[i] > scalarMax) scalarMax = span[i];
|
{
|
||||||
|
scalarMin = span[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (span[i] > scalarMax)
|
||||||
|
{
|
||||||
|
scalarMax = span[i];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return (scalarMin, scalarMax);
|
return (scalarMin, scalarMax);
|
||||||
}
|
}
|
||||||
@@ -105,7 +129,10 @@ public static class SimdExtensions
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static bool ContainsNonFinite(this ReadOnlySpan<double> span)
|
public static bool ContainsNonFinite(this ReadOnlySpan<double> span)
|
||||||
{
|
{
|
||||||
if (span.IsEmpty) return false;
|
if (span.IsEmpty)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (Vector.IsHardwareAccelerated && span.Length >= Vector<double>.Count)
|
if (Vector.IsHardwareAccelerated && span.Length >= Vector<double>.Count)
|
||||||
{
|
{
|
||||||
@@ -120,19 +147,25 @@ public static class SimdExtensions
|
|||||||
// NaN check: NaN != NaN, so Vector.Equals(v, v) will be false for NaN lanes
|
// NaN check: NaN != NaN, so Vector.Equals(v, v) will be false for NaN lanes
|
||||||
var nanCheck = Vector.Equals(vector, vector);
|
var nanCheck = Vector.Equals(vector, vector);
|
||||||
if (!nanCheck.Equals(Vector<long>.AllBitsSet))
|
if (!nanCheck.Equals(Vector<long>.AllBitsSet))
|
||||||
|
{
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Infinity check: |v| > MaxValue (Infinity has magnitude > MaxValue)
|
// Infinity check: |v| > MaxValue (Infinity has magnitude > MaxValue)
|
||||||
var absVec = Vector.Abs(vector);
|
var absVec = Vector.Abs(vector);
|
||||||
var infCheck = Vector.GreaterThan(absVec, maxValue);
|
var infCheck = Vector.GreaterThan(absVec, maxValue);
|
||||||
if (!infCheck.Equals(Vector<long>.Zero))
|
if (!infCheck.Equals(Vector<long>.Zero))
|
||||||
|
{
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (; i < span.Length; i++)
|
for (; i < span.Length; i++)
|
||||||
{
|
{
|
||||||
if (!double.IsFinite(span[i]))
|
if (!double.IsFinite(span[i]))
|
||||||
|
{
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -151,7 +184,10 @@ public static class SimdExtensions
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static double SumSIMD(this ReadOnlySpan<double> span)
|
public static double SumSIMD(this ReadOnlySpan<double> span)
|
||||||
{
|
{
|
||||||
if (span.IsEmpty) return 0.0;
|
if (span.IsEmpty)
|
||||||
|
{
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
if (Vector.IsHardwareAccelerated && span.Length >= Vector<double>.Count)
|
if (Vector.IsHardwareAccelerated && span.Length >= Vector<double>.Count)
|
||||||
{
|
{
|
||||||
@@ -167,16 +203,22 @@ public static class SimdExtensions
|
|||||||
|
|
||||||
double result = 0.0;
|
double result = 0.0;
|
||||||
for (int j = 0; j < vectorSize; j++)
|
for (int j = 0; j < vectorSize; j++)
|
||||||
|
{
|
||||||
result += sum[j];
|
result += sum[j];
|
||||||
|
}
|
||||||
|
|
||||||
for (; i < span.Length; i++)
|
for (; i < span.Length; i++)
|
||||||
|
{
|
||||||
result += span[i];
|
result += span[i];
|
||||||
|
}
|
||||||
|
|
||||||
// Lazy check: if result is non-finite AND input contained non-finite values, return NaN
|
// Lazy check: if result is non-finite AND input contained non-finite values, return NaN
|
||||||
// NaN + anything = NaN, Inf + anything finite = Inf
|
// NaN + anything = NaN, Inf + anything finite = Inf
|
||||||
// If result is infinite from overflow (no input NaN/Inf), return as-is
|
// If result is infinite from overflow (no input NaN/Inf), return as-is
|
||||||
if (!double.IsFinite(result) && span.ContainsNonFinite())
|
if (!double.IsFinite(result) && span.ContainsNonFinite())
|
||||||
|
{
|
||||||
return double.NaN;
|
return double.NaN;
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -194,11 +236,21 @@ public static class SimdExtensions
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static double MinSIMD(this ReadOnlySpan<double> span)
|
public static double MinSIMD(this ReadOnlySpan<double> span)
|
||||||
{
|
{
|
||||||
if (span.IsEmpty) return double.NaN;
|
if (span.IsEmpty)
|
||||||
if (span.Length == 1) return span[0];
|
{
|
||||||
|
return double.NaN;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (span.Length == 1)
|
||||||
|
{
|
||||||
|
return span[0];
|
||||||
|
}
|
||||||
|
|
||||||
// Guard against non-finite inputs
|
// Guard against non-finite inputs
|
||||||
if (span.ContainsNonFinite()) return double.NaN;
|
if (span.ContainsNonFinite())
|
||||||
|
{
|
||||||
|
return double.NaN;
|
||||||
|
}
|
||||||
|
|
||||||
if (Vector.IsHardwareAccelerated && span.Length >= Vector<double>.Count)
|
if (Vector.IsHardwareAccelerated && span.Length >= Vector<double>.Count)
|
||||||
{
|
{
|
||||||
@@ -216,13 +268,17 @@ public static class SimdExtensions
|
|||||||
for (int j = 1; j < vectorSize; j++)
|
for (int j = 1; j < vectorSize; j++)
|
||||||
{
|
{
|
||||||
if (minVec[j] < result)
|
if (minVec[j] < result)
|
||||||
|
{
|
||||||
result = minVec[j];
|
result = minVec[j];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (; i < span.Length; i++)
|
for (; i < span.Length; i++)
|
||||||
{
|
{
|
||||||
if (span[i] < result)
|
if (span[i] < result)
|
||||||
|
{
|
||||||
result = span[i];
|
result = span[i];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@@ -239,11 +295,21 @@ public static class SimdExtensions
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static double MaxSIMD(this ReadOnlySpan<double> span)
|
public static double MaxSIMD(this ReadOnlySpan<double> span)
|
||||||
{
|
{
|
||||||
if (span.IsEmpty) return double.NaN;
|
if (span.IsEmpty)
|
||||||
if (span.Length == 1) return span[0];
|
{
|
||||||
|
return double.NaN;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (span.Length == 1)
|
||||||
|
{
|
||||||
|
return span[0];
|
||||||
|
}
|
||||||
|
|
||||||
// Guard against non-finite inputs
|
// Guard against non-finite inputs
|
||||||
if (span.ContainsNonFinite()) return double.NaN;
|
if (span.ContainsNonFinite())
|
||||||
|
{
|
||||||
|
return double.NaN;
|
||||||
|
}
|
||||||
|
|
||||||
if (Vector.IsHardwareAccelerated && span.Length >= Vector<double>.Count)
|
if (Vector.IsHardwareAccelerated && span.Length >= Vector<double>.Count)
|
||||||
{
|
{
|
||||||
@@ -261,13 +327,17 @@ public static class SimdExtensions
|
|||||||
for (int j = 1; j < vectorSize; j++)
|
for (int j = 1; j < vectorSize; j++)
|
||||||
{
|
{
|
||||||
if (maxVec[j] > result)
|
if (maxVec[j] > result)
|
||||||
|
{
|
||||||
result = maxVec[j];
|
result = maxVec[j];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (; i < span.Length; i++)
|
for (; i < span.Length; i++)
|
||||||
{
|
{
|
||||||
if (span[i] > result)
|
if (span[i] > result)
|
||||||
|
{
|
||||||
result = span[i];
|
result = span[i];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@@ -284,7 +354,10 @@ public static class SimdExtensions
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static double AverageSIMD(this ReadOnlySpan<double> span)
|
public static double AverageSIMD(this ReadOnlySpan<double> span)
|
||||||
{
|
{
|
||||||
if (span.IsEmpty) return double.NaN;
|
if (span.IsEmpty)
|
||||||
|
{
|
||||||
|
return double.NaN;
|
||||||
|
}
|
||||||
// SumSIMD already guards against non-finite, which will propagate NaN
|
// SumSIMD already guards against non-finite, which will propagate NaN
|
||||||
return span.SumSIMD() / span.Length;
|
return span.SumSIMD() / span.Length;
|
||||||
}
|
}
|
||||||
@@ -300,13 +373,20 @@ public static class SimdExtensions
|
|||||||
public static double VarianceSIMD(this ReadOnlySpan<double> span, double? mean = null)
|
public static double VarianceSIMD(this ReadOnlySpan<double> span, double? mean = null)
|
||||||
{
|
{
|
||||||
// Match VarianceScalar behavior: return 0.0 for length <= 1 to avoid inconsistency
|
// Match VarianceScalar behavior: return 0.0 for length <= 1 to avoid inconsistency
|
||||||
if (span.Length <= 1) return 0.0;
|
if (span.Length <= 1)
|
||||||
|
{
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
double m;
|
double m;
|
||||||
if (mean.HasValue)
|
if (mean.HasValue)
|
||||||
{
|
{
|
||||||
// Mean provided externally - need explicit non-finite check
|
// Mean provided externally - need explicit non-finite check
|
||||||
if (span.ContainsNonFinite()) return double.NaN;
|
if (span.ContainsNonFinite())
|
||||||
|
{
|
||||||
|
return double.NaN;
|
||||||
|
}
|
||||||
|
|
||||||
m = mean.Value;
|
m = mean.Value;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -317,7 +397,10 @@ public static class SimdExtensions
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If mean is NaN (from input NaN or explicit NaN mean), return NaN
|
// If mean is NaN (from input NaN or explicit NaN mean), return NaN
|
||||||
if (!double.IsFinite(m)) return double.NaN;
|
if (!double.IsFinite(m))
|
||||||
|
{
|
||||||
|
return double.NaN;
|
||||||
|
}
|
||||||
|
|
||||||
if (Vector.IsHardwareAccelerated && span.Length >= Vector<double>.Count)
|
if (Vector.IsHardwareAccelerated && span.Length >= Vector<double>.Count)
|
||||||
{
|
{
|
||||||
@@ -335,7 +418,9 @@ public static class SimdExtensions
|
|||||||
|
|
||||||
double result = 0.0;
|
double result = 0.0;
|
||||||
for (int j = 0; j < vectorSize; j++)
|
for (int j = 0; j < vectorSize; j++)
|
||||||
|
{
|
||||||
result += sumSq[j];
|
result += sumSq[j];
|
||||||
|
}
|
||||||
|
|
||||||
for (; i < span.Length; i++)
|
for (; i < span.Length; i++)
|
||||||
{
|
{
|
||||||
@@ -368,11 +453,21 @@ public static class SimdExtensions
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static (double Min, double Max) MinMaxSIMD(this ReadOnlySpan<double> span)
|
public static (double Min, double Max) MinMaxSIMD(this ReadOnlySpan<double> span)
|
||||||
{
|
{
|
||||||
if (span.IsEmpty) return (double.NaN, double.NaN);
|
if (span.IsEmpty)
|
||||||
if (span.Length == 1) return (span[0], span[0]);
|
{
|
||||||
|
return (double.NaN, double.NaN);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (span.Length == 1)
|
||||||
|
{
|
||||||
|
return (span[0], span[0]);
|
||||||
|
}
|
||||||
|
|
||||||
// Guard against non-finite inputs
|
// Guard against non-finite inputs
|
||||||
if (span.ContainsNonFinite()) return (double.NaN, double.NaN);
|
if (span.ContainsNonFinite())
|
||||||
|
{
|
||||||
|
return (double.NaN, double.NaN);
|
||||||
|
}
|
||||||
|
|
||||||
if (Vector.IsHardwareAccelerated && span.Length >= Vector<double>.Count)
|
if (Vector.IsHardwareAccelerated && span.Length >= Vector<double>.Count)
|
||||||
{
|
{
|
||||||
@@ -392,14 +487,28 @@ public static class SimdExtensions
|
|||||||
double max = maxVec[0];
|
double max = maxVec[0];
|
||||||
for (int j = 1; j < vectorSize; j++)
|
for (int j = 1; j < vectorSize; j++)
|
||||||
{
|
{
|
||||||
if (minVec[j] < min) min = minVec[j];
|
if (minVec[j] < min)
|
||||||
if (maxVec[j] > max) max = maxVec[j];
|
{
|
||||||
|
min = minVec[j];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxVec[j] > max)
|
||||||
|
{
|
||||||
|
max = maxVec[j];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (; i < span.Length; i++)
|
for (; i < span.Length; i++)
|
||||||
{
|
{
|
||||||
if (span[i] < min) min = span[i];
|
if (span[i] < min)
|
||||||
if (span[i] > max) max = span[i];
|
{
|
||||||
|
min = span[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (span[i] > max)
|
||||||
|
{
|
||||||
|
max = span[i];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (min, max);
|
return (min, max);
|
||||||
@@ -416,7 +525,9 @@ public static class SimdExtensions
|
|||||||
public static void Add(ReadOnlySpan<double> left, ReadOnlySpan<double> right, Span<double> result)
|
public static void Add(ReadOnlySpan<double> left, ReadOnlySpan<double> right, Span<double> result)
|
||||||
{
|
{
|
||||||
if (left.Length != right.Length || left.Length != result.Length)
|
if (left.Length != right.Length || left.Length != result.Length)
|
||||||
|
{
|
||||||
throw new ArgumentException("All spans must have the same length", nameof(result));
|
throw new ArgumentException("All spans must have the same length", nameof(result));
|
||||||
|
}
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
if (Vector.IsHardwareAccelerated && left.Length >= Vector<double>.Count)
|
if (Vector.IsHardwareAccelerated && left.Length >= Vector<double>.Count)
|
||||||
@@ -444,7 +555,9 @@ public static class SimdExtensions
|
|||||||
public static void Scale(ReadOnlySpan<double> source, double scalar, Span<double> result)
|
public static void Scale(ReadOnlySpan<double> source, double scalar, Span<double> result)
|
||||||
{
|
{
|
||||||
if (source.Length != result.Length)
|
if (source.Length != result.Length)
|
||||||
|
{
|
||||||
throw new ArgumentException("Source and result spans must have the same length", nameof(result));
|
throw new ArgumentException("Source and result spans must have the same length", nameof(result));
|
||||||
|
}
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
if (Vector.IsHardwareAccelerated && source.Length >= Vector<double>.Count)
|
if (Vector.IsHardwareAccelerated && source.Length >= Vector<double>.Count)
|
||||||
@@ -472,7 +585,9 @@ public static class SimdExtensions
|
|||||||
public static void Subtract(ReadOnlySpan<double> left, ReadOnlySpan<double> right, Span<double> result)
|
public static void Subtract(ReadOnlySpan<double> left, ReadOnlySpan<double> right, Span<double> result)
|
||||||
{
|
{
|
||||||
if (left.Length != right.Length || left.Length != result.Length)
|
if (left.Length != right.Length || left.Length != result.Length)
|
||||||
|
{
|
||||||
throw new ArgumentException("All spans must have the same length", nameof(result));
|
throw new ArgumentException("All spans must have the same length", nameof(result));
|
||||||
|
}
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
if (Vector.IsHardwareAccelerated && left.Length >= Vector<double>.Count)
|
if (Vector.IsHardwareAccelerated && left.Length >= Vector<double>.Count)
|
||||||
@@ -500,9 +615,14 @@ public static class SimdExtensions
|
|||||||
public static double DotProduct(this ReadOnlySpan<double> a, ReadOnlySpan<double> b)
|
public static double DotProduct(this ReadOnlySpan<double> a, ReadOnlySpan<double> b)
|
||||||
{
|
{
|
||||||
if (a.Length != b.Length)
|
if (a.Length != b.Length)
|
||||||
|
{
|
||||||
throw new ArgumentException("Spans must have equal length", nameof(b));
|
throw new ArgumentException("Spans must have equal length", nameof(b));
|
||||||
|
}
|
||||||
|
|
||||||
if (a.IsEmpty) return 0.0;
|
if (a.IsEmpty)
|
||||||
|
{
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
int len = a.Length;
|
int len = a.Length;
|
||||||
|
|
||||||
@@ -513,19 +633,33 @@ public static class SimdExtensions
|
|||||||
ref double bRef = ref MemoryMarshal.GetReference(b);
|
ref double bRef = ref MemoryMarshal.GetReference(b);
|
||||||
|
|
||||||
double sum = aRef * bRef;
|
double sum = aRef * bRef;
|
||||||
if (len > 1) sum += Unsafe.Add(ref aRef, 1) * Unsafe.Add(ref bRef, 1);
|
if (len > 1)
|
||||||
if (len > 2) sum += Unsafe.Add(ref aRef, 2) * Unsafe.Add(ref bRef, 2);
|
{
|
||||||
|
sum += Unsafe.Add(ref aRef, 1) * Unsafe.Add(ref bRef, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len > 2)
|
||||||
|
{
|
||||||
|
sum += Unsafe.Add(ref aRef, 2) * Unsafe.Add(ref bRef, 2);
|
||||||
|
}
|
||||||
|
|
||||||
return sum;
|
return sum;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Avx512F.IsSupported)
|
if (Avx512F.IsSupported)
|
||||||
|
{
|
||||||
return DotProductAvx512(a, b);
|
return DotProductAvx512(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
if (Avx2.IsSupported)
|
if (Avx2.IsSupported)
|
||||||
|
{
|
||||||
return DotProductAvx2(a, b);
|
return DotProductAvx2(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
if (AdvSimd.Arm64.IsSupported)
|
if (AdvSimd.Arm64.IsSupported)
|
||||||
|
{
|
||||||
return DotProductNeon(a, b);
|
return DotProductNeon(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
double s1 = 0, s2 = 0, s3 = 0, s4 = 0;
|
double s1 = 0, s2 = 0, s3 = 0, s4 = 0;
|
||||||
ref double ar = ref MemoryMarshal.GetReference(a);
|
ref double ar = ref MemoryMarshal.GetReference(a);
|
||||||
@@ -779,4 +913,4 @@ public static class SimdExtensions
|
|||||||
|
|
||||||
return sum;
|
return sum;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -508,4 +508,4 @@ public class TBarTests
|
|||||||
// (90 + 120 + 60) / 3 = 270 / 3 = 90
|
// (90 + 120 + 60) / 3 = 270 / 3 = 90
|
||||||
Assert.Equal(90.0, bar.OHL3);
|
Assert.Equal(90.0, bar.OHL3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,4 +53,4 @@ public readonly record struct TBar(long Time, double Open, double High, double L
|
|||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public override string ToString() => $"[{AsDateTime:yyyy-MM-dd HH:mm:ss}: O={Open:F2}, H={High:F2}, L={Low:F2}, C={Close:F2}, V={Volume:F2}]";
|
public override string ToString() => $"[{AsDateTime:yyyy-MM-dd HH:mm:ss}: O={Open:F2}, H={High:F2}, L={Low:F2}, C={Close:F2}, V={Volume:F2}]";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,7 +64,9 @@ public struct TBarSeriesEnumerator : IEnumerator<TBar>, IEquatable<TBarSeriesEnu
|
|||||||
public bool MoveNext()
|
public bool MoveNext()
|
||||||
{
|
{
|
||||||
if (_index + 1 >= _count)
|
if (_index + 1 >= _count)
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
_index++;
|
_index++;
|
||||||
_current = new TBar(_t[_index], _o[_index], _h[_index], _l[_index], _c[_index], _v[_index]);
|
_current = new TBar(_t[_index], _o[_index], _h[_index], _l[_index], _c[_index], _v[_index]);
|
||||||
@@ -323,9 +325,14 @@ public class TBarSeries : IReadOnlyList<TBar>
|
|||||||
{
|
{
|
||||||
int len = t.Length;
|
int len = t.Length;
|
||||||
if (o.Length != len || h.Length != len || l.Length != len || c.Length != len || v.Length != len)
|
if (o.Length != len || h.Length != len || l.Length != len || c.Length != len || v.Length != len)
|
||||||
|
{
|
||||||
throw new ArgumentException("All spans must have the same length", nameof(t));
|
throw new ArgumentException("All spans must have the same length", nameof(t));
|
||||||
|
}
|
||||||
|
|
||||||
if (len == 0) return;
|
if (len == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int oldCount = _c.Count;
|
int oldCount = _c.Count;
|
||||||
int newCount = oldCount + len;
|
int newCount = oldCount + len;
|
||||||
@@ -366,7 +373,10 @@ public class TBarSeries : IReadOnlyList<TBar>
|
|||||||
public void AddRange(ReadOnlySpan<TBar> bars)
|
public void AddRange(ReadOnlySpan<TBar> bars)
|
||||||
{
|
{
|
||||||
int len = bars.Length;
|
int len = bars.Length;
|
||||||
if (len == 0) return;
|
if (len == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int oldCount = _c.Count;
|
int oldCount = _c.Count;
|
||||||
int newCount = oldCount + len;
|
int newCount = oldCount + len;
|
||||||
@@ -417,4 +427,4 @@ public class TBarSeries : IReadOnlyList<TBar>
|
|||||||
|
|
||||||
IEnumerator<TBar> IEnumerable<TBar>.GetEnumerator() => GetEnumerator();
|
IEnumerator<TBar> IEnumerable<TBar>.GetEnumerator() => GetEnumerator();
|
||||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,4 +49,4 @@ public interface ITValuePublisher
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
event TValuePublishedHandler? Pub;
|
event TValuePublishedHandler? Pub;
|
||||||
}
|
}
|
||||||
#pragma warning restore MA0046
|
#pragma warning restore MA0046
|
||||||
|
|||||||
@@ -41,7 +41,9 @@ public struct TSeriesEnumerator : IEnumerator<TValue>, IEquatable<TSeriesEnumera
|
|||||||
public bool MoveNext()
|
public bool MoveNext()
|
||||||
{
|
{
|
||||||
if (_index + 1 >= _count)
|
if (_index + 1 >= _count)
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
_index++;
|
_index++;
|
||||||
_current = new TValue(_t[_index], _v[_index]);
|
_current = new TValue(_t[_index], _v[_index]);
|
||||||
@@ -227,4 +229,4 @@ public class TSeries : IReadOnlyList<TValue>, ITValuePublisher
|
|||||||
|
|
||||||
IEnumerator<TValue> IEnumerable<TValue>.GetEnumerator() => GetEnumerator();
|
IEnumerator<TValue> IEnumerable<TValue>.GetEnumerator() => GetEnumerator();
|
||||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -373,4 +373,4 @@ public class TValueTests
|
|||||||
|
|
||||||
Assert.Equal(long.MaxValue, tValue.Time);
|
Assert.Equal(long.MaxValue, tValue.Time);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,9 @@ public readonly record struct TValue(long Time, double Value) : ISpanFormattable
|
|||||||
public string ToString(string? format, IFormatProvider? formatProvider)
|
public string ToString(string? format, IFormatProvider? formatProvider)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(format))
|
if (!string.IsNullOrEmpty(format))
|
||||||
|
{
|
||||||
throw new NotSupportedException($"Custom format '{format}' is not supported by TValue. Use ToString() for the default format.");
|
throw new NotSupportedException($"Custom format '{format}' is not supported by TValue. Use ToString() for the default format.");
|
||||||
|
}
|
||||||
|
|
||||||
return ToString();
|
return ToString();
|
||||||
}
|
}
|
||||||
@@ -70,7 +72,9 @@ public readonly record struct TValue(long Time, double Value) : ISpanFormattable
|
|||||||
// This is a heuristic check; actual buffer-overflow protection is performed
|
// This is a heuristic check; actual buffer-overflow protection is performed
|
||||||
// by the explicit length checks that guard each write operation below.
|
// by the explicit length checks that guard each write operation below.
|
||||||
if (destination.Length < 24)
|
if (destination.Length < 24)
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Write opening bracket
|
// Write opening bracket
|
||||||
destination[0] = '[';
|
destination[0] = '[';
|
||||||
@@ -78,12 +82,18 @@ public readonly record struct TValue(long Time, double Value) : ISpanFormattable
|
|||||||
|
|
||||||
// Format datetime: yyyy-MM-dd HH:mm:ss (19 chars)
|
// Format datetime: yyyy-MM-dd HH:mm:ss (19 chars)
|
||||||
if (!AsDateTime.TryFormat(destination.Slice(pos), out int dtChars, "yyyy-MM-dd HH:mm:ss", provider))
|
if (!AsDateTime.TryFormat(destination.Slice(pos), out int dtChars, "yyyy-MM-dd HH:mm:ss", provider))
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
pos += dtChars;
|
pos += dtChars;
|
||||||
|
|
||||||
// Write separator
|
// Write separator
|
||||||
if (pos + 2 > destination.Length)
|
if (pos + 2 > destination.Length)
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
destination[pos++] = ',';
|
destination[pos++] = ',';
|
||||||
destination[pos++] = ' ';
|
destination[pos++] = ' ';
|
||||||
|
|
||||||
@@ -91,20 +101,29 @@ public readonly record struct TValue(long Time, double Value) : ISpanFormattable
|
|||||||
if (double.IsPositiveInfinity(Value))
|
if (double.IsPositiveInfinity(Value))
|
||||||
{
|
{
|
||||||
if (pos + 1 > destination.Length)
|
if (pos + 1 > destination.Length)
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
destination[pos++] = (char)0x221E; //
|
destination[pos++] = (char)0x221E; //
|
||||||
}
|
}
|
||||||
else if (double.IsNegativeInfinity(Value))
|
else if (double.IsNegativeInfinity(Value))
|
||||||
{
|
{
|
||||||
if (pos + 2 > destination.Length)
|
if (pos + 2 > destination.Length)
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
destination[pos++] = '-';
|
destination[pos++] = '-';
|
||||||
destination[pos++] = (char)0x221E; // -
|
destination[pos++] = (char)0x221E; // -
|
||||||
}
|
}
|
||||||
else if (double.IsNaN(Value))
|
else if (double.IsNaN(Value))
|
||||||
{
|
{
|
||||||
if (pos + 3 > destination.Length)
|
if (pos + 3 > destination.Length)
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
destination[pos++] = 'N';
|
destination[pos++] = 'N';
|
||||||
destination[pos++] = 'a';
|
destination[pos++] = 'a';
|
||||||
destination[pos++] = 'N';
|
destination[pos++] = 'N';
|
||||||
@@ -112,13 +131,19 @@ public readonly record struct TValue(long Time, double Value) : ISpanFormattable
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (!Value.TryFormat(destination.Slice(pos), out int valueChars, "F2", provider))
|
if (!Value.TryFormat(destination.Slice(pos), out int valueChars, "F2", provider))
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
pos += valueChars;
|
pos += valueChars;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write closing bracket
|
// Write closing bracket
|
||||||
if (pos + 1 > destination.Length)
|
if (pos + 1 > destination.Length)
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
destination[pos++] = ']';
|
destination[pos++] = ']';
|
||||||
|
|
||||||
charsWritten = pos;
|
charsWritten = pos;
|
||||||
|
|||||||
@@ -50,14 +50,14 @@ public sealed class StcValidationTests : IDisposable
|
|||||||
|
|
||||||
for (int i = skip; i < qResult.Count; i++)
|
for (int i = skip; i < qResult.Count; i++)
|
||||||
{
|
{
|
||||||
double sVal = sResult[i].Stc ?? double.NaN;
|
double sVal = sResult[i].Stc ?? double.NaN;
|
||||||
double qVal = qResult[i].Value;
|
double qVal = qResult[i].Value;
|
||||||
|
|
||||||
if (!double.IsNaN(sVal) && !double.IsNaN(qVal))
|
if (!double.IsNaN(sVal) && !double.IsNaN(qVal))
|
||||||
{
|
{
|
||||||
sumSq += (sVal - qVal) * (sVal - qVal);
|
sumSq += (sVal - qVal) * (sVal - qVal);
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
double rmse = Math.Sqrt(sumSq / count);
|
double rmse = Math.Sqrt(sumSq / count);
|
||||||
@@ -68,7 +68,7 @@ public sealed class StcValidationTests : IDisposable
|
|||||||
Assert.True(rmse > 5.0, "QuanTAlib STC matches Skender STC, which suggests regression to Single Smoothed logic.");
|
Assert.True(rmse > 5.0, "QuanTAlib STC matches Skender STC, which suggests regression to Single Smoothed logic.");
|
||||||
|
|
||||||
// Assert values are valid
|
// Assert values are valid
|
||||||
for(int i = skip; i < qResult.Count; i++)
|
for (int i = skip; i < qResult.Count; i++)
|
||||||
{
|
{
|
||||||
Assert.True(double.IsFinite(qResult[i].Value));
|
Assert.True(double.IsFinite(qResult[i].Value));
|
||||||
Assert.InRange(qResult[i].Value, 0, 100);
|
Assert.InRange(qResult[i].Value, 0, 100);
|
||||||
|
|||||||
+108
-18
@@ -113,7 +113,11 @@ public sealed class Stc : AbstractBase
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static double Clamp100(double x)
|
private static double Clamp100(double x)
|
||||||
{
|
{
|
||||||
if (double.IsNaN(x)) return x;
|
if (double.IsNaN(x))
|
||||||
|
{
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
return Math.Clamp(x, 0, 100);
|
return Math.Clamp(x, 0, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,9 +143,19 @@ public sealed class Stc : AbstractBase
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case StcSmoothing.Digital:
|
case StcSmoothing.Digital:
|
||||||
if (stoch2Raw > 75) stc = 100;
|
if (stoch2Raw > 75)
|
||||||
else if (stoch2Raw < 25) stc = 0;
|
{
|
||||||
else stc = double.IsNaN(prevStc) ? stoch2Raw : prevStc;
|
stc = 100;
|
||||||
|
}
|
||||||
|
else if (stoch2Raw < 25)
|
||||||
|
{
|
||||||
|
stc = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
stc = double.IsNaN(prevStc) ? stoch2Raw : prevStc;
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default: // Includes StcSmoothing.None
|
default: // Includes StcSmoothing.None
|
||||||
@@ -159,26 +173,48 @@ public sealed class Stc : AbstractBase
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static bool UpdateMinMaxCore(double added, double removed, bool hasRemoved, ref double min, ref double max)
|
private static bool UpdateMinMaxCore(double added, double removed, bool hasRemoved, ref double min, ref double max)
|
||||||
{
|
{
|
||||||
if (double.IsNaN(added)) return false;
|
if (double.IsNaN(added))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool expandMin = added < min;
|
bool expandMin = added < min;
|
||||||
bool expandMax = added > max;
|
bool expandMax = added > max;
|
||||||
|
|
||||||
if (!hasRemoved)
|
if (!hasRemoved)
|
||||||
{
|
{
|
||||||
if (expandMin) min = added;
|
if (expandMin)
|
||||||
if (expandMax) max = added;
|
{
|
||||||
|
min = added;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expandMax)
|
||||||
|
{
|
||||||
|
max = added;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use relative tolerance for floating-point comparison
|
// Use relative tolerance for floating-point comparison
|
||||||
double tolerance = Math.Max(Math.Abs(min), Math.Abs(max)) * 1e-12;
|
double tolerance = Math.Max(Math.Abs(min), Math.Abs(max)) * 1e-12;
|
||||||
if (tolerance < 1e-15) tolerance = 1e-15; // minimum absolute tolerance
|
if (tolerance < 1e-15)
|
||||||
|
{
|
||||||
|
tolerance = 1e-15; // minimum absolute tolerance
|
||||||
|
}
|
||||||
|
|
||||||
bool removedMin = Math.Abs(removed - min) <= tolerance;
|
bool removedMin = Math.Abs(removed - min) <= tolerance;
|
||||||
bool removedMax = Math.Abs(removed - max) <= tolerance;
|
bool removedMax = Math.Abs(removed - max) <= tolerance;
|
||||||
|
|
||||||
if (expandMin) min = added;
|
if (expandMin)
|
||||||
if (expandMax) max = added;
|
{
|
||||||
|
min = added;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expandMax)
|
||||||
|
{
|
||||||
|
max = added;
|
||||||
|
}
|
||||||
|
|
||||||
return (removedMin && !expandMin) || (removedMax && !expandMax);
|
return (removedMin && !expandMin) || (removedMax && !expandMax);
|
||||||
}
|
}
|
||||||
@@ -193,9 +229,20 @@ public sealed class Stc : AbstractBase
|
|||||||
max = double.NegativeInfinity;
|
max = double.NegativeInfinity;
|
||||||
foreach (double v in span)
|
foreach (double v in span)
|
||||||
{
|
{
|
||||||
if (double.IsNaN(v)) continue;
|
if (double.IsNaN(v))
|
||||||
if (v < min) min = v;
|
{
|
||||||
if (v > max) max = v;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (v < min)
|
||||||
|
{
|
||||||
|
min = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (v > max)
|
||||||
|
{
|
||||||
|
max = v;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,8 +272,14 @@ public sealed class Stc : AbstractBase
|
|||||||
public override TValue Update(TValue input, bool isNew = true)
|
public override TValue Update(TValue input, bool isNew = true)
|
||||||
{
|
{
|
||||||
_isNew = isNew;
|
_isNew = isNew;
|
||||||
if (isNew) _ps = _s;
|
if (isNew)
|
||||||
else _s = _ps;
|
{
|
||||||
|
_ps = _s;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_s = _ps;
|
||||||
|
}
|
||||||
|
|
||||||
var s = _s;
|
var s = _s;
|
||||||
|
|
||||||
@@ -276,9 +329,13 @@ public sealed class Stc : AbstractBase
|
|||||||
{
|
{
|
||||||
double span = s.MacdMax - s.MacdMin;
|
double span = s.MacdMax - s.MacdMin;
|
||||||
if (span > double.Epsilon)
|
if (span > double.Epsilon)
|
||||||
|
{
|
||||||
stoch1Raw = 100.0 * (macd - s.MacdMin) / span;
|
stoch1Raw = 100.0 * (macd - s.MacdMin) / span;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
stoch1Raw = double.IsNaN(s.Stoch1Ema) ? 50.0 : s.Stoch1Ema;
|
stoch1Raw = double.IsNaN(s.Stoch1Ema) ? 50.0 : s.Stoch1Ema;
|
||||||
|
}
|
||||||
|
|
||||||
stoch1Raw = Clamp100(stoch1Raw);
|
stoch1Raw = Clamp100(stoch1Raw);
|
||||||
}
|
}
|
||||||
@@ -322,9 +379,13 @@ public sealed class Stc : AbstractBase
|
|||||||
{
|
{
|
||||||
double span = s.Stoch1Max - s.Stoch1Min;
|
double span = s.Stoch1Max - s.Stoch1Min;
|
||||||
if (span > double.Epsilon)
|
if (span > double.Epsilon)
|
||||||
|
{
|
||||||
stoch2Raw = 100.0 * (stoch1 - s.Stoch1Min) / span;
|
stoch2Raw = 100.0 * (stoch1 - s.Stoch1Min) / span;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
stoch2Raw = double.IsNaN(s.Stoch2Ema) ? stoch1 : s.Stoch2Ema;
|
stoch2Raw = double.IsNaN(s.Stoch2Ema) ? stoch1 : s.Stoch2Ema;
|
||||||
|
}
|
||||||
|
|
||||||
stoch2Raw = Clamp100(stoch2Raw);
|
stoch2Raw = Clamp100(stoch2Raw);
|
||||||
}
|
}
|
||||||
@@ -340,7 +401,10 @@ public sealed class Stc : AbstractBase
|
|||||||
stc = ApplySmoothing(stoch2Raw, _smoothing, _dAlpha, ref s.Stoch2Ema, ref s.PrevStc);
|
stc = ApplySmoothing(stoch2Raw, _smoothing, _dAlpha, ref s.Stoch2Ema, ref s.PrevStc);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isNew) _samples++;
|
if (isNew)
|
||||||
|
{
|
||||||
|
_samples++;
|
||||||
|
}
|
||||||
|
|
||||||
_s = s;
|
_s = s;
|
||||||
Last = new TValue(input.Time, stc);
|
Last = new TValue(input.Time, stc);
|
||||||
@@ -352,14 +416,19 @@ public sealed class Stc : AbstractBase
|
|||||||
{
|
{
|
||||||
var result = new TSeries();
|
var result = new TSeries();
|
||||||
foreach (var item in source)
|
foreach (var item in source)
|
||||||
|
{
|
||||||
result.Add(Update(item, isNew: true));
|
result.Add(Update(item, isNew: true));
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Prime(ReadOnlySpan<double> source, TimeSpan? step = null)
|
public override void Prime(ReadOnlySpan<double> source, TimeSpan? step = null)
|
||||||
{
|
{
|
||||||
foreach (double v in source)
|
foreach (double v in source)
|
||||||
|
{
|
||||||
Update(new TValue(DateTime.MinValue, v), isNew: true);
|
Update(new TValue(DateTime.MinValue, v), isNew: true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
@@ -389,7 +458,9 @@ public sealed class Stc : AbstractBase
|
|||||||
int kPeriod = 10, int dPeriod = 3, int fastLength = 23, int slowLength = 50, StcSmoothing smoothing = StcSmoothing.Ema)
|
int kPeriod = 10, int dPeriod = 3, int fastLength = 23, int slowLength = 50, StcSmoothing smoothing = StcSmoothing.Ema)
|
||||||
{
|
{
|
||||||
if (source.Length != output.Length)
|
if (source.Length != output.Length)
|
||||||
|
{
|
||||||
throw new ArgumentException("Source and output spans must be of equal length.", nameof(output));
|
throw new ArgumentException("Source and output spans must be of equal length.", nameof(output));
|
||||||
|
}
|
||||||
|
|
||||||
double fastAlpha = 2.0 / (fastLength + 1.0);
|
double fastAlpha = 2.0 / (fastLength + 1.0);
|
||||||
double slowAlpha = 2.0 / (slowLength + 1.0);
|
double slowAlpha = 2.0 / (slowLength + 1.0);
|
||||||
@@ -465,7 +536,10 @@ public sealed class Stc : AbstractBase
|
|||||||
double macdRemoved = macdBuf[macdIdx];
|
double macdRemoved = macdBuf[macdIdx];
|
||||||
macdBuf[macdIdx] = macd;
|
macdBuf[macdIdx] = macd;
|
||||||
macdIdx = (macdIdx + 1) % kPeriod;
|
macdIdx = (macdIdx + 1) % kPeriod;
|
||||||
if (!macdHasRemoved) macdCount++;
|
if (!macdHasRemoved)
|
||||||
|
{
|
||||||
|
macdCount++;
|
||||||
|
}
|
||||||
|
|
||||||
ReadOnlySpan<double> macdValidSpan = macdBuf.Slice(0, macdCount);
|
ReadOnlySpan<double> macdValidSpan = macdBuf.Slice(0, macdCount);
|
||||||
UpdateMinMax(macd, macdRemoved, macdHasRemoved, macdValidSpan, ref macdMin, ref macdMax);
|
UpdateMinMax(macd, macdRemoved, macdHasRemoved, macdValidSpan, ref macdMin, ref macdMax);
|
||||||
@@ -476,9 +550,13 @@ public sealed class Stc : AbstractBase
|
|||||||
{
|
{
|
||||||
double span = macdMax - macdMin;
|
double span = macdMax - macdMin;
|
||||||
if (span > double.Epsilon)
|
if (span > double.Epsilon)
|
||||||
|
{
|
||||||
stoch1Raw = 100.0 * (macd - macdMin) / span;
|
stoch1Raw = 100.0 * (macd - macdMin) / span;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
stoch1Raw = double.IsNaN(stoch1Ema) ? 50.0 : stoch1Ema;
|
stoch1Raw = double.IsNaN(stoch1Ema) ? 50.0 : stoch1Ema;
|
||||||
|
}
|
||||||
|
|
||||||
stoch1Raw = Clamp100(stoch1Raw);
|
stoch1Raw = Clamp100(stoch1Raw);
|
||||||
}
|
}
|
||||||
@@ -505,7 +583,10 @@ public sealed class Stc : AbstractBase
|
|||||||
double stochRemoved = stoch1Buf[stoch1Idx];
|
double stochRemoved = stoch1Buf[stoch1Idx];
|
||||||
stoch1Buf[stoch1Idx] = stoch1;
|
stoch1Buf[stoch1Idx] = stoch1;
|
||||||
stoch1Idx = (stoch1Idx + 1) % kPeriod;
|
stoch1Idx = (stoch1Idx + 1) % kPeriod;
|
||||||
if (!stochHasRemoved) stoch1Count++;
|
if (!stochHasRemoved)
|
||||||
|
{
|
||||||
|
stoch1Count++;
|
||||||
|
}
|
||||||
|
|
||||||
ReadOnlySpan<double> stochValidSpan = stoch1Buf.Slice(0, stoch1Count);
|
ReadOnlySpan<double> stochValidSpan = stoch1Buf.Slice(0, stoch1Count);
|
||||||
UpdateMinMax(stoch1, stochRemoved, stochHasRemoved, stochValidSpan, ref stoch1Min, ref stoch1Max);
|
UpdateMinMax(stoch1, stochRemoved, stochHasRemoved, stochValidSpan, ref stoch1Min, ref stoch1Max);
|
||||||
@@ -517,9 +598,13 @@ public sealed class Stc : AbstractBase
|
|||||||
{
|
{
|
||||||
double span = stoch1Max - stoch1Min;
|
double span = stoch1Max - stoch1Min;
|
||||||
if (span > double.Epsilon)
|
if (span > double.Epsilon)
|
||||||
|
{
|
||||||
stoch2Raw = 100.0 * (stoch1 - stoch1Min) / span;
|
stoch2Raw = 100.0 * (stoch1 - stoch1Min) / span;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
stoch2Raw = double.IsNaN(stoch2Ema) ? stoch1 : stoch2Ema;
|
stoch2Raw = double.IsNaN(stoch2Ema) ? stoch1 : stoch2Ema;
|
||||||
|
}
|
||||||
|
|
||||||
stoch2Raw = Clamp100(stoch2Raw);
|
stoch2Raw = Clamp100(stoch2Raw);
|
||||||
}
|
}
|
||||||
@@ -541,9 +626,14 @@ public sealed class Stc : AbstractBase
|
|||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (rentedMacd != null)
|
if (rentedMacd != null)
|
||||||
|
{
|
||||||
ArrayPool<double>.Shared.Return(rentedMacd);
|
ArrayPool<double>.Shared.Return(rentedMacd);
|
||||||
|
}
|
||||||
|
|
||||||
if (rentedStoch1 != null)
|
if (rentedStoch1 != null)
|
||||||
|
{
|
||||||
ArrayPool<double>.Shared.Return(rentedStoch1);
|
ArrayPool<double>.Shared.Return(rentedStoch1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,9 @@ public class AdxTests
|
|||||||
var bars = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
var bars = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||||
|
|
||||||
for (int i = 0; i < 50; i++)
|
for (int i = 0; i < 50; i++)
|
||||||
|
{
|
||||||
adx.Update(bars[i]);
|
adx.Update(bars[i]);
|
||||||
|
}
|
||||||
|
|
||||||
var originalValue = adx.Last;
|
var originalValue = adx.Last;
|
||||||
|
|
||||||
@@ -110,7 +112,10 @@ public class AdxTests
|
|||||||
for (int i = 0; i < bars.Count; i++)
|
for (int i = 0; i < bars.Count; i++)
|
||||||
{
|
{
|
||||||
adx.Update(bars[i]);
|
adx.Update(bars[i]);
|
||||||
if (adx.IsHot) break;
|
if (adx.IsHot)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.True(adx.IsHot);
|
Assert.True(adx.IsHot);
|
||||||
@@ -124,7 +129,9 @@ public class AdxTests
|
|||||||
var bars = gbm.Fetch(50, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
var bars = gbm.Fetch(50, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||||
|
|
||||||
for (int i = 0; i < 40; i++)
|
for (int i = 0; i < 40; i++)
|
||||||
|
{
|
||||||
adx.Update(bars[i]);
|
adx.Update(bars[i]);
|
||||||
|
}
|
||||||
|
|
||||||
var nanBar = new TBar(DateTime.UtcNow, double.NaN, double.NaN, double.NaN, double.NaN, 100);
|
var nanBar = new TBar(DateTime.UtcNow, double.NaN, double.NaN, double.NaN, double.NaN, 100);
|
||||||
var result = adx.Update(nanBar);
|
var result = adx.Update(nanBar);
|
||||||
@@ -140,7 +147,9 @@ public class AdxTests
|
|||||||
var bars = gbm.Fetch(50, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
var bars = gbm.Fetch(50, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||||
|
|
||||||
for (int i = 0; i < 40; i++)
|
for (int i = 0; i < 40; i++)
|
||||||
|
{
|
||||||
adx.Update(bars[i]);
|
adx.Update(bars[i]);
|
||||||
|
}
|
||||||
|
|
||||||
var infBar = new TBar(DateTime.UtcNow, double.PositiveInfinity, double.PositiveInfinity, 0, 100, 100);
|
var infBar = new TBar(DateTime.UtcNow, double.PositiveInfinity, double.PositiveInfinity, 0, 100, 100);
|
||||||
var result = adx.Update(infBar);
|
var result = adx.Update(infBar);
|
||||||
@@ -161,7 +170,10 @@ public class AdxTests
|
|||||||
// 2. Streaming Mode
|
// 2. Streaming Mode
|
||||||
var streamAdx = new Adx(14);
|
var streamAdx = new Adx(14);
|
||||||
for (int i = 0; i < bars.Count; i++)
|
for (int i = 0; i < bars.Count; i++)
|
||||||
|
{
|
||||||
streamAdx.Update(bars[i]);
|
streamAdx.Update(bars[i]);
|
||||||
|
}
|
||||||
|
|
||||||
double streamResult = streamAdx.Last.Value;
|
double streamResult = streamAdx.Last.Value;
|
||||||
|
|
||||||
Assert.Equal(expected, streamResult, 9);
|
Assert.Equal(expected, streamResult, 9);
|
||||||
@@ -235,4 +247,4 @@ public class AdxTests
|
|||||||
Assert.Throws<ArgumentException>(() => new Adx(0));
|
Assert.Throws<ArgumentException>(() => new Adx(0));
|
||||||
Assert.Throws<ArgumentException>(() => new Adx(-1));
|
Assert.Throws<ArgumentException>(() => new Adx(-1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+66
-16
@@ -92,7 +92,9 @@ public sealed class Adx : ITValuePublisher
|
|||||||
public Adx(int period)
|
public Adx(int period)
|
||||||
{
|
{
|
||||||
if (period <= 0)
|
if (period <= 0)
|
||||||
|
{
|
||||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||||
|
}
|
||||||
|
|
||||||
_period = period;
|
_period = period;
|
||||||
_decay = (period - 1.0) / 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 prevClose = double.IsFinite(_prevBar.Close) ? _prevBar.Close : high;
|
||||||
double prevHigh = double.IsFinite(_prevBar.High) ? _prevBar.High : high;
|
double prevHigh = double.IsFinite(_prevBar.High) ? _prevBar.High : high;
|
||||||
double prevLow = double.IsFinite(_prevBar.Low) ? _prevBar.Low : low;
|
double prevLow = double.IsFinite(_prevBar.Low) ? _prevBar.Low : low;
|
||||||
|
|
||||||
double hl = high - low;
|
double hl = high - low;
|
||||||
double hpc = Math.Abs(high - prevClose);
|
double hpc = Math.Abs(high - prevClose);
|
||||||
double lpc = Math.Abs(low - prevClose);
|
double lpc = Math.Abs(low - prevClose);
|
||||||
double tr = Math.Max(hl, Math.Max(hpc, lpc));
|
double tr = Math.Max(hl, Math.Max(hpc, lpc));
|
||||||
|
|
||||||
// Guard TR against non-finite values
|
// Guard TR against non-finite values
|
||||||
if (!double.IsFinite(tr)) tr = 0;
|
if (!double.IsFinite(tr))
|
||||||
|
{
|
||||||
|
tr = 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate DM using guarded values
|
// Calculate DM using guarded values
|
||||||
double dmPlus = 0;
|
double dmPlus = 0;
|
||||||
@@ -193,14 +198,25 @@ public sealed class Adx : ITValuePublisher
|
|||||||
double downMove = prevLow - low;
|
double downMove = prevLow - low;
|
||||||
|
|
||||||
// Guard moves against non-finite values
|
// Guard moves against non-finite values
|
||||||
if (!double.IsFinite(upMove)) upMove = 0;
|
if (!double.IsFinite(upMove))
|
||||||
if (!double.IsFinite(downMove)) downMove = 0;
|
{
|
||||||
|
upMove = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!double.IsFinite(downMove))
|
||||||
|
{
|
||||||
|
downMove = 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (upMove > downMove && upMove > 0)
|
if (upMove > downMove && upMove > 0)
|
||||||
|
{
|
||||||
dmPlus = upMove;
|
dmPlus = upMove;
|
||||||
|
}
|
||||||
|
|
||||||
if (downMove > upMove && downMove > 0)
|
if (downMove > upMove && downMove > 0)
|
||||||
|
{
|
||||||
dmMinus = downMove;
|
dmMinus = downMove;
|
||||||
|
}
|
||||||
|
|
||||||
if (isNew)
|
if (isNew)
|
||||||
{
|
{
|
||||||
@@ -251,8 +267,15 @@ public sealed class Adx : ITValuePublisher
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Guard against NaN/Infinity in DI calculations
|
// Guard against NaN/Infinity in DI calculations
|
||||||
if (!double.IsFinite(diPlus)) diPlus = 0;
|
if (!double.IsFinite(diPlus))
|
||||||
if (!double.IsFinite(diMinus)) diMinus = 0;
|
{
|
||||||
|
diPlus = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!double.IsFinite(diMinus))
|
||||||
|
{
|
||||||
|
diMinus = 0;
|
||||||
|
}
|
||||||
|
|
||||||
double diSum = diPlus + diMinus;
|
double diSum = diPlus + diMinus;
|
||||||
if (diSum > 1e-10)
|
if (diSum > 1e-10)
|
||||||
@@ -261,7 +284,10 @@ public sealed class Adx : ITValuePublisher
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Guard against NaN/Infinity in DX calculation
|
// Guard against NaN/Infinity in DX calculation
|
||||||
if (!double.IsFinite(dx)) dx = 0;
|
if (!double.IsFinite(dx))
|
||||||
|
{
|
||||||
|
dx = 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Smooth DX to get ADX
|
// Smooth DX to get ADX
|
||||||
if (_dxSamples < _period)
|
if (_dxSamples < _period)
|
||||||
@@ -281,17 +307,34 @@ public sealed class Adx : ITValuePublisher
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Final guard on ADX
|
// 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
|
// 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(diPlus))
|
||||||
if (!double.IsFinite(diMinus)) diMinus = double.IsFinite(DiMinus.Value) ? DiMinus.Value : 0;
|
{
|
||||||
|
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
|
// Final guard on ADX output - ensure we always return a finite value
|
||||||
double finalAdx = _adx;
|
double finalAdx = _adx;
|
||||||
if (!double.IsFinite(finalAdx)) finalAdx = _p_adx;
|
if (!double.IsFinite(finalAdx))
|
||||||
if (!double.IsFinite(finalAdx)) finalAdx = 0;
|
{
|
||||||
|
finalAdx = _p_adx;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!double.IsFinite(finalAdx))
|
||||||
|
{
|
||||||
|
finalAdx = 0;
|
||||||
|
}
|
||||||
|
|
||||||
DiPlus = new TValue(input.Time, diPlus);
|
DiPlus = new TValue(input.Time, diPlus);
|
||||||
DiMinus = new TValue(input.Time, diMinus);
|
DiMinus = new TValue(input.Time, diMinus);
|
||||||
@@ -309,7 +352,10 @@ public sealed class Adx : ITValuePublisher
|
|||||||
|
|
||||||
public TSeries Update(TBarSeries source)
|
public TSeries Update(TBarSeries source)
|
||||||
{
|
{
|
||||||
if (source.Count == 0) return new TSeries([], []);
|
if (source.Count == 0)
|
||||||
|
{
|
||||||
|
return new TSeries([], []);
|
||||||
|
}
|
||||||
|
|
||||||
var len = source.Count;
|
var len = source.Count;
|
||||||
var v = new double[len];
|
var v = new double[len];
|
||||||
@@ -450,7 +496,11 @@ public sealed class Adx : ITValuePublisher
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static TSeries Batch(TBarSeries source, int period)
|
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 len = source.Count;
|
||||||
var v = new double[len];
|
var v = new double[len];
|
||||||
Calculate(source.High.Values, source.Low.Values, source.Close.Values, period, v);
|
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]);
|
return new TSeries(tList, [.. v]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,9 @@ public sealed class Adxr : ITValuePublisher
|
|||||||
public Adxr(int period)
|
public Adxr(int period)
|
||||||
{
|
{
|
||||||
if (period <= 0)
|
if (period <= 0)
|
||||||
|
{
|
||||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||||
|
}
|
||||||
|
|
||||||
_period = period;
|
_period = period;
|
||||||
Name = $"Adxr({period})";
|
Name = $"Adxr({period})";
|
||||||
@@ -126,7 +128,10 @@ public sealed class Adxr : ITValuePublisher
|
|||||||
|
|
||||||
public TSeries Update(TBarSeries source)
|
public TSeries Update(TBarSeries source)
|
||||||
{
|
{
|
||||||
if (source.Count == 0) return new TSeries([], []);
|
if (source.Count == 0)
|
||||||
|
{
|
||||||
|
return new TSeries([], []);
|
||||||
|
}
|
||||||
|
|
||||||
int len = source.Count;
|
int len = source.Count;
|
||||||
var v = new double[len];
|
var v = new double[len];
|
||||||
@@ -205,14 +210,19 @@ public sealed class Adxr : ITValuePublisher
|
|||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (rentedAdx != null)
|
if (rentedAdx != null)
|
||||||
|
{
|
||||||
ArrayPool<double>.Shared.Return(rentedAdx);
|
ArrayPool<double>.Shared.Return(rentedAdx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static TSeries Batch(TBarSeries source, int period)
|
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;
|
int len = source.Count;
|
||||||
var v = new double[len];
|
var v = new double[len];
|
||||||
@@ -228,4 +238,4 @@ public sealed class Adxr : ITValuePublisher
|
|||||||
|
|
||||||
return new TSeries(tList, [.. v]);
|
return new TSeries(tList, [.. v]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -388,7 +388,9 @@ public class AmatTests
|
|||||||
for (int i = warmup; i < source.Length; i++)
|
for (int i = warmup; i < source.Length; i++)
|
||||||
{
|
{
|
||||||
if (Math.Abs(tseriesResult[i].Value - trend[i]) < 0.01)
|
if (Math.Abs(tseriesResult[i].Value - trend[i]) < 0.01)
|
||||||
|
{
|
||||||
matched++;
|
matched++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// At least 95% of values after warmup should match
|
// At least 95% of values after warmup should match
|
||||||
double matchRate = (double)matched / (source.Length - warmup);
|
double matchRate = (double)matched / (source.Length - warmup);
|
||||||
|
|||||||
@@ -373,7 +373,7 @@ public sealed class AmatValidationTests : IDisposable
|
|||||||
|
|
||||||
double trendMatchRate = (double)trendMatchCount / totalCount;
|
double trendMatchRate = (double)trendMatchCount / totalCount;
|
||||||
double strengthMatchRate = (double)strengthMatchCount / totalCount;
|
double strengthMatchRate = (double)strengthMatchCount / totalCount;
|
||||||
|
|
||||||
Assert.True(trendMatchRate > 0.95, $"Expected >95% trend match rate after warmup, got {trendMatchRate:P2}");
|
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}");
|
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}%");
|
_output.WriteLine($"Period combination ({fastPeriod}, {slowPeriod}) validated: Trend={amat.Last.Value}, Strength={amat.Strength.Value:F2}%");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,11 +123,19 @@ public sealed class Amat : ITValuePublisher, IDisposable
|
|||||||
public Amat(int fastPeriod = 10, int slowPeriod = 50)
|
public Amat(int fastPeriod = 10, int slowPeriod = 50)
|
||||||
{
|
{
|
||||||
if (fastPeriod <= 0)
|
if (fastPeriod <= 0)
|
||||||
|
{
|
||||||
throw new ArgumentException("Fast period must be greater than 0", nameof(fastPeriod));
|
throw new ArgumentException("Fast period must be greater than 0", nameof(fastPeriod));
|
||||||
|
}
|
||||||
|
|
||||||
if (slowPeriod <= 0)
|
if (slowPeriod <= 0)
|
||||||
|
{
|
||||||
throw new ArgumentException("Slow period must be greater than 0", nameof(slowPeriod));
|
throw new ArgumentException("Slow period must be greater than 0", nameof(slowPeriod));
|
||||||
|
}
|
||||||
|
|
||||||
if (fastPeriod >= slowPeriod)
|
if (fastPeriod >= slowPeriod)
|
||||||
|
{
|
||||||
throw new ArgumentException("Fast period must be less than slow period", nameof(fastPeriod));
|
throw new ArgumentException("Fast period must be less than slow period", nameof(fastPeriod));
|
||||||
|
}
|
||||||
|
|
||||||
_fastAlpha = 2.0 / (fastPeriod + 1);
|
_fastAlpha = 2.0 / (fastPeriod + 1);
|
||||||
_slowAlpha = 2.0 / (slowPeriod + 1);
|
_slowAlpha = 2.0 / (slowPeriod + 1);
|
||||||
@@ -314,7 +322,10 @@ public sealed class Amat : ITValuePublisher, IDisposable
|
|||||||
/// <returns>Series of trend values</returns>
|
/// <returns>Series of trend values</returns>
|
||||||
public TSeries Update(TSeries source)
|
public TSeries Update(TSeries source)
|
||||||
{
|
{
|
||||||
if (source.Count == 0) return [];
|
if (source.Count == 0)
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
int len = source.Count;
|
int len = source.Count;
|
||||||
var t = new List<long>(len);
|
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)
|
private static double GetCompensatedValue(double ema, double e, bool isCompensated)
|
||||||
{
|
{
|
||||||
if (isCompensated || e <= COMPENSATOR_THRESHOLD)
|
if (isCompensated || e <= COMPENSATOR_THRESHOLD)
|
||||||
|
{
|
||||||
return ema;
|
return ema;
|
||||||
|
}
|
||||||
|
|
||||||
return ema / (1.0 - e);
|
return ema / (1.0 - e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -358,7 +372,9 @@ public sealed class Amat : ITValuePublisher, IDisposable
|
|||||||
e *= decay;
|
e *= decay;
|
||||||
|
|
||||||
if (!isHot && e <= COVERAGE_THRESHOLD)
|
if (!isHot && e <= COVERAGE_THRESHOLD)
|
||||||
|
{
|
||||||
isHot = true;
|
isHot = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (e <= COMPENSATOR_THRESHOLD)
|
if (e <= COMPENSATOR_THRESHOLD)
|
||||||
{
|
{
|
||||||
@@ -391,18 +407,35 @@ public sealed class Amat : ITValuePublisher, IDisposable
|
|||||||
int fastPeriod = 10, int slowPeriod = 50)
|
int fastPeriod = 10, int slowPeriod = 50)
|
||||||
{
|
{
|
||||||
if (source.Length != trend.Length)
|
if (source.Length != trend.Length)
|
||||||
|
{
|
||||||
throw new ArgumentException("Source and trend must have the same length", nameof(trend));
|
throw new ArgumentException("Source and trend must have the same length", nameof(trend));
|
||||||
|
}
|
||||||
|
|
||||||
if (source.Length != strength.Length)
|
if (source.Length != strength.Length)
|
||||||
|
{
|
||||||
throw new ArgumentException("Source and strength must have the same length", nameof(strength));
|
throw new ArgumentException("Source and strength must have the same length", nameof(strength));
|
||||||
|
}
|
||||||
|
|
||||||
if (fastPeriod <= 0)
|
if (fastPeriod <= 0)
|
||||||
|
{
|
||||||
throw new ArgumentException("Fast period must be greater than 0", nameof(fastPeriod));
|
throw new ArgumentException("Fast period must be greater than 0", nameof(fastPeriod));
|
||||||
|
}
|
||||||
|
|
||||||
if (slowPeriod <= 0)
|
if (slowPeriod <= 0)
|
||||||
|
{
|
||||||
throw new ArgumentException("Slow period must be greater than 0", nameof(slowPeriod));
|
throw new ArgumentException("Slow period must be greater than 0", nameof(slowPeriod));
|
||||||
|
}
|
||||||
|
|
||||||
if (fastPeriod >= slowPeriod)
|
if (fastPeriod >= slowPeriod)
|
||||||
|
{
|
||||||
throw new ArgumentException("Fast period must be less than slow period", nameof(fastPeriod));
|
throw new ArgumentException("Fast period must be less than slow period", nameof(fastPeriod));
|
||||||
|
}
|
||||||
|
|
||||||
int len = source.Length;
|
int len = source.Length;
|
||||||
if (len == 0) return;
|
if (len == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
double fastAlpha = 2.0 / (fastPeriod + 1);
|
double fastAlpha = 2.0 / (fastPeriod + 1);
|
||||||
double slowAlpha = 2.0 / (slowPeriod + 1);
|
double slowAlpha = 2.0 / (slowPeriod + 1);
|
||||||
@@ -483,16 +516,30 @@ public sealed class Amat : ITValuePublisher, IDisposable
|
|||||||
int fastPeriod = 10, int slowPeriod = 50)
|
int fastPeriod = 10, int slowPeriod = 50)
|
||||||
{
|
{
|
||||||
if (source.Length != trend.Length)
|
if (source.Length != trend.Length)
|
||||||
|
{
|
||||||
throw new ArgumentException("Source and trend must have the same length", nameof(trend));
|
throw new ArgumentException("Source and trend must have the same length", nameof(trend));
|
||||||
|
}
|
||||||
|
|
||||||
if (fastPeriod <= 0)
|
if (fastPeriod <= 0)
|
||||||
|
{
|
||||||
throw new ArgumentException("Fast period must be greater than 0", nameof(fastPeriod));
|
throw new ArgumentException("Fast period must be greater than 0", nameof(fastPeriod));
|
||||||
|
}
|
||||||
|
|
||||||
if (slowPeriod <= 0)
|
if (slowPeriod <= 0)
|
||||||
|
{
|
||||||
throw new ArgumentException("Slow period must be greater than 0", nameof(slowPeriod));
|
throw new ArgumentException("Slow period must be greater than 0", nameof(slowPeriod));
|
||||||
|
}
|
||||||
|
|
||||||
if (fastPeriod >= slowPeriod)
|
if (fastPeriod >= slowPeriod)
|
||||||
|
{
|
||||||
throw new ArgumentException("Fast period must be less than slow period", nameof(fastPeriod));
|
throw new ArgumentException("Fast period must be less than slow period", nameof(fastPeriod));
|
||||||
|
}
|
||||||
|
|
||||||
int len = source.Length;
|
int len = source.Length;
|
||||||
if (len == 0) return;
|
if (len == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
double fastAlpha = 2.0 / (fastPeriod + 1);
|
double fastAlpha = 2.0 / (fastPeriod + 1);
|
||||||
double slowAlpha = 2.0 / (slowPeriod + 1);
|
double slowAlpha = 2.0 / (slowPeriod + 1);
|
||||||
@@ -575,4 +622,4 @@ public sealed class Amat : ITValuePublisher, IDisposable
|
|||||||
var amat = new Amat(fastPeriod, slowPeriod);
|
var amat = new Amat(fastPeriod, slowPeriod);
|
||||||
return amat.Update(source);
|
return amat.Update(source);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,9 @@ public sealed class Aroon : ITValuePublisher
|
|||||||
public Aroon(int period)
|
public Aroon(int period)
|
||||||
{
|
{
|
||||||
if (period <= 0)
|
if (period <= 0)
|
||||||
|
{
|
||||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||||
|
}
|
||||||
|
|
||||||
_period = period;
|
_period = period;
|
||||||
Name = $"Aroon({period})";
|
Name = $"Aroon({period})";
|
||||||
@@ -163,7 +165,10 @@ public sealed class Aroon : ITValuePublisher
|
|||||||
|
|
||||||
public TSeries Update(TBarSeries source)
|
public TSeries Update(TBarSeries source)
|
||||||
{
|
{
|
||||||
if (source.Count == 0) return new TSeries([], []);
|
if (source.Count == 0)
|
||||||
|
{
|
||||||
|
return new TSeries([], []);
|
||||||
|
}
|
||||||
|
|
||||||
int len = source.Count;
|
int len = source.Count;
|
||||||
var v = new double[len];
|
var v = new double[len];
|
||||||
@@ -288,7 +293,10 @@ public sealed class Aroon : ITValuePublisher
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static TSeries Batch(TBarSeries source, int period)
|
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;
|
int len = source.Count;
|
||||||
var v = new double[len];
|
var v = new double[len];
|
||||||
@@ -304,4 +312,4 @@ public sealed class Aroon : ITValuePublisher
|
|||||||
|
|
||||||
return new TSeries(tList, [.. v]);
|
return new TSeries(tList, [.. v]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,9 @@ public sealed class AroonOsc : ITValuePublisher
|
|||||||
public AroonOsc(int period)
|
public AroonOsc(int period)
|
||||||
{
|
{
|
||||||
if (period <= 0)
|
if (period <= 0)
|
||||||
|
{
|
||||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||||
|
}
|
||||||
|
|
||||||
_period = period;
|
_period = period;
|
||||||
Name = $"AroonOsc({period})";
|
Name = $"AroonOsc({period})";
|
||||||
@@ -148,7 +150,10 @@ public sealed class AroonOsc : ITValuePublisher
|
|||||||
|
|
||||||
public TSeries Update(TBarSeries source)
|
public TSeries Update(TBarSeries source)
|
||||||
{
|
{
|
||||||
if (source.Count == 0) return new TSeries([], []);
|
if (source.Count == 0)
|
||||||
|
{
|
||||||
|
return new TSeries([], []);
|
||||||
|
}
|
||||||
|
|
||||||
int len = source.Count;
|
int len = source.Count;
|
||||||
var v = new double[len];
|
var v = new double[len];
|
||||||
@@ -189,7 +194,10 @@ public sealed class AroonOsc : ITValuePublisher
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static TSeries Batch(TBarSeries source, int period)
|
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;
|
int len = source.Count;
|
||||||
var v = new double[len];
|
var v = new double[len];
|
||||||
@@ -205,4 +213,4 @@ public sealed class AroonOsc : ITValuePublisher
|
|||||||
|
|
||||||
return new TSeries(tList, [.. v]);
|
return new TSeries(tList, [.. v]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,7 +68,9 @@ public class DmxTests
|
|||||||
var bars = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
var bars = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||||
|
|
||||||
for (int i = 0; i < 50; i++)
|
for (int i = 0; i < 50; i++)
|
||||||
|
{
|
||||||
dmx.Update(bars[i]);
|
dmx.Update(bars[i]);
|
||||||
|
}
|
||||||
|
|
||||||
var originalValue = dmx.Last;
|
var originalValue = dmx.Last;
|
||||||
|
|
||||||
@@ -114,7 +116,9 @@ public class DmxTests
|
|||||||
var bars = gbm.Fetch(50, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
var bars = gbm.Fetch(50, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||||
|
|
||||||
for (int i = 0; i < 30; i++)
|
for (int i = 0; i < 30; i++)
|
||||||
|
{
|
||||||
dmx.Update(bars[i]);
|
dmx.Update(bars[i]);
|
||||||
|
}
|
||||||
|
|
||||||
var nanBar = new TBar(DateTime.UtcNow, double.NaN, double.NaN, double.NaN, double.NaN, 100);
|
var nanBar = new TBar(DateTime.UtcNow, double.NaN, double.NaN, double.NaN, double.NaN, 100);
|
||||||
var result = dmx.Update(nanBar);
|
var result = dmx.Update(nanBar);
|
||||||
@@ -130,7 +134,9 @@ public class DmxTests
|
|||||||
var bars = gbm.Fetch(50, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
var bars = gbm.Fetch(50, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||||
|
|
||||||
for (int i = 0; i < 30; i++)
|
for (int i = 0; i < 30; i++)
|
||||||
|
{
|
||||||
dmx.Update(bars[i]);
|
dmx.Update(bars[i]);
|
||||||
|
}
|
||||||
|
|
||||||
var infBar = new TBar(DateTime.UtcNow, double.PositiveInfinity, double.PositiveInfinity, 0, 100, 100);
|
var infBar = new TBar(DateTime.UtcNow, double.PositiveInfinity, double.PositiveInfinity, 0, 100, 100);
|
||||||
var result = dmx.Update(infBar);
|
var result = dmx.Update(infBar);
|
||||||
@@ -151,7 +157,10 @@ public class DmxTests
|
|||||||
// 2. Streaming Mode
|
// 2. Streaming Mode
|
||||||
var streamDmx = new Dmx(14);
|
var streamDmx = new Dmx(14);
|
||||||
for (int i = 0; i < bars.Count; i++)
|
for (int i = 0; i < bars.Count; i++)
|
||||||
|
{
|
||||||
streamDmx.Update(bars[i]);
|
streamDmx.Update(bars[i]);
|
||||||
|
}
|
||||||
|
|
||||||
double streamResult = streamDmx.Last.Value;
|
double streamResult = streamDmx.Last.Value;
|
||||||
|
|
||||||
Assert.Equal(expected, streamResult, 9);
|
Assert.Equal(expected, streamResult, 9);
|
||||||
|
|||||||
+19
-1
@@ -109,10 +109,14 @@ public sealed class Dmx : ITValuePublisher
|
|||||||
double downMove = _prevBar.Low - input.Low;
|
double downMove = _prevBar.Low - input.Low;
|
||||||
|
|
||||||
if (upMove > downMove && upMove > 0)
|
if (upMove > downMove && upMove > 0)
|
||||||
|
{
|
||||||
dmPlusRaw = upMove;
|
dmPlusRaw = upMove;
|
||||||
|
}
|
||||||
|
|
||||||
if (downMove > upMove && downMove > 0)
|
if (downMove > upMove && downMove > 0)
|
||||||
|
{
|
||||||
dmMinusRaw = downMove;
|
dmMinusRaw = downMove;
|
||||||
|
}
|
||||||
|
|
||||||
double tr1 = input.High - input.Low;
|
double tr1 = input.High - input.Low;
|
||||||
double tr2 = Math.Abs(input.High - _prevBar.Close);
|
double tr2 = Math.Abs(input.High - _prevBar.Close);
|
||||||
@@ -147,7 +151,9 @@ public sealed class Dmx : ITValuePublisher
|
|||||||
{
|
{
|
||||||
int count = source.Count;
|
int count = source.Count;
|
||||||
if (count == 0)
|
if (count == 0)
|
||||||
|
{
|
||||||
return [];
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
var t = new List<long>(count);
|
var t = new List<long>(count);
|
||||||
var v = new List<double>(count);
|
var v = new List<double>(count);
|
||||||
@@ -182,13 +188,19 @@ public sealed class Dmx : ITValuePublisher
|
|||||||
{
|
{
|
||||||
int len = high.Length;
|
int len = high.Length;
|
||||||
if (len == 0)
|
if (len == 0)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (low.Length != len || close.Length != len || destination.Length != len)
|
if (low.Length != len || close.Length != len || destination.Length != len)
|
||||||
|
{
|
||||||
throw new ArgumentException("All input spans must have the same length", nameof(destination));
|
throw new ArgumentException("All input spans must have the same length", nameof(destination));
|
||||||
|
}
|
||||||
|
|
||||||
if (period <= 0)
|
if (period <= 0)
|
||||||
|
{
|
||||||
throw new ArgumentException("Period must be greater than zero.", nameof(period));
|
throw new ArgumentException("Period must be greater than zero.", nameof(period));
|
||||||
|
}
|
||||||
|
|
||||||
// Use single ArrayPool rent with slicing for better cache locality and fewer allocations
|
// 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
|
// 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;
|
double dmMinusRaw = 0.0;
|
||||||
|
|
||||||
if (upMove > downMove && upMove > 0.0)
|
if (upMove > downMove && upMove > 0.0)
|
||||||
|
{
|
||||||
dmPlusRaw = upMove;
|
dmPlusRaw = upMove;
|
||||||
|
}
|
||||||
|
|
||||||
if (downMove > upMove && downMove > 0.0)
|
if (downMove > upMove && downMove > 0.0)
|
||||||
|
{
|
||||||
dmMinusRaw = downMove;
|
dmMinusRaw = downMove;
|
||||||
|
}
|
||||||
|
|
||||||
double tr1 = h - l;
|
double tr1 = h - l;
|
||||||
double tr2 = Math.Abs(h - pc);
|
double tr2 = Math.Abs(h - pc);
|
||||||
@@ -275,7 +291,9 @@ public sealed class Dmx : ITValuePublisher
|
|||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (rented != null)
|
if (rented != null)
|
||||||
|
{
|
||||||
ArrayPool<double>.Shared.Return(rented);
|
ArrayPool<double>.Shared.Return(rented);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,4 +302,4 @@ public sealed class Dmx : ITValuePublisher
|
|||||||
var dmx = new Dmx(period);
|
var dmx = new Dmx(period);
|
||||||
return dmx.Update(source);
|
return dmx.Update(source);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -265,4 +265,4 @@ public sealed class Super : ITValuePublisher
|
|||||||
var indicator = new Super(period, multiplier);
|
var indicator = new Super(period, multiplier);
|
||||||
return indicator.Update(source);
|
return indicator.Update(source);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user