mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-06-03 15:10:49 +00:00
futures: Implement GetLatestFundingRates across exchanges (#1339)
* adds funding rate implementations and improvements * merge fixes x1 * lint * kucoin funding rates func make * migrate sync-manager to keys * some kucoin work * adds some kucoin wrapper funcs * ehhh, todo * kucoin position * start of orders * adds the kucoin tests yay * multiplier * nits, EWS includes order limits * NotYetImplemented, IsPerp improvements, cleaning * lint, test fix, huobi time * fixes issues, improves testing * fixes linters I WRECKED * local lint but remote lint, lint, lint, lint * fixes err * skip CI * lint * Supported rates, binance endpoints * fixes weird mocktest problems * no, CZ is invalid * fixes some new EWS test errors
This commit is contained in:
@@ -232,11 +232,21 @@ func (b *Bitmex) GetAccountExecutionTradeHistory(ctx context.Context, params *Ge
|
||||
func (b *Bitmex) GetFullFundingHistory(ctx context.Context, symbol, count, filter, columns, start string, reverse bool, startTime, endTime time.Time) ([]Funding, error) {
|
||||
var fundingHistory []Funding
|
||||
params := url.Values{}
|
||||
params.Set("symbol", symbol)
|
||||
params.Set("count", count)
|
||||
params.Set("filter", filter)
|
||||
params.Set("columns", columns)
|
||||
params.Set("start", start)
|
||||
if symbol != "" {
|
||||
params.Set("symbol", symbol)
|
||||
}
|
||||
if count != "" {
|
||||
params.Set("count", count)
|
||||
}
|
||||
if filter != "" {
|
||||
params.Set("filter", filter)
|
||||
}
|
||||
if columns != "" {
|
||||
params.Set("columns", columns)
|
||||
}
|
||||
if !startTime.IsZero() {
|
||||
params.Set("start", start)
|
||||
}
|
||||
params.Set("reverse", "true")
|
||||
if !reverse {
|
||||
params.Set("reverse", "false")
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/fundingrate"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/futures"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
@@ -1256,3 +1257,61 @@ func TestGetFuturesContractDetails(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetLatestFundingRates(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := b.GetLatestFundingRates(context.Background(), &fundingrate.LatestRateRequest{
|
||||
Asset: asset.USDTMarginedFutures,
|
||||
Pair: currency.NewPair(currency.BTC, currency.USDT),
|
||||
IncludePredictedRate: true,
|
||||
})
|
||||
if !errors.Is(err, common.ErrFunctionNotSupported) {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
_, err = b.GetLatestFundingRates(context.Background(), &fundingrate.LatestRateRequest{
|
||||
Asset: asset.Futures,
|
||||
Pair: currency.NewPair(currency.BTC, currency.KLAY),
|
||||
})
|
||||
if !errors.Is(err, futures.ErrNotPerpetualFuture) {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
_, err = b.GetLatestFundingRates(context.Background(), &fundingrate.LatestRateRequest{
|
||||
Asset: asset.PerpetualContract,
|
||||
})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
cp, err := currency.NewPairFromString("ETHUSD")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
_, err = b.GetLatestFundingRates(context.Background(), &fundingrate.LatestRateRequest{
|
||||
Asset: asset.PerpetualContract,
|
||||
Pair: cp,
|
||||
})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsPerpetualFutureCurrency(t *testing.T) {
|
||||
t.Parallel()
|
||||
isPerp, err := b.IsPerpetualFutureCurrency(asset.Futures, currency.NewPair(currency.BTC, currency.USD))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if isPerp {
|
||||
t.Error("expected false")
|
||||
}
|
||||
|
||||
isPerp, err = b.IsPerpetualFutureCurrency(asset.PerpetualContract, currency.NewPair(currency.BTC, currency.USD))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !isPerp {
|
||||
t.Error("expected true")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,6 +123,7 @@ func (b *Bitmex) SetDefaults() {
|
||||
CryptoWithdrawal: true,
|
||||
TradeFee: true,
|
||||
CryptoWithdrawalFee: true,
|
||||
FundingRateFetching: true,
|
||||
},
|
||||
WebsocketCapabilities: protocol.Features{
|
||||
TradeFetching: true,
|
||||
@@ -134,6 +135,16 @@ func (b *Bitmex) SetDefaults() {
|
||||
DeadMansSwitch: true,
|
||||
GetOrders: true,
|
||||
GetOrder: true,
|
||||
FundingRateFetching: false, // supported but not implemented // TODO when multi-websocket support added
|
||||
},
|
||||
FuturesCapabilities: exchange.FuturesCapabilities{
|
||||
FundingRates: true,
|
||||
SupportedFundingRateFrequencies: map[kline.Interval]bool{
|
||||
kline.EightHour: true,
|
||||
},
|
||||
FundingRateBatching: map[asset.Item]bool{
|
||||
asset.PerpetualContract: true,
|
||||
},
|
||||
},
|
||||
WithdrawPermissions: exchange.AutoWithdrawCryptoWithAPIPermission |
|
||||
exchange.WithdrawCryptoWithEmail |
|
||||
@@ -402,12 +413,9 @@ instruments:
|
||||
pair, enabled, err = b.MatchSymbolCheckEnabled(tick[j].Symbol, a, false)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if !errors.Is(err, currency.ErrPairNotFound) {
|
||||
return err
|
||||
}
|
||||
if err != nil && !errors.Is(err, currency.ErrPairNotFound) {
|
||||
return err
|
||||
}
|
||||
|
||||
if !enabled {
|
||||
continue
|
||||
}
|
||||
@@ -1241,3 +1249,86 @@ func (b *Bitmex) GetFuturesContractDetails(ctx context.Context, item asset.Item)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GetLatestFundingRates returns the latest funding rates data
|
||||
func (b *Bitmex) GetLatestFundingRates(ctx context.Context, r *fundingrate.LatestRateRequest) ([]fundingrate.LatestRateResponse, error) {
|
||||
if r == nil {
|
||||
return nil, fmt.Errorf("%w LatestRateRequest", common.ErrNilPointer)
|
||||
}
|
||||
|
||||
if r.IncludePredictedRate {
|
||||
return nil, fmt.Errorf("%w IncludePredictedRate", common.ErrFunctionNotSupported)
|
||||
}
|
||||
|
||||
count := "1"
|
||||
if r.Pair.IsEmpty() {
|
||||
count = "500"
|
||||
} else {
|
||||
isPerp, err := b.IsPerpetualFutureCurrency(r.Asset, r.Pair)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !isPerp {
|
||||
return nil, fmt.Errorf("%w %v %v", futures.ErrNotPerpetualFuture, r.Asset, r.Pair)
|
||||
}
|
||||
}
|
||||
|
||||
format, err := b.GetPairFormat(r.Asset, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fPair := format.Format(r.Pair)
|
||||
rates, err := b.GetFullFundingHistory(ctx, fPair, count, "", "", "", true, time.Time{}, time.Time{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp := make([]fundingrate.LatestRateResponse, 0, len(rates))
|
||||
// Bitmex returns historical rates from this endpoint, we only want the latest
|
||||
latestRateSymbol := make(map[string]bool)
|
||||
for i := range rates {
|
||||
if _, ok := latestRateSymbol[rates[i].Symbol]; ok {
|
||||
continue
|
||||
}
|
||||
latestRateSymbol[rates[i].Symbol] = true
|
||||
var nr time.Time
|
||||
nr, err = time.Parse(time.RFC3339, rates[i].FundingInterval)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var cp currency.Pair
|
||||
var isEnabled bool
|
||||
cp, isEnabled, err = b.MatchSymbolCheckEnabled(rates[i].Symbol, r.Asset, false)
|
||||
if err != nil && !errors.Is(err, currency.ErrPairNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
if !isEnabled {
|
||||
continue
|
||||
}
|
||||
var isPerp bool
|
||||
isPerp, err = b.IsPerpetualFutureCurrency(r.Asset, cp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !isPerp {
|
||||
continue
|
||||
}
|
||||
resp = append(resp, fundingrate.LatestRateResponse{
|
||||
Exchange: b.Name,
|
||||
Asset: r.Asset,
|
||||
Pair: cp,
|
||||
LatestRate: fundingrate.Rate{
|
||||
Time: rates[i].Timestamp,
|
||||
Rate: decimal.NewFromFloat(rates[i].FundingRate),
|
||||
},
|
||||
TimeOfNextRate: rates[i].Timestamp.Add(time.Duration(nr.Hour()) * time.Hour),
|
||||
TimeChecked: time.Now(),
|
||||
})
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// IsPerpetualFutureCurrency ensures a given asset and currency is a perpetual future
|
||||
func (b *Bitmex) IsPerpetualFutureCurrency(a asset.Item, _ currency.Pair) (bool, error) {
|
||||
return a == asset.PerpetualContract, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user