From 12b03be6ca961ac5740f2f8d597a64df1cd7e597 Mon Sep 17 00:00:00 2001 From: mkzung <103102868+mkzung@users.noreply.github.com> Date: Wed, 19 Aug 2026 20:48:46 +0500 Subject: [PATCH 1/2] Reject the two point window Beta, Correlation and Covariance describe All three carry the comment "assert the period is greater than two" and raise "must be greater than 2 but was {period}", and all three test period < 2, so a period of two reaches the calculation. Two points always correlate perfectly. Fed ten bars of two series that are not proportional, Correlation at period 2 returns 1 or -1 on every one of its nine readings, while at period 3 the same series gives values between -0.40 and 0.33. Beta and Covariance share the window. ValueAtRisk states the same bound and tests period < 3. --- Indicators/Beta.cs | 2 +- Indicators/Correlation.cs | 2 +- Indicators/Covariance.cs | 2 +- Tests/Indicators/BetaIndicatorTests.cs | 12 ++++++++++++ Tests/Indicators/CorrelationPearsonTests.cs | 12 ++++++++++++ Tests/Indicators/CovarianceTests.cs | 12 ++++++++++++ 6 files changed, 39 insertions(+), 3 deletions(-) diff --git a/Indicators/Beta.cs b/Indicators/Beta.cs index b475bf543b65..f1260914364f 100644 --- a/Indicators/Beta.cs +++ b/Indicators/Beta.cs @@ -59,7 +59,7 @@ public Beta(string name, Symbol targetSymbol, Symbol referenceSymbol, int period : base(name, targetSymbol, referenceSymbol, 2) { // Assert the period is greater than two, otherwise the beta can not be computed - if (period < 2) + if (period < 3) { throw new ArgumentException($"Period parameter for Beta indicator must be greater than 2 but was {period}."); } diff --git a/Indicators/Correlation.cs b/Indicators/Correlation.cs index aab80112c13b..b2bdc9f09e2d 100644 --- a/Indicators/Correlation.cs +++ b/Indicators/Correlation.cs @@ -61,7 +61,7 @@ public Correlation(string name, Symbol targetSymbol, Symbol referenceSymbol, int : base(name, targetSymbol, referenceSymbol, period) { // Assert the period is greater than two, otherwise the correlation can not be computed - if (period < 2) + if (period < 3) { throw new ArgumentException($"Period parameter for Correlation indicator must be greater than 2 but was {period}"); } diff --git a/Indicators/Covariance.cs b/Indicators/Covariance.cs index ee3a85a003cf..5a630014f94c 100644 --- a/Indicators/Covariance.cs +++ b/Indicators/Covariance.cs @@ -52,7 +52,7 @@ public Covariance(string name, Symbol targetSymbol, Symbol referenceSymbol, int : base(name, targetSymbol, referenceSymbol, 2) { // Assert the period is greater than two, otherwise the covariance can not be computed - if (period < 2) + if (period < 3) { throw new ArgumentException($"Period parameter for Covariance indicator must be greater than 2 but was {period}."); } diff --git a/Tests/Indicators/BetaIndicatorTests.cs b/Tests/Indicators/BetaIndicatorTests.cs index 0068e1a01ac0..b0ce13ca8fd3 100644 --- a/Tests/Indicators/BetaIndicatorTests.cs +++ b/Tests/Indicators/BetaIndicatorTests.cs @@ -312,5 +312,17 @@ public override void IndicatorShouldHaveSymbolAfterUpdates() Assert.AreEqual(Symbols.AAPL, indicator.Current.Symbol); } } + + [Test] + public void PeriodBelowMinimumThrows() + { + // A two point window leaves no spread to measure: the correlation of two + // points is 1 or -1 whatever the data says, so three is the smallest period + // that carries information + var period = 2; + + var exception = Assert.Throws(() => new Beta(Symbols.SPY, Symbols.AAPL, period)); + Assert.That(exception.Message, Is.EqualTo($"Period parameter for Beta indicator must be greater than 2 but was {period}.")); + } } } diff --git a/Tests/Indicators/CorrelationPearsonTests.cs b/Tests/Indicators/CorrelationPearsonTests.cs index a98c882126df..c65d90441474 100644 --- a/Tests/Indicators/CorrelationPearsonTests.cs +++ b/Tests/Indicators/CorrelationPearsonTests.cs @@ -223,5 +223,17 @@ public void CorrelationWithDifferentTimeZones() } Assert.AreEqual(1, (double)indicator.Current.Value); } + + [Test] + public void PeriodBelowMinimumThrows() + { + // A two point window leaves no spread to measure: the correlation of two + // points is 1 or -1 whatever the data says, so three is the smallest period + // that carries information + var period = 2; + + var exception = Assert.Throws(() => new Correlation(Symbols.SPY, Symbols.AAPL, period)); + Assert.That(exception.Message, Is.EqualTo($"Period parameter for Correlation indicator must be greater than 2 but was {period}")); + } } } diff --git a/Tests/Indicators/CovarianceTests.cs b/Tests/Indicators/CovarianceTests.cs index 32981af7f30a..37d99c008b9b 100644 --- a/Tests/Indicators/CovarianceTests.cs +++ b/Tests/Indicators/CovarianceTests.cs @@ -287,5 +287,17 @@ public override void IndicatorShouldHaveSymbolAfterUpdates() Assert.AreEqual(Symbols.AAPL, indicator.Current.Symbol); } } + + [Test] + public void PeriodBelowMinimumThrows() + { + // A two point window leaves no spread to measure: the correlation of two + // points is 1 or -1 whatever the data says, so three is the smallest period + // that carries information + var period = 2; + + var exception = Assert.Throws(() => new Covariance(Symbols.SPY, Symbols.AAPL, period)); + Assert.That(exception.Message, Is.EqualTo($"Period parameter for Covariance indicator must be greater than 2 but was {period}.")); + } } } From 8c8a7521ba9121f37ac609bf4bb010a963e27b80 Mon Sep 17 00:00:00 2001 From: mkzung <103102868+mkzung@users.noreply.github.com> Date: Tue, 25 Aug 2026 18:04:40 +0100 Subject: [PATCH 2/2] Keep the Correlation guard, restore Beta and Covariance to what they enforce --- Indicators/Beta.cs | 6 +++--- Indicators/Covariance.cs | 6 +++--- Tests/Indicators/BetaIndicatorTests.cs | 16 +++++++++++----- Tests/Indicators/CovarianceTests.cs | 16 +++++++++++----- 4 files changed, 28 insertions(+), 16 deletions(-) diff --git a/Indicators/Beta.cs b/Indicators/Beta.cs index f1260914364f..959fee63ad5b 100644 --- a/Indicators/Beta.cs +++ b/Indicators/Beta.cs @@ -58,10 +58,10 @@ public class Beta : DualSymbolIndicator public Beta(string name, Symbol targetSymbol, Symbol referenceSymbol, int period) : base(name, targetSymbol, referenceSymbol, 2) { - // Assert the period is greater than two, otherwise the beta can not be computed - if (period < 3) + // Assert the period is greater than one, otherwise the beta can not be computed + if (period < 2) { - throw new ArgumentException($"Period parameter for Beta indicator must be greater than 2 but was {period}."); + throw new ArgumentException($"Period parameter for Beta indicator must be greater than 1 but was {period}."); } _targetReturns = new RollingWindow(period); diff --git a/Indicators/Covariance.cs b/Indicators/Covariance.cs index 5a630014f94c..2dfdaaf87549 100644 --- a/Indicators/Covariance.cs +++ b/Indicators/Covariance.cs @@ -51,10 +51,10 @@ public class Covariance : DualSymbolIndicator public Covariance(string name, Symbol targetSymbol, Symbol referenceSymbol, int period) : base(name, targetSymbol, referenceSymbol, 2) { - // Assert the period is greater than two, otherwise the covariance can not be computed - if (period < 3) + // Assert the period is greater than one, otherwise the covariance can not be computed + if (period < 2) { - throw new ArgumentException($"Period parameter for Covariance indicator must be greater than 2 but was {period}."); + throw new ArgumentException($"Period parameter for Covariance indicator must be greater than 1 but was {period}."); } _targetReturns = new RollingWindow(period); diff --git a/Tests/Indicators/BetaIndicatorTests.cs b/Tests/Indicators/BetaIndicatorTests.cs index b0ce13ca8fd3..84ccaa1bf742 100644 --- a/Tests/Indicators/BetaIndicatorTests.cs +++ b/Tests/Indicators/BetaIndicatorTests.cs @@ -316,13 +316,19 @@ public override void IndicatorShouldHaveSymbolAfterUpdates() [Test] public void PeriodBelowMinimumThrows() { - // A two point window leaves no spread to measure: the correlation of two - // points is 1 or -1 whatever the data says, so three is the smallest period - // that carries information - var period = 2; + // One return is not a sample, so the smallest period this can accept is two + var period = 1; var exception = Assert.Throws(() => new Beta(Symbols.SPY, Symbols.AAPL, period)); - Assert.That(exception.Message, Is.EqualTo($"Period parameter for Beta indicator must be greater than 2 but was {period}.")); + Assert.That(exception.Message, Is.EqualTo($"Period parameter for Beta indicator must be greater than 1 but was {period}.")); + } + + [Test] + public void PeriodOfTwoIsAccepted() + { + // Two returns give a finite, well defined beta, unlike the correlation + // of two points, which is 1 or -1 whatever the data says + Assert.DoesNotThrow(() => new Beta(Symbols.SPY, Symbols.AAPL, 2)); } } } diff --git a/Tests/Indicators/CovarianceTests.cs b/Tests/Indicators/CovarianceTests.cs index 37d99c008b9b..902a077f708b 100644 --- a/Tests/Indicators/CovarianceTests.cs +++ b/Tests/Indicators/CovarianceTests.cs @@ -291,13 +291,19 @@ public override void IndicatorShouldHaveSymbolAfterUpdates() [Test] public void PeriodBelowMinimumThrows() { - // A two point window leaves no spread to measure: the correlation of two - // points is 1 or -1 whatever the data says, so three is the smallest period - // that carries information - var period = 2; + // One return is not a sample, so the smallest period this can accept is two + var period = 1; var exception = Assert.Throws(() => new Covariance(Symbols.SPY, Symbols.AAPL, period)); - Assert.That(exception.Message, Is.EqualTo($"Period parameter for Covariance indicator must be greater than 2 but was {period}.")); + Assert.That(exception.Message, Is.EqualTo($"Period parameter for Covariance indicator must be greater than 1 but was {period}.")); + } + + [Test] + public void PeriodOfTwoIsAccepted() + { + // Two returns give a finite, well defined covariance, unlike the correlation + // of two points, which is 1 or -1 whatever the data says + Assert.DoesNotThrow(() => new Covariance(Symbols.SPY, Symbols.AAPL, 2)); } } }