Skip to content

Commit

Permalink
Balance of Power (BoP) strategy added.
Browse files Browse the repository at this point in the history
  • Loading branch information
cinar committed Dec 25, 2023
1 parent e217512 commit e085aa4
Show file tree
Hide file tree
Showing 8 changed files with 484 additions and 24 deletions.
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ You can find the [v1 version](https://github.com/cinar/indicator) of the library

The [v2 version](https://github.com/cinar/indicator/tree/v2) is a complete rewrite of the library with the following goals:

- Achieving and maintaining 100% code coverage, along with test files for each indicator and strategy.
- Achieving and maintaining minimum of 90% code coverage.
- Having test data in CSV format for each indicator and strategy for each validation.
- Operating on data streams (Go channels) for both inputs and outputs. If you prefer using slices, helper functions like [helper.SliceToChan](helper/README.md#func-slicetochan) and [helper.ChanToSlice](helper/README.md#func-chantoslice) are available. Alternatively, you can still use the `v1 version`.
- Having each indicator and strategy fully configurable with no preset values.
- Supporting all numeric formats using Golang generics.

Not everything has been fully ported from `v1 version` to `v2 version`. Any indicator or strategy without a link to documentation is not currently implemented in the `v2 version`. Your contributions are highly welcomed. Feel free to contribute to the project and help us expand the library.
Expand All @@ -28,7 +30,7 @@ The following list of indicators are currently supported by this package:

- [Absolute Price Oscillator (APO)](trend/README.md#type-apo)
- [Aroon Indicator](trend/README.md#type-aroon)
- [Balance of Power (BOP)](trend/README.md#type-bop)
- [Balance of Power (BoP)](trend/README.md#type-bop)
- Chande Forecast Oscillator (CFO)
- Community Channel Index (CMI)
- Double Exponential Moving Average (DEMA)
Expand Down Expand Up @@ -99,8 +101,9 @@ The following list of strategies are currently supported by this package:

### Trend Strategies

- [Absolute Price Oscillator Strategy](strategy/README.md#type-apostrategy)
- [Absolute Price Oscillator (APO) Strategy](strategy/README.md#type-apostrategy)
- [Aroon Strategy](strategy/README.md#type-aroonstrategy)
- [Balance of Power (BoP) Strategy](strategy/README.md#type-bopstrategy)
- Chande Forecast Oscillator Strategy
- KDJ Strategy
- MACD Strategy
Expand Down
1 change: 1 addition & 0 deletions pre-commit
21 changes: 0 additions & 21 deletions pre-commit.sh

This file was deleted.

56 changes: 56 additions & 0 deletions strategy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ The information provided on this project is strictly for informational purposes
- [type Backtest](<#Backtest>)
- [func NewBacktest\(repository asset.Repository, outputDir string\) \*Backtest](<#NewBacktest>)
- [func \(b \*Backtest\) Run\(\) error](<#Backtest.Run>)
- [type BopStrategy](<#BopStrategy>)
- [func NewBopStrategy\(\) \*BopStrategy](<#NewBopStrategy>)
- [func \(b \*BopStrategy\) Compute\(c \<\-chan \*asset.Snapshot\) \<\-chan Action](<#BopStrategy.Compute>)
- [func \(\*BopStrategy\) Name\(\) string](<#BopStrategy.Name>)
- [func \(b \*BopStrategy\) Report\(c \<\-chan \*asset.Snapshot\) \*helper.Report](<#BopStrategy.Report>)
- [type BuyAndHoldStrategy](<#BuyAndHoldStrategy>)
- [func NewBuyAndHoldStrategy\(\) \*BuyAndHoldStrategy](<#NewBuyAndHoldStrategy>)
- [func \(b \*BuyAndHoldStrategy\) Compute\(snapshots \<\-chan \*asset.Snapshot\) \<\-chan Action](<#BuyAndHoldStrategy.Compute>)
Expand Down Expand Up @@ -272,6 +277,57 @@ func (b *Backtest) Run() error

Run executes a comprehensive performance evaluation of the designated strategies, applied to a specified collection of assets. In the absence of explicitly defined assets, encompasses all assets within the repository. Likewise, in the absence of explicitly defined strategies, encompasses all the registered strategies.

<a name="BopStrategy"></a>
## type [BopStrategy](<https://github.com/cinar/indicator/blob/v2/strategy/bop_strategy.go#L17-L23>)

BopStrategy gauges the strength of buying and selling forces using the Balance of Power \(BoP\) indicator. A positive BoP value suggests an upward trend, while a negative value indicates a downward trend. A BoP value of zero implies equilibrium between the two forces.

```go
type BopStrategy struct {
Strategy

// Bop represents the configuration parameters for calculating the
// Balance of Power (BoP).
Bop *trend.Bop[float64]
}
```

<a name="NewBopStrategy"></a>
### func [NewBopStrategy](<https://github.com/cinar/indicator/blob/v2/strategy/bop_strategy.go#L26>)

```go
func NewBopStrategy() *BopStrategy
```

NewBopStrategy function initializes a new BoP strategy instance with the default parameters.

<a name="BopStrategy.Compute"></a>
### func \(\*BopStrategy\) [Compute](<https://github.com/cinar/indicator/blob/v2/strategy/bop_strategy.go#L39>)

```go
func (b *BopStrategy) Compute(c <-chan *asset.Snapshot) <-chan Action
```

Compute processes the provided asset snapshots and generates a stream of actionable recommendations.

<a name="BopStrategy.Name"></a>
### func \(\*BopStrategy\) [Name](<https://github.com/cinar/indicator/blob/v2/strategy/bop_strategy.go#L33>)

```go
func (*BopStrategy) Name() string
```

Name returns the name of the strategy.

<a name="BopStrategy.Report"></a>
### func \(\*BopStrategy\) [Report](<https://github.com/cinar/indicator/blob/v2/strategy/bop_strategy.go#L64>)

```go
func (b *BopStrategy) Report(c <-chan *asset.Snapshot) *helper.Report
```

Report processes the provided asset snapshots and generates a report annotated with the recommended actions.

<a name="BuyAndHoldStrategy"></a>
## type [BuyAndHoldStrategy](<https://github.com/cinar/indicator/blob/v2/strategy/buy_and_hold_strategy.go#L16-L18>)

Expand Down
1 change: 1 addition & 0 deletions strategy/backtest.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ func (b *Backtest) allStrategies() []Strategy {
NewApoStrategy(),
NewAroonStrategy(),
NewBuyAndHoldStrategy(),
NewBopStrategy(),
}
}

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

package strategy

import (
"github.com/cinar/indicator/asset"
"github.com/cinar/indicator/helper"
"github.com/cinar/indicator/trend"
)

// BopStrategy gauges the strength of buying and selling forces using the
// Balance of Power (BoP) indicator. A positive BoP value suggests an
// upward trend, while a negative value indicates a downward trend. A
// BoP value of zero implies equilibrium between the two forces.
type BopStrategy struct {
Strategy

// Bop represents the configuration parameters for calculating the
// Balance of Power (BoP).
Bop *trend.Bop[float64]
}

// NewBopStrategy function initializes a new BoP strategy instance with the default parameters.
func NewBopStrategy() *BopStrategy {
return &BopStrategy{
Bop: trend.NewBop[float64](),
}
}

// Name returns the name of the strategy.
func (*BopStrategy) Name() string {
return "BoP Strategy"
}

// Compute processes the provided asset snapshots and generates a
// stream of actionable recommendations.
func (b *BopStrategy) Compute(c <-chan *asset.Snapshot) <-chan Action {
snapshots := helper.Duplicate(c, 4)

openings := asset.SnapshotsAsOpenings(snapshots[0])
highs := asset.SnapshotsAsHighs(snapshots[1])
lows := asset.SnapshotsAsLows(snapshots[2])
closings := asset.SnapshotsAsClosings(snapshots[3])

bops := b.Bop.Compute(openings, highs, lows, closings)

return NormalizeActions(helper.Map(bops, func(bop float64) Action {
if bop > 0 {
return Buy
}

if bop < 0 {
return Sell
}

return Hold
}))
}

// Report processes the provided asset snapshots and generates a
// report annotated with the recommended actions.
func (b *BopStrategy) Report(c <-chan *asset.Snapshot) *helper.Report {
//
// snapshots[0] -> dates
// snapshots[1] -> openings |
// snapshots[2] -> highs |
// snapshots[3] -> lows |
// snapshots[4] -> closings[1] |> bop
// closings[0] -> closings
// snapshots[5] -> actions -> annotations
// outcomes
//
snapshots := helper.Duplicate(c, 6)

dates := asset.SnapshotsAsDates(snapshots[0])
openings := asset.SnapshotsAsOpenings(snapshots[1])
highs := asset.SnapshotsAsHighs(snapshots[2])
lows := asset.SnapshotsAsLows(snapshots[3])
closings := helper.Duplicate(asset.SnapshotsAsClosings(snapshots[4]), 2)

bop := b.Bop.Compute(openings, highs, lows, closings[1])

actions, outcomes := ComputeWithOutcome(b, snapshots[5])
annotations := ActionsToAnnotations(actions)
outcomes = helper.MultiplyBy(outcomes, 100)

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

report.AddColumn(helper.NewNumericReportColumn("Close", closings[0]))
report.AddColumn(helper.NewNumericReportColumn("BoP", bop), 1)
report.AddColumn(helper.NewAnnotationReportColumn(annotations), 0, 1)

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

return report
}
68 changes: 68 additions & 0 deletions strategy/bop_strategy_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright (c) 2021-2023 Onur Cinar.
// The source code is provided under GNU AGPLv3 License.
// https://github.com/cinar/indicator

package strategy_test

import (
"os"
"testing"

"github.com/cinar/indicator/asset"
"github.com/cinar/indicator/helper"
"github.com/cinar/indicator/strategy"
)

func TestBopStrategy(t *testing.T) {
type Result struct {
Action strategy.Action
Outcome float64
}

snapshots, err := helper.ReadFromCsvFile[asset.Snapshot]("testdata/repository/brk-b.csv", true)
if err != nil {
t.Fatal(err)
}

results, err := helper.ReadFromCsvFile[Result]("testdata/bop_strategy.csv", true)
if err != nil {
t.Fatal(err)
}

bop := strategy.NewBopStrategy()
actions, outcomes := strategy.ComputeWithOutcome(bop, snapshots)

outcomes = helper.RoundDigits(outcomes, 2)

for result := range results {
action := <-actions
outcome := <-outcomes

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

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

func TestBopStrategyReport(t *testing.T) {
snapshots, err := helper.ReadFromCsvFile[asset.Snapshot]("testdata/repository/brk-b.csv", true)
if err != nil {
t.Fatal(err)
}

bop := strategy.NewBopStrategy()

report := bop.Report(snapshots)

fileName := "bop_strategy.html"
defer os.Remove(fileName)

err = report.WriteToFile(fileName)
if err != nil {
t.Fatal(err)
}
}
Loading

0 comments on commit e085aa4

Please sign in to comment.