-
-
Notifications
You must be signed in to change notification settings - Fork 103
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
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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()) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Undefined variable 't' in the Report method The variable To fix this issue, replace instances of - 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
|
||
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove incorrect TSI and Signal computations The
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.
|
||
|
||
report.AddColumn(helper.NewNumericReportColumn("Outcome", outcomes), 2) | ||
|
||
return report | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
|
||
} |
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. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix typos in the type comment. There are typos in the comment for the 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
Suggested change
|
||||||
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. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 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
Suggested change
|
||||||
func NewEnvelopeWithSma[T helper.Number]() *Envelope[T] { | ||||||
return NewEnvelope( | ||||||
NewSmaWithPeriod[T](DefaultEnvelopePeriod), | ||||||
DefaultEnvelopePercentage, | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix type mismatch when passing There is a type mismatch error when passing Apply this diff to cast 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 Also applies to: 50-50 🧰 Tools🪛 golangci-lint
|
||||||
) | ||||||
} | ||||||
|
||||||
// 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Potential integer division may cause incorrect envelope calculations In lines 63 and 68, the expressions To ensure accurate calculations, consider converting 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 - type Envelope[T helper.Number] struct {
+ type Envelope[T helper.FloatNumber] struct { Where 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) | ||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codebase verification
Update
NewOrStrategy
Usages to Match New Signature and Revise DocumentationThe
NewOrStrategy
function signature has been updated to accept a variadicstrategies
parameter, but existing usages instrategy/or_strategy_test.go
have not been updated accordingly. This discrepancy can lead to compilation errors or unexpected behavior.Function Description Update:
Update Existing Usages:
🔗 Analysis chain
Update description for
NewOrStrategy
and consider backwards compatibilityThe function signature has been changed to accept multiple strategies, but the description hasn't been updated to reflect this change.
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:
Length of output: 277