Skip to content

Commit

Permalink
feat(indicators): create bollinder bands
Browse files Browse the repository at this point in the history
  • Loading branch information
h-varmazyar committed Jan 16, 2024
1 parent 8b93541 commit 798aa25
Show file tree
Hide file tree
Showing 3 changed files with 210 additions and 169 deletions.
267 changes: 99 additions & 168 deletions services/indicators/pkg/calculator/bollinger_bands.go
Original file line number Diff line number Diff line change
@@ -1,170 +1,101 @@
package calculator

//
//import (
// "context"
// "github.com/google/uuid"
// chipmunkApi "github.com/h-varmazyar/Gate/services/chipmunk/api/proto"
// "github.com/h-varmazyar/Gate/services/chipmunk/internal/pkg/buffer"
// "github.com/h-varmazyar/Gate/services/chipmunk/internal/pkg/entity"
// log "github.com/sirupsen/logrus"
// "math"
//)
//
//type BollingerBands struct {
// id uuid.UUID
// entity.BollingerBandsConfigs
// sma *movingAverage
//}
//
//type BollingerBandsValue struct {
// UpperBand float64
// LowerBand float64
// MA float64
//}
//
//func NewBollingerBands(configs *entity.BollingerBandsConfigs) (*BollingerBands, error) {
// if err := validateBollingerBandsConfigs(configs); err != nil {
// return nil, err
// }
// return &BollingerBands{
// id: id,
// BollingerBandsConfigs: *configs,
// sma: &movingAverage{
// id: id,
// MovingAverageConfigs: entity.MovingAverageConfigs{
// Length: configs.Length,
// Source: configs.Source,
// },
// },
// }, nil
//}
//
//func (conf *BollingerBands) Calculate(ctx context.Context, candles []*chipmunkApi.Candle, values []*BollingerBandsValue) error {
// _, err := conf.sma.sma(candles)
// if err != nil {
// return err
// }
// for i := conf.Length - 1; i < len(candles); i++ {
// conf.calculateBB(candles[1+i-conf.Length : i+1])
// //variance := float64(0)
// //ma := rateLimiters[i].MovingAverages[conf.id].Simple
// //for j := 1 + i - conf.Length; j <= i; j++ {
// // sum := float64(0)
// // switch conf.Source {
// // case chipmunkApi.Source_Open:
// // sum = rateLimiters[j].Open
// // case chipmunkApi.Source_High:
// // sum = rateLimiters[j].High
// // case chipmunkApi.Source_Low:
// // sum = rateLimiters[j].Low
// // case chipmunkApi.Source_Close:
// // sum = rateLimiters[j].Close
// // case chipmunkApi.Source_OHLC4:
// // sum = (rateLimiters[j].Open + rateLimiters[j].High + rateLimiters[j].Low + rateLimiters[j].Close) / 4
// // case chipmunkApi.Source_HLC3:
// // sum = (rateLimiters[j].Low + rateLimiters[j].High + rateLimiters[j].Close) / 3
// // case chipmunkApi.Source_HL2:
// // sum = (rateLimiters[j].Low + rateLimiters[j].High) / 2
// // }
// // variance += math.Pow(ma-sum, 2)
// //}
// //variance /= float64(conf.Length)
// //
// //if rateLimiters[i] == nil {
// // log.Errorf("nil candle")
// //}
// //
// //rateLimiters[i].BollingerBands[conf.id] = &entity.BollingerBandsValue{
// // UpperBand: ma + float64(conf.Deviation)*math.Sqrt(variance),
// // LowerBand: ma - float64(conf.Deviation)*math.Sqrt(variance),
// // MA: ma,
// //}
// }
// return nil
//}
//
//func (conf *BollingerBands) UpdateLast(ctx context.Context, candles []*entity.Candle, value *BollingerBandsValue) {
// if len(candles) == 0 {
// return
// }
// first := candles[0]
// start := buffer.CandleBuffer.Before(first.MarketID.String(), first.ResolutionID.String(), first.Time, conf.Length-1)
// if len(start) == 0 {
// return
// }
//
// internalCandles := append(start, candles...)
//
// _, err := conf.sma.sma(internalCandles)
// if err != nil {
// log.WithError(err).Error("failed to calculate sma for bollinger bands")
// return
// }
//
// for i := conf.Length - 1; i < len(internalCandles); i++ {
// conf.calculateBB(candles[1+i-conf.Length : i+1])
// //variance := float64(0)
// //ma := internalCandles[i].MovingAverages[conf.id].Simple
// //for j := 1 + i - conf.Length; j <= i; j++ {
// // sum := float64(0)
// // switch conf.Source {
// // case chipmunkApi.Source_Open:
// // sum = rateLimiters[j].Open
// // case chipmunkApi.Source_High:
// // sum = rateLimiters[j].High
// // case chipmunkApi.Source_Low:
// // sum = rateLimiters[j].Low
// // case chipmunkApi.Source_Close:
// // sum = rateLimiters[j].Close
// // case chipmunkApi.Source_OHLC4:
// // sum = (rateLimiters[j].Open + rateLimiters[j].High + rateLimiters[j].Low + rateLimiters[j].Close) / 4
// // case chipmunkApi.Source_HLC3:
// // sum = (rateLimiters[j].Low + rateLimiters[j].High + rateLimiters[j].Close) / 3
// // case chipmunkApi.Source_HL2:
// // sum = (rateLimiters[j].Low + rateLimiters[j].High) / 2
// // }
// // variance += math.Pow(ma-sum, 2)
// //}
// //variance /= float64(conf.Length)
// //
// //rateLimiters[i].BollingerBands[conf.id] = &entity.BollingerBandsValue{
// // UpperBand: ma + float64(conf.Deviation)*math.Sqrt(variance),
// // LowerBand: ma - float64(conf.Deviation)*math.Sqrt(variance),
// // MA: ma,
// //}
// }
//}
//
//func (conf *BollingerBands) calculateBB(candles []*entity.Candle) {
// variance := float64(0)
// index := len(candles) - 1
// ma := candles[index].MovingAverages[conf.id].Simple
// for j := 0; j < len(candles); j++ {
// sum := float64(0)
// switch conf.Source {
// case chipmunkApi.Source_Open:
// sum = candles[j].Open
// case chipmunkApi.Source_High:
// sum = candles[j].High
// case chipmunkApi.Source_Low:
// sum = candles[j].Low
// case chipmunkApi.Source_Close:
// sum = candles[j].Close
// case chipmunkApi.Source_OHLC4:
// sum = (candles[j].Open + candles[j].High + candles[j].Low + candles[j].Close) / 4
// case chipmunkApi.Source_HLC3:
// sum = (candles[j].Low + candles[j].High + candles[j].Close) / 3
// case chipmunkApi.Source_HL2:
// sum = (candles[j].Low + candles[j].High) / 2
// }
// variance += math.Pow(ma-sum, 2)
// }
// variance /= float64(conf.Length)
//
// candles[index].BollingerBands[conf.id] = &entity.BollingerBandsValue{
// UpperBand: ma + float64(conf.Deviation)*math.Sqrt(variance),
// LowerBand: ma - float64(conf.Deviation)*math.Sqrt(variance),
// MA: ma,
// }
//}
import (
"context"
"github.com/google/uuid"
chipmunkAPI "github.com/h-varmazyar/Gate/services/chipmunk/api/proto"
indicatorsAPI "github.com/h-varmazyar/Gate/services/indicators/api/proto"
"math"
)

type BollingerBands struct {
id uuid.UUID
Period int
Deviation int
Source indicatorsAPI.Source
sma *SMA
lastSMA *SMAValue
periodCandles []*chipmunkAPI.Candle
}

type BollingerBandsValue struct {
UpperBand float64
LowerBand float64
MA float64
}

func NewBollingerBands(period, deviation int, source indicatorsAPI.Source) (*BollingerBands, error) {
sma, err := NewSMA(period, source)
if err != nil {
return nil, err
}
return &BollingerBands{
id: uuid.New(),
Period: period,
Deviation: deviation,
Source: source,
sma: sma,
}, nil
}

func (conf *BollingerBands) Calculate(ctx context.Context, candles []*chipmunkAPI.Candle, values []*BollingerBandsValue) error {
smaValues := make([]*SMAValue, len(candles))
err := conf.sma.Calculate(ctx, candles, smaValues)
if err != nil {
return err
}

for i := conf.Period - 1; i < len(candles); i++ {
values[i] = conf.calculateBB(candles[1+i-conf.Period:i+1], smaValues[i])
}

lastValue := *smaValues[len(smaValues)-1].Value
conf.lastSMA = &SMAValue{
Value: &lastValue,
TimeFrame: smaValues[len(smaValues)-1].TimeFrame,
}
conf.periodCandles = cloneCandles(candles[len(candles)-conf.Period:])

return nil
}

func (conf *BollingerBands) UpdateLast(ctx context.Context, candle *chipmunkAPI.Candle, value *BollingerBandsValue) {
if candle.Time > conf.periodCandles[len(conf.periodCandles)-1].Time {
conf.periodCandles = conf.periodCandles[1:] //todo: must be check
}
conf.periodCandles[conf.Period-1] = cloneCandle(candle)

conf.sma.UpdateLast(ctx, candle, conf.lastSMA)
value = conf.calculateBB(conf.periodCandles, conf.lastSMA)
}

func (conf *BollingerBands) calculateBB(candles []*chipmunkAPI.Candle, smaValue *SMAValue) *BollingerBandsValue {
variance := float64(0)
for j := 0; j < len(candles); j++ {
num := float64(0)
switch conf.Source {
case indicatorsAPI.Source_OPEN:
num = candles[j].Open
case indicatorsAPI.Source_HIGH:
num = candles[j].High
case indicatorsAPI.Source_LOW:
num = candles[j].Low
case indicatorsAPI.Source_CLOSE:
num = candles[j].Close
case indicatorsAPI.Source_OHLC4:
num = (candles[j].Open + candles[j].High + candles[j].Low + candles[j].Close) / 4
case indicatorsAPI.Source_HLC3:
num = (candles[j].Low + candles[j].High + candles[j].Close) / 3
case indicatorsAPI.Source_HL2:
num = (candles[j].Low + candles[j].High) / 2
}
variance += math.Pow(*smaValue.Value-num, 2)
}
variance /= float64(len(candles))

return &BollingerBandsValue{
UpperBand: *smaValue.Value + float64(conf.Deviation)*math.Sqrt(variance),
LowerBand: *smaValue.Value - float64(conf.Deviation)*math.Sqrt(variance),
MA: *smaValue.Value,
}
}
110 changes: 110 additions & 0 deletions services/indicators/pkg/calculator/bollinter_bands_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package calculator

import (
"fmt"
chipmunkAPI "github.com/h-varmazyar/Gate/services/chipmunk/api/proto"
indicatorsAPI "github.com/h-varmazyar/Gate/services/indicators/api/proto"
"golang.org/x/net/context"
"testing"
)

func TestBollingerBands_Calculate(t *testing.T) {
calculate := func(t testing.TB, conf *BollingerBands, candles []*chipmunkAPI.Candle, want []*BollingerBandsValue) {
t.Helper()
values := make([]*BollingerBandsValue, len(candles))
err := conf.Calculate(context.Background(), candles, values)
if err != nil {
t.Errorf("failed to calculate rsi: %v", err)
return
}

if len(candles) != len(want) {
t.Errorf("value and want length mismatch (values(%v) != want(%v))", len(candles), len(want))
}

if len(values) != len(candles) {
t.Errorf("value and candles length mismatch (values(%v) != candles(%v))", len(values), len(candles))
}

for i, value := range values {
if value == nil && want[i] == nil {
continue
}
if value == nil {
t.Errorf("nil value: %v", i)
continue
}
if want[i] == nil {
t.Errorf("nil want: %v", i)
continue
}
if fmt.Sprintf("%.6f", value.MA) != fmt.Sprintf("%.6f", want[i].MA) {
t.Errorf("ma mismatch: %v - %v", value.MA, want[i].MA)
}
if fmt.Sprintf("%.6f", value.UpperBand) != fmt.Sprintf("%.6f", want[i].UpperBand) {
t.Errorf("upper band mismatch: %v - %v", value.UpperBand, want[i].UpperBand)
}
if fmt.Sprintf("%.6f", value.LowerBand) != fmt.Sprintf("%.6f", want[i].LowerBand) {
t.Errorf("lower band mismatch: %v - %v", value.LowerBand, want[i].LowerBand)
}
}
}

t.Run("calculating", func(t *testing.T) {
candles := []*chipmunkAPI.Candle{
{Close: 0.088595, Time: 1704902000}, //1
{Close: 0.113548, Time: 1704903000}, //2
{Close: 0.106478, Time: 1704904000}, //3
{Close: 0.102785, Time: 1704905000}, //4
{Close: 0.101691, Time: 1704906000}, //5
{Close: 0.088481, Time: 1704907000}, //6
{Close: 0.099272, Time: 1704908000}, //7
{Close: 0.100330, Time: 1704909000}, //8
{Close: 0.101711, Time: 1704910000}, //9
{Close: 0.127273, Time: 1704911000}, //10
{Close: 0.117460, Time: 1704912000}, //11
{Close: 0.113066, Time: 1704913000}, //12
//{Close: 0.166468, Time: 1704914000}, //13
//{Close: 0.170083, Time: 1704915000}, //14
//{Close: 0.142484, Time: 1704916000}, //15
//{Close: 0.146316, Time: 1704917000}, //16
//{Close: 0.145755, Time: 1704918000}, //17
//{Close: 0.146820, Time: 1704919000}, //18
//{Close: 0.179320, Time: 1704920000}, //19
//{Close: 0.167225, Time: 1704921000}, //20
//{Close: 0.144266, Time: 1704922000}, //21
//{Close: 0.123643, Time: 1704923000}, //22
//{Close: 0.109257, Time: 1704924000}, //23
}

results := []*BollingerBandsValue{
nil, //1
nil, //2
nil, //3
nil, //4
nil, //5
nil, //6
nil, //7
nil, //8
nil, //9
&BollingerBandsValue{
UpperBand: 0.124513,
LowerBand: 0.081520,
MA: 0.103016,
}, //11
&BollingerBandsValue{
UpperBand: 0.126616,
LowerBand: 0.085190,
MA: 0.105903,
}, //12
&BollingerBandsValue{
UpperBand: 0.126499,
LowerBand: 0.085211,
MA: 0.105855,
}, //13
}

conf, _ := NewBollingerBands(10, 2, indicatorsAPI.Source_CLOSE)
calculate(t, conf, candles, results)
})
}
2 changes: 1 addition & 1 deletion services/indicators/pkg/calculator/sma.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func (conf *SMA) UpdateLast(_ context.Context, candle *chipmunkAPI.Candle, value
changeableCandle = conf.periodCandles[conf.Period-1]
} else if conf.lastValue.TimeFrame.Unix() < candle.Time {
changeableCandle = cloneCandle(conf.periodCandles[0])
conf.periodCandles = conf.periodCandles[1:]
conf.periodCandles = conf.periodCandles[1:] //todo: must be check
}
conf.periodCandles[conf.Period-1] = cloneCandle(candle)

Expand Down

0 comments on commit 798aa25

Please sign in to comment.