Skip to content

Commit

Permalink
add optional K-factor to Dynamic (#901)
Browse files Browse the repository at this point in the history
+semver: minor
  • Loading branch information
DaveSkender committed Sep 17, 2022
1 parent 315f2df commit cfc8471
Show file tree
Hide file tree
Showing 7 changed files with 46 additions and 20 deletions.
2 changes: 1 addition & 1 deletion docs/GemFile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ GIT
GEM
remote: https://rubygems.org/
specs:
activesupport (6.0.5.1)
activesupport (6.0.6)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2)
minitest (~> 5.1)
Expand Down
11 changes: 6 additions & 5 deletions docs/_indicators/Beta.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ You must have at least `N` periods of `quotesEval` to cover the warmup periods.
| `Down` | Downside Beta only. Uses historical quotes from market down bars only.
| `All` | Returns all of the above. Use this option if you want `Ratio` and `Convexity` values returned. Note: 3× slower to calculate.

### Pro tips

> Financial institutions often depict a single number for Beta on their sites. To get that same long-term Beta value, use 5 years of monthly bars for `quotes` and a value of 60 for `lookbackPeriods`. If you only have smaller bars, use the [Aggregate()]({{site.baseurl}}/utilities#resize-quote-history) utility to convert it.
>
> [Alpha](https://en.wikipedia.org/wiki/Alpha_(finance)) is calculated as `R – Rf – Beta (Rm - Rf)`, where `Rf` is the risk-free rate.
## Response

```csharp
Expand Down Expand Up @@ -93,8 +99,3 @@ var results = quotesEval
.GetBeta(quotesMarket, ..)
.GetSlope(..);
```

## Pro tips

- Financial institutions often depict a single number for Beta on their sites. To get that same long-term Beta value, use 5 years of monthly bars for `quotes` and a value of 60 for `lookbackPeriods`. If you only have daily bars, use the [quotes.Aggregate(PeriodSize.Monthly)]({{site.baseurl}}/utilities#resize-quote-history) utility to convert it.
- [Alpha](https://en.wikipedia.org/wiki/Alpha_(finance)) is calculated as `R – Rf – Beta (Rm - Rf)`, where `Rf` is the risk-free rate.
9 changes: 8 additions & 1 deletion docs/_indicators/Dynamic.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,28 @@ Created by John R. McGinley, the [McGinley Dynamic](https://www.investopedia.com
```csharp
// usage (with Close price)
IEnumerable<DynamicResult> results =
quotes.GetDynamic(lookbackPeriods);
quotes.GetDynamic(lookbackPeriods, kFactor);
```

## Parameters

| name | type | notes
| -- |-- |--
| `lookbackPeriods` | int | Number of periods (`N`) in the moving average. Must be greater than 0.
| `kFactor` | double | Optional. Range adjustment factor (`K`). Must be greater than 0. Default is 0.6

### Historical quotes requirements

You must have at least `2` periods of `quotes`, to cover the initialization periods. Since this uses a smoothing technique, we recommend you use at least `4×N` data points prior to the intended usage date for better precision.

`quotes` is a collection of generic `TQuote` historical price quotes. It should have a consistent frequency (day, hour, minute, etc). See [the Guide]({{site.baseurl}}/guide/#historical-quotes) for more information.

### Pro tips

> Use a `kFactor` value of `1` if you do not want to adjust the `N` value.
>
> McGinley suggests that using a `K` value of 60% (0.6) allows you to use a `N` equivalent to other moving averages. For example, DYNAMIC(20,0.6) is comparable to EMA(20); conversely, DYNAMIC(20,1) uses the raw 1:1 `N` value and is not equivalent.
## Response

```csharp
Expand Down
15 changes: 9 additions & 6 deletions src/a-d/Dynamic/Dynamic.Api.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,26 @@ public static partial class Indicator
///
public static IEnumerable<DynamicResult> GetDynamic<TQuote>(
this IEnumerable<TQuote> quotes,
int lookbackPeriods)
int lookbackPeriods,
double kFactor = 0.6)
where TQuote : IQuote => quotes
.ToBasicTuple(CandlePart.Close)
.CalcDynamic(lookbackPeriods);
.CalcDynamic(lookbackPeriods, kFactor);

// SERIES, from CHAIN
public static IEnumerable<DynamicResult> GetDynamic(
this IEnumerable<IReusableResult> results,
int lookbackPeriods) => results
int lookbackPeriods,
double kFactor = 0.6) => results
.ToResultTuple()
.CalcDynamic(lookbackPeriods)
.CalcDynamic(lookbackPeriods, kFactor)
.SyncIndex(results, SyncType.Prepend);

// SERIES, from TUPLE
public static IEnumerable<DynamicResult> GetDynamic(
this IEnumerable<(DateTime, double)> priceTuples,
int lookbackPeriods) => priceTuples
int lookbackPeriods,
double kFactor = 0.6) => priceTuples
.ToSortedList()
.CalcDynamic(lookbackPeriods);
.CalcDynamic(lookbackPeriods, kFactor);
}
16 changes: 12 additions & 4 deletions src/a-d/Dynamic/Dynamic.Series.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ public static partial class Indicator
{
internal static List<DynamicResult> CalcDynamic(
this List<(DateTime, double)> tpList,
int lookbackPeriods)
int lookbackPeriods,
double kFactor)
{
// check parameter arguments
ValidateDynamic(lookbackPeriods);
ValidateDynamic(lookbackPeriods, kFactor);

// initialize
int iStart = 1;
Expand Down Expand Up @@ -39,7 +40,7 @@ public static partial class Indicator
else
{
double md = prevMD + ((value - prevMD) /
(0.6 * lookbackPeriods * Math.Pow(value / prevMD, 4)));
(kFactor * lookbackPeriods * Math.Pow(value / prevMD, 4)));

if (i >= iStart)
{
Expand All @@ -55,13 +56,20 @@ public static partial class Indicator

// parameter validation
private static void ValidateDynamic(
int lookbackPeriods)
int lookbackPeriods,
double kFactor)
{
// check parameter arguments
if (lookbackPeriods <= 0)
{
throw new ArgumentOutOfRangeException(nameof(lookbackPeriods), lookbackPeriods,
"Lookback periods must be greater than 0 for DYNAMIC.");
}

if (kFactor <= 0)
{
throw new ArgumentOutOfRangeException(nameof(kFactor), kFactor,
"K-Factor range adjustment must be greater than 0 for DYNAMIC.");
}
}
}
1 change: 1 addition & 0 deletions src/a-d/Dynamic/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<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>
<param name="kFactor">Optional. Range adjustment factor.</param>
<returns>Time series of Dynamic values.</returns>
<exception cref="ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
</info>
12 changes: 9 additions & 3 deletions tests/indicators/a-d/Dynamic/Dynamic.Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,15 @@ public void NoQuotes()
Assert.AreEqual(1, r1.Count());
}

// bad lookback period
[TestMethod]
public void Exceptions()
=> Assert.ThrowsException<ArgumentOutOfRangeException>(()
=> Indicator.GetDynamic(quotes, 0));
{
// bad lookback period
_ = Assert.ThrowsException<ArgumentOutOfRangeException>(()
=> quotes.GetDynamic(0));

// bad k-factor
_ = Assert.ThrowsException<ArgumentOutOfRangeException>(()
=> quotes.GetDynamic(14, 0));
}
}

0 comments on commit cfc8471

Please sign in to comment.