mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-24 13:38:05 +00:00
python wrapper
This commit is contained in:
@@ -8,7 +8,7 @@ Channels define dynamic support and resistance. Upper band shows where price ten
|
||||
|
||||
| Indicator | Full Name | Description |
|
||||
| :--- | :--- | :--- |
|
||||
| [ABBER](abber/Abber.md) | Aberration Bands | Absolute deviation-based volatility bands. More robust than standard deviation. |
|
||||
| [ABERR](aberr/Aberr.md) | Aberration Bands | Absolute deviation-based volatility bands. More robust than standard deviation. |
|
||||
| [ACCBANDS](accbands/Accbands.md) | Acceleration Bands | Volatility-based adaptive channel by Price Headley. Width adapts to momentum. |
|
||||
| [APCHANNEL](apchannel/Apchannel.md) | Adaptive Price Channel | Channel based on adaptive moving average with volatility bands. |
|
||||
| [APZ](apz/Apz.md) | Adaptive Price Zone | Double-smoothed EMA volatility channel by Lee Leibfarth. Adapts to recent volatility. |
|
||||
|
||||
+18
-18
@@ -3,18 +3,18 @@ using Xunit;
|
||||
|
||||
namespace QuanTAlib.Tests;
|
||||
|
||||
public class AbberQuantowerTests
|
||||
public class AberrQuantowerTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_SetsDefaults()
|
||||
{
|
||||
var indicator = new AbberIndicator();
|
||||
var indicator = new AberrIndicator();
|
||||
|
||||
Assert.Equal(20, indicator.Period);
|
||||
Assert.Equal(2.0, indicator.Multiplier);
|
||||
Assert.Equal(SourceType.Close, indicator.Source);
|
||||
Assert.True(indicator.ShowColdValues);
|
||||
Assert.Equal("ABBER - Aberration Bands", indicator.Name);
|
||||
Assert.Equal("ABERR - Aberration Bands", indicator.Name);
|
||||
Assert.False(indicator.SeparateWindow);
|
||||
Assert.True(indicator.OnBackGround);
|
||||
}
|
||||
@@ -22,14 +22,14 @@ public class AbberQuantowerTests
|
||||
[Fact]
|
||||
public void MinHistoryDepths_MatchesPeriod()
|
||||
{
|
||||
var indicator = new AbberIndicator { Period = 25 };
|
||||
var indicator = new AberrIndicator { Period = 25 };
|
||||
Assert.Equal(25, indicator.MinHistoryDepths);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShortName_IncludesParameters()
|
||||
{
|
||||
var indicator = new AbberIndicator { Period = 15, Multiplier = 1.5 };
|
||||
var indicator = new AberrIndicator { Period = 15, Multiplier = 1.5 };
|
||||
Assert.Contains("15", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("1.5", indicator.ShortName, StringComparison.Ordinal);
|
||||
}
|
||||
@@ -37,7 +37,7 @@ public class AbberQuantowerTests
|
||||
[Fact]
|
||||
public void Initialize_CreatesThreeLineSeries()
|
||||
{
|
||||
var indicator = new AbberIndicator { Period = 14 };
|
||||
var indicator = new AberrIndicator { Period = 14 };
|
||||
indicator.Initialize();
|
||||
|
||||
Assert.Equal(3, indicator.LinesSeries.Count);
|
||||
@@ -49,7 +49,7 @@ public class AbberQuantowerTests
|
||||
[Fact]
|
||||
public void ProcessUpdate_HistoricalBar_ComputesValue()
|
||||
{
|
||||
var indicator = new AbberIndicator { Period = 3 };
|
||||
var indicator = new AberrIndicator { Period = 3 };
|
||||
indicator.Initialize();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
@@ -67,7 +67,7 @@ public class AbberQuantowerTests
|
||||
[Fact]
|
||||
public void ProcessUpdate_NewBar_ComputesValue()
|
||||
{
|
||||
var indicator = new AbberIndicator { Period = 3 };
|
||||
var indicator = new AberrIndicator { Period = 3 };
|
||||
indicator.Initialize();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
@@ -83,7 +83,7 @@ public class AbberQuantowerTests
|
||||
[Fact]
|
||||
public void ProcessUpdate_NewTick_ProcessesWithoutError()
|
||||
{
|
||||
var indicator = new AbberIndicator { Period = 5 };
|
||||
var indicator = new AberrIndicator { Period = 5 };
|
||||
indicator.Initialize();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
@@ -98,7 +98,7 @@ public class AbberQuantowerTests
|
||||
[Fact]
|
||||
public void ProcessUpdate_EmptyData_HandlesGracefully()
|
||||
{
|
||||
var indicator = new AbberIndicator { Period = 5 };
|
||||
var indicator = new AberrIndicator { Period = 5 };
|
||||
indicator.Initialize();
|
||||
|
||||
var args = new UpdateArgs(UpdateReason.NewBar);
|
||||
@@ -110,7 +110,7 @@ public class AbberQuantowerTests
|
||||
[Fact]
|
||||
public void MultipleUpdates_ProducesCorrectSequence()
|
||||
{
|
||||
var indicator = new AbberIndicator { Period = 5 };
|
||||
var indicator = new AberrIndicator { Period = 5 };
|
||||
indicator.Initialize();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
@@ -136,7 +136,7 @@ public class AbberQuantowerTests
|
||||
[Fact]
|
||||
public void BandRelationship_UpperAboveLowerBelowMiddle()
|
||||
{
|
||||
var indicator = new AbberIndicator { Period = 5, Multiplier = 2.0 };
|
||||
var indicator = new AberrIndicator { Period = 5, Multiplier = 2.0 };
|
||||
indicator.Initialize();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
@@ -166,11 +166,11 @@ public class AbberQuantowerTests
|
||||
var prices = new[] { 100, 105, 98, 110, 95, 115, 92, 118, 90, 120 };
|
||||
|
||||
// Narrow bands with multiplier 1.0
|
||||
var narrowIndicator = new AbberIndicator { Period = 5, Multiplier = 1.0 };
|
||||
var narrowIndicator = new AberrIndicator { Period = 5, Multiplier = 1.0 };
|
||||
narrowIndicator.Initialize();
|
||||
|
||||
// Wide bands with multiplier 3.0
|
||||
var wideIndicator = new AbberIndicator { Period = 5, Multiplier = 3.0 };
|
||||
var wideIndicator = new AberrIndicator { Period = 5, Multiplier = 3.0 };
|
||||
wideIndicator.Initialize();
|
||||
|
||||
for (int i = 0; i < prices.Length; i++)
|
||||
@@ -191,7 +191,7 @@ public class AbberQuantowerTests
|
||||
[Fact]
|
||||
public void SourceType_CanBeChanged()
|
||||
{
|
||||
var indicator = new AbberIndicator { Source = SourceType.Close };
|
||||
var indicator = new AberrIndicator { Source = SourceType.Close };
|
||||
Assert.Equal(SourceType.Close, indicator.Source);
|
||||
|
||||
indicator.Source = SourceType.HLC3;
|
||||
@@ -201,7 +201,7 @@ public class AbberQuantowerTests
|
||||
[Fact]
|
||||
public void ShowColdValues_CanBeToggled()
|
||||
{
|
||||
var indicator = new AbberIndicator { ShowColdValues = true };
|
||||
var indicator = new AberrIndicator { ShowColdValues = true };
|
||||
Assert.True(indicator.ShowColdValues);
|
||||
|
||||
indicator.ShowColdValues = false;
|
||||
@@ -211,8 +211,8 @@ public class AbberQuantowerTests
|
||||
[Fact]
|
||||
public void SourceCodeLink_IsValid()
|
||||
{
|
||||
var indicator = new AbberIndicator();
|
||||
var indicator = new AberrIndicator();
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.Contains("Abber.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
Assert.Contains("Aberr.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
}
|
||||
}
|
||||
@@ -5,12 +5,12 @@ using TradingPlatform.BusinessLayer;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// ABBER (Aberration Bands) - Volatility bands using absolute deviation
|
||||
/// ABERR (Aberration Bands) - Volatility bands using absolute deviation
|
||||
/// A Quantower indicator adapter that provides three bands based on mean absolute deviation
|
||||
/// rather than standard deviation, making it more robust to outliers than Bollinger Bands.
|
||||
/// </summary>
|
||||
[SkipLocalsInit]
|
||||
public sealed class AbberIndicator : Indicator, IWatchlistIndicator
|
||||
public sealed class AberrIndicator : Indicator, IWatchlistIndicator
|
||||
{
|
||||
[InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)]
|
||||
public int Period { get; set; } = 20;
|
||||
@@ -24,7 +24,7 @@ public sealed class AbberIndicator : Indicator, IWatchlistIndicator
|
||||
"Low", SourceType.Low,
|
||||
"Close", SourceType.Close,
|
||||
"HL/2 (Median)", SourceType.HL2,
|
||||
"OC/2 (Midpoint)", SourceType.OC2,
|
||||
"Midbody (O+C)/2", SourceType.Midbody,
|
||||
"OHL/3 (Mean)", SourceType.OHL3,
|
||||
"HLC/3 (Typical)", SourceType.HLC3,
|
||||
"OHLC/4 (Average)", SourceType.OHLC4,
|
||||
@@ -35,7 +35,7 @@ public sealed class AbberIndicator : Indicator, IWatchlistIndicator
|
||||
[InputParameter("Show cold values", sortIndex: 21)]
|
||||
public bool ShowColdValues { get; set; } = true;
|
||||
|
||||
private Abber? _abber;
|
||||
private Aberr? _aberr;
|
||||
private Func<IHistoryItem, double>? _selector;
|
||||
private readonly LineSeries _middleSeries;
|
||||
private readonly LineSeries _upperSeries;
|
||||
@@ -43,14 +43,14 @@ public sealed class AbberIndicator : Indicator, IWatchlistIndicator
|
||||
|
||||
public int MinHistoryDepths => Period;
|
||||
|
||||
public override string ShortName => $"ABBER {Period},{Multiplier:F1}";
|
||||
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/channels/abber/Abber.Quantower.cs";
|
||||
public override string ShortName => $"ABERR {Period},{Multiplier:F1}";
|
||||
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/channels/aberr/Aberr.Quantower.cs";
|
||||
|
||||
public AbberIndicator()
|
||||
public AberrIndicator()
|
||||
{
|
||||
OnBackGround = true;
|
||||
SeparateWindow = false;
|
||||
Name = "ABBER - Aberration Bands";
|
||||
Name = "ABERR - Aberration Bands";
|
||||
Description = "Volatility bands using absolute deviation (robust to outliers)";
|
||||
|
||||
_middleSeries = new LineSeries(name: "Middle", color: Color.FromArgb(255, 128, 128), width: 2, style: LineStyle.Solid);
|
||||
@@ -65,7 +65,7 @@ public sealed class AbberIndicator : Indicator, IWatchlistIndicator
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void OnInit()
|
||||
{
|
||||
_abber = new Abber(Period, Multiplier);
|
||||
_aberr = new Aberr(Period, Multiplier);
|
||||
_selector = Source.GetPriceSelector();
|
||||
base.OnInit();
|
||||
}
|
||||
@@ -73,7 +73,7 @@ public sealed class AbberIndicator : Indicator, IWatchlistIndicator
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void OnUpdate(UpdateArgs args)
|
||||
{
|
||||
if (HistoricalData.Count == 0 || _abber is null || _selector is null)
|
||||
if (HistoricalData.Count == 0 || _aberr is null || _selector is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -82,10 +82,11 @@ public sealed class AbberIndicator : Indicator, IWatchlistIndicator
|
||||
double value = _selector(item);
|
||||
TValue input = new(item.TimeLeft, value);
|
||||
|
||||
_abber.Update(input, args.IsNewBar());
|
||||
_aberr.Update(input, args.IsNewBar());
|
||||
|
||||
_middleSeries.SetValue(_abber.Last.Value, _abber.IsHot, ShowColdValues);
|
||||
_upperSeries.SetValue(_abber.Upper.Value, _abber.IsHot, ShowColdValues);
|
||||
_lowerSeries.SetValue(_abber.Lower.Value, _abber.IsHot, ShowColdValues);
|
||||
_middleSeries.SetValue(_aberr.Last.Value, _aberr.IsHot, ShowColdValues);
|
||||
_upperSeries.SetValue(_aberr.Upper.Value, _aberr.IsHot, ShowColdValues);
|
||||
_lowerSeries.SetValue(_aberr.Lower.Value, _aberr.IsHot, ShowColdValues);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,196 +1,196 @@
|
||||
namespace QuanTAlib.Tests;
|
||||
|
||||
public class AbberTests
|
||||
public class AberrTests
|
||||
{
|
||||
[Fact]
|
||||
public void Abber_Constructor_ValidatesInput()
|
||||
public void Aberr_Constructor_ValidatesInput()
|
||||
{
|
||||
// Period validation
|
||||
Assert.Throws<ArgumentException>(() => new Abber(0));
|
||||
Assert.Throws<ArgumentException>(() => new Abber(-1));
|
||||
Assert.Throws<ArgumentException>(() => new Aberr(0));
|
||||
Assert.Throws<ArgumentException>(() => new Aberr(-1));
|
||||
|
||||
// Multiplier validation
|
||||
Assert.Throws<ArgumentException>(() => new Abber(10, 0));
|
||||
Assert.Throws<ArgumentException>(() => new Abber(10, -1));
|
||||
Assert.Throws<ArgumentException>(() => new Aberr(10, 0));
|
||||
Assert.Throws<ArgumentException>(() => new Aberr(10, -1));
|
||||
|
||||
// Valid construction
|
||||
var abber = new Abber(10);
|
||||
Assert.NotNull(abber);
|
||||
var aberr = new Aberr(10);
|
||||
Assert.NotNull(aberr);
|
||||
|
||||
var abber2 = new Abber(20, 3.0);
|
||||
Assert.NotNull(abber2);
|
||||
var aberr2 = new Aberr(20, 3.0);
|
||||
Assert.NotNull(aberr2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Abber_Calc_ReturnsValue()
|
||||
public void Aberr_Calc_ReturnsValue()
|
||||
{
|
||||
var abber = new Abber(10);
|
||||
var aberr = new Aberr(10);
|
||||
|
||||
Assert.Equal(0, abber.Last.Value);
|
||||
Assert.Equal(0, abber.Upper.Value);
|
||||
Assert.Equal(0, abber.Lower.Value);
|
||||
Assert.Equal(0, aberr.Last.Value);
|
||||
Assert.Equal(0, aberr.Upper.Value);
|
||||
Assert.Equal(0, aberr.Lower.Value);
|
||||
|
||||
TValue result = abber.Update(new TValue(DateTime.UtcNow, 100));
|
||||
TValue result = aberr.Update(new TValue(DateTime.UtcNow, 100));
|
||||
|
||||
Assert.True(double.IsFinite(result.Value));
|
||||
Assert.Equal(result.Value, abber.Last.Value);
|
||||
Assert.True(double.IsFinite(abber.Upper.Value));
|
||||
Assert.True(double.IsFinite(abber.Lower.Value));
|
||||
Assert.Equal(result.Value, aberr.Last.Value);
|
||||
Assert.True(double.IsFinite(aberr.Upper.Value));
|
||||
Assert.True(double.IsFinite(aberr.Lower.Value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Abber_FirstValue_ReturnsExpected()
|
||||
public void Aberr_FirstValue_ReturnsExpected()
|
||||
{
|
||||
var abber = new Abber(10);
|
||||
var aberr = new Aberr(10);
|
||||
|
||||
// First value: source = 100
|
||||
// SMA(1) = 100, Deviation = |100 - 100| = 0, AvgDeviation = 0
|
||||
// Middle = 100, Upper = 100 + 0 = 100, Lower = 100 - 0 = 100
|
||||
abber.Update(new TValue(DateTime.UtcNow, 100));
|
||||
aberr.Update(new TValue(DateTime.UtcNow, 100));
|
||||
|
||||
Assert.Equal(100.0, abber.Last.Value, 1e-10);
|
||||
Assert.Equal(100.0, abber.Upper.Value, 1e-10);
|
||||
Assert.Equal(100.0, abber.Lower.Value, 1e-10);
|
||||
Assert.Equal(100.0, aberr.Last.Value, 1e-10);
|
||||
Assert.Equal(100.0, aberr.Upper.Value, 1e-10);
|
||||
Assert.Equal(100.0, aberr.Lower.Value, 1e-10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Abber_Calc_IsNew_AcceptsParameter()
|
||||
public void Aberr_Calc_IsNew_AcceptsParameter()
|
||||
{
|
||||
var abber = new Abber(10);
|
||||
var aberr = new Aberr(10);
|
||||
|
||||
abber.Update(new TValue(DateTime.UtcNow, 100), isNew: true);
|
||||
double value1 = abber.Last.Value;
|
||||
aberr.Update(new TValue(DateTime.UtcNow, 100), isNew: true);
|
||||
double value1 = aberr.Last.Value;
|
||||
|
||||
abber.Update(new TValue(DateTime.UtcNow, 110), isNew: true);
|
||||
double value2 = abber.Last.Value;
|
||||
aberr.Update(new TValue(DateTime.UtcNow, 110), isNew: true);
|
||||
double value2 = aberr.Last.Value;
|
||||
|
||||
// Values should change with new data
|
||||
Assert.NotEqual(value1, value2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Abber_Calc_IsNew_False_UpdatesValue()
|
||||
public void Aberr_Calc_IsNew_False_UpdatesValue()
|
||||
{
|
||||
var abber = new Abber(10);
|
||||
var aberr = new Aberr(10);
|
||||
|
||||
abber.Update(new TValue(DateTime.UtcNow, 100), isNew: true);
|
||||
abber.Update(new TValue(DateTime.UtcNow, 110), isNew: true);
|
||||
double beforeUpdate = abber.Last.Value;
|
||||
aberr.Update(new TValue(DateTime.UtcNow, 100), isNew: true);
|
||||
aberr.Update(new TValue(DateTime.UtcNow, 110), isNew: true);
|
||||
double beforeUpdate = aberr.Last.Value;
|
||||
|
||||
abber.Update(new TValue(DateTime.UtcNow, 120), isNew: false);
|
||||
double afterUpdate = abber.Last.Value;
|
||||
aberr.Update(new TValue(DateTime.UtcNow, 120), isNew: false);
|
||||
double afterUpdate = aberr.Last.Value;
|
||||
|
||||
// Update should change the value
|
||||
Assert.NotEqual(beforeUpdate, afterUpdate);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Abber_Reset_ClearsState()
|
||||
public void Aberr_Reset_ClearsState()
|
||||
{
|
||||
var abber = new Abber(10);
|
||||
var aberr = new Aberr(10);
|
||||
|
||||
abber.Update(new TValue(DateTime.UtcNow, 100));
|
||||
abber.Update(new TValue(DateTime.UtcNow, 105));
|
||||
double middleBefore = abber.Last.Value;
|
||||
aberr.Update(new TValue(DateTime.UtcNow, 100));
|
||||
aberr.Update(new TValue(DateTime.UtcNow, 105));
|
||||
double middleBefore = aberr.Last.Value;
|
||||
|
||||
abber.Reset();
|
||||
aberr.Reset();
|
||||
|
||||
Assert.Equal(0, abber.Last.Value);
|
||||
Assert.Equal(0, abber.Upper.Value);
|
||||
Assert.Equal(0, abber.Lower.Value);
|
||||
Assert.False(abber.IsHot);
|
||||
Assert.Equal(0, aberr.Last.Value);
|
||||
Assert.Equal(0, aberr.Upper.Value);
|
||||
Assert.Equal(0, aberr.Lower.Value);
|
||||
Assert.False(aberr.IsHot);
|
||||
|
||||
// After reset, should accept new values
|
||||
abber.Update(new TValue(DateTime.UtcNow, 50));
|
||||
Assert.NotEqual(0, abber.Last.Value);
|
||||
Assert.NotEqual(middleBefore, abber.Last.Value);
|
||||
aberr.Update(new TValue(DateTime.UtcNow, 50));
|
||||
Assert.NotEqual(0, aberr.Last.Value);
|
||||
Assert.NotEqual(middleBefore, aberr.Last.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Abber_Properties_Accessible()
|
||||
public void Aberr_Properties_Accessible()
|
||||
{
|
||||
var abber = new Abber(10, 2.5);
|
||||
var aberr = new Aberr(10, 2.5);
|
||||
|
||||
Assert.Equal(0, abber.Last.Value);
|
||||
Assert.False(abber.IsHot);
|
||||
Assert.Contains("Abber", abber.Name, StringComparison.Ordinal);
|
||||
Assert.Equal(10, abber.WarmupPeriod);
|
||||
Assert.Equal(0, aberr.Last.Value);
|
||||
Assert.False(aberr.IsHot);
|
||||
Assert.Contains("Aberr", aberr.Name, StringComparison.Ordinal);
|
||||
Assert.Equal(10, aberr.WarmupPeriod);
|
||||
|
||||
abber.Update(new TValue(DateTime.UtcNow, 100));
|
||||
aberr.Update(new TValue(DateTime.UtcNow, 100));
|
||||
|
||||
Assert.NotEqual(0, abber.Last.Value);
|
||||
Assert.NotEqual(0, aberr.Last.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Abber_IsHot_BecomesTrueWhenBufferFull()
|
||||
public void Aberr_IsHot_BecomesTrueWhenBufferFull()
|
||||
{
|
||||
var abber = new Abber(5);
|
||||
var aberr = new Aberr(5);
|
||||
|
||||
Assert.False(abber.IsHot);
|
||||
Assert.False(aberr.IsHot);
|
||||
|
||||
for (int i = 1; i <= 4; i++)
|
||||
{
|
||||
abber.Update(new TValue(DateTime.UtcNow, 100 + i));
|
||||
Assert.False(abber.IsHot);
|
||||
aberr.Update(new TValue(DateTime.UtcNow, 100 + i));
|
||||
Assert.False(aberr.IsHot);
|
||||
}
|
||||
|
||||
abber.Update(new TValue(DateTime.UtcNow, 105));
|
||||
Assert.True(abber.IsHot);
|
||||
aberr.Update(new TValue(DateTime.UtcNow, 105));
|
||||
Assert.True(aberr.IsHot);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Abber_CalculatesCorrectBands()
|
||||
public void Aberr_CalculatesCorrectBands()
|
||||
{
|
||||
var abber = new Abber(3, 2.0);
|
||||
var aberr = new Aberr(3, 2.0);
|
||||
|
||||
// Bar 1: source = 100
|
||||
// SMA = 100, Deviation = |100-100| = 0, AvgDev = 0
|
||||
abber.Update(new TValue(DateTime.UtcNow, 100));
|
||||
Assert.Equal(100.0, abber.Last.Value, 1e-10);
|
||||
aberr.Update(new TValue(DateTime.UtcNow, 100));
|
||||
Assert.Equal(100.0, aberr.Last.Value, 1e-10);
|
||||
|
||||
// Bar 2: source = 110
|
||||
// SMA(2) = (100+110)/2 = 105
|
||||
// Dev1 = 0, Dev2 = |110 - 105| = 5 (same-bar SMA)
|
||||
// AvgDev = (0+5)/2 = 2.5
|
||||
// Upper = 105 + 2*2.5 = 110, Lower = 105 - 2*2.5 = 100
|
||||
abber.Update(new TValue(DateTime.UtcNow, 110));
|
||||
Assert.Equal(105.0, abber.Last.Value, 1e-10);
|
||||
aberr.Update(new TValue(DateTime.UtcNow, 110));
|
||||
Assert.Equal(105.0, aberr.Last.Value, 1e-10);
|
||||
|
||||
// Bar 3: source = 120
|
||||
// SMA(3) = (100+110+120)/3 = 110
|
||||
// Dev3 = |120 - 110| = 10 (same-bar SMA)
|
||||
// AvgDev = (0+5+10)/3 = 5.0
|
||||
// Upper = 110 + 2*5 = 120, Lower = 110 - 2*5 = 100
|
||||
abber.Update(new TValue(DateTime.UtcNow, 120));
|
||||
Assert.Equal(110.0, abber.Last.Value, 1e-10);
|
||||
Assert.Equal(120.0, abber.Upper.Value, 1e-10);
|
||||
Assert.Equal(100.0, abber.Lower.Value, 1e-10);
|
||||
aberr.Update(new TValue(DateTime.UtcNow, 120));
|
||||
Assert.Equal(110.0, aberr.Last.Value, 1e-10);
|
||||
Assert.Equal(120.0, aberr.Upper.Value, 1e-10);
|
||||
Assert.Equal(100.0, aberr.Lower.Value, 1e-10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Abber_SlidingWindow_Works()
|
||||
public void Aberr_SlidingWindow_Works()
|
||||
{
|
||||
var abber = new Abber(3, 2.0);
|
||||
var aberr = new Aberr(3, 2.0);
|
||||
|
||||
// Feed initial values
|
||||
abber.Update(new TValue(DateTime.UtcNow, 100));
|
||||
abber.Update(new TValue(DateTime.UtcNow, 110));
|
||||
abber.Update(new TValue(DateTime.UtcNow, 120));
|
||||
aberr.Update(new TValue(DateTime.UtcNow, 100));
|
||||
aberr.Update(new TValue(DateTime.UtcNow, 110));
|
||||
aberr.Update(new TValue(DateTime.UtcNow, 120));
|
||||
|
||||
double middle1 = abber.Last.Value;
|
||||
double middle1 = aberr.Last.Value;
|
||||
|
||||
// Add another value - window slides
|
||||
abber.Update(new TValue(DateTime.UtcNow, 130));
|
||||
aberr.Update(new TValue(DateTime.UtcNow, 130));
|
||||
|
||||
// SMA(3) should now be (110+120+130)/3 = 120
|
||||
Assert.NotEqual(middle1, abber.Last.Value);
|
||||
Assert.Equal(120.0, abber.Last.Value, 1e-10);
|
||||
Assert.NotEqual(middle1, aberr.Last.Value);
|
||||
Assert.Equal(120.0, aberr.Last.Value, 1e-10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Abber_IterativeCorrections_RestoreToOriginalState()
|
||||
public void Aberr_IterativeCorrections_RestoreToOriginalState()
|
||||
{
|
||||
var abber = new Abber(5);
|
||||
var aberr = new Aberr(5);
|
||||
var gbm = new GBM(startPrice: 100.0, mu: 0.02, sigma: 0.1);
|
||||
|
||||
// Feed 10 new values
|
||||
@@ -199,34 +199,34 @@ public class AbberTests
|
||||
{
|
||||
var bar = gbm.Next(isNew: true);
|
||||
tenthInput = new TValue(bar.Time, bar.Close);
|
||||
abber.Update(tenthInput, isNew: true);
|
||||
aberr.Update(tenthInput, isNew: true);
|
||||
}
|
||||
|
||||
// Remember state after 10 values
|
||||
double middleAfterTen = abber.Last.Value;
|
||||
double upperAfterTen = abber.Upper.Value;
|
||||
double lowerAfterTen = abber.Lower.Value;
|
||||
double middleAfterTen = aberr.Last.Value;
|
||||
double upperAfterTen = aberr.Upper.Value;
|
||||
double lowerAfterTen = aberr.Lower.Value;
|
||||
|
||||
// Generate 9 corrections with isNew=false (different values)
|
||||
for (int i = 0; i < 9; i++)
|
||||
{
|
||||
var bar = gbm.Next(isNew: false);
|
||||
abber.Update(new TValue(bar.Time, bar.Close), isNew: false);
|
||||
aberr.Update(new TValue(bar.Time, bar.Close), isNew: false);
|
||||
}
|
||||
|
||||
// Feed the remembered 10th input again with isNew=false
|
||||
abber.Update(tenthInput, isNew: false);
|
||||
aberr.Update(tenthInput, isNew: false);
|
||||
|
||||
// State should match the original state after 10 values
|
||||
Assert.Equal(middleAfterTen, abber.Last.Value, 1e-10);
|
||||
Assert.Equal(upperAfterTen, abber.Upper.Value, 1e-10);
|
||||
Assert.Equal(lowerAfterTen, abber.Lower.Value, 1e-10);
|
||||
Assert.Equal(middleAfterTen, aberr.Last.Value, 1e-10);
|
||||
Assert.Equal(upperAfterTen, aberr.Upper.Value, 1e-10);
|
||||
Assert.Equal(lowerAfterTen, aberr.Lower.Value, 1e-10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Abber_BatchCalc_MatchesIterativeCalc()
|
||||
public void Aberr_BatchCalc_MatchesIterativeCalc()
|
||||
{
|
||||
var abberIterative = new Abber(10);
|
||||
var aberrIterative = new Aberr(10);
|
||||
var gbm = new GBM(startPrice: 100.0, mu: 0.02, sigma: 0.1);
|
||||
|
||||
// Generate data
|
||||
@@ -245,15 +245,15 @@ public class AbberTests
|
||||
var iterativeLower = new List<double>();
|
||||
foreach (var item in series)
|
||||
{
|
||||
abberIterative.Update(item);
|
||||
iterativeMiddle.Add(abberIterative.Last.Value);
|
||||
iterativeUpper.Add(abberIterative.Upper.Value);
|
||||
iterativeLower.Add(abberIterative.Lower.Value);
|
||||
aberrIterative.Update(item);
|
||||
iterativeMiddle.Add(aberrIterative.Last.Value);
|
||||
iterativeUpper.Add(aberrIterative.Upper.Value);
|
||||
iterativeLower.Add(aberrIterative.Lower.Value);
|
||||
}
|
||||
|
||||
// Calculate batch
|
||||
var abberBatch = new Abber(10);
|
||||
var (batchMiddle, batchUpper, batchLower) = abberBatch.Update(series);
|
||||
var aberrBatch = new Aberr(10);
|
||||
var (batchMiddle, batchUpper, batchLower) = aberrBatch.Update(series);
|
||||
|
||||
// Compare
|
||||
Assert.Equal(iterativeMiddle.Count, batchMiddle.Count);
|
||||
@@ -266,59 +266,59 @@ public class AbberTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Abber_NaN_Input_UsesLastValidValue()
|
||||
public void Aberr_NaN_Input_UsesLastValidValue()
|
||||
{
|
||||
var abber = new Abber(5);
|
||||
var aberr = new Aberr(5);
|
||||
|
||||
// Feed some valid values
|
||||
abber.Update(new TValue(DateTime.UtcNow, 100));
|
||||
abber.Update(new TValue(DateTime.UtcNow, 105));
|
||||
aberr.Update(new TValue(DateTime.UtcNow, 100));
|
||||
aberr.Update(new TValue(DateTime.UtcNow, 105));
|
||||
|
||||
// Feed NaN - should use last valid value
|
||||
var resultAfterNaN = abber.Update(new TValue(DateTime.UtcNow, double.NaN));
|
||||
var resultAfterNaN = aberr.Update(new TValue(DateTime.UtcNow, double.NaN));
|
||||
|
||||
// Result should be finite (not NaN)
|
||||
Assert.True(double.IsFinite(resultAfterNaN.Value));
|
||||
Assert.True(double.IsFinite(abber.Upper.Value));
|
||||
Assert.True(double.IsFinite(abber.Lower.Value));
|
||||
Assert.True(double.IsFinite(aberr.Upper.Value));
|
||||
Assert.True(double.IsFinite(aberr.Lower.Value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Abber_Infinity_Input_UsesLastValidValue()
|
||||
public void Aberr_Infinity_Input_UsesLastValidValue()
|
||||
{
|
||||
var abber = new Abber(5);
|
||||
var aberr = new Aberr(5);
|
||||
|
||||
// Feed some valid values
|
||||
abber.Update(new TValue(DateTime.UtcNow, 100));
|
||||
abber.Update(new TValue(DateTime.UtcNow, 105));
|
||||
aberr.Update(new TValue(DateTime.UtcNow, 100));
|
||||
aberr.Update(new TValue(DateTime.UtcNow, 105));
|
||||
|
||||
// Feed positive infinity
|
||||
var resultAfterPosInf = abber.Update(new TValue(DateTime.UtcNow, double.PositiveInfinity));
|
||||
var resultAfterPosInf = aberr.Update(new TValue(DateTime.UtcNow, double.PositiveInfinity));
|
||||
Assert.True(double.IsFinite(resultAfterPosInf.Value));
|
||||
Assert.True(double.IsFinite(abber.Upper.Value));
|
||||
Assert.True(double.IsFinite(abber.Lower.Value));
|
||||
Assert.True(double.IsFinite(aberr.Upper.Value));
|
||||
Assert.True(double.IsFinite(aberr.Lower.Value));
|
||||
|
||||
// Feed negative infinity
|
||||
var resultAfterNegInf = abber.Update(new TValue(DateTime.UtcNow, double.NegativeInfinity));
|
||||
var resultAfterNegInf = aberr.Update(new TValue(DateTime.UtcNow, double.NegativeInfinity));
|
||||
Assert.True(double.IsFinite(resultAfterNegInf.Value));
|
||||
Assert.True(double.IsFinite(abber.Upper.Value));
|
||||
Assert.True(double.IsFinite(abber.Lower.Value));
|
||||
Assert.True(double.IsFinite(aberr.Upper.Value));
|
||||
Assert.True(double.IsFinite(aberr.Lower.Value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Abber_MultipleNaN_ContinuesWithLastValid()
|
||||
public void Aberr_MultipleNaN_ContinuesWithLastValid()
|
||||
{
|
||||
var abber = new Abber(5);
|
||||
var aberr = new Aberr(5);
|
||||
|
||||
// Feed valid values
|
||||
abber.Update(new TValue(DateTime.UtcNow, 100));
|
||||
abber.Update(new TValue(DateTime.UtcNow, 105));
|
||||
abber.Update(new TValue(DateTime.UtcNow, 110));
|
||||
aberr.Update(new TValue(DateTime.UtcNow, 100));
|
||||
aberr.Update(new TValue(DateTime.UtcNow, 105));
|
||||
aberr.Update(new TValue(DateTime.UtcNow, 110));
|
||||
|
||||
// Feed multiple NaN values
|
||||
var r1 = abber.Update(new TValue(DateTime.UtcNow, double.NaN));
|
||||
var r2 = abber.Update(new TValue(DateTime.UtcNow, double.NaN));
|
||||
var r3 = abber.Update(new TValue(DateTime.UtcNow, double.NaN));
|
||||
var r1 = aberr.Update(new TValue(DateTime.UtcNow, double.NaN));
|
||||
var r2 = aberr.Update(new TValue(DateTime.UtcNow, double.NaN));
|
||||
var r3 = aberr.Update(new TValue(DateTime.UtcNow, double.NaN));
|
||||
|
||||
// All results should be finite
|
||||
Assert.True(double.IsFinite(r1.Value));
|
||||
@@ -327,7 +327,7 @@ public class AbberTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Abber_StaticBatch_Works()
|
||||
public void Aberr_StaticBatch_Works()
|
||||
{
|
||||
var series = new TSeries();
|
||||
series.Add(DateTime.UtcNow, 100);
|
||||
@@ -336,7 +336,7 @@ public class AbberTests
|
||||
series.Add(DateTime.UtcNow, 130);
|
||||
series.Add(DateTime.UtcNow, 140);
|
||||
|
||||
var (middle, upper, lower) = Abber.Batch(series, 3);
|
||||
var (middle, upper, lower) = Aberr.Batch(series, 3);
|
||||
|
||||
Assert.Equal(5, middle.Count);
|
||||
Assert.Equal(5, upper.Count);
|
||||
@@ -352,29 +352,29 @@ public class AbberTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Abber_Period1_ReturnsDirectCalculation()
|
||||
public void Aberr_Period1_ReturnsDirectCalculation()
|
||||
{
|
||||
var abber = new Abber(1);
|
||||
var aberr = new Aberr(1);
|
||||
|
||||
// Single value: SMA(1) = 100, Deviation = 0
|
||||
abber.Update(new TValue(DateTime.UtcNow, 100));
|
||||
Assert.Equal(100.0, abber.Last.Value, 1e-10);
|
||||
Assert.Equal(100.0, abber.Upper.Value, 1e-10);
|
||||
Assert.Equal(100.0, abber.Lower.Value, 1e-10);
|
||||
aberr.Update(new TValue(DateTime.UtcNow, 100));
|
||||
Assert.Equal(100.0, aberr.Last.Value, 1e-10);
|
||||
Assert.Equal(100.0, aberr.Upper.Value, 1e-10);
|
||||
Assert.Equal(100.0, aberr.Lower.Value, 1e-10);
|
||||
|
||||
// Next value: SMA(1) = 110, Deviation from previous SMA = |110 - 100| = 10
|
||||
// But with period 1, the old value drops out, so AvgDev = |110 - 110| = 0?
|
||||
// Actually deviation is calculated BEFORE adding to buffer
|
||||
// When 110 comes in, SMA is still 100, so Dev = |110 - 100| = 10
|
||||
// Then buffer updates to just [110], so SMA = 110, AvgDev = 10
|
||||
abber.Update(new TValue(DateTime.UtcNow, 110));
|
||||
Assert.Equal(110.0, abber.Last.Value, 1e-10);
|
||||
aberr.Update(new TValue(DateTime.UtcNow, 110));
|
||||
Assert.Equal(110.0, aberr.Last.Value, 1e-10);
|
||||
}
|
||||
|
||||
// ============== Span API Tests ==============
|
||||
|
||||
[Fact]
|
||||
public void Abber_SpanBatch_ValidatesInput()
|
||||
public void Aberr_SpanBatch_ValidatesInput()
|
||||
{
|
||||
double[] source = [100, 110, 120];
|
||||
double[] middle = new double[3];
|
||||
@@ -383,24 +383,24 @@ public class AbberTests
|
||||
|
||||
// Period must be > 0
|
||||
Assert.Throws<ArgumentException>(() =>
|
||||
Abber.Batch(source.AsSpan(), middle.AsSpan(), upper.AsSpan(), lower.AsSpan(), 0));
|
||||
Aberr.Batch(source.AsSpan(), middle.AsSpan(), upper.AsSpan(), lower.AsSpan(), 0));
|
||||
Assert.Throws<ArgumentException>(() =>
|
||||
Abber.Batch(source.AsSpan(), middle.AsSpan(), upper.AsSpan(), lower.AsSpan(), -1));
|
||||
Aberr.Batch(source.AsSpan(), middle.AsSpan(), upper.AsSpan(), lower.AsSpan(), -1));
|
||||
|
||||
// Multiplier must be > 0
|
||||
Assert.Throws<ArgumentException>(() =>
|
||||
Abber.Batch(source.AsSpan(), middle.AsSpan(), upper.AsSpan(), lower.AsSpan(), 3, 0));
|
||||
Aberr.Batch(source.AsSpan(), middle.AsSpan(), upper.AsSpan(), lower.AsSpan(), 3, 0));
|
||||
Assert.Throws<ArgumentException>(() =>
|
||||
Abber.Batch(source.AsSpan(), middle.AsSpan(), upper.AsSpan(), lower.AsSpan(), 3, -1));
|
||||
Aberr.Batch(source.AsSpan(), middle.AsSpan(), upper.AsSpan(), lower.AsSpan(), 3, -1));
|
||||
|
||||
// Output buffers must be same length as input
|
||||
double[] shortOutput = new double[2];
|
||||
Assert.Throws<ArgumentException>(() =>
|
||||
Abber.Batch(source.AsSpan(), shortOutput.AsSpan(), upper.AsSpan(), lower.AsSpan(), 3));
|
||||
Aberr.Batch(source.AsSpan(), shortOutput.AsSpan(), upper.AsSpan(), lower.AsSpan(), 3));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Abber_SpanBatch_MatchesTSeriesBatch()
|
||||
public void Aberr_SpanBatch_MatchesTSeriesBatch()
|
||||
{
|
||||
var gbm = new GBM(startPrice: 100.0, mu: 0.02, sigma: 0.1, seed: 42);
|
||||
var series = new TSeries();
|
||||
@@ -415,13 +415,13 @@ public class AbberTests
|
||||
}
|
||||
|
||||
// Calculate with TSeries API
|
||||
var (tseriesMiddle, tseriesUpper, tseriesLower) = Abber.Batch(series, 10);
|
||||
var (tseriesMiddle, tseriesUpper, tseriesLower) = Aberr.Batch(series, 10);
|
||||
|
||||
// Calculate with Span API
|
||||
double[] spanMiddle = new double[100];
|
||||
double[] spanUpper = new double[100];
|
||||
double[] spanLower = new double[100];
|
||||
Abber.Batch(source.AsSpan(), spanMiddle.AsSpan(), spanUpper.AsSpan(), spanLower.AsSpan(), 10);
|
||||
Aberr.Batch(source.AsSpan(), spanMiddle.AsSpan(), spanUpper.AsSpan(), spanLower.AsSpan(), 10);
|
||||
|
||||
// Compare results
|
||||
for (int i = 0; i < 100; i++)
|
||||
@@ -433,7 +433,7 @@ public class AbberTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Abber_SpanBatch_ZeroAllocation()
|
||||
public void Aberr_SpanBatch_ZeroAllocation()
|
||||
{
|
||||
var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 42);
|
||||
double[] source = new double[10000];
|
||||
@@ -447,7 +447,7 @@ public class AbberTests
|
||||
}
|
||||
|
||||
// Warm up
|
||||
Abber.Batch(source.AsSpan(), middle.AsSpan(), upper.AsSpan(), lower.AsSpan(), 100);
|
||||
Aberr.Batch(source.AsSpan(), middle.AsSpan(), upper.AsSpan(), lower.AsSpan(), 100);
|
||||
|
||||
// Verify method completes without OOM or stack overflow
|
||||
Assert.True(double.IsFinite(middle[^1]));
|
||||
@@ -456,14 +456,14 @@ public class AbberTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Abber_SpanBatch_HandlesNaN()
|
||||
public void Aberr_SpanBatch_HandlesNaN()
|
||||
{
|
||||
double[] source = [100, 110, double.NaN, 130, 140];
|
||||
double[] middle = new double[5];
|
||||
double[] upper = new double[5];
|
||||
double[] lower = new double[5];
|
||||
|
||||
Abber.Batch(source.AsSpan(), middle.AsSpan(), upper.AsSpan(), lower.AsSpan(), 3);
|
||||
Aberr.Batch(source.AsSpan(), middle.AsSpan(), upper.AsSpan(), lower.AsSpan(), 3);
|
||||
|
||||
// All outputs should be finite
|
||||
for (int i = 0; i < 5; i++)
|
||||
@@ -475,7 +475,7 @@ public class AbberTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Abber_AllModes_ProduceSameResult()
|
||||
public void Aberr_AllModes_ProduceSameResult()
|
||||
{
|
||||
// Arrange
|
||||
const int period = 10;
|
||||
@@ -485,7 +485,7 @@ public class AbberTests
|
||||
var series = bars.Close;
|
||||
|
||||
// 1. Batch Mode
|
||||
var (batchMiddle, batchUpper, batchLower) = Abber.Batch(series, period, multiplier);
|
||||
var (batchMiddle, batchUpper, batchLower) = Aberr.Batch(series, period, multiplier);
|
||||
double expectedMiddle = batchMiddle.Last.Value;
|
||||
double expectedUpper = batchUpper.Last.Value;
|
||||
double expectedLower = batchLower.Last.Value;
|
||||
@@ -495,10 +495,10 @@ public class AbberTests
|
||||
double[] spanMiddle = new double[series.Count];
|
||||
double[] spanUpper = new double[series.Count];
|
||||
double[] spanLower = new double[series.Count];
|
||||
Abber.Batch(source.AsSpan(), spanMiddle.AsSpan(), spanUpper.AsSpan(), spanLower.AsSpan(), period, multiplier);
|
||||
Aberr.Batch(source.AsSpan(), spanMiddle.AsSpan(), spanUpper.AsSpan(), spanLower.AsSpan(), period, multiplier);
|
||||
|
||||
// 3. Streaming Mode
|
||||
var streamingInd = new Abber(period, multiplier);
|
||||
var streamingInd = new Aberr(period, multiplier);
|
||||
foreach (var item in series)
|
||||
{
|
||||
streamingInd.Update(item);
|
||||
@@ -509,7 +509,7 @@ public class AbberTests
|
||||
|
||||
// 4. Eventing Mode
|
||||
var pubSource = new TSeries();
|
||||
var eventingInd = new Abber(pubSource, period, multiplier);
|
||||
var eventingInd = new Aberr(pubSource, period, multiplier);
|
||||
foreach (var item in series)
|
||||
{
|
||||
pubSource.Add(item);
|
||||
@@ -533,26 +533,26 @@ public class AbberTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Abber_Chainability_Works()
|
||||
public void Aberr_Chainability_Works()
|
||||
{
|
||||
var source = new TSeries();
|
||||
var abber = new Abber(source, 10);
|
||||
var aberr = new Aberr(source, 10);
|
||||
|
||||
source.Add(new TValue(DateTime.UtcNow, 100));
|
||||
Assert.Equal(100, abber.Last.Value);
|
||||
Assert.Equal(100, aberr.Last.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Abber_WarmupPeriod_IsSetCorrectly()
|
||||
public void Aberr_WarmupPeriod_IsSetCorrectly()
|
||||
{
|
||||
var abber = new Abber(10);
|
||||
Assert.Equal(10, abber.WarmupPeriod);
|
||||
var aberr = new Aberr(10);
|
||||
Assert.Equal(10, aberr.WarmupPeriod);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Abber_Prime_SetsStateCorrectly()
|
||||
public void Aberr_Prime_SetsStateCorrectly()
|
||||
{
|
||||
var abber = new Abber(3, 2.0);
|
||||
var aberr = new Aberr(3, 2.0);
|
||||
var series = new TSeries();
|
||||
|
||||
// Add 5 values
|
||||
@@ -562,21 +562,21 @@ public class AbberTests
|
||||
series.Add(DateTime.UtcNow, 130);
|
||||
series.Add(DateTime.UtcNow, 140);
|
||||
|
||||
abber.Prime(series);
|
||||
aberr.Prime(series);
|
||||
|
||||
Assert.True(abber.IsHot);
|
||||
Assert.True(aberr.IsHot);
|
||||
|
||||
// Last 3 values: 120, 130, 140 -> SMA = 130
|
||||
Assert.Equal(130.0, abber.Last.Value, 1e-10);
|
||||
Assert.Equal(130.0, aberr.Last.Value, 1e-10);
|
||||
|
||||
// Verify it continues correctly
|
||||
abber.Update(new TValue(DateTime.UtcNow, 150));
|
||||
aberr.Update(new TValue(DateTime.UtcNow, 150));
|
||||
// New window: 130, 140, 150 -> SMA = 140
|
||||
Assert.Equal(140.0, abber.Last.Value, 1e-10);
|
||||
Assert.Equal(140.0, aberr.Last.Value, 1e-10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Abber_Calculate_ReturnsCorrectResultsAndHotIndicator()
|
||||
public void Aberr_Calculate_ReturnsCorrectResultsAndHotIndicator()
|
||||
{
|
||||
var series = new TSeries();
|
||||
series.Add(DateTime.UtcNow, 100);
|
||||
@@ -585,7 +585,7 @@ public class AbberTests
|
||||
series.Add(DateTime.UtcNow, 130);
|
||||
series.Add(DateTime.UtcNow, 140);
|
||||
|
||||
var ((middle, upper, lower), indicator) = Abber.Calculate(series, 3, 2.0);
|
||||
var ((middle, upper, lower), indicator) = Aberr.Calculate(series, 3, 2.0);
|
||||
|
||||
// Check results
|
||||
Assert.Equal(5, middle.Count);
|
||||
@@ -603,7 +603,7 @@ public class AbberTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Abber_DifferentMultipliers_Work()
|
||||
public void Aberr_DifferentMultipliers_Work()
|
||||
{
|
||||
var series = new TSeries();
|
||||
for (int i = 0; i < 10; i++)
|
||||
@@ -612,10 +612,10 @@ public class AbberTests
|
||||
}
|
||||
|
||||
// Multiplier 1.0
|
||||
var (middle1, upper1, _) = Abber.Batch(series, 5, 1.0);
|
||||
var (middle1, upper1, _) = Aberr.Batch(series, 5, 1.0);
|
||||
|
||||
// Multiplier 3.0
|
||||
var (middle3, upper3, _) = Abber.Batch(series, 5, 3.0);
|
||||
var (middle3, upper3, _) = Aberr.Batch(series, 5, 3.0);
|
||||
|
||||
// Middle should be the same for all multipliers
|
||||
Assert.Equal(middle1.Last.Value, middle3.Last.Value, 1e-10);
|
||||
@@ -627,47 +627,47 @@ public class AbberTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Abber_FlatLine_ReturnsSameValues()
|
||||
public void Aberr_FlatLine_ReturnsSameValues()
|
||||
{
|
||||
var abber = new Abber(10);
|
||||
var aberr = new Aberr(10);
|
||||
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
abber.Update(new TValue(DateTime.UtcNow, 100));
|
||||
aberr.Update(new TValue(DateTime.UtcNow, 100));
|
||||
}
|
||||
|
||||
// When all values are the same, SMA = 100, all deviations = 0
|
||||
Assert.Equal(100.0, abber.Last.Value, 1e-10);
|
||||
Assert.Equal(100.0, abber.Upper.Value, 1e-10);
|
||||
Assert.Equal(100.0, abber.Lower.Value, 1e-10);
|
||||
Assert.Equal(100.0, aberr.Last.Value, 1e-10);
|
||||
Assert.Equal(100.0, aberr.Upper.Value, 1e-10);
|
||||
Assert.Equal(100.0, aberr.Lower.Value, 1e-10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Abber_Pub_EventFires()
|
||||
public void Aberr_Pub_EventFires()
|
||||
{
|
||||
var abber = new Abber(10);
|
||||
var aberr = new Aberr(10);
|
||||
bool eventFired = false;
|
||||
abber.Pub += (object? _, in TValueEventArgs _) => eventFired = true;
|
||||
aberr.Pub += (object? _, in TValueEventArgs _) => eventFired = true;
|
||||
|
||||
abber.Update(new TValue(DateTime.UtcNow, 100));
|
||||
aberr.Update(new TValue(DateTime.UtcNow, 100));
|
||||
Assert.True(eventFired);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Abber_BandsAreSymmetric()
|
||||
public void Aberr_BandsAreSymmetric()
|
||||
{
|
||||
var abber = new Abber(10, 2.0);
|
||||
var aberr = new Aberr(10, 2.0);
|
||||
var gbm = new GBM(startPrice: 100.0, mu: 0.02, sigma: 0.1, seed: 42);
|
||||
|
||||
for (int i = 0; i < 50; i++)
|
||||
{
|
||||
var bar = gbm.Next(isNew: true);
|
||||
abber.Update(new TValue(bar.Time, bar.Close));
|
||||
aberr.Update(new TValue(bar.Time, bar.Close));
|
||||
}
|
||||
|
||||
// Upper - Middle should equal Middle - Lower
|
||||
double upperDiff = abber.Upper.Value - abber.Last.Value;
|
||||
double lowerDiff = abber.Last.Value - abber.Lower.Value;
|
||||
double upperDiff = aberr.Upper.Value - aberr.Last.Value;
|
||||
double lowerDiff = aberr.Last.Value - aberr.Lower.Value;
|
||||
|
||||
Assert.Equal(upperDiff, lowerDiff, 1e-10);
|
||||
}
|
||||
+48
-48
@@ -3,12 +3,12 @@ using Xunit.Abstractions;
|
||||
namespace QuanTAlib.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Validation tests for Abber indicator.
|
||||
/// Validation tests for Aberr indicator.
|
||||
/// Note: Skender.Stock.Indicators, TA-Lib, Tulip, and OoplesFinance do not provide
|
||||
/// Abber (Aberration Bands) implementation for cross-validation. These tests validate
|
||||
/// Aberr (Aberration Bands) implementation for cross-validation. These tests validate
|
||||
/// against manual calculations and internal consistency across all API modes.
|
||||
/// </summary>
|
||||
public sealed class AbberValidationTests(ITestOutputHelper output) : IDisposable
|
||||
public sealed class AberrValidationTests(ITestOutputHelper output) : IDisposable
|
||||
{
|
||||
private readonly ValidationTestData _testData = new();
|
||||
private bool _disposed;
|
||||
@@ -48,8 +48,8 @@ public sealed class AbberValidationTests(ITestOutputHelper output) : IDisposable
|
||||
series.Add(new TValue(time.AddMinutes(1), 110));
|
||||
series.Add(new TValue(time.AddMinutes(2), 120));
|
||||
|
||||
var abber = new Abber(3, 2.0);
|
||||
var (middle, upper, lower) = abber.Update(series);
|
||||
var aberr = new Aberr(3, 2.0);
|
||||
var (middle, upper, lower) = aberr.Update(series);
|
||||
|
||||
// SMA(3) = 110
|
||||
Assert.Equal(110.0, middle.Last.Value, 1e-10);
|
||||
@@ -61,7 +61,7 @@ public sealed class AbberValidationTests(ITestOutputHelper output) : IDisposable
|
||||
Assert.Equal(110.0 + expectedBandWidth, upper.Last.Value, 1e-10);
|
||||
Assert.Equal(110.0 - expectedBandWidth, lower.Last.Value, 1e-10);
|
||||
|
||||
output.WriteLine("Abber manual calculation (period 3) validated successfully");
|
||||
output.WriteLine("Aberr manual calculation (period 3) validated successfully");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -78,13 +78,13 @@ public sealed class AbberValidationTests(ITestOutputHelper output) : IDisposable
|
||||
series.Add(new TValue(time.AddMinutes(i), values[i]));
|
||||
}
|
||||
|
||||
var abber = new Abber(5, 2.0);
|
||||
var (middle, _, _) = abber.Update(series);
|
||||
var aberr = new Aberr(5, 2.0);
|
||||
var (middle, _, _) = aberr.Update(series);
|
||||
|
||||
// SMA(5) = (100 + 110 + 120 + 130 + 140) / 5 = 120
|
||||
Assert.Equal(120.0, middle.Last.Value, 1e-10);
|
||||
|
||||
output.WriteLine("Abber manual calculation (period 5) validated successfully");
|
||||
output.WriteLine("Aberr manual calculation (period 5) validated successfully");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -101,9 +101,9 @@ public sealed class AbberValidationTests(ITestOutputHelper output) : IDisposable
|
||||
series.Add(new TValue(time.AddMinutes(i), value));
|
||||
}
|
||||
|
||||
var (middle1, upper1, _) = Abber.Batch(series, 10, 1.0);
|
||||
var (middle2, upper2, _) = Abber.Batch(series, 10, 2.0);
|
||||
var (middle3, upper3, _) = Abber.Batch(series, 10, 3.0);
|
||||
var (middle1, upper1, _) = Aberr.Batch(series, 10, 1.0);
|
||||
var (middle2, upper2, _) = Aberr.Batch(series, 10, 2.0);
|
||||
var (middle3, upper3, _) = Aberr.Batch(series, 10, 3.0);
|
||||
|
||||
// Middle should be the same regardless of multiplier
|
||||
Assert.Equal(middle1.Last.Value, middle2.Last.Value, 1e-10);
|
||||
@@ -117,7 +117,7 @@ public sealed class AbberValidationTests(ITestOutputHelper output) : IDisposable
|
||||
Assert.Equal(bw1 * 2.0, bw2, 1e-10);
|
||||
Assert.Equal(bw1 * 3.0, bw3, 1e-10);
|
||||
|
||||
output.WriteLine("Abber multiplier effect validated successfully");
|
||||
output.WriteLine("Aberr multiplier effect validated successfully");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -128,18 +128,18 @@ public sealed class AbberValidationTests(ITestOutputHelper output) : IDisposable
|
||||
foreach (var period in periods)
|
||||
{
|
||||
// Batch mode using instance
|
||||
var abber = new Abber(period, 2.0);
|
||||
var (qMiddle, qUpper, qLower) = abber.Update(_testData.Data);
|
||||
var aberr = new Aberr(period, 2.0);
|
||||
var (qMiddle, qUpper, qLower) = aberr.Update(_testData.Data);
|
||||
|
||||
// Static batch
|
||||
var (sMiddle, sUpper, sLower) = Abber.Batch(_testData.Data, period, 2.0);
|
||||
var (sMiddle, sUpper, sLower) = Aberr.Batch(_testData.Data, period, 2.0);
|
||||
|
||||
// Verify match
|
||||
ValidationHelper.VerifySeriesEqual(qMiddle, sMiddle);
|
||||
ValidationHelper.VerifySeriesEqual(qUpper, sUpper);
|
||||
ValidationHelper.VerifySeriesEqual(qLower, sLower);
|
||||
}
|
||||
output.WriteLine("Abber Batch modes consistency validated successfully");
|
||||
output.WriteLine("Aberr Batch modes consistency validated successfully");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -150,27 +150,27 @@ public sealed class AbberValidationTests(ITestOutputHelper output) : IDisposable
|
||||
foreach (var period in periods)
|
||||
{
|
||||
// Streaming mode
|
||||
var streamingAbber = new Abber(period, 2.0);
|
||||
var streamingAberr = new Aberr(period, 2.0);
|
||||
var streamMiddle = new TSeries();
|
||||
var streamUpper = new TSeries();
|
||||
var streamLower = new TSeries();
|
||||
foreach (var item in _testData.Data)
|
||||
{
|
||||
streamingAbber.Update(item);
|
||||
streamMiddle.Add(streamingAbber.Last);
|
||||
streamUpper.Add(streamingAbber.Upper);
|
||||
streamLower.Add(streamingAbber.Lower);
|
||||
streamingAberr.Update(item);
|
||||
streamMiddle.Add(streamingAberr.Last);
|
||||
streamUpper.Add(streamingAberr.Upper);
|
||||
streamLower.Add(streamingAberr.Lower);
|
||||
}
|
||||
|
||||
// Batch mode for comparison
|
||||
var (batchMiddle, batchUpper, batchLower) = Abber.Batch(_testData.Data, period, 2.0);
|
||||
var (batchMiddle, batchUpper, batchLower) = Aberr.Batch(_testData.Data, period, 2.0);
|
||||
|
||||
// Verify match
|
||||
ValidationHelper.VerifySeriesEqual(batchMiddle, streamMiddle);
|
||||
ValidationHelper.VerifySeriesEqual(batchUpper, streamUpper);
|
||||
ValidationHelper.VerifySeriesEqual(batchLower, streamLower);
|
||||
}
|
||||
output.WriteLine("Abber Streaming mode consistency validated successfully");
|
||||
output.WriteLine("Aberr Streaming mode consistency validated successfully");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -188,11 +188,11 @@ public sealed class AbberValidationTests(ITestOutputHelper output) : IDisposable
|
||||
double[] spanUpper = new double[len];
|
||||
double[] spanLower = new double[len];
|
||||
|
||||
Abber.Batch(source.AsSpan(), spanMiddle.AsSpan(), spanUpper.AsSpan(), spanLower.AsSpan(),
|
||||
Aberr.Batch(source.AsSpan(), spanMiddle.AsSpan(), spanUpper.AsSpan(), spanLower.AsSpan(),
|
||||
period, 2.0);
|
||||
|
||||
// Batch mode for comparison
|
||||
var (batchMiddle, batchUpper, batchLower) = Abber.Batch(_testData.Data, period, 2.0);
|
||||
var (batchMiddle, batchUpper, batchLower) = Aberr.Batch(_testData.Data, period, 2.0);
|
||||
|
||||
// Verify match
|
||||
for (int i = 0; i < len; i++)
|
||||
@@ -202,7 +202,7 @@ public sealed class AbberValidationTests(ITestOutputHelper output) : IDisposable
|
||||
Assert.Equal(batchLower[i].Value, spanLower[i], 9);
|
||||
}
|
||||
}
|
||||
output.WriteLine("Abber Span mode consistency validated successfully");
|
||||
output.WriteLine("Aberr Span mode consistency validated successfully");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -214,7 +214,7 @@ public sealed class AbberValidationTests(ITestOutputHelper output) : IDisposable
|
||||
{
|
||||
// Eventing mode
|
||||
var pubSource = new TSeries();
|
||||
var eventingInd = new Abber(pubSource, period, 2.0);
|
||||
var eventingInd = new Aberr(pubSource, period, 2.0);
|
||||
var eventMiddle = new TSeries();
|
||||
var eventUpper = new TSeries();
|
||||
var eventLower = new TSeries();
|
||||
@@ -228,14 +228,14 @@ public sealed class AbberValidationTests(ITestOutputHelper output) : IDisposable
|
||||
}
|
||||
|
||||
// Batch mode for comparison
|
||||
var (batchMiddle, batchUpper, batchLower) = Abber.Batch(_testData.Data, period, 2.0);
|
||||
var (batchMiddle, batchUpper, batchLower) = Aberr.Batch(_testData.Data, period, 2.0);
|
||||
|
||||
// Verify match
|
||||
ValidationHelper.VerifySeriesEqual(batchMiddle, eventMiddle);
|
||||
ValidationHelper.VerifySeriesEqual(batchUpper, eventUpper);
|
||||
ValidationHelper.VerifySeriesEqual(batchLower, eventLower);
|
||||
}
|
||||
output.WriteLine("Abber Eventing mode consistency validated successfully");
|
||||
output.WriteLine("Aberr Eventing mode consistency validated successfully");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -245,7 +245,7 @@ public sealed class AbberValidationTests(ITestOutputHelper output) : IDisposable
|
||||
|
||||
foreach (var period in periods)
|
||||
{
|
||||
var ((_, _, _), indicator) = Abber.Calculate(_testData.Data, period, 2.0);
|
||||
var ((_, _, _), indicator) = Aberr.Calculate(_testData.Data, period, 2.0);
|
||||
|
||||
// Verify indicator is hot
|
||||
Assert.True(indicator.IsHot);
|
||||
@@ -264,14 +264,14 @@ public sealed class AbberValidationTests(ITestOutputHelper output) : IDisposable
|
||||
indicator.Update(nextValue);
|
||||
Assert.True(indicator.IsHot);
|
||||
}
|
||||
output.WriteLine("Abber Calculate method validated successfully");
|
||||
output.WriteLine("Aberr Calculate method validated successfully");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_LargeDataset_NoOverflow()
|
||||
{
|
||||
// Test with the full 5000 bar dataset
|
||||
var (middle, upper, lower) = Abber.Batch(_testData.Data, 100, 2.0);
|
||||
var (middle, upper, lower) = Aberr.Batch(_testData.Data, 100, 2.0);
|
||||
|
||||
// All outputs should be finite
|
||||
ValidationHelper.VerifyAllFinite(middle, startIndex: 0);
|
||||
@@ -287,7 +287,7 @@ public sealed class AbberValidationTests(ITestOutputHelper output) : IDisposable
|
||||
$"Middle ({middle[i].Value}) should be >= Lower ({lower[i].Value}) at index {i}");
|
||||
}
|
||||
|
||||
output.WriteLine("Abber large dataset (5000 bars) validated successfully");
|
||||
output.WriteLine("Aberr large dataset (5000 bars) validated successfully");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -296,7 +296,7 @@ public sealed class AbberValidationTests(ITestOutputHelper output) : IDisposable
|
||||
// Verify that Upper - Middle == Middle - Lower
|
||||
// This confirms the band width is applied symmetrically
|
||||
|
||||
var (middle, upper, lower) = Abber.Batch(_testData.Data, 20, 2.0);
|
||||
var (middle, upper, lower) = Aberr.Batch(_testData.Data, 20, 2.0);
|
||||
|
||||
// After warmup, verify symmetry
|
||||
for (int i = 20; i < _testData.Data.Count; i++)
|
||||
@@ -307,7 +307,7 @@ public sealed class AbberValidationTests(ITestOutputHelper output) : IDisposable
|
||||
Assert.Equal(upperDiff, lowerDiff, 1e-9);
|
||||
}
|
||||
|
||||
output.WriteLine("Abber band width symmetry validated successfully");
|
||||
output.WriteLine("Aberr band width symmetry validated successfully");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -317,10 +317,10 @@ public sealed class AbberValidationTests(ITestOutputHelper output) : IDisposable
|
||||
int period = 20;
|
||||
|
||||
// Full batch calculation
|
||||
var (batchMiddle, batchUpper, batchLower) = Abber.Batch(_testData.Data, period, 2.0);
|
||||
var (batchMiddle, batchUpper, batchLower) = Aberr.Batch(_testData.Data, period, 2.0);
|
||||
|
||||
// Prime indicator with subset and continue
|
||||
var primedIndicator = new Abber(period, 2.0);
|
||||
var primedIndicator = new Aberr(period, 2.0);
|
||||
var subset = new TSeries();
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
@@ -339,7 +339,7 @@ public sealed class AbberValidationTests(ITestOutputHelper output) : IDisposable
|
||||
Assert.Equal(batchUpper.Last.Value, primedIndicator.Upper.Value, 1e-9);
|
||||
Assert.Equal(batchLower.Last.Value, primedIndicator.Lower.Value, 1e-9);
|
||||
|
||||
output.WriteLine("Abber Prime method validated successfully");
|
||||
output.WriteLine("Aberr Prime method validated successfully");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -348,19 +348,19 @@ public sealed class AbberValidationTests(ITestOutputHelper output) : IDisposable
|
||||
// Verify the middle band is exactly the SMA
|
||||
int period = 20;
|
||||
|
||||
var abber = new Abber(period, 2.0);
|
||||
var aberr = new Aberr(period, 2.0);
|
||||
var sma = new Sma(period);
|
||||
|
||||
var abberResults = abber.Update(_testData.Data);
|
||||
var aberrResults = aberr.Update(_testData.Data);
|
||||
var smaResults = sma.Update(_testData.Data);
|
||||
|
||||
// Middle band should match SMA exactly
|
||||
for (int i = 0; i < _testData.Data.Count; i++)
|
||||
{
|
||||
Assert.Equal(smaResults[i].Value, abberResults.Middle[i].Value, 1e-10);
|
||||
Assert.Equal(smaResults[i].Value, aberrResults.Middle[i].Value, 1e-10);
|
||||
}
|
||||
|
||||
output.WriteLine("Abber middle band matches SMA validated successfully");
|
||||
output.WriteLine("Aberr middle band matches SMA validated successfully");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -378,8 +378,8 @@ public sealed class AbberValidationTests(ITestOutputHelper output) : IDisposable
|
||||
series.Add(new TValue(time.AddMinutes(i), values[i]));
|
||||
}
|
||||
|
||||
var abber = new Abber(period, 1.0); // multiplier = 1 for easier verification
|
||||
var (middle, upper, _) = abber.Update(series);
|
||||
var aberr = new Aberr(period, 1.0); // multiplier = 1 for easier verification
|
||||
var (middle, upper, _) = aberr.Update(series);
|
||||
|
||||
// SMA(5) = (100 + 120 + 80 + 110 + 90) / 5 = 100
|
||||
Assert.Equal(100.0, middle.Last.Value, 1e-10);
|
||||
@@ -390,7 +390,7 @@ public sealed class AbberValidationTests(ITestOutputHelper output) : IDisposable
|
||||
Assert.True(bandWidth >= 0, "Band width should be non-negative");
|
||||
Assert.True(double.IsFinite(bandWidth), "Band width should be finite");
|
||||
|
||||
output.WriteLine("Abber deviation calculation validated successfully");
|
||||
output.WriteLine("Aberr deviation calculation validated successfully");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -401,7 +401,7 @@ public sealed class AbberValidationTests(ITestOutputHelper output) : IDisposable
|
||||
|
||||
foreach (var period in periods)
|
||||
{
|
||||
var (middle, upper, lower) = Abber.Batch(_testData.Data, period, 2.0);
|
||||
var (middle, upper, lower) = Aberr.Batch(_testData.Data, period, 2.0);
|
||||
|
||||
// All values should be finite
|
||||
for (int i = 0; i < middle.Count; i++)
|
||||
@@ -419,6 +419,6 @@ public sealed class AbberValidationTests(ITestOutputHelper output) : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
output.WriteLine($"Abber consistency across {periods.Length} periods validated successfully");
|
||||
output.WriteLine($"Aberr consistency across {periods.Length} periods validated successfully");
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ using System.Runtime.InteropServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// Abber: Aberration Bands
|
||||
/// Aberr: Aberration Bands
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Aberration Bands measure price deviation from a central moving average using absolute
|
||||
@@ -27,10 +27,10 @@ namespace QuanTAlib;
|
||||
/// - Bands expand during volatile periods and contract during consolidation
|
||||
///
|
||||
/// Sources:
|
||||
/// Pine Script implementation: https://github.com/mihakralj/pinescript/blob/main/indicators/channels/abber.pine
|
||||
/// Pine Script implementation: https://github.com/mihakralj/pinescript/blob/main/indicators/channels/aberr.pine
|
||||
/// </remarks>
|
||||
[SkipLocalsInit]
|
||||
public sealed class Abber : ITValuePublisher, IDisposable
|
||||
public sealed class Aberr : ITValuePublisher, IDisposable
|
||||
{
|
||||
private readonly int _period;
|
||||
private readonly double _multiplier;
|
||||
@@ -88,11 +88,11 @@ public sealed class Abber : ITValuePublisher, IDisposable
|
||||
public event TValuePublishedHandler? Pub;
|
||||
|
||||
/// <summary>
|
||||
/// Creates Abber with specified period and multiplier.
|
||||
/// Creates Aberr with specified period and multiplier.
|
||||
/// </summary>
|
||||
/// <param name="period">Lookback period for SMA and deviation calculations (must be > 0)</param>
|
||||
/// <param name="multiplier">Multiplier for band width (must be > 0, default: 2.0)</param>
|
||||
public Abber(int period, double multiplier = 2.0)
|
||||
public Aberr(int period, double multiplier = 2.0)
|
||||
{
|
||||
if (period <= 0)
|
||||
{
|
||||
@@ -108,15 +108,15 @@ public sealed class Abber : ITValuePublisher, IDisposable
|
||||
_multiplier = multiplier;
|
||||
_sourceBuffer = new RingBuffer(period);
|
||||
_deviationBuffer = new RingBuffer(period);
|
||||
Name = $"Abber({period},{multiplier:F2})";
|
||||
Name = $"Aberr({period},{multiplier:F2})";
|
||||
WarmupPeriod = period;
|
||||
_handler = HandleValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates Abber with TSeries source.
|
||||
/// Creates Aberr with TSeries source.
|
||||
/// </summary>
|
||||
public Abber(TSeries source, int period, double multiplier = 2.0) : this(period, multiplier)
|
||||
public Aberr(TSeries source, int period, double multiplier = 2.0) : this(period, multiplier)
|
||||
{
|
||||
_source = source ?? throw new ArgumentNullException(nameof(source));
|
||||
Prime(source);
|
||||
@@ -124,9 +124,9 @@ public sealed class Abber : ITValuePublisher, IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates Abber with ITValuePublisher source.
|
||||
/// Creates Aberr with ITValuePublisher source.
|
||||
/// </summary>
|
||||
public Abber(ITValuePublisher source, int period, double multiplier = 2.0) : this(period, multiplier)
|
||||
public Aberr(ITValuePublisher source, int period, double multiplier = 2.0) : this(period, multiplier)
|
||||
{
|
||||
_source = source ?? throw new ArgumentNullException(nameof(source));
|
||||
_source.Pub += _handler;
|
||||
@@ -408,7 +408,7 @@ public sealed class Abber : ITValuePublisher, IDisposable
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// <summary>
|
||||
/// Output buffers for batch Abber calculation.
|
||||
/// Output buffers for batch Aberr calculation.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
#pragma warning disable S1104 // Fields should not have public accessibility
|
||||
@@ -457,16 +457,16 @@ public sealed class Abber : ITValuePublisher, IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates Abber for the entire TSeries using a new instance.
|
||||
/// Calculates Aberr for the entire TSeries using a new instance.
|
||||
/// </summary>
|
||||
public static (TSeries Middle, TSeries Upper, TSeries Lower) Batch(TSeries source, int period, double multiplier = 2.0)
|
||||
{
|
||||
var abber = new Abber(period, multiplier);
|
||||
return abber.Update(source);
|
||||
var aberr = new Aberr(period, multiplier);
|
||||
return aberr.Update(source);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates Abber in-place using spans for maximum performance.
|
||||
/// Calculates Aberr in-place using spans for maximum performance.
|
||||
/// Zero-allocation method.
|
||||
/// </summary>
|
||||
/// <param name="source">Source price values</param>
|
||||
@@ -484,7 +484,7 @@ public sealed class Abber : ITValuePublisher, IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates Abber in-place using spans for maximum performance.
|
||||
/// Calculates Aberr in-place using spans for maximum performance.
|
||||
/// Zero-allocation method.
|
||||
/// </summary>
|
||||
/// <param name="source">Source price values</param>
|
||||
@@ -677,17 +677,17 @@ public sealed class Abber : ITValuePublisher, IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs a high-performance batch calculation and returns a "Hot" Abber instance.
|
||||
/// Runs a high-performance batch calculation and returns a "Hot" Aberr instance.
|
||||
/// </summary>
|
||||
public static ((TSeries Middle, TSeries Upper, TSeries Lower) Results, Abber Indicator) Calculate(TSeries source, int period, double multiplier = 2.0)
|
||||
public static ((TSeries Middle, TSeries Upper, TSeries Lower) Results, Aberr Indicator) Calculate(TSeries source, int period, double multiplier = 2.0)
|
||||
{
|
||||
var abber = new Abber(period, multiplier);
|
||||
var results = abber.Update(source);
|
||||
return (results, abber);
|
||||
var aberr = new Aberr(period, multiplier);
|
||||
var results = aberr.Update(source);
|
||||
return (results, aberr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the Abber instance, unsubscribing from the source publisher.
|
||||
/// Disposes the Aberr instance, unsubscribing from the source publisher.
|
||||
/// This method is idempotent.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
@@ -1,4 +1,4 @@
|
||||
# ABBER: Aberration Bands
|
||||
# ABERR: Aberration Bands
|
||||
|
||||
| Property | Value |
|
||||
| ---------------- | -------------------------------- |
|
||||
@@ -11,19 +11,19 @@
|
||||
|
||||
### TL;DR
|
||||
|
||||
- ABBER measures price deviation from a central moving average using mean absolute deviation rather than standard deviation, producing dynamic bands ...
|
||||
- ABERR measures price deviation from a central moving average using mean absolute deviation rather than standard deviation, producing dynamic bands ...
|
||||
- Parameterized by `period`, `multiplier` (default 2.0).
|
||||
- Output range: Tracks input.
|
||||
- Requires `period` bars of warmup before first valid output (IsHot = true).
|
||||
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
|
||||
|
||||
ABBER measures price deviation from a central moving average using mean absolute deviation rather than standard deviation, producing dynamic bands that adapt to volatility while remaining robust against extreme outliers. Where Bollinger Bands amplify outliers through squaring (the $L^2$ norm), ABBER uses raw absolute differences (the $L^1$ norm), so bands respond to typical price behavior rather than the occasional spike that yanks everything sideways. For a 20-period window with a 2.0 multiplier, ABBER contains approximately 89% of normally-distributed price action, but its real advantage emerges with fat-tailed distributions where standard deviation overreacts to single-bar anomalies.
|
||||
ABERR measures price deviation from a central moving average using mean absolute deviation rather than standard deviation, producing dynamic bands that adapt to volatility while remaining robust against extreme outliers. Where Bollinger Bands amplify outliers through squaring (the $L^2$ norm), ABERR uses raw absolute differences (the $L^1$ norm), so bands respond to typical price behavior rather than the occasional spike that yanks everything sideways. For a 20-period window with a 2.0 multiplier, ABERR contains approximately 89% of normally-distributed price action, but its real advantage emerges with fat-tailed distributions where standard deviation overreacts to single-bar anomalies.
|
||||
|
||||
## Historical Context
|
||||
|
||||
The absolute deviation approach predates Bollinger's work by decades. Mean absolute deviation appears in early 20th-century statistics as a robust alternative to standard deviation, championed by statisticians who recognized that squaring deviations gives disproportionate weight to outliers. In financial markets, applying absolute deviation to band construction arrived after practitioners grew tired of watching Bollinger Bands blow out on single-bar anomalies such as flash crashes, earnings gaps, and fat-finger trades.
|
||||
|
||||
No single inventor claims credit for ABBER. The technique spread through trading floors where robustness mattered more than textbook elegance. The mathematical distinction is fundamental: standard deviation is a quadratic spring that amplifies outliers, while mean absolute deviation is a linear damper that treats all deviations proportionally. Under Gaussian assumptions, $\text{MAD} \approx 0.7979 \sigma$, so ABBER with multiplier 2.0 is roughly equivalent to Bollinger Bands with multiplier 1.6. But on real market data with kurtosis > 3, the gap widens in ABBER's favor.
|
||||
No single inventor claims credit for ABERR. The technique spread through trading floors where robustness mattered more than textbook elegance. The mathematical distinction is fundamental: standard deviation is a quadratic spring that amplifies outliers, while mean absolute deviation is a linear damper that treats all deviations proportionally. Under Gaussian assumptions, $\text{MAD} \approx 0.7979 \sigma$, so ABERR with multiplier 2.0 is roughly equivalent to Bollinger Bands with multiplier 1.6. But on real market data with kurtosis > 3, the gap widens in ABERR's favor.
|
||||
|
||||
## Architecture & Physics
|
||||
|
||||
@@ -72,12 +72,12 @@ For a normal distribution:
|
||||
|
||||
$$\text{MAD} = \sigma \sqrt{\frac{2}{\pi}} \approx 0.7979\,\sigma$$
|
||||
|
||||
Therefore ABBER with $k = 2.0$ captures approximately the same range as Bollinger Bands with $k \approx 1.596$.
|
||||
Therefore ABERR with $k = 2.0$ captures approximately the same range as Bollinger Bands with $k \approx 1.596$.
|
||||
|
||||
### Pseudo-code
|
||||
|
||||
```
|
||||
function ABBER(source, ma_line, period, multiplier):
|
||||
function ABERR(source, ma_line, period, multiplier):
|
||||
// Deviation from center line
|
||||
deviation = |source - ma_line|
|
||||
|
||||
@@ -103,7 +103,7 @@ function ABBER(source, ma_line, period, multiplier):
|
||||
|
||||
### Operation Count (Streaming Mode)
|
||||
|
||||
ABBER maintains two running-sum ring buffers (SMA of price and SMA of absolute deviations), each updated in $O(1)$:
|
||||
ABERR maintains two running-sum ring buffers (SMA of price and SMA of absolute deviations), each updated in $O(1)$:
|
||||
|
||||
| Operation | Count | Cost (cycles) | Subtotal |
|
||||
| :--- | :---: | :---: | :---: |
|
||||
@@ -1,7 +1,7 @@
|
||||
// The MIT License (MIT)
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("Aberration (ABBER)", "ABBER", overlay=true)
|
||||
indicator("Aberration (ABERR)", "ABERR", overlay=true)
|
||||
|
||||
//@function Calculates Aberration bands measuring deviation from a central moving average
|
||||
//@param source Series to calculate aberration from
|
||||
@@ -10,7 +10,7 @@ indicator("Aberration (ABBER)", "ABBER", overlay=true)
|
||||
//@param multiplier Multiplier for deviation bands
|
||||
//@returns [upper_band, lower_band, deviation] Aberration band values and deviation
|
||||
//@optimized Uses simple deviation averaging with O(n) complexity
|
||||
abber(series float source, series float ma_line, simple int period, simple float multiplier) =>
|
||||
aberr(series float source, series float ma_line, simple int period, simple float multiplier) =>
|
||||
if period <= 0 or multiplier <= 0.0
|
||||
runtime.error("Period and multiplier must be greater than 0")
|
||||
float deviation = math.abs(nz(source) - nz(ma_line))
|
||||
@@ -38,7 +38,7 @@ ma_line = switch i_ma_type
|
||||
=> ta.sma(i_source, i_period)
|
||||
|
||||
// Calculation
|
||||
[upper_band, lower_band, deviation] = abber(i_source, ma_line, i_period, i_multiplier)
|
||||
[upper_band, lower_band, deviation] = aberr(i_source, ma_line, i_period, i_multiplier)
|
||||
|
||||
// Plots
|
||||
p_upper = plot(upper_band, "Upper Band", color=color.yellow, linewidth=2)
|
||||
@@ -12,7 +12,7 @@ public class MaenvIndicatorTests
|
||||
|
||||
Assert.Equal(20, ind.Period);
|
||||
Assert.Equal(1.0, ind.Percentage);
|
||||
Assert.Equal(MaenvType.EMA, ind.maType);
|
||||
Assert.Equal(MaenvType.EMA, ind.MaType);
|
||||
Assert.Equal(PriceType.Close, ind.SourceType);
|
||||
Assert.True(ind.ShowColdValues);
|
||||
Assert.Equal("Maenv - Moving Average Envelope", ind.Name);
|
||||
@@ -30,7 +30,7 @@ public class MaenvIndicatorTests
|
||||
[Fact]
|
||||
public void ShortName_ReflectsParameters()
|
||||
{
|
||||
var ind = new MaenvIndicator { Period = 12, Percentage = 2.5, maType = MaenvType.SMA };
|
||||
var ind = new MaenvIndicator { Period = 12, Percentage = 2.5, MaType = MaenvType.SMA };
|
||||
Assert.Contains("12", ind.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("2.5", ind.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("SMA", ind.ShortName, StringComparison.Ordinal);
|
||||
@@ -213,7 +213,7 @@ public class MaenvIndicatorTests
|
||||
{
|
||||
foreach (MaenvType maType in Enum.GetValues<MaenvType>())
|
||||
{
|
||||
var ind = new MaenvIndicator { Period = 10, Percentage = 2.0, maType = maType };
|
||||
var ind = new MaenvIndicator { Period = 10, Percentage = 2.0, MaType = maType };
|
||||
ind.Initialize();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
Reference in New Issue
Block a user