mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-07-27 17:27:43 +00:00
Merge branch 'dev'
This commit is contained in:
@@ -0,0 +1,251 @@
|
||||
using Xunit;
|
||||
|
||||
namespace QuanTAlib.Tests;
|
||||
|
||||
public class CoreTests
|
||||
{
|
||||
#region CircularBuffer Tests
|
||||
|
||||
[Fact]
|
||||
public void CircularBuffer_BasicOperations()
|
||||
{
|
||||
var buffer = new CircularBuffer(5);
|
||||
|
||||
// Test initial state
|
||||
Assert.Equal(5, buffer.Capacity);
|
||||
Assert.Equal(0, buffer.Count);
|
||||
|
||||
// Test adding items
|
||||
buffer.Add(1.0);
|
||||
buffer.Add(2.0);
|
||||
Assert.Equal(2, buffer.Count);
|
||||
Assert.Equal(1.0, buffer[0]);
|
||||
Assert.Equal(2.0, buffer[^1]);
|
||||
|
||||
// Test overflow behavior
|
||||
buffer.Add(3.0);
|
||||
buffer.Add(4.0);
|
||||
buffer.Add(5.0);
|
||||
buffer.Add(6.0); // Should remove oldest item (1.0)
|
||||
Assert.Equal(5, buffer.Count);
|
||||
Assert.Equal(2.0, buffer[0]);
|
||||
Assert.Equal(6.0, buffer[^1]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CircularBuffer_UpdateBehavior()
|
||||
{
|
||||
var buffer = new CircularBuffer(3);
|
||||
|
||||
// Add new values
|
||||
buffer.Add(1.0, isNew: true);
|
||||
buffer.Add(2.0, isNew: true);
|
||||
Assert.Equal(2, buffer.Count);
|
||||
|
||||
// Update last value
|
||||
buffer.Add(2.5, isNew: false);
|
||||
Assert.Equal(2, buffer.Count);
|
||||
Assert.Equal(2.5, buffer[^1]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CircularBuffer_MinMaxSumAverage()
|
||||
{
|
||||
var buffer = new CircularBuffer(5);
|
||||
|
||||
buffer.Add(1.0);
|
||||
buffer.Add(2.0);
|
||||
buffer.Add(3.0);
|
||||
buffer.Add(4.0);
|
||||
buffer.Add(5.0);
|
||||
|
||||
Assert.Equal(1.0, buffer.Min());
|
||||
Assert.Equal(5.0, buffer.Max());
|
||||
Assert.Equal(15.0, buffer.Sum());
|
||||
Assert.Equal(3.0, buffer.Average());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CircularBuffer_Enumeration()
|
||||
{
|
||||
var buffer = new CircularBuffer(3);
|
||||
|
||||
buffer.Add(1.0);
|
||||
buffer.Add(2.0);
|
||||
buffer.Add(3.0);
|
||||
|
||||
var list = buffer.ToList();
|
||||
Assert.Equal(3, list.Count);
|
||||
Assert.Equal(1.0, list[0]);
|
||||
Assert.Equal(3.0, list[2]);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region TBar Tests
|
||||
|
||||
[Fact]
|
||||
public void TBar_Construction()
|
||||
{
|
||||
// Default constructor
|
||||
var bar1 = new TBar();
|
||||
Assert.Equal(0, bar1.Open);
|
||||
Assert.True(bar1.IsNew);
|
||||
|
||||
// Value constructor
|
||||
var bar2 = new TBar(10.0);
|
||||
Assert.Equal(10.0, bar2.Open);
|
||||
Assert.Equal(10.0, bar2.High);
|
||||
Assert.Equal(10.0, bar2.Low);
|
||||
Assert.Equal(10.0, bar2.Close);
|
||||
|
||||
// Full constructor
|
||||
var time = DateTime.Now;
|
||||
var bar3 = new TBar(time, 10.0, 12.0, 9.0, 11.0, 1000.0, false);
|
||||
Assert.Equal(time, bar3.Time);
|
||||
Assert.Equal(10.0, bar3.Open);
|
||||
Assert.Equal(12.0, bar3.High);
|
||||
Assert.Equal(9.0, bar3.Low);
|
||||
Assert.Equal(11.0, bar3.Close);
|
||||
Assert.Equal(1000.0, bar3.Volume);
|
||||
Assert.False(bar3.IsNew);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TBar_DerivedValues()
|
||||
{
|
||||
var bar = new TBar(DateTime.Now, 10.0, 20.0, 5.0, 15.0, 1000.0);
|
||||
|
||||
Assert.Equal(12.5, bar.HL2); // (20 + 5) / 2
|
||||
Assert.Equal(12.5, bar.OC2); // (10 + 15) / 2
|
||||
Assert.Equal(11.67, bar.OHL3, 2); // (10 + 20 + 5) / 3
|
||||
Assert.Equal(13.33, bar.HLC3, 2); // (20 + 5 + 15) / 3
|
||||
Assert.Equal(12.5, bar.OHLC4); // (10 + 20 + 5 + 15) / 4
|
||||
Assert.Equal(13.75, bar.HLCC4); // (20 + 5 + 15 + 15) / 4
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TBarSeries_Operations()
|
||||
{
|
||||
var series = new TBarSeries();
|
||||
var time = DateTime.Now;
|
||||
var bar1 = new TBar(time, 10.0, 12.0, 9.0, 11.0, 1000.0);
|
||||
var bar2 = new TBar(time.AddMinutes(1), 11.0, 13.0, 10.0, 12.0, 1100.0);
|
||||
|
||||
// Test adding bars
|
||||
series.Add(bar1);
|
||||
series.Add(bar2);
|
||||
Assert.Equal(2, series.Count);
|
||||
|
||||
// Test updating last bar
|
||||
var bar2Update = new TBar(bar2.Time, 11.0, 13.5, 9.5, 12.5, 1200.0, false);
|
||||
series.Add(bar2Update);
|
||||
Assert.Equal(2, series.Count);
|
||||
Assert.Equal(12.5, series.Last.Close);
|
||||
|
||||
// Test derived series
|
||||
Assert.Equal(11.0, series.Open.Last.Value);
|
||||
Assert.Equal(13.5, series.High.Last.Value);
|
||||
Assert.Equal(9.5, series.Low.Last.Value);
|
||||
Assert.Equal(12.5, series.Close.Last.Value);
|
||||
Assert.Equal(1200.0, series.Volume.Last.Value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region TValue Tests
|
||||
|
||||
[Fact]
|
||||
public void TValue_Construction()
|
||||
{
|
||||
// Default constructor
|
||||
var value1 = new TValue();
|
||||
Assert.Equal(0, value1.Value);
|
||||
Assert.True(value1.IsNew);
|
||||
Assert.True(value1.IsHot);
|
||||
|
||||
// Value constructor
|
||||
var value2 = new TValue(10.0);
|
||||
Assert.Equal(10.0, value2.Value);
|
||||
|
||||
// Full constructor
|
||||
var time = DateTime.Now;
|
||||
var value3 = new TValue(time, 10.0, false, false);
|
||||
Assert.Equal(time, value3.Time);
|
||||
Assert.Equal(10.0, value3.Value);
|
||||
Assert.False(value3.IsNew);
|
||||
Assert.False(value3.IsHot);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TValue_Conversions()
|
||||
{
|
||||
var value = new TValue(10.0);
|
||||
|
||||
// Test implicit conversions
|
||||
double d = value;
|
||||
Assert.Equal(10.0, d);
|
||||
|
||||
DateTime time = value;
|
||||
Assert.Equal(value.Time, time);
|
||||
|
||||
// Test implicit conversion from double
|
||||
TValue newValue = 20.0;
|
||||
Assert.Equal(20.0, newValue.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TSeries_Operations()
|
||||
{
|
||||
var series = new TSeries();
|
||||
var time = DateTime.Now;
|
||||
|
||||
// Test adding values
|
||||
series.Add(time, 10.0);
|
||||
series.Add(time.AddMinutes(1), 20.0);
|
||||
Assert.Equal(2, series.Count);
|
||||
|
||||
// Test updating last value
|
||||
series.Add(new TValue(time.AddMinutes(1), 25.0, false));
|
||||
Assert.Equal(2, series.Count);
|
||||
Assert.Equal(25.0, series.Last.Value);
|
||||
|
||||
// Test adding range of values
|
||||
var values = new[] { 30.0, 40.0, 50.0 };
|
||||
foreach (var value in values)
|
||||
{
|
||||
series.Add(time.AddMinutes(series.Count + 1), value);
|
||||
}
|
||||
Assert.Equal(5, series.Count);
|
||||
|
||||
// Test conversions
|
||||
var doubleList = (List<double>)series;
|
||||
Assert.Equal(5, doubleList.Count);
|
||||
Assert.Equal(50.0, doubleList[^1]);
|
||||
|
||||
var doubleArray = (double[])series;
|
||||
Assert.Equal(5, doubleArray.Length);
|
||||
Assert.Equal(50.0, doubleArray[^1]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TSeries_EventHandling()
|
||||
{
|
||||
var series = new TSeries();
|
||||
var receivedValues = new List<double>();
|
||||
var time = DateTime.Now;
|
||||
|
||||
series.Pub += (object sender, in ValueEventArgs args) => receivedValues.Add(args.Tick.Value);
|
||||
|
||||
series.Add(time, 10.0);
|
||||
series.Add(time.AddMinutes(1), 20.0);
|
||||
series.Add(time.AddMinutes(2), 30.0);
|
||||
|
||||
Assert.Equal(3, receivedValues.Count);
|
||||
Assert.Equal(10.0, receivedValues[0]);
|
||||
Assert.Equal(20.0, receivedValues[1]);
|
||||
Assert.Equal(30.0, receivedValues[2]);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
+53
-16
@@ -13,12 +13,13 @@ public class EventingTests
|
||||
// Create a cryptographically secure random number generator
|
||||
using var rng = RandomNumberGenerator.Create();
|
||||
|
||||
// Create an input series to hold our random values
|
||||
// Create input series to hold our random values
|
||||
var input = new TSeries();
|
||||
var barInput = new TBarSeries();
|
||||
int p = 10;
|
||||
|
||||
// Create a list of indicator pairs (direct calculation and event-based) with names
|
||||
var indicators = new List<(string Name, AbstractBase Direct, AbstractBase EventBased)>
|
||||
// Create a list of value-based indicator pairs
|
||||
var valueIndicators = new List<(string Name, AbstractBase Direct, AbstractBase EventBased)>
|
||||
{
|
||||
("Afirma", new Afirma(p,p,Afirma.WindowType.BlackmanHarris), new Afirma(input, p,p,Afirma.WindowType.BlackmanHarris)),
|
||||
("Alma", new Alma(p), new Alma(input, p)),
|
||||
@@ -51,19 +52,15 @@ 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)),
|
||||
@@ -77,12 +74,12 @@ public class EventingTests
|
||||
("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
|
||||
// Volatility indicators (value-based)
|
||||
("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
|
||||
// Error classes
|
||||
("Mae", new Mae(p), new Mae(input, p)),
|
||||
("Mapd", new Mapd(p), new Mapd(input, p)),
|
||||
("Mape", new Mape(p), new Mape(input, p)),
|
||||
@@ -101,26 +98,66 @@ public class EventingTests
|
||||
("Huber", new Huber(p), new Huber(input, p))
|
||||
};
|
||||
|
||||
// Generate 200 random values and feed them to both direct and event-based indicators
|
||||
// Create a list of bar-based indicator pairs
|
||||
var barIndicators = new List<(string Name, AbstractBase Direct, AbstractBase EventBased)>
|
||||
{
|
||||
// Volume indicators
|
||||
("Adl", new Adl(), new Adl(barInput)),
|
||||
("Adosc", new Adosc(3, 10), new Adosc(barInput, 3, 10)),
|
||||
("Aobv", new Aobv(), new Aobv(barInput)),
|
||||
("Cmf", new Cmf(20), new Cmf(barInput, 20)),
|
||||
("Eom", new Eom(14), new Eom(barInput, 14)),
|
||||
("Kvo", new Kvo(34, 55), new Kvo(barInput, 34, 55)),
|
||||
// Volatility indicators (bar-based)
|
||||
("Atr", new Atr(14), new Atr(barInput, 14))
|
||||
};
|
||||
|
||||
// Generate 200 random values and feed them to indicators
|
||||
for (int i = 0; i < 200; i++)
|
||||
{
|
||||
// Generate random value for value-based indicators
|
||||
double randomValue = GetRandomDouble(rng) * 100;
|
||||
input.Add(randomValue);
|
||||
|
||||
// Calculate direct indicators
|
||||
foreach (var (_, direct, _) in indicators)
|
||||
// Calculate value-based indicators
|
||||
foreach (var (_, direct, _) in valueIndicators)
|
||||
{
|
||||
direct.Calc(randomValue);
|
||||
}
|
||||
|
||||
// Generate random bar for bar-based indicators
|
||||
var bar = new TBar(
|
||||
DateTime.Now,
|
||||
randomValue,
|
||||
randomValue + Math.Abs(GetRandomDouble(rng) * 10),
|
||||
randomValue - Math.Abs(GetRandomDouble(rng) * 10),
|
||||
randomValue + GetRandomDouble(rng) * 5,
|
||||
Math.Abs(GetRandomDouble(rng) * 1000),
|
||||
true
|
||||
);
|
||||
barInput.Add(bar);
|
||||
|
||||
// Calculate bar-based indicators
|
||||
foreach (var (_, direct, _) in barIndicators)
|
||||
{
|
||||
direct.Calc(bar);
|
||||
}
|
||||
}
|
||||
|
||||
// Compare the results of direct and event-based calculations
|
||||
for (int i = 0; i < indicators.Count; i++)
|
||||
// Compare the results for value-based indicators
|
||||
foreach (var (name, direct, eventBased) in valueIndicators)
|
||||
{
|
||||
var (name, direct, eventBased) = indicators[i];
|
||||
bool areEqual = (double.IsNaN(direct.Value) && double.IsNaN(eventBased.Value)) ||
|
||||
Math.Abs(direct.Value - eventBased.Value) < 1e-9;
|
||||
Assert.True(areEqual, $"Indicator {name} failed: Expected {direct.Value}, Actual {eventBased.Value}");
|
||||
Assert.True(areEqual, $"Value indicator {name} failed: Expected {direct.Value}, Actual {eventBased.Value}");
|
||||
}
|
||||
|
||||
// Compare the results for bar-based indicators
|
||||
foreach (var (name, direct, eventBased) in barIndicators)
|
||||
{
|
||||
bool areEqual = (double.IsNaN(direct.Value) && double.IsNaN(eventBased.Value)) ||
|
||||
Math.Abs(direct.Value - eventBased.Value) < 1e-9;
|
||||
Assert.True(areEqual, $"Bar indicator {name} failed: Expected {direct.Value}, Actual {eventBased.Value}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -88,4 +88,129 @@ public class MomentumUpdateTests
|
||||
|
||||
Assert.Equal(initialValue, finalValue, precision);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Dmx_Update()
|
||||
{
|
||||
var indicator = new Dmx(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 Pmo_Update()
|
||||
{
|
||||
var indicator = new Pmo(period1: 35, period2: 20);
|
||||
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 Po_Update()
|
||||
{
|
||||
var indicator = new Po(fastPeriod: 10, slowPeriod: 21);
|
||||
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 Ppo_Update()
|
||||
{
|
||||
var indicator = new Ppo(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 Prs_Update()
|
||||
{
|
||||
var indicator = new Prs();
|
||||
indicator.SetBenchmark(ReferenceValue);
|
||||
double initialValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: true));
|
||||
|
||||
for (int i = 0; i < RandomUpdates; i++)
|
||||
{
|
||||
indicator.SetBenchmark(GetRandomDouble() + 100); // Ensure positive benchmark
|
||||
indicator.Calc(new TValue(DateTime.Now, GetRandomDouble(), IsNew: false));
|
||||
}
|
||||
|
||||
indicator.SetBenchmark(ReferenceValue);
|
||||
double finalValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: false));
|
||||
|
||||
Assert.Equal(initialValue, finalValue, precision);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Roc_Update()
|
||||
{
|
||||
var indicator = new Roc(period: 12);
|
||||
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() + 100, IsNew: false)); // Ensure positive prices
|
||||
}
|
||||
double finalValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: false));
|
||||
|
||||
Assert.Equal(initialValue, finalValue, precision);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Mom_Update()
|
||||
{
|
||||
var indicator = new Mom(period: 10);
|
||||
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 Vel_Update()
|
||||
{
|
||||
var indicator = new Vel(period: 10);
|
||||
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,4 +61,36 @@ public class OscillatorsUpdateTests
|
||||
|
||||
Assert.Equal(initialValue, finalValue, precision);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Ao_Update()
|
||||
{
|
||||
var indicator = new Ao();
|
||||
TBar r = new(DateTime.Now, ReferenceValue, ReferenceValue, ReferenceValue, ReferenceValue, 1000, IsNew: true);
|
||||
double initialValue = indicator.Calc(r);
|
||||
|
||||
for (int i = 0; i < RandomUpdates; i++)
|
||||
{
|
||||
indicator.Calc(new TBar(DateTime.Now, GetRandomDouble(), GetRandomDouble(), GetRandomDouble(), GetRandomDouble(), 1000, 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 Ac_Update()
|
||||
{
|
||||
var indicator = new Ac();
|
||||
TBar r = new(DateTime.Now, ReferenceValue, ReferenceValue, ReferenceValue, ReferenceValue, 1000, IsNew: true);
|
||||
double initialValue = indicator.Calc(r);
|
||||
|
||||
for (int i = 0; i < RandomUpdates; i++)
|
||||
{
|
||||
indicator.Calc(new TBar(DateTime.Now, GetRandomDouble(), GetRandomDouble(), GetRandomDouble(), GetRandomDouble(), 1000, 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
using Xunit;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace QuanTAlib.Tests;
|
||||
|
||||
public class VolumeUpdateTests
|
||||
{
|
||||
private readonly RandomNumberGenerator rng = RandomNumberGenerator.Create();
|
||||
private const int RandomUpdates = 100;
|
||||
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();
|
||||
double volume = Math.Abs(GetRandomDouble()) * 1000; // Random positive volume
|
||||
return new TBar(DateTime.Now, open, high, low, close, volume, IsNew);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Adl_Update()
|
||||
{
|
||||
var indicator = new Adl();
|
||||
TBar r = GetRandomBar(true);
|
||||
|
||||
// First calculation with IsNew: true
|
||||
double value1 = indicator.Calc(r);
|
||||
|
||||
// Multiple recalculations with IsNew: false should not change the value
|
||||
for (int i = 0; i < RandomUpdates; i++)
|
||||
{
|
||||
indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
|
||||
}
|
||||
|
||||
// Final calculation with IsNew: false should match initial value
|
||||
double value2 = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
|
||||
Assert.Equal(value1, value2, precision);
|
||||
|
||||
// New calculation with IsNew: true should update the value
|
||||
double value3 = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: true));
|
||||
Assert.NotEqual(value1, value3, precision);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Adosc_Update()
|
||||
{
|
||||
var indicator = new Adosc(shortPeriod: 3, longPeriod: 10);
|
||||
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 Aobv_Update()
|
||||
{
|
||||
var indicator = new Aobv();
|
||||
TBar r = GetRandomBar(true);
|
||||
|
||||
// First calculation with IsNew: true
|
||||
double value1 = indicator.Calc(r);
|
||||
|
||||
// Multiple recalculations with IsNew: false should not change the value
|
||||
for (int i = 0; i < RandomUpdates; i++)
|
||||
{
|
||||
indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
|
||||
}
|
||||
|
||||
// Final calculation with IsNew: false should match initial value
|
||||
double value2 = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
|
||||
Assert.Equal(value1, value2, precision);
|
||||
|
||||
// New calculation with IsNew: true should update the value
|
||||
double value3 = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: true));
|
||||
Assert.NotEqual(value1, value3, precision);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Cmf_Update()
|
||||
{
|
||||
var indicator = new Cmf(period: 20);
|
||||
TBar r = GetRandomBar(true);
|
||||
|
||||
// Generate a sequence of bars for warmup
|
||||
var warmupBars = new List<TBar>();
|
||||
for (int i = 0; i < indicator.WarmupPeriod; i++)
|
||||
{
|
||||
var bar = GetRandomBar(IsNew: true);
|
||||
warmupBars.Add(bar);
|
||||
indicator.Calc(bar);
|
||||
}
|
||||
|
||||
// Calculate initial value after warmup
|
||||
double initialValue = indicator.Calc(r);
|
||||
|
||||
// Apply random updates
|
||||
for (int i = 0; i < RandomUpdates; i++)
|
||||
{
|
||||
indicator.Calc(GetRandomBar(IsNew: false));
|
||||
}
|
||||
|
||||
// Reset and replay the same sequence
|
||||
indicator.Init();
|
||||
foreach (var bar in warmupBars)
|
||||
{
|
||||
indicator.Calc(bar);
|
||||
}
|
||||
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 Eom_Update()
|
||||
{
|
||||
var indicator = new Eom(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 Kvo_Update()
|
||||
{
|
||||
var indicator = new Kvo(shortPeriod: 34, longPeriod: 55);
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// DMX: Enhanced Directional Movement Index using JMA smoothing
|
||||
/// An improvement over the traditional DMI indicator that uses Jurik Moving Average (JMA)
|
||||
/// for smoothing instead of Wilder's moving average. This enhancement provides better
|
||||
/// noise reduction while maintaining responsiveness to significant price movements.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The DMX 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 JMA instead of Wilder's smoothing
|
||||
/// 5. Calculate +DI and -DI as percentages
|
||||
///
|
||||
/// Key improvements over DMI:
|
||||
/// - Uses JMA's adaptive volatility-based smoothing
|
||||
/// - Better noise reduction in the directional movement signals
|
||||
/// - Maintains responsiveness to significant price movements
|
||||
/// - Reduced lag through JMA's phase-shifting
|
||||
///
|
||||
/// 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 * JMA(+DM) / JMA(TR)
|
||||
/// -DI = 100 * JMA(-DM) / JMA(TR)
|
||||
///
|
||||
/// Sources:
|
||||
/// Original DMI by J. Welles Wilder Jr. - "New Concepts in Technical Trading Systems" (1978)
|
||||
/// Enhanced with JMA smoothing by Mark Jurik
|
||||
/// </remarks>
|
||||
|
||||
[SkipLocalsInit]
|
||||
public sealed class Dmx : AbstractBarBase
|
||||
{
|
||||
private readonly Jma _smoothedTr;
|
||||
private readonly Jma _smoothedPlusDm;
|
||||
private readonly Jma _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 = 10;
|
||||
private const int DefaultPhase = 100;
|
||||
private const double DefaultFactor = 0.25;
|
||||
|
||||
/// <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 DMX calculation (default 14).</param>
|
||||
/// <param name="phase">The phase for the JMA smoothing (default 0).</param>
|
||||
/// <param name="factor">The factor for the JMA smoothing (default 0.45).</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 1.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Dmx(int period = DefaultPeriod, int phase = DefaultPhase, double factor = DefaultFactor)
|
||||
{
|
||||
if (period < 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(period));
|
||||
_smoothedTr = new(period, phase, factor);
|
||||
_smoothedPlusDm = new(period, phase, factor);
|
||||
_smoothedMinusDm = new(period, phase, factor);
|
||||
_index = 0;
|
||||
WarmupPeriod = period * 2; // JMA needs more warmup periods than RMA
|
||||
Name = $"DMX({period})";
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The number of periods used in the DMX calculation.</param>
|
||||
/// <param name="phase">The phase for the JMA smoothing.</param>
|
||||
/// <param name="factor">The factor for the JMA smoothing.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Dmx(object source, int period, int phase = DefaultPhase, double factor = DefaultFactor) : this(period, phase, factor)
|
||||
{
|
||||
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 JMA
|
||||
_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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// Mom: Momentum
|
||||
/// A basic momentum indicator that measures the change in price over a specified
|
||||
/// period, helping identify the strength and speed of price movements.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The Momentum calculation process:
|
||||
/// 1. Store historical prices in a circular buffer
|
||||
/// 2. Calculate absolute difference between current and historical price
|
||||
/// 3. No scaling factor applied to maintain raw price difference
|
||||
///
|
||||
/// Key characteristics:
|
||||
/// - Basic momentum measurement
|
||||
/// - Shows absolute price changes
|
||||
/// - Zero line crossovers signal trend changes
|
||||
/// - Foundation for other momentum indicators
|
||||
///
|
||||
/// Formula:
|
||||
/// Mom = Price - PriceN
|
||||
/// where PriceN is the price N periods ago
|
||||
///
|
||||
/// Sources:
|
||||
/// Technical Analysis of Financial Markets by John J. Murphy
|
||||
/// Technical Analysis Using Multiple Timeframes by Brian Shannon
|
||||
/// </remarks>
|
||||
|
||||
[SkipLocalsInit]
|
||||
public sealed class Mom : AbstractBase
|
||||
{
|
||||
private readonly CircularBuffer _priceBuffer;
|
||||
private const int DefaultPeriod = 10;
|
||||
|
||||
/// <param name="period">The lookback period for momentum calculation (default 10).</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 1.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Mom(int period = DefaultPeriod)
|
||||
{
|
||||
if (period < 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(period));
|
||||
|
||||
_priceBuffer = new(period + 1);
|
||||
WarmupPeriod = period;
|
||||
Name = $"MOM({period})";
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The lookback period for momentum calculation.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Mom(object source, int period) : this(period)
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
_priceBuffer.Add(Input.Value);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
if (_priceBuffer.Count < _priceBuffer.Capacity)
|
||||
return 0.0;
|
||||
|
||||
return Input.Value - _priceBuffer[0];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// PMO: Price Momentum Oscillator
|
||||
/// A momentum indicator that uses exponential moving averages of ROC (Rate of Change)
|
||||
/// to identify overbought and oversold conditions in price movements.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The PMO calculation process:
|
||||
/// 1. Calculate ROC (Rate of Change) of closing prices
|
||||
/// 2. Apply a first smoothing EMA to the ROC values
|
||||
/// 3. Apply a second smoothing EMA to the result
|
||||
/// 4. Multiply by a scaling factor for better visualization
|
||||
///
|
||||
/// Key characteristics:
|
||||
/// - Double-smoothed momentum indicator
|
||||
/// - Helps identify overbought/oversold conditions
|
||||
/// - Useful for trend confirmation and divergence analysis
|
||||
/// - More responsive than traditional momentum oscillators
|
||||
///
|
||||
/// Formula:
|
||||
/// ROC = (Close - PrevClose) / PrevClose
|
||||
/// Signal1 = EMA(ROC, Period1)
|
||||
/// PMO = EMA(Signal1, Period2) * ScalingFactor
|
||||
///
|
||||
/// Sources:
|
||||
/// Developed by Carl Swenlin
|
||||
/// Technical Analysis of Stocks and Commodities magazine
|
||||
/// </remarks>
|
||||
|
||||
[SkipLocalsInit]
|
||||
public sealed class Pmo : AbstractBase
|
||||
{
|
||||
private readonly Ema _smoothing1;
|
||||
private readonly Ema _smoothing2;
|
||||
private double _prevClose;
|
||||
private double _p_prevClose;
|
||||
private const double ScalingFactor = 100.0;
|
||||
private const int DefaultPeriod1 = 35;
|
||||
private const int DefaultPeriod2 = 20;
|
||||
|
||||
/// <param name="period1">The first smoothing period (default 35).</param>
|
||||
/// <param name="period2">The second smoothing period (default 20).</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when either period is less than 1.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Pmo(int period1 = DefaultPeriod1, int period2 = DefaultPeriod2)
|
||||
{
|
||||
if (period1 < 1 || period2 < 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(period1));
|
||||
|
||||
_smoothing1 = new(period1);
|
||||
_smoothing2 = new(period2);
|
||||
_index = 0;
|
||||
WarmupPeriod = period1 + period2;
|
||||
Name = $"PMO({period1},{period2})";
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period1">The first smoothing period.</param>
|
||||
/// <param name="period2">The second smoothing period.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Pmo(object source, int period1, int period2) : this(period1, period2)
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_index++;
|
||||
_p_prevClose = _prevClose;
|
||||
}
|
||||
else
|
||||
{
|
||||
_prevClose = _p_prevClose;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
if (_index == 1)
|
||||
{
|
||||
_prevClose = Input.Value;
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// Calculate Rate of Change
|
||||
double roc = (Input.Value - _prevClose) / _prevClose;
|
||||
_prevClose = Input.Value;
|
||||
|
||||
// Apply double smoothing
|
||||
double signal1 = _smoothing1.Calc(roc, Input.IsNew);
|
||||
return _smoothing2.Calc(signal1, Input.IsNew) * ScalingFactor;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// PO: Price Oscillator
|
||||
/// A momentum indicator that measures the difference between two moving averages
|
||||
/// of different periods to identify price momentum and potential trend changes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The PO calculation process:
|
||||
/// 1. Calculate fast EMA of closing prices
|
||||
/// 2. Calculate slow EMA of closing prices
|
||||
/// 3. Calculate the difference between fast and slow EMAs
|
||||
/// 4. Multiply by a scaling factor for better visualization
|
||||
///
|
||||
/// Key characteristics:
|
||||
/// - Measures momentum through moving average differences
|
||||
/// - Helps identify trend direction and potential reversals
|
||||
/// - Zero line crossovers signal trend changes
|
||||
/// - Similar to MACD but more customizable periods
|
||||
///
|
||||
/// Formula:
|
||||
/// FastMA = EMA(Close, FastPeriod)
|
||||
/// SlowMA = EMA(Close, SlowPeriod)
|
||||
/// PO = (FastMA - SlowMA) * ScalingFactor
|
||||
///
|
||||
/// Sources:
|
||||
/// Technical Analysis of Financial Markets by John J. Murphy
|
||||
/// </remarks>
|
||||
|
||||
[SkipLocalsInit]
|
||||
public sealed class Po : AbstractBase
|
||||
{
|
||||
private readonly Ema _fastEma;
|
||||
private readonly Ema _slowEma;
|
||||
private const double ScalingFactor = 1.0;
|
||||
private const int DefaultFastPeriod = 10;
|
||||
private const int DefaultSlowPeriod = 21;
|
||||
|
||||
/// <param name="fastPeriod">The fast EMA period (default 10).</param>
|
||||
/// <param name="slowPeriod">The slow EMA period (default 21).</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when either period is less than 1.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Po(int fastPeriod = DefaultFastPeriod, int slowPeriod = DefaultSlowPeriod)
|
||||
{
|
||||
if (fastPeriod < 1 || slowPeriod < 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(fastPeriod));
|
||||
if (fastPeriod >= slowPeriod)
|
||||
throw new ArgumentException("Fast period must be less than slow period");
|
||||
|
||||
_fastEma = new(fastPeriod);
|
||||
_slowEma = new(slowPeriod);
|
||||
WarmupPeriod = slowPeriod;
|
||||
Name = $"PO({fastPeriod},{slowPeriod})";
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="fastPeriod">The fast EMA period.</param>
|
||||
/// <param name="slowPeriod">The slow EMA period.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Po(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)
|
||||
{
|
||||
// No state management needed for this indicator
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
double fastEma = _fastEma.Calc(Input.Value, Input.IsNew);
|
||||
double slowEma = _slowEma.Calc(Input.Value, Input.IsNew);
|
||||
return (fastEma - slowEma) * ScalingFactor;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// PPO: Percentage Price Oscillator
|
||||
/// A momentum indicator that shows the percentage difference between two moving averages
|
||||
/// of different periods, helping identify price momentum and potential trend changes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The PPO calculation process:
|
||||
/// 1. Calculate fast EMA of closing prices
|
||||
/// 2. Calculate slow EMA of closing prices
|
||||
/// 3. Calculate the percentage difference between fast and slow EMAs
|
||||
/// 4. Multiply by a scaling factor for better visualization
|
||||
///
|
||||
/// Key characteristics:
|
||||
/// - Measures momentum through percentage differences
|
||||
/// - Normalized for comparison across different price levels
|
||||
/// - Zero line crossovers signal trend changes
|
||||
/// - Similar to MACD but expressed as a percentage
|
||||
///
|
||||
/// Formula:
|
||||
/// FastMA = EMA(Close, FastPeriod)
|
||||
/// SlowMA = EMA(Close, SlowPeriod)
|
||||
/// PPO = ((FastMA - SlowMA) / SlowMA) * 100
|
||||
///
|
||||
/// Sources:
|
||||
/// Technical Analysis of Financial Markets by John J. Murphy
|
||||
/// StockCharts.com Technical Indicators
|
||||
/// </remarks>
|
||||
|
||||
[SkipLocalsInit]
|
||||
public sealed class Ppo : AbstractBase
|
||||
{
|
||||
private readonly Ema _fastEma;
|
||||
private readonly Ema _slowEma;
|
||||
private const double ScalingFactor = 100.0;
|
||||
private const int DefaultFastPeriod = 12;
|
||||
private const int DefaultSlowPeriod = 26;
|
||||
|
||||
/// <param name="fastPeriod">The fast EMA period (default 12).</param>
|
||||
/// <param name="slowPeriod">The slow EMA period (default 26).</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when either period is less than 1.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Ppo(int fastPeriod = DefaultFastPeriod, int slowPeriod = DefaultSlowPeriod)
|
||||
{
|
||||
if (fastPeriod < 1 || slowPeriod < 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(fastPeriod));
|
||||
if (fastPeriod >= slowPeriod)
|
||||
throw new ArgumentException("Fast period must be less than slow period");
|
||||
|
||||
_fastEma = new(fastPeriod);
|
||||
_slowEma = new(slowPeriod);
|
||||
WarmupPeriod = slowPeriod;
|
||||
Name = $"PPO({fastPeriod},{slowPeriod})";
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="fastPeriod">The fast EMA period.</param>
|
||||
/// <param name="slowPeriod">The slow EMA period.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Ppo(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)
|
||||
{
|
||||
// No state management needed for this indicator
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
double fastEma = _fastEma.Calc(Input.Value, Input.IsNew);
|
||||
double slowEma = _slowEma.Calc(Input.Value, Input.IsNew);
|
||||
|
||||
if (Math.Abs(slowEma) <= double.Epsilon)
|
||||
return 0.0;
|
||||
|
||||
return ((fastEma - slowEma) / slowEma) * ScalingFactor;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// PRS: Price Relative Strength
|
||||
/// A momentum indicator that compares the performance of a security against a benchmark,
|
||||
/// helping identify which is showing stronger relative momentum.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The PRS calculation process:
|
||||
/// 1. Take the current price of the security
|
||||
/// 2. Take the current price of the benchmark
|
||||
/// 3. Calculate the ratio between them
|
||||
/// 4. Multiply by a scaling factor for better visualization
|
||||
///
|
||||
/// Key characteristics:
|
||||
/// - Measures relative performance against a benchmark
|
||||
/// - Helps identify market leaders and laggards
|
||||
/// - Rising PRS indicates outperformance
|
||||
/// - Falling PRS indicates underperformance
|
||||
///
|
||||
/// Formula:
|
||||
/// PRS = (Price / Benchmark) * 100
|
||||
///
|
||||
/// Sources:
|
||||
/// Technical Analysis of Financial Markets by John J. Murphy
|
||||
/// StockCharts.com Technical Indicators
|
||||
/// </remarks>
|
||||
|
||||
[SkipLocalsInit]
|
||||
public sealed class Prs : AbstractBase
|
||||
{
|
||||
private const double ScalingFactor = 100.0;
|
||||
private double _benchmark;
|
||||
private double _p_benchmark;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the PRS indicator
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Prs()
|
||||
{
|
||||
WarmupPeriod = 1;
|
||||
Name = "PRS";
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Prs(object source) : this()
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the current benchmark value
|
||||
/// </summary>
|
||||
/// <param name="benchmark">The benchmark value to compare against</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetBenchmark(double benchmark)
|
||||
{
|
||||
_benchmark = benchmark;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
_p_benchmark = _benchmark;
|
||||
else
|
||||
_benchmark = _p_benchmark;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
if (_benchmark <= double.Epsilon)
|
||||
return 0.0;
|
||||
|
||||
return (Input.Value / _benchmark) * ScalingFactor;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// ROC: Rate of Change
|
||||
/// A momentum indicator that measures the percentage change in price over a specified
|
||||
/// period, helping identify the speed and strength of price movements.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The ROC calculation process:
|
||||
/// 1. Store historical prices in a circular buffer
|
||||
/// 2. Calculate percentage change between current and historical price
|
||||
/// 3. Multiply by scaling factor for better visualization
|
||||
///
|
||||
/// Key characteristics:
|
||||
/// - Pure momentum indicator
|
||||
/// - Oscillates around zero line
|
||||
/// - Helps identify overbought/oversold conditions
|
||||
/// - Useful for divergence analysis
|
||||
///
|
||||
/// Formula:
|
||||
/// ROC = ((Price - PriceN) / PriceN) * 100
|
||||
/// where PriceN is the price N periods ago
|
||||
///
|
||||
/// Sources:
|
||||
/// Technical Analysis of Financial Markets by John J. Murphy
|
||||
/// Technical Analysis of Stock Trends by Robert D. Edwards and John Magee
|
||||
/// </remarks>
|
||||
|
||||
[SkipLocalsInit]
|
||||
public sealed class Roc : AbstractBase
|
||||
{
|
||||
private readonly CircularBuffer _priceBuffer;
|
||||
private const double ScalingFactor = 100.0;
|
||||
private const int DefaultPeriod = 12;
|
||||
|
||||
/// <param name="period">The lookback period for ROC calculation (default 12).</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 1.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Roc(int period = DefaultPeriod)
|
||||
{
|
||||
if (period < 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(period));
|
||||
|
||||
_priceBuffer = new(period + 1);
|
||||
WarmupPeriod = period;
|
||||
Name = $"ROC({period})";
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The lookback period for ROC calculation.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Roc(object source, int period) : this(period)
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
_priceBuffer.Add(Input.Value);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
if (_priceBuffer.Count < _priceBuffer.Capacity)
|
||||
return 0.0;
|
||||
|
||||
double oldPrice = _priceBuffer[0];
|
||||
if (oldPrice <= double.Epsilon)
|
||||
return 0.0;
|
||||
|
||||
return ((Input.Value - oldPrice) / oldPrice) * ScalingFactor;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// Vel: Velocity
|
||||
/// An enhanced momentum indicator that applies Jurik Moving Average (JMA) smoothing
|
||||
/// to the basic momentum calculation, providing better noise reduction while
|
||||
/// maintaining responsiveness to significant price movements.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The Velocity calculation process:
|
||||
/// 1. Calculate basic momentum (price difference)
|
||||
/// 2. Apply JMA smoothing to the momentum values
|
||||
/// 3. No scaling factor applied to maintain price-based units
|
||||
///
|
||||
/// Key characteristics:
|
||||
/// - Enhanced momentum measurement with JMA smoothing
|
||||
/// - Better noise reduction than basic momentum
|
||||
/// - Maintains responsiveness to significant moves
|
||||
/// - Reduced lag through JMA's phase-shifting
|
||||
///
|
||||
/// Formula:
|
||||
/// Mom = Price - PriceN
|
||||
/// Vel = JMA(Mom, period)
|
||||
///
|
||||
/// Sources:
|
||||
/// Enhanced with JMA smoothing by Mark Jurik
|
||||
/// Technical Analysis of Financial Markets by John J. Murphy
|
||||
/// </remarks>
|
||||
|
||||
[SkipLocalsInit]
|
||||
public sealed class Vel : AbstractBase
|
||||
{
|
||||
private readonly CircularBuffer _priceBuffer;
|
||||
private readonly Jma _smoothing;
|
||||
private const int DefaultPeriod = 10;
|
||||
private const int DefaultPhase = 100;
|
||||
private const double DefaultFactor = 0.25;
|
||||
|
||||
/// <param name="period">The lookback period for velocity calculation (default 10).</param>
|
||||
/// <param name="phase">The phase for the JMA smoothing (default 0).</param>
|
||||
/// <param name="power">The power factor for the JMA smoothing (default 2.0).</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 1.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Vel(int period = DefaultPeriod, int phase = DefaultPhase, double factor = DefaultFactor)
|
||||
{
|
||||
if (period < 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(period));
|
||||
|
||||
_priceBuffer = new(period + 1);
|
||||
_smoothing = new(period, phase, factor);
|
||||
WarmupPeriod = period * 2; // JMA needs more warmup periods
|
||||
Name = $"VEL({period})";
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The lookback period for velocity calculation.</param>
|
||||
/// <param name="phase">The phase for the JMA smoothing.</param>
|
||||
/// <param name="power">The power factor for the JMA smoothing.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Vel(object source, int period, int phase = DefaultPhase, double power = DefaultFactor)
|
||||
: this(period, phase, power)
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
_priceBuffer.Add(Input.Value);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
if (_priceBuffer.Count < _priceBuffer.Capacity)
|
||||
return 0.0;
|
||||
|
||||
// Calculate basic momentum
|
||||
double momentum = Input.Value - _priceBuffer[0];
|
||||
|
||||
// Apply JMA smoothing
|
||||
return _smoothing.Calc(momentum, Input.IsNew);
|
||||
}
|
||||
}
|
||||
+11
-9
@@ -1,16 +1,18 @@
|
||||
# Momentum indicators
|
||||
|
||||
✔️ ADX - Average Directional Movement Index
|
||||
✔️ ADXR - Average Directional Movement Index Rating
|
||||
✔️ APO - Absolute Price Oscillator
|
||||
DMI - Directional Movement Index
|
||||
DMX - Jurik Directional Movement Index
|
||||
✔️ DMI - Directional Movement Index
|
||||
✔️ DMX - Jurik Directional Movement Index
|
||||
DPO - Detrended Price Oscillator
|
||||
MACD - Moving Average Convergence/Divergence
|
||||
MOM - Momentum
|
||||
PMO - Price Momentum Oscillator
|
||||
PO - Price Oscillator
|
||||
PPO - Percentage Price Oscillator
|
||||
PRS - Price Relative Strength
|
||||
ROC - Rate of Change
|
||||
✔️ MOM - Momentum
|
||||
✔️ PMO - Price Momentum Oscillator
|
||||
✔️ PO - Price Oscillator
|
||||
✔️ PPO - Percentage Price Oscillator
|
||||
✔️ PRS - Price Relative Strength
|
||||
✔️ ROC - Rate of Change
|
||||
TRIX - 1-day ROC of TEMA
|
||||
VEL - Jurik Signal Velocity
|
||||
✔️ VEL - Jurik Signal Velocity
|
||||
VORTEX - Vortex Indicator
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// AC: Acceleration/Deceleration Oscillator
|
||||
/// A momentum indicator that measures the acceleration and deceleration of the current driving force.
|
||||
/// It is derived from the Awesome Oscillator (AO) and helps identify potential trend reversals.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The AC calculation process:
|
||||
/// 1. Calculate the Awesome Oscillator (AO)
|
||||
/// 2. Calculate a 5-period simple moving average of the AO
|
||||
/// 3. Subtract the 5-period SMA from the current AO value
|
||||
///
|
||||
/// Key characteristics:
|
||||
/// - Oscillates above and below zero
|
||||
/// - Measures the acceleration/deceleration of market driving force
|
||||
/// - Positive values indicate increasing momentum
|
||||
/// - Negative values indicate decreasing momentum
|
||||
/// - Can be used to identify potential trend reversals
|
||||
///
|
||||
/// Formula:
|
||||
/// AC = AO - SMA(AO, 5)
|
||||
///
|
||||
/// Sources:
|
||||
/// Bill Williams - "Trading Chaos" (1995)
|
||||
/// https://www.investopedia.com/terms/a/ac.asp
|
||||
/// </remarks>
|
||||
|
||||
[SkipLocalsInit]
|
||||
public sealed class Ac : AbstractBase
|
||||
{
|
||||
private readonly Ao _ao;
|
||||
private readonly Sma _sma5;
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Ac(object source) : this()
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new BarSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Ac()
|
||||
{
|
||||
_ao = new Ao();
|
||||
_sma5 = new Sma(5);
|
||||
WarmupPeriod = 39; // AO requires 34 periods + 5 for AC's SMA
|
||||
Name = "AC";
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_index++;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(BarInput.IsNew);
|
||||
var ao = _ao.Calc(BarInput, BarInput.IsNew);
|
||||
_sma5.Calc(ao, BarInput.IsNew);
|
||||
|
||||
return ao - _sma5.Value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// AO: Awesome Oscillator
|
||||
/// A momentum indicator that reflects the precise changes in the market driving force.
|
||||
/// It is used to affirm trends or to anticipate possible reversals.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The AO calculation process:
|
||||
/// 1. Calculates the 5-period simple moving average of the HL2 (High+Low)/2 values.
|
||||
/// 2. Calculates the 34-period simple moving average of the HL2 (High+Low)/2 values.
|
||||
/// 3. Subtracts the 34-period SMA from the 5-period SMA.
|
||||
///
|
||||
/// Key characteristics:
|
||||
/// - Oscillates above and below zero
|
||||
/// - Positive values indicate bullish momentum
|
||||
/// - Negative values indicate bearish momentum
|
||||
/// - Crosses above zero suggest buying opportunities
|
||||
/// - Crosses below zero suggest selling opportunities
|
||||
///
|
||||
/// Formula:
|
||||
/// AO = SMA(HL2, 5) - SMA(HL2, 34)
|
||||
///
|
||||
/// Sources:
|
||||
/// Bill Williams - "Trading Chaos" (1995)
|
||||
/// https://www.investopedia.com/terms/a/awesomeoscillator.asp
|
||||
/// </remarks>
|
||||
|
||||
[SkipLocalsInit]
|
||||
public sealed class Ao : AbstractBase
|
||||
{
|
||||
private readonly Sma _sma5;
|
||||
private readonly Sma _sma34;
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Ao(object source) : this()
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new BarSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Ao()
|
||||
{
|
||||
_sma5 = new Sma(5);
|
||||
_sma34 = new Sma(34);
|
||||
WarmupPeriod = 34;
|
||||
Name = "AO";
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_index++;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(BarInput.IsNew);
|
||||
_sma5.Calc(BarInput.HL2, BarInput.IsNew);
|
||||
_sma34.Calc(BarInput.HL2, BarInput.IsNew);
|
||||
|
||||
return _sma5.Value - _sma34.Value;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
AC - Acceleration Oscillator
|
||||
AO - Awesome Oscillator
|
||||
# Oscillators indicators
|
||||
|
||||
✔️ AC - Acceleration Oscillator
|
||||
✔️ AO - Awesome Oscillator
|
||||
AROON - Aroon oscillator
|
||||
BOP - Balance of Power
|
||||
CCI - Commodity Channel Index
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Statistics indicators
|
||||
|
||||
BETA - Beta coefficient
|
||||
CORR - Correlation Coefficient
|
||||
✔️ CURVATURE - Rate of Change in Direction or Slope
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Volatility indicators
|
||||
|
||||
ADR - Average Daily Range
|
||||
AP - Andrew's Pitchfork
|
||||
✔️ ATR - Average True Range
|
||||
@@ -22,7 +24,7 @@ PSAR - Parabolic Stop and Reverse
|
||||
PV - Parkinson Volatility
|
||||
RSV - Rogers-Satchell Volatility
|
||||
✔️ RV - Realized Volatility
|
||||
RVI - Relative Volatility Index
|
||||
✔️ RVI - Relative Volatility Index
|
||||
STARC - Starc Bands
|
||||
SV - Stochastic Volatility
|
||||
TR - True Range
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// ADL: Accumulation Distribution Line (Chaikin)
|
||||
/// A volume-based indicator that measures the cumulative flow of money into and out
|
||||
/// of a security. It assesses the relationship between price and volume to determine
|
||||
/// buying/selling pressure.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The ADL calculation process:
|
||||
/// 1. Calculates Money Flow Multiplier (MFM):
|
||||
/// MFM = ((Close - Low) - (High - Close)) / (High - Low)
|
||||
/// 2. Calculates Money Flow Volume (MFV):
|
||||
/// MFV = MFM × Volume
|
||||
/// 3. ADL is cumulative sum of MFV values
|
||||
///
|
||||
/// Key characteristics:
|
||||
/// - Volume-weighted measure
|
||||
/// - Cumulative indicator
|
||||
/// - No upper/lower bounds
|
||||
/// - Trend confirmation tool
|
||||
/// - Divergence indicator
|
||||
///
|
||||
/// Formula:
|
||||
/// MFM = ((Close - Low) - (High - Close)) / (High - Low)
|
||||
/// MFV = MFM × Volume
|
||||
/// ADL = Previous ADL + MFV
|
||||
///
|
||||
/// Market Applications:
|
||||
/// - Trend confirmation
|
||||
/// - Volume analysis
|
||||
/// - Price/volume divergence
|
||||
/// - Support/resistance levels
|
||||
/// - Market participation
|
||||
///
|
||||
/// Sources:
|
||||
/// Marc Chaikin - Original development
|
||||
/// https://www.investopedia.com/terms/a/accumulationdistribution.asp
|
||||
///
|
||||
/// Note: Focuses on the relationship between price and volume
|
||||
/// </remarks>
|
||||
|
||||
[SkipLocalsInit]
|
||||
public sealed class Adl : AbstractBase
|
||||
{
|
||||
private double _cumulativeAdl;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Adl()
|
||||
{
|
||||
WarmupPeriod = 1;
|
||||
Name = "ADL";
|
||||
Init();
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Adl(object source) : this()
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new BarSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
_cumulativeAdl = 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_lastValidValue = Value;
|
||||
_index++;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static double CalculateMoneyFlowMultiplier(double close, double high, double low)
|
||||
{
|
||||
double range = high - low;
|
||||
if (range > 0)
|
||||
{
|
||||
return ((close - low) - (high - close)) / range;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(BarInput.IsNew);
|
||||
|
||||
// Calculate Money Flow Multiplier
|
||||
double mfm = CalculateMoneyFlowMultiplier(BarInput.Close, BarInput.High, BarInput.Low);
|
||||
|
||||
// Calculate Money Flow Volume
|
||||
double mfv = mfm * BarInput.Volume;
|
||||
|
||||
// Update cumulative ADL only for new bars
|
||||
if (BarInput.IsNew)
|
||||
{
|
||||
_cumulativeAdl += mfv;
|
||||
}
|
||||
|
||||
IsHot = _index >= WarmupPeriod;
|
||||
return _cumulativeAdl;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// ADOSC: Chaikin Accumulation/Distribution Oscillator
|
||||
/// A momentum indicator that measures the strength of accumulation/distribution by combining
|
||||
/// price and volume with moving averages. It helps identify potential trend reversals and
|
||||
/// buying/selling pressure.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The ADOSC calculation process:
|
||||
/// 1. Calculate ADL (Accumulation/Distribution Line)
|
||||
/// a. Money Flow Multiplier = ((Close - Low) - (High - Close)) / (High - Low)
|
||||
/// b. Money Flow Volume = MFM × Volume
|
||||
/// c. ADL = Previous ADL + MFV
|
||||
/// 2. Calculate two EMAs of ADL values
|
||||
/// 3. Subtract longer EMA from shorter EMA
|
||||
///
|
||||
/// Key characteristics:
|
||||
/// - Volume-weighted measure
|
||||
/// - Oscillates around zero
|
||||
/// - Uses two different time periods
|
||||
/// - Default periods are 3 and 10 days
|
||||
/// - Shows momentum of money flow
|
||||
///
|
||||
/// Formula:
|
||||
/// MFM = ((Close - Low) - (High - Close)) / (High - Low)
|
||||
/// MFV = MFM × Volume
|
||||
/// ADL = Previous ADL + MFV
|
||||
/// ADOSC = EMA(ADL, shortPeriod) - EMA(ADL, longPeriod)
|
||||
///
|
||||
/// Market Applications:
|
||||
/// - Trend confirmation
|
||||
/// - Divergence analysis
|
||||
/// - Volume/price relationship
|
||||
/// - Support/resistance levels
|
||||
/// - Market reversals
|
||||
///
|
||||
/// Sources:
|
||||
/// Marc Chaikin - Original development
|
||||
/// https://www.investopedia.com/terms/c/chaikinoscillator.asp
|
||||
///
|
||||
/// Note: Positive values indicate buying pressure, while negative values indicate selling pressure
|
||||
/// </remarks>
|
||||
|
||||
[SkipLocalsInit]
|
||||
public sealed class Adosc : AbstractBase
|
||||
{
|
||||
private readonly int _longPeriod;
|
||||
private double _cumulativeAdl;
|
||||
private double _shortEma;
|
||||
private double _longEma;
|
||||
private readonly double _shortAlpha;
|
||||
private readonly double _longAlpha;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Adosc(int shortPeriod = 3, int longPeriod = 10)
|
||||
{
|
||||
_longPeriod = longPeriod;
|
||||
WarmupPeriod = longPeriod; // Need longer period for EMA calculation
|
||||
Name = $"ADOSC({shortPeriod},{_longPeriod})";
|
||||
_shortAlpha = 2.0 / (shortPeriod + 1);
|
||||
_longAlpha = 2.0 / (longPeriod + 1);
|
||||
Init();
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Adosc(object source, int shortPeriod = 3, int longPeriod = 10) : this(shortPeriod, longPeriod)
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new BarSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
_cumulativeAdl = 0;
|
||||
_shortEma = 0;
|
||||
_longEma = 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_lastValidValue = Value;
|
||||
_index++;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static double CalculateMoneyFlowMultiplier(double close, double high, double low)
|
||||
{
|
||||
double range = high - low;
|
||||
if (range > 0)
|
||||
{
|
||||
return ((close - low) - (high - close)) / range;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(BarInput.IsNew);
|
||||
|
||||
// Calculate Money Flow Multiplier
|
||||
double mfm = CalculateMoneyFlowMultiplier(BarInput.Close, BarInput.High, BarInput.Low);
|
||||
|
||||
// Calculate Money Flow Volume
|
||||
double mfv = mfm * BarInput.Volume;
|
||||
|
||||
// Update cumulative ADL
|
||||
_cumulativeAdl += mfv;
|
||||
|
||||
// Calculate EMAs
|
||||
if (_index <= _longPeriod)
|
||||
{
|
||||
// Initialize EMAs
|
||||
_shortEma = _cumulativeAdl;
|
||||
_longEma = _cumulativeAdl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Update EMAs
|
||||
_shortEma = (_shortAlpha * _cumulativeAdl) + ((1 - _shortAlpha) * _shortEma);
|
||||
_longEma = (_longAlpha * _cumulativeAdl) + ((1 - _longAlpha) * _longEma);
|
||||
|
||||
// Calculate ADOSC
|
||||
double adosc = _shortEma - _longEma;
|
||||
|
||||
IsHot = _index >= WarmupPeriod;
|
||||
return adosc;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// AOBV: Archer On-Balance Volume
|
||||
/// A modified version of the traditional On-Balance Volume (OBV) indicator that uses a more
|
||||
/// sophisticated method to determine buying and selling pressure. It considers both the
|
||||
/// closing price and the price range to provide a more nuanced view of volume flow.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The AOBV calculation process:
|
||||
/// 1. Determine price position within the day's range
|
||||
/// 2. Apply volume based on price position:
|
||||
/// - If close is in upper 1/3 of range: Add full volume
|
||||
/// - If close is in middle 1/3 of range: Add/subtract half volume
|
||||
/// - If close is in lower 1/3 of range: Subtract full volume
|
||||
///
|
||||
/// Key characteristics:
|
||||
/// - Volume-weighted measure
|
||||
/// - Cumulative indicator
|
||||
/// - No upper/lower bounds
|
||||
/// - More nuanced than traditional OBV
|
||||
/// - Considers price position in range
|
||||
///
|
||||
/// Formula:
|
||||
/// Range = High - Low
|
||||
/// UpperThird = High - (Range / 3)
|
||||
/// LowerThird = Low + (Range / 3)
|
||||
/// If Close >= UpperThird:
|
||||
/// AOBV = Previous AOBV + Volume
|
||||
/// Else if Close <= LowerThird:
|
||||
/// AOBV = Previous AOBV - Volume
|
||||
/// Else:
|
||||
/// If Close > Previous Close:
|
||||
/// AOBV = Previous AOBV + (Volume / 2)
|
||||
/// Else:
|
||||
/// AOBV = Previous AOBV - (Volume / 2)
|
||||
///
|
||||
/// Market Applications:
|
||||
/// - Trend confirmation
|
||||
/// - Volume analysis
|
||||
/// - Price/volume divergence
|
||||
/// - Support/resistance levels
|
||||
/// - Market participation
|
||||
///
|
||||
/// Sources:
|
||||
/// Steve Archer - Original development
|
||||
/// Technical Analysis of Stock Trends (Edwards, Magee)
|
||||
///
|
||||
/// Note: Provides a more detailed analysis of volume flow than traditional OBV
|
||||
/// </remarks>
|
||||
|
||||
[SkipLocalsInit]
|
||||
public sealed class Aobv : AbstractBase
|
||||
{
|
||||
private double _cumulativeAobv;
|
||||
private double _prevClose;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Aobv()
|
||||
{
|
||||
WarmupPeriod = 1;
|
||||
Name = "AOBV";
|
||||
Init();
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Aobv(object source) : this()
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new BarSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
_cumulativeAobv = 0;
|
||||
_prevClose = 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_lastValidValue = Value;
|
||||
_index++;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(BarInput.IsNew);
|
||||
|
||||
// Skip first period to establish previous close
|
||||
if (_index == 1)
|
||||
{
|
||||
_prevClose = BarInput.Close;
|
||||
return 0;
|
||||
}
|
||||
|
||||
double range = BarInput.High - BarInput.Low;
|
||||
if (range > 0)
|
||||
{
|
||||
double upperThird = BarInput.High - (range / 3);
|
||||
double lowerThird = BarInput.Low + (range / 3);
|
||||
|
||||
// Determine volume flow based on price position
|
||||
if (BarInput.Close >= upperThird)
|
||||
{
|
||||
_cumulativeAobv += BarInput.Volume;
|
||||
}
|
||||
else if (BarInput.Close <= lowerThird)
|
||||
{
|
||||
_cumulativeAobv -= BarInput.Volume;
|
||||
}
|
||||
else
|
||||
{
|
||||
// In middle third, use half volume based on close comparison
|
||||
_cumulativeAobv += (BarInput.Close > _prevClose) ?
|
||||
(BarInput.Volume / 2) : -(BarInput.Volume / 2);
|
||||
}
|
||||
}
|
||||
|
||||
_prevClose = BarInput.Close;
|
||||
|
||||
IsHot = _index >= WarmupPeriod;
|
||||
return _cumulativeAobv;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// CMF: Chaikin Money Flow
|
||||
/// A volume-weighted technical indicator that measures the amount of Money Flow Volume (MFV)
|
||||
/// over a specific period. Unlike ADL which is cumulative, CMF averages the Money Flow
|
||||
/// Volume over a specified period.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The CMF calculation process:
|
||||
/// 1. Calculates Money Flow Multiplier (MFM):
|
||||
/// MFM = ((Close - Low) - (High - Close)) / (High - Low)
|
||||
/// 2. Calculates Money Flow Volume (MFV):
|
||||
/// MFV = MFM × Volume
|
||||
/// 3. CMF = Sum(MFV) / Sum(Volume) over N periods
|
||||
///
|
||||
/// Key characteristics:
|
||||
/// - Oscillator between -1 and +1
|
||||
/// - Volume-weighted measure
|
||||
/// - Non-cumulative indicator
|
||||
/// - Default period is 20 days
|
||||
///
|
||||
/// Formula:
|
||||
/// MFM = ((Close - Low) - (High - Close)) / (High - Low)
|
||||
/// MFV = MFM × Volume
|
||||
/// CMF = Sum(MFV over N periods) / Sum(Volume over N periods)
|
||||
///
|
||||
/// Market Applications:
|
||||
/// - Trend confirmation
|
||||
/// - Volume analysis
|
||||
/// - Price/volume divergence
|
||||
/// - Support/resistance levels
|
||||
/// - Market participation
|
||||
///
|
||||
/// Sources:
|
||||
/// Marc Chaikin - Original development
|
||||
/// https://www.investopedia.com/terms/c/chaikinmoneyflow.asp
|
||||
///
|
||||
/// Note: Values above zero indicate buying pressure, while values below zero indicate selling pressure
|
||||
/// </remarks>
|
||||
|
||||
[SkipLocalsInit]
|
||||
public sealed class Cmf : AbstractBase
|
||||
{
|
||||
private readonly int _period;
|
||||
private readonly double[] _mfv;
|
||||
private readonly double[] _volume;
|
||||
private int _position;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Cmf(int period = 20)
|
||||
{
|
||||
_period = period;
|
||||
WarmupPeriod = period;
|
||||
Name = $"CMF({_period})";
|
||||
_mfv = new double[period];
|
||||
_volume = new double[period];
|
||||
Init();
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Cmf(object source, int period = 20) : this(period)
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new BarSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
_position = 0;
|
||||
Array.Clear(_mfv, 0, _mfv.Length);
|
||||
Array.Clear(_volume, 0, _volume.Length);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_lastValidValue = Value;
|
||||
_index++;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static double CalculateMoneyFlowMultiplier(double close, double high, double low)
|
||||
{
|
||||
double range = high - low;
|
||||
if (range > 0)
|
||||
{
|
||||
return ((close - low) - (high - close)) / range;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(BarInput.IsNew);
|
||||
|
||||
// Calculate Money Flow Multiplier
|
||||
double mfm = CalculateMoneyFlowMultiplier(BarInput.Close, BarInput.High, BarInput.Low);
|
||||
|
||||
// Calculate Money Flow Volume
|
||||
double currentMfv = mfm * BarInput.Volume;
|
||||
|
||||
// Update circular buffers
|
||||
_mfv[_position] = currentMfv;
|
||||
_volume[_position] = BarInput.Volume;
|
||||
_position = (_position + 1) % _period;
|
||||
|
||||
// Calculate CMF
|
||||
double sumMfv = 0;
|
||||
double sumVolume = 0;
|
||||
for (int i = 0; i < _period; i++)
|
||||
{
|
||||
sumMfv += _mfv[i];
|
||||
sumVolume += _volume[i];
|
||||
}
|
||||
|
||||
double cmf = Math.Abs(sumVolume) > double.Epsilon ? sumMfv / sumVolume : 0;
|
||||
IsHot = _index >= WarmupPeriod;
|
||||
return cmf;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// EOM: Ease of Movement
|
||||
/// A volume-based technical indicator that relates price change to volume, showing the
|
||||
/// relationship between price change and volume. It emphasizes days where price changes
|
||||
/// are accomplished with minimal volume and minimizes days where large volume generates
|
||||
/// small price changes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The EOM calculation process:
|
||||
/// 1. Calculate the distance moved:
|
||||
/// Distance = ((High + Low)/2 - (Prior High + Prior Low)/2)
|
||||
/// 2. Calculate the Box Ratio:
|
||||
/// BoxRatio = Volume / (High - Low)
|
||||
/// 3. Calculate single-period EMV:
|
||||
/// EMV = Distance / BoxRatio
|
||||
/// 4. Smooth EMV using simple moving average (optional)
|
||||
///
|
||||
/// Key characteristics:
|
||||
/// - Volume-weighted measure
|
||||
/// - Oscillates around zero
|
||||
/// - Shows ease of price movement
|
||||
/// - Default period is 14 days
|
||||
///
|
||||
/// Formula:
|
||||
/// Distance = ((H + L)/2 - (pH + pL)/2)
|
||||
/// BoxRatio = Volume / (High - Low)
|
||||
/// EMV = Distance / BoxRatio
|
||||
/// EOM = SMA(EMV, period)
|
||||
///
|
||||
/// Market Applications:
|
||||
/// - Trend strength analysis
|
||||
/// - Volume/price relationship
|
||||
/// - Support/resistance breakouts
|
||||
/// - Market momentum
|
||||
/// - Divergence identification
|
||||
///
|
||||
/// Sources:
|
||||
/// Richard W. Arms Jr. - Original development
|
||||
/// https://www.investopedia.com/terms/e/easeofmovement.asp
|
||||
///
|
||||
/// Note: Positive values suggest prices are rising with light volume (bullish),
|
||||
/// while negative values suggest prices are falling with light volume (bearish)
|
||||
/// </remarks>
|
||||
|
||||
[SkipLocalsInit]
|
||||
public sealed class Eom : AbstractBase
|
||||
{
|
||||
private readonly int _period;
|
||||
private readonly double[] _emv;
|
||||
private int _position;
|
||||
private double _prevMidpoint;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Eom(int period = 14)
|
||||
{
|
||||
_period = period;
|
||||
WarmupPeriod = period + 1; // Need one extra period for previous midpoint
|
||||
Name = $"EOM({_period})";
|
||||
_emv = new double[period];
|
||||
Init();
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Eom(object source, int period = 14) : this(period)
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new BarSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
_position = 0;
|
||||
_prevMidpoint = 0;
|
||||
Array.Clear(_emv, 0, _emv.Length);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_lastValidValue = Value;
|
||||
_index++;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(BarInput.IsNew);
|
||||
|
||||
double midpoint = (BarInput.High + BarInput.Low) / 2;
|
||||
double boxRatio = BarInput.Volume / (BarInput.High - BarInput.Low + double.Epsilon); // Avoid division by zero
|
||||
|
||||
// Skip first period to establish previous midpoint
|
||||
if (_index == 1)
|
||||
{
|
||||
_prevMidpoint = midpoint;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Calculate distance moved
|
||||
double distance = midpoint - _prevMidpoint;
|
||||
|
||||
// Calculate EMV for this period
|
||||
double emv = distance / boxRatio * 10000; // Multiply by 10000 to make values more readable
|
||||
|
||||
// Store in circular buffer
|
||||
_emv[_position] = emv;
|
||||
_position = (_position + 1) % _period;
|
||||
|
||||
// Calculate EOM (simple moving average of EMV)
|
||||
double sum = 0;
|
||||
for (int i = 0; i < _period; i++)
|
||||
{
|
||||
sum += _emv[i];
|
||||
}
|
||||
double eom = sum / _period;
|
||||
|
||||
// Store current midpoint for next calculation
|
||||
_prevMidpoint = midpoint;
|
||||
|
||||
IsHot = _index >= WarmupPeriod;
|
||||
return eom;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// KVO: Klinger Volume Oscillator
|
||||
/// A volume-based technical indicator that compares volume to price movement to identify
|
||||
/// long-term trends and potential reversals. It helps determine the long-term money flow
|
||||
/// while remaining sensitive to short-term fluctuations.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The KVO calculation process:
|
||||
/// 1. Calculate Trend:
|
||||
/// Trend = Current DM > Previous DM ? +1 : -1
|
||||
/// 2. Calculate Volume Force (VF):
|
||||
/// VF = Volume * abs(ROC) * Trend * 100
|
||||
/// 3. Calculate two EMAs of VF and their difference:
|
||||
/// Signal = EMA(VF, shortPeriod) - EMA(VF, longPeriod)
|
||||
///
|
||||
/// Key characteristics:
|
||||
/// - Volume-weighted measure
|
||||
/// - Oscillates around zero
|
||||
/// - Uses two different time periods
|
||||
/// - Default periods are 34 and 55 days
|
||||
/// - Shows volume force and price direction
|
||||
///
|
||||
/// Formula:
|
||||
/// DM = (H + L + C) / 3
|
||||
/// Trend = DM > Previous DM ? +1 : -1
|
||||
/// VF = Volume * abs(ROC) * Trend * 100
|
||||
/// KVO = EMA(VF, shortPeriod) - EMA(VF, longPeriod)
|
||||
///
|
||||
/// Market Applications:
|
||||
/// - Trend confirmation
|
||||
/// - Divergence analysis
|
||||
/// - Volume/price relationship
|
||||
/// - Support/resistance levels
|
||||
/// - Market reversals
|
||||
///
|
||||
/// Sources:
|
||||
/// Stephen Klinger - Original development
|
||||
/// https://www.investopedia.com/terms/k/klingeroscillator.asp
|
||||
///
|
||||
/// Note: Positive values indicate buying pressure, while negative values indicate selling pressure
|
||||
/// </remarks>
|
||||
|
||||
[SkipLocalsInit]
|
||||
public sealed class Kvo : AbstractBase
|
||||
{
|
||||
private readonly int _longPeriod;
|
||||
private double _prevDm;
|
||||
private double _shortEma;
|
||||
private double _longEma;
|
||||
private readonly double _shortAlpha;
|
||||
private readonly double _longAlpha;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Kvo(int shortPeriod = 34, int longPeriod = 55)
|
||||
{
|
||||
_longPeriod = longPeriod;
|
||||
WarmupPeriod = longPeriod + 1; // Need one extra period for previous DM
|
||||
Name = $"KVO({shortPeriod},{_longPeriod})";
|
||||
_shortAlpha = 2.0 / (shortPeriod + 1);
|
||||
_longAlpha = 2.0 / (longPeriod + 1);
|
||||
Init();
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Kvo(object source, int shortPeriod = 34, int longPeriod = 55) : this(shortPeriod, longPeriod)
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new BarSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
_prevDm = 0;
|
||||
_shortEma = 0;
|
||||
_longEma = 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_lastValidValue = Value;
|
||||
_index++;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(BarInput.IsNew);
|
||||
|
||||
// Calculate Daily Mean
|
||||
double dm = (BarInput.High + BarInput.Low + BarInput.Close) / 3;
|
||||
|
||||
// Skip first period to establish previous DM
|
||||
if (_index == 1)
|
||||
{
|
||||
_prevDm = dm;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Calculate Trend
|
||||
int trend = dm > _prevDm ? 1 : -1;
|
||||
|
||||
// Calculate Rate of Change
|
||||
double roc = Math.Abs(dm - _prevDm) / _prevDm;
|
||||
|
||||
// Calculate Volume Force
|
||||
double vf = BarInput.Volume * roc * trend * 100;
|
||||
|
||||
// Calculate EMAs
|
||||
if (_index <= _longPeriod)
|
||||
{
|
||||
// Initialize EMAs
|
||||
_shortEma = vf;
|
||||
_longEma = vf;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Update EMAs
|
||||
_shortEma = (_shortAlpha * vf) + ((1 - _shortAlpha) * _shortEma);
|
||||
_longEma = (_longAlpha * vf) + ((1 - _longAlpha) * _longEma);
|
||||
}
|
||||
|
||||
// Store current DM for next calculation
|
||||
_prevDm = dm;
|
||||
|
||||
// Calculate KVO
|
||||
double kvo = _shortEma - _longEma;
|
||||
|
||||
IsHot = _index >= WarmupPeriod;
|
||||
return kvo;
|
||||
}
|
||||
}
|
||||
+8
-6
@@ -1,9 +1,11 @@
|
||||
ADL - Chaikin Accumulation Distribution Line
|
||||
ADOSC - Chaikin Accumulation Distribution Oscillator
|
||||
AOBV - Archer On-Balance Volume
|
||||
CMF - Chaikin Money Flow
|
||||
EOM - Ease of Movement
|
||||
KVO - Klinger Volume Oscillator
|
||||
# Volume indicators
|
||||
|
||||
✔️ ADL - Chaikin Accumulation Distribution Line
|
||||
✔️ ADOSC - Chaikin Accumulation Distribution Oscillator
|
||||
✔️ AOBV - Archer On-Balance Volume
|
||||
✔️ CMF - Chaikin Money Flow
|
||||
✔️ EOM - Ease of Movement
|
||||
✔️ KVO - Klinger Volume Oscillator
|
||||
MFI - Money Flow Index
|
||||
NVI - Negative Volume Index
|
||||
OBV - On-Balance Volume
|
||||
|
||||
Reference in New Issue
Block a user