Skip to content

Conversation

dboyliao
Copy link
Collaborator

@dboyliao dboyliao commented Feb 12, 2025

Implement following exchange interfaces:

  • ExchangeMinimal
  • ExchangeTradeService
  • ExchangeOrderQueryService
  • ExchangeMarketDataService is WIP (Impl. for NewStream will be in another PR)

TODO: unit tests

@dboyliao dboyliao requested review from c9s and kbearXD February 12, 2025 09:56
@CLAassistant
Copy link

CLAassistant commented Feb 12, 2025

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
1 out of 2 committers have signed the CLA.

✅ dboyliao
❌ dboy


dboy seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

@dboyliao dboyliao force-pushed the dboy/coinbase branch 2 times, most recently from c353e97 to 43a6476 Compare February 13, 2025 01:25
// ClientOID must be uuid
ClientOID string `json:"client_oid" db:"client_oid"`
Stop string `json:"stop" db:"stop"`
StopPrice json.Number `json:"stop_price" db:"stop_price"`
Copy link
Owner

Choose a reason for hiding this comment

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

can be fixedpoint.Value

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

import "context"

func (client *RestAPIClient) GetOrderTrades(ctx context.Context, orderID string, limit int, before *string) (TradeSnapshot, error) {
req := GetOrderTradesRequest{
Copy link
Owner

Choose a reason for hiding this comment

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

ditto

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

@echo "Generate symbols.go" && go run ./generate_symbol_map.go

go-generate:
@echo "Running \`go generate\`" && go generate ./...
Copy link
Owner

Choose a reason for hiding this comment

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

you don't need the makefile actually? you can embed them in the go:generate comment

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I just want a way to document how I generate the symbols.go

QuoteQuantity: cbTrade.Size.Mul(cbTrade.Price),
Symbol: cbTrade.ProductID,
Side: cbTrade.Side.GlobalSideType(),
IsBuyer: cbTrade.Liquidity == "T",
Copy link
Collaborator

Choose a reason for hiding this comment

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

What do T and M represent?

Copy link
Collaborator Author

@dboyliao dboyliao Feb 14, 2025

Choose a reason for hiding this comment

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

"T"aker and "M"aker

Copy link
Collaborator

Choose a reason for hiding this comment

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

Perhaps you can use cb Trade.Liquidity == api.Liquidity Maker

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good idea. Will do.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

cur := strings.ToUpper(b.Currency)
balances[cur] = types.Balance{
Currency: cur,
Available: b.Available,
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think you missed the Lock field?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It's not well documented in the CoinBase API which is the right value for Locked:
https://docs.cdp.coinbase.com/exchange/reference/exchangerestapi_getaccounts

After searching around on the web, I think the reasonable choice would be balance - available from the CoinBase side.
Let's use balance - available for Locked for now.
I'll update the NetAsset as well.

QuoteQuantity: cbTrade.Size.Mul(cbTrade.Price),
Symbol: cbTrade.ProductID,
Side: cbTrade.Side.GlobalSideType(),
IsBuyer: cbTrade.Liquidity == "T",
Copy link
Collaborator

Choose a reason for hiding this comment

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

Perhaps you can use cb Trade.Liquidity == api.Liquidity Maker

Comment on lines 124 to 127
done := false
if len(cbOrders) < 1000 || len(cbOrders) == 0 {
done = true
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

It can be simplify to

if len(cbOrders) < paginationLimit {
    return cbOrders,nil)
}

done := false

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

Comment on lines 223 to 225
if len(cbTrades) < paginationLimit || len(cbTrades) == 0 {
done = true
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is it removable?

Copy link
Collaborator Author

@dboyliao dboyliao Feb 14, 2025

Choose a reason for hiding this comment

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

In order to get all trades from Coinbase Exchange API, I believe applying pagination is a must.
See:

}
done := false
for {
if done {
Copy link
Owner

Choose a reason for hiding this comment

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

you need to handle the ctx.Done() in the loop.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.


done := false
for {
if done {
Copy link
Owner

Choose a reason for hiding this comment

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

you need to handle the ctx.Done() in the loop.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

return &ticker
}

func toGlobalBalance(cur string, cbBalance *api.Balance) *types.Balance {
Copy link
Owner

Choose a reason for hiding this comment

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

for this case, you can just return the value struct

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

balances := make(types.BalanceMap)
for _, cbBalance := range accounts {
cur := strings.ToUpper(cbBalance.Currency)
balances[cur] = *toGlobalBalance(cur, &cbBalance)
Copy link
Owner

Choose a reason for hiding this comment

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

using dereferencing the return pointer could be dangerous, so for this function, you can just return value (you always have a value to return)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

}
return &types.Order{
Exchange: types.ExchangeCoinBase,
UUID: res.ID,
Copy link
Owner

Choose a reason for hiding this comment

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

you need to assign SubmitOrder into the SubmitOrder field,

and you might need to query the order details, take a look at bybit or bitget's SubmitOrder() implementation

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Got it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

}
orders := make([]types.Order, 0, len(cbOrders))
for _, cbOrder := range cbOrders {
orders = append(orders, *toGlobalOrder(&cbOrder))
Copy link
Owner

@c9s c9s Feb 24, 2025

Choose a reason for hiding this comment

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

ditto, avoid de-referencing the value directly from the function call since it's a plain data object.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

return orders, nil
}

func (e *Exchange) queryOrdersByPagination(ctx context.Context, symbol string, status []string) ([]api.Order, error) {
Copy link
Owner

Choose a reason for hiding this comment

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

you might need to apply the rate limiter here, or from the request itself. requestgen supports rate limiter's Wait() method call.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

}

func (e *Exchange) CancelOrders(ctx context.Context, orders ...types.Order) error {
failedOrderIDs := make([]string, 0)
Copy link
Owner

Choose a reason for hiding this comment

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

this is minor, simple form is:

var failedOrderIDs []string

they are equivalent.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

log.WithError(err).Errorf("failed to cancel order: %v", order.UUID)
failedOrderIDs = append(failedOrderIDs, order.UUID)
}
log.Infof("order %v has been cancelled", res)
Copy link
Owner

Choose a reason for hiding this comment

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

you need an "else" for this Infof log?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.


// ExchangeMarketDataService
func (e *Exchange) NewStream() types.Stream {
return nil
Copy link
Owner

Choose a reason for hiding this comment

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

add a TODO mark comment here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

}
marketMap := make(types.MarketMap)
for _, m := range markets {
marketMap[toGlobalSymbol(m.ID)] = *toGlobalMarket(&m)
Copy link
Owner

Choose a reason for hiding this comment

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

ditto

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

}
// default limit is 300, which is the maximum limit of the Coinbase Exchange API
if options.Limit == 0 {
options.Limit = 300
Copy link
Owner

Choose a reason for hiding this comment

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

pull these numbers out to constants?

Copy link
Collaborator Author

@dboyliao dboyliao Feb 25, 2025

Choose a reason for hiding this comment

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

Done.
It has been renamed as DefaultKLineLimit.

}
if options.Limit > 300 {
log.Warnf("limit %d is greater than the maximum limit 300, set to 300", options.Limit)
options.Limit = 300
Copy link
Owner

Choose a reason for hiding this comment

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

ditto

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

@dboyliao dboyliao marked this pull request as ready for review February 24, 2025 09:43
Comment on lines 221 to 223
if done {
break
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

remove?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

My bad. Silly rebase error.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

Comment on lines 377 to 379
if done {
break
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

remove?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

ditto.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

Copy link
Collaborator

@bailantaotao bailantaotao left a comment

Choose a reason for hiding this comment

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

To implement rate limits on each API, refer to the code snippet found in the following link: Cancel Order Request in Bybit API.

@dboyliao dboyliao removed the request for review from kbearXD February 25, 2025 06:00
@dboyliao dboyliao merged commit e0d7159 into main Feb 25, 2025
2 of 3 checks passed
@dboyliao dboyliao deleted the dboy/coinbase branch February 25, 2025 06:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants