FEATURE: add metrics for dca2

add log to debug
This commit is contained in:
chiahung.lin 2024-02-22 16:15:54 +08:00 committed by kbearXD
parent 3e087e8af3
commit 5936cf32c7
4 changed files with 193 additions and 17 deletions

View File

@ -0,0 +1,116 @@
package dca2
import (
"strconv"
"github.com/prometheus/client_golang/prometheus"
)
var (
metricsState *prometheus.GaugeVec
metricsNumOfActiveOrders *prometheus.GaugeVec
metricsNumOfOpenOrders *prometheus.GaugeVec
metricsProfit *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) {
if metricsState == nil {
metricsState = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "bbgo_dca2_state",
Help: "state of this DCA2 strategy",
},
append([]string{
"exchange",
"symbol",
}, extendedLabels...),
)
}
if metricsNumOfActiveOrders == nil {
metricsNumOfActiveOrders = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "bbgo_dca2_num_of_active_orders",
Help: "number of active orders",
},
append([]string{
"exchange",
"symbol",
}, extendedLabels...),
)
}
if metricsNumOfOpenOrders == nil {
metricsNumOfOpenOrders = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "bbgo_dca2_num_of_open_orders",
Help: "number of open orders",
},
append([]string{
"exchange",
"symbol",
}, extendedLabels...),
)
}
if metricsProfit == nil {
metricsProfit = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "bbgo_dca2_profit",
Help: "profit of this DCA@ strategy",
},
append([]string{
"exchange",
"symbol",
"round",
}, extendedLabels...),
)
}
}
var metricsRegistered = false
func registerMetrics() {
if metricsRegistered {
return
}
initMetrics(nil)
prometheus.MustRegister(
metricsState,
metricsNumOfActiveOrders,
metricsNumOfOpenOrders,
metricsProfit,
)
metricsRegistered = true
}
func updateProfitMetrics(round int64, profit float64) {
labels := mergeLabels(baseLabels, prometheus.Labels{
"round": strconv.FormatInt(round, 10),
})
metricsProfit.With(labels).Set(profit)
}

View File

@ -67,7 +67,7 @@ func (s *Strategy) recover(ctx context.Context) error {
if err != nil { if err != nil {
return err return err
} }
s.state = state s.updateState(state)
s.logger.Info("recover stats DONE") s.logger.Info("recover stats DONE")
return nil return nil

View File

@ -46,6 +46,13 @@ func (s *Strategy) initializeNextStateC() bool {
return isInitialize return isInitialize
} }
func (s *Strategy) updateState(state State) {
s.state = state
s.logger.Infof("[state] update state to %d", state)
metricsState.With(baseLabels).Set(float64(s.state))
}
func (s *Strategy) emitNextState(nextState State) { func (s *Strategy) emitNextState(nextState State) {
select { select {
case s.nextStateC <- nextState: case s.nextStateC <- nextState:
@ -63,17 +70,22 @@ func (s *Strategy) emitNextState(nextState State) {
// TakeProfitReady -> the takeProfit order filled -> // TakeProfitReady -> the takeProfit order filled ->
func (s *Strategy) runState(ctx context.Context) { func (s *Strategy) runState(ctx context.Context) {
s.logger.Info("[DCA] runState") s.logger.Info("[DCA] runState")
ticker := time.NewTicker(5 * time.Second) stateTriggerTicker := time.NewTicker(5 * time.Second)
defer ticker.Stop() defer stateTriggerTicker.Stop()
monitorTicker := time.NewTicker(10 * time.Minute)
defer monitorTicker.Stop()
for { for {
select { select {
case <-ctx.Done(): case <-ctx.Done():
s.logger.Info("[DCA] runState DONE") s.logger.Info("[DCA] runState DONE")
return return
case <-ticker.C: case <-stateTriggerTicker.C:
s.logger.Infof("[DCA] triggerNextState current state: %d", s.state) // s.logger.Infof("[DCA] triggerNextState current state: %d", s.state)
s.triggerNextState() s.triggerNextState()
case <-monitorTicker.C:
s.updateNumOfOrdersMetrics(ctx)
case nextState := <-s.nextStateC: case nextState := <-s.nextStateC:
s.logger.Infof("[DCA] currenct state: %d, next state: %d", s.state, nextState) s.logger.Infof("[DCA] currenct state: %d, next state: %d", s.state, nextState)
@ -131,7 +143,7 @@ func (s *Strategy) runWaitToOpenPositionState(ctx context.Context, next State) {
return return
} }
s.state = PositionOpening s.updateState(PositionOpening)
s.logger.Info("[State] WaitToOpenPosition -> PositionOpening") s.logger.Info("[State] WaitToOpenPosition -> PositionOpening")
} }
@ -141,17 +153,17 @@ func (s *Strategy) runPositionOpening(ctx context.Context, next State) {
s.logger.WithError(err).Error("failed to place dca orders, please check it.") s.logger.WithError(err).Error("failed to place dca orders, please check it.")
return return
} }
s.state = OpenPositionReady s.updateState(OpenPositionReady)
s.logger.Info("[State] PositionOpening -> OpenPositionReady") s.logger.Info("[State] PositionOpening -> OpenPositionReady")
} }
func (s *Strategy) runOpenPositionReady(_ context.Context, next State) { func (s *Strategy) runOpenPositionReady(_ context.Context, next State) {
s.state = OpenPositionOrderFilled s.updateState(OpenPositionOrderFilled)
s.logger.Info("[State] OpenPositionReady -> OpenPositionOrderFilled") s.logger.Info("[State] OpenPositionReady -> OpenPositionOrderFilled")
} }
func (s *Strategy) runOpenPositionOrderFilled(_ context.Context, next State) { func (s *Strategy) runOpenPositionOrderFilled(_ context.Context, next State) {
s.state = OpenPositionOrdersCancelling s.updateState(OpenPositionOrdersCancelling)
s.logger.Info("[State] OpenPositionOrderFilled -> OpenPositionOrdersCancelling") s.logger.Info("[State] OpenPositionOrderFilled -> OpenPositionOrdersCancelling")
// after open position cancelling, immediately trigger open position cancelled to cancel the other orders // after open position cancelling, immediately trigger open position cancelled to cancel the other orders
@ -164,7 +176,7 @@ func (s *Strategy) runOpenPositionOrdersCancelling(ctx context.Context, next Sta
s.logger.WithError(err).Error("failed to cancel maker orders") s.logger.WithError(err).Error("failed to cancel maker orders")
return return
} }
s.state = OpenPositionOrdersCancelled s.updateState(OpenPositionOrdersCancelled)
s.logger.Info("[State] OpenPositionOrdersCancelling -> OpenPositionOrdersCancelled") s.logger.Info("[State] OpenPositionOrdersCancelling -> OpenPositionOrdersCancelled")
// after open position cancelled, immediately trigger take profit ready to open take-profit order // after open position cancelled, immediately trigger take profit ready to open take-profit order
@ -177,7 +189,7 @@ func (s *Strategy) runOpenPositionOrdersCancelled(ctx context.Context, next Stat
s.logger.WithError(err).Error("failed to open take profit orders") s.logger.WithError(err).Error("failed to open take profit orders")
return return
} }
s.state = TakeProfitReady s.updateState(TakeProfitReady)
s.logger.Info("[State] OpenPositionOrdersCancelled -> TakeProfitReady") s.logger.Info("[State] OpenPositionOrdersCancelled -> TakeProfitReady")
} }
@ -200,6 +212,6 @@ func (s *Strategy) runTakeProfitReady(ctx context.Context, next State) {
// set the start time of the next round // set the start time of the next round
s.startTimeOfNextRound = time.Now().Add(s.CoolDownInterval.Duration()) s.startTimeOfNextRound = time.Now().Add(s.CoolDownInterval.Duration())
s.state = WaitToOpenPosition s.updateState(WaitToOpenPosition)
s.logger.Info("[State] TakeProfitReady -> WaitToOpenPosition") s.logger.Info("[State] TakeProfitReady -> WaitToOpenPosition")
} }

View File

@ -21,11 +21,15 @@ import (
"github.com/c9s/bbgo/pkg/util/tradingutil" "github.com/c9s/bbgo/pkg/util/tradingutil"
) )
const ID = "dca2" const (
ID = "dca2"
orderTag = "dca2"
)
const orderTag = "dca2" var (
log = logrus.WithField("strategy", ID)
var log = logrus.WithField("strategy", ID) baseLabels prometheus.Labels
)
func init() { func init() {
bbgo.RegisterStrategy(ID, &Strategy{}) bbgo.RegisterStrategy(ID, &Strategy{})
@ -119,6 +123,7 @@ func (s *Strategy) Defaults() error {
s.LogFields["symbol"] = s.Symbol s.LogFields["symbol"] = s.Symbol
s.LogFields["strategy"] = ID s.LogFields["strategy"] = ID
return nil return nil
} }
@ -135,6 +140,23 @@ 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})
} }
func (s *Strategy) newPrometheusLabels() prometheus.Labels {
labels := prometheus.Labels{
"exchange": "default",
"symbol": s.Symbol,
}
if s.Session != nil {
labels["exchange"] = s.Session.Name
}
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()
s.Session = session s.Session = session
@ -146,6 +168,15 @@ func (s *Strategy) Run(ctx context.Context, _ bbgo.OrderExecutor, session *bbgo.
s.Position = types.NewPositionFromMarket(s.Market) s.Position = types.NewPositionFromMarket(s.Market)
} }
// prometheus
if s.PrometheusLabels != nil {
initMetrics(labelKeys(s.PrometheusLabels))
}
registerMetrics()
// prometheus labels
baseLabels = s.newPrometheusLabels()
// if dev mode is on and it's not a new strategy // if dev mode is on and it's not a new strategy
if s.DevMode != nil && s.DevMode.Enabled && !s.DevMode.IsNewAccount { if s.DevMode != nil && s.DevMode.Enabled && !s.DevMode.IsNewAccount {
s.ProfitStats = newProfitStats(s.Market, s.QuoteInvestment) s.ProfitStats = newProfitStats(s.Market, s.QuoteInvestment)
@ -192,6 +223,9 @@ func (s *Strategy) Run(ctx context.Context, _ bbgo.OrderExecutor, session *bbgo.
default: default:
s.logger.Infof("[DCA] unsupported side (%s) of order: %s", o.Side, o) s.logger.Infof("[DCA] unsupported side (%s) of order: %s", o.Side, o)
} }
// update metrics when filled
s.updateNumOfOrdersMetrics(ctx)
}) })
session.MarketDataStream.OnKLine(func(kline types.KLine) { session.MarketDataStream.OnKLine(func(kline types.KLine) {
@ -218,7 +252,7 @@ func (s *Strategy) Run(ctx context.Context, _ bbgo.OrderExecutor, session *bbgo.
// 1. recoverWhenStart is false // 1. recoverWhenStart is false
// 2. dev mode is on and it's not new strategy // 2. dev mode is on and it's not new strategy
if !s.RecoverWhenStart || (s.DevMode != nil && s.DevMode.Enabled && !s.DevMode.IsNewAccount) { if !s.RecoverWhenStart || (s.DevMode != nil && s.DevMode.Enabled && !s.DevMode.IsNewAccount) {
s.state = WaitToOpenPosition s.updateState(WaitToOpenPosition)
} else { } else {
// recover // recover
if err := s.recover(ctx); err != nil { if err := s.recover(ctx); err != nil {
@ -400,9 +434,23 @@ func (s *Strategy) CalculateAndEmitProfit(ctx context.Context) error {
// emit profit // emit profit
s.EmitProfit(s.ProfitStats) s.EmitProfit(s.ProfitStats)
updateProfitMetrics(s.ProfitStats.Round, s.ProfitStats.CurrentRoundProfit.Float64())
s.ProfitStats.NewRound() s.ProfitStats.NewRound()
} }
return nil return nil
} }
func (s *Strategy) updateNumOfOrdersMetrics(ctx context.Context) {
// update open orders metrics
openOrders, err := s.Session.Exchange.QueryOpenOrders(ctx, s.Symbol)
if err != nil {
s.logger.WithError(err).Warn("failed to query open orders to update num of the orders metrics")
} else {
metricsNumOfOpenOrders.With(baseLabels).Set(float64(len(openOrders)))
}
// update active orders metrics
metricsNumOfActiveOrders.With(baseLabels).Set(float64(s.OrderExecutor.ActiveMakerOrders().NumOfOrders()))
}