Skip to content

Commit a180074

Browse files
authored
McGinley Dynamic and README update (#334)
# Describe Request This PR implements the McGinley Dynamic indicator in the trend package and updates the main README.md to include both the newly added McGinley Dynamic and the previously merged Choppiness Index (CHOP). Fixed #305 # Change Type - New indicator: McGinley Dynamic - Documentation update: README.md
1 parent ac76f5c commit a180074

5 files changed

Lines changed: 511 additions & 0 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ The following list of indicators are currently supported by this package:
4040
- [Kaufman's Adaptive Moving Average (KAMA)](trend/README.md#type-kama)
4141
- [Know Sure Thing (KST)](trend/README.md#type-kst)
4242
- [Mass Index (MI)](trend/README.md#type-massindex)
43+
- [McGinley Dynamic](trend/README.md#type-mcginleydynamic)
4344
- [Moving Average Convergence Divergence (MACD)](trend/README.md#type-macd)
4445
- [Moving Least Square (MLS)](trend/README.md#type-mls)
4546
- [Moving Linear Regression (MLR)](trend/README.md#type-mlr)
@@ -94,6 +95,7 @@ The following list of indicators are currently supported by this package:
9495
- [Bollinger Band Width](volatility/README.md#type-bollingerbandwidth)
9596
- [Bollinger Bands](volatility/README.md#type-bollingerbands)
9697
- [Chandelier Exit](volatility/README.md#type-chandelierexit)
98+
- [Choppiness Index (CHOP)](volatility/README.md#type-chop)
9799
- [Donchian Channel (DC)](volatility/README.md#type-donchianchannel)
98100
- [Keltner Channel (KC)](volatility/README.md#type-keltnerchannel)
99101
- [Moving Standard Deviation (Std)](volatility/README.md#type-movingstd)

trend/README.md

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,12 @@ The information provided on this project is strictly for informational purposes
9898
- [func NewMassIndex\[T helper.Number\]\(\) \*MassIndex\[T\]](<#NewMassIndex>)
9999
- [func \(m \*MassIndex\[T\]\) Compute\(highs, lows \<\-chan T\) \<\-chan T](<#MassIndex[T].Compute>)
100100
- [func \(m \*MassIndex\[T\]\) IdlePeriod\(\) int](<#MassIndex[T].IdlePeriod>)
101+
- [type McGinleyDynamic](<#McGinleyDynamic>)
102+
- [func NewMcGinleyDynamic\[T helper.Number\]\(\) \*McGinleyDynamic\[T\]](<#NewMcGinleyDynamic>)
103+
- [func NewMcGinleyDynamicWithPeriod\[T helper.Number\]\(period int\) \*McGinleyDynamic\[T\]](<#NewMcGinleyDynamicWithPeriod>)
104+
- [func \(m \*McGinleyDynamic\[T\]\) Compute\(c \<\-chan T\) \<\-chan T](<#McGinleyDynamic[T].Compute>)
105+
- [func \(m \*McGinleyDynamic\[T\]\) IdlePeriod\(\) int](<#McGinleyDynamic[T].IdlePeriod>)
106+
- [func \(m \*McGinleyDynamic\[T\]\) String\(\) string](<#McGinleyDynamic[T].String>)
101107
- [type Mlr](<#Mlr>)
102108
- [func NewMlrWithPeriod\[T helper.Number\]\(period int\) \*Mlr\[T\]](<#NewMlrWithPeriod>)
103109
- [func \(m \*Mlr\[T\]\) Compute\(x, y \<\-chan T\) \<\-chan T](<#Mlr[T].Compute>)
@@ -442,6 +448,15 @@ const (
442448
const DefaultDpoPeriod = 20
443449
```
444450

451+
<a name="DefaultMcGinleyDynamicPeriod"></a>
452+
453+
```go
454+
const (
455+
// DefaultMcGinleyDynamicPeriod is the default period for the McGinley Dynamic.
456+
DefaultMcGinleyDynamicPeriod = 14
457+
)
458+
```
459+
445460
<a name="DefaultRmaPeriod"></a>
446461

447462
```go
@@ -1447,6 +1462,74 @@ func (m *MassIndex[T]) IdlePeriod() int
14471462

14481463
IdlePeriod is the initial period that Mass Index won't yield any results.
14491464

1465+
<a name="McGinleyDynamic"></a>
1466+
## type [McGinleyDynamic](<https://github.com/cinar/indicator/blob/master/trend/mcginley_dynamic.go#L29-L32>)
1467+
1468+
McGinleyDynamic represents the parameters for calculating the McGinley Dynamic. It is a technical analysis indicator that is an improvement over the Exponential Moving Average \(EMA\). It is designed to adjust for changes in market speed.
1469+
1470+
```
1471+
MD_today = MD_yesterday + (Close - MD_yesterday) / (Period * (Close / MD_yesterday)^4)
1472+
```
1473+
1474+
Example:
1475+
1476+
```
1477+
md := trend.NewMcGinleyDynamic[float64]()
1478+
result := md.Compute(c)
1479+
```
1480+
1481+
```go
1482+
type McGinleyDynamic[T helper.Number] struct {
1483+
// Period is the smoothing period.
1484+
Period int
1485+
}
1486+
```
1487+
1488+
<a name="NewMcGinleyDynamic"></a>
1489+
### func [NewMcGinleyDynamic](<https://github.com/cinar/indicator/blob/master/trend/mcginley_dynamic.go#L35>)
1490+
1491+
```go
1492+
func NewMcGinleyDynamic[T helper.Number]() *McGinleyDynamic[T]
1493+
```
1494+
1495+
NewMcGinleyDynamic function initializes a new McGinley Dynamic instance with the default parameters.
1496+
1497+
<a name="NewMcGinleyDynamicWithPeriod"></a>
1498+
### func [NewMcGinleyDynamicWithPeriod](<https://github.com/cinar/indicator/blob/master/trend/mcginley_dynamic.go#L40>)
1499+
1500+
```go
1501+
func NewMcGinleyDynamicWithPeriod[T helper.Number](period int) *McGinleyDynamic[T]
1502+
```
1503+
1504+
NewMcGinleyDynamicWithPeriod function initializes a new McGinley Dynamic instance with the given period.
1505+
1506+
<a name="McGinleyDynamic[T].Compute"></a>
1507+
### func \(\*McGinleyDynamic\[T\]\) [Compute](<https://github.com/cinar/indicator/blob/master/trend/mcginley_dynamic.go#L47>)
1508+
1509+
```go
1510+
func (m *McGinleyDynamic[T]) Compute(c <-chan T) <-chan T
1511+
```
1512+
1513+
Compute function takes a channel of numbers and computes the McGinley Dynamic over the specified period.
1514+
1515+
<a name="McGinleyDynamic[T].IdlePeriod"></a>
1516+
### func \(\*McGinleyDynamic\[T\]\) [IdlePeriod](<https://github.com/cinar/indicator/blob/master/trend/mcginley_dynamic.go#L82>)
1517+
1518+
```go
1519+
func (m *McGinleyDynamic[T]) IdlePeriod() int
1520+
```
1521+
1522+
IdlePeriod is the initial period that McGinley Dynamic yield any results.
1523+
1524+
<a name="McGinleyDynamic[T].String"></a>
1525+
### func \(\*McGinleyDynamic\[T\]\) [String](<https://github.com/cinar/indicator/blob/master/trend/mcginley_dynamic.go#L87>)
1526+
1527+
```go
1528+
func (m *McGinleyDynamic[T]) String() string
1529+
```
1530+
1531+
String is the string representation of the McGinley Dynamic.
1532+
14501533
<a name="Mlr"></a>
14511534
## type [Mlr](<https://github.com/cinar/indicator/blob/master/trend/mlr.go#L19-L22>)
14521535

trend/mcginley_dynamic.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Copyright (c) 2021-2026 Onur Cinar.
2+
// The source code is provided under GNU AGPLv3 License.
3+
// https://github.com/cinar/indicator
4+
5+
package trend
6+
7+
import (
8+
"fmt"
9+
"math"
10+
11+
"github.com/cinar/indicator/v2/helper"
12+
)
13+
14+
const (
15+
// DefaultMcGinleyDynamicPeriod is the default period for the McGinley Dynamic.
16+
DefaultMcGinleyDynamicPeriod = 14
17+
)
18+
19+
// McGinleyDynamic represents the parameters for calculating the McGinley Dynamic.
20+
// It is a technical analysis indicator that is an improvement over the Exponential
21+
// Moving Average (EMA). It is designed to adjust for changes in market speed.
22+
//
23+
// MD_today = MD_yesterday + (Close - MD_yesterday) / (Period * (Close / MD_yesterday)^4)
24+
//
25+
// Example:
26+
//
27+
// md := trend.NewMcGinleyDynamic[float64]()
28+
// result := md.Compute(c)
29+
type McGinleyDynamic[T helper.Number] struct {
30+
// Period is the smoothing period.
31+
Period int
32+
}
33+
34+
// NewMcGinleyDynamic function initializes a new McGinley Dynamic instance with the default parameters.
35+
func NewMcGinleyDynamic[T helper.Number]() *McGinleyDynamic[T] {
36+
return NewMcGinleyDynamicWithPeriod[T](DefaultMcGinleyDynamicPeriod)
37+
}
38+
39+
// NewMcGinleyDynamicWithPeriod function initializes a new McGinley Dynamic instance with the given period.
40+
func NewMcGinleyDynamicWithPeriod[T helper.Number](period int) *McGinleyDynamic[T] {
41+
return &McGinleyDynamic[T]{
42+
Period: period,
43+
}
44+
}
45+
46+
// Compute function takes a channel of numbers and computes the McGinley Dynamic over the specified period.
47+
func (m *McGinleyDynamic[T]) Compute(c <-chan T) <-chan T {
48+
result := make(chan T, cap(c))
49+
50+
go func() {
51+
defer close(result)
52+
53+
var before float64
54+
first := true
55+
56+
for n := range c {
57+
val := float64(n)
58+
if first {
59+
before = val
60+
first = false
61+
result <- T(before)
62+
continue
63+
}
64+
65+
if before == 0 {
66+
before = val
67+
result <- T(before)
68+
continue
69+
}
70+
71+
// MD_today = MD_yesterday + (Close - MD_yesterday) / (Period * (Close / MD_yesterday)^4)
72+
ratio := val / before
73+
before = before + (val-before)/(float64(m.Period)*math.Pow(ratio, 4))
74+
result <- T(before)
75+
}
76+
}()
77+
78+
return result
79+
}
80+
81+
// IdlePeriod is the initial period that McGinley Dynamic yield any results.
82+
func (m *McGinleyDynamic[T]) IdlePeriod() int {
83+
return 0
84+
}
85+
86+
// String is the string representation of the McGinley Dynamic.
87+
func (m *McGinleyDynamic[T]) String() string {
88+
return fmt.Sprintf("MD(%d)", m.Period)
89+
}

trend/mcginley_dynamic_test.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// Copyright (c) 2021-2026 Onur Cinar.
2+
// The source code is provided under GNU AGPLv3 License.
3+
// https://github.com/cinar/indicator
4+
5+
package trend_test
6+
7+
import (
8+
"math"
9+
"testing"
10+
11+
"github.com/cinar/indicator/v2/helper"
12+
"github.com/cinar/indicator/v2/trend"
13+
)
14+
15+
func TestMcGinleyDynamic(t *testing.T) {
16+
type Data struct {
17+
Close float64 `header:"Close"`
18+
Expected float64 `header:"Expected"`
19+
}
20+
21+
input, err := helper.ReadFromCsvFile[Data]("testdata/mcginley_dynamic.csv")
22+
if err != nil {
23+
t.Fatal(err)
24+
}
25+
26+
inputs := helper.Duplicate(input, 2)
27+
closings := helper.Map(inputs[0], func(d *Data) float64 { return d.Close })
28+
29+
ind := trend.NewMcGinleyDynamic[float64]()
30+
actuals := ind.Compute(closings)
31+
actuals = helper.RoundDigits(actuals, 6)
32+
33+
inputs[1] = helper.Skip(inputs[1], ind.IdlePeriod())
34+
35+
for data := range inputs[1] {
36+
actual, ok := <-actuals
37+
if !ok {
38+
t.Fatal("actuals channel closed early")
39+
}
40+
if actual != data.Expected {
41+
t.Fatalf("actual %v expected %v", actual, data.Expected)
42+
}
43+
}
44+
}
45+
46+
func TestNewMcGinleyDynamic(t *testing.T) {
47+
ind := trend.NewMcGinleyDynamic[float64]()
48+
if ind.Period != 14 {
49+
t.Fatalf("expected 14 but got %d", ind.Period)
50+
}
51+
}
52+
53+
func TestNewMcGinleyDynamicWithPeriod(t *testing.T) {
54+
ind := trend.NewMcGinleyDynamicWithPeriod[float64](20)
55+
if ind.Period != 20 {
56+
t.Fatalf("expected 20 but got %d", ind.Period)
57+
}
58+
}
59+
60+
func TestMcGinleyDynamicString(t *testing.T) {
61+
ind := trend.NewMcGinleyDynamicWithPeriod[float64](14)
62+
if ind.String() != "MD(14)" {
63+
t.Fatalf("expected MD(14) but got %s", ind.String())
64+
}
65+
}
66+
67+
func TestMcGinleyDynamicZero(t *testing.T) {
68+
closings := helper.SliceToChan([]float64{0, 10, 20})
69+
ind := trend.NewMcGinleyDynamicWithPeriod[float64](14)
70+
actuals := ind.Compute(closings)
71+
72+
if <-actuals != 0 {
73+
t.Fatal("expected 0")
74+
}
75+
if <-actuals != 10 {
76+
t.Fatal("expected 10")
77+
}
78+
// (20-10) / (14 * (20/10)^4) = 10 / (14 * 16) = 10 / 224 = 0.044642857
79+
// 10 + 0.044642857 = 10.044642857
80+
actual := <-actuals
81+
expected := 10.044642857
82+
if math.Abs(float64(actual)-expected) > 0.000001 {
83+
t.Fatalf("expected %v but got %v", expected, actual)
84+
}
85+
}

0 commit comments

Comments
 (0)