From cfc84710a4c02aebf8fbadfe3fb2c283017e65a6 Mon Sep 17 00:00:00 2001 From: Dave Skender <8432125+DaveSkender@users.noreply.github.com> Date: Sat, 17 Sep 2022 18:04:09 -0400 Subject: [PATCH] add optional K-factor to Dynamic (#901) +semver: minor --- docs/GemFile.lock | 2 +- docs/_indicators/Beta.md | 11 ++++++----- docs/_indicators/Dynamic.md | 9 ++++++++- src/a-d/Dynamic/Dynamic.Api.cs | 15 +++++++++------ src/a-d/Dynamic/Dynamic.Series.cs | 16 ++++++++++++---- src/a-d/Dynamic/info.xml | 1 + tests/indicators/a-d/Dynamic/Dynamic.Tests.cs | 12 +++++++++--- 7 files changed, 46 insertions(+), 20 deletions(-) diff --git a/docs/GemFile.lock b/docs/GemFile.lock index 849a28829..ccaf819d7 100644 --- a/docs/GemFile.lock +++ b/docs/GemFile.lock @@ -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) diff --git a/docs/_indicators/Beta.md b/docs/_indicators/Beta.md index 4e9187fd4..3a2388005 100644 --- a/docs/_indicators/Beta.md +++ b/docs/_indicators/Beta.md @@ -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 @@ -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. diff --git a/docs/_indicators/Dynamic.md b/docs/_indicators/Dynamic.md index c519f2c8a..6e458e94f 100644 --- a/docs/_indicators/Dynamic.md +++ b/docs/_indicators/Dynamic.md @@ -17,7 +17,7 @@ Created by John R. McGinley, the [McGinley Dynamic](https://www.investopedia.com ```csharp // usage (with Close price) IEnumerable results = - quotes.GetDynamic(lookbackPeriods); + quotes.GetDynamic(lookbackPeriods, kFactor); ``` ## Parameters @@ -25,6 +25,7 @@ IEnumerable results = | 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 @@ -32,6 +33,12 @@ You must have at least `2` periods of `quotes`, to cover the initialization peri `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 diff --git a/src/a-d/Dynamic/Dynamic.Api.cs b/src/a-d/Dynamic/Dynamic.Api.cs index 46deda96b..99d7979e6 100644 --- a/src/a-d/Dynamic/Dynamic.Api.cs +++ b/src/a-d/Dynamic/Dynamic.Api.cs @@ -8,23 +8,26 @@ public static partial class Indicator /// public static IEnumerable GetDynamic( this IEnumerable 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 GetDynamic( this IEnumerable 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 GetDynamic( this IEnumerable<(DateTime, double)> priceTuples, - int lookbackPeriods) => priceTuples + int lookbackPeriods, + double kFactor = 0.6) => priceTuples .ToSortedList() - .CalcDynamic(lookbackPeriods); + .CalcDynamic(lookbackPeriods, kFactor); } diff --git a/src/a-d/Dynamic/Dynamic.Series.cs b/src/a-d/Dynamic/Dynamic.Series.cs index 90dcd841d..9d0f47e01 100644 --- a/src/a-d/Dynamic/Dynamic.Series.cs +++ b/src/a-d/Dynamic/Dynamic.Series.cs @@ -5,10 +5,11 @@ public static partial class Indicator { internal static List CalcDynamic( this List<(DateTime, double)> tpList, - int lookbackPeriods) + int lookbackPeriods, + double kFactor) { // check parameter arguments - ValidateDynamic(lookbackPeriods); + ValidateDynamic(lookbackPeriods, kFactor); // initialize int iStart = 1; @@ -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) { @@ -55,7 +56,8 @@ public static partial class Indicator // parameter validation private static void ValidateDynamic( - int lookbackPeriods) + int lookbackPeriods, + double kFactor) { // check parameter arguments if (lookbackPeriods <= 0) @@ -63,5 +65,11 @@ public static partial class Indicator 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."); + } } } diff --git a/src/a-d/Dynamic/info.xml b/src/a-d/Dynamic/info.xml index e5981940c..c142ed752 100644 --- a/src/a-d/Dynamic/info.xml +++ b/src/a-d/Dynamic/info.xml @@ -12,6 +12,7 @@ Configurable Quote type. See Guide for more information. Historical price quotes. Number of periods in the lookback window. + Optional. Range adjustment factor. Time series of Dynamic values. Invalid parameter value provided. \ No newline at end of file diff --git a/tests/indicators/a-d/Dynamic/Dynamic.Tests.cs b/tests/indicators/a-d/Dynamic/Dynamic.Tests.cs index c9cdeea21..529756757 100644 --- a/tests/indicators/a-d/Dynamic/Dynamic.Tests.cs +++ b/tests/indicators/a-d/Dynamic/Dynamic.Tests.cs @@ -90,9 +90,15 @@ public void NoQuotes() Assert.AreEqual(1, r1.Count()); } - // bad lookback period [TestMethod] public void Exceptions() - => Assert.ThrowsException(() - => Indicator.GetDynamic(quotes, 0)); + { + // bad lookback period + _ = Assert.ThrowsException(() + => quotes.GetDynamic(0)); + + // bad k-factor + _ = Assert.ThrowsException(() + => quotes.GetDynamic(14, 0)); + } }