-
Notifications
You must be signed in to change notification settings - Fork 248
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add Detrended Price Oscillator (#552)
+semver: minor
- Loading branch information
1 parent
8fbafa6
commit 870ee07
Showing
11 changed files
with
797 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
--- | ||
title: Detrended Price Oscillator (DPO) | ||
permalink: /indicators/Dpo/ | ||
layout: default | ||
--- | ||
|
||
# {{ page.title }} | ||
|
||
[Detrended Price Oscillator](https://en.wikipedia.org/wiki/Detrended_price_oscillator) depicts the difference between price and an offset simple moving average. It is used to identify trend cycles and duration. | ||
[[Discuss] :speech_balloon:]({{site.github.repository_url}}/discussions/551 "Community discussion about this indicator") | ||
|
||
![image]({{site.baseurl}}/assets/charts/Dpo.png) | ||
|
||
```csharp | ||
// usage | ||
IEnumerable<DpoResult> results = | ||
quotes.GetDpo(lookbackPeriods); | ||
``` | ||
|
||
## Parameters | ||
|
||
| name | type | notes | ||
| -- |-- |-- | ||
| `lookbackPeriods` | int | Number of periods (`N`) in the moving average. Must be greater than 0. | ||
|
||
### Historical quotes requirements | ||
|
||
You must have at least `N` historical quotes. | ||
|
||
`quotes` is an `IEnumerable<TQuote>` collection of historical price quotes. It should have a consistent frequency (day, hour, minute, etc). See [the Guide]({{site.baseurl}}/guide#historical-quotes) for more information. | ||
|
||
## Response | ||
|
||
```csharp | ||
IEnumerable<DpoResult> | ||
``` | ||
|
||
- This method returns a time series of all available indicator values for the `quotes` provided. | ||
- It always returns the same number of elements as there are in the historical quotes. | ||
- It does not return a single incremental indicator value. | ||
- The first `N/2-2` and last `N/2+1` periods will be `null` since they cannot be calculated. | ||
|
||
### DpoResult | ||
|
||
| name | type | notes | ||
| -- |-- |-- | ||
| `Date` | DateTime | Date | ||
| `Sma` | decimal | Simple moving average offset by `N/2+1` periods | ||
| `Dpo` | decimal | Detrended Price Oscillator (DPO) | ||
|
||
### Utilities | ||
|
||
- [.ConvertToQuotes()]({{site.baseurl}}/utilities#convert-to-quotes) | ||
- [.Find(lookupDate)]({{site.baseurl}}/utilities#find-indicator-result-by-date) | ||
- [.RemoveWarmupPeriods(qty)]({{site.baseurl}}/utilities#remove-warmup-periods) | ||
|
||
See [Utilities and Helpers]({{site.baseurl}}/utilities#utilities-for-indicator-results) for more information. | ||
|
||
## Example | ||
|
||
```csharp | ||
// fetch historical quotes from your feed (your method) | ||
IEnumerable<Quote> quotes = GetHistoryFromFeed("SPY"); | ||
|
||
// calculate | ||
IEnumerable<DpoResult> results = quotes.GetDpo(14); | ||
``` |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
using System; | ||
|
||
namespace Skender.Stock.Indicators | ||
{ | ||
[Serializable] | ||
public class DpoResult : ResultBase | ||
{ | ||
public decimal? Sma { get; set; } | ||
public decimal? Dpo { get; set; } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
|
||
namespace Skender.Stock.Indicators | ||
{ | ||
public static partial class Indicator | ||
{ | ||
// DETRENDED PRICE OSCILLATOR (DPO) | ||
/// <include file='./info.xml' path='indicator/*' /> | ||
/// | ||
public static IEnumerable<DpoResult> GetDpo<TQuote>( | ||
this IEnumerable<TQuote> quotes, | ||
int lookbackPeriods) | ||
where TQuote : IQuote | ||
{ | ||
|
||
// sort quotes | ||
List<TQuote> quotesList = quotes.Sort(); | ||
|
||
// check parameter arguments | ||
ValidateDpo(quotes, lookbackPeriods); | ||
|
||
// initialize | ||
int size = quotesList.Count; | ||
int offset = lookbackPeriods / 2 + 1; | ||
List<SmaResult> sma = quotes.GetSma(lookbackPeriods).ToList(); | ||
List<DpoResult> results = new(size); | ||
|
||
// roll through quotes | ||
for (int i = 0; i < size; i++) | ||
{ | ||
TQuote q = quotesList[i]; | ||
|
||
DpoResult r = new() | ||
{ | ||
Date = q.Date | ||
}; | ||
results.Add(r); | ||
|
||
if (i >= lookbackPeriods - offset - 1 && i < size - offset) | ||
{ | ||
SmaResult s = sma[i + offset]; | ||
r.Sma = s.Sma; | ||
r.Dpo = q.Close - s.Sma; | ||
} | ||
} | ||
|
||
return results; | ||
} | ||
|
||
|
||
// convert to quotes | ||
/// <include file='../../_Common/Results/info.xml' path='info/type[@name="Convert"]/*' /> | ||
/// | ||
public static IEnumerable<Quote> ConvertToQuotes( | ||
this IEnumerable<DpoResult> results) | ||
{ | ||
return results | ||
.Where(x => x.Dpo != null) | ||
.Select(x => new Quote | ||
{ | ||
Date = x.Date, | ||
Open = (decimal)x.Dpo, | ||
High = (decimal)x.Dpo, | ||
Low = (decimal)x.Dpo, | ||
Close = (decimal)x.Dpo, | ||
Volume = (decimal)x.Dpo | ||
}) | ||
.ToList(); | ||
} | ||
|
||
|
||
// parameter validation | ||
private static void ValidateDpo<TQuote>( | ||
IEnumerable<TQuote> quotes, | ||
int lookbackPeriods) | ||
where TQuote : IQuote | ||
{ | ||
|
||
// check parameter arguments | ||
if (lookbackPeriods <= 0) | ||
{ | ||
throw new ArgumentOutOfRangeException(nameof(lookbackPeriods), lookbackPeriods, | ||
"Lookback periods must be greater than 0 for DPO."); | ||
} | ||
|
||
// check quotes | ||
int qtyHistory = quotes.Count(); | ||
int minHistory = lookbackPeriods; | ||
if (qtyHistory < minHistory) | ||
{ | ||
string message = string.Format(EnglishCulture, | ||
"Insufficient quotes provided for Detrended Price Oscillator. " + | ||
"You provided {0} periods of quotes when at least {1} are required.", | ||
qtyHistory, minHistory); | ||
|
||
throw new BadQuotesException(nameof(quotes), message); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
|
||
<indicator> | ||
<summary> | ||
Detrended Price Oscillator (DPO) depicts the difference between price and an offset simple moving average. | ||
See | ||
<see href="https://daveskender.github.io/Stock.Indicators/indicators/Dpo/#content">documentation</see> | ||
for more information. | ||
</summary> | ||
<typeparam name="TQuote">Configurable Quote type. See Guide for more information.</typeparam> | ||
<param name="quotes">Historical price quotes.</param> | ||
<param name="lookbackPeriods">Number of periods in the lookback window.</param> | ||
<returns>Time series of DPO values.</returns> | ||
<exception cref="ArgumentOutOfRangeException">Invalid parameter value provided.</exception> | ||
<exception cref="BadQuotesException">Insufficient quotes provided.</exception> | ||
</indicator> |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Linq; | ||
using Microsoft.VisualStudio.TestTools.UnitTesting; | ||
using Skender.Stock.Indicators; | ||
|
||
namespace Internal.Tests | ||
{ | ||
[TestClass] | ||
public class Dpo : TestBase | ||
{ | ||
[TestMethod] | ||
public void Standard() | ||
{ | ||
List<DpoResult> act = quotes.GetDpo(14) | ||
.ToList(); | ||
|
||
// get test data | ||
List<DpoResult> exp = File.ReadAllLines("A-D/Dpo/data.csv") | ||
.Skip(1) | ||
.Select(t => | ||
{ | ||
string[] csv = t.Split(","); | ||
return new DpoResult | ||
{ | ||
Date = Convert.ToDateTime(csv[1], englishCulture), | ||
Sma = decimal.TryParse(csv[6], out decimal sma) ? sma : null, | ||
Dpo = decimal.TryParse(csv[7], out decimal dpo) ? dpo : null | ||
}; | ||
}) | ||
.ToList(); | ||
|
||
// assertions | ||
Assert.AreEqual(exp.Count, act.Count); | ||
|
||
// compare all values | ||
for (int i = 0; i < exp.Count; i++) | ||
{ | ||
DpoResult e = exp[i]; | ||
DpoResult a = act[i]; | ||
|
||
Assert.AreEqual(e.Date, a.Date); | ||
Assert.AreEqual(e.Sma, a.Sma == null | ||
? a.Sma | ||
: Math.Round((decimal)a.Sma, 5), | ||
$"at index {i}"); | ||
Assert.AreEqual(e.Dpo, a.Dpo == null | ||
? a.Dpo | ||
: Math.Round((decimal)a.Dpo, 5), | ||
$"at index {i}"); | ||
} | ||
} | ||
|
||
[TestMethod] | ||
public void ConvertToQuotes() | ||
{ | ||
List<Quote> newQuotes = quotes.GetDpo(14) | ||
.ConvertToQuotes() | ||
.ToList(); | ||
|
||
Assert.AreEqual(489, newQuotes.Count); | ||
|
||
Quote q = newQuotes.LastOrDefault(); | ||
Assert.AreEqual(2.18214m, Math.Round(q.Close, 5)); | ||
} | ||
|
||
[TestMethod] | ||
public void BadData() | ||
{ | ||
IEnumerable<DpoResult> r = badQuotes.GetDpo(5); | ||
Assert.AreEqual(502, r.Count()); | ||
} | ||
|
||
[TestMethod] | ||
public void Exceptions() | ||
{ | ||
// bad SMA period | ||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => | ||
quotes.GetDpo(0)); | ||
|
||
// insufficient quotes | ||
Assert.ThrowsException<BadQuotesException>(() => | ||
Indicator.GetDpo(TestData.GetDefault(10), 11)); | ||
} | ||
} | ||
} |
Oops, something went wrong.