filters update

This commit is contained in:
Miha Kralj
2026-02-23 17:27:35 -08:00
parent 7253f61299
commit 467a8c1cef
239 changed files with 17880 additions and 6329 deletions
+2 -2
View File
@@ -9,13 +9,13 @@ Volatility measures the magnitude of price changes, independent of direction. Lo
| [ADR](adr/Adr.md) | Average Daily Range | Simple High-Low range without gap adjustment. |
| [ATR](atr/Atr.md) | Average True Range | Standard volatility measure accounting for gaps via True Range. |
| [ATRN](atrn/Atrn.md) | ATR Normalized | ATR normalized to [0,1] based on historical min/max. |
| [ATRP](atrp/Atrp.md) | ATR Percent | ATR as percentage of close price. |
| [BBW](bbw/Bbw.md) | Bollinger Band Width | Distance between upper and lower Bollinger Bands. |
| [BBWN](bbwn/Bbwn.md) | BB Width Normalized | BBW normalized to [0,1] range. |
| [BBWP](bbwp/Bbwp.md) | BB Width Percentile | BBW percentile rank over lookback. |
| [CCV](ccv/Ccv.md) | Close-to-Close Volatility | Annualized volatility from log returns. |
| [CV](cv/Cv.md) | Conditional Volatility | GARCH(1,1) model for time-varying volatility. |
| [CVI](cvi/Cvi.md) | Chaikin Volatility | Rate of change in smoothed High-Low range. |
| ETHERM | Elder's Thermometer | Absolute bar range in ATR units. Identifies abnormal activity. |
| [EWMA](ewma/Ewma.md) | EWMA Volatility | Exponentially weighted squared returns with bias correction. |
| [GKV](gkv/Gkv.md) | Garman-Klass Volatility | Efficient OHLC-based estimator with RMA smoothing. |
| [HLV](hlv/Hlv.md) | High-Low Volatility (Parkinson) | Range-based volatility using only high-low prices. |
@@ -23,7 +23,7 @@ Volatility measures the magnitude of price changes, independent of direction. Lo
| [JVOLTY](jvolty/Jvolty.md) | Jurik Volatility | Adaptive volatility from JMA with 128-bar trimmed mean distribution. |
| [JVOLTYN](jvoltyn/Jvoltyn.md) | Jurik Volatility Normalized | JVOLTY normalized to [0,100] scale. |
| [MASSI](massi/Massi.md) | Mass Index | Range expansion/contraction for reversal detection. |
| [NATR](natr/Natr.md) | Normalized ATR | ATR as percentage (equivalent to ATRP). |
| [NATR](natr/Natr.md) | Normalized ATR | ATR as percentage of close price. Also known as ATRP. |
| [RSV](rsv/Rsv.md) | Rogers-Satchell Volatility | OHLC estimator with drift adjustment. |
| [RV](rv/Rv.md) | Realized Volatility | High-frequency intraday volatility. |
| [RVI](rvi/Rvi.md) | Relative Volatility Index | Directional volatility measure. |
+1 -1
View File
@@ -155,7 +155,7 @@ Validated against external libraries in `Atr.Validation.Tests.cs`. Tests run aga
1. **Directionality Assumption**: ATR is non-directional. A crashing market has high ATR. A rallying market has high ATR. Do not use ATR to predict direction. Use it to measure potential magnitude of moves.
2. **Scale Dependence**: ATR is absolute, not percentage-based. An ATR of 5.0 on a $100 stock (5% daily range) differs from ATR of 5.0 on a $10 stock (50% daily range). Use ATRP (ATR Percent) or NATR for cross-asset comparisons.
2. **Scale Dependence**: ATR is absolute, not percentage-based. An ATR of 5.0 on a $100 stock (5% daily range) differs from ATR of 5.0 on a $10 stock (50% daily range). Use NATR (Normalized ATR, also known as ATRP) for cross-asset comparisons.
3. **Lag Characteristics**: Because RMA decays slowly, ATR lags actual volatility changes. It tells what *has* happened, not what *will* happen. A volatility spike appears immediately; the subsequent decay takes many bars.
+1 -1
View File
@@ -8,7 +8,7 @@ While ATR tells you *how much* an asset moves, ATRN tells you *how unusual* that
## Historical Context
ATRN is a practical extension of Wilder's ATR, developed to solve the **context problem** in volatility analysis. Raw ATR values are meaningless in isolation—you need to compare them to something. Some traders compare ATR to price (ATRP/NATR), which gives a percentage. ATRN takes a different approach: it compares ATR to its own recent range.
ATRN is a practical extension of Wilder's ATR, developed to solve the **context problem** in volatility analysis. Raw ATR values are meaningless in isolation—you need to compare them to something. Some traders compare ATR to price (NATR), which gives a percentage. ATRN takes a different approach: it compares ATR to its own recent range.
This normalization approach is common in machine learning and signal processing, where inputs are scaled to [0,1] for better model performance. ATRN applies the same principle to volatility measurement.
-158
View File
@@ -1,158 +0,0 @@
using TradingPlatform.BusinessLayer;
namespace QuanTAlib.Tests;
public class AtrpIndicatorTests
{
[Fact]
public void AtrpIndicator_Constructor_SetsDefaults()
{
var indicator = new AtrpIndicator();
Assert.Equal(14, indicator.Period);
Assert.True(indicator.ShowColdValues);
Assert.Equal("ATRP - Average True Range Percent", indicator.Name);
Assert.True(indicator.SeparateWindow);
Assert.True(indicator.OnBackGround);
}
[Fact]
public void AtrpIndicator_ShortName_IncludesParameters()
{
var indicator = new AtrpIndicator { Period = 20 };
Assert.Equal("ATRP 20", indicator.ShortName);
}
[Fact]
public void AtrpIndicator_MinHistoryDepths_EqualsZero()
{
var indicator = new AtrpIndicator();
Assert.Equal(0, AtrpIndicator.MinHistoryDepths);
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
public void AtrpIndicator_Initialize_CreatesInternalAtrp()
{
var indicator = new AtrpIndicator();
// Initialize should not throw
indicator.Initialize();
// After init, line series should exist
Assert.Single(indicator.LinesSeries);
}
[Fact]
public void AtrpIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
{
var indicator = new AtrpIndicator { Period = 5 };
indicator.Initialize();
// Add historical data with volatility
var now = DateTime.UtcNow;
for (int i = 0; i < 20; i++)
{
double basePrice = 100 + i;
indicator.HistoricalData.AddBar(now.AddMinutes(i), basePrice, basePrice + 5, basePrice - 5, basePrice + 2, 1000);
// 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 val = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(val));
Assert.True(val > 0); // ATRP should be positive with volatility
Assert.True(val < 100); // ATRP as percentage should be reasonable
}
[Fact]
public void AtrpIndicator_ProcessUpdate_NewBar_ComputesValue()
{
var indicator = new AtrpIndicator { Period = 5 };
indicator.Initialize();
var now = DateTime.UtcNow;
for (int i = 0; i < 20; i++)
{
double basePrice = 100 + i;
indicator.HistoricalData.AddBar(now.AddMinutes(i), basePrice, basePrice + 5, basePrice - 5, basePrice + 2, 1000);
}
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
// Add new bar
indicator.HistoricalData.AddBar(now.AddMinutes(20), 120, 128, 115, 125, 1500);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewBar));
Assert.Equal(2, indicator.LinesSeries[0].Count);
}
[Fact]
public void AtrpIndicator_DifferentPeriods_Work()
{
int[] periods = { 5, 10, 14, 20, 50 };
foreach (var period in periods)
{
var indicator = new AtrpIndicator { Period = period };
indicator.Initialize();
var now = DateTime.UtcNow;
for (int i = 0; i < 60; i++)
{
double basePrice = 100 + i;
indicator.HistoricalData.AddBar(now.AddMinutes(i), basePrice, basePrice + 5, basePrice - 5, basePrice + 2, 1000);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
}
double val = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(val), $"Period {period} should produce finite value");
Assert.True(val > 0, $"Period {period} should produce positive ATRP");
}
}
[Fact]
public void AtrpIndicator_Period_CanBeChanged()
{
var indicator = new AtrpIndicator();
Assert.Equal(14, indicator.Period);
indicator.Period = 20;
Assert.Equal(20, indicator.Period);
indicator.Period = 5;
Assert.Equal(5, indicator.Period);
}
[Fact]
public void AtrpIndicator_ShowColdValues_CanBeToggled()
{
var indicator = new AtrpIndicator();
Assert.True(indicator.ShowColdValues);
indicator.ShowColdValues = false;
Assert.False(indicator.ShowColdValues);
indicator.ShowColdValues = true;
Assert.True(indicator.ShowColdValues);
}
[Fact]
public void AtrpIndicator_SourceCodeLink_IsValid()
{
var indicator = new AtrpIndicator();
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal);
Assert.Contains("Atrp.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
}
[Fact]
public void AtrpIndicator_Description_IsSet()
{
var indicator = new AtrpIndicator();
Assert.Contains("percentage", indicator.Description, StringComparison.OrdinalIgnoreCase);
}
}
-51
View File
@@ -1,51 +0,0 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public sealed class AtrpIndicator : 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 Atrp _atrp = null!;
private readonly LineSeries _series;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"ATRP {Period}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/volatility/atrp/Atrp.Quantower.cs";
public AtrpIndicator()
{
OnBackGround = true;
SeparateWindow = true;
Name = "ATRP - Average True Range Percent";
Description = "Measures volatility as a percentage of the closing price";
_series = new LineSeries(name: "ATRP", color: Color.Blue, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
_atrp = new Atrp(Period);
base.OnInit();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
TBar bar = this.GetInputBar(args);
TValue result = _atrp.Update(bar, args.IsNewBar());
_series.SetValue(result.Value, _atrp.IsHot, ShowColdValues);
}
}
-455
View File
@@ -1,455 +0,0 @@
namespace QuanTAlib.Tests;
public class AtrpTests
{
// ============== Constructor & Parameter Validation ==============
[Fact]
public void Constructor_ValidatesInput()
{
Assert.Throws<ArgumentException>(() => new Atrp(0));
Assert.Throws<ArgumentException>(() => new Atrp(-1));
var atrp = new Atrp(14);
Assert.NotNull(atrp);
}
// ============== Basic Functionality ==============
[Fact]
public void BasicCalculation_DoesNotCrash()
{
var atrp = new Atrp(14);
var gbm = new GBM();
var bars = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
foreach (var bar in bars)
{
atrp.Update(bar);
}
Assert.True(double.IsFinite(atrp.Last.Value));
}
[Fact]
public void Calc_ReturnsValue()
{
var atrp = new Atrp(14);
var bar = new TBar(DateTime.UtcNow, 100, 105, 95, 102, 1000);
Assert.Equal(0, atrp.Last.Value);
TValue result = atrp.Update(bar);
Assert.True(result.Value > 0);
Assert.Equal(result.Value, atrp.Last.Value);
}
[Fact]
public void FirstValue_ReturnsPercentage()
{
var atrp = new Atrp(14);
var bar = new TBar(DateTime.UtcNow, 100, 110, 90, 100, 1000);
// First bar TR = High - Low = 110 - 90 = 20
// ATRP = (20 / 100) * 100 = 20%
TValue result = atrp.Update(bar);
Assert.Equal(20.0, result.Value, 1e-10);
}
[Fact]
public void Properties_Accessible()
{
var atrp = new Atrp(14);
Assert.Equal(0, atrp.Last.Value);
Assert.False(atrp.IsHot);
Assert.Contains("Atrp", atrp.Name, StringComparison.Ordinal);
Assert.True(atrp.WarmupPeriod > 0);
var bar = new TBar(DateTime.UtcNow, 100, 105, 95, 102, 1000);
atrp.Update(bar);
Assert.NotEqual(0, atrp.Last.Value);
}
// ============== State Management & Bar Correction ==============
[Fact]
public void Calc_IsNew_AcceptsParameter()
{
var atrp = new Atrp(14);
var bar1 = new TBar(DateTime.UtcNow, 100, 105, 95, 102, 1000);
atrp.Update(bar1, isNew: true);
double value1 = atrp.Last.Value;
var bar2 = new TBar(DateTime.UtcNow.AddMinutes(1), 102, 110, 100, 108, 1000);
atrp.Update(bar2, isNew: true);
double value2 = atrp.Last.Value;
Assert.NotEqual(value1, value2);
}
[Fact]
public void Calc_IsNew_False_UpdatesValue()
{
var atrp = new Atrp(14);
var bar1 = new TBar(DateTime.UtcNow, 100, 105, 95, 102, 1000);
atrp.Update(bar1, isNew: true);
var bar2 = new TBar(DateTime.UtcNow.AddMinutes(1), 102, 110, 100, 108, 1000);
atrp.Update(bar2, isNew: true);
double beforeUpdate = atrp.Last.Value;
var bar2Modified = new TBar(DateTime.UtcNow.AddMinutes(1), 102, 120, 90, 108, 1000);
atrp.Update(bar2Modified, isNew: false);
double afterUpdate = atrp.Last.Value;
Assert.NotEqual(beforeUpdate, afterUpdate);
}
[Fact]
public void IsNew_Consistency()
{
var atrp = new Atrp(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++)
{
atrp.Update(bars[i]);
}
// Update with 100th point (isNew=true)
atrp.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);
double val2 = atrp.Update(modifiedBar, false).Value;
// Create new instance and feed up to modified
var atrp2 = new Atrp(14);
for (int i = 0; i < 99; i++)
{
atrp2.Update(bars[i]);
}
double val3 = atrp2.Update(modifiedBar, true).Value;
Assert.Equal(val3, val2, 1e-9);
}
[Fact]
public void IterativeCorrections_RestoreToOriginalState()
{
var atrp = new Atrp(5);
var gbm = new GBM(startPrice: 100.0, mu: 0.02, sigma: 0.1);
var bars = gbm.Fetch(20, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
// Feed 10 new values
TBar tenthBar = default;
for (int i = 0; i < 10; i++)
{
tenthBar = bars[i];
atrp.Update(tenthBar, isNew: true);
}
// Remember state after 10 values
double stateAfterTen = atrp.Last.Value;
// Generate 9 corrections with isNew=false (different values)
for (int i = 10; i < 19; i++)
{
atrp.Update(bars[i], isNew: false);
}
// Feed the remembered 10th bar again with isNew=false
TValue finalResult = atrp.Update(tenthBar, isNew: false);
// State should match the original state after 10 values
Assert.Equal(stateAfterTen, finalResult.Value, 1e-10);
}
[Fact]
public void Reset_Works()
{
var atrp = new Atrp(14);
var gbm = new GBM();
var bars = gbm.Fetch(50, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
foreach (var bar in bars)
{
atrp.Update(bar);
}
double lastVal = atrp.Last.Value;
Assert.NotEqual(0, lastVal);
atrp.Reset();
Assert.Equal(0, atrp.Last.Value);
Assert.False(atrp.IsHot);
// After reset, should accept new values
atrp.Update(bars[0]);
Assert.NotEqual(0, atrp.Last.Value);
}
// ============== Warmup & Convergence ==============
[Fact]
public void IsHot_BecomesTrueAfterWarmup()
{
var atrp = new Atrp(5);
Assert.False(atrp.IsHot);
int steps = 0;
var baseTime = DateTime.UtcNow;
while (!atrp.IsHot && steps < 100)
{
var bar = new TBar(baseTime.AddMinutes(steps), 100, 110, 90, 100, 1000);
atrp.Update(bar);
steps++;
}
Assert.True(atrp.IsHot);
Assert.True(steps > 0);
}
[Fact]
public void WarmupPeriod_IsPositive()
{
var atrp = new Atrp(14);
Assert.True(atrp.WarmupPeriod > 0);
var atrp2 = new Atrp(20);
Assert.True(atrp2.WarmupPeriod > 0);
// WarmupPeriod should increase with the period parameter
Assert.True(atrp2.WarmupPeriod >= atrp.WarmupPeriod);
}
// ============== NaN/Infinity Handling ==============
[Fact]
public void NaN_Input_UsesLastValidValue()
{
var atrp = new Atrp(5);
var bar1 = new TBar(DateTime.UtcNow, 100, 105, 95, 102, 1000);
atrp.Update(bar1);
var bar2 = new TBar(DateTime.UtcNow.AddMinutes(1), 102, 110, 98, 108, 1000);
atrp.Update(bar2);
// Feed bar with NaN values
var barWithNaN = new TBar(DateTime.UtcNow.AddMinutes(2), double.NaN, 115, 100, 112, 1000);
var resultAfterNaN = atrp.Update(barWithNaN);
// Result should be finite
Assert.True(double.IsFinite(resultAfterNaN.Value));
}
[Fact]
public void Infinity_Input_UsesLastValidValue()
{
var atrp = new Atrp(5);
var bar1 = new TBar(DateTime.UtcNow, 100, 105, 95, 102, 1000);
atrp.Update(bar1);
var bar2 = new TBar(DateTime.UtcNow.AddMinutes(1), 102, 110, 98, 108, 1000);
atrp.Update(bar2);
// Feed bar with Infinity
var barWithInf = new TBar(DateTime.UtcNow.AddMinutes(2), 108, double.PositiveInfinity, 100, 112, 1000);
var resultAfterInf = atrp.Update(barWithInf);
Assert.True(double.IsFinite(resultAfterInf.Value) || double.IsPositiveInfinity(resultAfterInf.Value));
}
// ============== Consistency Tests ==============
[Fact]
public void BatchCalc_MatchesIterativeCalc()
{
var atrpIterative = new Atrp(14);
var gbm = new GBM(startPrice: 100.0, mu: 0.02, sigma: 0.1);
var bars = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
// Calculate iteratively
var iterativeResults = new TSeries();
foreach (var bar in bars)
{
iterativeResults.Add(atrpIterative.Update(bar));
}
// Calculate batch
var batchResults = Atrp.Batch(bars, 14);
// Compare
Assert.Equal(iterativeResults.Count, batchResults.Count);
for (int i = 0; i < iterativeResults.Count; i++)
{
Assert.Equal(iterativeResults[i].Value, batchResults[i].Value, 1e-10);
}
}
[Fact]
public void TBarSeries_Update_MatchesStreaming()
{
var atrp1 = new Atrp(14);
var atrp2 = new Atrp(14);
var gbm = new GBM();
var bars = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
// Streaming
foreach (var bar in bars)
{
atrp1.Update(bar);
}
// Batch
atrp2.Update(bars);
Assert.Equal(atrp1.Last.Value, atrp2.Last.Value, 1e-10);
}
[Fact]
public void Chainability_Works()
{
var atrp = new Atrp(14);
var gbm = new GBM();
var bars = gbm.Fetch(50, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var result = atrp.Update(bars);
Assert.Equal(50, result.Count);
Assert.Equal(atrp.Last.Value, result.Last.Value);
}
// ============== ATRP-Specific Tests ==============
[Fact]
public void ATRP_IsPercentageOfPrice()
{
var atrp = new Atrp(14);
var bar = new TBar(DateTime.UtcNow, 100, 110, 90, 100, 1000);
// TR = 20, Close = 100
// ATRP = (20 / 100) * 100 = 20%
var result = atrp.Update(bar);
Assert.Equal(20.0, result.Value, 1e-10);
}
[Fact]
public void ATRP_HigherPriceAsset_LowerPercentage()
{
// Same volatility (TR=20) but different price levels
var atrp1 = new Atrp(14);
var atrp2 = new Atrp(14);
// Low price asset: Close = 100, TR = 20 -> ATRP = 20%
var bar1 = new TBar(DateTime.UtcNow, 100, 110, 90, 100, 1000);
var result1 = atrp1.Update(bar1);
// High price asset: Close = 1000, TR = 20 -> ATRP = 2%
var bar2 = new TBar(DateTime.UtcNow, 1000, 1010, 990, 1000, 1000);
var result2 = atrp2.Update(bar2);
Assert.True(result1.Value > result2.Value);
Assert.Equal(20.0, result1.Value, 1e-10);
Assert.Equal(2.0, result2.Value, 1e-10);
}
[Fact]
public void ATRP_ProportionalVolatility_SamePercentage()
{
var atrp1 = new Atrp(14);
var atrp2 = new Atrp(14);
// Asset 1: Close = 100, TR = 10 (10% volatility)
var bar1 = new TBar(DateTime.UtcNow, 100, 105, 95, 100, 1000);
var result1 = atrp1.Update(bar1);
// Asset 2: Close = 1000, TR = 100 (10% volatility)
var bar2 = new TBar(DateTime.UtcNow, 1000, 1050, 950, 1000, 1000);
var result2 = atrp2.Update(bar2);
Assert.Equal(result1.Value, result2.Value, 1e-10);
Assert.Equal(10.0, result1.Value, 1e-10);
}
// ============== Static Batch Method ==============
[Fact]
public void StaticBatch_Works()
{
var gbm = new GBM();
var bars = gbm.Fetch(50, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var results = Atrp.Batch(bars, 14);
Assert.Equal(50, results.Count);
Assert.True(double.IsFinite(results.Last.Value));
}
// ============== Edge Cases ==============
[Fact]
public void SingleBar_ReturnsValidResult()
{
var atrp = new Atrp(14);
var bar = new TBar(DateTime.UtcNow, 100, 110, 90, 100, 1000);
var result = atrp.Update(bar);
Assert.True(double.IsFinite(result.Value));
Assert.Equal(20.0, result.Value, 1e-10); // (H-L)/Close * 100 = 20/100 * 100 = 20%
}
[Fact]
public void Period1_Works()
{
var atrp = new Atrp(1);
var gbm = new GBM();
var bars = gbm.Fetch(10, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
foreach (var bar in bars)
{
var result = atrp.Update(bar);
Assert.True(double.IsFinite(result.Value));
}
Assert.True(atrp.IsHot);
}
[Fact]
public void FlatBars_ZeroVolatility()
{
var atrp = new Atrp(5);
// All bars have same OHLC values
for (int i = 0; i < 10; i++)
{
var bar = new TBar(DateTime.UtcNow.AddMinutes(i), 100, 100, 100, 100, 1000);
atrp.Update(bar);
}
// ATRP should be 0 for flat bars
Assert.Equal(0.0, atrp.Last.Value, 1e-10);
}
[Fact]
public void ZeroClose_ReturnsNaN()
{
var atrp = new Atrp(14);
var bar = new TBar(DateTime.UtcNow, 0, 10, -10, 0, 1000);
var result = atrp.Update(bar);
Assert.True(double.IsNaN(result.Value));
}
}
@@ -1,350 +0,0 @@
using OoplesFinance.StockIndicators;
using OoplesFinance.StockIndicators.Enums;
using OoplesFinance.StockIndicators.Models;
using Skender.Stock.Indicators;
using TALib;
using Xunit.Abstractions;
namespace QuanTAlib.Tests;
/// <summary>
/// ATRP validation tests.
/// ATRP = (ATR / Close) × 100
/// Since external libraries don't have direct ATRP, we validate by computing ATR
/// from external libraries and converting to ATRP using the same formula.
/// </summary>
public sealed class AtrpValidationTests : IDisposable
{
private readonly ValidationTestData _testData;
private readonly ITestOutputHelper _output;
private bool _disposed;
public AtrpValidationTests(ITestOutputHelper output)
{
_output = output;
_testData = new ValidationTestData();
}
public void Dispose()
{
Dispose(true);
}
private void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
_disposed = true;
if (disposing)
{
_testData?.Dispose();
}
}
[Fact]
public void Validate_Skender_Batch()
{
int[] periods = { 14 };
foreach (var period in periods)
{
// Calculate QuanTAlib ATRP (batch TSeries)
var atrp = new Atrp(period);
var qResult = atrp.Update(_testData.Bars);
// Calculate Skender ATR and convert to ATRP
var sAtr = _testData.SkenderQuotes.GetAtr(period).ToList();
var closeValues = _testData.SkenderQuotes.ToList();
// Build expected ATRP values: (ATR / Close) * 100
var expectedAtrp = new List<double>();
for (int i = 0; i < sAtr.Count; i++)
{
double? atr = sAtr[i].Atr;
double close = (double)closeValues[i].Close;
if (atr.HasValue && close > 0)
{
expectedAtrp.Add((atr.Value / close) * 100.0);
}
else
{
expectedAtrp.Add(double.NaN);
}
}
// Compare last 100 records
ValidationHelper.VerifyData(qResult, expectedAtrp, (s) => s, 100, ValidationHelper.SkenderTolerance);
}
_output.WriteLine("ATRP Batch(TSeries) validated successfully against Skender ATR");
}
[Fact]
public void Validate_Skender_Streaming()
{
int[] periods = { 14 };
foreach (var period in periods)
{
// Calculate QuanTAlib ATRP (streaming)
var atrp = new Atrp(period);
var qResults = new List<double>();
foreach (var item in _testData.Bars)
{
qResults.Add(atrp.Update(item).Value);
}
// Calculate Skender ATR and convert to ATRP
var sAtr = _testData.SkenderQuotes.GetAtr(period).ToList();
var closeValues = _testData.SkenderQuotes.ToList();
// Build expected ATRP values
var expectedAtrp = new List<double>();
for (int i = 0; i < sAtr.Count; i++)
{
double? atr = sAtr[i].Atr;
double close = (double)closeValues[i].Close;
if (atr.HasValue && close > 0)
{
expectedAtrp.Add((atr.Value / close) * 100.0);
}
else
{
expectedAtrp.Add(double.NaN);
}
}
// Compare last 100 records
ValidationHelper.VerifyData(qResults, expectedAtrp, (s) => s, 100, ValidationHelper.SkenderTolerance);
}
_output.WriteLine("ATRP Streaming validated successfully against Skender ATR");
}
[Fact]
public void Validate_Talib_Batch()
{
int[] periods = { 14 };
// Note: QuanTAlib ATRP uses warmup-compensated RMA which gives slightly different
// results than TA-Lib's classic Wilder's approach. The difference (~4-7%) accumulates
// over 5000 bars but both implementations are mathematically valid.
// Using absolute tolerance of 0.10 to account for accumulated drift divergence
// QuanTAlib warmup-compensated RMA diverges from TA-Lib classic Wilder over time
const double AtrpTolerance = 0.10;
// Prepare data for TA-Lib (double[])
double[] hData = _testData.Bars.High.Select(x => x.Value).ToArray();
double[] lData = _testData.Bars.Low.Select(x => x.Value).ToArray();
double[] cData = _testData.Bars.Close.Select(x => x.Value).ToArray();
double[] atrOutput = new double[hData.Length];
foreach (var period in periods)
{
// Calculate QuanTAlib ATRP (batch TSeries)
var atrp = new Atrp(period);
var qResult = atrp.Update(_testData.Bars);
// Calculate TA-Lib ATR
var retCode = TALib.Functions.Atr(hData, lData, cData, 0..^0, atrOutput, out var outRange, period);
Assert.Equal(Core.RetCode.Success, retCode);
int lookback = TALib.Functions.AtrLookback(period);
// Convert ATR to ATRP: (ATR / Close) * 100
var expectedAtrp = new double[atrOutput.Length];
for (int i = outRange.Start.Value; i < outRange.End.Value; i++)
{
double atr = atrOutput[i];
double close = cData[i];
expectedAtrp[i] = close > 0 ? (atr / close) * 100.0 : double.NaN;
}
// Compare last 100 records
ValidationHelper.VerifyData(qResult, expectedAtrp, outRange, lookback, tolerance: AtrpTolerance);
}
_output.WriteLine("ATRP Batch(TSeries) validated successfully against TA-Lib ATR");
}
[Fact]
public void Validate_Talib_Streaming()
{
int[] periods = { 14 };
// Note: QuanTAlib ATRP uses warmup-compensated RMA which gives slightly different
// results than TA-Lib's classic Wilder's approach. The difference (~4-7%) accumulates
// over 5000 bars but both implementations are mathematically valid.
// Using absolute tolerance of 0.10 to account for accumulated drift divergence
// QuanTAlib warmup-compensated RMA diverges from TA-Lib classic Wilder over time
const double AtrpTolerance = 0.10;
// Prepare data for TA-Lib (double[])
double[] hData = _testData.Bars.High.Select(x => x.Value).ToArray();
double[] lData = _testData.Bars.Low.Select(x => x.Value).ToArray();
double[] cData = _testData.Bars.Close.Select(x => x.Value).ToArray();
double[] atrOutput = new double[hData.Length];
foreach (var period in periods)
{
// Calculate QuanTAlib ATRP (streaming)
var atrp = new Atrp(period);
var qResults = new List<double>();
foreach (var item in _testData.Bars)
{
qResults.Add(atrp.Update(item).Value);
}
// Calculate TA-Lib ATR
var retCode = TALib.Functions.Atr(hData, lData, cData, 0..^0, atrOutput, out var outRange, period);
Assert.Equal(Core.RetCode.Success, retCode);
int lookback = TALib.Functions.AtrLookback(period);
// Convert ATR to ATRP
var expectedAtrp = new double[atrOutput.Length];
for (int i = outRange.Start.Value; i < outRange.End.Value; i++)
{
double atr = atrOutput[i];
double close = cData[i];
expectedAtrp[i] = close > 0 ? (atr / close) * 100.0 : double.NaN;
}
// Compare last 100 records
ValidationHelper.VerifyData(qResults, expectedAtrp, outRange, lookback, tolerance: AtrpTolerance);
}
_output.WriteLine("ATRP Streaming validated successfully against TA-Lib ATR");
}
[Fact]
public void Validate_Tulip_Batch()
{
int[] periods = { 14 };
// Prepare data for Tulip (double[])
double[] hData = _testData.Bars.High.Select(x => x.Value).ToArray();
double[] lData = _testData.Bars.Low.Select(x => x.Value).ToArray();
double[] cData = _testData.Bars.Close.Select(x => x.Value).ToArray();
foreach (var period in periods)
{
// Calculate QuanTAlib ATRP (batch TSeries)
var atrp = new Atrp(period);
var qResult = atrp.Update(_testData.Bars);
// Calculate Tulip ATR
var atrIndicator = Tulip.Indicators.atr;
double[][] inputs = { hData, lData, cData };
double[] options = { period };
// Tulip ATR lookback
int lookback = atrIndicator.Start(options);
double[][] outputs = { new double[hData.Length - lookback] };
atrIndicator.Run(inputs, options, outputs);
var tAtr = outputs[0];
// Convert ATR to ATRP: (ATR / Close) * 100
var expectedAtrp = new double[tAtr.Length];
for (int i = 0; i < tAtr.Length; i++)
{
int dataIndex = lookback + i;
double close = cData[dataIndex];
expectedAtrp[i] = close > 0 ? (tAtr[i] / close) * 100.0 : double.NaN;
}
// Compare last 100 records
ValidationHelper.VerifyData(qResult, expectedAtrp, lookback, tolerance: ValidationHelper.TulipTolerance);
}
_output.WriteLine("ATRP Batch(TSeries) validated successfully against Tulip ATR");
}
[Fact]
public void Validate_Tulip_Streaming()
{
int[] periods = { 14 };
// Prepare data for Tulip (double[])
double[] hData = _testData.Bars.High.Select(x => x.Value).ToArray();
double[] lData = _testData.Bars.Low.Select(x => x.Value).ToArray();
double[] cData = _testData.Bars.Close.Select(x => x.Value).ToArray();
foreach (var period in periods)
{
// Calculate QuanTAlib ATRP (streaming)
var atrp = new Atrp(period);
var qResults = new List<double>();
foreach (var item in _testData.Bars)
{
qResults.Add(atrp.Update(item).Value);
}
// Calculate Tulip ATR
var atrIndicator = Tulip.Indicators.atr;
double[][] inputs = { hData, lData, cData };
double[] options = { period };
// Tulip ATR lookback
int lookback = atrIndicator.Start(options);
double[][] outputs = { new double[hData.Length - lookback] };
atrIndicator.Run(inputs, options, outputs);
var tAtr = outputs[0];
// Convert ATR to ATRP
var expectedAtrp = new double[tAtr.Length];
for (int i = 0; i < tAtr.Length; i++)
{
int dataIndex = lookback + i;
double close = cData[dataIndex];
expectedAtrp[i] = close > 0 ? (tAtr[i] / close) * 100.0 : double.NaN;
}
// Compare last 100 records
ValidationHelper.VerifyData(qResults, expectedAtrp, lookback, tolerance: ValidationHelper.TulipTolerance);
}
_output.WriteLine("ATRP Streaming validated successfully against Tulip ATR");
}
[Fact]
public void Validate_Ooples_Batch()
{
int[] periods = { 14 };
// Prepare data for Ooples (List<TickerData>)
var ooplesData = _testData.SkenderQuotes.Select(q => new TickerData
{
Date = q.Date,
Close = (double)q.Close,
High = (double)q.High,
Low = (double)q.Low,
Open = (double)q.Open,
Volume = (double)q.Volume
}).ToList();
foreach (var period in periods)
{
// Calculate QuanTAlib ATRP (batch TSeries)
var atrp = new Atrp(period);
var qResult = atrp.Update(_testData.Bars);
// Calculate Ooples ATR
var stockData = new StockData(ooplesData);
var oAtr = stockData.CalculateAverageTrueRange(MovingAvgType.WildersSmoothingMethod, period).OutputValues.Values.First();
// Convert ATR to ATRP
var expectedAtrp = new List<double>();
for (int i = 0; i < oAtr.Count; i++)
{
double atr = oAtr[i];
double close = ooplesData[i].Close;
expectedAtrp.Add(close > 0 ? (atr / close) * 100.0 : double.NaN);
}
// Compare last 100 records
ValidationHelper.VerifyData(qResult, expectedAtrp, (s) => s, 100, ValidationHelper.OoplesTolerance);
}
_output.WriteLine("ATRP Batch(TSeries) validated successfully against Ooples ATR");
}
}
-280
View File
@@ -1,280 +0,0 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace QuanTAlib;
/// <summary>
/// ATRP: Average True Range Percent
/// </summary>
/// <remarks>
/// ATR as percentage of closing price for cross-asset volatility comparison.
/// Higher values indicate greater relative volatility; typical range 0-10%.
///
/// Calculation: <c>ATRP = (ATR / Close) × 100</c>.
/// </remarks>
/// <seealso href="Atrp.md">Detailed documentation</seealso>
[SkipLocalsInit]
public sealed class Atrp : AbstractBase
{
private readonly double _alpha;
private readonly double _decay;
private const double ConvergenceThreshold = 1e-10;
[StructLayout(LayoutKind.Auto)]
private record struct State(
double RawRma,
double E,
double PrevClose,
double LastValidHigh,
double LastValidLow,
double LastValidClose,
bool IsInitialized);
private State _state;
private State _p_state;
/// <summary>
/// Creates ATRP with specified period.
/// </summary>
/// <param name="period">Period for ATR calculation (must be > 0)</param>
public Atrp(int period)
{
if (period <= 0)
{
throw new ArgumentException("Period must be greater than 0", nameof(period));
}
_alpha = 1.0 / period;
_decay = 1.0 - _alpha;
Name = $"Atrp({period})";
// Warmup based on RMA convergence: ln(0.05) / ln(1 - alpha)
WarmupPeriod = (int)Math.Ceiling(Math.Log(0.05) / Math.Log(_decay));
_state = new State(0, 1.0, double.NaN, double.NaN, double.NaN, double.NaN, false);
_p_state = _state;
}
/// <summary>
/// Creates ATRP with specified source and period.
/// </summary>
/// <param name="source">Source to subscribe to</param>
/// <param name="period">Period for ATRP calculation</param>
public Atrp(ITValuePublisher source, int period) : this(period)
{
source.Pub += Handle;
}
/// <summary>
/// Creates ATRP from a TBarSeries.
/// </summary>
/// <param name="source">Bar series source</param>
/// <param name="period">Period for ATRP calculation</param>
public Atrp(TBarSeries source, int period) : this(period)
{
var result = Update(source);
if (result.Count > 0)
{
Last = result.Last;
}
}
private void Handle(object? sender, in TValueEventArgs e) => Update(e.Value, e.IsNew);
/// <summary>
/// True if the ATRP has warmed up and is providing valid results.
/// </summary>
public override bool IsHot => _state.E <= 0.05;
/// <summary>
/// Initializes the indicator state using the provided history.
/// Note: ATRP needs OHLCV data. This Prime method expects pre-calculated TR values.
/// </summary>
public override void Prime(ReadOnlySpan<double> source, TimeSpan? step = null)
{
for (int i = 0; i < source.Length; i++)
{
double tr = source[i];
_state.RawRma = Math.FusedMultiplyAdd(_state.RawRma, _decay, _alpha * tr);
_state.E *= _decay;
}
if (source.Length > 0)
{
double atr = _state.E > ConvergenceThreshold ? _state.RawRma / (1.0 - _state.E) : _state.RawRma;
// Without close price, we can't calculate ATRP percentage
Last = new TValue(DateTime.UtcNow, atr);
}
_p_state = _state;
}
/// <summary>
/// Resets the ATRP state.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void Reset()
{
_state = new State(0, 1.0, double.NaN, double.NaN, double.NaN, double.NaN, false);
_p_state = _state;
Last = default;
}
/// <summary>
/// Updates ATRP with a new bar.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public TValue Update(TBar input, bool isNew = true)
{
if (isNew)
{
_p_state = _state;
}
else
{
_state = _p_state;
}
// Get valid values with last-value substitution
double high = input.High;
double low = input.Low;
double close = input.Close;
if (double.IsFinite(high))
{
_state.LastValidHigh = high;
}
else
{
high = _state.LastValidHigh;
}
if (double.IsFinite(low))
{
_state.LastValidLow = low;
}
else
{
low = _state.LastValidLow;
}
if (double.IsFinite(close))
{
_state.LastValidClose = close;
}
else
{
close = _state.LastValidClose;
}
// Handle case where no valid values yet
if (double.IsNaN(close))
{
Last = new TValue(input.Time, double.NaN);
PubEvent(Last, isNew);
return Last;
}
// Calculate True Range
double tr;
if (!_state.IsInitialized || double.IsNaN(_state.PrevClose))
{
// First bar: TR = High - Low
tr = high - low;
}
else
{
double hl = high - low;
double hpc = Math.Abs(high - _state.PrevClose);
double lpc = Math.Abs(low - _state.PrevClose);
tr = Math.Max(hl, Math.Max(hpc, lpc));
}
// Calculate ATR using RMA with warmup compensation
_state.RawRma = Math.FusedMultiplyAdd(_state.RawRma, _decay, _alpha * tr);
_state.E *= _decay;
double atr = _state.E > ConvergenceThreshold ? _state.RawRma / (1.0 - _state.E) : _state.RawRma;
// Calculate ATRP: (ATR / Close) * 100
double atrp = Math.Abs(close) > 0 ? (atr / close) * 100.0 : double.NaN;
// Update state
if (isNew)
{
_state.PrevClose = close;
_state.IsInitialized = true;
}
TValue result = new(input.Time, atrp);
Last = result;
PubEvent(Last, isNew);
return result;
}
/// <summary>
/// Updates ATRP with a TValue input.
/// </summary>
/// <exception cref="NotSupportedException">
/// ATRP requires OHLC bar data to calculate the percentage (ATR/Close * 100).
/// Use Update(TBar) instead.
/// </exception>
public override TValue Update(TValue input, bool isNew = true)
{
throw new NotSupportedException(
"ATRP requires OHLC bar data to calculate the percentage (ATR/Close * 100). " +
"Use Update(TBar) instead.");
}
/// <summary>
/// Updates ATRP from a TBarSeries.
/// </summary>
public TSeries Update(TBarSeries source)
{
if (source.Count == 0)
{
return [];
}
var t = new List<long>(source.Count);
var v = new List<double>(source.Count);
for (int i = 0; i < source.Count; i++)
{
TValue result = Update(source[i], true);
t.Add(result.Time);
v.Add(result.Value);
}
return new TSeries(t, v);
}
/// <summary>
/// Updates ATRP from a TSeries.
/// </summary>
/// <exception cref="NotSupportedException">
/// ATRP requires OHLC bar data to calculate the percentage (ATR/Close * 100).
/// Use Update(TBarSeries) instead.
/// </exception>
public override TSeries Update(TSeries source)
{
throw new NotSupportedException(
"ATRP requires OHLC bar data to calculate the percentage (ATR/Close * 100). " +
"Use Update(TBarSeries) instead.");
}
/// <summary>
/// Calculates ATRP for the entire series using a new instance.
/// </summary>
public static TSeries Batch(TBarSeries source, int period)
{
var atrp = new Atrp(period);
return atrp.Update(source);
}
public static (TSeries Results, Atrp Indicator) Calculate(TBarSeries source, int period)
{
var indicator = new Atrp(period);
TSeries results = indicator.Update(source);
return (results, indicator);
}
}
-128
View File
@@ -1,128 +0,0 @@
# ATRP: Average True Range Percent
> "Volatility without context is noise. ATRP gives you context."
ATRP normalizes the Average True Range (ATR) as a percentage of the closing price. This transforms an absolute volatility measure into a relative one, enabling meaningful comparisons across different price levels and different assets.
A $5 stock and a $500 stock might both have an ATR of 2.0, but their volatility profiles are completely different. ATRP reveals the truth: the $5 stock is moving 40% while the $500 stock is moving 0.4%.
## Historical Context
ATRP is a derivative of J. Welles Wilder Jr.'s ATR, introduced in his 1978 work *New Concepts in Technical Trading Systems*. While Wilder focused on absolute range, traders quickly realized that percentage-based normalization was necessary for portfolio-level analysis and cross-asset comparison.
The indicator gained prominence with the rise of systematic trading strategies that needed to compare volatility across diverse asset classes—equities, commodities, forex—without the distortion of absolute price differences.
## Architecture & Physics
ATRP builds on ATR's foundation and adds a single normalization step:
1. **True Range (TR)**: Captures the "real" distance price traveled, including gaps.
2. **RMA Smoothing**: Wilder's smoothing method ($\alpha = 1/N$) provides the characteristic slow decay.
3. **Percentage Normalization**: Divides by current close price and multiplies by 100.
### Why Percentage Matters
Consider two scenarios:
* **Stock A**: Price = \$100, ATR = 5.0 → ATRP = 5%
* **Stock B**: Price = \$10, ATR = 2.0 → ATRP = 20%
ATR alone suggests Stock A is more volatile. ATRP reveals Stock B moves four times more in percentage terms—critical information for position sizing and risk management.
## Mathematical Foundation
### 1. True Range (TR)
$$
TR_t = \max(H_t - L_t, |H_t - C_{t-1}|, |L_t - C_{t-1}|)
$$
Where:
* $H_t$: Current High
* $L_t$: Current Low
* $C_{t-1}$: Previous Close
### 2. Average True Range (ATR)
$$
ATR_t = RMA(TR, N)
$$
Expanding the RMA:
$$
ATR_t = \frac{ATR_{t-1} \times (N-1) + TR_t}{N}
$$
### 3. ATRP (Percentage)
$$
ATRP_t = \frac{ATR_t}{C_t} \times 100
$$
Where $C_t$ is the current closing price.
## Performance Profile
| Metric | Score | Notes |
| :--- | :--- | :--- |
| **Throughput** | 10 | High; O(1) calculation via RMA + single division. |
| **Allocations** | 0 | Zero-allocation in hot paths. |
| **Complexity** | O(1) | Constant time regardless of period. |
| **Accuracy** | 10 | Matches ATR-based calculation exactly. |
| **Timeliness** | 4 | Inherits ATR's lag due to RMA smoothing. |
| **Overshoot** | 0 | Bounded by mathematical definition. |
| **Smoothness** | 8 | Smooth decay from RMA; slight additional noise from close price variation. |
## Validation
ATRP is validated by computing ATR from external libraries and applying the same percentage formula.
| Library | Status | Notes |
| :--- | :--- | :--- |
| **QuanTAlib** | ✅ | Validated. |
| **TA-Lib** | ✅ | Validated via `(TA_ATR / Close) × 100`. |
| **Skender** | ✅ | Validated via `(GetAtr / Close) × 100`. |
| **Tulip** | ✅ | Validated via `(atr / Close) × 100`. |
| **Ooples** | ✅ | Validated via `(CalculateAverageTrueRange / Close) × 100`. |
## Use Cases
### Position Sizing
ATRP enables volatility-adjusted position sizing:
```
Position Size = Risk Capital / (ATRP × Entry Price)
```
This ensures each position carries equivalent percentage risk regardless of the asset's absolute price.
### Cross-Asset Comparison
Compare volatility across:
* Different price levels (penny stocks vs. blue chips)
* Different asset classes (equities vs. commodities)
* Different time periods (adjusting for price drift)
### Regime Detection
* **ATRP < 1%**: Low volatility regime—expect consolidation, mean reversion strategies favored.
* **ATRP 2-4%**: Normal volatility—standard trend-following conditions.
* **ATRP > 5%**: High volatility regime—crisis conditions, wider stops required.
## Common Pitfalls
* **Lag**: ATRP inherits ATR's lag. It tells you what volatility *was*, not what it *will be*.
* **Close Price Sensitivity**: A sharp close price move affects both the numerator (via TR) and denominator (close), creating transient spikes. Use multiple periods for confirmation.
* **Zero/Near-Zero Prices**: Assets approaching zero will show extreme ATRP values. Ensure minimum price thresholds in screeners.
* **Dividend Adjustments**: Unadjusted price data can create artificial gaps around ex-dividend dates, inflating TR.
## Related Indicators
* **ATR**: The absolute volatility measure ATRP normalizes.
* **NATR**: Similar concept; some implementations differ in smoothing or warmup handling.
* **ATRN**: ATR normalized to [0,1] range based on historical min/max.
* **Volatility Ratio**: Compares current TR to average TR for breakout detection.
-40
View File
@@ -1,40 +0,0 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("Average True Range Percent (ATRP)", "ATRP", overlay=false, format=format.percent, precision=2)
//@function Calculates the Average True Range Percent (ATRP)
//@param length The period length for the ATR calculation.
//@returns The ATRP value.
//@optimized Beta precomputation for RMA warmup compensation
atrp(simple int length) =>
if length <= 0
runtime.error("Period must be greater than 0")
var float prevClose = close
float tr1 = high - low
float tr2 = math.abs(high - prevClose)
float tr3 = math.abs(low - prevClose)
float trueRange = math.max(tr1, tr2, tr3)
prevClose := close
float alpha = 1.0 / float(length)
float beta = 1.0 - alpha
var float EPSILON = 1e-10
var float raw_rma = 0.0
var float e = 1.0
float atr = na
if not na(trueRange)
raw_rma := (raw_rma * (length - 1) + trueRange) / length
e *= beta
atr := e > EPSILON ? raw_rma / (1.0 - e) : raw_rma
close != 0.0 ? atr / close * 100 : na
// ---------- Main loop ----------
// Inputs
i_length = input.int(14, "Length", minval=1, tooltip="Number of bars used for the ATR calculation")
// Calculation
atrpValue = atrp(i_length)
// Plot
plot(atrpValue, "ATRP", color=color.yellow, linewidth=2)
+1 -2
View File
@@ -186,8 +186,7 @@ Ensures equal percentage risk per position regardless of asset price.
## Related Indicators
- **ATR**: Absolute volatility measure NATR normalizes
- **ATRP**: Mathematically identical; different naming convention
- **ATRN**: ATR normalized to [0,1] based on historical min/max
- **ATRN**: ATR normalized to [0,1] based on historical min/max (different algorithm)
- **CV**: Coefficient of Variation—alternative percentage volatility measure
- **HV**: Historical Volatility—annualized standard deviation approach
+2 -3
View File
@@ -201,7 +201,7 @@ TR is one of the most consistently implemented indicators across all libraries.
2. **Confusing TR with ATR**: TR is the raw, unsmoothed value per bar. ATR is TR smoothed over a period. TR can be very volatile; ATR provides a more stable volatility estimate.
3. **Unit dependency**: TR is in the same units as price. A $500 stock might have TR=10 while a $50 stock has TR=1, even if percentage volatility is identical. Use NATR (Normalized ATR) or ATRP (ATR Percent) for percentage-based comparisons.
3. **Unit dependency**: TR is in the same units as price. A $500 stock might have TR=10 while a $50 stock has TR=1, even if percentage volatility is identical. Use NATR (Normalized ATR) for percentage-based comparisons.
4. **Gap sensitivity**: TR captures gaps, which may or may not be desirable. For intraday-only volatility, use High-Low range instead.
@@ -263,8 +263,7 @@ If gap contribution > 50% of TR: Significant gap move
| Indicator | Relationship to TR |
| :--- | :--- |
| **ATR** | Smoothed TR (RMA/Wilder's MA) |
| **NATR** | ATR / Close × 100 |
| **ATRP** | ATR / Close × 100 (same as NATR) |
| **NATR** | ATR / Close × 100 (also known as ATRP) |
| **Keltner Channel** | Uses ATR for band width |
| **Chandelier Exit** | Uses ATR for trailing stop |
| **SuperTrend** | Uses ATR for trend bands |
+1 -1
View File
@@ -240,7 +240,7 @@ Trend following: Best when VR 1.0-1.5 (movement with stability)
| **Bollinger Width** | Both measure volatility; VR uses TR, BB uses std dev |
| **Keltner Width** | KC uses ATR; VR provides ratio view of same data |
| **ADX** | ADX measures trend strength; VR measures volatility expansion |
| **ATRP** | ATRP = ATR/Close×100; VR = TR/ATR |
| **NATR** | NATR = ATR/Close×100; VR = TR/ATR |
## Implementation Notes