Adx, Adxr, Apo, Dmi

This commit is contained in:
Miha
2024-10-27 21:59:46 -07:00
parent 6c67a0cf31
commit 6b79f8158c
18 changed files with 859 additions and 127 deletions
+10 -4
View File
@@ -7,11 +7,17 @@
}
],
"sarif-viewer.connectToGithubCodeScanning": "on",
"dotnet.dotnetPath": "/opt/homebrew/bin/",
"omnisharp.useModernNet": true,
"omnisharp.sdkPath": "/opt/homebrew/bin/dotnet",
"sonarlint.connectedMode.project": {
"connectionId": "mihakralj",
"projectKey": "mihakralj_QuanTAlib"
}
}
},
"dotnet.backgroundAnalysis.analyzerDiagnosticsScope": "fullSolution",
"dotnet.completion.showCompletionItemsFromUnimportedNamespaces": true,
"dotnetAcquisitionExtension.enableTelemetry": false,
"dotnet-test-explorer.testProjectPath": "Tests",
"dotnet-test-explorer.autoWatch": true,
"dotnet-test-explorer.showCodeLens": true,
"dotnet-test-explorer.testArguments": "/p:CollectCoverage=true /p:CoverletOutputFormat=cobertura /p:CoverletOutput=./TestResults/coverage.cobertura.xml"
}
+5 -4
View File
@@ -2,15 +2,16 @@
<PropertyGroup>
<RootNamespace>QuanTAlib.Tests</RootNamespace>
<AssemblyName>QuanTAlib.Tests</AssemblyName>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.msbuild" Version="6.0.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
<PackageReference Include="coverlet.msbuild" Version="6.0.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="coverlet.collector" Version="6.0.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.0-pre.35">
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.0-pre.42">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
+25
View File
@@ -51,12 +51,37 @@ public class EventingTests
("Tema", new Tema(p), new Tema(input, p)),
("Kama", new Kama(2, 30, 6), new Kama(input, 2, 30, 6)),
("Zlema", new Zlema(p), new Zlema(input, p)),
// Added missing averages
("Sinema", new Sinema(p), new Sinema(input, p)),
("Smma", new Smma(p), new Smma(input, p)),
("T3", new T3(p), new T3(input, p)),
("Trima", new Trima(p), new Trima(input, p)),
("Vidya", new Vidya(p), new Vidya(input, p)),
// momentum indicators
("Apo", new Apo(12, 26), new Apo(input, 12, 26)),
// oscillators
("Rsi", new Rsi(p), new Rsi(input, p)),
("Rsx", new Rsx(p), new Rsx(input, p)),
("Cmo", new Cmo(p), new Cmo(input, p)),
// statistics
("Curvature", new Curvature(p), new Curvature(input, p)),
("Entropy", new Entropy(p), new Entropy(input, p)),
("Kurtosis", new Kurtosis(p), new Kurtosis(input, p)),
("Max", new Max(p), new Max(input, p)),
("Median", new Median(p), new Median(input, p)),
("Min", new Min(p), new Min(input, p)),
("Mode", new Mode(p), new Mode(input, p)),
("Percentile", new Percentile(p, 0.5), new Percentile(input, p, 0.5)),
("Skew", new Skew(p), new Skew(input, p)),
("Slope", new Slope(p), new Slope(input, p)),
("Stddev", new Stddev(p), new Stddev(input, p)),
("Variance", new Variance(p), new Variance(input, p)),
("Zscore", new Zscore(p), new Zscore(input, p)),
// volatility
("Hv", new Hv(p), new Hv(input, p)),
("Jvolty", new Jvolty(p), new Jvolty(input, p)),
("Rv", new Rv(p), new Rv(input, p)),
("Rvi", new Rvi(p), new Rvi(input, p)),
// error classes
("Mae", new Mae(p), new Mae(input, p)),
("Mapd", new Mapd(p), new Mapd(input, p)),
+91
View File
@@ -0,0 +1,91 @@
using Xunit;
using System.Security.Cryptography;
namespace QuanTAlib.Tests;
public class MomentumUpdateTests
{
private readonly RandomNumberGenerator rng = RandomNumberGenerator.Create();
private const int RandomUpdates = 100;
private const double ReferenceValue = 100.0;
private const int precision = 8;
private double GetRandomDouble()
{
byte[] bytes = new byte[8];
rng.GetBytes(bytes);
return (double)BitConverter.ToUInt64(bytes, 0) / ulong.MaxValue * 200 - 100; // Range: -100 to 100
}
private TBar GetRandomBar(bool IsNew)
{
double open = GetRandomDouble();
double high = open + Math.Abs(GetRandomDouble());
double low = open - Math.Abs(GetRandomDouble());
double close = low + (high - low) * GetRandomDouble();
return new TBar(DateTime.Now, open, high, low, close, 1000, IsNew);
}
[Fact]
public void Adx_Update()
{
var indicator = new Adx(period: 14);
TBar r = GetRandomBar(true);
double initialValue = indicator.Calc(r);
for (int i = 0; i < RandomUpdates; i++)
{
indicator.Calc(GetRandomBar(IsNew: false));
}
double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
Assert.Equal(initialValue, finalValue, precision);
}
[Fact]
public void Adxr_Update()
{
var indicator = new Adxr(period: 14);
TBar r = GetRandomBar(true);
double initialValue = indicator.Calc(r);
for (int i = 0; i < RandomUpdates; i++)
{
indicator.Calc(GetRandomBar(IsNew: false));
}
double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
Assert.Equal(initialValue, finalValue, precision);
}
[Fact]
public void Apo_Update()
{
var indicator = new Apo(fastPeriod: 12, slowPeriod: 26);
double initialValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: true));
for (int i = 0; i < RandomUpdates; i++)
{
indicator.Calc(new TValue(DateTime.Now, GetRandomDouble(), IsNew: false));
}
double finalValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: false));
Assert.Equal(initialValue, finalValue, precision);
}
[Fact]
public void Dmi_Update()
{
var indicator = new Dmi(period: 14);
TBar r = GetRandomBar(true);
double initialValue = indicator.Calc(r);
for (int i = 0; i < RandomUpdates; i++)
{
indicator.Calc(GetRandomBar(IsNew: false));
}
double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
Assert.Equal(initialValue, finalValue, precision);
}
}
+64
View File
@@ -0,0 +1,64 @@
using Xunit;
using System.Security.Cryptography;
namespace QuanTAlib.Tests;
public class OscillatorsUpdateTests
{
private readonly RandomNumberGenerator rng = RandomNumberGenerator.Create();
private const int RandomUpdates = 100;
private const double ReferenceValue = 100.0;
private const int precision = 8;
private double GetRandomDouble()
{
byte[] bytes = new byte[8];
rng.GetBytes(bytes);
return (double)BitConverter.ToUInt64(bytes, 0) / ulong.MaxValue * 200 - 100; // Range: -100 to 100
}
[Fact]
public void Rsi_Update()
{
var indicator = new Rsi(period: 14);
double initialValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: true));
for (int i = 0; i < RandomUpdates; i++)
{
indicator.Calc(new TValue(DateTime.Now, GetRandomDouble(), IsNew: false));
}
double finalValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: false));
Assert.Equal(initialValue, finalValue, precision);
}
[Fact]
public void Rsx_Update()
{
var indicator = new Rsx(period: 14);
double initialValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: true));
for (int i = 0; i < RandomUpdates; i++)
{
indicator.Calc(new TValue(DateTime.Now, GetRandomDouble(), IsNew: false));
}
double finalValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: false));
Assert.Equal(initialValue, finalValue, precision);
}
[Fact]
public void Cmo_Update()
{
var indicator = new Cmo(period: 14);
double initialValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: true));
for (int i = 0; i < RandomUpdates; i++)
{
indicator.Calc(new TValue(DateTime.Now, GetRandomDouble(), IsNew: false));
}
double finalValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: false));
Assert.Equal(initialValue, finalValue, precision);
}
}
+24 -9
View File
@@ -46,13 +46,28 @@ public class VolatilityUpdateTests
public void Historical_Update()
{
var indicator = new Hv(period: 14);
double initialValue = indicator.Calc(new TBar(DateTime.Now, ReferenceValue, ReferenceValue, ReferenceValue, ReferenceValue, 1000, IsNew: true));
double initialValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: true));
for (int i = 0; i < RandomUpdates; i++)
{
indicator.Calc(GetRandomBar(false));
indicator.Calc(new TValue(DateTime.Now, GetRandomDouble(), IsNew: false));
}
double finalValue = indicator.Calc(new TBar(DateTime.Now, ReferenceValue, ReferenceValue, ReferenceValue, ReferenceValue, 1000, IsNew: false));
double finalValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: false));
Assert.Equal(initialValue, finalValue, precision);
}
[Fact]
public void Jvolty_Update()
{
var indicator = new Jvolty(period: 14);
double initialValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: true));
for (int i = 0; i < RandomUpdates; i++)
{
indicator.Calc(new TValue(DateTime.Now, GetRandomDouble(), IsNew: false));
}
double finalValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: false));
Assert.Equal(initialValue, finalValue, precision);
}
@@ -61,13 +76,13 @@ public class VolatilityUpdateTests
public void Realized_Update()
{
var indicator = new Rv(period: 14);
double initialValue = indicator.Calc(new TBar(DateTime.Now, ReferenceValue, ReferenceValue, ReferenceValue, ReferenceValue, 1000, IsNew: true));
double initialValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: true));
for (int i = 0; i < RandomUpdates; i++)
{
indicator.Calc(GetRandomBar(false));
indicator.Calc(new TValue(DateTime.Now, GetRandomDouble(), IsNew: false));
}
double finalValue = indicator.Calc(new TBar(DateTime.Now, ReferenceValue, ReferenceValue, ReferenceValue, ReferenceValue, 1000, IsNew: false));
double finalValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: false));
Assert.Equal(initialValue, finalValue, precision);
}
@@ -76,13 +91,13 @@ public class VolatilityUpdateTests
public void Rvi_Update()
{
var indicator = new Rvi(period: 14);
double initialValue = indicator.Calc(new TBar(DateTime.Now, ReferenceValue, ReferenceValue, ReferenceValue, ReferenceValue, 1000, IsNew: true));
double initialValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: true));
for (int i = 0; i < RandomUpdates; i++)
{
indicator.Calc(GetRandomBar(false));
indicator.Calc(new TValue(DateTime.Now, GetRandomDouble(), IsNew: false));
}
double finalValue = indicator.Calc(new TBar(DateTime.Now, ReferenceValue, ReferenceValue, ReferenceValue, ReferenceValue, 1000, IsNew: false));
double finalValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: false));
Assert.Equal(initialValue, finalValue, precision);
}
-28
View File
@@ -1,28 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
<GenerateProgramFile>false</GenerateProgramFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.5" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\lib\quantalib.csproj" />
</ItemGroup>
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<DebugSymbols>true</DebugSymbols>
<Optimize>true</Optimize>
</PropertyGroup>
</Project>
-73
View File
@@ -1,73 +0,0 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Running;
namespace QuanTAlib;
public class Program
{
public static void Main(string[] args)
{
var config = DefaultConfig.Instance
.WithOption(ConfigOptions.DisableOptimizationsValidator, true);
BenchmarkRunner.Run<EmaBenchmark>(config);
}
}
[MemoryDiagnoser]
[SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 3, iterationCount: 5)]
public class EmaBenchmark
{
private const int Period = 10;
private const int Length = 100_000;
private GbmFeed gbm = null!;
private TSeries inputs = null!;
[GlobalSetup]
public void Setup()
{
gbm = new GbmFeed();
inputs = new();
for (int i = 0; i < Length; i++)
{
TBar item = gbm.Generate(DateTime.Now);
inputs.Add(new TValue(item.Time, item.Close, true, true));
}
}
[Benchmark]
public void Ema_bench()
{
Ema ma1 = new(Period);
for (int i = 0; i < Length; i++)
{
TValue item = gbm.Generate(DateTime.Now).Close;
ma1.Calc(item);
}
}
[Benchmark]
public void Alma_bench()
{
Alma ma1 = new(Period);
for (int i = 0; i < Length; i++)
{
TValue item = gbm.Generate(DateTime.Now).Close;
ma1.Calc(item);
}
}
[Benchmark]
public void Dema_bench()
{
Dema ma1 = new(Period);
for (int i = 0; i < Length; i++)
{
TValue item = gbm.Generate(DateTime.Now).Close;
ma1.Calc(item);
}
}
}
+5 -5
View File
@@ -63,11 +63,11 @@
<script src="https://cdn.jsdelivr.net/npm/docsify@4/lib/docsify.min.js" integrity="sha384-KaHhgnx/OTLoJ4J33SSJsF4x1pk4I7q3s5ZOfIDHJYl6IG7Oyn2vNDsHiWJe46fD" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/docsify-themeable@0/dist/js/docsify-themeable.min.js" integrity="sha384-ibjVZCUwWPrRrNc9BNkbbJvtYmTh8GYDNQgj+2jQVNDudOFgSQWs+Es6JhdoTIvf" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/docsify-sidebar-collapse/dist/docsify-sidebar-collapse.min.js" integrity="sha384-lMHOyuqf3B/T/BgfYxUKprN0bdRf8KhVTE11w76Tvtze86XHVSDYzxqNGDGgIi9I" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/docsify/lib/plugins/search.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/docsify-copy-code/dist/docsify-copy-code.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/docsify-pagination/dist/docsify-pagination.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/docsify/lib/plugins/zoom-image.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/docsify-tabs/dist/docsify-tabs.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/docsify/lib/plugins/search.min.js" integrity="sha384-LthJPBJ4RGco78kBY+EmKz5rmISZ5vrtAu3+l2ALQ2mrZHOe6Wyf6knyKjeC4cL6" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/docsify-copy-code/dist/docsify-copy-code.min.js" integrity="sha384-Vu4LjJ210wltbg1I3zl1f5n8qLSDr9GE7ij2JfiMbV64UCFP2RgFtG8W2Gh6Khwe" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/docsify-pagination/dist/docsify-pagination.min.js" integrity="sha384-uFEp18/1vv2dYlO64J2lOcnbhirAQZPtruqAZZ7PZYoXenm5c+tIs3AqSMjVMRe8" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/docsify/lib/plugins/zoom-image.min.js" integrity="sha384-fYBaBAsdPMjWt1AuA+txMoztHnl+zLgObSvwddIabbfUp+36gf/vNOKx88f37zix" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/docsify-tabs/dist/docsify-tabs.min.js" integrity="sha384-QA3IVKzXq6xcKR8yQJHgDbI1FcJiQ137Cuetl1VWzUxJ6BQ7ew0MMq5fg2HbFK1o" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/docsify-katex@1.4.3/dist/docsify-katex.js" integrity="sha384-M76M/x5vGyeoej1covQQifl7T945mY+qgzNVrM/urHMCYjP8tnMsx5WCdeLZ74UE" crossorigin="anonymous"></script>
</body>
</html>
+179
View File
@@ -0,0 +1,179 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// ADX: Average Directional Movement Index
/// A technical analysis indicator used to measure the strength of a trend,
/// regardless of its direction. ADX combines the Positive and Negative
/// Directional Movement Indicators to determine trend strength.
/// </summary>
/// <remarks>
/// The ADX calculation process:
/// 1. Calculate True Range (TR)
/// 2. Calculate +DM (Positive Directional Movement)
/// 3. Calculate -DM (Negative Directional Movement)
/// 4. Smooth TR, +DM, and -DM using Wilder's smoothing
/// 5. Calculate +DI and -DI
/// 6. Calculate DX (Directional Index)
/// 7. Smooth DX to get ADX
///
/// Key characteristics:
/// - Oscillates between 0 and 100
/// - Values above 25 indicate strong trend
/// - Values below 20 indicate weak or no trend
/// - Can be used with +DI and -DI for trade signals
/// - Does not indicate trend direction, only strength
///
/// Formula:
/// TR = max(high-low, abs(high-prevClose), abs(low-prevClose))
/// +DM = if(high-prevHigh > prevLow-low) then max(high-prevHigh, 0) else 0
/// -DM = if(prevLow-low > high-prevHigh) then max(prevLow-low, 0) else 0
/// +DI = 100 * smoothed(+DM) / smoothed(TR)
/// -DI = 100 * smoothed(-DM) / smoothed(TR)
/// DX = 100 * abs(+DI - -DI) / (+DI + -DI)
/// ADX = smoothed(DX)
///
/// Sources:
/// J. Welles Wilder Jr. - "New Concepts in Technical Trading Systems" (1978)
/// https://www.investopedia.com/terms/a/adx.asp
///
/// Note: Default period of 14 was recommended by Wilder
/// </remarks>
[SkipLocalsInit]
public sealed class Adx : AbstractBarBase
{
private readonly Rma _smoothedTr;
private readonly Rma _smoothedPlusDm;
private readonly Rma _smoothedMinusDm;
private readonly Rma _smoothedDx;
private double _prevHigh, _prevLow, _prevClose;
private double _p_prevHigh, _p_prevLow, _p_prevClose;
private const double ScalingFactor = 100.0;
private const int DefaultPeriod = 14;
/// <param name="period">The number of periods used in the ADX calculation (default 14).</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 1.</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Adx(int period = DefaultPeriod)
{
if (period < 1)
throw new ArgumentOutOfRangeException(nameof(period));
_smoothedTr = new(period, useSma: true);
_smoothedPlusDm = new(period, useSma: true);
_smoothedMinusDm = new(period, useSma: true);
_smoothedDx = new(period, useSma: true);
_index = 0;
WarmupPeriod = period * 2; // Need extra period for DX smoothing
Name = $"ADX({period})";
}
/// <param name="source">The data source object that publishes updates.</param>
/// <param name="period">The number of periods used in the ADX calculation.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Adx(object source, int period) : this(period)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new BarSignal(Sub));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void ManageState(bool isNew)
{
if (isNew)
{
_index++;
_p_prevHigh = _prevHigh;
_p_prevLow = _prevLow;
_p_prevClose = _prevClose;
}
else
{
_prevHigh = _p_prevHigh;
_prevLow = _p_prevLow;
_prevClose = _p_prevClose;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static double CalculateTrueRange(double high, double low, double prevClose)
{
double hl = high - low;
double hpc = Math.Abs(high - prevClose);
double lpc = Math.Abs(low - prevClose);
return Math.Max(hl, Math.Max(hpc, lpc));
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static (double plusDm, double minusDm) CalculateDirectionalMovement(
double high, double low, double prevHigh, double prevLow)
{
double upMove = high - prevHigh;
double downMove = prevLow - low;
double plusDm = 0.0;
double minusDm = 0.0;
if (upMove > downMove && upMove > 0)
plusDm = upMove;
else if (downMove > upMove && downMove > 0)
minusDm = downMove;
return (plusDm, minusDm);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static double CalculateDx(double plusDi, double minusDi)
{
double sum = plusDi + minusDi;
if (sum > 0)
return ScalingFactor * Math.Abs(plusDi - minusDi) / sum;
return 0.0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
protected override double Calculation()
{
ManageState(Input.IsNew);
if (_index == 1)
{
_prevHigh = Input.High;
_prevLow = Input.Low;
_prevClose = Input.Close;
return 0.0;
}
// Calculate True Range and Directional Movement
double tr = CalculateTrueRange(Input.High, Input.Low, _prevClose);
var (plusDm, minusDm) = CalculateDirectionalMovement(
Input.High, Input.Low, _prevHigh, _prevLow);
// Update previous values
_prevHigh = Input.High;
_prevLow = Input.Low;
_prevClose = Input.Close;
// Smooth the indicators using Wilder's method
_smoothedTr.Calc(tr, Input.IsNew);
_smoothedPlusDm.Calc(plusDm, Input.IsNew);
_smoothedMinusDm.Calc(minusDm, Input.IsNew);
// Calculate +DI and -DI
double smoothedTr = _smoothedTr.Value;
if (smoothedTr > 0)
{
double plusDi = ScalingFactor * _smoothedPlusDm.Value / smoothedTr;
double minusDi = ScalingFactor * _smoothedMinusDm.Value / smoothedTr;
// Calculate DX
double dx = CalculateDx(plusDi, minusDi);
// Smooth DX to get ADX
_smoothedDx.Calc(dx, Input.IsNew);
return _smoothedDx.Value;
}
return 0.0;
}
}
+90
View File
@@ -0,0 +1,90 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// ADXR: Average Directional Movement Index Rating
/// A momentum indicator that measures trend strength by comparing the current ADX
/// value with a historical ADX value. ADXR helps identify potential trend
/// reversals earlier than standard ADX.
/// </summary>
/// <remarks>
/// The ADXR calculation process:
/// 1. Calculate current period ADX
/// 2. Calculate historical period ADX (shifted back by period)
/// 3. Average the current and historical ADX values
///
/// Key characteristics:
/// - Oscillates between 0 and 100
/// - Values above 25 indicate strong trend
/// - Values below 20 indicate weak or no trend
/// - Faster at identifying trend changes than ADX
/// - Does not indicate trend direction, only strength
///
/// Formula:
/// ADXR = (Current ADX + Historical ADX) / 2
/// where:
/// Historical ADX = ADX value from 'period' bars ago
///
/// Sources:
/// J. Welles Wilder Jr. - "New Concepts in Technical Trading Systems" (1978)
/// https://www.investopedia.com/terms/a/adxr.asp
///
/// Note: Default period of 14 was recommended by Wilder
/// </remarks>
[SkipLocalsInit]
public sealed class Adxr : AbstractBarBase
{
private readonly Adx _currentAdx;
private readonly CircularBuffer _historicalAdx;
private const int DefaultPeriod = 14;
/// <param name="period">The number of periods used in the ADXR calculation (default 14).</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 1.</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Adxr(int period = DefaultPeriod)
{
if (period < 1)
throw new ArgumentOutOfRangeException(nameof(period));
_currentAdx = new(period);
_historicalAdx = new(period);
_index = 0;
WarmupPeriod = period * 3; // Need extra periods for historical ADX
Name = $"ADXR({period})";
}
/// <param name="source">The data source object that publishes updates.</param>
/// <param name="period">The number of periods used in the ADXR calculation.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Adxr(object source, int period) : this(period)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new BarSignal(Sub));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void ManageState(bool isNew)
{
if (isNew)
_index++;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
protected override double Calculation()
{
ManageState(Input.IsNew);
// Calculate current ADX
double currentAdx = _currentAdx.Value;
_currentAdx.Calc(Input);
// Store ADX value in historical buffer
_historicalAdx.Add(currentAdx, Input.IsNew);
// Calculate ADXR once we have enough historical data
if (_index > _historicalAdx.Capacity)
return (currentAdx + _historicalAdx.Oldest()) / 2.0;
return currentAdx;
}
}
+92
View File
@@ -0,0 +1,92 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// APO: Absolute Price Oscillator
/// A momentum indicator that measures the absolute difference between two moving
/// averages of different periods. APO helps identify trend direction and potential
/// reversals by showing the momentum of price movement.
/// </summary>
/// <remarks>
/// The APO calculation process:
/// 1. Calculate fast period moving average
/// 2. Calculate slow period moving average
/// 3. Calculate absolute difference between the two averages
///
/// Key characteristics:
/// - Oscillates above and below zero
/// - Positive values indicate upward price momentum
/// - Negative values indicate downward price momentum
/// - Zero line crossovers signal potential trend changes
/// - Similar to MACD but uses simple moving averages
///
/// Formula:
/// APO = Fast MA - Slow MA
/// where:
/// Fast MA = Moving average of shorter period
/// Slow MA = Moving average of longer period
///
/// Sources:
/// https://www.investopedia.com/terms/p/ppo.asp
/// https://school.stockcharts.com/doku.php?id=technical_indicators:price_oscillators_ppo
///
/// Note: Default periods are 12 and 26, similar to MACD
/// </remarks>
[SkipLocalsInit]
public sealed class Apo : AbstractBase
{
private readonly Sma _fastMa;
private readonly Sma _slowMa;
private const int DefaultFastPeriod = 12;
private const int DefaultSlowPeriod = 26;
/// <param name="fastPeriod">The number of periods for the fast moving average (default 12).</param>
/// <param name="slowPeriod">The number of periods for the slow moving average (default 26).</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when either period is less than 1.</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Apo(int fastPeriod = DefaultFastPeriod, int slowPeriod = DefaultSlowPeriod)
{
if (fastPeriod < 1)
throw new ArgumentOutOfRangeException(nameof(fastPeriod));
if (slowPeriod < 1)
throw new ArgumentOutOfRangeException(nameof(slowPeriod));
if (fastPeriod >= slowPeriod)
throw new ArgumentException("Fast period must be less than slow period");
_fastMa = new(fastPeriod);
_slowMa = new(slowPeriod);
WarmupPeriod = slowPeriod;
Name = $"APO({fastPeriod},{slowPeriod})";
}
/// <param name="source">The data source object that publishes updates.</param>
/// <param name="fastPeriod">The number of periods for the fast moving average.</param>
/// <param name="slowPeriod">The number of periods for the slow moving average.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Apo(object source, int fastPeriod, int slowPeriod) : this(fastPeriod, slowPeriod)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void ManageState(bool isNew)
{
if (isNew)
_index++;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
protected override double Calculation()
{
ManageState(Input.IsNew);
// Calculate both moving averages
double fastMa = _fastMa.Calc(Input.Value, Input.IsNew);
double slowMa = _slowMa.Calc(Input.Value, Input.IsNew);
// Calculate absolute difference
return fastMa - slowMa;
}
}
+171
View File
@@ -0,0 +1,171 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// DMI: Directional Movement Index
/// A technical indicator that identifies the directional movement of price by
/// comparing successive highs and lows. DMI consists of two lines: +DI and -DI,
/// which help determine trend direction and strength.
/// </summary>
/// <remarks>
/// The DMI calculation process:
/// 1. Calculate True Range (TR)
/// 2. Calculate +DM (Positive Directional Movement)
/// 3. Calculate -DM (Negative Directional Movement)
/// 4. Smooth TR, +DM, and -DM using Wilder's smoothing
/// 5. Calculate +DI and -DI as percentages
///
/// Key characteristics:
/// - Both +DI and -DI oscillate between 0 and 100
/// - When +DI > -DI, uptrend is indicated
/// - When -DI > +DI, downtrend is indicated
/// - Crossovers of +DI and -DI signal potential trend changes
/// - Used in conjunction with ADX for trend trading
///
/// Formula:
/// TR = max(high-low, abs(high-prevClose), abs(low-prevClose))
/// +DM = if(high-prevHigh > prevLow-low) then max(high-prevHigh, 0) else 0
/// -DM = if(prevLow-low > high-prevHigh) then max(prevLow-low, 0) else 0
/// +DI = 100 * smoothed(+DM) / smoothed(TR)
/// -DI = 100 * smoothed(-DM) / smoothed(TR)
///
/// Sources:
/// J. Welles Wilder Jr. - "New Concepts in Technical Trading Systems" (1978)
/// https://www.investopedia.com/terms/d/dmi.asp
///
/// Note: Default period of 14 was recommended by Wilder
/// </remarks>
[SkipLocalsInit]
public sealed class Dmi : AbstractBarBase
{
private readonly Rma _smoothedTr;
private readonly Rma _smoothedPlusDm;
private readonly Rma _smoothedMinusDm;
private double _prevHigh, _prevLow, _prevClose;
private double _p_prevHigh, _p_prevLow, _p_prevClose;
private double _plusDi, _minusDi;
private const double ScalingFactor = 100.0;
private const int DefaultPeriod = 14;
/// <summary>
/// Gets the most recent +DI value
/// </summary>
public double PlusDI => _plusDi;
/// <summary>
/// Gets the most recent -DI value
/// </summary>
public double MinusDI => _minusDi;
/// <param name="period">The number of periods used in the DMI calculation (default 14).</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 1.</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Dmi(int period = DefaultPeriod)
{
if (period < 1)
throw new ArgumentOutOfRangeException(nameof(period));
_smoothedTr = new(period, useSma: true);
_smoothedPlusDm = new(period, useSma: true);
_smoothedMinusDm = new(period, useSma: true);
_index = 0;
WarmupPeriod = period + 1;
Name = $"DMI({period})";
}
/// <param name="source">The data source object that publishes updates.</param>
/// <param name="period">The number of periods used in the DMI calculation.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Dmi(object source, int period) : this(period)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new BarSignal(Sub));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void ManageState(bool isNew)
{
if (isNew)
{
_index++;
_p_prevHigh = _prevHigh;
_p_prevLow = _prevLow;
_p_prevClose = _prevClose;
}
else
{
_prevHigh = _p_prevHigh;
_prevLow = _p_prevLow;
_prevClose = _p_prevClose;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static double CalculateTrueRange(double high, double low, double prevClose)
{
double hl = high - low;
double hpc = Math.Abs(high - prevClose);
double lpc = Math.Abs(low - prevClose);
return Math.Max(hl, Math.Max(hpc, lpc));
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static (double plusDm, double minusDm) CalculateDirectionalMovement(
double high, double low, double prevHigh, double prevLow)
{
double upMove = high - prevHigh;
double downMove = prevLow - low;
double plusDm = 0.0;
double minusDm = 0.0;
if (upMove > downMove && upMove > 0)
plusDm = upMove;
else if (downMove > upMove && downMove > 0)
minusDm = downMove;
return (plusDm, minusDm);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
protected override double Calculation()
{
ManageState(Input.IsNew);
if (_index == 1)
{
_prevHigh = Input.High;
_prevLow = Input.Low;
_prevClose = Input.Close;
return 0.0;
}
// Calculate True Range and Directional Movement
double tr = CalculateTrueRange(Input.High, Input.Low, _prevClose);
var (plusDm, minusDm) = CalculateDirectionalMovement(
Input.High, Input.Low, _prevHigh, _prevLow);
// Update previous values
_prevHigh = Input.High;
_prevLow = Input.Low;
_prevClose = Input.Close;
// Smooth the indicators using Wilder's method
_smoothedTr.Calc(tr, Input.IsNew);
_smoothedPlusDm.Calc(plusDm, Input.IsNew);
_smoothedMinusDm.Calc(minusDm, Input.IsNew);
// Calculate +DI and -DI
double smoothedTr = _smoothedTr.Value;
if (smoothedTr > 0)
{
_plusDi = ScalingFactor * _smoothedPlusDm.Value / smoothedTr;
_minusDi = ScalingFactor * _smoothedMinusDm.Value / smoothedTr;
return _plusDi - _minusDi; // Return the difference as main value
}
_plusDi = 0.0;
_minusDi = 0.0;
return 0.0;
}
}
+3 -3
View File
@@ -1,6 +1,6 @@
ADX - Average Directional Movement Index
ADXR - Average Directional Movement Index
APO - Absolute Price Oscillator
✔️ ADX - Average Directional Movement Index
✔️ ADXR - Average Directional Movement Index Rating
✔️ APO - Absolute Price Oscillator
DMI - Directional Movement Index
DMX - Jurik Directional Movement Index
DPO - Detrended Price Oscillator
+1 -1
View File
@@ -96,7 +96,7 @@ public sealed class Jvolty : AbstractBase
public Jvolty(object source, int period, int phase = DefaultPhase) : this(period, phase)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new BarSignal(Sub));
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
+33
View File
@@ -0,0 +1,33 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AssemblyName>Momentum</AssemblyName>
<AlgoType>Indicator</AlgoType>
<AssemblyVersion>0.0.0.0</AssemblyVersion>
<OutputPath>bin\$(Configuration)\</OutputPath>
<IsLocalBuild Condition="'$(GITHUB_ACTIONS)' == ''">true</IsLocalBuild>
<UpdateAssemblyInfo>true</UpdateAssemblyInfo>
<GenerateGitVersionInformation>true</GenerateGitVersionInformation>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Drawing.Common" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\*.cs" />
<Compile Include="*.cs" />
<Compile Include="..\..\lib\**\*.cs" Exclude="..\..\lib\bin\**;..\..\lib\obj\**" />
<Reference Include="TradingPlatform.BusinessLayer">
<HintPath>..\..\.github\TradingPlatform.BusinessLayer.dll</HintPath>
</Reference>
<None Include="..\..\.github\TradingPlatform.BusinessLayer.xml">
<Link>TradingPlatform.BusinessLayer.xml</Link>
</None>
</ItemGroup>
<Target Name="CopyCustomContent" AfterTargets="AfterBuild"
Condition="'$(IsLocalBuild)' == 'true' AND $([MSBuild]::IsOSPlatform('Windows'))">
<Copy SourceFiles="$(OutputPath)\Momentum.dll" DestinationFolder="$(QuantowerRoot)\Settings\Scripts\Indicators\QuanTAlib\Momentum" />
</Target>
</Project>
+33
View File
@@ -0,0 +1,33 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AssemblyName>Oscillators</AssemblyName>
<AlgoType>Indicator</AlgoType>
<AssemblyVersion>0.0.0.0</AssemblyVersion>
<OutputPath>bin\$(Configuration)\</OutputPath>
<IsLocalBuild Condition="'$(GITHUB_ACTIONS)' == ''">true</IsLocalBuild>
<UpdateAssemblyInfo>true</UpdateAssemblyInfo>
<GenerateGitVersionInformation>true</GenerateGitVersionInformation>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Drawing.Common" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\*.cs" />
<Compile Include="*.cs" />
<Compile Include="..\..\lib\**\*.cs" Exclude="..\..\lib\bin\**;..\..\lib\obj\**" />
<Reference Include="TradingPlatform.BusinessLayer">
<HintPath>..\..\.github\TradingPlatform.BusinessLayer.dll</HintPath>
</Reference>
<None Include="..\..\.github\TradingPlatform.BusinessLayer.xml">
<Link>TradingPlatform.BusinessLayer.xml</Link>
</None>
</ItemGroup>
<Target Name="CopyCustomContent" AfterTargets="AfterBuild"
Condition="'$(IsLocalBuild)' == 'true' AND $([MSBuild]::IsOSPlatform('Windows'))">
<Copy SourceFiles="$(OutputPath)\Oscillators.dll" DestinationFolder="$(QuantowerRoot)\Settings\Scripts\Indicators\QuanTAlib\Oscillators" />
</Target>
</Project>
+33
View File
@@ -0,0 +1,33 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AssemblyName>Volume</AssemblyName>
<AlgoType>Indicator</AlgoType>
<AssemblyVersion>0.0.0.0</AssemblyVersion>
<OutputPath>bin\$(Configuration)\</OutputPath>
<IsLocalBuild Condition="'$(GITHUB_ACTIONS)' == ''">true</IsLocalBuild>
<UpdateAssemblyInfo>true</UpdateAssemblyInfo>
<GenerateGitVersionInformation>true</GenerateGitVersionInformation>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Drawing.Common" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\*.cs" />
<Compile Include="*.cs" />
<Compile Include="..\..\lib\**\*.cs" Exclude="..\..\lib\bin\**;..\..\lib\obj\**" />
<Reference Include="TradingPlatform.BusinessLayer">
<HintPath>..\..\.github\TradingPlatform.BusinessLayer.dll</HintPath>
</Reference>
<None Include="..\..\.github\TradingPlatform.BusinessLayer.xml">
<Link>TradingPlatform.BusinessLayer.xml</Link>
</None>
</ItemGroup>
<Target Name="CopyCustomContent" AfterTargets="AfterBuild"
Condition="'$(IsLocalBuild)' == 'true' AND $([MSBuild]::IsOSPlatform('Windows'))">
<Copy SourceFiles="$(OutputPath)\Volume.dll" DestinationFolder="$(QuantowerRoot)\Settings\Scripts\Indicators\QuanTAlib\Volume" />
</Target>
</Project>