diff --git a/pkg/dynamicmetric/bollsetting.go b/pkg/dynamicmetric/bollsetting.go new file mode 100644 index 000000000..79bff4ef1 --- /dev/null +++ b/pkg/dynamicmetric/bollsetting.go @@ -0,0 +1,9 @@ +package dynamicmetric + +import "github.com/c9s/bbgo/pkg/types" + +// BollingerSetting is for Bollinger Band settings +type BollingerSetting struct { + types.IntervalWindow + BandWidth float64 `json:"bandWidth"` +} diff --git a/pkg/dynamicmetric/dynamic_exposure.go b/pkg/dynamicmetric/dynamic_exposure.go new file mode 100644 index 000000000..aadd1ece3 --- /dev/null +++ b/pkg/dynamicmetric/dynamic_exposure.go @@ -0,0 +1,83 @@ +package dynamicmetric + +import ( + "github.com/c9s/bbgo/pkg/bbgo" + "github.com/c9s/bbgo/pkg/fixedpoint" + "github.com/c9s/bbgo/pkg/indicator" + "github.com/c9s/bbgo/pkg/types" + "github.com/pkg/errors" + log "github.com/sirupsen/logrus" + "math" +) + +type DynamicExposure struct { + // BollBandExposure calculates the max exposure with the Bollinger Band + BollBandExposure *DynamicExposureBollBand `json:"bollBandExposure"` +} + +// Initialize dynamic exposure +func (d *DynamicExposure) Initialize(symbol string, session *bbgo.ExchangeSession) { + switch { + case d.BollBandExposure != nil: + d.BollBandExposure.initialize(symbol, session) + } +} + +func (d *DynamicExposure) IsEnabled() bool { + return d.BollBandExposure != nil +} + +// GetMaxExposure returns the max exposure +func (d *DynamicExposure) GetMaxExposure(price float64) (maxExposure fixedpoint.Value, err error) { + switch { + case d.BollBandExposure != nil: + return d.BollBandExposure.getMaxExposure(price) + default: + return fixedpoint.Zero, errors.New("dynamic exposure is not enabled") + } +} + +// DynamicExposureBollBand calculates the max exposure with the Bollinger Band +type DynamicExposureBollBand struct { + // DynamicExposureBollBandScale is used to define the exposure range with the given percentage. + DynamicExposureBollBandScale *bbgo.PercentageScale `json:"dynamicExposurePositionScale"` + + *BollingerSetting + + StandardIndicatorSet *bbgo.StandardIndicatorSet + + dynamicExposureBollBand *indicator.BOLL +} + +// Initialize DynamicExposureBollBand +func (d *DynamicExposureBollBand) initialize(symbol string, session *bbgo.ExchangeSession) { + d.dynamicExposureBollBand = d.StandardIndicatorSet.BOLL(d.IntervalWindow, d.BandWidth) + + // Subscribe kline + session.Subscribe(types.KLineChannel, symbol, types.SubscribeOptions{ + Interval: d.dynamicExposureBollBand.Interval, + }) +} + +// getMaxExposure returns the max exposure +func (d *DynamicExposureBollBand) getMaxExposure(price float64) (fixedpoint.Value, error) { + downBand := d.dynamicExposureBollBand.DownBand.Last() + upBand := d.dynamicExposureBollBand.UpBand.Last() + sma := d.dynamicExposureBollBand.SMA.Last() + log.Infof("dynamicExposureBollBand bollinger band: up %f sma %f down %f", upBand, sma, downBand) + + bandPercentage := 0.0 + if price < sma { + // should be negative percentage + bandPercentage = (price - sma) / math.Abs(sma-downBand) + } else if price > sma { + // should be positive percentage + bandPercentage = (price - sma) / math.Abs(upBand-sma) + } + + v, err := d.DynamicExposureBollBandScale.Scale(bandPercentage) + if err != nil { + return fixedpoint.Zero, err + } + return fixedpoint.NewFromFloat(v), nil +} diff --git a/pkg/dynamicmetric/dynamic_spread.go b/pkg/dynamicmetric/dynamic_spread.go index 2f4d66360..a1e5e631c 100644 --- a/pkg/dynamicmetric/dynamic_spread.go +++ b/pkg/dynamicmetric/dynamic_spread.go @@ -160,12 +160,6 @@ func (ds *DynamicSpreadAmp) getBidSpread() (bidSpread float64, err error) { return 0, errors.New("incomplete dynamic spread settings or not enough data yet") } -// BollingerSetting is for Bollinger Band settings -type BollingerSetting struct { - types.IntervalWindow - BandWidth float64 `json:"bandWidth"` -} - type DynamicSpreadBollWidthRatio struct { // AskSpreadScale is used to define the ask spread range with the given percentage. AskSpreadScale *bbgo.PercentageScale `json:"askSpreadScale"`