-
-
Notifications
You must be signed in to change notification settings - Fork 104
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
# Describe Request Force Index Strategy added. # Change Type New strategy. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Updated documentation to reflect enhancements in version 2 of the Indicator Go module. - Introduced the `ForceIndexStrategy`, expanding the library's volume strategies. - Added dedicated test data in CSV format for easier validation of indicators and strategies. - Streamlined data handling with support for Go channels and helper functions. - **Bug Fixes** - Enhanced overall code quality and testability with a minimum of 90% code coverage. - **Documentation** - Comprehensive updates to installation, usage instructions, and backtesting functionality. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
- Loading branch information
Showing
6 changed files
with
483 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
// Copyright (c) 2021-2024 Onur Cinar. | ||
// The source code is provided under GNU AGPLv3 License. | ||
// https://github.com/cinar/indicator | ||
|
||
package volume | ||
|
||
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/volume" | ||
) | ||
|
||
// ForceIndexStrategy represents the configuration parameters for calculating the Force Index strategy. | ||
// It recommends a Buy action when it crosses above zero, and a Sell action when it crosses below zero. | ||
type ForceIndexStrategy struct { | ||
// ForceIndex is the Force Index instance. | ||
ForceIndex *volume.Fi[float64] | ||
} | ||
|
||
// NewForceIndexStrategy function initializes a new Force Index strategy instance with the default parameters. | ||
func NewForceIndexStrategy() *ForceIndexStrategy { | ||
return NewForceIndexStrategyWith( | ||
volume.DefaultFiPeriod, | ||
) | ||
} | ||
|
||
// NewForceIndexStrategyWith function initializes a new Force Index strategy instance with the given parameters. | ||
func NewForceIndexStrategyWith(period int) *ForceIndexStrategy { | ||
return &ForceIndexStrategy{ | ||
ForceIndex: volume.NewFiWithPeriod[float64](period), | ||
} | ||
} | ||
|
||
// Name returns the name of the strategy. | ||
func (f *ForceIndexStrategy) Name() string { | ||
return fmt.Sprintf("Force Index Strategy (%d)", f.ForceIndex.IdlePeriod()+1) | ||
} | ||
|
||
// Compute processes the provided asset snapshots and generates a stream of actionable recommendations. | ||
func (f *ForceIndexStrategy) Compute(snapshots <-chan *asset.Snapshot) <-chan strategy.Action { | ||
snapshotsSplice := helper.Duplicate(snapshots, 2) | ||
|
||
closings := asset.SnapshotsAsClosings(snapshotsSplice[0]) | ||
volumes := asset.SnapshotsAsVolumes(snapshotsSplice[1]) | ||
|
||
fis := f.ForceIndex.Compute(closings, volumes) | ||
|
||
actions := helper.Map(fis, func(fi float64) strategy.Action { | ||
if fi > 0 { | ||
return strategy.Buy | ||
} | ||
|
||
if fi < 0 { | ||
return strategy.Sell | ||
} | ||
|
||
return strategy.Hold | ||
}) | ||
|
||
// Force Index starts only after a full period. | ||
actions = helper.Shift(actions, f.ForceIndex.IdlePeriod(), strategy.Hold) | ||
|
||
return actions | ||
} | ||
|
||
// Report processes the provided asset snapshots and generates a report annotated with the recommended actions. | ||
func (f *ForceIndexStrategy) Report(c <-chan *asset.Snapshot) *helper.Report { | ||
// | ||
// snapshots[0] -> dates | ||
// snapshots[1] -> closings[0] -> closings | ||
// closings[1] -> force index | ||
// snapshots[2] -> volumes | ||
// snapshots[3] -> actions -> annotations | ||
// -> outcomes | ||
// | ||
snapshots := helper.Duplicate(c, 4) | ||
|
||
dates := helper.Skip(asset.SnapshotsAsDates(snapshots[0]), f.ForceIndex.IdlePeriod()) | ||
|
||
closingsSplice := helper.Duplicate( | ||
asset.SnapshotsAsClosings(snapshots[1]), | ||
2, | ||
) | ||
volumes := asset.SnapshotsAsVolumes(snapshots[2]) | ||
|
||
fis := f.ForceIndex.Compute(closingsSplice[0], volumes) | ||
|
||
closingsSplice[1] = helper.Skip(closingsSplice[1], f.ForceIndex.IdlePeriod()) | ||
|
||
actions, outcomes := strategy.ComputeWithOutcome(f, snapshots[3]) | ||
actions = helper.Skip(actions, f.ForceIndex.IdlePeriod()) | ||
outcomes = helper.Skip(outcomes, f.ForceIndex.IdlePeriod()) | ||
|
||
annotations := strategy.ActionsToAnnotations(actions) | ||
outcomes = helper.MultiplyBy(outcomes, 100) | ||
|
||
report := helper.NewReport(f.Name(), dates) | ||
report.AddChart() | ||
report.AddChart() | ||
|
||
report.AddColumn(helper.NewNumericReportColumn("Close", closingsSplice[1])) | ||
report.AddColumn(helper.NewNumericReportColumn("Force Index", fis), 1) | ||
report.AddColumn(helper.NewAnnotationReportColumn(annotations), 0, 1) | ||
|
||
report.AddColumn(helper.NewNumericReportColumn("Outcome", outcomes), 2) | ||
|
||
return report | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
// Copyright (c) 2021-2024 Onur Cinar. | ||
// The source code is provided under GNU AGPLv3 License. | ||
// https://github.com/cinar/indicator | ||
|
||
package volume_test | ||
|
||
import ( | ||
"os" | ||
"testing" | ||
|
||
"github.com/cinar/indicator/v2/asset" | ||
"github.com/cinar/indicator/v2/helper" | ||
"github.com/cinar/indicator/v2/strategy" | ||
"github.com/cinar/indicator/v2/strategy/volume" | ||
) | ||
|
||
func TestForceIndexStrategy(t *testing.T) { | ||
snapshots, err := helper.ReadFromCsvFile[asset.Snapshot]("testdata/brk-b.csv", true) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
results, err := helper.ReadFromCsvFile[strategy.Result]("testdata/force_index_strategy.csv", true) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
expected := helper.Map(results, func(r *strategy.Result) strategy.Action { return r.Action }) | ||
|
||
fis := volume.NewForceIndexStrategy() | ||
actual := fis.Compute(snapshots) | ||
|
||
err = helper.CheckEquals(actual, expected) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
} | ||
|
||
func TestForceIndexStrategyReport(t *testing.T) { | ||
snapshots, err := helper.ReadFromCsvFile[asset.Snapshot]("testdata/brk-b.csv", true) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
fis := volume.NewForceIndexStrategy() | ||
report := fis.Report(snapshots) | ||
|
||
fileName := "force_index_strategy.html" | ||
defer os.Remove(fileName) | ||
|
||
err = report.WriteToFile(fileName) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
} |
Oops, something went wrong.