Skip to content
This repository has been archived by the owner on Feb 1, 2024. It is now read-only.

Add metrics for trade tracking. #574

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from

Conversation

debnil
Copy link
Contributor

@debnil debnil commented Nov 3, 2020

This PR adds metrics to track trades. Thus far, it adds a handler interface and implementation. The metrics will be actually added after #508 is merged, because the architecture around adding individual metrics changes.

@debnil
Copy link
Contributor Author

debnil commented Nov 3, 2020

@nikhilsaraf WIP PR for trade metrics. The actual metrics are not added, since we change how metrics are added in the existing PRs, but all the functions to generate those metrics already exist - they just need to be called. Let me know if this handler architecture is what you had in mind when you suggested fillHandler and fillTracker as references.

Copy link
Contributor

@nikhilsaraf nikhilsaraf left a comment

Choose a reason for hiding this comment

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

added comments to help with the general direction of this PR, thanks for the draft!

api/exchange.go Outdated Show resolved Hide resolved
api/exchange.go Outdated Show resolved Hide resolved
cmd/trade.go Outdated Show resolved Hide resolved
plugins/tradeMetricsHandler.go Show resolved Hide resolved
plugins/tradeMetricsHandler.go Outdated Show resolved Hide resolved
plugins/tradeMetricsHandler.go Outdated Show resolved Hide resolved
}

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

Choose a reason for hiding this comment

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

👍

Copy link
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
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
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.

support/metrics/metricsTracker.go Outdated Show resolved Hide resolved
support/metrics/metricsTracker.go Outdated Show resolved Hide resolved
@@ -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
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!

@debnil
Copy link
Contributor Author

debnil commented Nov 6, 2020

@nikhilsaraf: awesome! I've done the bulk of this review, thanks so much. The only part that's left is the merging trade metrics with map. This only makes sense to do once the GUI metrics tracker is added in, since that changes the architecture of event updates.

Copy link
Contributor

@nikhilsaraf nikhilsaraf left a comment

Choose a reason for hiding this comment

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

this is the right direction. I've added some inline comments and we can merge once completed.

we also need to add logic that will "start the trade metrics handler" process by registering it with the fill tracker.

you can take this PR out of draft stage and we can merge after the next set of changes.

plugins/tradeMetricsHandler.go Show resolved Hide resolved
support/metrics/metricsTracker.go Outdated Show resolved Hide resolved
support/metrics/metricsTracker.go Outdated Show resolved Hide resolved
support/metrics/metricsTracker.go Outdated Show resolved Hide resolved
support/metrics/metricsTracker.go Outdated Show resolved Hide resolved
support/metrics/metricsTracker.go Outdated Show resolved Hide resolved
support/metrics/metricsTracker.go Outdated Show resolved Hide resolved
support/metrics/metricsTracker.go Outdated Show resolved Hide resolved
return
// HandleFill handles a new trade
// Implements FillHandler interface
func (h *TradeMetricsHandler) HandleFill(trade model.Trade) error {
Copy link
Contributor

Choose a reason for hiding this comment

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

we need to test this tradeMetricsHandler. For now let's have one simple test case, where we add 3 trades:

  • buy XLM/USDT
  • buy XLM/USDT
  • sell XLM/USDT

then we assert that the computed values on the struct returned from ComputeTradeMetrics is correct.

this TestComputeTradeMetrics would be very simple and would ensure our calculations are correct.

@debnil
Copy link
Contributor Author

debnil commented Nov 20, 2020

@nikhilsaraf : most of the requested changes are made, but there's now an import cycle. plugins/tradeMetricsHandler imports models.Trade, which imports plugins indirectly. I don't think there's a way around the handler importing models.Trade, since that's the base data structure for all of its operations. Curious what you think the best fix is.

Copy link
Contributor

@nikhilsaraf nikhilsaraf left a comment

Choose a reason for hiding this comment

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

can you point out where model.Trade imports plugins?

if there is a import cycle then the code won't compile.

@@ -921,6 +923,10 @@ func makeFillTracker(
}
}

metricsHandler := metricsTracker.GetHandler()
Copy link
Contributor

Choose a reason for hiding this comment

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

  1. we should not pull the metricsHandler from within the metricsTracker but pass it directly to this function. i.e. we don't need the GetHandler() method on metricsTracker either.

  2. this registration of the handler should happen before the if strategyFillHandlers != nil { paragraph. we want the strategy handlers to always be last because we execute the handler in the order in which they are added as well. I should have added this as a comment, can you please add this as well (that "handlers are executed in the order in which they are added")?

@@ -215,7 +216,7 @@ func MakeMetricsTrackerCli(
botStartTime: botStartTime,
isDisabled: isDisabled,
updateEventSentTime: nil,
handler: plugins.MakeTradeMetricsHandler(),
handler: handler,
Copy link
Contributor

Choose a reason for hiding this comment

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

👍


return tradeMetrics
// GetHandler returns the TradeMetricsHandler
func (mt *MetricsTracker) GetHandler() *plugins.TradeMetricsHandler {
Copy link
Contributor

Choose a reason for hiding this comment

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

can remove this (as commented above)

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.
Copy link
Contributor

Choose a reason for hiding this comment

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

thanks

quote := base * price

totalBaseVolume += base
totalPrice += price
Copy link
Contributor

Choose a reason for hiding this comment

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

don't think the math works out this way for price to get avgTradePrice accurately.

avgTradePrice should be totalQuoteVolume / totalBaseVolume (i.e. we don't need to track totalPrice) -- which we call vwap

not sure if I had suggested adding this, but if I had that is wrong.
we should split this up into avgBuyPrice and avgSellPrice
we already have vwap which is the price inclusive of both buys and sells so getting these separate buy/sell prices will be more informative

netQuoteVolume -= quote
} else {
netBaseVolume -= base
netQuoteVolume -= quote
Copy link
Contributor

Choose a reason for hiding this comment

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

may be a typo, should be netQuoteVolume += quote instead of netQuoteVolume -= quote

avgTradeSizeBase := totalBaseVolume / numTrades
avgTradeSizeQuote := totalQuoteVolume / numTrades
avgTradePrice := totalPrice / numTrades
avgTradeThroughputBase := totalBaseVolume / secondsSinceLastUpdate
Copy link
Contributor

Choose a reason for hiding this comment

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

can we rename this to tradeThroughputPerSecondBase (and quote equivalent)?

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants