Skip to content

Commit

Permalink
add Ultimate Oscillator (#153)
Browse files Browse the repository at this point in the history
* adding ultimate-oscillator indicator
  • Loading branch information
chetanku authored Sep 25, 2020
1 parent 6ab03fd commit 0c1b454
Show file tree
Hide file tree
Showing 7 changed files with 260 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/INDICATORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
- [Stochastic RSI](../indicators/StochRsi/README.md#content)
- [Triple Exponential Moving Average (TEMA)](../indicators/Ema/README.md#content)
- [Ulcer Index](../indicators/UlcerIndex/README.md#content)
- [Ultimate Oscillator](../indicators/Ultimate/README.md#content)
- [Volume Simple Moving Average](../indicators/VolSma/README.md#content)
- [Weighted Moving Average (WMA)](../indicators/Wma/README.md#content)
- [Williams %R](../indicators/WilliamR/README.md#content)
Expand Down
53 changes: 53 additions & 0 deletions indicators/Ultimate/README.md
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
```
14 changes: 14 additions & 0 deletions indicators/Ultimate/Ultimate.Models.cs
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
}
}
118 changes: 118 additions & 0 deletions indicators/Ultimate/Ultimate.cs
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));
}

}
}

}
Binary file added indicators/Ultimate/chart.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
68 changes: 68 additions & 0 deletions tests/indicators/Test.Ultimate.cs
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);
}
}
}
6 changes: 6 additions & 0 deletions tests/performance/Perf.Indicators.cs
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,12 @@ public object GetUlcerIndex()
return Indicator.GetUlcerIndex(hm);
}

[Benchmark]
public object GetUltimate()
{
return Indicator.GetUltimate(hm);
}

[Benchmark]
public object GetVolSma()
{
Expand Down

0 comments on commit 0c1b454

Please sign in to comment.