Skip to content

Commit

Permalink
add Awesome Oscillator (#296)
Browse files Browse the repository at this point in the history
* add Awesome Oscillator
  • Loading branch information
DaveSkender authored Dec 30, 2020
1 parent 9fbba45 commit 3527648
Show file tree
Hide file tree
Showing 8 changed files with 252 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 @@ -5,6 +5,7 @@
- [Arnaud Legoux Moving Average (ALMA)](../indicators/Alma/README.md#content)
- [Average Directional Index (ADX)](../indicators/Adx/README.md#content)
- [Average True Range (ATR)](../indicators/Atr/README.md#content)
- [Awesome Oscillator (AO)](../indicators/Awesome/README.md#content)
- [Beta Coefficient](../indicators/Beta/README.md#content)
- [Bollinger Bands](../indicators/BollingerBands/README.md#content)
- [Chaikin Money Flow (CMF)](../indicators/Cmf/README.md#content)
Expand Down
Binary file added indicators/Awesome/Awesome.Calc.xlsx
Binary file not shown.
11 changes: 11 additions & 0 deletions indicators/Awesome/Awesome.Models.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System;

namespace Skender.Stock.Indicators
{
[Serializable]
public class AwesomeResult : ResultBase
{
public decimal? Oscillator { get; set; }
public decimal? Normalized { get; set; }
}
}
101 changes: 101 additions & 0 deletions indicators/Awesome/Awesome.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
using System;
using System.Collections.Generic;
using System.Linq;

namespace Skender.Stock.Indicators
{
public static partial class Indicator
{
// HEIKIN-ASHI
public static IEnumerable<AwesomeResult> GetAwesome<TQuote>(
IEnumerable<TQuote> history,
int fastPeriod = 5,
int slowPeriod = 34)
where TQuote : IQuote
{

// sort history
List<TQuote> historyList = history.Sort();

// check parameter arguments
ValidateAwesome(history, fastPeriod, slowPeriod);

// initialize
int size = historyList.Count;
List<AwesomeResult> results = new List<AwesomeResult>();
decimal[] pr = new decimal[size]; // median price

// roll through history
for (int i = 0; i < size; i++)
{
TQuote h = historyList[i];
pr[i] = (h.High + h.Low) / 2;
int index = i + 1;

AwesomeResult r = new AwesomeResult
{
Date = h.Date
};

if (index >= slowPeriod)
{
decimal sumSlow = 0m;
decimal sumFast = 0m;

for (int p = index - slowPeriod; p < index; p++)
{
sumSlow += pr[p];

if (p >= index - fastPeriod)
{
sumFast += pr[p];
}
}

r.Oscillator = (sumFast / fastPeriod) - (sumSlow / slowPeriod);
r.Normalized = (pr[i] != 0) ? 100 * r.Oscillator / pr[i] : null;
}

results.Add(r);
}

return results;
}


private static void ValidateAwesome<TQuote>(
IEnumerable<TQuote> history,
int fastPeriod,
int slowPeriod)
where TQuote : IQuote
{

// check parameter arguments
if (fastPeriod <= 0)
{
throw new ArgumentOutOfRangeException(nameof(slowPeriod), slowPeriod,
"Fast period must be greater than 0 for Awesome Oscillator.");
}

if (slowPeriod <= fastPeriod)
{
throw new ArgumentOutOfRangeException(nameof(slowPeriod), slowPeriod,
"Slow period must be larger than Fast Period for Awesome Oscillator.");
}

// check history
int qtyHistory = history.Count();
int minHistory = 34;
if (qtyHistory < minHistory)
{
string message = "Insufficient history provided for Awesome Oscillator. " +
string.Format(englishCulture,
"You provided {0} periods of history when at least {1} is required.",
qtyHistory, minHistory);

throw new BadHistoryException(nameof(history), message);
}
}

}
}
57 changes: 57 additions & 0 deletions indicators/Awesome/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Awesome Oscillator (AO)

Created by Bill Williams, the Awesome Oscillator (aka Super AO) is a measure of the gap between a fast and slow period modified moving average.
[[Discuss] :speech_balloon:](https://github.com/DaveSkender/Stock.Indicators/discussions/282 "Community discussion about this indicator")

![image](chart.png)

```csharp
// usage
IEnumerable<AwesomeResult> results = Indicator.GetAwesome(history, fastPeriod, slowPeriod);
```

## Parameters

| name | type | notes
| -- |-- |--
| `history` | IEnumerable\<[TQuote](../../docs/GUIDE.md#quote)\> | Historical price quotes should have a consistent frequency (day, hour, minute, etc).
| `fastPeriod` | int | Number of periods (`F`) for the faster moving average. Must be greater than 0. Default is 5.
| `slowPeriod` | int | Number of periods (`S`) for the slower moving average. Must be greater than `fastPeriod`. Default is 34.

### Minimum history requirements

You must supply at least `S` periods of `history`.

## Response

```csharp
IEnumerable<AwesomeResult>
```

The first period `S-1` periods will have `null` values since there's not enough data to calculate. We always return the same number of elements as there are in the historical quotes.

### AwesomeResult

| name | type | notes
| -- |-- |--
| `Date` | DateTime | Date
| `Oscillator` | decimal | Awesome Oscillator
| `Normalized` | decimal | `100 × Oscillator ÷ (median price)`

## Example

```csharp
// fetch historical quotes from your favorite feed, in Quote format
IEnumerable<Quote> history = GetHistoryFromFeed("MSFT");

// calculate
IEnumerable<AwesomeResult> results = Indicator.GetAwesome(history,5,34);

// use results as needed
AwesomeResult r = results.LastOrDefault();
Console.WriteLine("AO on {0} was {1}", r.Date, r.Oscillator);
```

```bash
AO on 12/31/2018 was -17.77
```
Binary file added indicators/Awesome/chart.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
76 changes: 76 additions & 0 deletions tests/indicators/Test.Awesome.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Skender.Stock.Indicators;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Internal.Tests
{
[TestClass]
public class AwesomeTests : TestBase
{

[TestMethod()]
public void Standard()
{

List<AwesomeResult> results = Indicator.GetAwesome(history, 5, 34).ToList();

// assertions

// should always be the same number of results as there is history
Assert.AreEqual(502, results.Count);
Assert.AreEqual(469, results.Where(x => x.Oscillator != null).Count());

// sample values
AwesomeResult r1 = results[501];
Assert.AreEqual(-17.7692m, Math.Round((decimal)r1.Oscillator, 4));
Assert.AreEqual(-7.2763m, Math.Round((decimal)r1.Normalized, 4));

AwesomeResult r2 = results[249];
Assert.AreEqual(5.0618m, Math.Round((decimal)r2.Oscillator, 4));
Assert.AreEqual(1.9634m, Math.Round((decimal)r2.Normalized, 4));

AwesomeResult r3 = results[33];
Assert.AreEqual(5.4756m, Math.Round((decimal)r3.Oscillator, 4));
Assert.AreEqual(2.4548m, Math.Round((decimal)r3.Normalized, 4));

AwesomeResult r4 = results[32];
Assert.AreEqual(null, r4.Oscillator);
Assert.AreEqual(null, r4.Normalized);
}

[TestMethod()]
public void BadData()
{
IEnumerable<AwesomeResult> r = Indicator.GetAwesome(historyBad);
Assert.AreEqual(502, r.Count());
}


/* EXCEPTIONS */

[TestMethod()]
[ExpectedException(typeof(ArgumentOutOfRangeException), "Bad fast period.")]
public void BadFastPeriod()
{
Indicator.GetAwesome(history, 0, 34);
}

[TestMethod()]
[ExpectedException(typeof(ArgumentOutOfRangeException), "Bad slow period.")]
public void BadSlowPeriod()
{
Indicator.GetAwesome(history, 25, 25);
}

[TestMethod()]
[ExpectedException(typeof(BadHistoryException), "Insufficient history.")]
public void InsufficientHistory()
{
IEnumerable<Quote> h = History.GetHistory(33);
Indicator.GetAwesome(h, 5, 34);
}

}
}
6 changes: 6 additions & 0 deletions tests/performance/Perf.Indicators.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ public object GetAtr()
return Indicator.GetAtr(h);
}

[Benchmark]
public object GetAwesome()
{
return Indicator.GetAwesome(h);
}

[Benchmark]
public object GetBeta()
{
Expand Down

0 comments on commit 3527648

Please sign in to comment.