Skip to content
This repository was archived by the owner on Feb 1, 2024. It is now read-only.
Draft
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions api/exchange.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,19 @@ type ExchangeShim interface {
FillTrackable
}

// TradeMetricsHandler is invoked by the MetricsTracker to process new trades
type TradeMetricsHandler interface {
Comment thread
debnil marked this conversation as resolved.
Outdated
Read(trades []model.Trade)
Reset()
TotalBaseVolume() float64
NumTrades() int
}

// MetricsTracker knows how to track metrics, including trades
type MetricsTracker interface {
Comment thread
debnil marked this conversation as resolved.
Outdated
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{}
Expand Down
3 changes: 3 additions & 0 deletions cmd/trade.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Comment thread
debnil marked this conversation as resolved.
Outdated

e = metricsTracker.SendStartupEvent()
if e != nil {
logger.Fatal(l, fmt.Errorf("could not send startup event metric: %s", e))
Expand Down
42 changes: 42 additions & 0 deletions plugins/tradeMetricsHandler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package plugins

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 {
Comment thread
debnil marked this conversation as resolved.
return &TradeMetricsHandler{
trades: []model.Trade{},
}
}

// Reset sets the handler's trade counter to zero.
func (h *TradeMetricsHandler) Reset() {
h.trades = []model.Trade{}
}

// Read stores new trades internally.
func (h *TradeMetricsHandler) Read(newTrades []model.Trade) {
Comment thread
debnil marked this conversation as resolved.
Outdated
for _, nt := range newTrades {
h.trades = append(h.trades, nt)
}
}

// NumTrades returns the number of trades.
func (h *TradeMetricsHandler) NumTrades() int {
Comment thread
debnil marked this conversation as resolved.
Outdated
return len(h.trades)
}

// TotalBaseVolume returns the total base volume.
func (h *TradeMetricsHandler) TotalBaseVolume() (total float64) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should add additional metrics here -- maybe can return a map instead of individual metrics and call the function ComputeMetrics or something similar to indicate that we are (a) running calculations and (b) we will return the metrics result that we wanted to collect from this handler.

(Note: base units is model.Trade.Amount and quote units is model.Trade.Amount * model.Trade.Price)

  • totalBaseVolume (buy and sell action are both +ve) -- buying $100 and selling $100 should equal $200
  • totalQuoteVolume (buy and sell action are both +ve)
  • netBaseVolume (buy action is +ve, sell is -ve) -- buying $100 and selling $100 should equal $0
  • netQuoteVolume (buy action is -ve, sell action is +ve)
  • numTrades
  • avgTradeSizeBase (totalBaseVolume/numTrades)
  • avgTradeSizeQuote (totalQuoteVolume/numTrades)
  • avgTradePrice (simple average of the trade price)
  • vwap (volume-weighted average of the trade price) -- I think this simplifies to totalQuoteVolume/totalBaseVolume but you'll have to confirm

anything else you can think of that would be important to capture for trades?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PS: we will need to combine this with Reset() and make sure that we create a copy of the trades structure and then reset it, and then run our computations. This will ensure that there is a very small window in which we lose trades. We should also add a TODO NS at that point so we add proper locking around the trades field on the metrics handler, since we will face contention for it when writing from the HandleFills function which will be called from a separate thread.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is everything (including that VWAP formula) -- super helpful, thank you!! I've made these changes.

for _, t := range h.trades {
total += t.Volume.AsFloat()
}
return
}
45 changes: 45 additions & 0 deletions support/metrics/metricsTracker.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"runtime/debug"
"time"

"github.com/stellar/kelp/api"
"github.com/stellar/kelp/model"
"github.com/stellar/kelp/support/networking"
)

Expand All @@ -31,6 +33,9 @@ type MetricsTracker struct {
botStartTime time.Time
isDisabled bool
updateEventSentTime *time.Time

// uninitialized
handlers []api.TradeMetricsHandler
Comment thread
debnil marked this conversation as resolved.
Outdated
}

// TODO DS Investigate other fields to add to this top-level event.
Expand Down Expand Up @@ -81,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"`
Comment thread
debnil marked this conversation as resolved.
Outdated
BaseVolumeTradesSinceLastUpdate float64 `json:"base_volume_trades_since_last_update"`
}

// updateProps holds the properties for the update Amplitude event.
Expand Down Expand Up @@ -304,3 +311,41 @@ func (mt *MetricsTracker) sendEvent(eventType string, eventProps interface{}) er
}
return nil
}

// RegisterHandler adds an internal handler.
func (mt *MetricsTracker) RegisterHandler(handler api.TradeMetricsHandler) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't need any of these functions here.

instead inside sendUpdate we can call ComputeMetrics on the tradeMetricsHandler instance that we hold and merge that output with the update map that we are sending to sendEvent -- that's it!

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
}
2 changes: 2 additions & 0 deletions trader/trader.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down