mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-18 18:48:05 +00:00
feat: add new indicators (Decay, Edecay, MinusDi, MinusDm, PlusDi, PlusDm, Maxindex, Minindex, Sarext) and update pine scripts, core libs, validation tests, and python bindings
This commit is contained in:
@@ -17,6 +17,8 @@ Dynamics indicators measure trend strength, speed, and direction. Unlike momentu
|
||||
| [CHOP](chop/Chop.md) | Choppiness Index | Trendiness measure. High values = choppy. Low = trending. |
|
||||
| [DMX](dmx/Dmx.md) | Jurik DMX | Smoothed bipolar DMI using Jurik smoothing. Low noise. |
|
||||
| [DX](dx/Dx.md) | Directional Movement Index | Raw directional strength. Unsmoothed ADX component. |
|
||||
| [MINUS_DI](minusdi/MinusDi.md) | Minus Directional Indicator | Downward directional movement as % of true range. 0-100. |
|
||||
| [MINUS_DM](minusdm/MinusDm.md) | Minus Directional Movement | Wilder-smoothed downward directional movement. Price units. |
|
||||
| [HT_TRENDMODE](ht_trendmode/Httrendmode.md) | Ehlers Hilbert Transform Trend vs Cycle Mode | Ehlers Hilbert Transform. Binary trend/cycle mode detection. |
|
||||
| [ICHIMOKU](ichimoku/Ichimoku.md) | Ichimoku Cloud | Five-line system. Cloud defines support/resistance zones. |
|
||||
| [IMPULSE](impulse/Impulse.md) | Elder Impulse System | EMA + MACD histogram alignment. Color-coded trend/momentum filter. |
|
||||
@@ -27,5 +29,7 @@ Dynamics indicators measure trend strength, speed, and direction. Unlike momentu
|
||||
| [VORTEX](vortex/Vortex.md) | Vortex Indicator | VI+ and VI- measure positive/negative trend movement. |
|
||||
| [GHLA](ghla/Ghla.md) | Gann High-Low Activator | SMA(High)/SMA(Low) alternating on crossover. |
|
||||
| [PFE](pfe/Pfe.md) | Polarized Fractal Efficiency | Trend efficiency: straight-line / total path distance. |
|
||||
| [PLUS_DI](plusdi/PlusDi.md) | Plus Directional Indicator | Upward directional movement as % of true range. 0-100. |
|
||||
| [PLUS_DM](plusdm/PlusDm.md) | Plus Directional Movement | Wilder-smoothed upward directional movement. Price units. |
|
||||
| [RAVI](ravi/Ravi.md) | Chande Range Action Verification Index | \|SMA(short) − SMA(long)\| / SMA(long) × 100. |
|
||||
| [VHF](vhf/Vhf.md) | Vertical Horizontal Filter | Max-min range / sum of absolute changes. |
|
||||
|
||||
@@ -91,6 +91,78 @@ public sealed class AdxValidationTests : IDisposable
|
||||
ValidationHelper.VerifyData(results, tulipResults, lookback: offset);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DiPlus_MatchesTalib()
|
||||
{
|
||||
var adx = new Adx(14);
|
||||
var diPlusResults = new List<double>();
|
||||
|
||||
for (int i = 0; i < _data.Bars.Count; i++)
|
||||
{
|
||||
adx.Update(_data.Bars[i]);
|
||||
diPlusResults.Add(adx.DiPlus.Value);
|
||||
}
|
||||
|
||||
double[] hData = _data.Bars.High.Select(x => x.Value).ToArray();
|
||||
double[] lData = _data.Bars.Low.Select(x => x.Value).ToArray();
|
||||
double[] cData = _data.Bars.Close.Select(x => x.Value).ToArray();
|
||||
double[] outReal = new double[_data.Bars.Count];
|
||||
|
||||
var retCode = Functions.PlusDI(hData, lData, cData, 0..^0, outReal, out var outRange, 14);
|
||||
Assert.Equal(TALib.Core.RetCode.Success, retCode);
|
||||
|
||||
int lookback = Functions.PlusDILookback(14);
|
||||
ValidationHelper.VerifyData(diPlusResults, outReal, outRange, lookback);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DiMinus_MatchesTalib()
|
||||
{
|
||||
var adx = new Adx(14);
|
||||
var diMinusResults = new List<double>();
|
||||
|
||||
for (int i = 0; i < _data.Bars.Count; i++)
|
||||
{
|
||||
adx.Update(_data.Bars[i]);
|
||||
diMinusResults.Add(adx.DiMinus.Value);
|
||||
}
|
||||
|
||||
double[] hData = _data.Bars.High.Select(x => x.Value).ToArray();
|
||||
double[] lData = _data.Bars.Low.Select(x => x.Value).ToArray();
|
||||
double[] cData = _data.Bars.Close.Select(x => x.Value).ToArray();
|
||||
double[] outReal = new double[_data.Bars.Count];
|
||||
|
||||
var retCode = Functions.MinusDI(hData, lData, cData, 0..^0, outReal, out var outRange, 14);
|
||||
Assert.Equal(TALib.Core.RetCode.Success, retCode);
|
||||
|
||||
int lookback = Functions.MinusDILookback(14);
|
||||
ValidationHelper.VerifyData(diMinusResults, outReal, outRange, lookback);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesSkender_DiValues()
|
||||
{
|
||||
var adx = new Adx(14);
|
||||
var diPlusResults = new List<double>();
|
||||
var diMinusResults = new List<double>();
|
||||
|
||||
for (int i = 0; i < _data.Bars.Count; i++)
|
||||
{
|
||||
adx.Update(_data.Bars[i]);
|
||||
diPlusResults.Add(adx.DiPlus.Value);
|
||||
diMinusResults.Add(adx.DiMinus.Value);
|
||||
}
|
||||
|
||||
// Skender's GetAdx returns ADX with +DI and -DI values
|
||||
var skenderResults = _data.SkenderQuotes.GetAdx(14).ToList();
|
||||
|
||||
// Verify +DI
|
||||
ValidationHelper.VerifyData(diPlusResults, skenderResults, x => x.Pdi);
|
||||
|
||||
// Verify -DI
|
||||
ValidationHelper.VerifyData(diMinusResults, skenderResults, x => x.Mdi);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesOoples()
|
||||
{
|
||||
|
||||
@@ -62,6 +62,16 @@ public sealed class Adx : ITValuePublisher
|
||||
/// </summary>
|
||||
public TValue DiMinus { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Current smoothed +DM value (RMA-smoothed raw plus directional movement, before TR normalization).
|
||||
/// </summary>
|
||||
public TValue DmPlus { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Current smoothed -DM value (RMA-smoothed raw minus directional movement, before TR normalization).
|
||||
/// </summary>
|
||||
public TValue DmMinus { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// True if the ADX has warmed up and is providing valid results.
|
||||
/// </summary>
|
||||
@@ -116,6 +126,8 @@ public sealed class Adx : ITValuePublisher
|
||||
Last = default;
|
||||
DiPlus = default;
|
||||
DiMinus = default;
|
||||
DmPlus = default;
|
||||
DmMinus = default;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -325,6 +337,8 @@ public sealed class Adx : ITValuePublisher
|
||||
|
||||
DiPlus = new TValue(input.Time, diPlus);
|
||||
DiMinus = new TValue(input.Time, diMinus);
|
||||
DmPlus = new TValue(input.Time, _samples >= _period ? _dmPlusSmooth : 0);
|
||||
DmMinus = new TValue(input.Time, _samples >= _period ? _dmMinusSmooth : 0);
|
||||
Last = new TValue(input.Time, finalAdx);
|
||||
|
||||
Pub?.Invoke(this, new TValueEventArgs { Value = Last, IsNew = isNew });
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("Average Directional Movement Index (ADX)", "ADX", overlay=false)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("Average Directional Movement Index Rating (ADXR)", "ADXR", overlay=false)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("Williams Alligator", "ALLIGATOR", overlay=true)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("Archer Moving Averages Trends (AMAT)", "AMAT", overlay=false)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("Aroon (AROON)", "AROON", overlay=false)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("Aroon Oscillator", "AROONOSC", overlay=false)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("Choppiness Index", "CHOP", overlay=false)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("Jurik Directional Movement Index (DMX)", "DMX", overlay=false)
|
||||
|
||||
@@ -102,6 +102,52 @@ public sealed class DxValidationTests : IDisposable
|
||||
ValidationHelper.VerifyData(diMinusResults, outReal, outRange, lookback);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DmPlus_MatchesTalib()
|
||||
{
|
||||
var dx = new Dx(14);
|
||||
var dmPlusResults = new List<double>();
|
||||
|
||||
for (int i = 0; i < _data.Bars.Count; i++)
|
||||
{
|
||||
dx.Update(_data.Bars[i]);
|
||||
dmPlusResults.Add(dx.DmPlus.Value);
|
||||
}
|
||||
|
||||
double[] hData = _data.Bars.High.Select(x => x.Value).ToArray();
|
||||
double[] lData = _data.Bars.Low.Select(x => x.Value).ToArray();
|
||||
double[] outReal = new double[_data.Bars.Count];
|
||||
|
||||
var retCode = Functions.PlusDM(hData, lData, 0..^0, outReal, out var outRange, 14);
|
||||
Assert.Equal(TALib.Core.RetCode.Success, retCode);
|
||||
|
||||
int lookback = Functions.PlusDMLookback(14);
|
||||
ValidationHelper.VerifyData(dmPlusResults, outReal, outRange, lookback);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DmMinus_MatchesTalib()
|
||||
{
|
||||
var dx = new Dx(14);
|
||||
var dmMinusResults = new List<double>();
|
||||
|
||||
for (int i = 0; i < _data.Bars.Count; i++)
|
||||
{
|
||||
dx.Update(_data.Bars[i]);
|
||||
dmMinusResults.Add(dx.DmMinus.Value);
|
||||
}
|
||||
|
||||
double[] hData = _data.Bars.High.Select(x => x.Value).ToArray();
|
||||
double[] lData = _data.Bars.Low.Select(x => x.Value).ToArray();
|
||||
double[] outReal = new double[_data.Bars.Count];
|
||||
|
||||
var retCode = Functions.MinusDM(hData, lData, 0..^0, outReal, out var outRange, 14);
|
||||
Assert.Equal(TALib.Core.RetCode.Success, retCode);
|
||||
|
||||
int lookback = Functions.MinusDMLookback(14);
|
||||
ValidationHelper.VerifyData(dmMinusResults, outReal, outRange, lookback);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesSkender_DiValues()
|
||||
{
|
||||
|
||||
@@ -53,6 +53,18 @@ public sealed class Dx : ITValuePublisher
|
||||
/// </summary>
|
||||
public TValue DiMinus { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Current smoothed +DM value (Wilder-smoothed raw plus directional movement, before TR normalization).
|
||||
/// Equivalent to TA-Lib PLUS_DM.
|
||||
/// </summary>
|
||||
public TValue DmPlus { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Current smoothed -DM value (Wilder-smoothed raw minus directional movement, before TR normalization).
|
||||
/// Equivalent to TA-Lib MINUS_DM.
|
||||
/// </summary>
|
||||
public TValue DmMinus { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// True if the DX has warmed up and is providing valid results.
|
||||
/// </summary>
|
||||
@@ -106,6 +118,8 @@ public sealed class Dx : ITValuePublisher
|
||||
Last = default;
|
||||
DiPlus = default;
|
||||
DiMinus = default;
|
||||
DmPlus = default;
|
||||
DmMinus = default;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -276,6 +290,8 @@ public sealed class Dx : ITValuePublisher
|
||||
|
||||
DiPlus = new TValue(input.Time, diPlus);
|
||||
DiMinus = new TValue(input.Time, diMinus);
|
||||
DmPlus = new TValue(input.Time, _samples >= _period ? _dmPlusSmooth : 0);
|
||||
DmMinus = new TValue(input.Time, _samples >= _period ? _dmMinusSmooth : 0);
|
||||
Last = new TValue(input.Time, dx);
|
||||
|
||||
Pub?.Invoke(this, new TValueEventArgs { Value = Last, IsNew = isNew });
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("Directional Movement Index (DX)", "DX", overlay=false)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("Gann High-Low Activator", "GHLA", overlay=true)
|
||||
|
||||
@@ -197,4 +197,32 @@ public sealed class HtTrendmodeValidationTests : IDisposable
|
||||
Assert.Equal(results1[i], results2[i]);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HtTrendmode_Correction_Recomputes()
|
||||
{
|
||||
var ind = new HtTrendmode();
|
||||
var t0 = new DateTime(946_684_800_000_000_0L, DateTimeKind.Utc);
|
||||
|
||||
// Build state well past warmup
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
ind.Update(new TValue(t0.AddMinutes(i),
|
||||
100.0 + 10.0 * Math.Sin(2.0 * Math.PI * i / 20.0)), isNew: true);
|
||||
}
|
||||
|
||||
// Anchor bar
|
||||
var anchorTime = t0.AddMinutes(100);
|
||||
const double anchorPrice = 105.5;
|
||||
ind.Update(new TValue(anchorTime, anchorPrice), isNew: true);
|
||||
double anchorSmooth = ind.SmoothPeriod;
|
||||
|
||||
// Correction with a dramatically different price — SmoothPeriod must change
|
||||
ind.Update(new TValue(anchorTime, anchorPrice * 10.0), isNew: false);
|
||||
Assert.NotEqual(anchorSmooth, ind.SmoothPeriod);
|
||||
|
||||
// Correction back to original price — must exactly restore original SmoothPeriod
|
||||
ind.Update(new TValue(anchorTime, anchorPrice), isNew: false);
|
||||
Assert.Equal(anchorSmooth, ind.SmoothPeriod, 1e-9);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("Ehlers Hilbert Transform Trend vs Cycle Mode (HT_TRENDMODE)", "HT_TRENDMODE", overlay=false)
|
||||
|
||||
@@ -679,4 +679,32 @@ public sealed class IchimokuValidationTests : IDisposable
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
[Fact]
|
||||
public void Ichimoku_Correction_Recomputes()
|
||||
{
|
||||
var ind = new Ichimoku();
|
||||
var t0 = new DateTime(946_684_800_000_000_0L, DateTimeKind.Utc);
|
||||
|
||||
// Build state well past warmup
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
double p = 100.0 + 10.0 * Math.Sin(2.0 * Math.PI * i / 20.0);
|
||||
ind.Update(new TBar(t0.AddMinutes(i), p, p + 2, p - 2, p, 1000), isNew: true);
|
||||
}
|
||||
|
||||
// Anchor bar
|
||||
var anchorTime = t0.AddMinutes(100);
|
||||
const double anchorClose = 105.5;
|
||||
ind.Update(new TBar(anchorTime, anchorClose, anchorClose + 2, anchorClose - 2, anchorClose, 1000), isNew: true);
|
||||
double anchorTenkan = ind.Tenkan.Value;
|
||||
|
||||
// Correction with a dramatically different price — Tenkan must change
|
||||
ind.Update(new TBar(anchorTime, anchorClose * 10, (anchorClose + 2) * 10, (anchorClose - 2) * 10, anchorClose * 10, 1000), isNew: false);
|
||||
Assert.NotEqual(anchorTenkan, ind.Tenkan.Value);
|
||||
|
||||
// Correction back to original price — must exactly restore original Tenkan
|
||||
ind.Update(new TBar(anchorTime, anchorClose, anchorClose + 2, anchorClose - 2, anchorClose, 1000), isNew: false);
|
||||
Assert.Equal(anchorTenkan, ind.Tenkan.Value, 1e-9);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("Ichimoku Cloud", "ICHIMOKU", overlay=true)
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// MINUS_DI: Minus Directional Indicator (Wilder, 1978)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Measures downward directional movement as a percentage of true range.
|
||||
/// Extracted from the DX calculation: -DI = Smoothed(-DM) / Smoothed(TR) × 100.
|
||||
/// Range: 0 to 100. Higher values indicate stronger downward movement.
|
||||
/// </remarks>
|
||||
[SkipLocalsInit]
|
||||
public sealed class MinusDi : ITValuePublisher
|
||||
{
|
||||
private readonly Dx _dx;
|
||||
|
||||
/// <summary>Display name for the indicator.</summary>
|
||||
public string Name { get; }
|
||||
|
||||
public event TValuePublishedHandler? Pub;
|
||||
|
||||
/// <summary>Current -DI value.</summary>
|
||||
public TValue Last { get; private set; }
|
||||
|
||||
/// <summary>True when the indicator has warmed up.</summary>
|
||||
public bool IsHot => _dx.IsHot;
|
||||
|
||||
/// <summary>Bars required for warmup.</summary>
|
||||
public int WarmupPeriod => _dx.WarmupPeriod;
|
||||
|
||||
/// <summary>The period parameter.</summary>
|
||||
public int Period => _dx.Period;
|
||||
|
||||
/// <summary>
|
||||
/// Creates MinusDi with specified period.
|
||||
/// </summary>
|
||||
/// <param name="period">Wilder smoothing period (must be > 0)</param>
|
||||
public MinusDi(int period = 14)
|
||||
{
|
||||
if (period <= 0)
|
||||
{
|
||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
}
|
||||
_dx = new Dx(period);
|
||||
Name = $"MinusDi({period})";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates MinusDi and immediately processes the bar series.
|
||||
/// </summary>
|
||||
public MinusDi(TBarSeries source, int period = 14) : this(period)
|
||||
{
|
||||
var result = Batch(source, period);
|
||||
Last = result[^1];
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public TValue Update(TBar input, bool isNew = true)
|
||||
{
|
||||
_dx.Update(input, isNew);
|
||||
Last = _dx.DiMinus;
|
||||
if (isNew)
|
||||
{
|
||||
Pub?.Invoke(this, new TValueEventArgs { Value = Last, IsNew = isNew });
|
||||
}
|
||||
return Last;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public TValue Update(TValue input, bool isNew = true)
|
||||
{
|
||||
// DI requires OHLC data — scalar update not meaningful
|
||||
return Last;
|
||||
}
|
||||
|
||||
public TSeries Update(TBarSeries source)
|
||||
{
|
||||
var result = new TSeries(source.Count);
|
||||
foreach (var bar in source)
|
||||
{
|
||||
result.Add(Update(bar));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the indicator state using the provided bar series history.
|
||||
/// </summary>
|
||||
public void Prime(TBarSeries source)
|
||||
{
|
||||
foreach (var bar in source)
|
||||
{
|
||||
Update(bar);
|
||||
}
|
||||
}
|
||||
|
||||
public void Prime(ReadOnlySpan<double> source, TimeSpan? step = null)
|
||||
{
|
||||
// Not applicable — DI requires OHLC bar data, not scalar values
|
||||
}
|
||||
|
||||
public static TSeries Batch(TBarSeries source, int period = 14)
|
||||
{
|
||||
var indicator = new MinusDi(period);
|
||||
return indicator.Update(source);
|
||||
}
|
||||
|
||||
public static (TSeries Results, MinusDi Indicator) Calculate(TBarSeries source, int period = 14)
|
||||
{
|
||||
var indicator = new MinusDi(period);
|
||||
return (indicator.Update(source), indicator);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Reset()
|
||||
{
|
||||
_dx.Reset();
|
||||
Last = default;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
# MINUS_DI: Minus Directional Indicator
|
||||
|
||||
### TL;DR
|
||||
Measures downward directional movement strength as a percentage (0-100).
|
||||
|
||||
## Introduction
|
||||
The Minus Directional Indicator (-DI) measures the strength of downward price movement relative to the true range. It is one of the components of the Directional Movement System developed by J. Welles Wilder Jr.
|
||||
|
||||
When -DI is rising, downward price pressure is increasing. When -DI crosses above +DI, it signals a potential bearish trend. The -DI line is commonly plotted alongside +DI to visualize directional balance.
|
||||
|
||||
## Calculation
|
||||
-DI = Smoothed(-DM) / Smoothed(TR) × 100
|
||||
|
||||
Where:
|
||||
- -DM (Minus Directional Movement) = max(PrevLow - Low, 0) when PrevLow - Low > High - PrevHigh, else 0
|
||||
- TR (True Range) = max(High - Low, |High - PrevClose|, |Low - PrevClose|)
|
||||
- Smoothing uses Wilder's method: Smooth = Smooth - Smooth/N + Input
|
||||
|
||||
## Parameters
|
||||
| Parameter | Default | Range | Description |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| Period | 14 | 2-∞ | Wilder smoothing period |
|
||||
|
||||
## Interpretation
|
||||
- **Rising -DI:** Strengthening downward movement
|
||||
- **-DI > +DI:** Bears dominate; potential downtrend
|
||||
- **-DI crossover above +DI:** Bearish signal
|
||||
- **High -DI (>40):** Strong downward momentum
|
||||
|
||||
## References
|
||||
- Wilder, J. Welles Jr. "New Concepts in Technical Trading Systems" (1978)
|
||||
@@ -0,0 +1,121 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// MINUS_DM: Minus Directional Movement (Wilder, 1978)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Wilder-smoothed downward directional movement in price units.
|
||||
/// Extracted from the DX calculation: Smoothed(-DM) using Wilder's method.
|
||||
/// Values ≥ 0 in price units. Higher values indicate stronger downward movement magnitude.
|
||||
/// </remarks>
|
||||
[SkipLocalsInit]
|
||||
public sealed class MinusDm : ITValuePublisher
|
||||
{
|
||||
private readonly Dx _dx;
|
||||
|
||||
/// <summary>Display name for the indicator.</summary>
|
||||
public string Name { get; }
|
||||
|
||||
public event TValuePublishedHandler? Pub;
|
||||
|
||||
/// <summary>Current smoothed -DM value.</summary>
|
||||
public TValue Last { get; private set; }
|
||||
|
||||
/// <summary>True when the indicator has warmed up.</summary>
|
||||
public bool IsHot => _dx.IsHot;
|
||||
|
||||
/// <summary>Bars required for warmup.</summary>
|
||||
public int WarmupPeriod => _dx.WarmupPeriod;
|
||||
|
||||
/// <summary>The period parameter.</summary>
|
||||
public int Period => _dx.Period;
|
||||
|
||||
/// <summary>
|
||||
/// Creates MinusDm with specified period.
|
||||
/// </summary>
|
||||
/// <param name="period">Wilder smoothing period (must be > 0)</param>
|
||||
public MinusDm(int period = 14)
|
||||
{
|
||||
if (period <= 0)
|
||||
{
|
||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
}
|
||||
_dx = new Dx(period);
|
||||
Name = $"MinusDm({period})";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates MinusDm and immediately processes the bar series.
|
||||
/// </summary>
|
||||
public MinusDm(TBarSeries source, int period = 14) : this(period)
|
||||
{
|
||||
var result = Batch(source, period);
|
||||
Last = result[^1];
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public TValue Update(TBar input, bool isNew = true)
|
||||
{
|
||||
_dx.Update(input, isNew);
|
||||
Last = _dx.DmMinus;
|
||||
if (isNew)
|
||||
{
|
||||
Pub?.Invoke(this, new TValueEventArgs { Value = Last, IsNew = isNew });
|
||||
}
|
||||
return Last;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public TValue Update(TValue input, bool isNew = true)
|
||||
{
|
||||
// DM requires OHLC data — scalar update not meaningful
|
||||
return Last;
|
||||
}
|
||||
|
||||
public TSeries Update(TBarSeries source)
|
||||
{
|
||||
var result = new TSeries(source.Count);
|
||||
foreach (var bar in source)
|
||||
{
|
||||
result.Add(Update(bar));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the indicator state using the provided bar series history.
|
||||
/// </summary>
|
||||
public void Prime(TBarSeries source)
|
||||
{
|
||||
foreach (var bar in source)
|
||||
{
|
||||
Update(bar);
|
||||
}
|
||||
}
|
||||
|
||||
public void Prime(ReadOnlySpan<double> source, TimeSpan? step = null)
|
||||
{
|
||||
// Not applicable — DM requires OHLC bar data, not scalar values
|
||||
}
|
||||
|
||||
public static TSeries Batch(TBarSeries source, int period = 14)
|
||||
{
|
||||
var indicator = new MinusDm(period);
|
||||
return indicator.Update(source);
|
||||
}
|
||||
|
||||
public static (TSeries Results, MinusDm Indicator) Calculate(TBarSeries source, int period = 14)
|
||||
{
|
||||
var indicator = new MinusDm(period);
|
||||
return (indicator.Update(source), indicator);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Reset()
|
||||
{
|
||||
_dx.Reset();
|
||||
Last = default;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
# MINUS_DM: Minus Directional Movement
|
||||
|
||||
### TL;DR
|
||||
Wilder-smoothed downward directional movement in price units (≥0).
|
||||
|
||||
## Introduction
|
||||
Minus Directional Movement (-DM) measures the magnitude of downward price movement, smoothed using Wilder's method. Unlike -DI which normalizes by true range to produce a percentage, -DM outputs raw smoothed values in price units.
|
||||
|
||||
-DM captures when the previous bar's low exceeds the current bar's low by more than the current bar's high exceeds the previous bar's high. It is the raw building block of the Directional Movement System.
|
||||
|
||||
## Calculation
|
||||
-DM = max(PrevLow - Low, 0) when PrevLow - Low > High - PrevHigh, else 0
|
||||
|
||||
Smoothed using Wilder's method: Smooth = Smooth - Smooth/N + Input
|
||||
|
||||
## Parameters
|
||||
| Parameter | Default | Range | Description |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| Period | 14 | 2-∞ | Wilder smoothing period |
|
||||
|
||||
## Interpretation
|
||||
- **Rising -DM:** Increasing downward price extension
|
||||
- **-DM > +DM:** Downward movement exceeds upward movement
|
||||
- **Zero -DM:** No downward directional movement on the bar
|
||||
- Values are in price units and scale with the instrument
|
||||
|
||||
## References
|
||||
- Wilder, J. Welles Jr. "New Concepts in Technical Trading Systems" (1978)
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("PFE: Polarized Fractal Efficiency", "PFE", overlay=false)
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// PLUS_DI: Plus Directional Indicator (Wilder, 1978)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Measures upward directional movement as a percentage of true range.
|
||||
/// Extracted from the DX calculation: +DI = Smoothed(+DM) / Smoothed(TR) × 100.
|
||||
/// Range: 0 to 100. Higher values indicate stronger upward movement.
|
||||
/// </remarks>
|
||||
[SkipLocalsInit]
|
||||
public sealed class PlusDi : ITValuePublisher
|
||||
{
|
||||
private readonly Dx _dx;
|
||||
|
||||
/// <summary>Display name for the indicator.</summary>
|
||||
public string Name { get; }
|
||||
|
||||
public event TValuePublishedHandler? Pub;
|
||||
|
||||
/// <summary>Current +DI value.</summary>
|
||||
public TValue Last { get; private set; }
|
||||
|
||||
/// <summary>True when the indicator has warmed up.</summary>
|
||||
public bool IsHot => _dx.IsHot;
|
||||
|
||||
/// <summary>Bars required for warmup.</summary>
|
||||
public int WarmupPeriod => _dx.WarmupPeriod;
|
||||
|
||||
/// <summary>The period parameter.</summary>
|
||||
public int Period => _dx.Period;
|
||||
|
||||
/// <summary>
|
||||
/// Creates PlusDi with specified period.
|
||||
/// </summary>
|
||||
/// <param name="period">Wilder smoothing period (must be > 0)</param>
|
||||
public PlusDi(int period = 14)
|
||||
{
|
||||
if (period <= 0)
|
||||
{
|
||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
}
|
||||
_dx = new Dx(period);
|
||||
Name = $"PlusDi({period})";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates PlusDi and immediately processes the bar series.
|
||||
/// </summary>
|
||||
public PlusDi(TBarSeries source, int period = 14) : this(period)
|
||||
{
|
||||
var result = Batch(source, period);
|
||||
Last = result[^1];
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public TValue Update(TBar input, bool isNew = true)
|
||||
{
|
||||
_dx.Update(input, isNew);
|
||||
Last = _dx.DiPlus;
|
||||
if (isNew)
|
||||
{
|
||||
Pub?.Invoke(this, new TValueEventArgs { Value = Last, IsNew = isNew });
|
||||
}
|
||||
return Last;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public TValue Update(TValue input, bool isNew = true)
|
||||
{
|
||||
// DI requires OHLC data — scalar update not meaningful
|
||||
return Last;
|
||||
}
|
||||
|
||||
public TSeries Update(TBarSeries source)
|
||||
{
|
||||
var result = new TSeries(source.Count);
|
||||
foreach (var bar in source)
|
||||
{
|
||||
result.Add(Update(bar));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the indicator state using the provided bar series history.
|
||||
/// </summary>
|
||||
public void Prime(TBarSeries source)
|
||||
{
|
||||
foreach (var bar in source)
|
||||
{
|
||||
Update(bar);
|
||||
}
|
||||
}
|
||||
|
||||
public void Prime(ReadOnlySpan<double> source, TimeSpan? step = null)
|
||||
{
|
||||
// Not applicable — DI requires OHLC bar data, not scalar values
|
||||
}
|
||||
|
||||
public static TSeries Batch(TBarSeries source, int period = 14)
|
||||
{
|
||||
var indicator = new PlusDi(period);
|
||||
return indicator.Update(source);
|
||||
}
|
||||
|
||||
public static (TSeries Results, PlusDi Indicator) Calculate(TBarSeries source, int period = 14)
|
||||
{
|
||||
var indicator = new PlusDi(period);
|
||||
return (indicator.Update(source), indicator);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Reset()
|
||||
{
|
||||
_dx.Reset();
|
||||
Last = default;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
# PLUS_DI: Plus Directional Indicator
|
||||
|
||||
### TL;DR
|
||||
Measures upward directional movement strength as a percentage (0-100).
|
||||
|
||||
## Introduction
|
||||
The Plus Directional Indicator (+DI) measures the strength of upward price movement relative to the true range. It is one of the components of the Directional Movement System developed by J. Welles Wilder Jr.
|
||||
|
||||
When +DI is rising, upward price pressure is increasing. When +DI crosses above -DI, it signals a potential bullish trend. The +DI line is commonly plotted alongside -DI to visualize directional balance.
|
||||
|
||||
## Calculation
|
||||
+DI = Smoothed(+DM) / Smoothed(TR) × 100
|
||||
|
||||
Where:
|
||||
- +DM (Plus Directional Movement) = max(High - PrevHigh, 0) when High - PrevHigh > PrevLow - Low, else 0
|
||||
- TR (True Range) = max(High - Low, |High - PrevClose|, |Low - PrevClose|)
|
||||
- Smoothing uses Wilder's method: Smooth = Smooth - Smooth/N + Input
|
||||
|
||||
## Parameters
|
||||
| Parameter | Default | Range | Description |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| Period | 14 | 2-∞ | Wilder smoothing period |
|
||||
|
||||
## Interpretation
|
||||
- **Rising +DI:** Strengthening upward movement
|
||||
- **+DI > -DI:** Bulls dominate; potential uptrend
|
||||
- **+DI crossover above -DI:** Bullish signal
|
||||
- **High +DI (>40):** Strong upward momentum
|
||||
|
||||
## References
|
||||
- Wilder, J. Welles Jr. "New Concepts in Technical Trading Systems" (1978)
|
||||
@@ -0,0 +1,121 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// PLUS_DM: Plus Directional Movement (Wilder, 1978)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Wilder-smoothed upward directional movement in price units.
|
||||
/// Extracted from the DX calculation: Smoothed(+DM) using Wilder's method.
|
||||
/// Values ≥ 0 in price units. Higher values indicate stronger upward movement magnitude.
|
||||
/// </remarks>
|
||||
[SkipLocalsInit]
|
||||
public sealed class PlusDm : ITValuePublisher
|
||||
{
|
||||
private readonly Dx _dx;
|
||||
|
||||
/// <summary>Display name for the indicator.</summary>
|
||||
public string Name { get; }
|
||||
|
||||
public event TValuePublishedHandler? Pub;
|
||||
|
||||
/// <summary>Current smoothed +DM value.</summary>
|
||||
public TValue Last { get; private set; }
|
||||
|
||||
/// <summary>True when the indicator has warmed up.</summary>
|
||||
public bool IsHot => _dx.IsHot;
|
||||
|
||||
/// <summary>Bars required for warmup.</summary>
|
||||
public int WarmupPeriod => _dx.WarmupPeriod;
|
||||
|
||||
/// <summary>The period parameter.</summary>
|
||||
public int Period => _dx.Period;
|
||||
|
||||
/// <summary>
|
||||
/// Creates PlusDm with specified period.
|
||||
/// </summary>
|
||||
/// <param name="period">Wilder smoothing period (must be > 0)</param>
|
||||
public PlusDm(int period = 14)
|
||||
{
|
||||
if (period <= 0)
|
||||
{
|
||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
}
|
||||
_dx = new Dx(period);
|
||||
Name = $"PlusDm({period})";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates PlusDm and immediately processes the bar series.
|
||||
/// </summary>
|
||||
public PlusDm(TBarSeries source, int period = 14) : this(period)
|
||||
{
|
||||
var result = Batch(source, period);
|
||||
Last = result[^1];
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public TValue Update(TBar input, bool isNew = true)
|
||||
{
|
||||
_dx.Update(input, isNew);
|
||||
Last = _dx.DmPlus;
|
||||
if (isNew)
|
||||
{
|
||||
Pub?.Invoke(this, new TValueEventArgs { Value = Last, IsNew = isNew });
|
||||
}
|
||||
return Last;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public TValue Update(TValue input, bool isNew = true)
|
||||
{
|
||||
// DM requires OHLC data — scalar update not meaningful
|
||||
return Last;
|
||||
}
|
||||
|
||||
public TSeries Update(TBarSeries source)
|
||||
{
|
||||
var result = new TSeries(source.Count);
|
||||
foreach (var bar in source)
|
||||
{
|
||||
result.Add(Update(bar));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the indicator state using the provided bar series history.
|
||||
/// </summary>
|
||||
public void Prime(TBarSeries source)
|
||||
{
|
||||
foreach (var bar in source)
|
||||
{
|
||||
Update(bar);
|
||||
}
|
||||
}
|
||||
|
||||
public void Prime(ReadOnlySpan<double> source, TimeSpan? step = null)
|
||||
{
|
||||
// Not applicable — DM requires OHLC bar data, not scalar values
|
||||
}
|
||||
|
||||
public static TSeries Batch(TBarSeries source, int period = 14)
|
||||
{
|
||||
var indicator = new PlusDm(period);
|
||||
return indicator.Update(source);
|
||||
}
|
||||
|
||||
public static (TSeries Results, PlusDm Indicator) Calculate(TBarSeries source, int period = 14)
|
||||
{
|
||||
var indicator = new PlusDm(period);
|
||||
return (indicator.Update(source), indicator);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Reset()
|
||||
{
|
||||
_dx.Reset();
|
||||
Last = default;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
# PLUS_DM: Plus Directional Movement
|
||||
|
||||
### TL;DR
|
||||
Wilder-smoothed upward directional movement in price units (≥0).
|
||||
|
||||
## Introduction
|
||||
Plus Directional Movement (+DM) measures the magnitude of upward price movement, smoothed using Wilder's method. Unlike +DI which normalizes by true range to produce a percentage, +DM outputs raw smoothed values in price units.
|
||||
|
||||
+DM captures when the current bar's high exceeds the previous bar's high by more than the previous bar's low exceeds the current bar's low. It is the raw building block of the Directional Movement System.
|
||||
|
||||
## Calculation
|
||||
+DM = max(High - PrevHigh, 0) when High - PrevHigh > PrevLow - Low, else 0
|
||||
|
||||
Smoothed using Wilder's method: Smooth = Smooth - Smooth/N + Input
|
||||
|
||||
## Parameters
|
||||
| Parameter | Default | Range | Description |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| Period | 14 | 2-∞ | Wilder smoothing period |
|
||||
|
||||
## Interpretation
|
||||
- **Rising +DM:** Increasing upward price extension
|
||||
- **+DM > -DM:** Upward movement exceeds downward movement
|
||||
- **Zero +DM:** No upward directional movement on the bar
|
||||
- Values are in price units and scale with the instrument
|
||||
|
||||
## References
|
||||
- Wilder, J. Welles Jr. "New Concepts in Technical Trading Systems" (1978)
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("Qstick Indicator", "QSTICK", overlay=false)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("RAVI: Chande Range Action Verification Index", "RAVI", overlay=false)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("SuperTrend", "SUPER", overlay=true)
|
||||
|
||||
@@ -325,5 +325,33 @@ public class TtmSqueezeValidationTests
|
||||
Assert.InRange(squeeze.ColorCode, 0, 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TtmSqueeze_Correction_Recomputes()
|
||||
{
|
||||
var ind = new TtmSqueeze();
|
||||
var t0 = new DateTime(946_684_800_000_000_0L, DateTimeKind.Utc);
|
||||
|
||||
// Build state well past warmup
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
double p = 100.0 + 10.0 * Math.Sin(2.0 * Math.PI * i / 20.0);
|
||||
ind.Update(new TBar(t0.AddMinutes(i), p, p + 2, p - 2, p, 1000), isNew: true);
|
||||
}
|
||||
|
||||
// Anchor bar
|
||||
var anchorTime = t0.AddMinutes(100);
|
||||
const double anchorClose = 105.5;
|
||||
ind.Update(new TBar(anchorTime, anchorClose, anchorClose + 2, anchorClose - 2, anchorClose, 1000), isNew: true);
|
||||
double anchorMomentum = ind.Momentum.Value;
|
||||
|
||||
// Correction with a dramatically different price — Momentum must change
|
||||
ind.Update(new TBar(anchorTime, anchorClose * 10, (anchorClose + 2) * 10, (anchorClose - 2) * 10, anchorClose * 10, 1000), isNew: false);
|
||||
Assert.NotEqual(anchorMomentum, ind.Momentum.Value);
|
||||
|
||||
// Correction back to original price — must exactly restore original Momentum
|
||||
ind.Update(new TBar(anchorTime, anchorClose, anchorClose + 2, anchorClose - 2, anchorClose, 1000), isNew: false);
|
||||
Assert.Equal(anchorMomentum, ind.Momentum.Value, 1e-9);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -322,7 +322,7 @@ public sealed class TtmSqueeze : ITValuePublisher
|
||||
_priceSum -= oldest;
|
||||
_priceSumSquares -= oldest * oldest;
|
||||
}
|
||||
_priceBuffer.Add(close, isNew);
|
||||
_priceBuffer.Add(close);
|
||||
_priceSum += close;
|
||||
_priceSumSquares += close * close;
|
||||
|
||||
@@ -365,11 +365,11 @@ public sealed class TtmSqueeze : ITValuePublisher
|
||||
_prevSqueezeOn = squeezeOn;
|
||||
|
||||
// === Donchian Midline ===
|
||||
_highBuffer.Add(high, isNew);
|
||||
_lowBuffer.Add(low, isNew);
|
||||
_highBuffer.Add(high);
|
||||
_lowBuffer.Add(low);
|
||||
|
||||
double donchianHigh = GetMax(_highBuffer);
|
||||
double donchianLow = GetMin(_lowBuffer);
|
||||
double donchianHigh = _highBuffer.Max();
|
||||
double donchianLow = _lowBuffer.Min();
|
||||
double donchianMid = (donchianHigh + donchianLow) / 2;
|
||||
|
||||
// === Momentum (Linear Regression) ===
|
||||
@@ -383,7 +383,7 @@ public sealed class TtmSqueeze : ITValuePublisher
|
||||
_momentumSumXY = _momentumSumXY + prevSumY - _momPeriod * oldest;
|
||||
_momentumSumY -= oldest;
|
||||
}
|
||||
_momentumBuffer.Add(deviation, isNew);
|
||||
_momentumBuffer.Add(deviation);
|
||||
_momentumSumY += deviation;
|
||||
|
||||
// Recalculate sumXY during warmup (non-O(1), but short duration)
|
||||
@@ -535,6 +535,10 @@ public sealed class TtmSqueeze : ITValuePublisher
|
||||
_saved_prevMomentum = _prevMomentum;
|
||||
_saved_prevSqueezeOn = _prevSqueezeOn;
|
||||
_saved_barCount = _barCount;
|
||||
_priceBuffer.Snapshot();
|
||||
_highBuffer.Snapshot();
|
||||
_lowBuffer.Snapshot();
|
||||
_momentumBuffer.Snapshot();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -552,43 +556,10 @@ public sealed class TtmSqueeze : ITValuePublisher
|
||||
_prevMomentum = _saved_prevMomentum;
|
||||
_prevSqueezeOn = _saved_prevSqueezeOn;
|
||||
_barCount = _saved_barCount;
|
||||
_priceBuffer.Restore();
|
||||
_highBuffer.Restore();
|
||||
_lowBuffer.Restore();
|
||||
_momentumBuffer.Restore();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static double GetMax(RingBuffer buffer)
|
||||
{
|
||||
if (buffer.Count == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
var span = buffer.GetSpan();
|
||||
double max = span[0];
|
||||
for (int i = 1; i < span.Length; i++)
|
||||
{
|
||||
if (span[i] > max)
|
||||
{
|
||||
max = span[i];
|
||||
}
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static double GetMin(RingBuffer buffer)
|
||||
{
|
||||
if (buffer.Count == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
var span = buffer.GetSpan();
|
||||
double min = span[0];
|
||||
for (int i = 1; i < span.Length; i++)
|
||||
{
|
||||
if (span[i] < min)
|
||||
{
|
||||
min = span[i];
|
||||
}
|
||||
}
|
||||
return min;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("TTM Trend", "TTM_TREND", overlay=true)
|
||||
|
||||
+1
-16
@@ -412,22 +412,7 @@ public sealed class Vhf : AbstractBase
|
||||
// Calculate VHF
|
||||
if (closeFilled >= closeBufSize && diffFilled >= period)
|
||||
{
|
||||
// Scan for max/min over close buffer
|
||||
double hi = double.MinValue;
|
||||
double lo = double.MaxValue;
|
||||
for (int k = 0; k < closeBufSize; k++)
|
||||
{
|
||||
double cv = closeBuf[k];
|
||||
if (cv > hi)
|
||||
{
|
||||
hi = cv;
|
||||
}
|
||||
if (cv < lo)
|
||||
{
|
||||
lo = cv;
|
||||
}
|
||||
}
|
||||
|
||||
var (lo, hi) = ((ReadOnlySpan<double>)closeBuf).MinMaxSIMD();
|
||||
double numerator = hi - lo;
|
||||
|
||||
if (diffSum > 1e-10)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("VHF: Vertical Horizontal Filter", "VHF", overlay=false)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("Vortex Indicator", "VORTEX", overlay=false)
|
||||
|
||||
Reference in New Issue
Block a user