-
Notifications
You must be signed in to change notification settings - Fork 874
Coinbase: Update exchange implementation #1480
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Coinbase: Update exchange implementation #1480
Conversation
Continual enhance of Coinbase tests The revamp continues Oh jeez the Orderbook part's unfinished don't look Coinbase revamp, Orderbook still unfinished
…otrader into coinbase_api_revamp
V3 done, onto V2 Coinbase revamp nears completion Coinbase revamp nears completion Test commit should fail Coinbase revamp nears completion
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for updating against the API docs after last time
Auth and unauth works well, coverage is excellent. Just some really quick changes from me, but I am happy with its state
exchanges/order/orders.go
Outdated
@@ -40,12 +40,12 @@ var ( | |||
ErrAmountMustBeSet = errors.New("amount must be set") | |||
ErrClientOrderIDMustBeSet = errors.New("client order ID must be set") | |||
ErrUnknownSubmissionAmountType = errors.New("unknown submission amount type") | |||
ErrIDNotSet = errors.New("ID not set") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Required] You have exported this but it is unused
sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders) | ||
curSweeps, err := e.ListFuturesSweeps(t.Context()) | ||
assert.NoError(t, err) | ||
partialSkip := false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Optional] Remove this extra handholding please
Test works fine just as
_, err := e.CancelPendingFuturesSweep(t.Context())
assert.NoError(t, err)
sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders) | ||
curSweeps, err := e.ListFuturesSweeps(t.Context()) | ||
assert.NoError(t, err) | ||
preCancel := false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Optional] Remove this handholding stuff. Just call the func
ordID, err := e.ListOrders(t.Context(), nil, nil, nil, nil, nil, nil, "", "", "", "", "", "", 0, 10, time.Time{}, time.Time{}, currency.Code{}) | ||
assert.NoError(t, err) | ||
if ordID == nil || len(ordID.Orders) == 0 { | ||
t.Skip(skipInsufficientOrders) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Optional] rm handholdy
resp, err := e.GetOrderInfo(t.Context(), "123", testPairStable, asset.Spot)
is fine
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like you meant to make this comment about the TestGetOrderInfo function that comes way later. I've removed the handholding there, but not here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work 👍
Partial review with feedback on first 28 files.
exchanges/coinbasepro/coinbasepro.go
Outdated
} | ||
|
||
// createOrderConfig populates the OrderConfiguration struct | ||
func createOrderConfig(orderType order.Type, timeInForce order.TimeInForce, stopDirection string, baseAmount, quoteAmount, limitPrice, stopPrice, bucketSize float64, endTime time.Time, postOnly, rfqDisabled bool, bucketNumber int64, bucketDuration time.Duration) (OrderConfiguration, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚧 Can we create a type for this, and then embed that type into the structs for PreviewOrderInfo
and PlaceOrderInfo
?
Also, once we've done that, if there's a big overlap between those 2 types, can we just make an orderInfoReq
that contains a union of the fields?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean a type for the parameters?
I'm a bit concerned about the user experience behind combining those types due to the points where they don't overlap; we'd at least need some communication over which fields are only relevant to one endpoint.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A type for the fields that both of them send to createOrderConfig
, yes, embedded in both.
However you don't need to create a orderInfoReq
type to union if it doesn't feel right to you 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good work, thanks for those changes.
Another partial review, up to 35 files.
var sliToSend []ticker.Price | ||
aliases := e.pairAliases.GetAliases() | ||
for i := range wsTicker { | ||
for j := range wsTicker[i].Tickers { | ||
tickAlias := aliases[wsTicker[i].Tickers[j].ProductID] | ||
newTick := ticker.Price{ | ||
LastUpdated: inc.Timestamp, | ||
AssetType: asset.Spot, | ||
ExchangeName: e.Name, | ||
High: wsTicker[i].Tickers[j].High24H.Float64(), | ||
Low: wsTicker[i].Tickers[j].Low24H.Float64(), | ||
Last: wsTicker[i].Tickers[j].Price.Float64(), | ||
Volume: wsTicker[i].Tickers[j].Volume24H.Float64(), | ||
Bid: wsTicker[i].Tickers[j].BestBid.Float64(), | ||
BidSize: wsTicker[i].Tickers[j].BestBidQuantity.Float64(), | ||
Ask: wsTicker[i].Tickers[j].BestAsk.Float64(), | ||
AskSize: wsTicker[i].Tickers[j].BestAskQuantity.Float64(), | ||
} | ||
var errs error | ||
for k := range tickAlias { | ||
isEnabled, err := e.CurrencyPairs.IsPairEnabled(tickAlias[k], asset.Spot) | ||
if err != nil { | ||
errs = common.AppendError(errs, err) | ||
continue | ||
} | ||
if isEnabled { | ||
newTick.Pair = tickAlias[k] | ||
sliToSend = append(sliToSend, newTick) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚧
Can this go in a function please?
Same for the other sections below.
wsHandleData is already unwieldy enough as it is, but inlining all this processing just makes it huge.
Also implies more targeted unit tests, probably.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've split these out, but have left the tests together for now; the test is already quite thorough, and splitting that out seems like quite a bit more of a hassle.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changes since last review ... reviewed.
Great work.
Just one thing.
@@ -365,29 +365,34 @@ type ErrorResponse struct { | |||
NewOrderFailureReason string `json:"new_order_failure_reason"` | |||
} | |||
|
|||
// SharedOrderConfig contains order configuration information used in both PlaceOrderInfo and PreviewOrderInfo | |||
type SharedOrderConfig struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🦍 I don't think we need to say it's shared. It's just OrderConfig
.
I think you're only thinking about it being shared because of where you started.
type SharedOrderConfig struct { | |
type OrderConfig struct { |
Do not need to action.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's already an OrderConfiguration type, which contains the full, properly-formatted depth of information. Typing that out, "shared" isn't the right way to distinguish this, but the other ideas I have (OrderConfigPreparation, OrderConfigConstructor) seem more verbose and also a bit wrong.
I do think that just "OrderConfig" is a bit close for comfort.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, that would not fly.
-
OrderConfiguration
smells likeOrderType
orOrderConds
Everything in there is about order type conditions. -
SharedOrderConfig
lives in 2 structs named*OrderInfo
. So I think it's actually justOrderInfo
😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The OrderConfiguration naming comes from what the exchange itself calls it. Would like to double-check changing it.
But I'll apply that change from SharedOrderConfig > OrderInfo.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
56 / 57 files reviewed
Only coinbasepro_wrapper.go
remaining.
exchanges/protocol/features.go
Outdated
@@ -3,6 +3,7 @@ package protocol | |||
// Features holds all variables for the exchanges supported features | |||
// for a protocol (e.g REST or Websocket) | |||
type Features struct { | |||
// TickerBatching allows the REST endpoint to fetch the entire ticker list available to the exchange |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🐒
- I don't think this should say REST
- Are we really saying it's always the entire thing. That feels like an exchange implementation thing. I think all we're saying here is "the exchange protocol allows batching". I could be wrong.
- Typo at the end of the sentence, but we can omit it.
Guessing we want:
// TickerBatching allows the REST endpoint to fetch the entire ticker list available to the exchange | |
// TickerBatching allows api to fetch the tickers in batches |
Though ... I mean ... it's not adding any information for me 🤷
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The state of this value doesn't seem to be directly used by our engine anywhere, so we don't functionally enshrine a definition that way.
It's hard to tell exactly why I added that comment, as it was written 20 months ago, but I suspect that I didn't understand exactly what that meant, asked shazbert/thrasher, and they responded with that. But really, even now, I wouldn't consider myself to have an authoritative view on what the definition ought to be. Maybe wait for input from others?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"the exchange protocol allows batching": this is correct. As in, the exchange supports an API which allows us to do a batch request for all available tickers.
Engine's syncer will attempt to use REST ticker batching if possible since it can update all tickers with a single API request as opposed to iterating over a pairs list and fetching a ticker with numerous requests:
https://github.com/thrasher-corp/gocryptotrader/blob/master/engine/sync_manager.go#L572
Which will then check if b.Features.Supports.RESTCapabilities.TickerBatching
== true
This is mainly a feature supported via REST which is why there's an emphasis on it, since websocket will just stream events in realtime or close to it.
As for the comment, both websocket and REST have their own supported Features
and I don't think we need a comment for it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there's a possible confusion; "allows batching" as in "you request a batch of tickers for a pair, and can receive up to 50 ticks for that pair", or as in "you can simultaneously request the latest ticker for every pair on the exchange", or even "you can simultaneously request the latest ticker for up to N pairs on the exchange".
I expect that's what I was initially confused about.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In all cases it's "you can request the latest tickers for every pair on the exchange in a single batched request". Can't think of any exchanges which require pagination for doing a bulk request of tickers off the top of my head
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I seem to have confused it with candles, my bad.
Yeah it should be fine without a comment then.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: Still outstanding to remove the comment.
@@ -370,6 +379,7 @@ const ( | |||
Chase // chase limit order | |||
OptimalLimit | |||
MarketMakerProtection | |||
Bracket // Sets both a profit target and a stop loss simultaneously. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚧 We already have StopLimit
and StopMarket
types, which is a synonym for Bracket order.
So I think that this is about treating the text representation of StopLimit
as BRACKET
for Coinbase, maybe?
Bracket // Sets both a profit target and a stop loss simultaneously. | |
Bracket // Sets both a profit target and a stop loss simultaneously. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are those a synonym for Bracket? I thought that Bracket would be Stop | TakeProfit (and now that such hybrid order types are allowed, I could change it to that).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, I wasn't focusing on the difference between TakeProfit
and Limit
.
I think you're right that we should switch to a hybrid type, but I think that needs more input from others.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is ready to merge. Tested auth and unauth and ensured the websocket order book stays in sync
Nice work Mr. Crank
} | ||
|
||
// ExchangeDepositAddresses is a map of currencies to their deposit addresses | ||
type ExchangeDepositAddresses map[string][]deposit.Address |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A surprising change in the Coinbase PR 😄
exchanges/coinbasepro/coinbasepro.go
Outdated
closedStatuses = []string{"FILLED", "CANCELLED", "EXPIRED", "FAILED"} | ||
openStatus = []string{"OPEN"} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't really think these are needed, but its not worth a change unless other things come up
exchanges/coinbasepro/coinbasepro.go
Outdated
"trade_incentive_metadata": map[string]any{ | ||
"user_incentive_id": userIncentiveID, | ||
"code_val": codeVal, | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no real need for map[string]any when you only use map[string]string. Not an issue, just an observation
exchanges/coinbasepro/coinbasepro.go
Outdated
if productID == "" { | ||
return nil, errProductIDEmpty | ||
} | ||
if !common.StringSliceContains(allowedGranularities, granularity) { | ||
gran, ok := allowedGranularities[granularity] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My gran is always allowed to retrieve historical Klines, thank you
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks - Minor changes
@@ -365,29 +365,34 @@ type ErrorResponse struct { | |||
NewOrderFailureReason string `json:"new_order_failure_reason"` | |||
} | |||
|
|||
// SharedOrderConfig contains order configuration information used in both PlaceOrderInfo and PreviewOrderInfo | |||
type SharedOrderConfig struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, that would not fly.
-
OrderConfiguration
smells likeOrderType
orOrderConds
Everything in there is about order type conditions. -
SharedOrderConfig
lives in 2 structs named*OrderInfo
. So I think it's actually justOrderInfo
😄
err := json.Unmarshal(respRaw, &msgType) | ||
if err != nil { | ||
// tickerDataHandler handles ticker data from the websocket | ||
func (e *Exchange) tickerDataHandler(resp *StandardWebsocketResponse) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚧 Thanks for breaking these out.
Can we normalise naming to match other exchanges, which call these wsProcess*
?
That is: I don't (ever) think "Data" adds anything, and we lack ws
context.
func (e *Exchange) tickerDataHandler(resp *StandardWebsocketResponse) error { | |
func (e *Exchange) wsProcessTicker(resp StandardWebsocketResponse) error { |
Can probably hit them all with a regex:
:%s/\s(.*?)DataHandler\(/wsProcess\1/g
exchanges/protocol/features.go
Outdated
@@ -3,6 +3,7 @@ package protocol | |||
// Features holds all variables for the exchanges supported features | |||
// for a protocol (e.g REST or Websocket) | |||
type Features struct { | |||
// TickerBatching allows the REST endpoint to fetch the entire ticker list available to the exchange |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: Still outstanding to remove the comment.
@@ -370,6 +379,7 @@ const ( | |||
Chase // chase limit order | |||
OptimalLimit | |||
MarketMakerProtection | |||
Bracket // Sets both a profit target and a stop loss simultaneously. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, I wasn't focusing on the difference between TakeProfit
and Limit
.
I think you're right that we should switch to a hybrid type, but I think that needs more input from others.
exchanges/coinbasepro/coinbasepro.go
Outdated
req := map[string]any{ | ||
"client_order_id": ord.ClientOID, | ||
"product_id": ord.ProductID, | ||
"side": ord.Side, | ||
"order_configuration": orderConfig, | ||
"retail_portfolio_id": ord.RetailPortfolioID, | ||
"preview_id": ord.PreviewID, | ||
"attached_order_configuration": ord.AttachedOrderConfiguration, | ||
} | ||
if ord.MarginType != "" { | ||
req["margin_type"] = FormatMarginType(ord.MarginType) | ||
} | ||
if ord.Leverage != 0 && ord.Leverage != 1 { | ||
req["leverage"] = strconv.FormatFloat(ord.Leverage, 'f', -1, 64) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚧 I realised that this appears to be a misplaced adhoc map instead of a PlaceOrderInfo
having json tags and marhsaling itself correctly.
Sorry if I'm missing something, but everything here appears to suggest that the entire thing should be a normal request struct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you mean? I'd need to rewrite the authenticated request function to be able to place bespoke structs rather than map[string]any objects in for the body of a request. And applying this broadly would require rewriting parts of 20 function calls.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, I think you've understood it already, because "I'd need to X" matches exactly what you need to do.
We use structured data everywhere else.
If it's possible, we should do it.
Would you like me to provide a patch?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would be quite helpful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry. I know you'll hate me for this, but now is the right time to do this:
Can we rename coinbasepro
to coinbase
inline with their renaming 3 years ago.
PR Description
Updating Coinbase to the Advanced Trade and Sign In With Coinbase APIs, as well as a few tiny fixes of other parts of the codebase found along the way.
Type of change
Please delete options that are not relevant and add an
x
in[]
as item is complete.How has this been tested
Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration and
also consider improving test coverage whilst working on a certain feature or package.
Checklist
The only tests which seem to be failing (exchanges/kucoin, database/models/postgres, config, and common/file) are parts that I haven't substantively changed, and which seem to be failing on master too.