mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 23:16:45 +00:00
rate limit: make context aware (#731)
* rate limits: Make context aware * binance: rate limit allow for cancellation of reservation when deadline is exceeded * request: add context.done() before initiating any bulk work. * binance: update error return for rate limiting * request: updated dealine check to remove after time.Now procedure as this will obfuscate a deadline which will be limited by the context check on every attempt, so no need to sleep with delay.
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
package binance
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
@@ -103,7 +105,7 @@ type RateLimit struct {
|
||||
}
|
||||
|
||||
// Limit executes rate limiting functionality for Binance
|
||||
func (r *RateLimit) Limit(f request.EndpointLimit) error {
|
||||
func (r *RateLimit) Limit(ctx context.Context, f request.EndpointLimit) error {
|
||||
var limiter *rate.Limiter
|
||||
var tokens int
|
||||
switch f {
|
||||
@@ -214,11 +216,25 @@ func (r *RateLimit) Limit(f request.EndpointLimit) error {
|
||||
}
|
||||
|
||||
var finalDelay time.Duration
|
||||
var reserves = make([]*rate.Reservation, tokens)
|
||||
for i := 0; i < tokens; i++ {
|
||||
// Consume tokens 1 at a time as this avoids needing burst capacity in the limiter,
|
||||
// which would otherwise allow the rate limit to be exceeded over short periods
|
||||
finalDelay = limiter.Reserve().Delay()
|
||||
reserves[i] = limiter.Reserve()
|
||||
finalDelay = reserves[i].Delay()
|
||||
}
|
||||
|
||||
if dl, ok := ctx.Deadline(); ok && dl.Before(time.Now().Add(finalDelay)) {
|
||||
// Cancel all potential reservations to free up rate limiter if deadline
|
||||
// is exceeded.
|
||||
for x := range reserves {
|
||||
reserves[x].Cancel()
|
||||
}
|
||||
return fmt.Errorf("rate limit delay of %s will exceed deadline: %w",
|
||||
finalDelay,
|
||||
context.DeadlineExceeded)
|
||||
}
|
||||
|
||||
time.Sleep(finalDelay)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package binance
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
)
|
||||
@@ -12,6 +15,7 @@ func TestRateLimit_Limit(t *testing.T) {
|
||||
testTable := map[string]struct {
|
||||
Expected request.EndpointLimit
|
||||
Limit request.EndpointLimit
|
||||
Deadline time.Time
|
||||
}{
|
||||
"All Orderbooks Ticker": {Expected: spotOrderbookTickerAllRate, Limit: bestPriceLimit("")},
|
||||
"Orderbook Ticker": {Expected: spotDefaultRate, Limit: bestPriceLimit(symbol)},
|
||||
@@ -24,6 +28,7 @@ func TestRateLimit_Limit(t *testing.T) {
|
||||
"Orderbook Depth 500": {Expected: spotOrderbookDepth500Rate, Limit: orderbookLimit(500)},
|
||||
"Orderbook Depth 1000": {Expected: spotOrderbookDepth1000Rate, Limit: orderbookLimit(1000)},
|
||||
"Orderbook Depth 5000": {Expected: spotOrderbookDepth5000Rate, Limit: orderbookLimit(5000)},
|
||||
"Exceeds deadline": {Expected: spotOrderbookDepth5000Rate, Limit: orderbookLimit(5000), Deadline: time.Now().Add(time.Nanosecond)},
|
||||
}
|
||||
for name, tt := range testTable {
|
||||
tt := tt
|
||||
@@ -35,8 +40,15 @@ func TestRateLimit_Limit(t *testing.T) {
|
||||
t.Fatalf("incorrect limit applied.\nexp: %v\ngot: %v", exp, got)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
if !tt.Deadline.IsZero() {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithDeadline(ctx, tt.Deadline)
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
l := SetRateLimit()
|
||||
if err := l.Limit(tt.Limit); err != nil {
|
||||
if err := l.Limit(ctx, tt.Limit); err != nil && !errors.Is(err, context.DeadlineExceeded) {
|
||||
t.Fatalf("error applying rate limit: %v", err)
|
||||
}
|
||||
})
|
||||
@@ -56,7 +68,7 @@ func TestRateLimit_LimitStatic(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
l := SetRateLimit()
|
||||
if err := l.Limit(tt); err != nil {
|
||||
if err := l.Limit(context.Background(), tt); err != nil {
|
||||
t.Fatalf("error applying rate limit: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package bitfinex
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
@@ -255,156 +256,155 @@ type RateLimit struct {
|
||||
}
|
||||
|
||||
// Limit limits outbound requests
|
||||
func (r *RateLimit) Limit(f request.EndpointLimit) error {
|
||||
func (r *RateLimit) Limit(ctx context.Context, f request.EndpointLimit) error {
|
||||
switch f {
|
||||
case platformStatus:
|
||||
time.Sleep(r.PlatformStatus.Reserve().Delay())
|
||||
return r.PlatformStatus.Wait(ctx)
|
||||
case tickerBatch:
|
||||
time.Sleep(r.TickerBatch.Reserve().Delay())
|
||||
return r.TickerBatch.Wait(ctx)
|
||||
case tickerFunction:
|
||||
time.Sleep(r.Ticker.Reserve().Delay())
|
||||
return r.Ticker.Wait(ctx)
|
||||
case tradeRateLimit:
|
||||
time.Sleep(r.Trade.Reserve().Delay())
|
||||
return r.Trade.Wait(ctx)
|
||||
case orderbookFunction:
|
||||
time.Sleep(r.Orderbook.Reserve().Delay())
|
||||
return r.Orderbook.Wait(ctx)
|
||||
case stats:
|
||||
time.Sleep(r.Stats.Reserve().Delay())
|
||||
return r.Stats.Wait(ctx)
|
||||
case candle:
|
||||
time.Sleep(r.Candle.Reserve().Delay())
|
||||
return r.Candle.Wait(ctx)
|
||||
case configs:
|
||||
time.Sleep(r.Configs.Reserve().Delay())
|
||||
return r.Configs.Wait(ctx)
|
||||
case status:
|
||||
time.Sleep(r.Stats.Reserve().Delay())
|
||||
return r.Stats.Wait(ctx)
|
||||
case liquid:
|
||||
time.Sleep(r.Liquid.Reserve().Delay())
|
||||
return r.Liquid.Wait(ctx)
|
||||
case leaderBoard:
|
||||
time.Sleep(r.LeaderBoard.Reserve().Delay())
|
||||
return r.LeaderBoard.Wait(ctx)
|
||||
case marketAveragePrice:
|
||||
time.Sleep(r.MarketAveragePrice.Reserve().Delay())
|
||||
return r.MarketAveragePrice.Wait(ctx)
|
||||
case fx:
|
||||
time.Sleep(r.Fx.Reserve().Delay())
|
||||
return r.Fx.Wait(ctx)
|
||||
case accountWalletBalance:
|
||||
time.Sleep(r.AccountWalletBalance.Reserve().Delay())
|
||||
return r.AccountWalletBalance.Wait(ctx)
|
||||
case accountWalletHistory:
|
||||
time.Sleep(r.AccountWalletHistory.Reserve().Delay())
|
||||
return r.AccountWalletHistory.Wait(ctx)
|
||||
case retrieveOrder:
|
||||
time.Sleep(r.RetrieveOrder.Reserve().Delay())
|
||||
return r.RetrieveOrder.Wait(ctx)
|
||||
case submitOrder:
|
||||
time.Sleep(r.SubmitOrder.Reserve().Delay())
|
||||
return r.SubmitOrder.Wait(ctx)
|
||||
case updateOrder:
|
||||
time.Sleep(r.UpdateOrder.Reserve().Delay())
|
||||
return r.UpdateOrder.Wait(ctx)
|
||||
case cancelOrder:
|
||||
time.Sleep(r.CancelOrder.Reserve().Delay())
|
||||
return r.CancelOrder.Wait(ctx)
|
||||
case orderBatch:
|
||||
time.Sleep(r.OrderBatch.Reserve().Delay())
|
||||
return r.OrderBatch.Wait(ctx)
|
||||
case cancelBatch:
|
||||
time.Sleep(r.CancelBatch.Reserve().Delay())
|
||||
return r.CancelBatch.Wait(ctx)
|
||||
case orderHistory:
|
||||
time.Sleep(r.OrderHistory.Reserve().Delay())
|
||||
return r.OrderHistory.Wait(ctx)
|
||||
case getOrderTrades:
|
||||
time.Sleep(r.GetOrderTrades.Reserve().Delay())
|
||||
return r.GetOrderTrades.Wait(ctx)
|
||||
case getTrades:
|
||||
time.Sleep(r.GetTrades.Reserve().Delay())
|
||||
return r.GetTrades.Wait(ctx)
|
||||
case getLedgers:
|
||||
time.Sleep(r.GetLedgers.Reserve().Delay())
|
||||
return r.GetLedgers.Wait(ctx)
|
||||
case getAccountMarginInfo:
|
||||
time.Sleep(r.GetAccountMarginInfo.Reserve().Delay())
|
||||
return r.GetAccountMarginInfo.Wait(ctx)
|
||||
case getActivePositions:
|
||||
time.Sleep(r.GetActivePositions.Reserve().Delay())
|
||||
return r.GetActivePositions.Wait(ctx)
|
||||
case claimPosition:
|
||||
time.Sleep(r.ClaimPosition.Reserve().Delay())
|
||||
return r.ClaimPosition.Wait(ctx)
|
||||
case getPositionHistory:
|
||||
time.Sleep(r.GetPositionHistory.Reserve().Delay())
|
||||
return r.GetPositionHistory.Wait(ctx)
|
||||
case getPositionAudit:
|
||||
time.Sleep(r.GetPositionAudit.Reserve().Delay())
|
||||
return r.GetPositionAudit.Wait(ctx)
|
||||
case updateCollateralOnPosition:
|
||||
time.Sleep(r.UpdateCollateralOnPosition.Reserve().Delay())
|
||||
return r.UpdateCollateralOnPosition.Wait(ctx)
|
||||
case getActiveFundingOffers:
|
||||
time.Sleep(r.GetActiveFundingOffers.Reserve().Delay())
|
||||
return r.GetActiveFundingOffers.Wait(ctx)
|
||||
case submitFundingOffer:
|
||||
time.Sleep(r.SubmitFundingOffer.Reserve().Delay())
|
||||
return r.SubmitFundingOffer.Wait(ctx)
|
||||
case cancelFundingOffer:
|
||||
time.Sleep(r.CancelFundingOffer.Reserve().Delay())
|
||||
return r.CancelFundingOffer.Wait(ctx)
|
||||
case cancelAllFundingOffer:
|
||||
time.Sleep(r.CancelAllFundingOffer.Reserve().Delay())
|
||||
return r.CancelAllFundingOffer.Wait(ctx)
|
||||
case closeFunding:
|
||||
time.Sleep(r.CloseFunding.Reserve().Delay())
|
||||
return r.CloseFunding.Wait(ctx)
|
||||
case fundingAutoRenew:
|
||||
time.Sleep(r.FundingAutoRenew.Reserve().Delay())
|
||||
return r.FundingAutoRenew.Wait(ctx)
|
||||
case keepFunding:
|
||||
time.Sleep(r.KeepFunding.Reserve().Delay())
|
||||
return r.KeepFunding.Wait(ctx)
|
||||
case getOffersHistory:
|
||||
time.Sleep(r.GetOffersHistory.Reserve().Delay())
|
||||
return r.GetOffersHistory.Wait(ctx)
|
||||
case getFundingLoans:
|
||||
time.Sleep(r.GetFundingLoans.Reserve().Delay())
|
||||
return r.GetFundingLoans.Wait(ctx)
|
||||
case getFundingLoanHistory:
|
||||
time.Sleep(r.GetFundingLoanHistory.Reserve().Delay())
|
||||
return r.GetFundingLoanHistory.Wait(ctx)
|
||||
case getFundingCredits:
|
||||
time.Sleep(r.GetFundingCredits.Reserve().Delay())
|
||||
return r.GetFundingCredits.Wait(ctx)
|
||||
case getFundingCreditsHistory:
|
||||
time.Sleep(r.GetFundingCreditsHistory.Reserve().Delay())
|
||||
return r.GetFundingCreditsHistory.Wait(ctx)
|
||||
case getFundingTrades:
|
||||
time.Sleep(r.GetFundingTrades.Reserve().Delay())
|
||||
return r.GetFundingTrades.Wait(ctx)
|
||||
case getFundingInfo:
|
||||
time.Sleep(r.GetFundingInfo.Reserve().Delay())
|
||||
return r.GetFundingInfo.Wait(ctx)
|
||||
case getUserInfo:
|
||||
time.Sleep(r.GetUserInfo.Reserve().Delay())
|
||||
return r.GetUserInfo.Wait(ctx)
|
||||
case transferBetweenWallets:
|
||||
time.Sleep(r.TransferBetweenWallets.Reserve().Delay())
|
||||
return r.TransferBetweenWallets.Wait(ctx)
|
||||
case getDepositAddress:
|
||||
time.Sleep(r.GetDepositAddress.Reserve().Delay())
|
||||
return r.GetDepositAddress.Wait(ctx)
|
||||
case withdrawal:
|
||||
time.Sleep(r.Withdrawal.Reserve().Delay())
|
||||
return r.Withdrawal.Wait(ctx)
|
||||
case getMovements:
|
||||
time.Sleep(r.GetMovements.Reserve().Delay())
|
||||
return r.GetMovements.Wait(ctx)
|
||||
case getAlertList:
|
||||
time.Sleep(r.GetAlertList.Reserve().Delay())
|
||||
return r.GetAlertList.Wait(ctx)
|
||||
case setPriceAlert:
|
||||
time.Sleep(r.SetPriceAlert.Reserve().Delay())
|
||||
return r.SetPriceAlert.Wait(ctx)
|
||||
case deletePriceAlert:
|
||||
time.Sleep(r.DeletePriceAlert.Reserve().Delay())
|
||||
return r.DeletePriceAlert.Wait(ctx)
|
||||
case getBalanceForOrdersOffers:
|
||||
time.Sleep(r.GetBalanceForOrdersOffers.Reserve().Delay())
|
||||
return r.GetBalanceForOrdersOffers.Wait(ctx)
|
||||
case userSettingsWrite:
|
||||
time.Sleep(r.UserSettingsWrite.Reserve().Delay())
|
||||
return r.UserSettingsWrite.Wait(ctx)
|
||||
case userSettingsRead:
|
||||
time.Sleep(r.UserSettingsRead.Reserve().Delay())
|
||||
return r.UserSettingsRead.Wait(ctx)
|
||||
case userSettingsDelete:
|
||||
time.Sleep(r.UserSettingsDelete.Reserve().Delay())
|
||||
return r.UserSettingsDelete.Wait(ctx)
|
||||
|
||||
// Bitfinex V1 API
|
||||
case getAccountFees:
|
||||
time.Sleep(r.GetAccountFees.Reserve().Delay())
|
||||
return r.GetAccountFees.Wait(ctx)
|
||||
case getWithdrawalFees:
|
||||
time.Sleep(r.GetWithdrawalFees.Reserve().Delay())
|
||||
return r.GetWithdrawalFees.Wait(ctx)
|
||||
case getAccountSummary:
|
||||
time.Sleep(r.GetAccountSummary.Reserve().Delay())
|
||||
return r.GetAccountSummary.Wait(ctx)
|
||||
case newDepositAddress:
|
||||
time.Sleep(r.NewDepositAddress.Reserve().Delay())
|
||||
return r.NewDepositAddress.Wait(ctx)
|
||||
case getKeyPermissions:
|
||||
time.Sleep(r.GetKeyPermissions.Reserve().Delay())
|
||||
return r.GetKeyPermissions.Wait(ctx)
|
||||
case getMarginInfo:
|
||||
time.Sleep(r.GetMarginInfo.Reserve().Delay())
|
||||
return r.GetMarginInfo.Wait(ctx)
|
||||
case getAccountBalance:
|
||||
time.Sleep(r.GetAccountBalance.Reserve().Delay())
|
||||
return r.GetAccountBalance.Wait(ctx)
|
||||
case walletTransfer:
|
||||
time.Sleep(r.WalletTransfer.Reserve().Delay())
|
||||
return r.WalletTransfer.Wait(ctx)
|
||||
case withdrawV1:
|
||||
time.Sleep(r.WithdrawV1.Reserve().Delay())
|
||||
return r.WithdrawV1.Wait(ctx)
|
||||
case orderV1:
|
||||
time.Sleep(r.OrderV1.Reserve().Delay())
|
||||
return r.OrderV1.Wait(ctx)
|
||||
case orderMulti:
|
||||
time.Sleep(r.OrderMulti.Reserve().Delay())
|
||||
return r.OrderMulti.Wait(ctx)
|
||||
case statsV1:
|
||||
time.Sleep(r.Stats.Reserve().Delay())
|
||||
return r.Stats.Wait(ctx)
|
||||
case fundingbook:
|
||||
time.Sleep(r.Fundingbook.Reserve().Delay())
|
||||
return r.Fundingbook.Wait(ctx)
|
||||
case lends:
|
||||
time.Sleep(r.Lends.Reserve().Delay())
|
||||
return r.Lends.Wait(ctx)
|
||||
default:
|
||||
return errors.New("endpoint rate limit functionality not found")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetRateLimit returns the rate limit for the exchange
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package bitflyer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
@@ -29,24 +30,29 @@ type RateLimit struct {
|
||||
}
|
||||
|
||||
// Limit limits outbound requests
|
||||
func (r *RateLimit) Limit(f request.EndpointLimit) error {
|
||||
func (r *RateLimit) Limit(ctx context.Context, f request.EndpointLimit) error {
|
||||
switch f {
|
||||
case request.Auth:
|
||||
time.Sleep(r.Auth.Reserve().Delay())
|
||||
return r.Auth.Wait(ctx)
|
||||
case orders:
|
||||
res := r.Auth.Reserve()
|
||||
time.Sleep(r.Order.Reserve().Delay())
|
||||
time.Sleep(res.Delay())
|
||||
err := r.Auth.Wait(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return r.Order.Wait(ctx)
|
||||
case lowVolume:
|
||||
authShell := r.Auth.Reserve()
|
||||
orderShell := r.Order.Reserve()
|
||||
time.Sleep(r.LowVolume.Reserve().Delay())
|
||||
time.Sleep(orderShell.Delay())
|
||||
time.Sleep(authShell.Delay())
|
||||
err := r.LowVolume.Wait(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = r.Order.Wait(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return r.Auth.Wait(ctx)
|
||||
default:
|
||||
time.Sleep(r.UnAuth.Reserve().Delay())
|
||||
return r.UnAuth.Wait(ctx)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetRateLimit returns the rate limit for the exchange
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package bithumb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
@@ -21,13 +22,11 @@ type RateLimit struct {
|
||||
}
|
||||
|
||||
// Limit limits requests
|
||||
func (r *RateLimit) Limit(f request.EndpointLimit) error {
|
||||
func (r *RateLimit) Limit(ctx context.Context, f request.EndpointLimit) error {
|
||||
if f == request.Auth {
|
||||
time.Sleep(r.Auth.Reserve().Delay())
|
||||
return nil
|
||||
return r.Auth.Wait(ctx)
|
||||
}
|
||||
time.Sleep(r.UnAuth.Reserve().Delay())
|
||||
return nil
|
||||
return r.UnAuth.Wait(ctx)
|
||||
}
|
||||
|
||||
// SetRateLimit returns the rate limit for the exchange
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package bitmex
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
@@ -21,13 +22,11 @@ type RateLimit struct {
|
||||
}
|
||||
|
||||
// Limit limits outbound calls
|
||||
func (r *RateLimit) Limit(f request.EndpointLimit) error {
|
||||
func (r *RateLimit) Limit(ctx context.Context, f request.EndpointLimit) error {
|
||||
if f == request.Auth {
|
||||
time.Sleep(r.Auth.Reserve().Delay())
|
||||
return nil
|
||||
return r.Auth.Wait(ctx)
|
||||
}
|
||||
time.Sleep(r.UnAuth.Reserve().Delay())
|
||||
return nil
|
||||
return r.UnAuth.Wait(ctx)
|
||||
}
|
||||
|
||||
// SetRateLimit returns the rate limit for the exchange
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package btcmarkets
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
@@ -35,22 +36,21 @@ type RateLimit struct {
|
||||
}
|
||||
|
||||
// Limit limits the outbound requests
|
||||
func (r *RateLimit) Limit(f request.EndpointLimit) error {
|
||||
func (r *RateLimit) Limit(ctx context.Context, f request.EndpointLimit) error {
|
||||
switch f {
|
||||
case request.Auth:
|
||||
time.Sleep(r.Auth.Reserve().Delay())
|
||||
return r.Auth.Wait(ctx)
|
||||
case orderFunc:
|
||||
time.Sleep(r.OrderPlacement.Reserve().Delay())
|
||||
return r.OrderPlacement.Wait(ctx)
|
||||
case batchFunc:
|
||||
time.Sleep(r.BatchOrders.Reserve().Delay())
|
||||
return r.BatchOrders.Wait(ctx)
|
||||
case withdrawFunc:
|
||||
time.Sleep(r.WithdrawRequest.Reserve().Delay())
|
||||
return r.WithdrawRequest.Wait(ctx)
|
||||
case newReportFunc:
|
||||
time.Sleep(r.CreateNewReport.Reserve().Delay())
|
||||
return r.CreateNewReport.Wait(ctx)
|
||||
default:
|
||||
time.Sleep(r.UnAuth.Reserve().Delay())
|
||||
return r.UnAuth.Wait(ctx)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetRateLimit returns the rate limit for the exchange
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package btse
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
@@ -23,14 +24,13 @@ type RateLimit struct {
|
||||
}
|
||||
|
||||
// Limit executes rate limiting functionality for exchange
|
||||
func (r *RateLimit) Limit(f request.EndpointLimit) error {
|
||||
func (r *RateLimit) Limit(ctx context.Context, f request.EndpointLimit) error {
|
||||
switch f {
|
||||
case orderFunc:
|
||||
time.Sleep(r.Orders.Reserve().Delay())
|
||||
return r.Orders.Wait(ctx)
|
||||
default:
|
||||
time.Sleep(r.Query.Reserve().Delay())
|
||||
return r.Query.Wait(ctx)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetRateLimit returns the rate limit for the exchange
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package coinbasepro
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
@@ -21,13 +22,11 @@ type RateLimit struct {
|
||||
}
|
||||
|
||||
// Limit limits outbound calls
|
||||
func (r *RateLimit) Limit(f request.EndpointLimit) error {
|
||||
func (r *RateLimit) Limit(ctx context.Context, f request.EndpointLimit) error {
|
||||
if f == request.Auth {
|
||||
time.Sleep(r.Auth.Reserve().Delay())
|
||||
return nil
|
||||
return r.Auth.Wait(ctx)
|
||||
}
|
||||
time.Sleep(r.UnAuth.Reserve().Delay())
|
||||
return nil
|
||||
return r.UnAuth.Wait(ctx)
|
||||
}
|
||||
|
||||
// SetRateLimit returns the rate limit for the exchange
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package coinbene
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
@@ -129,82 +130,81 @@ type RateLimit struct {
|
||||
}
|
||||
|
||||
// Limit limits outbound requests
|
||||
func (r *RateLimit) Limit(f request.EndpointLimit) error {
|
||||
func (r *RateLimit) Limit(ctx context.Context, f request.EndpointLimit) error {
|
||||
switch f {
|
||||
case contractOrderbook:
|
||||
time.Sleep(r.ContractOrderbook.Reserve().Delay())
|
||||
return r.ContractOrderbook.Wait(ctx)
|
||||
case contractTickers:
|
||||
time.Sleep(r.ContractTickers.Reserve().Delay())
|
||||
return r.ContractTickers.Wait(ctx)
|
||||
case contractKline:
|
||||
time.Sleep(r.ContractKline.Reserve().Delay())
|
||||
return r.ContractKline.Wait(ctx)
|
||||
case contractTrades:
|
||||
time.Sleep(r.ContractTrades.Reserve().Delay())
|
||||
return r.ContractTrades.Wait(ctx)
|
||||
case contractInstruments:
|
||||
time.Sleep(r.ContractInstruments.Reserve().Delay())
|
||||
return r.ContractInstruments.Wait(ctx)
|
||||
case contractAccountInfo:
|
||||
time.Sleep(r.ContractAccountInfo.Reserve().Delay())
|
||||
return r.ContractAccountInfo.Wait(ctx)
|
||||
case contractPositionInfo:
|
||||
time.Sleep(r.ContractPositionInfo.Reserve().Delay())
|
||||
return r.ContractPositionInfo.Wait(ctx)
|
||||
case contractPlaceOrder:
|
||||
time.Sleep(r.ContractPlaceOrder.Reserve().Delay())
|
||||
return r.ContractPlaceOrder.Wait(ctx)
|
||||
case contractCancelOrder:
|
||||
time.Sleep(r.ContractCancelOrder.Reserve().Delay())
|
||||
return r.ContractCancelOrder.Wait(ctx)
|
||||
case contractGetOpenOrders:
|
||||
time.Sleep(r.ContractGetOpenOrders.Reserve().Delay())
|
||||
return r.ContractGetOpenOrders.Wait(ctx)
|
||||
case contractOpenOrdersByPage:
|
||||
time.Sleep(r.ContractOpenOrdersByPage.Reserve().Delay())
|
||||
return r.ContractOpenOrdersByPage.Wait(ctx)
|
||||
case contractGetOrderInfo:
|
||||
time.Sleep(r.ContractGetOrderInfo.Reserve().Delay())
|
||||
return r.ContractGetOrderInfo.Wait(ctx)
|
||||
case contractGetClosedOrders:
|
||||
time.Sleep(r.ContractGetClosedOrders.Reserve().Delay())
|
||||
return r.ContractGetClosedOrders.Wait(ctx)
|
||||
case contractGetClosedOrdersbyPage:
|
||||
time.Sleep(r.ContractGetClosedOrdersbyPage.Reserve().Delay())
|
||||
return r.ContractGetClosedOrdersbyPage.Wait(ctx)
|
||||
case contractCancelMultipleOrders:
|
||||
time.Sleep(r.ContractCancelMultipleOrders.Reserve().Delay())
|
||||
return r.ContractCancelMultipleOrders.Wait(ctx)
|
||||
case contractGetOrderFills:
|
||||
time.Sleep(r.ContractGetOrderFills.Reserve().Delay())
|
||||
return r.ContractGetOrderFills.Wait(ctx)
|
||||
case contractGetFundingRates:
|
||||
time.Sleep(r.ContractGetFundingRates.Reserve().Delay())
|
||||
return r.ContractGetFundingRates.Wait(ctx)
|
||||
case spotPairs:
|
||||
time.Sleep(r.SpotPairs.Reserve().Delay())
|
||||
return r.SpotPairs.Wait(ctx)
|
||||
case spotPairInfo:
|
||||
time.Sleep(r.SpotPairInfo.Reserve().Delay())
|
||||
return r.SpotPairInfo.Wait(ctx)
|
||||
case spotOrderbook:
|
||||
time.Sleep(r.SpotOrderbook.Reserve().Delay())
|
||||
return r.SpotOrderbook.Wait(ctx)
|
||||
case spotTickerList:
|
||||
time.Sleep(r.SpotTickerList.Reserve().Delay())
|
||||
return r.SpotTickerList.Wait(ctx)
|
||||
case spotSpecificTicker:
|
||||
time.Sleep(r.SpotSpecificTicker.Reserve().Delay())
|
||||
return r.SpotSpecificTicker.Wait(ctx)
|
||||
case spotMarketTrades:
|
||||
time.Sleep(r.SpotMarketTrades.Reserve().Delay())
|
||||
return r.SpotMarketTrades.Wait(ctx)
|
||||
// case spotKline: // Not implemented yet
|
||||
// time.Sleep(r.SpotKline.Reserve().Delay())
|
||||
// return r.SpotKline.Wait(ctx)
|
||||
// case spotExchangeRate:
|
||||
// time.Sleep(r.SpotExchangeRate.Reserve().Delay())
|
||||
// return r.SpotExchangeRate.Wait(ctx)
|
||||
case spotAccountInfo:
|
||||
time.Sleep(r.SpotAccountInfo.Reserve().Delay())
|
||||
return r.SpotAccountInfo.Wait(ctx)
|
||||
case spotAccountAssetInfo:
|
||||
time.Sleep(r.SpotAccountAssetInfo.Reserve().Delay())
|
||||
return r.SpotAccountAssetInfo.Wait(ctx)
|
||||
case spotPlaceOrder:
|
||||
time.Sleep(r.SpotPlaceOrder.Reserve().Delay())
|
||||
return r.SpotPlaceOrder.Wait(ctx)
|
||||
case spotBatchOrder:
|
||||
time.Sleep(r.SpotBatchOrder.Reserve().Delay())
|
||||
return r.SpotBatchOrder.Wait(ctx)
|
||||
case spotQueryOpenOrders:
|
||||
time.Sleep(r.SpotQueryOpenOrders.Reserve().Delay())
|
||||
return r.SpotQueryOpenOrders.Wait(ctx)
|
||||
case spotQueryClosedOrders:
|
||||
time.Sleep(r.SpotQueryClosedOrders.Reserve().Delay())
|
||||
return r.SpotQueryClosedOrders.Wait(ctx)
|
||||
case spotQuerySpecficOrder:
|
||||
time.Sleep(r.SpotQuerySpecficOrder.Reserve().Delay())
|
||||
return r.SpotQuerySpecficOrder.Wait(ctx)
|
||||
case spotQueryTradeFills:
|
||||
time.Sleep(r.SpotQueryTradeFills.Reserve().Delay())
|
||||
return r.SpotQueryTradeFills.Wait(ctx)
|
||||
case spotCancelOrder:
|
||||
time.Sleep(r.SpotCancelOrder.Reserve().Delay())
|
||||
return r.SpotCancelOrder.Wait(ctx)
|
||||
case spotCancelOrdersBatch:
|
||||
time.Sleep(r.SpotCancelOrdersBatch.Reserve().Delay())
|
||||
return r.SpotCancelOrdersBatch.Wait(ctx)
|
||||
default:
|
||||
return errors.New("rate limit error endpoint functionality not set")
|
||||
return errors.New("rate limit endpoint functionality not set")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetRateLimit returns the rate limit for the exchange
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package gemini
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
@@ -21,13 +22,11 @@ type RateLimit struct {
|
||||
}
|
||||
|
||||
// Limit limits the endpoint functionality
|
||||
func (r *RateLimit) Limit(f request.EndpointLimit) error {
|
||||
func (r *RateLimit) Limit(ctx context.Context, f request.EndpointLimit) error {
|
||||
if f == request.Auth {
|
||||
time.Sleep(r.Auth.Reserve().Delay())
|
||||
return nil
|
||||
return r.Auth.Wait(ctx)
|
||||
}
|
||||
time.Sleep(r.UnAuth.Reserve().Delay())
|
||||
return nil
|
||||
return r.UnAuth.Wait(ctx)
|
||||
}
|
||||
|
||||
// SetRateLimit returns the rate limit for the exchange
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package hitbtc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
@@ -27,18 +28,17 @@ type RateLimit struct {
|
||||
}
|
||||
|
||||
// Limit limits outbound requests
|
||||
func (r *RateLimit) Limit(f request.EndpointLimit) error {
|
||||
func (r *RateLimit) Limit(ctx context.Context, f request.EndpointLimit) error {
|
||||
switch f {
|
||||
case marketRequests:
|
||||
time.Sleep(r.MarketData.Reserve().Delay())
|
||||
return r.MarketData.Wait(ctx)
|
||||
case tradingRequests:
|
||||
time.Sleep(r.Trading.Reserve().Delay())
|
||||
return r.Trading.Wait(ctx)
|
||||
case otherRequests:
|
||||
time.Sleep(r.Other.Reserve().Delay())
|
||||
return r.Other.Wait(ctx)
|
||||
default:
|
||||
return errors.New("functionality not found")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetRateLimit returns the rate limit for the exchange
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package huobi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
@@ -41,24 +42,23 @@ type RateLimit struct {
|
||||
}
|
||||
|
||||
// Limit limits outbound requests
|
||||
func (r *RateLimit) Limit(f request.EndpointLimit) error {
|
||||
func (r *RateLimit) Limit(ctx context.Context, f request.EndpointLimit) error {
|
||||
switch f {
|
||||
// TODO: Add futures and swap functionality
|
||||
case huobiFuturesAuth:
|
||||
time.Sleep(r.FuturesAuth.Reserve().Delay())
|
||||
return r.FuturesAuth.Wait(ctx)
|
||||
case huobiFuturesUnAuth:
|
||||
time.Sleep(r.FuturesUnauth.Reserve().Delay())
|
||||
return r.FuturesUnauth.Wait(ctx)
|
||||
case huobiFuturesTransfer:
|
||||
time.Sleep(r.FuturesXfer.Reserve().Delay())
|
||||
return r.FuturesXfer.Wait(ctx)
|
||||
case huobiSwapAuth:
|
||||
time.Sleep(r.SwapAuth.Reserve().Delay())
|
||||
return r.SwapAuth.Wait(ctx)
|
||||
case huobiSwapUnauth:
|
||||
time.Sleep(r.SwapUnauth.Reserve().Delay())
|
||||
return r.SwapUnauth.Wait(ctx)
|
||||
default:
|
||||
// Spot calls
|
||||
time.Sleep(r.Spot.Reserve().Delay())
|
||||
return r.Spot.Wait(ctx)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetRateLimit returns the rate limit for the exchange
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package poloniex
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
@@ -20,13 +21,11 @@ type RateLimit struct {
|
||||
}
|
||||
|
||||
// Limit limits outbound calls
|
||||
func (r *RateLimit) Limit(f request.EndpointLimit) error {
|
||||
func (r *RateLimit) Limit(ctx context.Context, f request.EndpointLimit) error {
|
||||
if f == request.Auth {
|
||||
time.Sleep(r.Auth.Reserve().Delay())
|
||||
return nil
|
||||
return r.Auth.Wait(ctx)
|
||||
}
|
||||
time.Sleep(r.UnAuth.Reserve().Delay())
|
||||
return nil
|
||||
return r.UnAuth.Wait(ctx)
|
||||
}
|
||||
|
||||
// SetRateLimit returns the rate limit for the exchange
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package request
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@@ -22,9 +23,8 @@ type BasicLimit struct {
|
||||
}
|
||||
|
||||
// Limit executes a single rate limit set by NewRateLimit
|
||||
func (b *BasicLimit) Limit(_ EndpointLimit) error {
|
||||
time.Sleep(b.r.Reserve().Delay())
|
||||
return nil
|
||||
func (b *BasicLimit) Limit(ctx context.Context, _ EndpointLimit) error {
|
||||
return b.r.Wait(ctx)
|
||||
}
|
||||
|
||||
// EndpointLimit defines individual endpoint rate limits that are set when
|
||||
@@ -35,7 +35,7 @@ type EndpointLimit int
|
||||
// wrapper for extended rate limiting configuration i.e. Shells of rate
|
||||
// limits with a global rate for sub rates.
|
||||
type Limiter interface {
|
||||
Limit(EndpointLimit) error
|
||||
Limit(context.Context, EndpointLimit) error
|
||||
}
|
||||
|
||||
// NewRateLimit creates a new RateLimit based of time interval and how many
|
||||
@@ -59,13 +59,13 @@ func NewBasicRateLimit(interval time.Duration, actions int) Limiter {
|
||||
}
|
||||
|
||||
// InitiateRateLimit sleeps for designated end point rate limits
|
||||
func (r *Requester) InitiateRateLimit(e EndpointLimit) error {
|
||||
func (r *Requester) InitiateRateLimit(ctx context.Context, e EndpointLimit) error {
|
||||
if atomic.LoadInt32(&r.disableRateLimiter) == 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if r.limiter != nil {
|
||||
return r.limiter.Limit(e)
|
||||
return r.limiter.Limit(ctx, e)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -112,10 +112,17 @@ func (i *Item) validateRequest(ctx context.Context, r *Requester) (*http.Request
|
||||
// DoRequest performs a HTTP/HTTPS request with the supplied params
|
||||
func (r *Requester) doRequest(ctx context.Context, endpoint EndpointLimit, newRequest Generate) error {
|
||||
for attempt := 1; ; attempt++ {
|
||||
// Check if context has finished before executing new attempt.
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
// Initiate a rate limit reservation and sleep on requested endpoint
|
||||
err := r.InitiateRateLimit(endpoint)
|
||||
err := r.InitiateRateLimit(ctx, endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to rate limit HTTP request: %w", err)
|
||||
}
|
||||
|
||||
p, err := newRequest()
|
||||
@@ -162,7 +169,7 @@ func (r *Requester) doRequest(ctx context.Context, endpoint EndpointLimit, newRe
|
||||
delay = after
|
||||
}
|
||||
|
||||
if d, ok := req.Context().Deadline(); ok && d.After(time.Now()) && time.Now().Add(delay).After(d) {
|
||||
if dl, ok := req.Context().Deadline(); ok && dl.Before(time.Now().Add(delay)) {
|
||||
if err != nil {
|
||||
return fmt.Errorf("deadline would be exceeded by retry, err: %v", err)
|
||||
}
|
||||
|
||||
@@ -180,20 +180,18 @@ type GlobalLimitTest struct {
|
||||
|
||||
var errEndpointLimitNotFound = errors.New("endpoint limit not found")
|
||||
|
||||
func (g *GlobalLimitTest) Limit(e EndpointLimit) error {
|
||||
func (g *GlobalLimitTest) Limit(ctx context.Context, e EndpointLimit) error {
|
||||
switch e {
|
||||
case Auth:
|
||||
if g.Auth == nil {
|
||||
return errors.New("auth rate not set")
|
||||
}
|
||||
time.Sleep(g.Auth.Reserve().Delay())
|
||||
return nil
|
||||
return g.Auth.Wait(ctx)
|
||||
case UnAuth:
|
||||
if g.UnAuth == nil {
|
||||
return errors.New("unauth rate not set")
|
||||
}
|
||||
time.Sleep(g.UnAuth.Reserve().Delay())
|
||||
return nil
|
||||
return g.UnAuth.Wait(ctx)
|
||||
default:
|
||||
return fmt.Errorf("cannot execute functionality: %d %w",
|
||||
e,
|
||||
@@ -527,11 +525,24 @@ func TestBasicLimiter(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
tn := time.Now()
|
||||
_ = r.SendPayload(ctx, Unset, func() (*Item, error) { return &i, nil })
|
||||
_ = r.SendPayload(ctx, Unset, func() (*Item, error) { return &i, nil })
|
||||
err := r.SendPayload(ctx, Unset, func() (*Item, error) { return &i, nil })
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = r.SendPayload(ctx, Unset, func() (*Item, error) { return &i, nil })
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if time.Since(tn) < time.Second {
|
||||
t.Error("rate limit issues")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithDeadline(ctx, tn.Add(time.Nanosecond))
|
||||
defer cancel()
|
||||
err = r.SendPayload(ctx, Unset, func() (*Item, error) { return &i, nil })
|
||||
if !errors.Is(err, context.DeadlineExceeded) {
|
||||
t.Fatalf("receieved: %v but expected: %v", err, context.DeadlineExceeded)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnableDisableRateLimit(t *testing.T) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package zb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
@@ -27,16 +28,15 @@ type RateLimit struct {
|
||||
}
|
||||
|
||||
// Limit limits the outbound requests
|
||||
func (r *RateLimit) Limit(f request.EndpointLimit) error {
|
||||
func (r *RateLimit) Limit(ctx context.Context, f request.EndpointLimit) error {
|
||||
switch f {
|
||||
case request.Auth:
|
||||
time.Sleep(r.Auth.Reserve().Delay())
|
||||
return r.Auth.Wait(ctx)
|
||||
case klineFunc:
|
||||
time.Sleep(r.KlineData.Reserve().Delay())
|
||||
return r.KlineData.Wait(ctx)
|
||||
default:
|
||||
time.Sleep(r.UnAuth.Reserve().Delay())
|
||||
return r.UnAuth.Wait(ctx)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetRateLimit returns the rate limit for the exchange
|
||||
|
||||
Reference in New Issue
Block a user