From 51b70acde4f199911a065539d3f4527ad90b2113 Mon Sep 17 00:00:00 2001 From: Debnil Sur Date: Fri, 30 Oct 2020 16:23:37 -0700 Subject: [PATCH 1/8] Initial commit - create trade metrics handler plugin --- plugins/tradeMetricsHandler.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 plugins/tradeMetricsHandler.go diff --git a/plugins/tradeMetricsHandler.go b/plugins/tradeMetricsHandler.go new file mode 100644 index 000000000..6c82823c5 --- /dev/null +++ b/plugins/tradeMetricsHandler.go @@ -0,0 +1,18 @@ +package metrics + +// TODO DS Rework handler to accumulate trades, not just count. +type tradeMetricsHandler struct { + numTrades int +} + +func (h *tradeMetricsHandler) Reset() { + h.numTrades = 0 +} + +func (h *tradeMetricsHandler) Add(numNewTrades int) { + h.numTrades += numNewTrades +} + +func (h *tradeMetricsHandler) Get() int { + return h.numTrades +} From 2d78a35b38851056d2222a3dd3e0e27347953888 Mon Sep 17 00:00:00 2001 From: Debnil Sur Date: Mon, 2 Nov 2020 17:30:23 -0800 Subject: [PATCH 2/8] Add handler and tracker interfaces and impls --- api/exchange.go | 12 +++++++++ plugins/tradeMetricsHandler.go | 43 ++++++++++++++++++++++++------- support/metrics/metricsTracker.go | 9 +++++++ 3 files changed, 54 insertions(+), 10 deletions(-) diff --git a/api/exchange.go b/api/exchange.go index 3618e4adc..de358d083 100644 --- a/api/exchange.go +++ b/api/exchange.go @@ -237,6 +237,18 @@ type ExchangeShim interface { FillTrackable } +// TradeMetricsHandler is invoked by the MetricsTracker to process new trades +type TradeMetricsHandler interface { + HandleTrade(trade model.Trade) error + Read(trades []model.Trade) + Reset() +} + +// MetricsTracker knows how to track metrics, including trades +type MetricsTracker interface { + RegisterHandler(handler TradeMetricsHandler) +} + // ConvertOperation2TM is a temporary adapter to support transitioning from the old Go SDK to the new SDK without having to bump the major version func ConvertOperation2TM(ops []txnbuild.Operation) []build.TransactionMutator { muts := []build.TransactionMutator{} diff --git a/plugins/tradeMetricsHandler.go b/plugins/tradeMetricsHandler.go index 6c82823c5..760aa08b7 100644 --- a/plugins/tradeMetricsHandler.go +++ b/plugins/tradeMetricsHandler.go @@ -1,18 +1,41 @@ -package metrics +package plugins -// TODO DS Rework handler to accumulate trades, not just count. -type tradeMetricsHandler struct { - numTrades int +import ( + "github.com/stellar/kelp/model" +) + +// TradeMetricsHandler tracks the number of trades +type TradeMetricsHandler struct { + trades []model.Trade +} + +// MakeTradeMetricsHandler is a factory method for the TradeMetricsHandler +func MakeTradeMetricsHandler() *TradeMetricsHandler { + return &TradeMetricsHandler{ + trades: []model.Trade{}, + } +} + +// Reset sets the handler's trade counter to zero. +func (h *TradeMetricsHandler) Reset() { + h.trades = []model.Trade{} } -func (h *tradeMetricsHandler) Reset() { - h.numTrades = 0 +// Read stores new trades internally. +func (h *TradeMetricsHandler) Read(newTrades []model.Trade) error { + for _, nt := range newTrades { + h.trades = append(h.trades, nt) + } } -func (h *tradeMetricsHandler) Add(numNewTrades int) { - h.numTrades += numNewTrades +// Get returns the number of trades. +func (h *TradeMetricsHandler) Get() int { + return len(h.trades) } -func (h *tradeMetricsHandler) Get() int { - return h.numTrades +// HandleTrade impl +func (h *TradeMetricsHandler) HandleTrade(trade model.Trade) error { + // TODO: Add more if needed. + h.trades = append(h.trades, trade) + return nil } diff --git a/support/metrics/metricsTracker.go b/support/metrics/metricsTracker.go index 46701fbc5..c82edd4d8 100644 --- a/support/metrics/metricsTracker.go +++ b/support/metrics/metricsTracker.go @@ -8,6 +8,7 @@ import ( "runtime/debug" "time" + "github.com/stellar/kelp/plugins" "github.com/stellar/kelp/support/networking" ) @@ -31,6 +32,9 @@ type MetricsTracker struct { botStartTime time.Time isDisabled bool updateEventSentTime *time.Time + + // uninitialized + handlers []plugins.TradeMetricsHandler } // TODO DS Investigate other fields to add to this top-level event. @@ -304,3 +308,8 @@ func (mt *MetricsTracker) sendEvent(eventType string, eventProps interface{}) er } return nil } + +// RegisterHandler adds an internal handler. +func (mt *MetricsTracker) RegisterHandler(handler plugins.TradeMetricsHandler) { + mt.handlers = append(mt.handlers, handler) +} From 92018eb03afa52bb1824faff63001f2c3d842c1c Mon Sep 17 00:00:00 2001 From: Debnil Sur Date: Mon, 2 Nov 2020 17:40:30 -0800 Subject: [PATCH 3/8] Create and inject metrics handler. --- cmd/trade.go | 3 +++ plugins/tradeMetricsHandler.go | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/cmd/trade.go b/cmd/trade.go index 29f9c33d6..784f47214 100644 --- a/cmd/trade.go +++ b/cmd/trade.go @@ -609,6 +609,9 @@ func runTradeCmd(options inputs) { logger.Fatal(l, fmt.Errorf("could not generate metrics tracker: %s", e)) } + tradeMetricsHandler := plugins.MakeTradeMetricsHandler() + metricsTracker.RegisterHandler(tradeMetricsHandler) + e = metricsTracker.SendStartupEvent() if e != nil { logger.Fatal(l, fmt.Errorf("could not send startup event metric: %s", e)) diff --git a/plugins/tradeMetricsHandler.go b/plugins/tradeMetricsHandler.go index 760aa08b7..6d5ee89ba 100644 --- a/plugins/tradeMetricsHandler.go +++ b/plugins/tradeMetricsHandler.go @@ -22,7 +22,7 @@ func (h *TradeMetricsHandler) Reset() { } // Read stores new trades internally. -func (h *TradeMetricsHandler) Read(newTrades []model.Trade) error { +func (h *TradeMetricsHandler) Read(newTrades []model.Trade) { for _, nt := range newTrades { h.trades = append(h.trades, nt) } From 7a113bb806e2106f9c171780097f0a56c2484a8a Mon Sep 17 00:00:00 2001 From: Debnil Sur Date: Tue, 3 Nov 2020 11:11:42 -0800 Subject: [PATCH 4/8] Refine API, hook in read trades. --- api/exchange.go | 3 ++- plugins/tradeMetricsHandler.go | 15 ++++++------ support/metrics/metricsTracker.go | 40 ++++++++++++++++++++++++++++--- trader/trader.go | 2 ++ 4 files changed, 49 insertions(+), 11 deletions(-) diff --git a/api/exchange.go b/api/exchange.go index de358d083..3b504d273 100644 --- a/api/exchange.go +++ b/api/exchange.go @@ -239,9 +239,10 @@ type ExchangeShim interface { // TradeMetricsHandler is invoked by the MetricsTracker to process new trades type TradeMetricsHandler interface { - HandleTrade(trade model.Trade) error Read(trades []model.Trade) Reset() + TotalBaseVolume() float64 + NumTrades() int } // MetricsTracker knows how to track metrics, including trades diff --git a/plugins/tradeMetricsHandler.go b/plugins/tradeMetricsHandler.go index 6d5ee89ba..acf1ebe46 100644 --- a/plugins/tradeMetricsHandler.go +++ b/plugins/tradeMetricsHandler.go @@ -28,14 +28,15 @@ func (h *TradeMetricsHandler) Read(newTrades []model.Trade) { } } -// Get returns the number of trades. -func (h *TradeMetricsHandler) Get() int { +// NumTrades returns the number of trades. +func (h *TradeMetricsHandler) NumTrades() int { return len(h.trades) } -// HandleTrade impl -func (h *TradeMetricsHandler) HandleTrade(trade model.Trade) error { - // TODO: Add more if needed. - h.trades = append(h.trades, trade) - return nil +// TotalBaseVolume returns the total base volume. +func (h *TradeMetricsHandler) TotalBaseVolume() (total float64) { + for _, t := range h.trades { + total += t.Volume.AsFloat() + } + return } diff --git a/support/metrics/metricsTracker.go b/support/metrics/metricsTracker.go index c82edd4d8..9f9cd270e 100644 --- a/support/metrics/metricsTracker.go +++ b/support/metrics/metricsTracker.go @@ -8,7 +8,8 @@ import ( "runtime/debug" "time" - "github.com/stellar/kelp/plugins" + "github.com/stellar/kelp/api" + "github.com/stellar/kelp/model" "github.com/stellar/kelp/support/networking" ) @@ -34,7 +35,7 @@ type MetricsTracker struct { updateEventSentTime *time.Time // uninitialized - handlers []plugins.TradeMetricsHandler + handlers []api.TradeMetricsHandler } // TODO DS Investigate other fields to add to this top-level event. @@ -310,6 +311,39 @@ func (mt *MetricsTracker) sendEvent(eventType string, eventProps interface{}) er } // RegisterHandler adds an internal handler. -func (mt *MetricsTracker) RegisterHandler(handler plugins.TradeMetricsHandler) { +func (mt *MetricsTracker) RegisterHandler(handler api.TradeMetricsHandler) { mt.handlers = append(mt.handlers, handler) } + +// GetHandlers returns the handlers +func (mt *MetricsTracker) GetHandlers() []api.TradeMetricsHandler { + return mt.handlers +} + +// ResetHandlers resets all handlers +func (mt *MetricsTracker) ResetHandlers() { + for _, h := range mt.handlers { + h.Reset() + } +} + +// ReadIntoHandlers reads to all handlers +func (mt *MetricsTracker) ReadIntoHandlers(trades []model.Trade) { + for _, h := range mt.handlers { + h.Read(trades) + } +} + +func (mt *MetricsTracker) getTotalVolume() (total float64) { + for _, h := range mt.handlers { + total += h.TotalBaseVolume() + } + return +} + +func (mt *MetricsTracker) getTotalNumTrades() (total int) { + for _, h := range mt.handlers { + total += h.NumTrades() + } + return +} diff --git a/trader/trader.go b/trader/trader.go index 1dd7925ac..6799f15bf 100644 --- a/trader/trader.go +++ b/trader/trader.go @@ -311,6 +311,8 @@ func (t *Trader) synchronizeFetchBalancesOffersTrades() error { buyingAOffers2, ) { // this is the only success case + t.metricsTracker.ResetHandlers() + t.metricsTracker.ReadIntoHandlers(trades) t.setBalances(baseBalance1, quoteBalance1) t.setExistingOffers(sellingAOffers1, buyingAOffers1) return nil From 69bc64267e50c488b3915b17324783e0b77cd4be Mon Sep 17 00:00:00 2001 From: Debnil Sur Date: Tue, 3 Nov 2020 11:34:44 -0800 Subject: [PATCH 5/8] Add metrics to common props. --- support/metrics/metricsTracker.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/support/metrics/metricsTracker.go b/support/metrics/metricsTracker.go index 9f9cd270e..6d351e4bd 100644 --- a/support/metrics/metricsTracker.go +++ b/support/metrics/metricsTracker.go @@ -86,6 +86,8 @@ type commonProps struct { OperationalBufferNonNativePct float64 `json:"operational_buffer_non_native_pct"` SimMode bool `json:"sim_mode"` FixedIterations uint64 `json:"fixed_iterations"` + NumTradesSinceLastUpdate int `json:"num_trades_since_last_update"` + BaseVolumeTradesSinceLastUpdate float64 `json:"base_volume_trades_since_last_update"` } // updateProps holds the properties for the update Amplitude event. From 40c5d20b3b1f25d1fdbb2b17ed53afecb143db23 Mon Sep 17 00:00:00 2001 From: Debnil Sur Date: Thu, 5 Nov 2020 16:20:29 -0800 Subject: [PATCH 6/8] Address review - nikhilsaraf --- api/exchange.go | 13 ----- cmd/trade.go | 3 -- plugins/tradeMetricsHandler.go | 26 ++++------ support/metrics/metricsTracker.go | 80 +++++++++++++++++-------------- 4 files changed, 53 insertions(+), 69 deletions(-) diff --git a/api/exchange.go b/api/exchange.go index 3b504d273..3618e4adc 100644 --- a/api/exchange.go +++ b/api/exchange.go @@ -237,19 +237,6 @@ type ExchangeShim interface { FillTrackable } -// TradeMetricsHandler is invoked by the MetricsTracker to process new trades -type TradeMetricsHandler interface { - Read(trades []model.Trade) - Reset() - TotalBaseVolume() float64 - NumTrades() int -} - -// MetricsTracker knows how to track metrics, including trades -type MetricsTracker interface { - RegisterHandler(handler TradeMetricsHandler) -} - // ConvertOperation2TM is a temporary adapter to support transitioning from the old Go SDK to the new SDK without having to bump the major version func ConvertOperation2TM(ops []txnbuild.Operation) []build.TransactionMutator { muts := []build.TransactionMutator{} diff --git a/cmd/trade.go b/cmd/trade.go index 784f47214..29f9c33d6 100644 --- a/cmd/trade.go +++ b/cmd/trade.go @@ -609,9 +609,6 @@ func runTradeCmd(options inputs) { logger.Fatal(l, fmt.Errorf("could not generate metrics tracker: %s", e)) } - tradeMetricsHandler := plugins.MakeTradeMetricsHandler() - metricsTracker.RegisterHandler(tradeMetricsHandler) - e = metricsTracker.SendStartupEvent() if e != nil { logger.Fatal(l, fmt.Errorf("could not send startup event metric: %s", e)) diff --git a/plugins/tradeMetricsHandler.go b/plugins/tradeMetricsHandler.go index acf1ebe46..e30ef24a1 100644 --- a/plugins/tradeMetricsHandler.go +++ b/plugins/tradeMetricsHandler.go @@ -16,27 +16,19 @@ func MakeTradeMetricsHandler() *TradeMetricsHandler { } } -// Reset sets the handler's trade counter to zero. +// Reset sets the handler's trades to empty. func (h *TradeMetricsHandler) Reset() { h.trades = []model.Trade{} } -// Read stores new trades internally. -func (h *TradeMetricsHandler) Read(newTrades []model.Trade) { - for _, nt := range newTrades { - h.trades = append(h.trades, nt) - } -} - -// NumTrades returns the number of trades. -func (h *TradeMetricsHandler) NumTrades() int { - return len(h.trades) +// GetTrades returns all stored trades. +func (h *TradeMetricsHandler) GetTrades() []model.Trade { + return h.trades } -// TotalBaseVolume returns the total base volume. -func (h *TradeMetricsHandler) TotalBaseVolume() (total float64) { - for _, t := range h.trades { - total += t.Volume.AsFloat() - } - return +// HandleFill handles a new trade +// Implements FillHandler interface +func (h *TradeMetricsHandler) HandleFill(trade model.Trade) error { + h.trades = append(h.trades, trade) + return nil } diff --git a/support/metrics/metricsTracker.go b/support/metrics/metricsTracker.go index 6d351e4bd..0d52f7c17 100644 --- a/support/metrics/metricsTracker.go +++ b/support/metrics/metricsTracker.go @@ -8,8 +8,7 @@ import ( "runtime/debug" "time" - "github.com/stellar/kelp/api" - "github.com/stellar/kelp/model" + "github.com/stellar/kelp/plugins" "github.com/stellar/kelp/support/networking" ) @@ -33,9 +32,7 @@ type MetricsTracker struct { botStartTime time.Time isDisabled bool updateEventSentTime *time.Time - - // uninitialized - handlers []api.TradeMetricsHandler + handler *plugins.TradeMetricsHandler } // TODO DS Investigate other fields to add to this top-level event. @@ -86,8 +83,6 @@ type commonProps struct { OperationalBufferNonNativePct float64 `json:"operational_buffer_non_native_pct"` SimMode bool `json:"sim_mode"` FixedIterations uint64 `json:"fixed_iterations"` - NumTradesSinceLastUpdate int `json:"num_trades_since_last_update"` - BaseVolumeTradesSinceLastUpdate float64 `json:"base_volume_trades_since_last_update"` } // updateProps holds the properties for the update Amplitude event. @@ -212,6 +207,7 @@ func MakeMetricsTracker( botStartTime: botStartTime, isDisabled: isDisabled, updateEventSentTime: nil, + handler: plugins.MakeTradeMetricsHandler(), }, nil } @@ -312,40 +308,52 @@ func (mt *MetricsTracker) sendEvent(eventType string, eventProps interface{}) er return nil } -// RegisterHandler adds an internal handler. -func (mt *MetricsTracker) RegisterHandler(handler api.TradeMetricsHandler) { - mt.handlers = append(mt.handlers, handler) -} +// ComputeTradeMetrics computes various trade metrics and outputs a map that can be combined with other metrics. +func (mt *MetricsTracker) ComputeTradeMetrics() map[string]interface{} { + // TODO NS Ensure proper locking around trades on metrics handler + trades := mt.handler.GetTrades() + mt.handler.Reset() -// GetHandlers returns the handlers -func (mt *MetricsTracker) GetHandlers() []api.TradeMetricsHandler { - return mt.handlers -} + totalBaseVolume := 0.0 + totalQuoteVolume := 0.0 + totalPrice := 0.0 + netBaseVolume := 0.0 + netQuoteVolume := 0.0 -// ResetHandlers resets all handlers -func (mt *MetricsTracker) ResetHandlers() { - for _, h := range mt.handlers { - h.Reset() - } -} + numTrades := float64(len(trades)) + for _, t := range trades { + base := t.Volume.AsFloat() + price := t.Price.AsFloat() + quote := base * price -// ReadIntoHandlers reads to all handlers -func (mt *MetricsTracker) ReadIntoHandlers(trades []model.Trade) { - for _, h := range mt.handlers { - h.Read(trades) - } -} + totalBaseVolume += base + totalPrice += price + totalQuoteVolume += quote -func (mt *MetricsTracker) getTotalVolume() (total float64) { - for _, h := range mt.handlers { - total += h.TotalBaseVolume() + if t.OrderAction.IsBuy() { + netBaseVolume += base + netQuoteVolume -= quote + } else { + netBaseVolume -= base + netQuoteVolume -= quote + } } - return -} -func (mt *MetricsTracker) getTotalNumTrades() (total int) { - for _, h := range mt.handlers { - total += h.NumTrades() + avgTradeSizeBase := totalBaseVolume / numTrades + avgTradeSizeQuote := totalQuoteVolume / numTrades + avgTradePrice := totalPrice / numTrades + + tradeMetrics := map[string]interface{}{ + "total_base_volume": totalBaseVolume, + "total_quote_volume": totalQuoteVolume, + "net_base_volume": netBaseVolume, + "net_quote_volume": netQuoteVolume, + "num_trades": numTrades, + "avg_trade_size_base": avgTradeSizeBase, + "avg_trade_size_quote": avgTradeSizeQuote, + "avg_trade_price": avgTradePrice, + "vwap": totalQuoteVolume / totalBaseVolume, } - return + + return tradeMetrics } From 79364e7820a5bbec8cca9524c827917b61bb40f9 Mon Sep 17 00:00:00 2001 From: Debnil Sur Date: Thu, 5 Nov 2020 16:26:09 -0800 Subject: [PATCH 7/8] Fix build issue. --- trader/trader.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/trader/trader.go b/trader/trader.go index 6799f15bf..1dd7925ac 100644 --- a/trader/trader.go +++ b/trader/trader.go @@ -311,8 +311,6 @@ func (t *Trader) synchronizeFetchBalancesOffersTrades() error { buyingAOffers2, ) { // this is the only success case - t.metricsTracker.ResetHandlers() - t.metricsTracker.ReadIntoHandlers(trades) t.setBalances(baseBalance1, quoteBalance1) t.setExistingOffers(sellingAOffers1, buyingAOffers1) return nil From ebdc97ad8d22b00ac58e7ed97b11511681dacc1c Mon Sep 17 00:00:00 2001 From: Debnil Sur Date: Thu, 19 Nov 2020 18:43:36 -0800 Subject: [PATCH 8/8] Address review - refactor handler and plugin relation. --- cmd/server_amd64.go | 3 +- cmd/trade.go | 6 ++++ plugins/metricsTracker.go | 56 ++++------------------------- plugins/tradeMetricsHandler.go | 66 ++++++++++++++++++++++++++++++++++ 4 files changed, 81 insertions(+), 50 deletions(-) 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 de0771d48..325e441d7 100644 --- a/plugins/metricsTracker.go +++ b/plugins/metricsTracker.go @@ -166,6 +166,7 @@ func MakeMetricsTrackerCli( operationalBufferNonNativePct float64, simMode bool, fixedIterations uint64, + handler *plugins.TradeMetricsHandler, ) (*MetricsTracker, error) { props := commonProps{ CliVersion: version, @@ -215,7 +216,7 @@ func MakeMetricsTrackerCli( botStartTime: botStartTime, isDisabled: isDisabled, updateEventSentTime: nil, - handler: plugins.MakeTradeMetricsHandler(), + handler: handler, cliVersion: version, }, nil } @@ -236,6 +237,7 @@ func MakeMetricsTrackerGui( goVersion string, guiVersion string, isDisabled bool, + handler *plugins.TradeMetricsHandler, ) (*MetricsTracker, error) { props := commonProps{ CliVersion: version, @@ -262,6 +264,7 @@ func MakeMetricsTrackerGui( botStartTime: botStartTime, isDisabled: isDisabled, updateEventSentTime: nil, + handler: handler, cliVersion: version, }, nil } @@ -372,52 +375,7 @@ func (mt *MetricsTracker) SendEvent(eventType string, eventPropsInterface interf return nil } -// ComputeTradeMetrics computes various trade metrics and outputs a map that can be combined with other metrics. -func (mt *MetricsTracker) ComputeTradeMetrics() map[string]interface{} { - // TODO NS Ensure proper locking around trades on metrics handler - trades := mt.handler.GetTrades() - mt.handler.Reset() - - 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 - - tradeMetrics := map[string]interface{}{ - "total_base_volume": totalBaseVolume, - "total_quote_volume": totalQuoteVolume, - "net_base_volume": netBaseVolume, - "net_quote_volume": netQuoteVolume, - "num_trades": numTrades, - "avg_trade_size_base": avgTradeSizeBase, - "avg_trade_size_quote": avgTradeSizeQuote, - "avg_trade_price": avgTradePrice, - "vwap": totalQuoteVolume / totalBaseVolume, - } - - return tradeMetrics +// GetHandler returns the TradeMetricsHandler +func (mt *MetricsTracker) GetHandler() *plugins.TradeMetricsHandler { + return mt.handler } diff --git a/plugins/tradeMetricsHandler.go b/plugins/tradeMetricsHandler.go index e30ef24a1..f5fd04173 100644 --- a/plugins/tradeMetricsHandler.go +++ b/plugins/tradeMetricsHandler.go @@ -9,6 +9,20 @@ 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{ @@ -32,3 +46,55 @@ 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 +}