grid2: integrate prometheus metrics

This commit is contained in:
c9s 2023-01-10 20:15:51 +08:00
parent 9ee4fa0064
commit 857b5d0f30
No known key found for this signature in database
GPG Key ID: 7385E7E464CB0A54
5 changed files with 176 additions and 5 deletions

View File

@ -18,8 +18,10 @@ const CancelOrderWaitTime = 20 * time.Millisecond
// ActiveOrderBook manages the local active order books. // ActiveOrderBook manages the local active order books.
//go:generate callbackgen -type ActiveOrderBook //go:generate callbackgen -type ActiveOrderBook
type ActiveOrderBook struct { type ActiveOrderBook struct {
Symbol string Symbol string
orders *types.SyncOrderMap orders *types.SyncOrderMap
newCallbacks []func(o types.Order)
filledCallbacks []func(o types.Order) filledCallbacks []func(o types.Order)
canceledCallbacks []func(o types.Order) canceledCallbacks []func(o types.Order)

View File

@ -6,6 +6,16 @@ import (
"github.com/c9s/bbgo/pkg/types" "github.com/c9s/bbgo/pkg/types"
) )
func (b *ActiveOrderBook) OnNew(cb func(o types.Order)) {
b.newCallbacks = append(b.newCallbacks, cb)
}
func (b *ActiveOrderBook) EmitNew(o types.Order) {
for _, cb := range b.newCallbacks {
cb(o)
}
}
func (b *ActiveOrderBook) OnFilled(cb func(o types.Order)) { func (b *ActiveOrderBook) OnFilled(cb func(o types.Order)) {
b.filledCallbacks = append(b.filledCallbacks, cb) b.filledCallbacks = append(b.filledCallbacks, cb)
} }

View File

@ -0,0 +1,77 @@
package grid2
import "github.com/prometheus/client_golang/prometheus"
var (
metricsGridNumOfOrders *prometheus.GaugeVec
metricsGridOrderPrices *prometheus.GaugeVec
metricsGridProfit *prometheus.GaugeVec
)
func labelKeys(labels prometheus.Labels) []string {
var keys []string
for k := range labels {
keys = append(keys, k)
}
return keys
}
func mergeLabels(a, b prometheus.Labels) prometheus.Labels {
labels := prometheus.Labels{}
for k, v := range a {
labels[k] = v
}
for k, v := range b {
labels[k] = v
}
return labels
}
func initMetrics(extendedLabels []string) {
metricsGridNumOfOrders = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "bbgo_grid2_num_of_orders",
Help: "number of orders",
},
append([]string{
"strategy_instance",
"exchange", // exchange name
"symbol", // symbol of the market
}, extendedLabels...),
)
metricsGridOrderPrices = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "bbgo_grid2_order_prices",
Help: "order prices",
},
append([]string{
"strategy_instance",
"exchange", // exchange name
"symbol", // symbol of the market
"ith",
}, extendedLabels...),
)
metricsGridProfit = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "bbgo_grid2_grid_profit",
Help: "realized grid profit",
},
append([]string{
"strategy_instance",
"exchange", // exchange name
"symbol", // symbol of the market
}, extendedLabels...),
)
}
func registerMetrics() {
prometheus.MustRegister(
metricsGridNumOfOrders,
metricsGridProfit,
metricsGridOrderPrices,
)
}

View File

@ -9,6 +9,7 @@ import (
"time" "time"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/c9s/bbgo/pkg/bbgo" "github.com/c9s/bbgo/pkg/bbgo"
@ -111,6 +112,13 @@ type Strategy struct {
ResetPositionWhenStart bool `json:"resetPositionWhenStart"` ResetPositionWhenStart bool `json:"resetPositionWhenStart"`
// StrategyInstance
// an optional field for prometheus metrics
StrategyInstance string `json:"strategyInstance"`
// PrometheusLabels will be used as the base prometheus labels
PrometheusLabels prometheus.Labels `json:"prometheusLabels"`
// FeeRate is used for calculating the minimal profit spread. // FeeRate is used for calculating the minimal profit spread.
// it makes sure that your grid configuration is profitable. // it makes sure that your grid configuration is profitable.
FeeRate fixedpoint.Value `json:"feeRate"` FeeRate fixedpoint.Value `json:"feeRate"`
@ -135,6 +143,7 @@ type Strategy struct {
gridReadyCallbacks []func() gridReadyCallbacks []func()
gridProfitCallbacks []func(stats *GridProfitStats, profit *GridProfit) gridProfitCallbacks []func(stats *GridProfitStats, profit *GridProfit)
gridClosedCallbacks []func() gridClosedCallbacks []func()
gridErrorCallbacks []func(err error)
} }
func (s *Strategy) ID() string { func (s *Strategy) ID() string {
@ -173,6 +182,13 @@ func (s *Strategy) Validate() error {
return nil return nil
} }
func (s *Strategy) Defaults() error {
if s.StrategyInstance == "" {
s.StrategyInstance = "main"
}
return nil
}
func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) { func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: types.Interval1m}) session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: types.Interval1m})
@ -355,10 +371,7 @@ func (s *Strategy) processFilledOrder(o types.Order) {
profit := s.calculateProfit(o, newPrice, newQuantity) profit := s.calculateProfit(o, newPrice, newQuantity)
s.logger.Infof("GENERATED GRID PROFIT: %+v", profit) s.logger.Infof("GENERATED GRID PROFIT: %+v", profit)
s.GridProfitStats.AddProfit(profit) s.GridProfitStats.AddProfit(profit)
s.EmitGridProfit(s.GridProfitStats, profit) s.EmitGridProfit(s.GridProfitStats, profit)
bbgo.Notify(profit)
bbgo.Notify(s.GridProfitStats)
case types.SideTypeBuy: case types.SideTypeBuy:
newSide = types.SideTypeSell newSide = types.SideTypeSell
@ -668,6 +681,8 @@ func (s *Strategy) newOrderUpdateHandler(ctx context.Context, session *bbgo.Exch
return func(o types.Order) { return func(o types.Order) {
s.handleOrderFilled(o) s.handleOrderFilled(o)
bbgo.Sync(ctx, s) bbgo.Sync(ctx, s)
s.updateOpenOrderPricesMetrics(s.orderExecutor.ActiveMakerOrders().Orders())
} }
} }
@ -835,6 +850,10 @@ func (s *Strategy) openGrid(ctx context.Context, session *bbgo.ExchangeSession)
return err return err
} }
// update the number of orders to metrics
baseLabels := s.newPrometheusLabels()
metricsGridNumOfOrders.With(baseLabels).Set(float64(len(createdOrders)))
var orderIds []uint64 var orderIds []uint64
for _, order := range createdOrders { for _, order := range createdOrders {
@ -854,9 +873,30 @@ func (s *Strategy) openGrid(ctx context.Context, session *bbgo.ExchangeSession)
s.logger.Infof("ALL GRID ORDERS SUBMITTED") s.logger.Infof("ALL GRID ORDERS SUBMITTED")
s.EmitGridReady() s.EmitGridReady()
s.updateOpenOrderPricesMetrics(createdOrders)
return nil return nil
} }
func (s *Strategy) updateOpenOrderPricesMetrics(orders []types.Order) {
orders = sortOrdersByPriceAscending(orders)
num := len(orders)
for idx, order := range orders {
labels := s.newPrometheusLabels()
labels["ith"] = strconv.Itoa(num - idx)
metricsGridOrderPrices.With(labels).Set(order.Price.Float64())
}
}
func sortOrdersByPriceAscending(orders []types.Order) []types.Order {
sort.Slice(orders, func(i, j int) bool {
a := orders[i]
b := orders[j]
return a.Price.Compare(b.Price) < 0
})
return orders
}
func (s *Strategy) debugGridOrders(submitOrders []types.SubmitOrder, lastPrice fixedpoint.Value) { func (s *Strategy) debugGridOrders(submitOrders []types.SubmitOrder, lastPrice fixedpoint.Value) {
s.logger.Infof("GRID ORDERS: [") s.logger.Infof("GRID ORDERS: [")
for i, order := range submitOrders { for i, order := range submitOrders {
@ -1253,6 +1293,20 @@ func scanMissingPinPrices(orderBook *bbgo.ActiveOrderBook, pins []Pin) PriceMap
return missingPrices return missingPrices
} }
func (s *Strategy) newPrometheusLabels() prometheus.Labels {
labels := prometheus.Labels{
"strategy_instance": s.StrategyInstance,
"exchange": s.session.Name,
"symbol": s.Symbol,
}
if s.PrometheusLabels == nil {
return labels
}
return mergeLabels(s.PrometheusLabels, labels)
}
func (s *Strategy) Run(ctx context.Context, _ bbgo.OrderExecutor, session *bbgo.ExchangeSession) error { func (s *Strategy) Run(ctx context.Context, _ bbgo.OrderExecutor, session *bbgo.ExchangeSession) error {
instanceID := s.InstanceID() instanceID := s.InstanceID()
@ -1290,6 +1344,14 @@ func (s *Strategy) Run(ctx context.Context, _ bbgo.OrderExecutor, session *bbgo.
s.Position = types.NewPositionFromMarket(s.Market) s.Position = types.NewPositionFromMarket(s.Market)
} }
// initialize and register prometheus metrics
if s.PrometheusLabels != nil {
initMetrics(labelKeys(s.PrometheusLabels))
} else {
initMetrics(nil)
}
registerMetrics()
if s.ResetPositionWhenStart { if s.ResetPositionWhenStart {
s.Position.Reset() s.Position.Reset()
} }
@ -1318,6 +1380,16 @@ func (s *Strategy) Run(ctx context.Context, _ bbgo.OrderExecutor, session *bbgo.
s.orderExecutor = orderExecutor s.orderExecutor = orderExecutor
s.OnGridProfit(func(stats *GridProfitStats, profit *GridProfit) {
bbgo.Notify(profit)
bbgo.Notify(stats)
})
s.OnGridProfit(func(stats *GridProfitStats, profit *GridProfit) {
labels := s.newPrometheusLabels()
metricsGridProfit.With(labels).Set(stats.TotalQuoteProfit.Float64())
})
// TODO: detect if there are previous grid orders on the order book // TODO: detect if there are previous grid orders on the order book
if s.ClearOpenOrdersWhenStart { if s.ClearOpenOrdersWhenStart {
if err := s.clearOpenOrders(ctx, session); err != nil { if err := s.clearOpenOrders(ctx, session); err != nil {

View File

@ -33,3 +33,13 @@ func (s *Strategy) EmitGridClosed() {
cb() cb()
} }
} }
func (s *Strategy) OnGridError(cb func(err error)) {
s.gridErrorCallbacks = append(s.gridErrorCallbacks, cb)
}
func (s *Strategy) EmitGridError(err error) {
for _, cb := range s.gridErrorCallbacks {
cb(err)
}
}