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 trend indicator and Envelope strategy are added. #233

Merged
merged 4 commits into from
Oct 12, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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>)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

⚠️ Potential issue

Update NewOrStrategy Usages to Match New Signature and Revise Documentation

The NewOrStrategy function signature has been updated to accept a variadic strategies parameter, but existing usages in strategy/or_strategy_test.go have not been updated accordingly. This discrepancy can lead to compilation errors or unexpected behavior.

  • Function Description Update:

    // NewOrStrategy initializes an OrStrategy with the given name and a variable number of strategies.
    // It allows for flexible creation of composite OR strategies.
    func NewOrStrategy(name string, strategies ...Strategy) *OrStrategy
  • Update Existing Usages:

    // Example update in strategy/or_strategy_test.go
    or := strategy.NewOrStrategy("Or Strategy", strategy1, strategy2)
🔗 Analysis chain

Update description for NewOrStrategy and consider backwards compatibility

The function signature has been changed to accept multiple strategies, but the description hasn't been updated to reflect this change.

  1. Update the description to accurately reflect the new signature:
// NewOrStrategy initializes an OrStrategy with the given name and a variable number of strategies.
// It allows for flexible creation of composite OR strategies.
func NewOrStrategy(name string, strategies ...Strategy) *OrStrategy
  1. This is a breaking change. Consider providing a backwards-compatible alternative or updating the version number accordingly.

To assess the impact of this change, let's check for existing usages of NewOrStrategy:

This will help determine the extent of necessary updates in the codebase.

Also applies to: 350-350

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for existing usages of NewOrStrategy
rg 'NewOrStrategy\s*\(' --type go

Length of output: 277

- [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
120 changes: 120 additions & 0 deletions strategy/trend/envelope_strategy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// 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/asset"
"github.com/cinar/indicator/v2/helper"
"github.com/cinar/indicator/v2/strategy"
"github.com/cinar/indicator/v2/trend"
)

// EnvelopeStrategy represents the configuration parameters for calculating the Envelope strategy. When the closing
// is above the upper band suggests a Sell recommendation, and when the closing is below the lower band suggests a
// buy recommendation.
type EnvelopeStrategy struct {
// Envelope is the envelope indicator instance.
Envelope *trend.Envelope[float64]
}

// NewEnvelopeStrategy function initializes a new Envelope strategy with the default parameters.
func NewEnvelopeStrategy() *EnvelopeStrategy {
return NewEnvelopeStrategyWith(
trend.NewEnvelopeWithSma[float64](),
)
}

// NewEnvelopeStrategyWith function initializes a new Envelope strategy with the given Envelope instance.
func NewEnvelopeStrategyWith(envelope *trend.Envelope[float64]) *EnvelopeStrategy {
return &EnvelopeStrategy{
Envelope: envelope,
}
}

// Name returns the name of the strategy.
func (e *EnvelopeStrategy) Name() string {
return fmt.Sprintf("Envelope Strategy (%s)", e.Envelope.String())
}

// Compute processes the provided asset snapshots and generates a stream of actionable recommendations.
func (e *EnvelopeStrategy) Compute(snapshots <-chan *asset.Snapshot) <-chan strategy.Action {
closingsSplice := helper.Duplicate(
asset.SnapshotsAsClosings(snapshots),
2,
)

uppers, middles, lowers := e.Envelope.Compute(closingsSplice[0])
go helper.Drain(middles)

actions := helper.Operate3(uppers, lowers, closingsSplice[1], func(upper, lower, closing float64) strategy.Action {
// When the closing is below the lower band suggests a buy recommendation.
if closing < lower {
return strategy.Buy
}

// When the closing is above the upper band suggests a Sell recommendation.
if closing > upper {
return strategy.Sell
}

return strategy.Hold
})

// Envelope start only after a full period.
actions = helper.Shift(actions, e.Envelope.IdlePeriod(), strategy.Hold)

return actions
}

// Report processes the provided asset snapshots and generates a report annotated with the recommended actions.
func (e *EnvelopeStrategy) Report(c <-chan *asset.Snapshot) *helper.Report {
//
// snapshots[0] -> dates
// snapshots[1] -> closings[0] -> closings
// closings[1] -> envelope -> upper
// -> middle
// -> lower
// snapshots[2] -> actions -> annotations
// -> outcomes
//
snapshotsSplice := helper.Duplicate(c, 3)

dates := helper.Skip(
asset.SnapshotsAsDates(snapshotsSplice[0]),
e.Envelope.IdlePeriod(),
)

closingsSplice := helper.Duplicate(asset.SnapshotsAsClosings(snapshotsSplice[1]), 2)
closingsSplice[1] = helper.Skip(closingsSplice[1], t.IdlePeriod())

tsisSplice := helper.Duplicate(t.Tsi.Compute(closingsSplice[0]), 2)
tsisSplice[0] = helper.Skip(tsisSplice[0], t.Signal.IdlePeriod())

signals := t.Signal.Compute(tsisSplice[1])

actions, outcomes := strategy.ComputeWithOutcome(t, snapshotsSplice[2])
actions = helper.Skip(actions, t.IdlePeriod())
outcomes = helper.Skip(outcomes, t.IdlePeriod())

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Undefined variable 't' in the Report method

The variable t is used in lines 92-102 but is not defined within the scope of the Report method. This will lead to compilation errors.

To fix this issue, replace instances of t with e.Envelope or adjust accordingly. Here's the corrected code:

- closingsSplice[1] = helper.Skip(closingsSplice[1], t.IdlePeriod())
+ closingsSplice[1] = helper.Skip(closingsSplice[1], e.Envelope.IdlePeriod())

- tsisSplice := helper.Duplicate(t.Tsi.Compute(closingsSplice[0]), 2)
+ uppers, middles, lowers := e.Envelope.Compute(closingsSplice[0])

- tsisSplice[0] = helper.Skip(tsisSplice[0], t.Signal.IdlePeriod())
+ // Not applicable for EnvelopeStrategy; remove or replace as needed.

- signals := t.Signal.Compute(tsisSplice[1])
+ // Not applicable for EnvelopeStrategy; remove or replace as needed.

- actions, outcomes := strategy.ComputeWithOutcome(t, snapshotsSplice[2])
+ actions, outcomes := strategy.ComputeWithOutcome(e, snapshotsSplice[2])

- actions = helper.Skip(actions, t.IdlePeriod())
+ actions = helper.Skip(actions, e.Envelope.IdlePeriod())

- outcomes = helper.Skip(outcomes, t.IdlePeriod())
+ outcomes = helper.Skip(outcomes, e.Envelope.IdlePeriod())

Please ensure all references are correctly mapped to the EnvelopeStrategy or its Envelope field.

Committable suggestion was skipped due to low confidence.

annotations := strategy.ActionsToAnnotations(actions)
outcomes = helper.MultiplyBy(outcomes, 100)

report := helper.NewReport(t.Name(), dates)
report.AddChart()
report.AddChart()

report.AddColumn(helper.NewNumericReportColumn("Close", closingsSplice[1]))

report.AddColumn(helper.NewNumericReportColumn("TSI", tsisSplice[0]), 1)
report.AddColumn(helper.NewNumericReportColumn("Signal", signals), 1)

report.AddColumn(helper.NewAnnotationReportColumn(annotations), 0, 1)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove incorrect TSI and Signal computations

The Report method includes computations for TSI and Signal, which are not relevant to the EnvelopeStrategy. Specifically:

  • Lines 94-97 involve TSI and Signal computations.
  • Lines 112-113 add columns for "TSI" and "Signal" to the report.

Since the Envelope strategy does not use TSI or Signal, these sections should be removed or replaced with appropriate computations for the upper, middle, and lower bands of the Envelope. Here's how you might adjust the code:

- tsisSplice := helper.Duplicate(t.Tsi.Compute(closingsSplice[0]), 2)
- tsisSplice[0] = helper.Skip(tsisSplice[0], t.Signal.IdlePeriod())

- signals := t.Signal.Compute(tsisSplice[1])

...

- report.AddColumn(helper.NewNumericReportColumn("TSI", tsisSplice[0]), 1)
- report.AddColumn(helper.NewNumericReportColumn("Signal", signals), 1)
+ uppers, middles, lowers := e.Envelope.Compute(closingsSplice[0])
+ uppers = helper.Skip(uppers, e.Envelope.IdlePeriod())
+ lowers = helper.Skip(lowers, e.Envelope.IdlePeriod())

+ report.AddColumn(helper.NewNumericReportColumn("Upper Band", uppers), 1)
+ report.AddColumn(helper.NewNumericReportColumn("Lower Band", lowers), 1)

This adjustment aligns the report with the data relevant to the Envelope strategy.

Committable suggestion was skipped due to low confidence.


report.AddColumn(helper.NewNumericReportColumn("Outcome", outcomes), 2)

return report
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance report with relevant data columns

The report currently includes an "Outcome" column and placeholders for charts. Consider adding the middle band and improving chart annotations to better reflect the Envelope strategy's results.

You might modify the report setup as follows:

 report.AddColumn(helper.NewNumericReportColumn("Close", closingsSplice[1]))

+report.AddColumn(helper.NewNumericReportColumn("Middle Band", middles), 1)

 report.AddColumn(helper.NewNumericReportColumn("Upper Band", uppers), 1)
 report.AddColumn(helper.NewNumericReportColumn("Lower Band", lowers), 1)

 report.AddColumn(helper.NewAnnotationReportColumn(annotations), 0, 1)

 report.AddColumn(helper.NewNumericReportColumn("Outcome", outcomes), 2)

This will provide a more comprehensive view of the strategy's performance.

Committable suggestion was skipped due to low confidence.

}
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.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix typos in the type comment.

There are typos in the comment for the Envelope type. The words "neededd" and "calcualte" are misspelled.

Apply this diff to correct the typos:

-// Envelope represents the parameters neededd to calcualte the Envelope.
+// Envelope represents the parameters needed to calculate the Envelope.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Envelope represents the parameters neededd to calcualte the Envelope.
// Envelope represents the parameters needed to calculate 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.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Correct typo in function comment.

The word "initalizes" is misspelled; it should be "initializes".

Apply this diff to fix the typo:

-// NewEnvelopeWithSma function initalizes a new Envelope instance using SMA.
+// NewEnvelopeWithSma function initializes a new Envelope instance using SMA.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// NewEnvelopeWithSma function initalizes a new Envelope instance using SMA.
// NewEnvelopeWithSma function initializes a new Envelope instance using SMA.

func NewEnvelopeWithSma[T helper.Number]() *Envelope[T] {
return NewEnvelope(
NewSmaWithPeriod[T](DefaultEnvelopePeriod),
DefaultEnvelopePercentage,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix type mismatch when passing DefaultEnvelopePercentage to NewEnvelope.

There is a type mismatch error when passing DefaultEnvelopePercentage (an untyped int constant) as a parameter of type T in NewEnvelopeWithSma and NewEnvelopeWithEma. This causes a compilation error because DefaultEnvelopePercentage cannot be used as a value of type T without explicit conversion.

Apply this diff to cast DefaultEnvelopePercentage to type T:

 func NewEnvelopeWithSma[T helper.Number]() *Envelope[T] {
     return NewEnvelope(
         NewSmaWithPeriod[T](DefaultEnvelopePeriod),
-        DefaultEnvelopePercentage,
+        T(DefaultEnvelopePercentage),
     )
 }

 func NewEnvelopeWithEma[T helper.Number]() *Envelope[T] {
     return NewEnvelope(
         NewEmaWithPeriod[T](DefaultEnvelopePeriod),
-        DefaultEnvelopePercentage,
+        T(DefaultEnvelopePercentage),
     )
 }

This ensures that DefaultEnvelopePercentage is correctly converted to the type T expected by the NewEnvelope function.

Also applies to: 50-50

🧰 Tools
🪛 golangci-lint

42-42: cannot use DefaultEnvelopePercentage (untyped int constant 20) as T value in argument to NewEnvelope

(typecheck)

)
}

// 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),
)
Comment on lines +63 to +64
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Potential integer division may cause incorrect envelope calculations

In lines 63 and 68, the expressions 1 + (e.Percentage / 100) and 1 - (e.Percentage / 100) may result in integer division if T is an integer type. Integer division truncates decimals, potentially making the expressions evaluate to 1 + 0 or 1 - 0, which could lead to incorrect envelope calculations where the upper and lower bands mirror the middle band.

To ensure accurate calculations, consider converting e.Percentage to a floating-point type before performing the division, or constrain T to floating-point types.

Apply this diff to fix the issue:

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

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

Alternatively, constrain T to floating-point types:

- type Envelope[T helper.Number] struct {
+ type Envelope[T helper.FloatNumber] struct {

Where helper.FloatNumber is defined as:

type FloatNumber interface {
	~float32 | ~float64
}

Also applies to: 68-69


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)
}
Loading
Loading