Skip to content

Commit 3ae91ac

Browse files
authored
%B indicator is added. (#264)
# 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 -->
1 parent 06e7424 commit 3ae91ac

File tree

7 files changed

+518
-5
lines changed

7 files changed

+518
-5
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ The following list of indicators are currently supported by this package:
7474

7575
### 🎢 Volatility Indicators
7676

77+
- [%B](volatility/README.md#type-percentb)
7778
- [Acceleration Bands](volatility/README.md#type-accelerationbands)
7879
- [Actual True Range (ATR)](volatility/README.md#type-atr)
7980
- [Bollinger Band Width](volatility/README.md#type-bollingerbandwidth)

trend/README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ The information provided on this project is strictly for informational purposes
148148
- [func \(v \*Vwma\[T\]\) IdlePeriod\(\) int](<#Vwma[T].IdlePeriod>)
149149
- [type WeightedClose](<#WeightedClose>)
150150
- [func NewWeightedClose\[T helper.Number\]\(\) \*WeightedClose\[T\]](<#NewWeightedClose>)
151-
- [func \(w \*WeightedClose\[T\]\) Compute\(highs, lows, closes \<\-chan T\) \<\-chan T](<#WeightedClose[T].Compute>)
151+
- [func \(\*WeightedClose\[T\]\) Compute\(highs, lows, closes \<\-chan T\) \<\-chan T](<#WeightedClose[T].Compute>)
152152
- [func \(\*WeightedClose\[T\]\) IdlePeriod\(\) int](<#WeightedClose[T].IdlePeriod>)
153153
- [func \(\*WeightedClose\[T\]\) String\(\) string](<#WeightedClose[T].String>)
154154
- [type Wma](<#Wma>)
@@ -1847,7 +1847,7 @@ NewWeightedClose function initializes a new Weighted Close instance with the def
18471847
### func \(\*WeightedClose\[T\]\) [Compute](<https://github.com/cinar/indicator/blob/master/trend/weighted_close.go#L28>)
18481848

18491849
```go
1850-
func (w *WeightedClose[T]) Compute(highs, lows, closes <-chan T) <-chan T
1850+
func (*WeightedClose[T]) Compute(highs, lows, closes <-chan T) <-chan T
18511851
```
18521852

18531853
Compute function takes a channel of numbers and computes the Weighted Close over the specified period.

volatility/README.md

+125-2
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ The information provided on this project is strictly for informational purposes
4141
- [func \(b \*BollingerBandWidth\[T\]\) IdlePeriod\(\) int](<#BollingerBandWidth[T].IdlePeriod>)
4242
- [type BollingerBands](<#BollingerBands>)
4343
- [func NewBollingerBands\[T helper.Number\]\(\) \*BollingerBands\[T\]](<#NewBollingerBands>)
44+
- [func NewBollingerBandsWithPeriod\[T helper.Number\]\(period int\) \*BollingerBands\[T\]](<#NewBollingerBandsWithPeriod>)
4445
- [func \(b \*BollingerBands\[T\]\) Compute\(c \<\-chan T\) \(\<\-chan T, \<\-chan T, \<\-chan T\)](<#BollingerBands[T].Compute>)
4546
- [func \(b \*BollingerBands\[T\]\) IdlePeriod\(\) int](<#BollingerBands[T].IdlePeriod>)
4647
- [type ChandelierExit](<#ChandelierExit>)
@@ -62,6 +63,12 @@ The information provided on this project is strictly for informational purposes
6263
- [func NewMovingStdWithPeriod\[T helper.Number\]\(period int\) \*MovingStd\[T\]](<#NewMovingStdWithPeriod>)
6364
- [func \(m \*MovingStd\[T\]\) Compute\(c \<\-chan T\) \<\-chan T](<#MovingStd[T].Compute>)
6465
- [func \(m \*MovingStd\[T\]\) IdlePeriod\(\) int](<#MovingStd[T].IdlePeriod>)
66+
- [type PercentB](<#PercentB>)
67+
- [func NewPercentB\[T helper.Number\]\(\) \*PercentB\[T\]](<#NewPercentB>)
68+
- [func NewPercentBWithPeriod\[T helper.Number\]\(period int\) \*PercentB\[T\]](<#NewPercentBWithPeriod>)
69+
- [func \(p \*PercentB\[T\]\) Compute\(closings \<\-chan T\) \<\-chan T](<#PercentB[T].Compute>)
70+
- [func \(p \*PercentB\[T\]\) IdlePeriod\(\) int](<#PercentB[T].IdlePeriod>)
71+
- [func \(p \*PercentB\[T\]\) String\(\) string](<#PercentB[T].String>)
6572
- [type Po](<#Po>)
6673
- [func NewPo\[T helper.Number\]\(\) \*Po\[T\]](<#NewPo>)
6774
- [func NewPoWithPeriod\[T helper.Number\]\(period int\) \*Po\[T\]](<#NewPoWithPeriod>)
@@ -386,8 +393,17 @@ func NewBollingerBands[T helper.Number]() *BollingerBands[T]
386393

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

396+
<a name="NewBollingerBandsWithPeriod"></a>
397+
### func [NewBollingerBandsWithPeriod](<https://github.com/cinar/indicator/blob/master/volatility/bollinger_bands.go#L40>)
398+
399+
```go
400+
func NewBollingerBandsWithPeriod[T helper.Number](period int) *BollingerBands[T]
401+
```
402+
403+
NewBollingerBandsWithPeriod function initializes a new Bollinger Bands instance with the given period.
404+
389405
<a name="BollingerBands[T].Compute"></a>
390-
### func \(\*BollingerBands\[T\]\) [Compute](<https://github.com/cinar/indicator/blob/master/volatility/bollinger_bands.go#L42>)
406+
### func \(\*BollingerBands\[T\]\) [Compute](<https://github.com/cinar/indicator/blob/master/volatility/bollinger_bands.go#L47>)
391407

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

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

401417
```go
402418
func (b *BollingerBands[T]) IdlePeriod() int
@@ -638,6 +654,113 @@ func (m *MovingStd[T]) IdlePeriod() int
638654

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

657+
<a name="PercentB"></a>
658+
## type [PercentB](<https://github.com/cinar/indicator/blob/master/volatility/percent_b.go#L16-L19>)
659+
660+
PercentB represents the parameters for calculating the %B indicator.
661+
662+
```
663+
%B = (Close - Lower Band) / (Upper Band - Lower Band)
664+
```
665+
666+
```go
667+
type PercentB[T helper.Number] struct {
668+
// BollingerBands is the underlying Bollinger Bands indicator used for calculations.
669+
BollingerBands *BollingerBands[T]
670+
}
671+
```
672+
673+
<details><summary>Example</summary>
674+
<p>
675+
676+
677+
678+
```go
679+
package main
680+
681+
import (
682+
"fmt"
683+
684+
"github.com/cinar/indicator/v2/helper"
685+
"github.com/cinar/indicator/v2/volatility"
686+
)
687+
688+
func main() {
689+
// Closing prices
690+
closes := helper.SliceToChan([]float64{
691+
318.600006, 315.839996, 316.149994, 310.570007, 307.779999,
692+
305.820007, 305.98999, 306.390015, 311.450012, 312.329987,
693+
309.290009, 301.910004, 300, 300.029999, 302,
694+
307.820007, 302.690002, 306.48999, 305.549988, 303.429993,
695+
})
696+
697+
// Initialize the %B indicator
698+
percentB := volatility.NewPercentB[float64]()
699+
700+
// Compute %B
701+
result := percentB.Compute(closes)
702+
703+
// Round digits
704+
result = helper.RoundDigits(result, 2)
705+
706+
fmt.Println(helper.ChanToSlice(result))
707+
}
708+
```
709+
710+
#### Output
711+
712+
```
713+
[0.3]
714+
```
715+
716+
</p>
717+
</details>
718+
719+
<a name="NewPercentB"></a>
720+
### func [NewPercentB](<https://github.com/cinar/indicator/blob/master/volatility/percent_b.go#L22>)
721+
722+
```go
723+
func NewPercentB[T helper.Number]() *PercentB[T]
724+
```
725+
726+
NewPercentB function initializes a new %B instance with the default parameters.
727+
728+
<a name="NewPercentBWithPeriod"></a>
729+
### func [NewPercentBWithPeriod](<https://github.com/cinar/indicator/blob/master/volatility/percent_b.go#L27>)
730+
731+
```go
732+
func NewPercentBWithPeriod[T helper.Number](period int) *PercentB[T]
733+
```
734+
735+
NewPercentBWithPeriod function initializes a new %B instance with the given period.
736+
737+
<a name="PercentB[T].Compute"></a>
738+
### func \(\*PercentB\[T\]\) [Compute](<https://github.com/cinar/indicator/blob/master/volatility/percent_b.go#L34>)
739+
740+
```go
741+
func (p *PercentB[T]) Compute(closings <-chan T) <-chan T
742+
```
743+
744+
Compute function takes a channel of numbers and computes the %B over the specified period.
745+
746+
<a name="PercentB[T].IdlePeriod"></a>
747+
### func \(\*PercentB\[T\]\) [IdlePeriod](<https://github.com/cinar/indicator/blob/master/volatility/percent_b.go#L53>)
748+
749+
```go
750+
func (p *PercentB[T]) IdlePeriod() int
751+
```
752+
753+
IdlePeriod is the initial period that %B yield any results.
754+
755+
<a name="PercentB[T].String"></a>
756+
### func \(\*PercentB\[T\]\) [String](<https://github.com/cinar/indicator/blob/master/volatility/percent_b.go#L58>)
757+
758+
```go
759+
func (p *PercentB[T]) String() string
760+
```
761+
762+
String is the string representation of the %B.
763+
641764
<a name="Po"></a>
642765
## type [Po](<https://github.com/cinar/indicator/blob/master/volatility/po.go#L28-L37>)
643766

volatility/bollinger_bands.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,13 @@ type BollingerBands[T helper.Number] struct {
3333

3434
// NewBollingerBands function initializes a new Bollinger Bands instance with the default parameters.
3535
func NewBollingerBands[T helper.Number]() *BollingerBands[T] {
36+
return NewBollingerBandsWithPeriod[T](DefaultBollingerBandsPeriod)
37+
}
38+
39+
// NewBollingerBandsWithPeriod function initializes a new Bollinger Bands instance with the given period.
40+
func NewBollingerBandsWithPeriod[T helper.Number](period int) *BollingerBands[T] {
3641
return &BollingerBands[T]{
37-
Period: DefaultBollingerBandsPeriod,
42+
Period: period,
3843
}
3944
}
4045

volatility/percent_b.go

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright (c) 2021-2024 Onur Cinar.
2+
// The source code is provided under GNU AGPLv3 License.
3+
// https://github.com/cinar/indicator
4+
5+
package volatility
6+
7+
import (
8+
"fmt"
9+
10+
"github.com/cinar/indicator/v2/helper"
11+
)
12+
13+
// PercentB represents the parameters for calculating the %B indicator.
14+
//
15+
// %B = (Close - Lower Band) / (Upper Band - Lower Band)
16+
type PercentB[T helper.Number] struct {
17+
// BollingerBands is the underlying Bollinger Bands indicator used for calculations.
18+
BollingerBands *BollingerBands[T]
19+
}
20+
21+
// NewPercentB function initializes a new %B instance with the default parameters.
22+
func NewPercentB[T helper.Number]() *PercentB[T] {
23+
return NewPercentBWithPeriod[T](DefaultBollingerBandsPeriod)
24+
}
25+
26+
// NewPercentBWithPeriod function initializes a new %B instance with the given period.
27+
func NewPercentBWithPeriod[T helper.Number](period int) *PercentB[T] {
28+
return &PercentB[T]{
29+
BollingerBands: NewBollingerBandsWithPeriod[T](period),
30+
}
31+
}
32+
33+
// Compute function takes a channel of numbers and computes the %B over the specified period.
34+
func (p *PercentB[T]) Compute(closings <-chan T) <-chan T {
35+
closingsSplice := helper.Duplicate(closings, 2)
36+
37+
// Compute the Bollinger Bands
38+
upperBands, middleBands, lowerBands := p.BollingerBands.Compute(closingsSplice[0])
39+
40+
// Skip closings until the Bollinger Bands are available
41+
closingsSplice[1] = helper.Skip(closingsSplice[1], p.BollingerBands.IdlePeriod())
42+
43+
// Drain the middle bands
44+
go helper.Drain(middleBands)
45+
46+
return helper.Operate3(upperBands, lowerBands, closingsSplice[1], func(upperBand, lowerBand, closing T) T {
47+
// %B = (Close - Lower Band) / (Upper Band - Lower Band)
48+
return (closing - lowerBand) / (upperBand - lowerBand)
49+
})
50+
}
51+
52+
// IdlePeriod is the initial period that %B yield any results.
53+
func (p *PercentB[T]) IdlePeriod() int {
54+
return p.BollingerBands.IdlePeriod()
55+
}
56+
57+
// String is the string representation of the %B.
58+
func (p *PercentB[T]) String() string {
59+
return fmt.Sprintf("%%B(%d)", p.BollingerBands.Period)
60+
}

volatility/percent_b_test.go

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Copyright (c) 2021-2024 Onur Cinar.
2+
// The source code is provided under GNU AGPLv3 License.
3+
// https://github.com/cinar/indicator
4+
5+
package volatility_test
6+
7+
import (
8+
"fmt"
9+
"testing"
10+
11+
"github.com/cinar/indicator/v2/helper"
12+
"github.com/cinar/indicator/v2/volatility"
13+
)
14+
15+
func ExamplePercentB() {
16+
// Closing prices
17+
closes := helper.SliceToChan([]float64{
18+
318.600006, 315.839996, 316.149994, 310.570007, 307.779999,
19+
305.820007, 305.98999, 306.390015, 311.450012, 312.329987,
20+
309.290009, 301.910004, 300, 300.029999, 302,
21+
307.820007, 302.690002, 306.48999, 305.549988, 303.429993,
22+
})
23+
24+
// Initialize the %B indicator
25+
percentB := volatility.NewPercentB[float64]()
26+
27+
// Compute %B
28+
result := percentB.Compute(closes)
29+
30+
// Round digits
31+
result = helper.RoundDigits(result, 2)
32+
33+
fmt.Println(helper.ChanToSlice(result))
34+
// Output: [0.3]
35+
}
36+
37+
func TestPercentB(t *testing.T) {
38+
type Data struct {
39+
Close float64
40+
PercentB float64
41+
}
42+
43+
input, err := helper.ReadFromCsvFile[Data]("testdata/percent_b.csv", true)
44+
if err != nil {
45+
t.Fatal(err)
46+
}
47+
48+
inputs := helper.Duplicate(input, 2)
49+
closing := helper.Map(inputs[0], func(d *Data) float64 { return d.Close })
50+
expected := helper.Map(inputs[1], func(d *Data) float64 { return d.PercentB })
51+
52+
percentB := volatility.NewPercentB[float64]()
53+
54+
actual := percentB.Compute(closing)
55+
actual = helper.RoundDigits(actual, 2)
56+
57+
expected = helper.Skip(expected, percentB.IdlePeriod())
58+
59+
err = helper.CheckEquals(actual, expected)
60+
if err != nil {
61+
t.Fatal(err)
62+
}
63+
}
64+
65+
func TestPercentBString(t *testing.T) {
66+
expected := "%B(10)"
67+
actual := volatility.NewPercentBWithPeriod[float64](10).String()
68+
69+
if actual != expected {
70+
t.Fatalf("actual %v expected %v", actual, expected)
71+
}
72+
}

0 commit comments

Comments
 (0)