diff --git a/cmd/server_amd64.go b/cmd/server_amd64.go index a9973b489..fae5cd1f4 100644 --- a/cmd/server_amd64.go +++ b/cmd/server_amd64.go @@ -300,6 +300,7 @@ func init() { } httpClient := &http.Client{} + metricsHandler := plugins.MakeTradeMetricsHandler() metricsTracker, e = plugins.MakeMetricsTrackerGui( deviceID, deviceID, @@ -315,7 +316,7 @@ func init() { runtime.Version(), guiVersion, *options.noHeaders, // disable metrics if the CLI specified no headers - + metricsHandler, ) if e != nil { panic(e) diff --git a/cmd/trade.go b/cmd/trade.go index 422360066..4fba55026 100644 --- a/cmd/trade.go +++ b/cmd/trade.go @@ -566,6 +566,7 @@ func runTradeCmd(options inputs) { isTestnet := strings.Contains(botConfig.HorizonURL, "test") && botConfig.IsTradingSdex() + metricsHandler := plugins.MakeTradeMetricsHandler() metricsTracker, e := plugins.MakeMetricsTrackerCli( userID, deviceID, @@ -603,6 +604,7 @@ func runTradeCmd(options inputs) { *options.operationalBufferNonNativePct, *options.simMode, *options.fixedIterations, + metricsHandler, ) if e != nil { logger.Fatal(l, fmt.Errorf("could not generate metrics tracker: %s", e)) @@ -921,6 +923,10 @@ func makeFillTracker( } } + metricsHandler := metricsTracker.GetHandler() + if metricsHandler != nil { + fillTracker.RegisterHandler(metricsHander) + } return fillTracker } diff --git a/plugins/metricsTracker.go b/plugins/metricsTracker.go index e35434c57..325e441d7 100644 --- a/plugins/metricsTracker.go +++ b/plugins/metricsTracker.go @@ -8,6 +8,7 @@ import ( "runtime/debug" "time" + "github.com/stellar/kelp/plugins" "github.com/stellar/kelp/support/networking" "github.com/stellar/kelp/support/utils" ) @@ -34,6 +35,8 @@ type MetricsTracker struct { isDisabled bool updateEventSentTime *time.Time cliVersion string + + handler *plugins.TradeMetricsHandler } // TODO DS Investigate other fields to add to this top-level event. @@ -163,6 +166,7 @@ func MakeMetricsTrackerCli( operationalBufferNonNativePct float64, simMode bool, fixedIterations uint64, + handler *plugins.TradeMetricsHandler, ) (*MetricsTracker, error) { props := commonProps{ CliVersion: version, @@ -212,6 +216,7 @@ func MakeMetricsTrackerCli( botStartTime: botStartTime, isDisabled: isDisabled, updateEventSentTime: nil, + handler: handler, cliVersion: version, }, nil } @@ -232,6 +237,7 @@ func MakeMetricsTrackerGui( goVersion string, guiVersion string, isDisabled bool, + handler *plugins.TradeMetricsHandler, ) (*MetricsTracker, error) { props := commonProps{ CliVersion: version, @@ -258,6 +264,7 @@ func MakeMetricsTrackerGui( botStartTime: botStartTime, isDisabled: isDisabled, updateEventSentTime: nil, + handler: handler, cliVersion: version, }, nil } @@ -367,3 +374,8 @@ func (mt *MetricsTracker) SendEvent(eventType string, eventPropsInterface interf } return nil } + +// GetHandler returns the TradeMetricsHandler +func (mt *MetricsTracker) GetHandler() *plugins.TradeMetricsHandler { + return mt.handler +} diff --git a/plugins/tradeMetricsHandler.go b/plugins/tradeMetricsHandler.go new file mode 100644 index 000000000..f5fd04173 --- /dev/null +++ b/plugins/tradeMetricsHandler.go @@ -0,0 +1,100 @@ +package plugins + +import ( + "github.com/stellar/kelp/model" +) + +// TradeMetricsHandler tracks the number of trades +type TradeMetricsHandler struct { + trades []model.Trade +} + +// TradeMetrics stores the computed trade-related metrics. +// TODO DS Pre-pend interval__ to all JSON fields. +type TradeMetrics struct { + totalBaseVolume float64 `json:"total_base_volume"` + totalQuoteVolume float64 `json:"total_quote_volume"` + netBaseVolume float64 `json:"net_base_volume"` + netQuoteVolume float64 `json:"net_quote_volume"` + numTrades float64 `json:"num_trades"` + avgTradeSizeBase float64 `json:"avg_trade_size_base"` + avgTradeSizeQuote float64 `json:"avg_trade_size_quote"` + avgTradePrice float64 `json:"avg_trade_price"` + vwap float64 `json:"vwap"` +} + +// MakeTradeMetricsHandler is a factory method for the TradeMetricsHandler +func MakeTradeMetricsHandler() *TradeMetricsHandler { + return &TradeMetricsHandler{ + trades: []model.Trade{}, + } +} + +// Reset sets the handler's trades to empty. +func (h *TradeMetricsHandler) Reset() { + h.trades = []model.Trade{} +} + +// GetTrades returns all stored trades. +func (h *TradeMetricsHandler) GetTrades() []model.Trade { + return h.trades +} + +// HandleFill handles a new trade +// Implements FillHandler interface +func (h *TradeMetricsHandler) HandleFill(trade model.Trade) error { + h.trades = append(h.trades, trade) + return nil +} + +// ComputeTradeMetrics computes trade-related metrics. +func (h *TradeMetricsHandler) ComputeTradeMetrics(secondsSinceLastUpdate float64) TradeMetrics { + trades := h.GetTrades() + h.Reset() + + // Note that we don't consider fees right now, as we don't have the infrastructure to understand fee units when tracking trades. + totalBaseVolume := 0.0 + totalQuoteVolume := 0.0 + totalPrice := 0.0 + netBaseVolume := 0.0 + netQuoteVolume := 0.0 + + numTrades := float64(len(trades)) + for _, t := range trades { + base := t.Volume.AsFloat() + price := t.Price.AsFloat() + quote := base * price + + totalBaseVolume += base + totalPrice += price + totalQuoteVolume += quote + + if t.OrderAction.IsBuy() { + netBaseVolume += base + netQuoteVolume -= quote + } else { + netBaseVolume -= base + netQuoteVolume -= quote + } + } + + avgTradeSizeBase := totalBaseVolume / numTrades + avgTradeSizeQuote := totalQuoteVolume / numTrades + avgTradePrice := totalPrice / numTrades + avgTradeThroughputBase := totalBaseVolume / secondsSinceLastUpdate + avgTradeThroughputQuote := totalQuoteVolume / secondsSinceLastUpdate + + tradeMetrics := TradeMetrics{ + totalBaseVolume: totalBaseVolume, + totalQuoteVolume: totalQuoteVolume, + netBaseVolume: netBaseVolume, + netQuoteVolume: netQuoteVolume, + numTrades: numTrades, + avgTradeSizeBase: avgTradeSizeBase, + avgTradeSizeQuote: avgTradeSizeQuote, + avgTradePrice: avgTradePrice, + vwap: totalQuoteVolume / totalBaseVolume, + } + + return tradeMetrics +}