Skip to content

Commit

Permalink
%B indicator is added. (#264)
Browse files Browse the repository at this point in the history
# Describe Request

%B indicator is added.

Fixed #254 

# Change Type

New indicator.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Introduced a new volatility indicator, `%B`, enhancing technical
analysis capabilities.
- Added methods for creating and computing the `%B` indicator, including
`NewPercentB` and `NewPercentBWithPeriod`.
- Updated Bollinger Bands functionality to allow custom periods with
`NewBollingerBandsWithPeriod`.

- **Bug Fixes**
	- Adjusted method signatures for improved clarity and usability.

- **Tests**
- Added tests for the `%B` indicator to validate functionality and
string representation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
  • Loading branch information
cinar authored Dec 24, 2024
1 parent 06e7424 commit 3ae91ac
Show file tree
Hide file tree
Showing 7 changed files with 518 additions and 5 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ The following list of indicators are currently supported by this package:

### 🎢 Volatility Indicators

- [%B](volatility/README.md#type-percentb)
- [Acceleration Bands](volatility/README.md#type-accelerationbands)
- [Actual True Range (ATR)](volatility/README.md#type-atr)
- [Bollinger Band Width](volatility/README.md#type-bollingerbandwidth)
Expand Down
4 changes: 2 additions & 2 deletions trend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ The information provided on this project is strictly for informational purposes
- [func \(v \*Vwma\[T\]\) IdlePeriod\(\) int](<#Vwma[T].IdlePeriod>)
- [type WeightedClose](<#WeightedClose>)
- [func NewWeightedClose\[T helper.Number\]\(\) \*WeightedClose\[T\]](<#NewWeightedClose>)
- [func \(w \*WeightedClose\[T\]\) Compute\(highs, lows, closes \<\-chan T\) \<\-chan T](<#WeightedClose[T].Compute>)
- [func \(\*WeightedClose\[T\]\) Compute\(highs, lows, closes \<\-chan T\) \<\-chan T](<#WeightedClose[T].Compute>)
- [func \(\*WeightedClose\[T\]\) IdlePeriod\(\) int](<#WeightedClose[T].IdlePeriod>)
- [func \(\*WeightedClose\[T\]\) String\(\) string](<#WeightedClose[T].String>)
- [type Wma](<#Wma>)
Expand Down Expand Up @@ -1847,7 +1847,7 @@ NewWeightedClose function initializes a new Weighted Close instance with the def
### func \(\*WeightedClose\[T\]\) [Compute](<https://github.com/cinar/indicator/blob/master/trend/weighted_close.go#L28>)

```go
func (w *WeightedClose[T]) Compute(highs, lows, closes <-chan T) <-chan T
func (*WeightedClose[T]) Compute(highs, lows, closes <-chan T) <-chan T
```

Compute function takes a channel of numbers and computes the Weighted Close over the specified period.
Expand Down
127 changes: 125 additions & 2 deletions volatility/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ The information provided on this project is strictly for informational purposes
- [func \(b \*BollingerBandWidth\[T\]\) IdlePeriod\(\) int](<#BollingerBandWidth[T].IdlePeriod>)
- [type BollingerBands](<#BollingerBands>)
- [func NewBollingerBands\[T helper.Number\]\(\) \*BollingerBands\[T\]](<#NewBollingerBands>)
- [func NewBollingerBandsWithPeriod\[T helper.Number\]\(period int\) \*BollingerBands\[T\]](<#NewBollingerBandsWithPeriod>)
- [func \(b \*BollingerBands\[T\]\) Compute\(c \<\-chan T\) \(\<\-chan T, \<\-chan T, \<\-chan T\)](<#BollingerBands[T].Compute>)
- [func \(b \*BollingerBands\[T\]\) IdlePeriod\(\) int](<#BollingerBands[T].IdlePeriod>)
- [type ChandelierExit](<#ChandelierExit>)
Expand All @@ -62,6 +63,12 @@ The information provided on this project is strictly for informational purposes
- [func NewMovingStdWithPeriod\[T helper.Number\]\(period int\) \*MovingStd\[T\]](<#NewMovingStdWithPeriod>)
- [func \(m \*MovingStd\[T\]\) Compute\(c \<\-chan T\) \<\-chan T](<#MovingStd[T].Compute>)
- [func \(m \*MovingStd\[T\]\) IdlePeriod\(\) int](<#MovingStd[T].IdlePeriod>)
- [type PercentB](<#PercentB>)
- [func NewPercentB\[T helper.Number\]\(\) \*PercentB\[T\]](<#NewPercentB>)
- [func NewPercentBWithPeriod\[T helper.Number\]\(period int\) \*PercentB\[T\]](<#NewPercentBWithPeriod>)
- [func \(p \*PercentB\[T\]\) Compute\(closings \<\-chan T\) \<\-chan T](<#PercentB[T].Compute>)
- [func \(p \*PercentB\[T\]\) IdlePeriod\(\) int](<#PercentB[T].IdlePeriod>)
- [func \(p \*PercentB\[T\]\) String\(\) string](<#PercentB[T].String>)
- [type Po](<#Po>)
- [func NewPo\[T helper.Number\]\(\) \*Po\[T\]](<#NewPo>)
- [func NewPoWithPeriod\[T helper.Number\]\(period int\) \*Po\[T\]](<#NewPoWithPeriod>)
Expand Down Expand Up @@ -386,8 +393,17 @@ func NewBollingerBands[T helper.Number]() *BollingerBands[T]

NewBollingerBands function initializes a new Bollinger Bands instance with the default parameters.

<a name="NewBollingerBandsWithPeriod"></a>
### func [NewBollingerBandsWithPeriod](<https://github.com/cinar/indicator/blob/master/volatility/bollinger_bands.go#L40>)

```go
func NewBollingerBandsWithPeriod[T helper.Number](period int) *BollingerBands[T]
```

NewBollingerBandsWithPeriod function initializes a new Bollinger Bands instance with the given period.

<a name="BollingerBands[T].Compute"></a>
### func \(\*BollingerBands\[T\]\) [Compute](<https://github.com/cinar/indicator/blob/master/volatility/bollinger_bands.go#L42>)
### func \(\*BollingerBands\[T\]\) [Compute](<https://github.com/cinar/indicator/blob/master/volatility/bollinger_bands.go#L47>)

```go
func (b *BollingerBands[T]) Compute(c <-chan T) (<-chan T, <-chan T, <-chan T)
Expand All @@ -396,7 +412,7 @@ func (b *BollingerBands[T]) Compute(c <-chan T) (<-chan T, <-chan T, <-chan T)
Compute function takes a channel of numbers and computes the Bollinger Bands over the specified period.

<a name="BollingerBands[T].IdlePeriod"></a>
### func \(\*BollingerBands\[T\]\) [IdlePeriod](<https://github.com/cinar/indicator/blob/master/volatility/bollinger_bands.go#L74>)
### func \(\*BollingerBands\[T\]\) [IdlePeriod](<https://github.com/cinar/indicator/blob/master/volatility/bollinger_bands.go#L79>)

```go
func (b *BollingerBands[T]) IdlePeriod() int
Expand Down Expand Up @@ -638,6 +654,113 @@ func (m *MovingStd[T]) IdlePeriod() int

IdlePeriod is the initial period that Moving Standard Deviation won't yield any results.

<a name="PercentB"></a>
## type [PercentB](<https://github.com/cinar/indicator/blob/master/volatility/percent_b.go#L16-L19>)

PercentB represents the parameters for calculating the %B indicator.

```
%B = (Close - Lower Band) / (Upper Band - Lower Band)
```

```go
type PercentB[T helper.Number] struct {
// BollingerBands is the underlying Bollinger Bands indicator used for calculations.
BollingerBands *BollingerBands[T]
}
```

<details><summary>Example</summary>
<p>



```go
package main

import (
"fmt"

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

func main() {
// Closing prices
closes := helper.SliceToChan([]float64{
318.600006, 315.839996, 316.149994, 310.570007, 307.779999,
305.820007, 305.98999, 306.390015, 311.450012, 312.329987,
309.290009, 301.910004, 300, 300.029999, 302,
307.820007, 302.690002, 306.48999, 305.549988, 303.429993,
})

// Initialize the %B indicator
percentB := volatility.NewPercentB[float64]()

// Compute %B
result := percentB.Compute(closes)

// Round digits
result = helper.RoundDigits(result, 2)

fmt.Println(helper.ChanToSlice(result))
}
```

#### Output

```
[0.3]
```

</p>
</details>

<a name="NewPercentB"></a>
### func [NewPercentB](<https://github.com/cinar/indicator/blob/master/volatility/percent_b.go#L22>)

```go
func NewPercentB[T helper.Number]() *PercentB[T]
```

NewPercentB function initializes a new %B instance with the default parameters.

<a name="NewPercentBWithPeriod"></a>
### func [NewPercentBWithPeriod](<https://github.com/cinar/indicator/blob/master/volatility/percent_b.go#L27>)

```go
func NewPercentBWithPeriod[T helper.Number](period int) *PercentB[T]
```

NewPercentBWithPeriod function initializes a new %B instance with the given period.

<a name="PercentB[T].Compute"></a>
### func \(\*PercentB\[T\]\) [Compute](<https://github.com/cinar/indicator/blob/master/volatility/percent_b.go#L34>)

```go
func (p *PercentB[T]) Compute(closings <-chan T) <-chan T
```

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

<a name="PercentB[T].IdlePeriod"></a>
### func \(\*PercentB\[T\]\) [IdlePeriod](<https://github.com/cinar/indicator/blob/master/volatility/percent_b.go#L53>)

```go
func (p *PercentB[T]) IdlePeriod() int
```

IdlePeriod is the initial period that %B yield any results.

<a name="PercentB[T].String"></a>
### func \(\*PercentB\[T\]\) [String](<https://github.com/cinar/indicator/blob/master/volatility/percent_b.go#L58>)

```go
func (p *PercentB[T]) String() string
```

String is the string representation of the %B.

<a name="Po"></a>
## type [Po](<https://github.com/cinar/indicator/blob/master/volatility/po.go#L28-L37>)

Expand Down
7 changes: 6 additions & 1 deletion volatility/bollinger_bands.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,13 @@ type BollingerBands[T helper.Number] struct {

// NewBollingerBands function initializes a new Bollinger Bands instance with the default parameters.
func NewBollingerBands[T helper.Number]() *BollingerBands[T] {
return NewBollingerBandsWithPeriod[T](DefaultBollingerBandsPeriod)
}

// NewBollingerBandsWithPeriod function initializes a new Bollinger Bands instance with the given period.
func NewBollingerBandsWithPeriod[T helper.Number](period int) *BollingerBands[T] {
return &BollingerBands[T]{
Period: DefaultBollingerBandsPeriod,
Period: period,
}
}

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

package volatility

import (
"fmt"

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

// PercentB represents the parameters for calculating the %B indicator.
//
// %B = (Close - Lower Band) / (Upper Band - Lower Band)
type PercentB[T helper.Number] struct {
// BollingerBands is the underlying Bollinger Bands indicator used for calculations.
BollingerBands *BollingerBands[T]
}

// NewPercentB function initializes a new %B instance with the default parameters.
func NewPercentB[T helper.Number]() *PercentB[T] {
return NewPercentBWithPeriod[T](DefaultBollingerBandsPeriod)
}

// NewPercentBWithPeriod function initializes a new %B instance with the given period.
func NewPercentBWithPeriod[T helper.Number](period int) *PercentB[T] {
return &PercentB[T]{
BollingerBands: NewBollingerBandsWithPeriod[T](period),
}
}

// Compute function takes a channel of numbers and computes the %B over the specified period.
func (p *PercentB[T]) Compute(closings <-chan T) <-chan T {
closingsSplice := helper.Duplicate(closings, 2)

// Compute the Bollinger Bands
upperBands, middleBands, lowerBands := p.BollingerBands.Compute(closingsSplice[0])

// Skip closings until the Bollinger Bands are available
closingsSplice[1] = helper.Skip(closingsSplice[1], p.BollingerBands.IdlePeriod())

// Drain the middle bands
go helper.Drain(middleBands)

return helper.Operate3(upperBands, lowerBands, closingsSplice[1], func(upperBand, lowerBand, closing T) T {
// %B = (Close - Lower Band) / (Upper Band - Lower Band)
return (closing - lowerBand) / (upperBand - lowerBand)
})
}

// IdlePeriod is the initial period that %B yield any results.
func (p *PercentB[T]) IdlePeriod() int {
return p.BollingerBands.IdlePeriod()
}

// String is the string representation of the %B.
func (p *PercentB[T]) String() string {
return fmt.Sprintf("%%B(%d)", p.BollingerBands.Period)
}
72 changes: 72 additions & 0 deletions volatility/percent_b_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright (c) 2021-2024 Onur Cinar.
// The source code is provided under GNU AGPLv3 License.
// https://github.com/cinar/indicator

package volatility_test

import (
"fmt"
"testing"

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

func ExamplePercentB() {
// Closing prices
closes := helper.SliceToChan([]float64{
318.600006, 315.839996, 316.149994, 310.570007, 307.779999,
305.820007, 305.98999, 306.390015, 311.450012, 312.329987,
309.290009, 301.910004, 300, 300.029999, 302,
307.820007, 302.690002, 306.48999, 305.549988, 303.429993,
})

// Initialize the %B indicator
percentB := volatility.NewPercentB[float64]()

// Compute %B
result := percentB.Compute(closes)

// Round digits
result = helper.RoundDigits(result, 2)

fmt.Println(helper.ChanToSlice(result))
// Output: [0.3]
}

func TestPercentB(t *testing.T) {
type Data struct {
Close float64
PercentB float64
}

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

inputs := helper.Duplicate(input, 2)
closing := helper.Map(inputs[0], func(d *Data) float64 { return d.Close })
expected := helper.Map(inputs[1], func(d *Data) float64 { return d.PercentB })

percentB := volatility.NewPercentB[float64]()

actual := percentB.Compute(closing)
actual = helper.RoundDigits(actual, 2)

expected = helper.Skip(expected, percentB.IdlePeriod())

err = helper.CheckEquals(actual, expected)
if err != nil {
t.Fatal(err)
}
}

func TestPercentBString(t *testing.T) {
expected := "%B(10)"
actual := volatility.NewPercentBWithPeriod[float64](10).String()

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

0 comments on commit 3ae91ac

Please sign in to comment.