From c58a054a2873b4118e668b24b63e938dada4f257 Mon Sep 17 00:00:00 2001
From: Miha Kralj <31756078+mihakralj@users.noreply.github.com>
Date: Mon, 4 Nov 2024 14:28:47 -0800
Subject: [PATCH 1/2] Add DOSC - Derivative Oscillator
---
For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/mihakralj/QuanTAlib?shareId=XXXX-XXXX-XXXX-XXXX).
---
Tests/test_eventing.cs | 3 +-
Tests/test_updates_oscillators.cs | 632 +++++++++++++++---------------
docs/indicators/indicators.md | 6 +-
lib/oscillators/Dosc.cs | 74 ++++
lib/oscillators/_list.md | 64 +--
5 files changed, 435 insertions(+), 344 deletions(-)
create mode 100644 lib/oscillators/Dosc.cs
diff --git a/Tests/test_eventing.cs b/Tests/test_eventing.cs
index d997b81b..5406876e 100644
--- a/Tests/test_eventing.cs
+++ b/Tests/test_eventing.cs
@@ -113,7 +113,8 @@ public class EventingTests
// Volatility indicators (bar-based)
("Atr", new Atr(14), new Atr(barInput, 14)),
// Oscillators (bar-based)
- ("Chop", new Chop(14), new Chop(barInput, 14))
+ ("Chop", new Chop(14), new Chop(barInput, 14)),
+ ("Dosc", new Dosc(), new Dosc(barInput))
};
// Generate 200 random values and feed them to indicators
diff --git a/Tests/test_updates_oscillators.cs b/Tests/test_updates_oscillators.cs
index cbc20ec1..b2e369e5 100644
--- a/Tests/test_updates_oscillators.cs
+++ b/Tests/test_updates_oscillators.cs
@@ -1,308 +1,324 @@
-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
- }
-
- 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 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);
- }
-
- [Fact]
- public void Ao_Update()
- {
- var indicator = new Ao();
- 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 Ac_Update()
- {
- var indicator = new Ac();
- 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 Aroon_Update()
- {
- var indicator = new Aroon(period: 25);
- 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 Bop_Update()
- {
- var indicator = new Bop();
- 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 Cci_Update()
- {
- var indicator = new Cci(period: 20);
- 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 Cfo_Update()
- {
- var indicator = new Cfo(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 Chop_Update()
- {
- var indicator = new Chop(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 Cog_Update()
- {
- var indicator = new Cog(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 Smi_Update()
- {
- var indicator = new Smi(period: 10, smooth1: 3, smooth2: 3);
- 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 Srsi_Update()
- {
- var indicator = new Srsi(rsiPeriod: 14, stochPeriod: 14, smoothK: 3, smoothD: 3);
- 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 Stc_Update()
- {
- var indicator = new Stc(cyclePeriod: 10, fastPeriod: 23, slowPeriod: 50);
- 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 Stoch_Update()
- {
- var indicator = new Stoch(period: 14, smoothK: 3, smoothD: 3);
- 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 Tsi_Update()
- {
- var indicator = new Tsi(firstPeriod: 25, secondPeriod: 13);
- 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 Uo_Update()
- {
- var indicator = new Uo(period1: 7, period2: 14, period3: 28);
- 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 Willr_Update()
- {
- var indicator = new Willr(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);
- }
-}
+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
+ }
+
+ 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 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);
+ }
+
+ [Fact]
+ public void Ao_Update()
+ {
+ var indicator = new Ao();
+ 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 Ac_Update()
+ {
+ var indicator = new Ac();
+ 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 Aroon_Update()
+ {
+ var indicator = new Aroon(period: 25);
+ 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 Bop_Update()
+ {
+ var indicator = new Bop();
+ 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 Cci_Update()
+ {
+ var indicator = new Cci(period: 20);
+ 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 Cfo_Update()
+ {
+ var indicator = new Cfo(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 Chop_Update()
+ {
+ var indicator = new Chop(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 Cog_Update()
+ {
+ var indicator = new Cog(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 Smi_Update()
+ {
+ var indicator = new Smi(period: 10, smooth1: 3, smooth2: 3);
+ 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 Srsi_Update()
+ {
+ var indicator = new Srsi(rsiPeriod: 14, stochPeriod: 14, smoothK: 3, smoothD: 3);
+ 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 Stc_Update()
+ {
+ var indicator = new Stc(cyclePeriod: 10, fastPeriod: 23, slowPeriod: 50);
+ 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 Stoch_Update()
+ {
+ var indicator = new Stoch(period: 14, smoothK: 3, smoothD: 3);
+ 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 Tsi_Update()
+ {
+ var indicator = new Tsi(firstPeriod: 25, secondPeriod: 13);
+ 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 Uo_Update()
+ {
+ var indicator = new Uo(period1: 7, period2: 14, period3: 28);
+ 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 Willr_Update()
+ {
+ var indicator = new Willr(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 Dosc_Update()
+ {
+ var indicator = new Dosc();
+ 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);
+ }
+}
diff --git a/docs/indicators/indicators.md b/docs/indicators/indicators.md
index 8c607e10..3916949a 100644
--- a/docs/indicators/indicators.md
+++ b/docs/indicators/indicators.md
@@ -5,12 +5,12 @@
| Basic Transforms | 6 of 6 | 100% |
| Averages & Trends | 33 of 33 | 100% |
| Momentum | 16 of 16 | 100% |
-| Oscillators | 20 of 29 | 69% |
+| Oscillators | 21 of 29 | 72% |
| Volatility | 24 of 35 | 69% |
| Volume | 15 of 19 | 79% |
| Numerical Analysis | 13 of 19 | 68% |
| Errors | 16 of 16 | 100% |
-| **Total** | **143 of 173** | **83%** |
+| **Total** | **144 of 173** | **83%** |
|Technical Indicator Name| Class Name|
|-----------|:----------:|
@@ -85,7 +85,6 @@
|COPPOCK - Coppock Curve|`Coppock`|
|CRSI - Connor RSI|`Crsi`|
|🚧 CTI - Ehler's Correlation Trend Indicator|`Cti`|
-|🚧 DOSC - Derivative Oscillator|`Dosc`|
|🚧 EFI - Elder Ray's Force Index|`Efi`|
|🚧 FISHER - Fisher Transform|`Fisher`|
|🚧 FOSC - Forecast Oscillator|`Fosc`|
@@ -102,6 +101,7 @@
|TSI - True Strength Index|`Tsi`|
|UO - Ultimate Oscillator|`Uo`|
|WILLR - Larry Williams' %R|`Willr`|
+|DOSC - Derivative Oscillator|`Dosc`|
|**VOLATILITY INDICATORS**||
|ADR - Average Daily Range|`Adr`|
|AP - Andrew's Pitchfork|`Ap`|
diff --git a/lib/oscillators/Dosc.cs b/lib/oscillators/Dosc.cs
new file mode 100644
index 00000000..906b2288
--- /dev/null
+++ b/lib/oscillators/Dosc.cs
@@ -0,0 +1,74 @@
+using System.Runtime.CompilerServices;
+namespace QuanTAlib;
+
+///
+/// DOSC: Derivative Oscillator
+/// A momentum indicator that combines the Relative Strength Index (RSI) and the Moving Average Convergence Divergence (MACD) to identify potential trend reversals.
+///
+///
+/// The DOSC calculation process:
+/// 1. Calculate the RSI
+/// 2. Calculate the MACD of the RSI
+/// 3. Calculate the signal line (SMA) of the MACD
+/// 4. Subtract the signal line from the MACD to get the DOSC
+///
+/// Key characteristics:
+/// - Combines RSI and MACD
+/// - 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:
+/// DOSC = MACD(RSI) - Signal(MACD(RSI))
+///
+/// Sources:
+/// Original development
+/// https://www.investopedia.com/terms/d/derivativeoscillator.asp
+///
+[SkipLocalsInit]
+public sealed class Dosc : AbstractBase
+{
+ private readonly Rsi _rsi;
+ private readonly Macd _macd;
+ private readonly Sma _signal;
+
+ /// The data source object that publishes updates.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Dosc(object source) : this()
+ {
+ var pubEvent = source.GetType().GetEvent("Pub");
+ pubEvent?.AddEventHandler(source, new BarSignal(Sub));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Dosc()
+ {
+ _rsi = new Rsi();
+ _macd = new Macd();
+ _signal = new Sma(9);
+ WarmupPeriod = 34; // RSI requires 14 periods + MACD requires 26 periods + 9 for signal line
+ Name = "DOSC";
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected override void ManageState(bool isNew)
+ {
+ if (isNew)
+ {
+ _index++;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected override double Calculation()
+ {
+ ManageState(BarInput.IsNew);
+ var rsi = _rsi.Calc(BarInput.Close, BarInput.IsNew);
+ var macd = _macd.Calc(rsi, BarInput.IsNew);
+ _signal.Calc(macd, BarInput.IsNew);
+
+ return macd - _signal.Value;
+ }
+}
diff --git a/lib/oscillators/_list.md b/lib/oscillators/_list.md
index 0cd477ee..72b74c86 100644
--- a/lib/oscillators/_list.md
+++ b/lib/oscillators/_list.md
@@ -1,32 +1,32 @@
-# Oscillators indicators
-Done: 20, Todo: 9
-
-✔️ AC - Acceleration Oscillator
-✔️ AO - Awesome Oscillator
-✔️ AROON - Aroon oscillator (Up, Down)
-✔️ BOP - Balance of Power
-✔️ CCI - Commodity Channel Index
-✔️ CFO - Chande Forcast Oscillator
-✔️ CHOP - Choppiness Index
-✔️ CMO - Chande Momentum Oscillator
-✔️ COG - Ehler's Center of Gravity
-✔️ COPPOCK - Coppock Curve
-✔️ CRSI - Connor RSI
-CTI - Ehler's Correlation Trend Indicator
-DOSC - Derivative Oscillator
-EFI - Elder Ray's Force Index
-FISHER - Fisher Transform
-FOSC - Forecast Oscillator
-*GATOR - Williams Alliator Oscillator (Upper Jaw, Lower Jaw, Teeth)
-*KDJ - KDJ Indicator (K, D, J lines)
-KRI - Kairi Relative Index
-✔️ RSI - Relative Strength Index
-✔️ RSX - Jurik Trend Strength Index
-*RVGI - Relative Vigor Index (RVGI, Signal)
-✔️ SMI - Stochastic Momentum Index
-✔️ SRSI - Stochastic RSI (SRSI, Signal)
-✔️ STC - Schaff Trend Cycle
-✔️ STOCH - Stochastic Oscillator (%K, %D)
-✔️ TSI - True Strength Index
-✔️ UO - Ultimate Oscillator
-✔️ WILLR - Larry Williams' %R
+# Oscillators indicators
+Done: 21, Todo: 8
+
+✔️ AC - Acceleration Oscillator
+✔️ AO - Awesome Oscillator
+✔️ AROON - Aroon oscillator (Up, Down)
+✔️ BOP - Balance of Power
+✔️ CCI - Commodity Channel Index
+✔️ CFO - Chande Forcast Oscillator
+✔️ CHOP - Choppiness Index
+✔️ CMO - Chande Momentum Oscillator
+✔️ COG - Ehler's Center of Gravity
+✔️ COPPOCK - Coppock Curve
+✔️ CRSI - Connor RSI
+CTI - Ehler's Correlation Trend Indicator
+✔️ DOSC - Derivative Oscillator
+EFI - Elder Ray's Force Index
+FISHER - Fisher Transform
+FOSC - Forecast Oscillator
+*GATOR - Williams Alliator Oscillator (Upper Jaw, Lower Jaw, Teeth)
+*KDJ - KDJ Indicator (K, D, J lines)
+KRI - Kairi Relative Index
+✔️ RSI - Relative Strength Index
+✔️ RSX - Jurik Trend Strength Index
+*RVGI - Relative Vigor Index (RVGI, Signal)
+✔️ SMI - Stochastic Momentum Index
+✔️ SRSI - Stochastic RSI (SRSI, Signal)
+✔️ STC - Schaff Trend Cycle
+✔️ STOCH - Stochastic Oscillator (%K, %D)
+✔️ TSI - True Strength Index
+✔️ UO - Ultimate Oscillator
+✔️ WILLR - Larry Williams' %R
From 5b333bd2ec694125aaa3a1645492f12fad61838e Mon Sep 17 00:00:00 2001
From: Miha Kralj
Date: Mon, 4 Nov 2024 14:57:02 -0800
Subject: [PATCH 2/2] semver fix
---
.github/workflows/Publish.yml | 1 +
Tests/test_updates_oscillators.cs | 4 ++--
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/Publish.yml b/.github/workflows/Publish.yml
index f68a9b89..8c2be816 100644
--- a/.github/workflows/Publish.yml
+++ b/.github/workflows/Publish.yml
@@ -252,6 +252,7 @@ jobs:
(github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev')) ||
github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
+
steps:
- name: Checkout repository
uses: actions/checkout@v4
diff --git a/Tests/test_updates_oscillators.cs b/Tests/test_updates_oscillators.cs
index b2e369e5..c1630815 100644
--- a/Tests/test_updates_oscillators.cs
+++ b/Tests/test_updates_oscillators.cs
@@ -20,8 +20,8 @@ public class OscillatorsUpdateTests
private TBar GetRandomBar(bool IsNew)
{
double open = GetRandomDouble();
- double high = open + Math.abs(GetRandomDouble());
- double low = open - Math.abs(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);
}