Skip to content

Commit

Permalink
Merge pull request #1722 from c9s/c9s/xmaker/add-signals
Browse files Browse the repository at this point in the history
FEATURE: [xmaker] add signals
  • Loading branch information
c9s authored Aug 30, 2024
2 parents 88d7783 + 7c4b3e8 commit 04bed16
Show file tree
Hide file tree
Showing 4 changed files with 343 additions and 32 deletions.
7 changes: 7 additions & 0 deletions pkg/strategy/xmaker/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ var askMarginMetrics = prometheus.NewGaugeVec(
Help: "the current ask margin (dynamic)",
}, []string{"strategy_type", "strategy_id", "exchange", "symbol"})

var aggregatedSignalMetrics = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "xmaker_aggregated_signal",
Help: "",
}, []string{"strategy_type", "strategy_id", "exchange", "symbol"})

var configNumOfLayersMetrics = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "xmaker_config_num_of_layers",
Expand Down Expand Up @@ -70,6 +76,7 @@ func init() {
makerBestAskPriceMetrics,
bidMarginMetrics,
askMarginMetrics,
aggregatedSignalMetrics,
configNumOfLayersMetrics,
configMaxExposureMetrics,
configBidMarginMetrics,
Expand Down
87 changes: 87 additions & 0 deletions pkg/strategy/xmaker/signal_boll.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package xmaker

import (
"context"

"github.com/prometheus/client_golang/prometheus"

"github.com/c9s/bbgo/pkg/bbgo"
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/indicator/v2"
"github.com/c9s/bbgo/pkg/types"
)

var bollingerBandSignalMetrics = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "xmaker_bollinger_band_signal",
Help: "",
}, []string{"symbol"})

func init() {
prometheus.MustRegister(bollingerBandSignalMetrics)
}

type BollingerBandTrendSignal struct {
types.IntervalWindow
MinBandWidth float64 `json:"minBandWidth"`
MaxBandWidth float64 `json:"maxBandWidth"`

indicator *indicatorv2.BOLLStream
symbol string
lastK *types.KLine
}

func (s *BollingerBandTrendSignal) Bind(ctx context.Context, session *bbgo.ExchangeSession, symbol string) error {
if s.MaxBandWidth == 0.0 {
s.MaxBandWidth = 2.0
}

if s.MinBandWidth == 0.0 {
s.MinBandWidth = 1.0
}

s.symbol = symbol
s.indicator = session.Indicators(symbol).BOLL(s.IntervalWindow, s.MinBandWidth)

session.MarketDataStream.OnKLineClosed(types.KLineWith(s.symbol, s.IntervalWindow.Interval, func(kline types.KLine) {
s.lastK = &kline
}))

bollingerBandSignalMetrics.WithLabelValues(s.symbol).Set(0.0)
return nil
}

func (s *BollingerBandTrendSignal) CalculateSignal(ctx context.Context) (float64, error) {
if s.lastK == nil {
return 0, nil
}

closePrice := s.lastK.Close

// when bid price is lower than the down band, then it's in the downtrend
// when ask price is higher than the up band, then it's in the uptrend
lastDownBand := fixedpoint.NewFromFloat(s.indicator.DownBand.Last(0))
lastUpBand := fixedpoint.NewFromFloat(s.indicator.UpBand.Last(0))

maxBandWidth := s.indicator.StdDev.Last(0) * s.MaxBandWidth

signal := 0.0

// if the price is inside the band, do not vote
if closePrice.Compare(lastDownBand) > 0 && closePrice.Compare(lastUpBand) < 0 {
signal = 0.0
} else if closePrice.Compare(lastDownBand) < 0 {
signal = lastDownBand.Sub(closePrice).Float64() / maxBandWidth * -2.0
} else if closePrice.Compare(lastUpBand) > 0 {
signal = closePrice.Sub(lastUpBand).Float64() / maxBandWidth * 2.0
}

log.Infof("[BollingerBandTrendSignal] %f up/down = %f/%f, close price = %f",
signal,
lastUpBand.Float64(),
lastDownBand.Float64(),
closePrice.Float64())

bollingerBandSignalMetrics.WithLabelValues(s.symbol).Set(signal)
return signal, nil
}
68 changes: 68 additions & 0 deletions pkg/strategy/xmaker/signal_book.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package xmaker

import (
"context"

"github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus"

"github.com/c9s/bbgo/pkg/bbgo"
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/types"
)

var orderBookSignalMetrics = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "xmaker_order_book_signal",
Help: "",
}, []string{"symbol"})

func init() {
prometheus.MustRegister(orderBookSignalMetrics)
}

type OrderBookBestPriceVolumeSignal struct {
RatioThreshold fixedpoint.Value `json:"ratioThreshold"`
MinVolume fixedpoint.Value `json:"minVolume"`

symbol string
book *types.StreamOrderBook
}

func (s *OrderBookBestPriceVolumeSignal) Bind(ctx context.Context, session *bbgo.ExchangeSession, symbol string) error {
if s.book == nil {
return errors.New("s.book can not be nil")
}

s.symbol = symbol
orderBookSignalMetrics.WithLabelValues(s.symbol).Set(0.0)
return nil
}

func (s *OrderBookBestPriceVolumeSignal) CalculateSignal(ctx context.Context) (float64, error) {
bid, ask, ok := s.book.BestBidAndAsk()
if !ok {
return 0.0, nil
}

// TODO: may use scale to define this
sumVol := bid.Volume.Add(ask.Volume)
bidRatio := bid.Volume.Div(sumVol)
askRatio := ask.Volume.Div(sumVol)
denominator := fixedpoint.One.Sub(s.RatioThreshold)
signal := 0.0
if bid.Volume.Compare(s.MinVolume) < 0 && ask.Volume.Compare(s.MinVolume) < 0 {
signal = 0.0
} else if bidRatio.Compare(s.RatioThreshold) >= 0 {
numerator := bidRatio.Sub(s.RatioThreshold)
signal = numerator.Div(denominator).Float64()
} else if askRatio.Compare(s.RatioThreshold) >= 0 {
numerator := askRatio.Sub(s.RatioThreshold)
signal = -numerator.Div(denominator).Float64()
}

log.Infof("[OrderBookBestPriceVolumeSignal] %f bid/ask = %f/%f", signal, bid.Volume.Float64(), ask.Volume.Float64())

orderBookSignalMetrics.WithLabelValues(s.symbol).Set(signal)
return signal, nil
}
Loading

0 comments on commit 04bed16

Please sign in to comment.