mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-19 11:08:05 +00:00
Rename EACP to ACP across entire codebase
- Renamed directory lib/cycles/eacp → lib/cycles/acp - Renamed class Eacp → Acp, EacpIndicator → AcpIndicator - Renamed all files: Eacp.cs → Acp.cs, Eacp.Quantower.cs → Acp.Quantower.cs, eacp.md → acp.md, eacp.pine → acp.pine, and all test files - Updated display names: EACP → ACP in Quantower Name/ShortName properties - Updated all documentation surfaces: _sidebar.md, lib/_index.md, lib/cycles/_index.md, docs/indicators.md, docs/validation.md, docs/pinescript.md, lib/cycles/cg/cg.md cross-reference - Updated Python bridge: qtl_eacp → qtl_acp entry point, _bridge.py, cycles.py wrapper, SPEC.md, test_shapes.py, run_all_exported - All 83 tests pass (38 AcpTests + 22 AcpValidationTests + 23 AcpIndicatorTests) - Build: 0 warnings, 0 errors across all projects
This commit is contained in:
+1
-1
@@ -103,7 +103,7 @@
|
||||
| [DWT](numerics/dwt/Dwt.md) | Discrete Wavelet Transform | Numerics |
|
||||
| [DX](dynamics/dx/Dx.md) | Directional Movement Index | Dynamics |
|
||||
| [DYMI](oscillators/dymi/Dymi.md) | Dynamic Momentum Index | Oscillators |
|
||||
| [EACP](cycles/eacp/Eacp.md) | Ehlers Autocorrelation Periodogram | Cycles |
|
||||
| [ACP](cycles/acp/Acp.md) | Ehlers Autocorrelation Periodogram | Cycles |
|
||||
| [EBSW](cycles/ebsw/Ebsw.md) | Ehlers Even Better Sinewave | Cycles |
|
||||
| [EDCF](filters/edcf/Edcf.md) | Ehlers Distance Coefficient Filter | Filters |
|
||||
| [EDECAY](numerics/edecay/Edecay.md) | Exponential Decay | Numerics |
|
||||
|
||||
@@ -10,7 +10,7 @@ Cycle analysis identifies repeating patterns in price data. John Ehlers pioneere
|
||||
| [CCYC](ccyc/Ccyc.md) | Ehlers Cyber Cycle | Ehlers. 4-tap FIR + 2-pole high-pass IIR. Isolates dominant cycle component. |
|
||||
| [CG](cg/Cg.md) | Ehlers Center of Gravity | Ehlers. Weighted sum position. Minimal lag cycle indicator. |
|
||||
| [DSP](dsp/Dsp.md) | Ehlers Detrended Synthetic Price | Removes trend to reveal underlying cycles. |
|
||||
| [EACP](eacp/Eacp.md) | Ehlers Autocorrelation Periodogram | Ehlers. Spectral analysis via autocorrelation. Detects dominant period. |
|
||||
| [ACP](acp/Acp.md) | Ehlers Autocorrelation Periodogram | Ehlers. Spectral analysis via autocorrelation. Detects dominant period. |
|
||||
| [EBSW](ebsw/Ebsw.md) | Ehlers Even Better Sinewave | Ehlers. Improved sinewave extraction. Reduces false signals. |
|
||||
| [HOMOD](homod/Homod.md) | Ehlers Homodyne Discriminator | Dominant cycle detection via homodyne technique. |
|
||||
| [HT_DCPERIOD](ht_dcperiod/Htdcperiod.md) | Ehlers Hilbert Transform Dominant Cycle Period | Ehlers Hilbert Transform. Measures current cycle length. |
|
||||
|
||||
@@ -5,7 +5,7 @@ using TradingPlatform.BusinessLayer;
|
||||
namespace QuanTAlib;
|
||||
|
||||
[SkipLocalsInit]
|
||||
public sealed class EacpIndicator : Indicator, IWatchlistIndicator
|
||||
public sealed class AcpIndicator : Indicator, IWatchlistIndicator
|
||||
{
|
||||
[InputParameter("Min Period", sortIndex: 1, 3, 100, 1, 0)]
|
||||
public int MinPeriod { get; set; } = 8;
|
||||
@@ -25,7 +25,7 @@ public sealed class EacpIndicator : Indicator, IWatchlistIndicator
|
||||
[InputParameter("Show cold values", sortIndex: 21)]
|
||||
public bool ShowColdValues { get; set; } = true;
|
||||
|
||||
private Eacp _eacp = null!;
|
||||
private Acp _acp = null!;
|
||||
private readonly LineSeries _cycleSeries;
|
||||
private readonly LineSeries _powerSeries;
|
||||
private Func<IHistoryItem, double> _priceSelector = null!;
|
||||
@@ -33,14 +33,14 @@ public sealed class EacpIndicator : Indicator, IWatchlistIndicator
|
||||
public static int MinHistoryDepths => 0;
|
||||
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
|
||||
|
||||
public override string ShortName => $"EACP ({MinPeriod},{MaxPeriod})";
|
||||
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/cycles/eacp/Eacp.Quantower.cs";
|
||||
public override string ShortName => $"ACP ({MinPeriod},{MaxPeriod})";
|
||||
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/cycles/acp/Acp.Quantower.cs";
|
||||
|
||||
public EacpIndicator()
|
||||
public AcpIndicator()
|
||||
{
|
||||
OnBackGround = true;
|
||||
SeparateWindow = true;
|
||||
Name = "EACP - Ehlers Autocorrelation Periodogram";
|
||||
Name = "ACP - Ehlers Autocorrelation Periodogram";
|
||||
Description = "Ehlers' Autocorrelation Periodogram estimates the dominant cycle period using autocorrelation and spectral analysis";
|
||||
|
||||
_cycleSeries = new LineSeries(name: "Cycle", color: IndicatorExtensions.Oscillators, width: 2, style: LineStyle.Solid);
|
||||
@@ -52,7 +52,7 @@ public sealed class EacpIndicator : Indicator, IWatchlistIndicator
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void OnInit()
|
||||
{
|
||||
_eacp = new Eacp(MinPeriod, MaxPeriod, AvgLength, Enhance);
|
||||
_acp = new Acp(MinPeriod, MaxPeriod, AvgLength, Enhance);
|
||||
_priceSelector = Source.GetPriceSelector();
|
||||
base.OnInit();
|
||||
}
|
||||
@@ -70,9 +70,9 @@ public sealed class EacpIndicator : Indicator, IWatchlistIndicator
|
||||
var time = this.HistoricalData.Time();
|
||||
|
||||
var input = new TValue(time, value);
|
||||
TValue result = _eacp.Update(input, args.IsNewBar());
|
||||
TValue result = _acp.Update(input, args.IsNewBar());
|
||||
|
||||
_cycleSeries.SetValue(result.Value, _eacp.IsHot, ShowColdValues);
|
||||
_powerSeries.SetValue(_eacp.NormalizedPower * MaxPeriod, _eacp.IsHot, ShowColdValues);
|
||||
_cycleSeries.SetValue(result.Value, _acp.IsHot, ShowColdValues);
|
||||
_powerSeries.SetValue(_acp.NormalizedPower * MaxPeriod, _acp.IsHot, ShowColdValues);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ using System.Runtime.InteropServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// EACP: Ehlers Autocorrelation Periodogram - Dominant cycle estimator using
|
||||
/// ACP: Ehlers Autocorrelation Periodogram - Dominant cycle estimator using
|
||||
/// autocorrelation and spectral analysis via the Wiener-Khinchin theorem.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
@@ -32,7 +32,7 @@ namespace QuanTAlib;
|
||||
/// to spectral density, enabling frequency domain analysis.
|
||||
/// </remarks>
|
||||
[SkipLocalsInit]
|
||||
public sealed class Eacp : AbstractBase
|
||||
public sealed class Acp : AbstractBase
|
||||
{
|
||||
private readonly int _minPeriod;
|
||||
private readonly int _maxPeriod;
|
||||
@@ -82,7 +82,7 @@ public sealed class Eacp : AbstractBase
|
||||
/// <param name="maxPeriod">Maximum period to evaluate (must be > minPeriod).</param>
|
||||
/// <param name="avgLength">Averaging length for Pearson correlation (0 uses lag length).</param>
|
||||
/// <param name="enhance">Apply cubic emphasis to highlight dominant peaks.</param>
|
||||
public Eacp(int minPeriod = 8, int maxPeriod = 48, int avgLength = 3, bool enhance = true)
|
||||
public Acp(int minPeriod = 8, int maxPeriod = 48, int avgLength = 3, bool enhance = true)
|
||||
{
|
||||
if (minPeriod < 3)
|
||||
{
|
||||
@@ -126,7 +126,7 @@ public sealed class Eacp : AbstractBase
|
||||
_p_smooth = new double[size];
|
||||
_filtHistory = new RingBuffer(size + maxPeriod);
|
||||
|
||||
Name = $"Eacp({minPeriod},{maxPeriod})";
|
||||
Name = $"Acp({minPeriod},{maxPeriod})";
|
||||
WarmupPeriod = maxPeriod * 2;
|
||||
|
||||
// Initialize state
|
||||
@@ -138,7 +138,7 @@ public sealed class Eacp : AbstractBase
|
||||
/// <summary>
|
||||
/// Creates a chained Ehlers Autocorrelation Periodogram indicator.
|
||||
/// </summary>
|
||||
public Eacp(ITValuePublisher source, int minPeriod = 8, int maxPeriod = 48, int avgLength = 3, bool enhance = true)
|
||||
public Acp(ITValuePublisher source, int minPeriod = 8, int maxPeriod = 48, int avgLength = 3, bool enhance = true)
|
||||
: this(minPeriod, maxPeriod, avgLength, enhance)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(source);
|
||||
@@ -424,17 +424,17 @@ public sealed class Eacp : AbstractBase
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates EACP for a time series.
|
||||
/// Calculates ACP for a time series.
|
||||
/// </summary>
|
||||
public static TSeries Batch(TSeries source, int minPeriod = 8, int maxPeriod = 48,
|
||||
int avgLength = 3, bool enhance = true)
|
||||
{
|
||||
var eacp = new Eacp(minPeriod, maxPeriod, avgLength, enhance);
|
||||
return eacp.Update(source);
|
||||
var acp = new Acp(minPeriod, maxPeriod, avgLength, enhance);
|
||||
return acp.Update(source);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates EACP in-place using a pre-allocated output span.
|
||||
/// Calculates ACP in-place using a pre-allocated output span.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Batch(ReadOnlySpan<double> source, Span<double> output,
|
||||
@@ -461,17 +461,17 @@ public sealed class Eacp : AbstractBase
|
||||
}
|
||||
|
||||
// Use streaming implementation for batch (complex state management)
|
||||
var eacp = new Eacp(minPeriod, maxPeriod, avgLength, enhance);
|
||||
var acp = new Acp(minPeriod, maxPeriod, avgLength, enhance);
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
var result = eacp.Update(new TValue(DateTime.MinValue, source[i]));
|
||||
var result = acp.Update(new TValue(DateTime.MinValue, source[i]));
|
||||
output[i] = result.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public static (TSeries Results, Eacp Indicator) Calculate(TSeries source, int minPeriod = 8, int maxPeriod = 48, int avgLength = 3, bool enhance = true)
|
||||
public static (TSeries Results, Acp Indicator) Calculate(TSeries source, int minPeriod = 8, int maxPeriod = 48, int avgLength = 3, bool enhance = true)
|
||||
{
|
||||
var indicator = new Eacp(minPeriod, maxPeriod, avgLength, enhance);
|
||||
var indicator = new Acp(minPeriod, maxPeriod, avgLength, enhance);
|
||||
TSeries results = indicator.Update(source);
|
||||
return (results, indicator);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
# EACP: Ehlers Autocorrelation Periodogram
|
||||
# ACP: Ehlers Autocorrelation Periodogram
|
||||
|
||||
> *Autocorrelation periodogram scans every possible cycle length and ranks them by strength — a spectral fingerprint of the market.*
|
||||
|
||||
@@ -7,16 +7,16 @@
|
||||
| **Category** | Cycle |
|
||||
| **Inputs** | Source (close) |
|
||||
| **Parameters** | `minPeriod` (default 8), `maxPeriod` (default 48), `avgLength` (default 3), `enhance` (default true) |
|
||||
| **Outputs** | Single series (Eacp) |
|
||||
| **Outputs** | Single series (Acp) |
|
||||
| **Output range** | Varies (see docs) |
|
||||
| **Warmup** | `maxPeriod * 2` bars |
|
||||
| **PineScript** | [eacp.pine](eacp.pine) |
|
||||
| **PineScript** | [acp.pine](acp.pine) |
|
||||
|
||||
- EACP estimates the dominant cycle period of a financial time series by computing autocorrelation across multiple lags and transforming the result i...
|
||||
- ACP estimates the dominant cycle period of a financial time series by computing autocorrelation across multiple lags and transforming the result into a power spectrum via the Wiener-Khinchin theorem.
|
||||
- **Similar:** [CG](../cg/cg.md), [HT_DCPeriod](../ht_dcperiod/ht_dcperiod.md) | **Complementary:** EBSW for trend/cycle classification | **Trading note:** Ehlers Autocorrelation Periodogram; identifies dominant cycle length adaptively.
|
||||
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
|
||||
|
||||
EACP estimates the dominant cycle period of a financial time series by computing autocorrelation across multiple lags and transforming the result into a power spectrum via the Wiener-Khinchin theorem. The output is a continuously updating cycle period measurement (in bars) that can adaptively tune other indicators to the market's current rhythm, making fixed-period assumptions unnecessary.
|
||||
ACP estimates the dominant cycle period of a financial time series by computing autocorrelation across multiple lags and transforming the result into a power spectrum via the Wiener-Khinchin theorem. The output is a continuously updating cycle period measurement (in bars) that can adaptively tune other indicators to the market's current rhythm, making fixed-period assumptions unnecessary.
|
||||
|
||||
## Historical Context
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("Ehlers Autocorrelation Periodogram (EACP)","EACP",overlay=false)
|
||||
indicator("Ehlers Autocorrelation Periodogram (ACP)","ACP",overlay=false)
|
||||
//@function Autocorrelation periodogram dominant cycle estimator
|
||||
//@param source Price input series
|
||||
//@param minPeriod Minimum period to evaluate
|
||||
@@ -11,7 +11,7 @@ indicator("Ehlers Autocorrelation Periodogram (EACP)","EACP",overlay=false)
|
||||
//@returns Smoothed dominant cycle estimate
|
||||
//@optimized Removed buffer complexity, uses native PineScript historical operator for O(n) correlation
|
||||
//@validation wolfram:"Wiener-Khinchin theorem","Pearson correlation coefficient" external:"TradingView TASC 2025.02 Autocorrelation","ImmortalFreedom Ehlers ACP","QuantStrat autocorrPeriodogram"
|
||||
eacp(series float source,simple int minPeriod,simple int maxPeriod,simple int avgLength,simple bool enhance)=>
|
||||
acp(series float source,simple int minPeriod,simple int maxPeriod,simple int avgLength,simple bool enhance)=>
|
||||
if minPeriod<3
|
||||
runtime.error("Min period must be at least 3")
|
||||
if maxPeriod<=minPeriod
|
||||
@@ -140,6 +140,6 @@ i_minPeriod=input.int(8,"Min Period",minval=3,maxval=500)
|
||||
i_maxPeriod=input.int(48,"Max Period",minval=4,maxval=500)
|
||||
i_avgLength=input.int(3,"Autocorrelation Length",minval=0,maxval=500)
|
||||
i_enhance=input.bool(true,"Enhance Resolution")
|
||||
[dominantCycle,normalizedPower]=eacp(i_source,i_minPeriod,i_maxPeriod,i_avgLength,i_enhance)
|
||||
[dominantCycle,normalizedPower]=acp(i_source,i_minPeriod,i_maxPeriod,i_avgLength,i_enhance)
|
||||
plot(dominantCycle,"Dominant Cycle",color=color.yellow,linewidth=2)
|
||||
plot(normalizedPower,"Normalized Power",color=color.orange,linewidth=2)
|
||||
+51
-51
@@ -2,12 +2,12 @@ using TradingPlatform.BusinessLayer;
|
||||
|
||||
namespace QuanTAlib.Quantower.Tests;
|
||||
|
||||
public class EacpIndicatorTests
|
||||
public class AcpIndicatorTests
|
||||
{
|
||||
[Fact]
|
||||
public void EacpIndicator_Constructor_SetsDefaults()
|
||||
public void AcpIndicator_Constructor_SetsDefaults()
|
||||
{
|
||||
var indicator = new EacpIndicator();
|
||||
var indicator = new AcpIndicator();
|
||||
|
||||
Assert.Equal(8, indicator.MinPeriod);
|
||||
Assert.Equal(48, indicator.MaxPeriod);
|
||||
@@ -15,34 +15,34 @@ public class EacpIndicatorTests
|
||||
Assert.True(indicator.Enhance);
|
||||
Assert.Equal(SourceType.Close, indicator.Source);
|
||||
Assert.True(indicator.ShowColdValues);
|
||||
Assert.Equal("EACP - Ehlers Autocorrelation Periodogram", indicator.Name);
|
||||
Assert.Equal("ACP - Ehlers Autocorrelation Periodogram", indicator.Name);
|
||||
Assert.True(indicator.SeparateWindow);
|
||||
Assert.True(indicator.OnBackGround);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EacpIndicator_MinHistoryDepths_EqualsZero()
|
||||
public void AcpIndicator_MinHistoryDepths_EqualsZero()
|
||||
{
|
||||
var indicator = new EacpIndicator();
|
||||
var indicator = new AcpIndicator();
|
||||
|
||||
Assert.Equal(0, EacpIndicator.MinHistoryDepths);
|
||||
Assert.Equal(0, AcpIndicator.MinHistoryDepths);
|
||||
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EacpIndicator_ShortName_IncludesPeriods()
|
||||
public void AcpIndicator_ShortName_IncludesPeriods()
|
||||
{
|
||||
var indicator = new EacpIndicator { MinPeriod = 10, MaxPeriod = 60 };
|
||||
var indicator = new AcpIndicator { MinPeriod = 10, MaxPeriod = 60 };
|
||||
|
||||
Assert.True(indicator.ShortName.Contains("EACP", StringComparison.Ordinal));
|
||||
Assert.True(indicator.ShortName.Contains("ACP", StringComparison.Ordinal));
|
||||
Assert.True(indicator.ShortName.Contains("10", StringComparison.Ordinal));
|
||||
Assert.True(indicator.ShortName.Contains("60", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EacpIndicator_Initialize_CreatesInternalEacp()
|
||||
public void AcpIndicator_Initialize_CreatesInternalAcp()
|
||||
{
|
||||
var indicator = new EacpIndicator { MinPeriod = 8, MaxPeriod = 48 };
|
||||
var indicator = new AcpIndicator { MinPeriod = 8, MaxPeriod = 48 };
|
||||
|
||||
// Initialize should not throw
|
||||
indicator.Initialize();
|
||||
@@ -52,9 +52,9 @@ public class EacpIndicatorTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EacpIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
|
||||
public void AcpIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
|
||||
{
|
||||
var indicator = new EacpIndicator { MinPeriod = 8, MaxPeriod = 48 };
|
||||
var indicator = new AcpIndicator { MinPeriod = 8, MaxPeriod = 48 };
|
||||
indicator.Initialize();
|
||||
|
||||
// Add historical data
|
||||
@@ -71,9 +71,9 @@ public class EacpIndicatorTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EacpIndicator_ProcessUpdate_NewBar_ComputesValue()
|
||||
public void AcpIndicator_ProcessUpdate_NewBar_ComputesValue()
|
||||
{
|
||||
var indicator = new EacpIndicator { MinPeriod = 8, MaxPeriod = 48 };
|
||||
var indicator = new AcpIndicator { MinPeriod = 8, MaxPeriod = 48 };
|
||||
indicator.Initialize();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
@@ -87,9 +87,9 @@ public class EacpIndicatorTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EacpIndicator_ProcessUpdate_NewTick_ProcessesWithoutError()
|
||||
public void AcpIndicator_ProcessUpdate_NewTick_ProcessesWithoutError()
|
||||
{
|
||||
var indicator = new EacpIndicator { MinPeriod = 8, MaxPeriod = 48 };
|
||||
var indicator = new AcpIndicator { MinPeriod = 8, MaxPeriod = 48 };
|
||||
indicator.Initialize();
|
||||
|
||||
// Should not throw an exception
|
||||
@@ -100,9 +100,9 @@ public class EacpIndicatorTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EacpIndicator_MultipleUpdates_ProducesCorrectSequence()
|
||||
public void AcpIndicator_MultipleUpdates_ProducesCorrectSequence()
|
||||
{
|
||||
var indicator = new EacpIndicator { MinPeriod = 8, MaxPeriod = 48 };
|
||||
var indicator = new AcpIndicator { MinPeriod = 8, MaxPeriod = 48 };
|
||||
indicator.Initialize();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
@@ -123,13 +123,13 @@ public class EacpIndicatorTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EacpIndicator_DifferentSourceTypes_Work()
|
||||
public void AcpIndicator_DifferentSourceTypes_Work()
|
||||
{
|
||||
var sources = new[] { SourceType.Open, SourceType.High, SourceType.Low, SourceType.Close, SourceType.HL2, SourceType.HLC3 };
|
||||
|
||||
foreach (var source in sources)
|
||||
{
|
||||
var indicator = new EacpIndicator { MinPeriod = 8, MaxPeriod = 48, Source = source };
|
||||
var indicator = new AcpIndicator { MinPeriod = 8, MaxPeriod = 48, Source = source };
|
||||
indicator.Initialize();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
@@ -142,9 +142,9 @@ public class EacpIndicatorTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EacpIndicator_MinPeriod_CanBeChanged()
|
||||
public void AcpIndicator_MinPeriod_CanBeChanged()
|
||||
{
|
||||
var indicator = new EacpIndicator { MinPeriod = 8 };
|
||||
var indicator = new AcpIndicator { MinPeriod = 8 };
|
||||
|
||||
Assert.Equal(8, indicator.MinPeriod);
|
||||
|
||||
@@ -153,9 +153,9 @@ public class EacpIndicatorTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EacpIndicator_MaxPeriod_CanBeChanged()
|
||||
public void AcpIndicator_MaxPeriod_CanBeChanged()
|
||||
{
|
||||
var indicator = new EacpIndicator { MaxPeriod = 48 };
|
||||
var indicator = new AcpIndicator { MaxPeriod = 48 };
|
||||
|
||||
Assert.Equal(48, indicator.MaxPeriod);
|
||||
|
||||
@@ -164,9 +164,9 @@ public class EacpIndicatorTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EacpIndicator_AvgLength_CanBeChanged()
|
||||
public void AcpIndicator_AvgLength_CanBeChanged()
|
||||
{
|
||||
var indicator = new EacpIndicator { AvgLength = 3 };
|
||||
var indicator = new AcpIndicator { AvgLength = 3 };
|
||||
|
||||
Assert.Equal(3, indicator.AvgLength);
|
||||
|
||||
@@ -175,9 +175,9 @@ public class EacpIndicatorTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EacpIndicator_Enhance_CanBeChanged()
|
||||
public void AcpIndicator_Enhance_CanBeChanged()
|
||||
{
|
||||
var indicator = new EacpIndicator { Enhance = true };
|
||||
var indicator = new AcpIndicator { Enhance = true };
|
||||
|
||||
Assert.True(indicator.Enhance);
|
||||
|
||||
@@ -186,9 +186,9 @@ public class EacpIndicatorTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EacpIndicator_Source_CanBeChanged()
|
||||
public void AcpIndicator_Source_CanBeChanged()
|
||||
{
|
||||
var indicator = new EacpIndicator { Source = SourceType.Close };
|
||||
var indicator = new AcpIndicator { Source = SourceType.Close };
|
||||
|
||||
Assert.Equal(SourceType.Close, indicator.Source);
|
||||
|
||||
@@ -197,9 +197,9 @@ public class EacpIndicatorTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EacpIndicator_ShowColdValues_CanBeChanged()
|
||||
public void AcpIndicator_ShowColdValues_CanBeChanged()
|
||||
{
|
||||
var indicator = new EacpIndicator { ShowColdValues = true };
|
||||
var indicator = new AcpIndicator { ShowColdValues = true };
|
||||
|
||||
Assert.True(indicator.ShowColdValues);
|
||||
|
||||
@@ -208,9 +208,9 @@ public class EacpIndicatorTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EacpIndicator_ShortName_UpdatesWhenPeriodsChange()
|
||||
public void AcpIndicator_ShortName_UpdatesWhenPeriodsChange()
|
||||
{
|
||||
var indicator = new EacpIndicator { MinPeriod = 8, MaxPeriod = 48 };
|
||||
var indicator = new AcpIndicator { MinPeriod = 8, MaxPeriod = 48 };
|
||||
string initialName = indicator.ShortName;
|
||||
|
||||
Assert.True(initialName.Contains("8", StringComparison.Ordinal));
|
||||
@@ -225,9 +225,9 @@ public class EacpIndicatorTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EacpIndicator_ProcessUpdate_IgnoresNonBarUpdates()
|
||||
public void AcpIndicator_ProcessUpdate_IgnoresNonBarUpdates()
|
||||
{
|
||||
var indicator = new EacpIndicator { MinPeriod = 8, MaxPeriod = 48 };
|
||||
var indicator = new AcpIndicator { MinPeriod = 8, MaxPeriod = 48 };
|
||||
indicator.Initialize();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
@@ -243,9 +243,9 @@ public class EacpIndicatorTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EacpIndicator_CycleSeries_HasCorrectProperties()
|
||||
public void AcpIndicator_CycleSeries_HasCorrectProperties()
|
||||
{
|
||||
var indicator = new EacpIndicator { MinPeriod = 8, MaxPeriod = 48 };
|
||||
var indicator = new AcpIndicator { MinPeriod = 8, MaxPeriod = 48 };
|
||||
indicator.Initialize();
|
||||
|
||||
var lineSeries = indicator.LinesSeries[0];
|
||||
@@ -256,9 +256,9 @@ public class EacpIndicatorTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EacpIndicator_PowerSeries_HasCorrectProperties()
|
||||
public void AcpIndicator_PowerSeries_HasCorrectProperties()
|
||||
{
|
||||
var indicator = new EacpIndicator { MinPeriod = 8, MaxPeriod = 48 };
|
||||
var indicator = new AcpIndicator { MinPeriod = 8, MaxPeriod = 48 };
|
||||
indicator.Initialize();
|
||||
|
||||
var powerSeries = indicator.LinesSeries[1];
|
||||
@@ -269,13 +269,13 @@ public class EacpIndicatorTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EacpIndicator_DifferentPeriodRanges_Work()
|
||||
public void AcpIndicator_DifferentPeriodRanges_Work()
|
||||
{
|
||||
var periodRanges = new[] { (8, 48), (10, 60), (6, 30), (12, 100) };
|
||||
|
||||
foreach (var (minPeriod, maxPeriod) in periodRanges)
|
||||
{
|
||||
var indicator = new EacpIndicator { MinPeriod = minPeriod, MaxPeriod = maxPeriod };
|
||||
var indicator = new AcpIndicator { MinPeriod = minPeriod, MaxPeriod = maxPeriod };
|
||||
indicator.Initialize();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
@@ -294,9 +294,9 @@ public class EacpIndicatorTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EacpIndicator_SineWave_DetectsCycle()
|
||||
public void AcpIndicator_SineWave_DetectsCycle()
|
||||
{
|
||||
var indicator = new EacpIndicator { MinPeriod = 8, MaxPeriod = 48 };
|
||||
var indicator = new AcpIndicator { MinPeriod = 8, MaxPeriod = 48 };
|
||||
indicator.Initialize();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
@@ -316,9 +316,9 @@ public class EacpIndicatorTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EacpIndicator_PowerOutput_ScaledCorrectly()
|
||||
public void AcpIndicator_PowerOutput_ScaledCorrectly()
|
||||
{
|
||||
var indicator = new EacpIndicator { MinPeriod = 8, MaxPeriod = 48 };
|
||||
var indicator = new AcpIndicator { MinPeriod = 8, MaxPeriod = 48 };
|
||||
indicator.Initialize();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
@@ -336,10 +336,10 @@ public class EacpIndicatorTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EacpIndicator_EnhanceMode_AffectsOutput()
|
||||
public void AcpIndicator_EnhanceMode_AffectsOutput()
|
||||
{
|
||||
var indicatorEnhanced = new EacpIndicator { MinPeriod = 8, MaxPeriod = 48, Enhance = true };
|
||||
var indicatorNormal = new EacpIndicator { MinPeriod = 8, MaxPeriod = 48, Enhance = false };
|
||||
var indicatorEnhanced = new AcpIndicator { MinPeriod = 8, MaxPeriod = 48, Enhance = true };
|
||||
var indicatorNormal = new AcpIndicator { MinPeriod = 8, MaxPeriod = 48, Enhance = false };
|
||||
indicatorEnhanced.Initialize();
|
||||
indicatorNormal.Initialize();
|
||||
|
||||
@@ -2,7 +2,7 @@ using Xunit;
|
||||
|
||||
namespace QuanTAlib.Tests;
|
||||
|
||||
public class EacpTests
|
||||
public class AcpTests
|
||||
{
|
||||
private const double Tolerance = 1e-9;
|
||||
|
||||
@@ -11,18 +11,18 @@ public class EacpTests
|
||||
[Fact]
|
||||
public void Constructor_DefaultParameters_SetsProperties()
|
||||
{
|
||||
var eacp = new Eacp();
|
||||
var acp = new Acp();
|
||||
|
||||
Assert.Equal("Eacp(8,48)", eacp.Name);
|
||||
Assert.False(eacp.IsHot);
|
||||
Assert.Equal("Acp(8,48)", acp.Name);
|
||||
Assert.False(acp.IsHot);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_CustomParameters_SetsProperties()
|
||||
{
|
||||
var eacp = new Eacp(minPeriod: 10, maxPeriod: 60, avgLength: 5, enhance: false);
|
||||
var acp = new Acp(minPeriod: 10, maxPeriod: 60, avgLength: 5, enhance: false);
|
||||
|
||||
Assert.Equal("Eacp(10,60)", eacp.Name);
|
||||
Assert.Equal("Acp(10,60)", acp.Name);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -31,7 +31,7 @@ public class EacpTests
|
||||
[InlineData(-1)]
|
||||
public void Constructor_InvalidMinPeriod_ThrowsArgumentOutOfRange(int minPeriod)
|
||||
{
|
||||
var ex = Assert.Throws<ArgumentOutOfRangeException>(() => new Eacp(minPeriod, 48));
|
||||
var ex = Assert.Throws<ArgumentOutOfRangeException>(() => new Acp(minPeriod, 48));
|
||||
Assert.Equal("minPeriod", ex.ParamName);
|
||||
}
|
||||
|
||||
@@ -41,31 +41,31 @@ public class EacpTests
|
||||
[InlineData(10, 10)]
|
||||
public void Constructor_MaxPeriodNotGreaterThanMin_ThrowsArgumentOutOfRange(int minPeriod, int maxPeriod)
|
||||
{
|
||||
var ex = Assert.Throws<ArgumentOutOfRangeException>(() => new Eacp(minPeriod, maxPeriod));
|
||||
var ex = Assert.Throws<ArgumentOutOfRangeException>(() => new Acp(minPeriod, maxPeriod));
|
||||
Assert.Equal("maxPeriod", ex.ParamName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_NegativeAvgLength_ThrowsArgumentOutOfRange()
|
||||
{
|
||||
var ex = Assert.Throws<ArgumentOutOfRangeException>(() => new Eacp(8, 48, avgLength: -1));
|
||||
var ex = Assert.Throws<ArgumentOutOfRangeException>(() => new Acp(8, 48, avgLength: -1));
|
||||
Assert.Equal("avgLength", ex.ParamName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithNullSource_ThrowsArgumentNullException()
|
||||
{
|
||||
Assert.Throws<ArgumentNullException>(() => new Eacp(null!, 8, 48));
|
||||
Assert.Throws<ArgumentNullException>(() => new Acp(null!, 8, 48));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithValidSource_Subscribes()
|
||||
{
|
||||
var source = new TSeries();
|
||||
var eacp = new Eacp(source, 8, 48);
|
||||
var acp = new Acp(source, 8, 48);
|
||||
|
||||
source.Add(new TValue(DateTime.UtcNow, 100.0));
|
||||
Assert.NotEqual(default, eacp.Last);
|
||||
Assert.NotEqual(default, acp.Last);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -75,8 +75,8 @@ public class EacpTests
|
||||
[Fact]
|
||||
public void Update_ReturnsValidTValue()
|
||||
{
|
||||
var eacp = new Eacp(8, 48);
|
||||
var result = eacp.Update(new TValue(DateTime.UtcNow, 100.0));
|
||||
var acp = new Acp(8, 48);
|
||||
var result = acp.Update(new TValue(DateTime.UtcNow, 100.0));
|
||||
|
||||
Assert.True(double.IsFinite(result.Value));
|
||||
}
|
||||
@@ -84,59 +84,59 @@ public class EacpTests
|
||||
[Fact]
|
||||
public void Update_AfterWarmup_IsHotTrue()
|
||||
{
|
||||
var eacp = new Eacp(8, 48);
|
||||
var acp = new Acp(8, 48);
|
||||
|
||||
var gbm = new GBM(seed: 42);
|
||||
var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
foreach (var bar in bars)
|
||||
{
|
||||
eacp.Update(new TValue(bar.Time, bar.Close));
|
||||
acp.Update(new TValue(bar.Time, bar.Close));
|
||||
}
|
||||
|
||||
Assert.True(eacp.IsHot);
|
||||
Assert.True(acp.IsHot);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_DominantCycle_WithinRange()
|
||||
{
|
||||
var eacp = new Eacp(8, 48);
|
||||
var acp = new Acp(8, 48);
|
||||
|
||||
var gbm = new GBM(seed: 42);
|
||||
var bars = gbm.Fetch(500, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
foreach (var bar in bars)
|
||||
{
|
||||
eacp.Update(new TValue(bar.Time, bar.Close));
|
||||
acp.Update(new TValue(bar.Time, bar.Close));
|
||||
}
|
||||
|
||||
// Dominant cycle should be within the specified range
|
||||
Assert.InRange(eacp.DominantCycle, 8, 48);
|
||||
Assert.InRange(acp.DominantCycle, 8, 48);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_NormalizedPower_BetweenZeroAndOne()
|
||||
{
|
||||
var eacp = new Eacp(8, 48);
|
||||
var acp = new Acp(8, 48);
|
||||
|
||||
var gbm = new GBM(seed: 42);
|
||||
var bars = gbm.Fetch(500, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
foreach (var bar in bars)
|
||||
{
|
||||
eacp.Update(new TValue(bar.Time, bar.Close));
|
||||
acp.Update(new TValue(bar.Time, bar.Close));
|
||||
}
|
||||
|
||||
Assert.InRange(eacp.NormalizedPower, 0, 1);
|
||||
Assert.InRange(acp.NormalizedPower, 0, 1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_InitialValue_NearMidpoint()
|
||||
{
|
||||
var eacp = new Eacp(8, 48);
|
||||
var acp = new Acp(8, 48);
|
||||
|
||||
// First update should return near midpoint of range
|
||||
var result = eacp.Update(new TValue(DateTime.UtcNow, 100.0));
|
||||
var result = acp.Update(new TValue(DateTime.UtcNow, 100.0));
|
||||
|
||||
// Initial dominant cycle starts at (8+48)/2 = 28
|
||||
Assert.True(result.Value >= 8 && result.Value <= 48);
|
||||
@@ -149,13 +149,13 @@ public class EacpTests
|
||||
[Fact]
|
||||
public void Update_IsNewTrue_AdvancesState()
|
||||
{
|
||||
var eacp = new Eacp(8, 48);
|
||||
var acp = new Acp(8, 48);
|
||||
|
||||
eacp.Update(new TValue(DateTime.UtcNow, 100.0), isNew: true);
|
||||
var first = eacp.Last.Value;
|
||||
acp.Update(new TValue(DateTime.UtcNow, 100.0), isNew: true);
|
||||
var first = acp.Last.Value;
|
||||
|
||||
eacp.Update(new TValue(DateTime.UtcNow.AddSeconds(1), 110.0), isNew: true);
|
||||
var second = eacp.Last.Value;
|
||||
acp.Update(new TValue(DateTime.UtcNow.AddSeconds(1), 110.0), isNew: true);
|
||||
var second = acp.Last.Value;
|
||||
|
||||
// Values should potentially differ
|
||||
Assert.True(double.IsFinite(first) && double.IsFinite(second));
|
||||
@@ -164,20 +164,20 @@ public class EacpTests
|
||||
[Fact]
|
||||
public void Update_IsNewFalse_ReplacesCurrentBar()
|
||||
{
|
||||
var eacp = new Eacp(8, 48);
|
||||
var acp = new Acp(8, 48);
|
||||
|
||||
// Build some history
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
eacp.Update(new TValue(DateTime.UtcNow.AddSeconds(i), 100.0 + Math.Sin(i * 0.1) * 10), isNew: true);
|
||||
acp.Update(new TValue(DateTime.UtcNow.AddSeconds(i), 100.0 + Math.Sin(i * 0.1) * 10), isNew: true);
|
||||
}
|
||||
|
||||
eacp.Update(new TValue(DateTime.UtcNow.AddSeconds(100), 110.0), isNew: true);
|
||||
var beforeCorrection = eacp.Last.Value;
|
||||
acp.Update(new TValue(DateTime.UtcNow.AddSeconds(100), 110.0), isNew: true);
|
||||
var beforeCorrection = acp.Last.Value;
|
||||
|
||||
// Correct the bar with a different value
|
||||
eacp.Update(new TValue(DateTime.UtcNow.AddSeconds(100), 90.0), isNew: false);
|
||||
var afterCorrection = eacp.Last.Value;
|
||||
acp.Update(new TValue(DateTime.UtcNow.AddSeconds(100), 90.0), isNew: false);
|
||||
var afterCorrection = acp.Last.Value;
|
||||
|
||||
// Values should differ after correction
|
||||
Assert.True(double.IsFinite(beforeCorrection) && double.IsFinite(afterCorrection));
|
||||
@@ -186,23 +186,23 @@ public class EacpTests
|
||||
[Fact]
|
||||
public void Update_MultipleCorrections_RestoresToSnapshot()
|
||||
{
|
||||
var eacp = new Eacp(8, 48);
|
||||
var acp = new Acp(8, 48);
|
||||
|
||||
// Build some history
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
eacp.Update(new TValue(DateTime.UtcNow.AddSeconds(i), 100.0 + i), isNew: true);
|
||||
acp.Update(new TValue(DateTime.UtcNow.AddSeconds(i), 100.0 + i), isNew: true);
|
||||
}
|
||||
|
||||
// Add a new bar
|
||||
eacp.Update(new TValue(DateTime.UtcNow.AddSeconds(100), 150.0), isNew: true);
|
||||
var originalValue = eacp.Last.Value;
|
||||
acp.Update(new TValue(DateTime.UtcNow.AddSeconds(100), 150.0), isNew: true);
|
||||
var originalValue = acp.Last.Value;
|
||||
|
||||
// Correct multiple times
|
||||
eacp.Update(new TValue(DateTime.UtcNow.AddSeconds(100), 160.0), isNew: false);
|
||||
eacp.Update(new TValue(DateTime.UtcNow.AddSeconds(100), 140.0), isNew: false);
|
||||
eacp.Update(new TValue(DateTime.UtcNow.AddSeconds(100), 150.0), isNew: false);
|
||||
var restoredValue = eacp.Last.Value;
|
||||
acp.Update(new TValue(DateTime.UtcNow.AddSeconds(100), 160.0), isNew: false);
|
||||
acp.Update(new TValue(DateTime.UtcNow.AddSeconds(100), 140.0), isNew: false);
|
||||
acp.Update(new TValue(DateTime.UtcNow.AddSeconds(100), 150.0), isNew: false);
|
||||
var restoredValue = acp.Last.Value;
|
||||
|
||||
Assert.Equal(originalValue, restoredValue, Tolerance);
|
||||
}
|
||||
@@ -214,41 +214,41 @@ public class EacpTests
|
||||
[Fact]
|
||||
public void Reset_ClearsState()
|
||||
{
|
||||
var eacp = new Eacp(8, 48);
|
||||
var acp = new Acp(8, 48);
|
||||
|
||||
for (int i = 0; i < 200; i++)
|
||||
{
|
||||
eacp.Update(new TValue(DateTime.UtcNow.AddSeconds(i), 100.0 + i));
|
||||
acp.Update(new TValue(DateTime.UtcNow.AddSeconds(i), 100.0 + i));
|
||||
}
|
||||
|
||||
Assert.True(eacp.IsHot);
|
||||
Assert.True(acp.IsHot);
|
||||
|
||||
eacp.Reset();
|
||||
acp.Reset();
|
||||
|
||||
Assert.False(eacp.IsHot);
|
||||
Assert.Equal(default, eacp.Last);
|
||||
Assert.False(acp.IsHot);
|
||||
Assert.Equal(default, acp.Last);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Reset_AllowsReuse()
|
||||
{
|
||||
var eacp = new Eacp(8, 48);
|
||||
var acp = new Acp(8, 48);
|
||||
|
||||
// First run
|
||||
for (int i = 0; i < 200; i++)
|
||||
{
|
||||
eacp.Update(new TValue(DateTime.UtcNow.AddSeconds(i), 100.0 + Math.Sin(i * 0.1) * 10));
|
||||
acp.Update(new TValue(DateTime.UtcNow.AddSeconds(i), 100.0 + Math.Sin(i * 0.1) * 10));
|
||||
}
|
||||
var firstResult = eacp.Last.Value;
|
||||
var firstResult = acp.Last.Value;
|
||||
|
||||
eacp.Reset();
|
||||
acp.Reset();
|
||||
|
||||
// Second run with same data
|
||||
for (int i = 0; i < 200; i++)
|
||||
{
|
||||
eacp.Update(new TValue(DateTime.UtcNow.AddSeconds(i), 100.0 + Math.Sin(i * 0.1) * 10));
|
||||
acp.Update(new TValue(DateTime.UtcNow.AddSeconds(i), 100.0 + Math.Sin(i * 0.1) * 10));
|
||||
}
|
||||
var secondResult = eacp.Last.Value;
|
||||
var secondResult = acp.Last.Value;
|
||||
|
||||
Assert.Equal(firstResult, secondResult, Tolerance);
|
||||
}
|
||||
@@ -260,34 +260,34 @@ public class EacpTests
|
||||
[Fact]
|
||||
public void Update_NaN_UsesLastValidValue()
|
||||
{
|
||||
var eacp = new Eacp(8, 48);
|
||||
var acp = new Acp(8, 48);
|
||||
|
||||
eacp.Update(new TValue(DateTime.UtcNow, 100.0));
|
||||
eacp.Update(new TValue(DateTime.UtcNow.AddSeconds(1), double.NaN));
|
||||
acp.Update(new TValue(DateTime.UtcNow, 100.0));
|
||||
acp.Update(new TValue(DateTime.UtcNow.AddSeconds(1), double.NaN));
|
||||
|
||||
Assert.True(double.IsFinite(eacp.Last.Value));
|
||||
Assert.True(double.IsFinite(acp.Last.Value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_Infinity_UsesLastValidValue()
|
||||
{
|
||||
var eacp = new Eacp(8, 48);
|
||||
var acp = new Acp(8, 48);
|
||||
|
||||
eacp.Update(new TValue(DateTime.UtcNow, 100.0));
|
||||
eacp.Update(new TValue(DateTime.UtcNow.AddSeconds(1), double.PositiveInfinity));
|
||||
acp.Update(new TValue(DateTime.UtcNow, 100.0));
|
||||
acp.Update(new TValue(DateTime.UtcNow.AddSeconds(1), double.PositiveInfinity));
|
||||
|
||||
Assert.True(double.IsFinite(eacp.Last.Value));
|
||||
Assert.True(double.IsFinite(acp.Last.Value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_NegativeInfinity_UsesLastValidValue()
|
||||
{
|
||||
var eacp = new Eacp(8, 48);
|
||||
var acp = new Acp(8, 48);
|
||||
|
||||
eacp.Update(new TValue(DateTime.UtcNow, 100.0));
|
||||
eacp.Update(new TValue(DateTime.UtcNow.AddSeconds(1), double.NegativeInfinity));
|
||||
acp.Update(new TValue(DateTime.UtcNow, 100.0));
|
||||
acp.Update(new TValue(DateTime.UtcNow.AddSeconds(1), double.NegativeInfinity));
|
||||
|
||||
Assert.True(double.IsFinite(eacp.Last.Value));
|
||||
Assert.True(double.IsFinite(acp.Last.Value));
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -308,7 +308,7 @@ public class EacpTests
|
||||
var bars = gbm.Fetch(dataLen, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
// Streaming
|
||||
var streaming = new Eacp(minPeriod, maxPeriod);
|
||||
var streaming = new Acp(minPeriod, maxPeriod);
|
||||
foreach (var bar in bars)
|
||||
{
|
||||
streaming.Update(new TValue(bar.Time, bar.Close));
|
||||
@@ -321,7 +321,7 @@ public class EacpTests
|
||||
tSeries.Add(new TValue(bar.Time, bar.Close));
|
||||
}
|
||||
|
||||
var batch = Eacp.Batch(tSeries, minPeriod, maxPeriod);
|
||||
var batch = Acp.Batch(tSeries, minPeriod, maxPeriod);
|
||||
|
||||
// Compare last values
|
||||
Assert.Equal(batch[^1].Value, streaming.Last.Value, Tolerance);
|
||||
@@ -338,7 +338,7 @@ public class EacpTests
|
||||
var bars = gbm.Fetch(dataLen, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
// Streaming
|
||||
var streaming = new Eacp(minPeriod, maxPeriod);
|
||||
var streaming = new Acp(minPeriod, maxPeriod);
|
||||
var streamingResults = new double[dataLen];
|
||||
for (int i = 0; i < dataLen; i++)
|
||||
{
|
||||
@@ -354,7 +354,7 @@ public class EacpTests
|
||||
source[i] = bars[i].Close;
|
||||
}
|
||||
|
||||
Eacp.Batch(source, batchResults, minPeriod, maxPeriod);
|
||||
Acp.Batch(source, batchResults, minPeriod, maxPeriod);
|
||||
|
||||
// Compare all values
|
||||
for (int i = 0; i < dataLen; i++)
|
||||
@@ -373,7 +373,7 @@ public class EacpTests
|
||||
double[] source = new double[100];
|
||||
double[] output = new double[50];
|
||||
|
||||
var ex = Assert.Throws<ArgumentException>(() => Eacp.Batch(source, output, 8, 48));
|
||||
var ex = Assert.Throws<ArgumentException>(() => Acp.Batch(source, output, 8, 48));
|
||||
Assert.Equal("output", ex.ParamName);
|
||||
}
|
||||
|
||||
@@ -383,7 +383,7 @@ public class EacpTests
|
||||
double[] source = new double[100];
|
||||
double[] output = new double[100];
|
||||
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => Eacp.Batch(source, output, 2, 48));
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => Acp.Batch(source, output, 2, 48));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -392,7 +392,7 @@ public class EacpTests
|
||||
double[] source = new double[100];
|
||||
double[] output = new double[100];
|
||||
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => Eacp.Batch(source, output, 8, 8));
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => Acp.Batch(source, output, 8, 8));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -401,7 +401,7 @@ public class EacpTests
|
||||
double[] source = [];
|
||||
double[] output = [];
|
||||
|
||||
var ex = Record.Exception(() => Eacp.Batch(source, output, 8, 48));
|
||||
var ex = Record.Exception(() => Acp.Batch(source, output, 8, 48));
|
||||
Assert.Null(ex);
|
||||
}
|
||||
|
||||
@@ -411,7 +411,7 @@ public class EacpTests
|
||||
double[] source = { 100, 101, double.NaN, 103, 104, 105, 106, 107, 108, 109 };
|
||||
double[] output = new double[10];
|
||||
|
||||
Eacp.Batch(source, output, 3, 8);
|
||||
Acp.Batch(source, output, 3, 8);
|
||||
|
||||
foreach (double v in output)
|
||||
{
|
||||
@@ -427,23 +427,23 @@ public class EacpTests
|
||||
public void Chaining_PropagatesUpdates()
|
||||
{
|
||||
var source = new TSeries();
|
||||
var eacp = new Eacp(source, 8, 48);
|
||||
var acp = new Acp(source, 8, 48);
|
||||
|
||||
for (int i = 0; i < 200; i++)
|
||||
{
|
||||
source.Add(new TValue(DateTime.UtcNow.AddSeconds(i), 100.0 + Math.Sin(i * 0.1) * 10));
|
||||
}
|
||||
|
||||
Assert.True(eacp.IsHot);
|
||||
Assert.True(double.IsFinite(eacp.Last.Value));
|
||||
Assert.True(acp.IsHot);
|
||||
Assert.True(double.IsFinite(acp.Last.Value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Chaining_MultipleIndicators()
|
||||
{
|
||||
var source = new TSeries();
|
||||
var eacp1 = new Eacp(source, 8, 48);
|
||||
var eacp2 = new Eacp(source, 12, 60);
|
||||
var acp1 = new Acp(source, 8, 48);
|
||||
var acp2 = new Acp(source, 12, 60);
|
||||
|
||||
for (int i = 0; i < 300; i++)
|
||||
{
|
||||
@@ -451,11 +451,11 @@ public class EacpTests
|
||||
}
|
||||
|
||||
// Both should have values
|
||||
Assert.True(double.IsFinite(eacp1.Last.Value));
|
||||
Assert.True(double.IsFinite(eacp2.Last.Value));
|
||||
Assert.True(double.IsFinite(acp1.Last.Value));
|
||||
Assert.True(double.IsFinite(acp2.Last.Value));
|
||||
|
||||
// Different ranges should produce different results
|
||||
Assert.NotEqual(eacp1.Last.Value, eacp2.Last.Value);
|
||||
Assert.NotEqual(acp1.Last.Value, acp2.Last.Value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -468,35 +468,35 @@ public class EacpTests
|
||||
[InlineData(12, 100)]
|
||||
public void Update_DifferentRanges_ProducesValidResults(int minPeriod, int maxPeriod)
|
||||
{
|
||||
var eacp = new Eacp(minPeriod, maxPeriod);
|
||||
var acp = new Acp(minPeriod, maxPeriod);
|
||||
|
||||
var gbm = new GBM(seed: 42);
|
||||
var bars = gbm.Fetch(500, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
foreach (var bar in bars)
|
||||
{
|
||||
eacp.Update(new TValue(bar.Time, bar.Close));
|
||||
acp.Update(new TValue(bar.Time, bar.Close));
|
||||
}
|
||||
|
||||
Assert.True(eacp.IsHot);
|
||||
Assert.InRange(eacp.DominantCycle, minPeriod, maxPeriod);
|
||||
Assert.True(acp.IsHot);
|
||||
Assert.InRange(acp.DominantCycle, minPeriod, maxPeriod);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_EnhanceFalse_ProducesValidResults()
|
||||
{
|
||||
var eacp = new Eacp(8, 48, avgLength: 3, enhance: false);
|
||||
var acp = new Acp(8, 48, avgLength: 3, enhance: false);
|
||||
|
||||
var gbm = new GBM(seed: 42);
|
||||
var bars = gbm.Fetch(500, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
foreach (var bar in bars)
|
||||
{
|
||||
eacp.Update(new TValue(bar.Time, bar.Close));
|
||||
acp.Update(new TValue(bar.Time, bar.Close));
|
||||
}
|
||||
|
||||
Assert.True(eacp.IsHot);
|
||||
Assert.InRange(eacp.DominantCycle, 8, 48);
|
||||
Assert.True(acp.IsHot);
|
||||
Assert.InRange(acp.DominantCycle, 8, 48);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -506,18 +506,18 @@ public class EacpTests
|
||||
[InlineData(10)]
|
||||
public void Update_DifferentAvgLength_ProducesValidResults(int avgLength)
|
||||
{
|
||||
var eacp = new Eacp(8, 48, avgLength: avgLength);
|
||||
var acp = new Acp(8, 48, avgLength: avgLength);
|
||||
|
||||
var gbm = new GBM(seed: 42);
|
||||
var bars = gbm.Fetch(500, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
foreach (var bar in bars)
|
||||
{
|
||||
eacp.Update(new TValue(bar.Time, bar.Close));
|
||||
acp.Update(new TValue(bar.Time, bar.Close));
|
||||
}
|
||||
|
||||
Assert.True(eacp.IsHot);
|
||||
Assert.InRange(eacp.DominantCycle, 8, 48);
|
||||
Assert.True(acp.IsHot);
|
||||
Assert.InRange(acp.DominantCycle, 8, 48);
|
||||
}
|
||||
|
||||
#endregion
|
||||
+70
-70
@@ -3,12 +3,12 @@ using Xunit;
|
||||
namespace QuanTAlib.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Validation tests for EACP (Ehlers Autocorrelation Periodogram).
|
||||
/// EACP is Ehlers' proprietary indicator not commonly implemented in trading libraries
|
||||
/// Validation tests for ACP (Ehlers Autocorrelation Periodogram).
|
||||
/// ACP is Ehlers' proprietary indicator not commonly implemented in trading libraries
|
||||
/// (TA-Lib, Skender, Tulip), so validation is done against mathematical properties
|
||||
/// and known theoretical results based on the original PineScript implementation.
|
||||
/// </summary>
|
||||
public class EacpValidationTests
|
||||
public class AcpValidationTests
|
||||
{
|
||||
private const double Tolerance = 1e-9;
|
||||
|
||||
@@ -19,42 +19,42 @@ public class EacpValidationTests
|
||||
{
|
||||
// For constant input, autocorrelation is undefined but the algorithm
|
||||
// should still produce a value within the valid range
|
||||
var eacp = new Eacp(8, 48, 3, true);
|
||||
var acp = new Acp(8, 48, 3, true);
|
||||
|
||||
for (int i = 0; i < 500; i++)
|
||||
{
|
||||
eacp.Update(new TValue(DateTime.UtcNow.AddSeconds(i), 100.0));
|
||||
acp.Update(new TValue(DateTime.UtcNow.AddSeconds(i), 100.0));
|
||||
}
|
||||
|
||||
Assert.InRange(eacp.DominantCycle, 8, 48);
|
||||
Assert.InRange(eacp.NormalizedPower, 0.0, 1.0);
|
||||
Assert.InRange(acp.DominantCycle, 8, 48);
|
||||
Assert.InRange(acp.NormalizedPower, 0.0, 1.0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validation_SineWave_DetectsPeriod()
|
||||
{
|
||||
// EACP should detect the dominant period in a sine wave
|
||||
// ACP should detect the dominant period in a sine wave
|
||||
const int knownPeriod = 20;
|
||||
var eacp = new Eacp(8, 48, 3, true);
|
||||
var acp = new Acp(8, 48, 3, true);
|
||||
|
||||
// Generate sine wave with known period
|
||||
for (int i = 0; i < 500; i++)
|
||||
{
|
||||
double price = 100.0 + (10.0 * Math.Sin(2.0 * Math.PI * i / knownPeriod));
|
||||
eacp.Update(new TValue(DateTime.UtcNow.AddSeconds(i), price));
|
||||
acp.Update(new TValue(DateTime.UtcNow.AddSeconds(i), price));
|
||||
}
|
||||
|
||||
// Dominant cycle should be close to the known period
|
||||
// Allow 20% tolerance due to filter lag and warmup effects
|
||||
double tolerance = knownPeriod * 0.3;
|
||||
Assert.InRange(eacp.DominantCycle, knownPeriod - tolerance, knownPeriod + tolerance);
|
||||
Assert.InRange(acp.DominantCycle, knownPeriod - tolerance, knownPeriod + tolerance);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validation_MultipleCycles_DetectsDominant()
|
||||
{
|
||||
// When multiple cycles are present, EACP should detect the dominant one
|
||||
var eacp = new Eacp(8, 48, 3, true);
|
||||
// When multiple cycles are present, ACP should detect the dominant one
|
||||
var acp = new Acp(8, 48, 3, true);
|
||||
|
||||
// Generate signal with dominant 16-period cycle and weaker 32-period cycle
|
||||
for (int i = 0; i < 500; i++)
|
||||
@@ -62,26 +62,26 @@ public class EacpValidationTests
|
||||
double cycle16 = 10.0 * Math.Sin(2.0 * Math.PI * i / 16.0); // Stronger
|
||||
double cycle32 = 5.0 * Math.Sin(2.0 * Math.PI * i / 32.0); // Weaker
|
||||
double price = 100.0 + cycle16 + cycle32;
|
||||
eacp.Update(new TValue(DateTime.UtcNow.AddSeconds(i), price));
|
||||
acp.Update(new TValue(DateTime.UtcNow.AddSeconds(i), price));
|
||||
}
|
||||
|
||||
// Should detect the dominant cycle (16) rather than the weaker one
|
||||
Assert.InRange(eacp.DominantCycle, 12, 24);
|
||||
Assert.InRange(acp.DominantCycle, 12, 24);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validation_NormalizedPower_BoundedZeroToOne()
|
||||
{
|
||||
// Normalized power should always be between 0 and 1
|
||||
var eacp = new Eacp(8, 48, 3, true);
|
||||
var acp = new Acp(8, 48, 3, true);
|
||||
|
||||
var gbm = new GBM(seed: 42);
|
||||
var bars = gbm.Fetch(500, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
foreach (var bar in bars)
|
||||
{
|
||||
eacp.Update(new TValue(bar.Time, bar.Close));
|
||||
Assert.InRange(eacp.NormalizedPower, 0.0, 1.0);
|
||||
acp.Update(new TValue(bar.Time, bar.Close));
|
||||
Assert.InRange(acp.NormalizedPower, 0.0, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,8 +104,8 @@ public class EacpValidationTests
|
||||
Assert.InRange(expectedAlphaHP, 0.0, 1.0);
|
||||
|
||||
// The indicator should use this coefficient
|
||||
var eacp = new Eacp(8, maxPeriod);
|
||||
Assert.True(eacp.Name.Contains("48", StringComparison.Ordinal));
|
||||
var acp = new Acp(8, maxPeriod);
|
||||
Assert.True(acp.Name.Contains("48", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -149,20 +149,20 @@ public class EacpValidationTests
|
||||
// Enhance mode applies cubic emphasis (pwr^3)
|
||||
// This should make peaks more pronounced
|
||||
|
||||
var eacpEnhanced = new Eacp(8, 48, 3, enhance: true);
|
||||
var eacpNormal = new Eacp(8, 48, 3, enhance: false);
|
||||
var acpEnhanced = new Acp(8, 48, 3, enhance: true);
|
||||
var acpNormal = new Acp(8, 48, 3, enhance: false);
|
||||
|
||||
// Generate sine wave
|
||||
for (int i = 0; i < 300; i++)
|
||||
{
|
||||
double price = 100.0 + (10.0 * Math.Sin(2.0 * Math.PI * i / 20.0));
|
||||
eacpEnhanced.Update(new TValue(DateTime.UtcNow.AddSeconds(i), price));
|
||||
eacpNormal.Update(new TValue(DateTime.UtcNow.AddSeconds(i), price));
|
||||
acpEnhanced.Update(new TValue(DateTime.UtcNow.AddSeconds(i), price));
|
||||
acpNormal.Update(new TValue(DateTime.UtcNow.AddSeconds(i), price));
|
||||
}
|
||||
|
||||
// Both should produce valid results
|
||||
Assert.InRange(eacpEnhanced.DominantCycle, 8, 48);
|
||||
Assert.InRange(eacpNormal.DominantCycle, 8, 48);
|
||||
Assert.InRange(acpEnhanced.DominantCycle, 8, 48);
|
||||
Assert.InRange(acpNormal.DominantCycle, 8, 48);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -183,7 +183,7 @@ public class EacpValidationTests
|
||||
var bars = gbm.Fetch(dataLen, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
// Streaming
|
||||
var streaming = new Eacp(minPeriod, maxPeriod);
|
||||
var streaming = new Acp(minPeriod, maxPeriod);
|
||||
foreach (var bar in bars)
|
||||
{
|
||||
streaming.Update(new TValue(bar.Time, bar.Close));
|
||||
@@ -196,7 +196,7 @@ public class EacpValidationTests
|
||||
tSeries.Add(new TValue(bar.Time, bar.Close));
|
||||
}
|
||||
|
||||
var batch = Eacp.Batch(tSeries, minPeriod, maxPeriod);
|
||||
var batch = Acp.Batch(tSeries, minPeriod, maxPeriod);
|
||||
|
||||
// Compare last values
|
||||
Assert.Equal(batch[^1].Value, streaming.Last.Value, Tolerance);
|
||||
@@ -219,7 +219,7 @@ public class EacpValidationTests
|
||||
tSeries.Add(new TValue(bar.Time, bar.Close));
|
||||
}
|
||||
|
||||
var tSeriesResult = Eacp.Batch(tSeries, minPeriod, maxPeriod);
|
||||
var tSeriesResult = Acp.Batch(tSeries, minPeriod, maxPeriod);
|
||||
|
||||
// Span approach
|
||||
double[] source = new double[dataLen];
|
||||
@@ -229,7 +229,7 @@ public class EacpValidationTests
|
||||
source[i] = bars[i].Close;
|
||||
}
|
||||
|
||||
Eacp.Batch(source, spanResult, minPeriod, maxPeriod);
|
||||
Acp.Batch(source, spanResult, minPeriod, maxPeriod);
|
||||
|
||||
// Compare all values
|
||||
for (int i = 0; i < dataLen; i++)
|
||||
@@ -252,15 +252,15 @@ public class EacpValidationTests
|
||||
var gbm = new GBM(seed: 42);
|
||||
var bars = gbm.Fetch(500, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
var eacp = new Eacp(minPeriod, maxPeriod);
|
||||
var acp = new Acp(minPeriod, maxPeriod);
|
||||
foreach (var bar in bars)
|
||||
{
|
||||
eacp.Update(new TValue(bar.Time, bar.Close));
|
||||
acp.Update(new TValue(bar.Time, bar.Close));
|
||||
}
|
||||
|
||||
Assert.True(eacp.IsHot);
|
||||
Assert.InRange(eacp.DominantCycle, minPeriod, maxPeriod);
|
||||
Assert.InRange(eacp.NormalizedPower, 0.0, 1.0);
|
||||
Assert.True(acp.IsHot);
|
||||
Assert.InRange(acp.DominantCycle, minPeriod, maxPeriod);
|
||||
Assert.InRange(acp.NormalizedPower, 0.0, 1.0);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -272,14 +272,14 @@ public class EacpValidationTests
|
||||
var gbm = new GBM(seed: 42);
|
||||
var bars = gbm.Fetch(300, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
var eacp = new Eacp(8, 48, avgLength);
|
||||
var acp = new Acp(8, 48, avgLength);
|
||||
foreach (var bar in bars)
|
||||
{
|
||||
eacp.Update(new TValue(bar.Time, bar.Close));
|
||||
acp.Update(new TValue(bar.Time, bar.Close));
|
||||
}
|
||||
|
||||
Assert.True(eacp.IsHot);
|
||||
Assert.InRange(eacp.DominantCycle, 8, 48);
|
||||
Assert.True(acp.IsHot);
|
||||
Assert.InRange(acp.DominantCycle, 8, 48);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -289,46 +289,46 @@ public class EacpValidationTests
|
||||
[Fact]
|
||||
public void Validation_VerySmallPrices_HandledCorrectly()
|
||||
{
|
||||
var eacp = new Eacp(8, 48);
|
||||
var acp = new Acp(8, 48);
|
||||
|
||||
for (int i = 0; i < 200; i++)
|
||||
{
|
||||
double price = 0.0001 + (0.00001 * Math.Sin(2.0 * Math.PI * i / 20.0));
|
||||
eacp.Update(new TValue(DateTime.UtcNow.AddSeconds(i), price));
|
||||
acp.Update(new TValue(DateTime.UtcNow.AddSeconds(i), price));
|
||||
}
|
||||
|
||||
Assert.True(eacp.IsHot);
|
||||
Assert.InRange(eacp.DominantCycle, 8, 48);
|
||||
Assert.True(acp.IsHot);
|
||||
Assert.InRange(acp.DominantCycle, 8, 48);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validation_VeryLargePrices_HandledCorrectly()
|
||||
{
|
||||
var eacp = new Eacp(8, 48);
|
||||
var acp = new Acp(8, 48);
|
||||
|
||||
for (int i = 0; i < 200; i++)
|
||||
{
|
||||
double price = 1e10 + (1e9 * Math.Sin(2.0 * Math.PI * i / 20.0));
|
||||
eacp.Update(new TValue(DateTime.UtcNow.AddSeconds(i), price));
|
||||
acp.Update(new TValue(DateTime.UtcNow.AddSeconds(i), price));
|
||||
}
|
||||
|
||||
Assert.True(eacp.IsHot);
|
||||
Assert.InRange(eacp.DominantCycle, 8, 48);
|
||||
Assert.True(acp.IsHot);
|
||||
Assert.InRange(acp.DominantCycle, 8, 48);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validation_HighVolatility_StableResults()
|
||||
{
|
||||
var eacp = new Eacp(8, 48);
|
||||
var acp = new Acp(8, 48);
|
||||
|
||||
var gbm = new GBM(seed: 42, sigma: 0.5); // High volatility
|
||||
var bars = gbm.Fetch(500, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
foreach (var bar in bars)
|
||||
{
|
||||
eacp.Update(new TValue(bar.Time, bar.Close));
|
||||
Assert.InRange(eacp.DominantCycle, 8, 48);
|
||||
Assert.InRange(eacp.NormalizedPower, 0.0, 1.0);
|
||||
acp.Update(new TValue(bar.Time, bar.Close));
|
||||
Assert.InRange(acp.DominantCycle, 8, 48);
|
||||
Assert.InRange(acp.NormalizedPower, 0.0, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,14 +337,14 @@ public class EacpValidationTests
|
||||
{
|
||||
// When all prices are identical, correlation is undefined
|
||||
// but the algorithm should still produce valid output
|
||||
var eacp = new Eacp(8, 48);
|
||||
var acp = new Acp(8, 48);
|
||||
|
||||
for (int i = 0; i < 300; i++)
|
||||
{
|
||||
eacp.Update(new TValue(DateTime.UtcNow.AddSeconds(i), 100.0));
|
||||
acp.Update(new TValue(DateTime.UtcNow.AddSeconds(i), 100.0));
|
||||
}
|
||||
|
||||
Assert.InRange(eacp.DominantCycle, 8, 48);
|
||||
Assert.InRange(acp.DominantCycle, 8, 48);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -355,38 +355,38 @@ public class EacpValidationTests
|
||||
public void Validation_Autocorrelation_SineWaveHighCorrelation()
|
||||
{
|
||||
// A pure sine wave should have high autocorrelation at its period
|
||||
var eacp = new Eacp(8, 48, 3, true);
|
||||
var acp = new Acp(8, 48, 3, true);
|
||||
|
||||
// Generate pure sine wave
|
||||
for (int i = 0; i < 300; i++)
|
||||
{
|
||||
double price = 100.0 + (10.0 * Math.Sin(2.0 * Math.PI * i / 20.0));
|
||||
eacp.Update(new TValue(DateTime.UtcNow.AddSeconds(i), price));
|
||||
acp.Update(new TValue(DateTime.UtcNow.AddSeconds(i), price));
|
||||
}
|
||||
|
||||
// Should have relatively high normalized power for a pure sine
|
||||
Assert.True(eacp.NormalizedPower > 0.1,
|
||||
$"Pure sine should have detectable power, got {eacp.NormalizedPower}");
|
||||
Assert.True(acp.NormalizedPower > 0.1,
|
||||
$"Pure sine should have detectable power, got {acp.NormalizedPower}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validation_RandomNoise_LowPower()
|
||||
{
|
||||
// Random noise should have low spectral power at any frequency
|
||||
var eacp = new Eacp(8, 48, 3, true);
|
||||
var acp = new Acp(8, 48, 3, true);
|
||||
|
||||
var gbm = new GBM(seed: 42, mu: 0, sigma: 0.01); // Nearly pure noise
|
||||
var bars = gbm.Fetch(500, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
foreach (var bar in bars)
|
||||
{
|
||||
eacp.Update(new TValue(bar.Time, bar.Close));
|
||||
acp.Update(new TValue(bar.Time, bar.Close));
|
||||
}
|
||||
|
||||
// For noise, dominant cycle detection is weak
|
||||
// Just verify it doesn't crash and produces valid output
|
||||
Assert.InRange(eacp.DominantCycle, 8, 48);
|
||||
Assert.InRange(eacp.NormalizedPower, 0.0, 1.0);
|
||||
Assert.InRange(acp.DominantCycle, 8, 48);
|
||||
Assert.InRange(acp.NormalizedPower, 0.0, 1.0);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -400,8 +400,8 @@ public class EacpValidationTests
|
||||
const int period1 = 12;
|
||||
const int period2 = 36;
|
||||
|
||||
var eacp1 = new Eacp(8, 48);
|
||||
var eacp2 = new Eacp(8, 48);
|
||||
var acp1 = new Acp(8, 48);
|
||||
var acp2 = new Acp(8, 48);
|
||||
|
||||
// Generate two different sine waves
|
||||
for (int i = 0; i < 500; i++)
|
||||
@@ -409,19 +409,19 @@ public class EacpValidationTests
|
||||
double price1 = 100.0 + (10.0 * Math.Sin(2.0 * Math.PI * i / period1));
|
||||
double price2 = 100.0 + (10.0 * Math.Sin(2.0 * Math.PI * i / period2));
|
||||
|
||||
eacp1.Update(new TValue(DateTime.UtcNow.AddSeconds(i), price1));
|
||||
eacp2.Update(new TValue(DateTime.UtcNow.AddSeconds(i), price2));
|
||||
acp1.Update(new TValue(DateTime.UtcNow.AddSeconds(i), price1));
|
||||
acp2.Update(new TValue(DateTime.UtcNow.AddSeconds(i), price2));
|
||||
}
|
||||
|
||||
// They should detect different dominant cycles
|
||||
double diff = Math.Abs(eacp1.DominantCycle - eacp2.DominantCycle);
|
||||
Assert.True(diff > 5, $"Should detect different cycles: {eacp1.DominantCycle} vs {eacp2.DominantCycle}");
|
||||
double diff = Math.Abs(acp1.DominantCycle - acp2.DominantCycle);
|
||||
Assert.True(diff > 5, $"Should detect different cycles: {acp1.DominantCycle} vs {acp2.DominantCycle}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Eacp_Correction_Recomputes()
|
||||
public void Acp_Correction_Recomputes()
|
||||
{
|
||||
var ind = new Eacp(8, 48, 3, true);
|
||||
var ind = new Acp(8, 48, 3, true);
|
||||
var t0 = new DateTime(946_684_800_000_000_0L, DateTimeKind.Utc);
|
||||
|
||||
// Build state well past warmup
|
||||
+1
-1
@@ -13,7 +13,7 @@
|
||||
| **PineScript** | [cg.pine](cg.pine) |
|
||||
|
||||
- CG identifies potential turning points using the physics concept of weighted center of mass applied to a price window.
|
||||
- **Similar:** [Ccyc](../ccyc/Ccyc.md), [EACP](../eacp/eacp.md) | **Complementary:** RSI for momentum confirmation | **Trading note:** Center of Gravity oscillator by Ehlers; leads price turns with minimal lag.
|
||||
- **Similar:** [Ccyc](../ccyc/Ccyc.md), [ACP](../acp/acp.md) | **Complementary:** RSI for momentum confirmation | **Trading note:** Center of Gravity oscillator by Ehlers; leads price turns with minimal lag.
|
||||
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
|
||||
|
||||
CG identifies potential turning points using the physics concept of weighted center of mass applied to a price window. Developed by John Ehlers, the oscillator measures where the "weight" of prices is concentrated within a lookback period, producing a leading indicator that oscillates around zero with minimal lag compared to traditional moving average crossover systems.
|
||||
|
||||
Reference in New Issue
Block a user