mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-16 09:38:05 +00:00
Add Aroon Indicator implementation and tests
- Implemented Aroon Indicator with constructor, initialization, and update methods. - Added unit tests for AroonIndicator to verify default settings, historical depth, short name, source code link, and processing of historical bars. - Created Aroon class for core calculations, including methods for updating with TBar and TBarSeries. - Added validation tests to ensure Aroon calculations match results from Skender and TA-Lib. - Updated documentation for Aroon Indicator with calculation methods and usage examples. - Refactored Dema and Wma classes to use Batch methods for calculations. - Enhanced performance benchmarks by increasing bar count and integrating OoplesFinance indicators. - Updated project dependencies to include OoplesFinance.StockIndicators.
This commit is contained in:
+1
-1
@@ -20,7 +20,7 @@
|
||||
| APCHANNEL | Andrews' Pitchfork | Channels |
|
||||
| APO | Absolute Price Oscillator | Momentum |
|
||||
| APZ | Adaptive Price Zone | Channels |
|
||||
| AROON | Aroon | Momentum |
|
||||
| [AROON](momentum/aroon/Aroon.md) | Aroon | Momentum |
|
||||
| AROONOSC | Aroon Oscillator | Momentum |
|
||||
| ATAN2 | Two-Argument Arctangent | Numerics |
|
||||
| ATR | Average True Range | Volatility |
|
||||
|
||||
@@ -9,7 +9,7 @@ Momentum indicators measure the speed or strength of price movements. This inclu
|
||||
| ADXR | Average Directional Movement Rating | |
|
||||
| [AO](ao/Ao.md) | Awesome Oscillator | Measures market momentum using the difference between 34-period and 5-period SMAs of median price. |
|
||||
| APO | Absolute Price Oscillator | |
|
||||
| AROON | Aroon | |
|
||||
| [AROON](aroon/Aroon.md) | Aroon | Identifies trend changes and strength using time since high/low. |
|
||||
| AROONOSC | Aroon Oscillator | |
|
||||
| BBB | Bollinger %B | |
|
||||
| BBS | Bollinger Band Squeeze | |
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
using Xunit;
|
||||
using TradingPlatform.BusinessLayer;
|
||||
using QuanTAlib;
|
||||
|
||||
namespace QuanTAlib.Tests;
|
||||
|
||||
public class AroonIndicatorTests
|
||||
{
|
||||
[Fact]
|
||||
public void AroonIndicator_Constructor_SetsDefaults()
|
||||
{
|
||||
var indicator = new AroonIndicator();
|
||||
|
||||
Assert.Equal(14, indicator.Period);
|
||||
Assert.True(indicator.ShowColdValues);
|
||||
Assert.Equal("Aroon", indicator.Name);
|
||||
Assert.True(indicator.SeparateWindow);
|
||||
Assert.True(indicator.OnBackGround);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AroonIndicator_MinHistoryDepths_EqualsPeriod()
|
||||
{
|
||||
var indicator = new AroonIndicator { Period = 20 };
|
||||
|
||||
Assert.Equal(20, indicator.MinHistoryDepths);
|
||||
IWatchlistIndicator watchlistIndicator = indicator;
|
||||
Assert.Equal(20, watchlistIndicator.MinHistoryDepths);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AroonIndicator_ShortName_IncludesParameters()
|
||||
{
|
||||
var indicator = new AroonIndicator { Period = 20 };
|
||||
indicator.Initialize();
|
||||
|
||||
Assert.Contains("Aroon", indicator.ShortName);
|
||||
Assert.Contains("20", indicator.ShortName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AroonIndicator_SourceCodeLink_IsValid()
|
||||
{
|
||||
var indicator = new AroonIndicator();
|
||||
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink);
|
||||
Assert.Contains("Aroon.Quantower.cs", indicator.SourceCodeLink);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AroonIndicator_Initialize_CreatesInternalAroon()
|
||||
{
|
||||
var indicator = new AroonIndicator { Period = 14 };
|
||||
|
||||
// Initialize should not throw
|
||||
indicator.Initialize();
|
||||
|
||||
// After init, line series should exist (Up, Down, Osc)
|
||||
Assert.Equal(3, indicator.LinesSeries.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AroonIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
|
||||
{
|
||||
var indicator = new AroonIndicator { Period = 5 };
|
||||
indicator.Initialize();
|
||||
|
||||
// Add historical data
|
||||
var now = DateTime.UtcNow;
|
||||
// Need enough bars for Period
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100 + i, 110 + i, 90 + i, 105 + i);
|
||||
|
||||
// Process update for each bar to simulate history loading
|
||||
var args = new UpdateArgs(UpdateReason.HistoricalBar);
|
||||
indicator.ProcessUpdate(args);
|
||||
}
|
||||
|
||||
// Line series should have a value
|
||||
double up = indicator.LinesSeries[0].GetValue(0);
|
||||
double down = indicator.LinesSeries[1].GetValue(0);
|
||||
double osc = indicator.LinesSeries[2].GetValue(0);
|
||||
|
||||
Assert.True(double.IsFinite(up));
|
||||
Assert.True(double.IsFinite(down));
|
||||
Assert.True(double.IsFinite(osc));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using System.Drawing;
|
||||
using TradingPlatform.BusinessLayer;
|
||||
|
||||
namespace QuanTAlib;
|
||||
|
||||
public class AroonIndicator : Indicator, IWatchlistIndicator
|
||||
{
|
||||
[InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)]
|
||||
public int Period { get; set; } = 14;
|
||||
|
||||
[InputParameter("Show cold values", sortIndex: 21)]
|
||||
public bool ShowColdValues { get; set; } = true;
|
||||
|
||||
private Aroon? _aroon;
|
||||
protected LineSeries? UpSeries;
|
||||
protected LineSeries? DownSeries;
|
||||
protected LineSeries? OscSeries;
|
||||
|
||||
public int MinHistoryDepths => Period;
|
||||
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
|
||||
|
||||
public override string ShortName => $"Aroon {Period}";
|
||||
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/momentum/aroon/Aroon.Quantower.cs";
|
||||
|
||||
public AroonIndicator()
|
||||
{
|
||||
OnBackGround = true;
|
||||
SeparateWindow = true;
|
||||
Name = "Aroon";
|
||||
Description = "Identifies trend changes and strength";
|
||||
|
||||
UpSeries = new(name: "Aroon Up", color: Color.Green, width: 1, style: LineStyle.Solid);
|
||||
DownSeries = new(name: "Aroon Down", color: Color.Red, width: 1, style: LineStyle.Solid);
|
||||
OscSeries = new(name: "Aroon Osc", color: Color.Blue, width: 2, style: LineStyle.Solid);
|
||||
|
||||
AddLineSeries(UpSeries);
|
||||
AddLineSeries(DownSeries);
|
||||
AddLineSeries(OscSeries);
|
||||
}
|
||||
|
||||
protected override void OnInit()
|
||||
{
|
||||
_aroon = new Aroon(Period);
|
||||
base.OnInit();
|
||||
}
|
||||
|
||||
protected override void OnUpdate(UpdateArgs args)
|
||||
{
|
||||
bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar;
|
||||
|
||||
TBar bar = this.GetInputBar(args);
|
||||
|
||||
TValue result = _aroon!.Update(bar, isNew);
|
||||
|
||||
if (!_aroon.IsHot && !ShowColdValues)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UpSeries!.SetValue(_aroon.Up.Value);
|
||||
DownSeries!.SetValue(_aroon.Down.Value);
|
||||
OscSeries!.SetValue(result.Value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Xunit;
|
||||
|
||||
namespace QuanTAlib;
|
||||
|
||||
public class AroonTests
|
||||
{
|
||||
[Fact]
|
||||
public void BasicCalculation_DoesNotCrash()
|
||||
{
|
||||
var aroon = new Aroon(14);
|
||||
var gbm = new GBM();
|
||||
var bars = gbm.Fetch(1000, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
for (int i = 0; i < bars.Count; i++)
|
||||
{
|
||||
aroon.Update(bars[i]);
|
||||
}
|
||||
|
||||
Assert.True(double.IsFinite(aroon.Last.Value));
|
||||
Assert.True(double.IsFinite(aroon.Up.Value));
|
||||
Assert.True(double.IsFinite(aroon.Down.Value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsNew_Consistency()
|
||||
{
|
||||
var aroon = new Aroon(14);
|
||||
var gbm = new GBM();
|
||||
var bars = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
// Feed first 99
|
||||
for (int i = 0; i < 99; i++)
|
||||
{
|
||||
aroon.Update(bars[i]);
|
||||
}
|
||||
|
||||
// Update with 100th point (isNew=true)
|
||||
aroon.Update(bars[99], true);
|
||||
|
||||
// Update with modified 100th point (isNew=false)
|
||||
var modifiedBar = new TBar(bars[99].Time, bars[99].Open, bars[99].High + 10.0, bars[99].Low - 10.0, bars[99].Close, bars[99].Volume);
|
||||
var val2 = aroon.Update(modifiedBar, false);
|
||||
|
||||
// Create new instance and feed up to modified
|
||||
var aroon2 = new Aroon(14);
|
||||
for (int i = 0; i < 99; i++)
|
||||
{
|
||||
aroon2.Update(bars[i]);
|
||||
}
|
||||
var val3 = aroon2.Update(modifiedBar, true);
|
||||
|
||||
Assert.Equal(val3.Value, val2.Value, 1e-9);
|
||||
Assert.Equal(aroon2.Up.Value, aroon.Up.Value, 1e-9);
|
||||
Assert.Equal(aroon2.Down.Value, aroon.Down.Value, 1e-9);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Reset_Works()
|
||||
{
|
||||
var aroon = new Aroon(14);
|
||||
var gbm = new GBM();
|
||||
var bars = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
for (int i = 0; i < bars.Count; i++)
|
||||
{
|
||||
aroon.Update(bars[i]);
|
||||
}
|
||||
|
||||
aroon.Reset();
|
||||
Assert.Equal(0, aroon.Last.Value);
|
||||
Assert.False(aroon.IsHot);
|
||||
|
||||
// Feed again
|
||||
for (int i = 0; i < bars.Count; i++)
|
||||
{
|
||||
aroon.Update(bars[i]);
|
||||
}
|
||||
|
||||
Assert.True(double.IsFinite(aroon.Last.Value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TBarSeries_Update_Matches_Streaming()
|
||||
{
|
||||
var aroon = new Aroon(14);
|
||||
var gbm = new GBM();
|
||||
var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
var streamingResults = new List<double>();
|
||||
for (int i = 0; i < bars.Count; i++)
|
||||
{
|
||||
streamingResults.Add(aroon.Update(bars[i]).Value);
|
||||
}
|
||||
|
||||
var aroon2 = new Aroon(14);
|
||||
var seriesResults = aroon2.Update(bars);
|
||||
|
||||
Assert.Equal(streamingResults.Count, seriesResults.Count);
|
||||
for (int i = 0; i < seriesResults.Count; i++)
|
||||
{
|
||||
Assert.Equal(streamingResults[i], seriesResults.Values[i], 1e-9);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StaticCalculate_Matches_Streaming()
|
||||
{
|
||||
var gbm = new GBM();
|
||||
var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
var aroon = new Aroon(14);
|
||||
var streamingResults = new List<double>();
|
||||
for (int i = 0; i < bars.Count; i++)
|
||||
{
|
||||
streamingResults.Add(aroon.Update(bars[i]).Value);
|
||||
}
|
||||
|
||||
var staticResults = Aroon.Batch(bars, 14);
|
||||
|
||||
Assert.Equal(streamingResults.Count, staticResults.Count);
|
||||
for (int i = 0; i < staticResults.Count; i++)
|
||||
{
|
||||
Assert.Equal(streamingResults[i], staticResults.Values[i], 1e-9);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_InvalidParameters_ThrowsArgumentException()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() => new Aroon(0));
|
||||
Assert.Throws<ArgumentException>(() => new Aroon(-1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ManualCalculation_Verify()
|
||||
{
|
||||
// Simple manual test
|
||||
// Period = 2
|
||||
// Highs: 10, 12, 11
|
||||
// Lows: 8, 9, 7
|
||||
|
||||
// T=0: H=10, L=8. Not enough data.
|
||||
// T=1: H=12, L=9. Not enough data.
|
||||
// T=2: H=11, L=7.
|
||||
// Window Highs: [10, 12, 11]. Max is 12 at index 1 (1 day ago).
|
||||
// Window Lows: [8, 9, 7]. Min is 7 at index 2 (0 days ago).
|
||||
|
||||
// Up = ((2 - 1) / 2) * 100 = 50
|
||||
// Down = ((2 - 0) / 2) * 100 = 100
|
||||
// Osc = 50 - 100 = -50
|
||||
|
||||
var aroon = new Aroon(2);
|
||||
var time = DateTime.UtcNow;
|
||||
|
||||
aroon.Update(new TBar(time, 10, 10, 8, 9, 100));
|
||||
aroon.Update(new TBar(time.AddMinutes(1), 11, 12, 9, 10, 100));
|
||||
var result = aroon.Update(new TBar(time.AddMinutes(2), 10, 11, 7, 8, 100));
|
||||
|
||||
Assert.Equal(50.0, aroon.Up.Value, 1e-9);
|
||||
Assert.Equal(100.0, aroon.Down.Value, 1e-9);
|
||||
Assert.Equal(-50.0, result.Value, 1e-9);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Skender.Stock.Indicators;
|
||||
using TALib;
|
||||
using Xunit;
|
||||
using QuanTAlib.Tests;
|
||||
|
||||
namespace QuanTAlib;
|
||||
|
||||
public sealed class AroonValidationTests : IDisposable
|
||||
{
|
||||
private readonly ValidationTestData _data;
|
||||
|
||||
public AroonValidationTests()
|
||||
{
|
||||
_data = new ValidationTestData();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_data.Dispose();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesSkender()
|
||||
{
|
||||
var aroon = new Aroon(14);
|
||||
var results = new List<double>();
|
||||
var upResults = new List<double>();
|
||||
var downResults = new List<double>();
|
||||
|
||||
for (int i = 0; i < _data.Bars.Count; i++)
|
||||
{
|
||||
var res = aroon.Update(_data.Bars[i]);
|
||||
results.Add(res.Value);
|
||||
upResults.Add(aroon.Up.Value);
|
||||
downResults.Add(aroon.Down.Value);
|
||||
}
|
||||
|
||||
var skenderResults = _data.SkenderQuotes.GetAroon(14).ToList();
|
||||
|
||||
// Verify Oscillator
|
||||
ValidationHelper.VerifyData(results, skenderResults, x => x.Oscillator);
|
||||
|
||||
// Verify Up
|
||||
ValidationHelper.VerifyData(upResults, skenderResults, x => x.AroonUp);
|
||||
|
||||
// Verify Down
|
||||
ValidationHelper.VerifyData(downResults, skenderResults, x => x.AroonDown);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesTalib()
|
||||
{
|
||||
var aroon = new Aroon(14);
|
||||
var results = new List<double>();
|
||||
var upResults = new List<double>();
|
||||
var downResults = new List<double>();
|
||||
|
||||
for (int i = 0; i < _data.Bars.Count; i++)
|
||||
{
|
||||
var res = aroon.Update(_data.Bars[i]);
|
||||
results.Add(res.Value);
|
||||
upResults.Add(aroon.Up.Value);
|
||||
downResults.Add(aroon.Down.Value);
|
||||
}
|
||||
|
||||
double[] hData = _data.Bars.High.Select(x => x.Value).ToArray();
|
||||
double[] lData = _data.Bars.Low.Select(x => x.Value).ToArray();
|
||||
double[] outAroonUp = new double[_data.Bars.Count];
|
||||
double[] outAroonDown = new double[_data.Bars.Count];
|
||||
double[] outAroonOsc = new double[_data.Bars.Count];
|
||||
|
||||
// TA-Lib Aroon (Up/Down)
|
||||
var retCode = TALib.Functions.Aroon(hData, lData, 0..^0, outAroonDown, outAroonUp, out var outRange, 14);
|
||||
Assert.Equal(Core.RetCode.Success, retCode);
|
||||
|
||||
// TA-Lib AroonOsc
|
||||
var retCodeOsc = TALib.Functions.AroonOsc(hData, lData, 0..^0, outAroonOsc, out var outRangeOsc, 14);
|
||||
Assert.Equal(Core.RetCode.Success, retCodeOsc);
|
||||
|
||||
int lookback = TALib.Functions.AroonLookback(14);
|
||||
|
||||
// Verify Up
|
||||
ValidationHelper.VerifyData(upResults, outAroonUp, outRange, lookback);
|
||||
|
||||
// Verify Down
|
||||
ValidationHelper.VerifyData(downResults, outAroonDown, outRange, lookback);
|
||||
|
||||
// Verify Oscillator
|
||||
ValidationHelper.VerifyData(results, outAroonOsc, outRangeOsc, lookback);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// Aroon Indicator
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The Aroon indicator is used to identify trend changes in the price of an asset, as well as the strength of that trend.
|
||||
/// It consists of two lines: Aroon Up and Aroon Down.
|
||||
///
|
||||
/// Calculation:
|
||||
/// Aroon Up = ((Period - Days Since Period High) / Period) * 100
|
||||
/// Aroon Down = ((Period - Days Since Period Low) / Period) * 100
|
||||
/// Aroon Oscillator = Aroon Up - Aroon Down
|
||||
///
|
||||
/// The indicator requires Period + 1 samples to fully calculate "Period" days ago.
|
||||
///
|
||||
/// Sources:
|
||||
/// https://www.investopedia.com/terms/a/aroon.asp
|
||||
/// Tushar Chande (1995)
|
||||
/// </remarks>
|
||||
[SkipLocalsInit]
|
||||
public sealed class Aroon : ITValuePublisher
|
||||
{
|
||||
private readonly int _period;
|
||||
private readonly RingBuffer _highs;
|
||||
private readonly RingBuffer _lows;
|
||||
|
||||
/// <summary>
|
||||
/// Display name for the indicator.
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
public event Action<TValue>? Pub;
|
||||
|
||||
/// <summary>
|
||||
/// Current Aroon Oscillator value (Up - Down).
|
||||
/// </summary>
|
||||
public TValue Last { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Current Aroon Up value.
|
||||
/// </summary>
|
||||
public TValue Up { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Current Aroon Down value.
|
||||
/// </summary>
|
||||
public TValue Down { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// True if the indicator has enough data for a full period calculation.
|
||||
/// </summary>
|
||||
public bool IsHot => _highs.IsFull;
|
||||
|
||||
/// <summary>
|
||||
/// The number of bars required for the indicator to warm up.
|
||||
/// </summary>
|
||||
public int WarmupPeriod { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates Aroon indicator with specified period.
|
||||
/// </summary>
|
||||
/// <param name="period">Lookback period (must be > 0)</param>
|
||||
public Aroon(int period)
|
||||
{
|
||||
if (period <= 0)
|
||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
|
||||
_period = period;
|
||||
Name = $"Aroon({period})";
|
||||
WarmupPeriod = period;
|
||||
// We need Period + 1 samples to cover the range [0, Period] days ago.
|
||||
_highs = new RingBuffer(period + 1);
|
||||
_lows = new RingBuffer(period + 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the indicator state.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Reset()
|
||||
{
|
||||
_highs.Clear();
|
||||
_lows.Clear();
|
||||
Last = default;
|
||||
Up = default;
|
||||
Down = default;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public TValue Update(TBar input, bool isNew = true)
|
||||
{
|
||||
_highs.Add(input.High, isNew);
|
||||
_lows.Add(input.Low, isNew);
|
||||
|
||||
if (_highs.Count == 0)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
// Find max index in highs (Zero allocation)
|
||||
var highsBuffer = _highs.InternalBuffer;
|
||||
int count = _highs.Count;
|
||||
int capacity = _highs.Capacity;
|
||||
int start = _highs.StartIndex;
|
||||
|
||||
double maxVal = double.MinValue;
|
||||
int maxIdxRelative = 0;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
int idx = (start + i) % capacity;
|
||||
double val = highsBuffer[idx];
|
||||
// Use >= to find the most recent high if values are equal
|
||||
if (val >= maxVal)
|
||||
{
|
||||
maxVal = val;
|
||||
maxIdxRelative = i;
|
||||
}
|
||||
}
|
||||
|
||||
// Find min index in lows (Zero allocation)
|
||||
var lowsBuffer = _lows.InternalBuffer;
|
||||
double minVal = double.MaxValue;
|
||||
int minIdxRelative = 0;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
int idx = (start + i) % capacity;
|
||||
double val = lowsBuffer[idx];
|
||||
// Use <= to find the most recent low if values are equal
|
||||
if (val <= minVal)
|
||||
{
|
||||
minVal = val;
|
||||
minIdxRelative = i;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate days since (0 means current bar is the high/low)
|
||||
int daysSinceHigh = (count - 1) - maxIdxRelative;
|
||||
int daysSinceLow = (count - 1) - minIdxRelative;
|
||||
|
||||
double up = ((double)(_period - daysSinceHigh) / _period) * 100.0;
|
||||
double down = ((double)(_period - daysSinceLow) / _period) * 100.0;
|
||||
double osc = up - down;
|
||||
|
||||
Up = new TValue(input.Time, up);
|
||||
Down = new TValue(input.Time, down);
|
||||
Last = new TValue(input.Time, osc);
|
||||
|
||||
Pub?.Invoke(Last);
|
||||
return Last;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public TValue Update(TValue input, bool isNew = true)
|
||||
{
|
||||
return Update(new TBar(input.Time, input.Value, input.Value, input.Value, input.Value, 0), isNew);
|
||||
}
|
||||
|
||||
public TSeries Update(TBarSeries source)
|
||||
{
|
||||
var t = new List<long>(source.Count);
|
||||
var v = new List<double>(source.Count);
|
||||
|
||||
Reset();
|
||||
|
||||
for (int i = 0; i < source.Count; i++)
|
||||
{
|
||||
var val = Update(source[i], true);
|
||||
t.Add(val.Time);
|
||||
v.Add(val.Value);
|
||||
}
|
||||
|
||||
return new TSeries(t, v);
|
||||
}
|
||||
|
||||
public static TSeries Batch(TBarSeries source, int period)
|
||||
{
|
||||
var aroon = new Aroon(period);
|
||||
return aroon.Update(source);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
# Aroon Indicator
|
||||
|
||||
The Aroon indicator is a technical indicator used to identify trend changes in the price of an asset, as well as the strength of that trend. It consists of two lines: Aroon Up and Aroon Down.
|
||||
|
||||
## Calculation
|
||||
|
||||
The Aroon indicator measures the time between highs and the time between lows over a time period.
|
||||
|
||||
$$
|
||||
\text{Aroon Up} = \frac{\text{Period} - \text{Days Since Period High}}{\text{Period}} \times 100
|
||||
$$
|
||||
|
||||
$$
|
||||
\text{Aroon Down} = \frac{\text{Period} - \text{Days Since Period Low}}{\text{Period}} \times 100
|
||||
$$
|
||||
|
||||
$$
|
||||
\text{Aroon Oscillator} = \text{Aroon Up} - \text{Aroon Down}
|
||||
$$
|
||||
|
||||
Where:
|
||||
|
||||
- **Period**: The lookback period (typically 25).
|
||||
- **Days Since Period High**: The number of days since the highest high within the period.
|
||||
- **Days Since Period Low**: The number of days since the lowest low within the period.
|
||||
|
||||
## Interpretation
|
||||
|
||||
- **Aroon Up**: Measures the strength of the uptrend. Values close to 100 indicate a strong uptrend, while values close to 0 indicate a weak uptrend.
|
||||
- **Aroon Down**: Measures the strength of the downtrend. Values close to 100 indicate a strong downtrend, while values close to 0 indicate a weak downtrend.
|
||||
- **Crossovers**: When Aroon Up crosses above Aroon Down, it signals a potential uptrend. When Aroon Down crosses above Aroon Up, it signals a potential downtrend.
|
||||
- **Extremes**: Values above 70 indicate a strong trend, while values below 30 indicate a weak trend.
|
||||
|
||||
## Usage
|
||||
|
||||
### C# code
|
||||
|
||||
```csharp
|
||||
using QuanTAlib;
|
||||
|
||||
// Create Aroon with period 14
|
||||
var aroon = new Aroon(14);
|
||||
|
||||
// Update with a TBar
|
||||
var bar = new TBar(DateTime.UtcNow, 100, 105, 95, 102, 1000);
|
||||
var result = aroon.Update(bar);
|
||||
|
||||
// Access values
|
||||
var osc = result.Value;
|
||||
var up = aroon.Up.Value;
|
||||
var down = aroon.Down.Value;
|
||||
|
||||
Console.WriteLine($"Aroon Osc: {osc:F2}, Up: {up:F2}, Down: {down:F2}");
|
||||
```
|
||||
|
||||
### Quantower
|
||||
|
||||
The Aroon indicator is available in Quantower as "Aroon".
|
||||
|
||||
- **Period**: The lookback period (default: 14).
|
||||
- **Show cold values**: Whether to show values before the indicator is fully warmed up.
|
||||
|
||||
## References
|
||||
|
||||
- [Investopedia: Aroon Indicator](https://www.investopedia.com/terms/a/aroon.asp)
|
||||
- Tushar Chande (1995)
|
||||
@@ -200,16 +200,16 @@ public sealed class Dema : AbstractBase
|
||||
return dema.Update(source);
|
||||
}
|
||||
|
||||
public static void Calculate(ReadOnlySpan<double> source, Span<double> output, int period)
|
||||
public static void Batch(ReadOnlySpan<double> source, Span<double> output, int period)
|
||||
{
|
||||
if (period <= 0)
|
||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
|
||||
double alpha = 2.0 / (period + 1);
|
||||
Calculate(source, output, alpha);
|
||||
Batch(source, output, alpha);
|
||||
}
|
||||
|
||||
public static void Calculate(ReadOnlySpan<double> source, Span<double> output, double alpha)
|
||||
public static void Batch(ReadOnlySpan<double> source, Span<double> output, double alpha)
|
||||
{
|
||||
if (source.Length != output.Length)
|
||||
throw new ArgumentException("Source and output must have the same length");
|
||||
|
||||
+14
-7
@@ -37,7 +37,14 @@ public sealed class Wma : AbstractBase
|
||||
private State _state;
|
||||
private State _p_state;
|
||||
|
||||
private const int ResyncInterval = 1000;
|
||||
private const int ResyncInterval = 10000;
|
||||
|
||||
private static readonly Vector512<long> V512_Idx_1 = Vector512.Create(0L, 0, 1, 2, 3, 4, 5, 6);
|
||||
private static readonly Vector512<long> V512_Idx_2 = Vector512.Create(0L, 0, 0, 0, 1, 2, 3, 4);
|
||||
private static readonly Vector512<long> V512_Idx_4 = Vector512.Create(0L, 0, 0, 0, 0, 0, 1, 2);
|
||||
private static readonly Vector512<double> V512_Mask_1 = Vector512.Create(0.0, 1, 1, 1, 1, 1, 1, 1);
|
||||
private static readonly Vector512<double> V512_Mask_2 = Vector512.Create(0.0, 0, 1, 1, 1, 1, 1, 1);
|
||||
private static readonly Vector512<double> V512_Mask_4 = Vector512.Create(0.0, 0, 0, 0, 1, 1, 1, 1);
|
||||
|
||||
public Wma(int period)
|
||||
{
|
||||
@@ -366,13 +373,13 @@ public sealed class Wma : AbstractBase
|
||||
var vDeltaS = Avx512F.Subtract(vNew, vOld);
|
||||
|
||||
// Prefix sum of DeltaS
|
||||
var vShiftS1 = Vector512.Create(0.0, vDeltaS.GetElement(0), vDeltaS.GetElement(1), vDeltaS.GetElement(2), vDeltaS.GetElement(3), vDeltaS.GetElement(4), vDeltaS.GetElement(5), vDeltaS.GetElement(6));
|
||||
var vShiftS1 = Avx512F.Multiply(Avx512F.PermuteVar8x64(vDeltaS, V512_Idx_1), V512_Mask_1);
|
||||
var vPS1 = Avx512F.Add(vDeltaS, vShiftS1);
|
||||
|
||||
var vShiftS2 = Vector512.Create(0.0, 0.0, vPS1.GetElement(0), vPS1.GetElement(1), vPS1.GetElement(2), vPS1.GetElement(3), vPS1.GetElement(4), vPS1.GetElement(5));
|
||||
var vShiftS2 = Avx512F.Multiply(Avx512F.PermuteVar8x64(vPS1, V512_Idx_2), V512_Mask_2);
|
||||
var vPS2 = Avx512F.Add(vPS1, vShiftS2);
|
||||
|
||||
var vShiftS4 = Vector512.Create(0.0, 0.0, 0.0, 0.0, vPS2.GetElement(0), vPS2.GetElement(1), vPS2.GetElement(2), vPS2.GetElement(3));
|
||||
var vShiftS4 = Avx512F.Multiply(Avx512F.PermuteVar8x64(vPS2, V512_Idx_4), V512_Mask_4);
|
||||
var vPS4 = Avx512F.Add(vPS2, vShiftS4);
|
||||
|
||||
var vSums = Avx512F.Add(vSumState, vPS4);
|
||||
@@ -382,13 +389,13 @@ public sealed class Wma : AbstractBase
|
||||
var vU = Avx512F.FusedMultiplySubtract(vPeriod, vNew, vSumsShifted);
|
||||
|
||||
// Prefix sum of vU
|
||||
var vShiftW1 = Vector512.Create(0.0, vU.GetElement(0), vU.GetElement(1), vU.GetElement(2), vU.GetElement(3), vU.GetElement(4), vU.GetElement(5), vU.GetElement(6));
|
||||
var vShiftW1 = Avx512F.Multiply(Avx512F.PermuteVar8x64(vU, V512_Idx_1), V512_Mask_1);
|
||||
var vPW1 = Avx512F.Add(vU, vShiftW1);
|
||||
|
||||
var vShiftW2 = Vector512.Create(0.0, 0.0, vPW1.GetElement(0), vPW1.GetElement(1), vPW1.GetElement(2), vPW1.GetElement(3), vPW1.GetElement(4), vPW1.GetElement(5));
|
||||
var vShiftW2 = Avx512F.Multiply(Avx512F.PermuteVar8x64(vPW1, V512_Idx_2), V512_Mask_2);
|
||||
var vPW2 = Avx512F.Add(vPW1, vShiftW2);
|
||||
|
||||
var vShiftW4 = Vector512.Create(0.0, 0.0, 0.0, 0.0, vPW2.GetElement(0), vPW2.GetElement(1), vPW2.GetElement(2), vPW2.GetElement(3));
|
||||
var vShiftW4 = Avx512F.Multiply(Avx512F.PermuteVar8x64(vPW2, V512_Idx_4), V512_Mask_4);
|
||||
var vPW4 = Avx512F.Add(vPW2, vShiftW4);
|
||||
|
||||
var vWsums = Avx512F.Add(vWsumState, vPW4);
|
||||
|
||||
Reference in New Issue
Block a user