Skip to content

Commit

Permalink
add Pivot Points indicators (#276)
Browse files Browse the repository at this point in the history
* add PivotPoints indicators

+semver: minor
  • Loading branch information
DaveSkender authored Dec 21, 2020
1 parent 96ef389 commit 9c96bea
Show file tree
Hide file tree
Showing 12 changed files with 2,440 additions and 1 deletion.
1 change: 1 addition & 0 deletions docs/INDICATORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
- [Moving Average Convergence/Divergence (MACD)](../indicators/Macd/README.md#content)
- [On-balance Volume (OBV)](../indicators/Obv/README.md#content)
- [Parabolic SAR](../indicators/ParabolicSar/README.md#content)
- [Pivot Points](../indicators/PivotPoints/README.md#content)
- [Price (Comparative) Relative Strength (PRS)](../indicators/Prs/README.md#content)
- [Price Momentum Oscillator (PMO)](../indicators/Pmo/README.md#content)
- [Rate of Change (ROC)](../indicators/Roc/README.md#content)
Expand Down
Binary file added indicators/PivotPoints/PivotPoint.Calc.xlsx
Binary file not shown.
30 changes: 30 additions & 0 deletions indicators/PivotPoints/PivotPoints.Models.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System;

namespace Skender.Stock.Indicators
{
[Serializable]
public class PivotPointsResult : ResultBase
{
public decimal? R4 { get; set; }
public decimal? R3 { get; set; }
public decimal? R2 { get; set; }
public decimal? R1 { get; set; }
public decimal? PP { get; set; }
public decimal? S1 { get; set; }
public decimal? S2 { get; set; }
public decimal? S3 { get; set; }
public decimal? S4 { get; set; }
}

public enum PivotPointType
{
// do not modify numbers,
// just add new random numbers if extending

Standard = 0,
Camarilla = 1,
Demark = 2,
Fibonacci = 3,
Woodie = 4
}
}
271 changes: 271 additions & 0 deletions indicators/PivotPoints/PivotPoints.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
using System;
using System.Collections.Generic;
using System.Linq;

namespace Skender.Stock.Indicators
{
public static partial class Indicator
{
// PIVOT POINTS
public static IEnumerable<PivotPointsResult> GetPivotPoints<TQuote>(
IEnumerable<TQuote> history,
PeriodSize windowSize,
PivotPointType pointType = PivotPointType.Standard)
where TQuote : IQuote
{

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

// check parameters
ValidatePivotPoints(history, windowSize);

// initialize
List<PivotPointsResult> results = new List<PivotPointsResult>(historyList.Count);
PivotPointsResult windowPoint = new PivotPointsResult();

TQuote h0 = historyList[0];
int windowId = GetWindowNumber(h0.Date, windowSize);
int windowEval;
bool firstWindow = true;

decimal windowHigh = h0.High;
decimal windowLow = h0.Low;
decimal windowOpen = h0.Open;
decimal windowClose = h0.Close;

// roll through history
for (int i = 0; i < historyList.Count; i++)
{
TQuote h = historyList[i];

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

// new window evaluation
windowEval = GetWindowNumber(h.Date, windowSize);

if (windowEval != windowId)
{
windowId = windowEval;
firstWindow = false;

// set new levels
if (pointType == PivotPointType.Woodie)
{
windowOpen = h.Open;
}

windowPoint = GetPivotPoint(pointType, windowOpen, windowHigh, windowLow, windowClose);

// reset window min/max thresholds
windowOpen = h.Open;
windowHigh = h.High;
windowLow = h.Low;
}

// add levels
if (!firstWindow)
{
// pivot point
r.PP = windowPoint.PP;

// support
r.S1 = windowPoint.S1;
r.S2 = windowPoint.S2;
r.S3 = windowPoint.S3;
r.S4 = windowPoint.S4;

// resistance
r.R1 = windowPoint.R1;
r.R2 = windowPoint.R2;
r.R3 = windowPoint.R3;
r.R4 = windowPoint.R4;
}

results.Add(r);

// capture window threholds (for next iteration)
windowHigh = (h.High > windowHigh) ? h.High : windowHigh;
windowLow = (h.Low < windowLow) ? h.Low : windowLow;
windowClose = h.Close;
}

return results;
}


internal static PivotPointsResult GetPivotPoint(
PivotPointType type, decimal open, decimal high, decimal low, decimal close)
{
return type switch
{
PivotPointType.Standard => GetPivotPointStandard(high, low, close),
PivotPointType.Camarilla => GetPivotPointCamarilla(high, low, close),
PivotPointType.Demark => GetPivotPointDemark(open, high, low, close),
PivotPointType.Fibonacci => GetPivotPointFibonacci(high, low, close),
PivotPointType.Woodie => GetPivotPointWoodie(open, high, low),
_ => null
};
}

public static PivotPointsResult GetPivotPointStandard(
decimal high, decimal low, decimal close)
{
decimal pp = (high + low + close) / 3;

return new PivotPointsResult
{
PP = pp,
S1 = pp * 2 - high,
S2 = pp - (high - low),
R1 = pp * 2 - low,
R2 = pp + (high - low)
};
}

public static PivotPointsResult GetPivotPointCamarilla(
decimal high, decimal low, decimal close)
{
return new PivotPointsResult
{
PP = (high + low + close) / 3,
S1 = close - (1m + 1m / 12) * (high - low),
S2 = close - (1m + 1m / 6) * (high - low),
S3 = close - (1m + 1m / 4) * (high - low),
S4 = close - (1m + 1m / 2) * (high - low),
R1 = close + (1m + 1m / 12) * (high - low),
R2 = close + (1m + 1m / 6) * (high - low),
R3 = close + (1m + 1m / 4) * (high - low),
R4 = close + (1m + 1m / 2) * (high - low)
};
}

public static PivotPointsResult GetPivotPointDemark(
decimal open, decimal high, decimal low, decimal close)
{
decimal? x;

if (close < open)
{
x = high + 2 * low + close;
}
else if (close > open)
{
x = 2 * high + low + close;
}
else if (close == open)
{
x = high + low + 2 * close;
}
else
{
x = null;
}

return new PivotPointsResult
{
PP = x / 4,
S1 = x / 2 - high,
R1 = x / 2 - low
};
}

public static PivotPointsResult GetPivotPointFibonacci(
decimal high, decimal low, decimal close)
{
decimal pp = (high + low + close) / 3;

return new PivotPointsResult
{
PP = pp,
S1 = pp - 0.382m * (high - low),
S2 = pp - 0.618m * (high - low),
S3 = pp - 1.000m * (high - low),
R1 = pp + 0.382m * (high - low),
R2 = pp + 0.618m * (high - low),
R3 = pp + 1.000m * (high - low)
};
}

public static PivotPointsResult GetPivotPointWoodie(
decimal currentOpen, decimal high, decimal low)
{
decimal pp = (high + low + 2 * currentOpen) / 4;

return new PivotPointsResult
{
PP = pp,
S1 = pp * 2 - high,
S2 = pp - high + low,
S3 = low - 2 * (high - pp),
R1 = pp * 2 - low,
R2 = pp + high - low,
R3 = high + 2 * (pp - low),
};
}


private static int GetWindowNumber(DateTime d, PeriodSize windowSize)
{
return windowSize switch
{
PeriodSize.Month => d.Month,
PeriodSize.Week => englishCalendar.GetWeekOfYear(d, englishCalendarWeekRule, englishFirstDayOfWeek),
PeriodSize.Day => d.Day,
PeriodSize.Hour => d.Hour,
_ => 0
};
}


private static void ValidatePivotPoints<TQuote>(
IEnumerable<TQuote> history, PeriodSize windowSize)
where TQuote : IQuote
{

// count periods based on periodSize
int qtyWindows = 0;

switch (windowSize)
{
case PeriodSize.Month:
qtyWindows = history.Select(x => x.Date.Month).Distinct().Count();
break;

case PeriodSize.Week:
qtyWindows = history.Select(x => englishCalendar
.GetWeekOfYear(x.Date, englishCalendarWeekRule, englishFirstDayOfWeek))
.Distinct().Count();
break;

case PeriodSize.Day:
qtyWindows = history.Select(x => x.Date.Day).Distinct().Count();
break;

case PeriodSize.Hour:
qtyWindows = history.Select(x => x.Date.Hour).Distinct().Count();
break;

default:
break;
};

// check history to ensure 2+ periods are present
if (qtyWindows < 2)
{
string message = "Insufficient history provided for Pivot Points. " +
string.Format(englishCulture,
"You provided {0} {1} windows of history when at least 2 are required. "
+ "This can be from either not enough history or insufficiently detailed Date values.",
qtyWindows, Enum.GetName(typeof(PeriodSize), windowSize));

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

}
}

}
83 changes: 83 additions & 0 deletions indicators/PivotPoints/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# PivotPoints

[Pivot Points](https://en.wikipedia.org/wiki/Pivot_point_(technical_analysis)) depict support and resistance levels, based on the prior lookback window. You can specify window size (e.g. month, week, day, etc).
[[Discuss] :speech_balloon:](https://github.com/DaveSkender/Stock.Indicators/discussions/274 "Community discussion about this indicator")

![image](chart.png)

```csharp
// usage
IEnumerable<PivotPointResult> results = Indicator.GetPivotPoints(history, windowSize, pointType);
```

## Parameters

| name | type | notes
| -- |-- |--
| `history` | IEnumerable\<[TQuote](../../docs/GUIDE.md#quote)\> | Historical price quotes should have a consistent frequency (day, hour, minute, etc)
| `windowSize` | PeriodSize | Size of the lookback window
| `pointType` | PivotPointType | Type of Pivot Point. Default is `PivotPointType.Standard`

### Minimum history requirements

You must supply at least `2` windows of `history`. For example, if you specify a `Week` window size, you need at least 14 days of `history`.

### PeriodSize options (for windowSize)

| type | description
|-- |--
| `PeriodSize.Month` | Use the prior month's data to calculate current month's Pivot Points
| `PeriodSize.Week` | [..] weekly
| `PeriodSize.Day` | [..] daily. Commonly used for intraday data.
| `PeriodSize.Hour` | [..] hourly

### PivotPointType options

| type | description
|-- |--
| `PivotPointType.Standard` | Standard "floor trading" Pivot Points
| `PivotPointType.Camarilla` | Camarilla
| `PivotPointType.Demark` | Demark
| `PivotPointType.Fibonacci` | Fibonacci
| `PivotPointType.Woodie` | Woodie

## Response

```csharp
IEnumerable<PivotPointsResult>
```

The first window 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.

:warning: **Warning**: The second window may be innaccurate if the first window contains incomplete data. For example, this can occur if you specify a `Month` window size and only provide 45 days (1.5 months) of `history`.

### PivotPointResult

| name | type | notes
| -- |-- |--
| `Date` | DateTime | Date
| `R3` | decimal | Resistance level 3
| `R2` | decimal | Resistance level 2
| `R1` | decimal | Resistance level 1
| `PP` | decimal | Pivot Point
| `S1` | decimal | Support level 1
| `S2` | decimal | Support level 2
| `S3` | decimal | Support level 3

## Example

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

// calculate Woodie-style month-based Pivot Points
IEnumerable<PivotPointResult> results = Indicator.GetPivotPoints(history,PeriodSize.Month,PivotPointType.Woodie);

// use results as needed
PivotPointsResult result = results.LastOrDefault();
Console.WriteLine("PP on {0} was ${1}", result.Date, result.PP);
```

```bash
PP on 12/31/2018 was $251.86
```
Binary file added indicators/PivotPoints/chart.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 9c96bea

Please sign in to comment.