-
Notifications
You must be signed in to change notification settings - Fork 250
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* adding ultimate-oscillator indicator
- Loading branch information
Showing
7 changed files
with
260 additions
and
0 deletions.
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
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,53 @@ | ||
# Ultimate Oscillator | ||
|
||
[Ultimate Oscillator](https://en.wikipedia.org/wiki/Ultimate_oscillator) uses several lookback periods to weigh buying power against true range price to produce on oversold / overbought oscillator. | ||
|
||
![image](chart.png) | ||
|
||
```csharp | ||
// usage | ||
IEnumerable<UltimateResult> results = Indicator.GetUltimate(history, shortPeriod, middlePeriod, longPeriod); | ||
``` | ||
|
||
## Parameters | ||
|
||
| name | type | notes | ||
| -- |-- |-- | ||
| `history` | IEnumerable\<[Quote](../../docs/GUIDE.md#quote)\> | Historical Quotes data should be at any consistent frequency (day, hour, minute, etc). You must supply at least `L+1` periods of `history`. | ||
| `shortPeriod` | int | Number of periods (`S`) in the short lookback. Must be greater than 0. | ||
| `middlePeriod` | int | Number of periods (`M`) in the middle lookback. Must be greater than `S`. | ||
| `longPeriod` | int | Number of periods (`L`) in the long lookback. Must be greater than `M`. | ||
|
||
## Response | ||
|
||
```csharp | ||
IEnumerable<UltimateResult> | ||
``` | ||
|
||
The first `L-1` periods will have `null` Ultimate values since there's not enough data to calculate. We always return the same number of elements as there are in the historical quotes. | ||
|
||
### UltimateResult | ||
|
||
| name | type | notes | ||
| -- |-- |-- | ||
| `Date` | DateTime | Date | ||
| `Ultimate` | decimal | Simple moving average for `N` lookback periods | ||
|
||
## Example | ||
|
||
```csharp | ||
// fetch historical quotes from your favorite feed, in Quote format | ||
IEnumerable<Quote> history = GetHistoryFromFeed("MSFT"); | ||
|
||
// calculate 20-period Ultimate | ||
IEnumerable<UltimateResult> results = Indicator.GetUltimate(history,7,14,28); | ||
|
||
// use results as needed | ||
DateTime evalDate = DateTime.Parse("12/31/2018"); | ||
UltimateResult result = results.Where(x=>x.Date==evalDate).FirstOrDefault(); | ||
Console.WriteLine("ULT on {0} was {1}", result.Date, result.Ultimate); | ||
``` | ||
|
||
```bash | ||
ULT on 12/31/2018 was 49.53 | ||
``` |
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,14 @@ | ||
using System; | ||
|
||
namespace Skender.Stock.Indicators | ||
{ | ||
[Serializable] | ||
public class UltimateResult : ResultBase | ||
{ | ||
public decimal? Ultimate { get; set; } | ||
|
||
// internal use only | ||
internal decimal? Bp { get; set; } // buying pressure | ||
internal decimal? Tr { get; set; } // true range | ||
} | ||
} |
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,118 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
|
||
namespace Skender.Stock.Indicators | ||
{ | ||
public static partial class Indicator | ||
{ | ||
// ULTIMATE OSCILLATOR | ||
public static IEnumerable<UltimateResult> GetUltimate( | ||
IEnumerable<Quote> history, int shortPeriod = 7, int middlePeriod = 14, int longPeriod = 28) | ||
{ | ||
|
||
// clean quotes | ||
List<Quote> historyList = Cleaners.PrepareHistory(history).ToList(); | ||
|
||
// check parameters | ||
ValidateUltimate(history, shortPeriod, middlePeriod, longPeriod); | ||
|
||
// initialize | ||
List<UltimateResult> results = new List<UltimateResult>(); | ||
decimal priorClose = 0; | ||
|
||
// roll through history | ||
for (int i = 0; i < historyList.Count; i++) | ||
{ | ||
Quote h = historyList[i]; | ||
|
||
UltimateResult r = new UltimateResult | ||
{ | ||
Index = (int)h.Index, | ||
Date = h.Date | ||
}; | ||
results.Add(r); | ||
|
||
if (i > 0) | ||
{ | ||
r.Bp = h.Close - Math.Min(h.Low, priorClose); | ||
r.Tr = Math.Max(h.High, priorClose) - Math.Min(h.Low, priorClose); | ||
} | ||
|
||
if (h.Index >= longPeriod + 1) | ||
{ | ||
decimal sumBP1 = 0m; | ||
decimal sumBP2 = 0m; | ||
decimal sumBP3 = 0m; | ||
|
||
decimal sumTR1 = 0m; | ||
decimal sumTR2 = 0m; | ||
decimal sumTR3 = 0m; | ||
|
||
for (int p = (int)h.Index - longPeriod; p < h.Index; p++) | ||
{ | ||
UltimateResult pr = results[p]; | ||
|
||
// short aggregate | ||
if (pr.Index > h.Index - shortPeriod) | ||
{ | ||
sumBP1 += (decimal)pr.Bp; | ||
sumTR1 += (decimal)pr.Tr; | ||
} | ||
|
||
// middle aggregate | ||
if (pr.Index > h.Index - middlePeriod) | ||
{ | ||
sumBP2 += (decimal)pr.Bp; | ||
sumTR2 += (decimal)pr.Tr; | ||
} | ||
|
||
// long aggregate | ||
sumBP3 += (decimal)pr.Bp; | ||
sumTR3 += (decimal)pr.Tr; | ||
} | ||
|
||
decimal avg1 = sumBP1 / sumTR1; | ||
decimal avg2 = sumBP2 / sumTR2; | ||
decimal avg3 = sumBP3 / sumTR3; | ||
|
||
r.Ultimate = 100 * (4m * avg1 + 2m * avg2 + avg3) / 7m; | ||
} | ||
|
||
priorClose = h.Close; | ||
} | ||
|
||
return results; | ||
} | ||
|
||
|
||
private static void ValidateUltimate( | ||
IEnumerable<Quote> history, int shortPeriod = 7, int middleAverage = 14, int longPeriod = 28) | ||
{ | ||
|
||
// check parameters | ||
if (shortPeriod <= 0 || middleAverage <= 0 || longPeriod <= 0) | ||
{ | ||
throw new BadParameterException("Average periods must be greater than 0 for Ultimate Oscillator."); | ||
} | ||
|
||
if (shortPeriod >= middleAverage || middleAverage >= longPeriod) | ||
{ | ||
throw new BadParameterException("Average periods must be increasingly larger than each other for Ultimate Oscillator."); | ||
} | ||
|
||
// check history | ||
int qtyHistory = history.Count(); | ||
int minHistory = longPeriod + 1; | ||
if (qtyHistory < minHistory) | ||
{ | ||
throw new BadHistoryException("Insufficient history provided for Ultimate. " + | ||
string.Format(englishCulture, | ||
"You provided {0} periods of history when at least {1} is required.", | ||
qtyHistory, minHistory)); | ||
} | ||
|
||
} | ||
} | ||
|
||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
using Microsoft.VisualStudio.TestTools.UnitTesting; | ||
using Skender.Stock.Indicators; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
|
||
namespace Internal.Tests | ||
{ | ||
[TestClass] | ||
public class UltimateTests : TestBase | ||
{ | ||
|
||
[TestMethod()] | ||
public void GetUltimateTest() | ||
{ | ||
IEnumerable<UltimateResult> results = Indicator.GetUltimate(history, 7, 14, 28); | ||
|
||
// assertions | ||
|
||
// proper quantities | ||
// should always be the same number of results as there is history | ||
Assert.AreEqual(502, results.Count()); | ||
Assert.AreEqual(474, results.Where(x => x.Ultimate != null).Count()); | ||
|
||
// sample values | ||
UltimateResult r1 = results.Where(x => x.Index == 502).FirstOrDefault(); | ||
Assert.AreEqual(49.5257m, Math.Round((decimal)r1.Ultimate, 4)); | ||
|
||
UltimateResult r2 = results.Where(x => x.Index == 250).FirstOrDefault(); | ||
Assert.AreEqual(45.3121m, Math.Round((decimal)r2.Ultimate, 4)); | ||
|
||
UltimateResult r3 = results.Where(x => x.Index == 75).FirstOrDefault(); | ||
Assert.AreEqual(51.7770m, Math.Round((decimal)r3.Ultimate, 4)); | ||
|
||
} | ||
|
||
|
||
/* EXCEPTIONS */ | ||
|
||
[TestMethod()] | ||
[ExpectedException(typeof(BadParameterException), "Bad short period.")] | ||
public void BadShortPeriod() | ||
{ | ||
Indicator.GetUltimate(history, 0); | ||
} | ||
|
||
[TestMethod()] | ||
[ExpectedException(typeof(BadParameterException), "Bad middle period.")] | ||
public void BadMiddlePeriod() | ||
{ | ||
Indicator.GetUltimate(history, 7, 6); | ||
} | ||
|
||
[TestMethod()] | ||
[ExpectedException(typeof(BadParameterException), "Bad long period.")] | ||
public void BadLongPeriod() | ||
{ | ||
Indicator.GetUltimate(history, 7, 14, 11); | ||
} | ||
|
||
[TestMethod()] | ||
[ExpectedException(typeof(BadHistoryException), "Insufficient history.")] | ||
public void InsufficientHistory() | ||
{ | ||
Indicator.GetUltimate(history.Where(x => x.Index <= 28), 7, 14, 28); | ||
} | ||
} | ||
} |
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