Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Envelope indicator is added. #232

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ The following list of indicators are currently supported by this package:
- [Balance of Power (BoP)](trend/README.md#type-bop)
- Chande Forecast Oscillator (CFO)
- [Community Channel Index (CCI)](trend/README.md#type-cci)
- [Envelope](trend/README.md#type-envelope)
- [Hull Moving Average (HMA)](trend/README.md#type-hma)
- [Double Exponential Moving Average (DEMA)](trend/README.md#type-dema)
- [Exponential Moving Average (EMA)](trend/README.md#type-ema)
Expand Down
8 changes: 4 additions & 4 deletions strategy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ The information provided on this project is strictly for informational purposes
- [type Action](<#Action>)
- [func \(a Action\) Annotation\(\) string](<#Action.Annotation>)
- [type AndStrategy](<#AndStrategy>)
- [func NewAndStrategy\(name string\) \*AndStrategy](<#NewAndStrategy>)
- [func NewAndStrategy\(name string, strategies ...Strategy\) \*AndStrategy](<#NewAndStrategy>)
- [func \(a \*AndStrategy\) Compute\(snapshots \<\-chan \*asset.Snapshot\) \<\-chan Action](<#AndStrategy.Compute>)
- [func \(a \*AndStrategy\) Name\(\) string](<#AndStrategy.Name>)
- [func \(a \*AndStrategy\) Report\(c \<\-chan \*asset.Snapshot\) \*helper.Report](<#AndStrategy.Report>)
Expand All @@ -51,7 +51,7 @@ The information provided on this project is strictly for informational purposes
- [func \(a \*MajorityStrategy\) Name\(\) string](<#MajorityStrategy.Name>)
- [func \(a \*MajorityStrategy\) Report\(c \<\-chan \*asset.Snapshot\) \*helper.Report](<#MajorityStrategy.Report>)
- [type OrStrategy](<#OrStrategy>)
- [func NewOrStrategy\(name string\) \*OrStrategy](<#NewOrStrategy>)
- [func NewOrStrategy\(name string, strategies ...Strategy\) \*OrStrategy](<#NewOrStrategy>)
- [func \(a \*OrStrategy\) Compute\(snapshots \<\-chan \*asset.Snapshot\) \<\-chan Action](<#OrStrategy.Compute>)
- [func \(a \*OrStrategy\) Name\(\) string](<#OrStrategy.Name>)
- [func \(a \*OrStrategy\) Report\(c \<\-chan \*asset.Snapshot\) \*helper.Report](<#OrStrategy.Report>)
Expand Down Expand Up @@ -194,7 +194,7 @@ type AndStrategy struct {
### func [NewAndStrategy](<https://github.com/cinar/indicator/blob/master/strategy/and_strategy.go#L26>)

```go
func NewAndStrategy(name string) *AndStrategy
func NewAndStrategy(name string, strategies ...Strategy) *AndStrategy
```

NewAndStrategy function initializes an empty and strategies group with the given name.
Expand Down Expand Up @@ -347,7 +347,7 @@ type OrStrategy struct {
### func [NewOrStrategy](<https://github.com/cinar/indicator/blob/master/strategy/or_strategy.go#L23>)

```go
func NewOrStrategy(name string) *OrStrategy
func NewOrStrategy(name string, strategies ...Strategy) *OrStrategy
```

NewOrStrategy function initializes an empty or strategies group with the given name.
Expand Down
88 changes: 88 additions & 0 deletions trend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@ The information provided on this project is strictly for informational purposes
- [func \(e \*Ema\[T\]\) Compute\(c \<\-chan T\) \<\-chan T](<#Ema[T].Compute>)
- [func \(e \*Ema\[T\]\) IdlePeriod\(\) int](<#Ema[T].IdlePeriod>)
- [func \(e \*Ema\[T\]\) String\(\) string](<#Ema[T].String>)
- [type Envelope](<#Envelope>)
- [func NewEnvelope\[T helper.Number\]\(ma Ma\[T\], percentage T\) \*Envelope\[T\]](<#NewEnvelope>)
- [func NewEnvelopeWithEma\[T helper.Number\]\(\) \*Envelope\[T\]](<#NewEnvelopeWithEma>)
- [func NewEnvelopeWithSma\[T helper.Number\]\(\) \*Envelope\[T\]](<#NewEnvelopeWithSma>)
- [func \(e \*Envelope\[T\]\) Compute\(closings \<\-chan T\) \(\<\-chan T, \<\-chan T, \<\-chan T\)](<#Envelope[T].Compute>)
- [func \(e \*Envelope\[T\]\) IdlePeriod\(\) int](<#Envelope[T].IdlePeriod>)
- [func \(e \*Envelope\[T\]\) String\(\) string](<#Envelope[T].String>)
- [type Hma](<#Hma>)
- [func NewHmaWithPeriod\[T helper.Number\]\(period int\) \*Hma\[T\]](<#NewHmaWithPeriod>)
- [func \(h \*Hma\[T\]\) Compute\(values \<\-chan T\) \<\-chan T](<#Hma[T].Compute>)
Expand Down Expand Up @@ -172,6 +179,18 @@ const (
)
```

<a name="DefaultEnvelopePercentage"></a>

```go
const (
// DefaultEnvelopePercentage is the default envelope percentage of 20%.
DefaultEnvelopePercentage = 20

// DefaultEnvelopePeriod is the default envelope period of 20.
DefaultEnvelopePeriod = 20
)
```

<a name="DefaultKamaErPeriod"></a>

```go
Expand Down Expand Up @@ -627,6 +646,75 @@ func (e *Ema[T]) String() string

String is the string representation of the EMA.

<a name="Envelope"></a>
## type [Envelope](<https://github.com/cinar/indicator/blob/master/trend/envelope.go#L22-L28>)

Envelope represents the parameters neededd to calcualte the Envelope.

```go
type Envelope[T helper.Number] struct {
// Ma is the moving average used.
Ma Ma[T]

// Percentage is the envelope percentage.
Percentage T
}
```

<a name="NewEnvelope"></a>
### func [NewEnvelope](<https://github.com/cinar/indicator/blob/master/trend/envelope.go#L31>)

```go
func NewEnvelope[T helper.Number](ma Ma[T], percentage T) *Envelope[T]
```

NewEnvelope function initializes a new Envelope instance with the default parameters.

<a name="NewEnvelopeWithEma"></a>
### func [NewEnvelopeWithEma](<https://github.com/cinar/indicator/blob/master/trend/envelope.go#L47>)

```go
func NewEnvelopeWithEma[T helper.Number]() *Envelope[T]
```

NewEnvelopeWithEma function initializes a new Envelope instance using EMA.

<a name="NewEnvelopeWithSma"></a>
### func [NewEnvelopeWithSma](<https://github.com/cinar/indicator/blob/master/trend/envelope.go#L39>)

```go
func NewEnvelopeWithSma[T helper.Number]() *Envelope[T]
```

NewEnvelopeWithSma function initalizes a new Envelope instance using SMA.

<a name="Envelope[T].Compute"></a>
### func \(\*Envelope\[T\]\) [Compute](<https://github.com/cinar/indicator/blob/master/trend/envelope.go#L55>)

```go
func (e *Envelope[T]) Compute(closings <-chan T) (<-chan T, <-chan T, <-chan T)
```

Compute function takes a channel of numbers and computes the Envelope over the specified period.

<a name="Envelope[T].IdlePeriod"></a>
### func \(\*Envelope\[T\]\) [IdlePeriod](<https://github.com/cinar/indicator/blob/master/trend/envelope.go#L75>)

```go
func (e *Envelope[T]) IdlePeriod() int
```

IdlePeriod is the initial period that Envelope yield any results.

<a name="Envelope[T].String"></a>
### func \(\*Envelope\[T\]\) [String](<https://github.com/cinar/indicator/blob/master/trend/envelope.go#L80>)

```go
func (e *Envelope[T]) String() string
```

String is the string representation of the Envelope.

<a name="Hma"></a>
## type [Hma](<https://github.com/cinar/indicator/blob/master/trend/hma.go#L21-L30>)

Expand Down
82 changes: 82 additions & 0 deletions trend/envelope.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright (c) 2021-2024 Onur Cinar.
// The source code is provided under GNU AGPLv3 License.
// https://github.com/cinar/indicator

package trend

import (
"fmt"

"github.com/cinar/indicator/v2/helper"
)

const (
// DefaultEnvelopePercentage is the default envelope percentage of 20%.
DefaultEnvelopePercentage = 20

// DefaultEnvelopePeriod is the default envelope period of 20.
DefaultEnvelopePeriod = 20
)

// Envelope represents the parameters neededd to calcualte the Envelope.
type Envelope[T helper.Number] struct {
// Ma is the moving average used.
Ma Ma[T]

// Percentage is the envelope percentage.
Percentage T
}

// NewEnvelope function initializes a new Envelope instance with the default parameters.
func NewEnvelope[T helper.Number](ma Ma[T], percentage T) *Envelope[T] {
return &Envelope[T]{
Ma: ma,
Percentage: percentage,
}
}

// NewEnvelopeWithSma function initalizes a new Envelope instance using SMA.
func NewEnvelopeWithSma[T helper.Number]() *Envelope[T] {
return NewEnvelope(
NewSmaWithPeriod[T](DefaultEnvelopePeriod),
DefaultEnvelopePercentage,
)
}

// NewEnvelopeWithEma function initializes a new Envelope instance using EMA.
func NewEnvelopeWithEma[T helper.Number]() *Envelope[T] {
return NewEnvelope(
NewEmaWithPeriod[T](DefaultEnvelopePeriod),
DefaultEnvelopePercentage,
)
}

// Compute function takes a channel of numbers and computes the Envelope over the specified period.
func (e *Envelope[T]) Compute(closings <-chan T) (<-chan T, <-chan T, <-chan T) {
middleSplice := helper.Duplicate(
e.Ma.Compute(closings),
3,
)

upper := helper.MultiplyBy(
middleSplice[0],
1+(e.Percentage/100),
)

lower := helper.MultiplyBy(
middleSplice[2],
1-(e.Percentage/100),
)

return upper, middleSplice[1], lower
}

// IdlePeriod is the initial period that Envelope yield any results.
func (e *Envelope[T]) IdlePeriod() int {
return e.Ma.IdlePeriod()
}

// String is the string representation of the Envelope.
func (e *Envelope[T]) String() string {
return fmt.Sprintf("Envelope(%s,%v)", e.Ma.String(), e.Percentage)
}
95 changes: 95 additions & 0 deletions trend/envelope_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright (c) 2021-2024 Onur Cinar.
// The source code is provided under GNU AGPLv3 License.
// https://github.com/cinar/indicator

package trend_test

import (
"testing"

"github.com/cinar/indicator/v2/helper"
"github.com/cinar/indicator/v2/trend"
)

func TestEnvelopeWithSma(t *testing.T) {
type Data struct {
Close float64
Upper float64
Middle float64
Lower float64
}

input, err := helper.ReadFromCsvFile[Data]("testdata/envelope_sma.csv", true)
if err != nil {
t.Fatal(err)
}

inputs := helper.Duplicate(input, 4)
closing := helper.Map(inputs[0], func(d *Data) float64 { return d.Close })
expectedUpper := helper.Map(inputs[1], func(d *Data) float64 { return d.Upper })
expectedMiddle := helper.Map(inputs[2], func(d *Data) float64 { return d.Middle })
expectedLower := helper.Map(inputs[3], func(d *Data) float64 { return d.Lower })

envelope := trend.NewEnvelopeWithSma[float64]()
actualUpper, actualMiddle, actualLower := envelope.Compute(closing)

actualUpper = helper.RoundDigits(actualUpper, 2)
actualMiddle = helper.RoundDigits(actualMiddle, 2)
actualLower = helper.RoundDigits(actualLower, 2)

expectedUpper = helper.Skip(expectedUpper, envelope.IdlePeriod())
expectedMiddle = helper.Skip(expectedMiddle, envelope.IdlePeriod())
expectedLower = helper.Skip(expectedLower, envelope.IdlePeriod())

err = helper.CheckEquals(actualUpper, expectedUpper, actualMiddle, expectedMiddle, actualLower, expectedLower)
if err != nil {
t.Fatal(err)
}
}

func TestEnvelopeWithEma(t *testing.T) {
type Data struct {
Close float64
Upper float64
Middle float64
Lower float64
}

input, err := helper.ReadFromCsvFile[Data]("testdata/envelope_ema.csv", true)
if err != nil {
t.Fatal(err)
}

inputs := helper.Duplicate(input, 4)
closing := helper.Map(inputs[0], func(d *Data) float64 { return d.Close })
expectedUpper := helper.Map(inputs[1], func(d *Data) float64 { return d.Upper })
expectedMiddle := helper.Map(inputs[2], func(d *Data) float64 { return d.Middle })
expectedLower := helper.Map(inputs[3], func(d *Data) float64 { return d.Lower })

envelope := trend.NewEnvelopeWithEma[float64]()
actualUpper, actualMiddle, actualLower := envelope.Compute(closing)

actualUpper = helper.RoundDigits(actualUpper, 2)
actualMiddle = helper.RoundDigits(actualMiddle, 2)
actualLower = helper.RoundDigits(actualLower, 2)

expectedUpper = helper.Skip(expectedUpper, envelope.IdlePeriod())
expectedMiddle = helper.Skip(expectedMiddle, envelope.IdlePeriod())
expectedLower = helper.Skip(expectedLower, envelope.IdlePeriod())

err = helper.CheckEquals(actualUpper, expectedUpper, actualMiddle, expectedMiddle, actualLower, expectedLower)
if err != nil {
t.Fatal(err)
}
}

func TestEnvelopeString(t *testing.T) {
expected := "Envelope(SMA(1),2)"

envelope := trend.NewEnvelope(trend.NewSmaWithPeriod[float64](1), 2)
actual := envelope.String()

if actual != expected {
t.Fatalf("actual %v expected %v", actual, expected)
}
}
Loading
Loading