mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-06-04 15:10:54 +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:
@@ -31,6 +31,7 @@ const (
|
||||
cfuturesContinuousKline = "/dapi/v1/continuousKlines?"
|
||||
cfuturesIndexKline = "/dapi/v1/indexPriceKlines?"
|
||||
cfuturesMarkPriceKline = "/dapi/v1/markPriceKlines?"
|
||||
cfuturesFundingRateInfo = "/dapi/v1/fundingInfo?"
|
||||
cfuturesMarkPrice = "/dapi/v1/premiumIndex?"
|
||||
cfuturesFundingRateHistory = "/dapi/v1/fundingRate?"
|
||||
cfuturesTickerPriceStats = "/dapi/v1/ticker/24hr?"
|
||||
@@ -236,6 +237,13 @@ func (b *Binance) GetIndexAndMarkPrice(ctx context.Context, symbol, pair string)
|
||||
return resp, b.SendHTTPRequest(ctx, exchange.RestCoinMargined, cfuturesMarkPrice+params.Encode(), cFuturesIndexMarkPriceRate, &resp)
|
||||
}
|
||||
|
||||
// GetFundingRateInfo returns extra details about funding rates
|
||||
func (b *Binance) GetFundingRateInfo(ctx context.Context) ([]FundingRateInfoResponse, error) {
|
||||
params := url.Values{}
|
||||
var resp []FundingRateInfoResponse
|
||||
return resp, b.SendHTTPRequest(ctx, exchange.RestCoinMargined, cfuturesFundingRateInfo+params.Encode(), uFuturesDefaultRate, &resp)
|
||||
}
|
||||
|
||||
// GetFuturesKlineData gets futures kline data for CoinMarginedFutures,
|
||||
func (b *Binance) GetFuturesKlineData(ctx context.Context, symbol currency.Pair, interval string, limit int64, startTime, endTime time.Time) ([]FuturesCandleStick, error) {
|
||||
params := url.Values{}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/core"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
@@ -2819,7 +2820,7 @@ func TestUpdateOrderExecutionLimits(t *testing.T) {
|
||||
func TestGetFundingRates(t *testing.T) {
|
||||
t.Parallel()
|
||||
s, e := getTime()
|
||||
_, err := b.GetFundingRates(context.Background(), &fundingrate.RatesRequest{
|
||||
_, err := b.GetHistoricalFundingRates(context.Background(), &fundingrate.HistoricalRatesRequest{
|
||||
Asset: asset.USDTMarginedFutures,
|
||||
Pair: currency.NewPair(currency.BTC, currency.USDT),
|
||||
StartDate: s,
|
||||
@@ -2831,7 +2832,7 @@ func TestGetFundingRates(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
_, err = b.GetFundingRates(context.Background(), &fundingrate.RatesRequest{
|
||||
_, err = b.GetHistoricalFundingRates(context.Background(), &fundingrate.HistoricalRatesRequest{
|
||||
Asset: asset.USDTMarginedFutures,
|
||||
Pair: currency.NewPair(currency.BTC, currency.USDT),
|
||||
StartDate: s,
|
||||
@@ -2842,7 +2843,7 @@ func TestGetFundingRates(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
r := &fundingrate.RatesRequest{
|
||||
r := &fundingrate.HistoricalRatesRequest{
|
||||
Asset: asset.USDTMarginedFutures,
|
||||
Pair: currency.NewPair(currency.BTC, currency.USDT),
|
||||
StartDate: s,
|
||||
@@ -2851,7 +2852,7 @@ func TestGetFundingRates(t *testing.T) {
|
||||
if sharedtestvalues.AreAPICredentialsSet(b) {
|
||||
r.IncludePayments = true
|
||||
}
|
||||
_, err = b.GetFundingRates(context.Background(), r)
|
||||
_, err = b.GetHistoricalFundingRates(context.Background(), r)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -2861,37 +2862,36 @@ func TestGetFundingRates(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = b.GetFundingRates(context.Background(), r)
|
||||
_, err = b.GetHistoricalFundingRates(context.Background(), r)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetLatestFundingRate(t *testing.T) {
|
||||
func TestGetLatestFundingRates(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := b.GetLatestFundingRate(context.Background(), &fundingrate.LatestRateRequest{
|
||||
cp := currency.NewPair(currency.BTC, currency.USDT)
|
||||
_, err := b.GetLatestFundingRates(context.Background(), &fundingrate.LatestRateRequest{
|
||||
Asset: asset.USDTMarginedFutures,
|
||||
Pair: currency.NewPair(currency.BTC, currency.USDT),
|
||||
Pair: cp,
|
||||
IncludePredictedRate: true,
|
||||
})
|
||||
if !errors.Is(err, common.ErrFunctionNotSupported) {
|
||||
t.Error(err)
|
||||
}
|
||||
_, err = b.GetLatestFundingRate(context.Background(), &fundingrate.LatestRateRequest{
|
||||
err = b.CurrencyPairs.EnablePair(asset.USDTMarginedFutures, cp)
|
||||
if err != nil && !errors.Is(err, currency.ErrPairAlreadyEnabled) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = b.GetLatestFundingRates(context.Background(), &fundingrate.LatestRateRequest{
|
||||
Asset: asset.USDTMarginedFutures,
|
||||
Pair: currency.NewPair(currency.BTC, currency.USDT),
|
||||
Pair: cp,
|
||||
})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
cp, err := currency.NewPairFromString("BTCUSD_PERP")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
_, err = b.GetLatestFundingRate(context.Background(), &fundingrate.LatestRateRequest{
|
||||
_, err = b.GetLatestFundingRates(context.Background(), &fundingrate.LatestRateRequest{
|
||||
Asset: asset.CoinMarginedFutures,
|
||||
Pair: cp,
|
||||
})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
@@ -3418,14 +3418,24 @@ func TestGetFuturesContractDetails(t *testing.T) {
|
||||
if !errors.Is(err, asset.ErrNotSupported) {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
_, err = b.GetFuturesContractDetails(context.Background(), asset.USDTMarginedFutures)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
_, err = b.GetFuturesContractDetails(context.Background(), asset.CoinMarginedFutures)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetFundingRateInfo(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := b.GetFundingRateInfo(context.Background())
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestUGetFundingRateInfo(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := b.UGetFundingRateInfo(context.Background())
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ const (
|
||||
ufuturesKlineData = "/fapi/v1/klines?"
|
||||
ufuturesMarkPrice = "/fapi/v1/premiumIndex?"
|
||||
ufuturesFundingRateHistory = "/fapi/v1/fundingRate?"
|
||||
ufuturesFundingRateInfo = "/fapi/v1/fundingInfo?"
|
||||
ufuturesTickerPriceStats = "/fapi/v1/ticker/24hr?"
|
||||
ufuturesSymbolPriceTicker = "/fapi/v1/ticker/price?"
|
||||
ufuturesSymbolOrderbook = "/fapi/v1/ticker/bookTicker?"
|
||||
@@ -379,6 +380,12 @@ func (b *Binance) UGetMarkPrice(ctx context.Context, symbol currency.Pair) ([]UM
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// UGetFundingRateInfo returns extra details about funding rates
|
||||
func (b *Binance) UGetFundingRateInfo(ctx context.Context) ([]FundingRateInfoResponse, error) {
|
||||
var resp []FundingRateInfoResponse
|
||||
return resp, b.SendHTTPRequest(ctx, exchange.RestUSDTMargined, ufuturesFundingRateInfo, uFuturesDefaultRate, &resp)
|
||||
}
|
||||
|
||||
// UGetFundingHistory gets funding history for USDTMarginedFutures
|
||||
func (b *Binance) UGetFundingHistory(ctx context.Context, symbol currency.Pair, limit int64, startTime, endTime time.Time) ([]FundingRateHistory, error) {
|
||||
var resp []FundingRateHistory
|
||||
|
||||
@@ -149,6 +149,7 @@ func (b *Binance) SetDefaults() {
|
||||
MultiChainDeposits: true,
|
||||
MultiChainWithdrawals: true,
|
||||
HasAssetTypeAccountSegregation: true,
|
||||
FundingRateFetching: true,
|
||||
},
|
||||
WebsocketCapabilities: protocol.Features{
|
||||
TradeFetching: true,
|
||||
@@ -161,6 +162,7 @@ func (b *Binance) SetDefaults() {
|
||||
GetOrders: true,
|
||||
Subscribe: true,
|
||||
Unsubscribe: true,
|
||||
FundingRateFetching: false, // supported but not implemented // TODO when multi-websocket support added
|
||||
},
|
||||
WithdrawPermissions: exchange.AutoWithdrawCrypto |
|
||||
exchange.NoFiatWithdrawals,
|
||||
@@ -169,11 +171,17 @@ func (b *Binance) SetDefaults() {
|
||||
Intervals: true,
|
||||
},
|
||||
FuturesCapabilities: exchange.FuturesCapabilities{
|
||||
Positions: true,
|
||||
Leverage: true,
|
||||
CollateralMode: true,
|
||||
FundingRates: true,
|
||||
FundingRateFrequency: kline.EightHour.Duration(),
|
||||
Positions: true,
|
||||
Leverage: true,
|
||||
CollateralMode: true,
|
||||
FundingRates: true,
|
||||
SupportedFundingRateFrequencies: map[kline.Interval]bool{
|
||||
kline.FourHour: true,
|
||||
kline.EightHour: true,
|
||||
},
|
||||
FundingRateBatching: map[asset.Item]bool{
|
||||
asset.USDTMarginedFutures: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
Enabled: exchange.FeaturesEnabled{
|
||||
@@ -1985,7 +1993,7 @@ func (b *Binance) UpdateOrderExecutionLimits(ctx context.Context, a asset.Item)
|
||||
err = fmt.Errorf("%w %v", asset.ErrNotSupported, a)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot update exchange execution limits: %v", err)
|
||||
return fmt.Errorf("cannot update exchange execution limits: %w", err)
|
||||
}
|
||||
return b.LoadLimits(limits)
|
||||
}
|
||||
@@ -2072,57 +2080,148 @@ func (b *Binance) GetServerTime(ctx context.Context, ai asset.Item) (time.Time,
|
||||
return time.Time{}, fmt.Errorf("%s %w", ai, asset.ErrNotSupported)
|
||||
}
|
||||
|
||||
// GetLatestFundingRate returns the latest funding rate for a given asset and currency
|
||||
func (b *Binance) GetLatestFundingRate(ctx context.Context, r *fundingrate.LatestRateRequest) (*fundingrate.LatestRateResponse, error) {
|
||||
// GetLatestFundingRates returns the latest funding rates data
|
||||
func (b *Binance) 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)
|
||||
}
|
||||
format, err := b.GetPairFormat(r.Asset, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fPair := r.Pair.Format(format)
|
||||
pairRate := fundingrate.LatestRateResponse{
|
||||
Exchange: b.Name,
|
||||
Asset: r.Asset,
|
||||
Pair: fPair,
|
||||
}
|
||||
switch r.Asset {
|
||||
case asset.USDTMarginedFutures:
|
||||
var mp []UMarkPrice
|
||||
mp, err = b.UGetMarkPrice(ctx, r.Pair)
|
||||
fPair := r.Pair
|
||||
var err error
|
||||
if !fPair.IsEmpty() {
|
||||
var format currency.PairFormat
|
||||
format, err = b.GetPairFormat(r.Asset, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pairRate.TimeOfNextRate = time.UnixMilli(mp[len(mp)-1].NextFundingTime)
|
||||
pairRate.LatestRate = fundingrate.Rate{
|
||||
Time: time.UnixMilli(mp[len(mp)-1].Time).Truncate(b.Features.Supports.FuturesCapabilities.FundingRateFrequency),
|
||||
Rate: decimal.NewFromFloat(mp[len(mp)-1].LastFundingRate),
|
||||
fPair = r.Pair.Format(format)
|
||||
}
|
||||
|
||||
switch r.Asset {
|
||||
case asset.USDTMarginedFutures:
|
||||
var mp []UMarkPrice
|
||||
var fri []FundingRateInfoResponse
|
||||
fri, err = b.UGetFundingRateInfo(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mp, err = b.UGetMarkPrice(ctx, fPair)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp := make([]fundingrate.LatestRateResponse, 0, len(mp))
|
||||
for i := range mp {
|
||||
var cp currency.Pair
|
||||
var isEnabled bool
|
||||
cp, isEnabled, err = b.MatchSymbolCheckEnabled(mp[i].Symbol, r.Asset, true)
|
||||
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
|
||||
}
|
||||
var fundingRateFrequency int64
|
||||
for x := range fri {
|
||||
if fri[x].Symbol != mp[i].Symbol {
|
||||
continue
|
||||
}
|
||||
fundingRateFrequency = fri[x].FundingIntervalHours
|
||||
break
|
||||
}
|
||||
nft := time.UnixMilli(mp[i].NextFundingTime)
|
||||
rate := fundingrate.LatestRateResponse{
|
||||
TimeChecked: time.Now(),
|
||||
Exchange: b.Name,
|
||||
Asset: r.Asset,
|
||||
Pair: cp,
|
||||
LatestRate: fundingrate.Rate{
|
||||
Time: time.UnixMilli(mp[i].Time).Truncate(time.Hour * time.Duration(fundingRateFrequency)),
|
||||
Rate: decimal.NewFromFloat(mp[i].LastFundingRate),
|
||||
},
|
||||
}
|
||||
if nft.Year() == rate.TimeChecked.Year() {
|
||||
rate.TimeOfNextRate = nft
|
||||
}
|
||||
resp = append(resp, rate)
|
||||
}
|
||||
if len(resp) == 0 {
|
||||
return nil, fmt.Errorf("%w %v %v", futures.ErrNotPerpetualFuture, r.Asset, r.Pair)
|
||||
}
|
||||
return resp, nil
|
||||
case asset.CoinMarginedFutures:
|
||||
var mp []IndexMarkPrice
|
||||
mp, err = b.GetIndexAndMarkPrice(ctx, fPair.String(), "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pairRate.TimeOfNextRate = time.UnixMilli(mp[len(mp)-1].NextFundingTime)
|
||||
pairRate.LatestRate = fundingrate.Rate{
|
||||
Time: time.UnixMilli(mp[len(mp)-1].Time).Truncate(b.Features.Supports.FuturesCapabilities.FundingRateFrequency),
|
||||
Rate: mp[len(mp)-1].LastFundingRate.Decimal(),
|
||||
var fri []FundingRateInfoResponse
|
||||
fri, err = b.GetFundingRateInfo(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("%s %w", r.Asset, asset.ErrNotSupported)
|
||||
|
||||
resp := make([]fundingrate.LatestRateResponse, 0, len(mp))
|
||||
for i := range mp {
|
||||
var cp currency.Pair
|
||||
cp, err = currency.NewPairFromString(mp[i].Symbol)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var isPerp bool
|
||||
isPerp, err = b.IsPerpetualFutureCurrency(r.Asset, cp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !isPerp {
|
||||
continue
|
||||
}
|
||||
var fundingRateFrequency int64
|
||||
for x := range fri {
|
||||
if fri[x].Symbol != mp[i].Symbol {
|
||||
continue
|
||||
}
|
||||
fundingRateFrequency = fri[x].FundingIntervalHours
|
||||
break
|
||||
}
|
||||
nft := time.UnixMilli(mp[i].NextFundingTime)
|
||||
rate := fundingrate.LatestRateResponse{
|
||||
TimeChecked: time.Now(),
|
||||
Exchange: b.Name,
|
||||
Asset: r.Asset,
|
||||
Pair: cp,
|
||||
LatestRate: fundingrate.Rate{
|
||||
Time: time.UnixMilli(mp[i].Time).Truncate(time.Duration(fundingRateFrequency) * time.Hour),
|
||||
Rate: mp[i].LastFundingRate.Decimal(),
|
||||
},
|
||||
}
|
||||
if nft.Year() == rate.TimeChecked.Year() {
|
||||
rate.TimeOfNextRate = nft
|
||||
}
|
||||
resp = append(resp, rate)
|
||||
}
|
||||
if len(resp) == 0 {
|
||||
return nil, fmt.Errorf("%w %v %v", futures.ErrNotPerpetualFuture, r.Asset, r.Pair)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
return &pairRate, nil
|
||||
return nil, fmt.Errorf("%s %w", r.Asset, asset.ErrNotSupported)
|
||||
}
|
||||
|
||||
// GetFundingRates returns funding rates for a given asset and currency for a time period
|
||||
func (b *Binance) GetFundingRates(ctx context.Context, r *fundingrate.RatesRequest) (*fundingrate.Rates, error) {
|
||||
// GetHistoricalFundingRates returns funding rates for a given asset and currency for a time period
|
||||
func (b *Binance) GetHistoricalFundingRates(ctx context.Context, r *fundingrate.HistoricalRatesRequest) (*fundingrate.HistoricalRates, error) {
|
||||
if r == nil {
|
||||
return nil, fmt.Errorf("%w RatesRequest", common.ErrNilPointer)
|
||||
return nil, fmt.Errorf("%w HistoricalRatesRequest", common.ErrNilPointer)
|
||||
}
|
||||
if r.IncludePredictedRate {
|
||||
return nil, fmt.Errorf("%w GetFundingRates IncludePredictedRate", common.ErrFunctionNotSupported)
|
||||
@@ -2138,7 +2237,7 @@ func (b *Binance) GetFundingRates(ctx context.Context, r *fundingrate.RatesReque
|
||||
return nil, err
|
||||
}
|
||||
fPair := r.Pair.Format(format)
|
||||
pairRate := fundingrate.Rates{
|
||||
pairRate := fundingrate.HistoricalRates{
|
||||
Exchange: b.Name,
|
||||
Asset: r.Asset,
|
||||
Pair: fPair,
|
||||
@@ -2149,6 +2248,20 @@ func (b *Binance) GetFundingRates(ctx context.Context, r *fundingrate.RatesReque
|
||||
case asset.USDTMarginedFutures:
|
||||
requestLimit := 1000
|
||||
sd := r.StartDate
|
||||
var fri []FundingRateInfoResponse
|
||||
fri, err = b.UGetFundingRateInfo(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var fundingRateFrequency int64
|
||||
fps := fPair.String()
|
||||
for x := range fri {
|
||||
if fri[x].Symbol != fps {
|
||||
continue
|
||||
}
|
||||
fundingRateFrequency = fri[x].FundingIntervalHours
|
||||
break
|
||||
}
|
||||
for {
|
||||
var frh []FundingRateHistory
|
||||
frh, err = b.UGetFundingHistory(ctx, fPair, int64(requestLimit), sd, r.EndDate)
|
||||
@@ -2172,7 +2285,7 @@ func (b *Binance) GetFundingRates(ctx context.Context, r *fundingrate.RatesReque
|
||||
return nil, err
|
||||
}
|
||||
pairRate.LatestRate = fundingrate.Rate{
|
||||
Time: time.UnixMilli(mp[len(mp)-1].Time).Truncate(b.Features.Supports.FuturesCapabilities.FundingRateFrequency),
|
||||
Time: time.UnixMilli(mp[len(mp)-1].Time).Truncate(time.Duration(fundingRateFrequency) * time.Hour),
|
||||
Rate: decimal.NewFromFloat(mp[len(mp)-1].LastFundingRate),
|
||||
}
|
||||
pairRate.TimeOfNextRate = time.UnixMilli(mp[len(mp)-1].NextFundingTime)
|
||||
@@ -2185,7 +2298,7 @@ func (b *Binance) GetFundingRates(ctx context.Context, r *fundingrate.RatesReque
|
||||
for j := range income {
|
||||
for x := range pairRate.FundingRates {
|
||||
tt := time.UnixMilli(income[j].Time)
|
||||
tt = tt.Truncate(b.Features.Supports.FuturesCapabilities.FundingRateFrequency)
|
||||
tt = tt.Truncate(time.Duration(fundingRateFrequency) * time.Hour)
|
||||
if !tt.Equal(pairRate.FundingRates[x].Time) {
|
||||
continue
|
||||
}
|
||||
@@ -2201,6 +2314,20 @@ func (b *Binance) GetFundingRates(ctx context.Context, r *fundingrate.RatesReque
|
||||
case asset.CoinMarginedFutures:
|
||||
requestLimit := 1000
|
||||
sd := r.StartDate
|
||||
var fri []FundingRateInfoResponse
|
||||
fri, err = b.GetFundingRateInfo(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var fundingRateFrequency int64
|
||||
fps := fPair.String()
|
||||
for x := range fri {
|
||||
if fri[x].Symbol != fps {
|
||||
continue
|
||||
}
|
||||
fundingRateFrequency = fri[x].FundingIntervalHours
|
||||
break
|
||||
}
|
||||
for {
|
||||
var frh []FundingRateHistory
|
||||
frh, err = b.FuturesGetFundingHistory(ctx, fPair, int64(requestLimit), sd, r.EndDate)
|
||||
@@ -2224,7 +2351,7 @@ func (b *Binance) GetFundingRates(ctx context.Context, r *fundingrate.RatesReque
|
||||
return nil, err
|
||||
}
|
||||
pairRate.LatestRate = fundingrate.Rate{
|
||||
Time: time.UnixMilli(mp[len(mp)-1].Time).Truncate(b.Features.Supports.FuturesCapabilities.FundingRateFrequency),
|
||||
Time: time.UnixMilli(mp[len(mp)-1].Time).Truncate(time.Duration(fundingRateFrequency) * time.Hour),
|
||||
Rate: mp[len(mp)-1].LastFundingRate.Decimal(),
|
||||
}
|
||||
pairRate.TimeOfNextRate = time.UnixMilli(mp[len(mp)-1].NextFundingTime)
|
||||
@@ -2237,7 +2364,7 @@ func (b *Binance) GetFundingRates(ctx context.Context, r *fundingrate.RatesReque
|
||||
for j := range income {
|
||||
for x := range pairRate.FundingRates {
|
||||
tt := time.UnixMilli(income[j].Timestamp)
|
||||
tt = tt.Truncate(b.Features.Supports.FuturesCapabilities.FundingRateFrequency)
|
||||
tt = tt.Truncate(time.Duration(fundingRateFrequency) * time.Hour)
|
||||
if !tt.Equal(pairRate.FundingRates[x].Time) {
|
||||
continue
|
||||
}
|
||||
@@ -2259,14 +2386,10 @@ func (b *Binance) GetFundingRates(ctx context.Context, r *fundingrate.RatesReque
|
||||
// IsPerpetualFutureCurrency ensures a given asset and currency is a perpetual future
|
||||
func (b *Binance) IsPerpetualFutureCurrency(a asset.Item, cp currency.Pair) (bool, error) {
|
||||
if a == asset.CoinMarginedFutures {
|
||||
if cp.Quote.Equal(currency.PERP) {
|
||||
return true, nil
|
||||
}
|
||||
return cp.Quote.Equal(currency.PERP), nil
|
||||
}
|
||||
if a == asset.USDTMarginedFutures {
|
||||
if cp.Quote.Equal(currency.USDT) || cp.Quote.Equal(currency.BUSD) {
|
||||
return true, nil
|
||||
}
|
||||
return cp.Quote.Equal(currency.USDT) || cp.Quote.Equal(currency.BUSD), nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
@@ -2406,7 +2529,7 @@ func (b *Binance) GetFuturesPositionSummary(ctx context.Context, req *futures.Po
|
||||
var leverage, maintenanceMargin, initialMargin,
|
||||
liquidationPrice, markPrice, positionSize,
|
||||
collateralTotal, collateralUsed, collateralAvailable,
|
||||
pnl, openPrice, isolatedMargin float64
|
||||
unrealisedPNL, openPrice, isolatedMargin float64
|
||||
|
||||
for i := range ai.Positions {
|
||||
if ai.Positions[i].Symbol != fPair.String() {
|
||||
@@ -2469,7 +2592,7 @@ func (b *Binance) GetFuturesPositionSummary(ctx context.Context, req *futures.Po
|
||||
}
|
||||
collateralTotal = collateralAsset.WalletBalance
|
||||
collateralAvailable = collateralAsset.AvailableBalance
|
||||
pnl = collateralAsset.UnrealizedProfit
|
||||
unrealisedPNL = collateralAsset.UnrealizedProfit
|
||||
c = currency.NewCode(collateralAsset.Asset)
|
||||
if marginType == margin.Multi {
|
||||
isolatedMargin = collateralAsset.CrossUnPnl
|
||||
@@ -2482,7 +2605,7 @@ func (b *Binance) GetFuturesPositionSummary(ctx context.Context, req *futures.Po
|
||||
collateralTotal = ai.TotalWalletBalance
|
||||
collateralUsed = ai.TotalWalletBalance - ai.AvailableBalance
|
||||
collateralAvailable = ai.AvailableBalance
|
||||
pnl = accountPosition.UnrealisedProfit
|
||||
unrealisedPNL = accountPosition.UnrealisedProfit
|
||||
}
|
||||
|
||||
var maintenanceMarginFraction decimal.Decimal
|
||||
@@ -2496,8 +2619,9 @@ func (b *Binance) GetFuturesPositionSummary(ctx context.Context, req *futures.Po
|
||||
return nil, err
|
||||
}
|
||||
var relevantPosition *UPositionInformationV2
|
||||
fps := fPair.String()
|
||||
for i := range positionsInfo {
|
||||
if positionsInfo[i].Symbol != fPair.String() {
|
||||
if positionsInfo[i].Symbol != fps {
|
||||
continue
|
||||
}
|
||||
relevantPosition = &positionsInfo[i]
|
||||
@@ -2522,7 +2646,7 @@ func (b *Binance) GetFuturesPositionSummary(ctx context.Context, req *futures.Po
|
||||
MarkPrice: decimal.NewFromFloat(markPrice),
|
||||
CurrentSize: decimal.NewFromFloat(positionSize),
|
||||
AverageOpenPrice: decimal.NewFromFloat(openPrice),
|
||||
PositionPNL: decimal.NewFromFloat(pnl),
|
||||
UnrealisedPNL: decimal.NewFromFloat(unrealisedPNL),
|
||||
MaintenanceMarginFraction: maintenanceMarginFraction,
|
||||
FreeCollateral: decimal.NewFromFloat(collateralAvailable),
|
||||
TotalCollateral: decimal.NewFromFloat(collateralTotal),
|
||||
@@ -2540,8 +2664,9 @@ func (b *Binance) GetFuturesPositionSummary(ctx context.Context, req *futures.Po
|
||||
pnl, openPrice, isolatedMargin float64
|
||||
|
||||
var accountPosition *FuturesAccountInformationPosition
|
||||
fps := fPair.String()
|
||||
for i := range ai.Positions {
|
||||
if ai.Positions[i].Symbol != fPair.String() {
|
||||
if ai.Positions[i].Symbol != fps {
|
||||
continue
|
||||
}
|
||||
accountPosition = &ai.Positions[i]
|
||||
@@ -2594,7 +2719,7 @@ func (b *Binance) GetFuturesPositionSummary(ctx context.Context, req *futures.Po
|
||||
}
|
||||
var relevantPosition *FuturesPositionInformation
|
||||
for i := range positionsInfo {
|
||||
if positionsInfo[i].Symbol != fPair.String() {
|
||||
if positionsInfo[i].Symbol != fps {
|
||||
continue
|
||||
}
|
||||
relevantPosition = &positionsInfo[i]
|
||||
@@ -2642,7 +2767,7 @@ func (b *Binance) GetFuturesPositionSummary(ctx context.Context, req *futures.Po
|
||||
MarkPrice: decimal.NewFromFloat(markPrice),
|
||||
CurrentSize: decimal.NewFromFloat(positionSize),
|
||||
AverageOpenPrice: decimal.NewFromFloat(openPrice),
|
||||
PositionPNL: decimal.NewFromFloat(pnl),
|
||||
UnrealisedPNL: decimal.NewFromFloat(pnl),
|
||||
MaintenanceMarginFraction: mmf,
|
||||
FreeCollateral: decimal.NewFromFloat(collateralAvailable),
|
||||
TotalCollateral: tc,
|
||||
@@ -2873,18 +2998,31 @@ func (b *Binance) GetFuturesContractDetails(ctx context.Context, item asset.Item
|
||||
}
|
||||
switch item {
|
||||
case asset.USDTMarginedFutures:
|
||||
fri, err := b.UGetFundingRateInfo(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ei, err := b.UExchangeInfo(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp := make([]futures.Contract, 0, len(ei.Symbols))
|
||||
for i := range ei.Symbols {
|
||||
var fundingRateFloor, fundingRateCeil decimal.Decimal
|
||||
for j := range fri {
|
||||
if fri[j].Symbol != ei.Symbols[i].Symbol {
|
||||
continue
|
||||
}
|
||||
fundingRateFloor = fri[j].AdjustedFundingRateFloor.Decimal()
|
||||
fundingRateCeil = fri[j].AdjustedFundingRateCap.Decimal()
|
||||
break
|
||||
}
|
||||
var cp currency.Pair
|
||||
cp, err = currency.NewPairFromStrings(ei.Symbols[i].BaseAsset, ei.Symbols[i].Symbol[len(ei.Symbols[i].BaseAsset):])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ct futures.ContractType
|
||||
var ed time.Time
|
||||
if cp.Quote.Equal(currency.USDT) || cp.Quote.Equal(currency.BUSD) {
|
||||
@@ -2894,27 +3032,43 @@ func (b *Binance) GetFuturesContractDetails(ctx context.Context, item asset.Item
|
||||
ed = ei.Symbols[i].DeliveryDate.Time()
|
||||
}
|
||||
resp = append(resp, futures.Contract{
|
||||
Exchange: b.Name,
|
||||
Name: cp,
|
||||
Underlying: currency.NewPair(currency.NewCode(ei.Symbols[i].BaseAsset), currency.NewCode(ei.Symbols[i].QuoteAsset)),
|
||||
Asset: item,
|
||||
SettlementType: futures.Linear,
|
||||
StartDate: ei.Symbols[i].OnboardDate.Time(),
|
||||
EndDate: ed,
|
||||
IsActive: ei.Symbols[i].Status == "TRADING",
|
||||
Status: ei.Symbols[i].Status,
|
||||
MarginCurrency: currency.NewCode(ei.Symbols[i].MarginAsset),
|
||||
Type: ct,
|
||||
Exchange: b.Name,
|
||||
Name: cp,
|
||||
Underlying: currency.NewPair(currency.NewCode(ei.Symbols[i].BaseAsset), currency.NewCode(ei.Symbols[i].QuoteAsset)),
|
||||
Asset: item,
|
||||
SettlementType: futures.Linear,
|
||||
StartDate: ei.Symbols[i].OnboardDate.Time(),
|
||||
EndDate: ed,
|
||||
IsActive: ei.Symbols[i].Status == "TRADING",
|
||||
Status: ei.Symbols[i].Status,
|
||||
MarginCurrency: currency.NewCode(ei.Symbols[i].MarginAsset),
|
||||
Type: ct,
|
||||
FundingRateFloor: fundingRateFloor,
|
||||
FundingRateCeiling: fundingRateCeil,
|
||||
})
|
||||
}
|
||||
return resp, nil
|
||||
case asset.CoinMarginedFutures:
|
||||
fri, err := b.GetFundingRateInfo(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ei, err := b.FuturesExchangeInfo(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp := make([]futures.Contract, 0, len(ei.Symbols))
|
||||
for i := range ei.Symbols {
|
||||
var fundingRateFloor, fundingRateCeil decimal.Decimal
|
||||
for j := range fri {
|
||||
if fri[j].Symbol != ei.Symbols[i].Symbol {
|
||||
continue
|
||||
}
|
||||
fundingRateFloor = fri[j].AdjustedFundingRateFloor.Decimal()
|
||||
fundingRateCeil = fri[j].AdjustedFundingRateCap.Decimal()
|
||||
break
|
||||
}
|
||||
var cp currency.Pair
|
||||
cp, err = currency.NewPairFromString(ei.Symbols[i].Symbol)
|
||||
if err != nil {
|
||||
@@ -2930,16 +3084,18 @@ func (b *Binance) GetFuturesContractDetails(ctx context.Context, item asset.Item
|
||||
ed = ei.Symbols[i].DeliveryDate.Time()
|
||||
}
|
||||
resp = append(resp, futures.Contract{
|
||||
Exchange: b.Name,
|
||||
Name: cp,
|
||||
Underlying: currency.NewPair(currency.NewCode(ei.Symbols[i].BaseAsset), currency.NewCode(ei.Symbols[i].QuoteAsset)),
|
||||
Asset: item,
|
||||
StartDate: ei.Symbols[i].OnboardDate.Time(),
|
||||
EndDate: ed,
|
||||
IsActive: ei.Symbols[i].ContractStatus == "TRADING",
|
||||
MarginCurrency: currency.NewCode(ei.Symbols[i].MarginAsset),
|
||||
SettlementType: futures.Inverse,
|
||||
Type: ct,
|
||||
Exchange: b.Name,
|
||||
Name: cp,
|
||||
Underlying: currency.NewPair(currency.NewCode(ei.Symbols[i].BaseAsset), currency.NewCode(ei.Symbols[i].QuoteAsset)),
|
||||
Asset: item,
|
||||
StartDate: ei.Symbols[i].OnboardDate.Time(),
|
||||
EndDate: ed,
|
||||
IsActive: ei.Symbols[i].ContractStatus == "TRADING",
|
||||
MarginCurrency: currency.NewCode(ei.Symbols[i].MarginAsset),
|
||||
SettlementType: futures.Inverse,
|
||||
Type: ct,
|
||||
FundingRateFloor: fundingRateFloor,
|
||||
FundingRateCeiling: fundingRateCeil,
|
||||
})
|
||||
}
|
||||
return resp, nil
|
||||
|
||||
@@ -3,6 +3,7 @@ package binance
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common/convert"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
)
|
||||
|
||||
@@ -81,6 +82,15 @@ type UMarkPrice struct {
|
||||
Time int64 `json:"time"`
|
||||
}
|
||||
|
||||
// FundingRateInfoResponse stores funding rate info
|
||||
type FundingRateInfoResponse struct {
|
||||
Symbol string `json:"symbol"`
|
||||
AdjustedFundingRateCap convert.StringToFloat64 `json:"adjustedFundingRateCap"`
|
||||
AdjustedFundingRateFloor convert.StringToFloat64 `json:"adjustedFundingRateFloor"`
|
||||
FundingIntervalHours int64 `json:"fundingIntervalHours"`
|
||||
Disclaimer bool `json:"disclaimer"`
|
||||
}
|
||||
|
||||
// FundingRateHistory stores funding rate history
|
||||
type FundingRateHistory struct {
|
||||
Symbol string `json:"symbol"`
|
||||
|
||||
Reference in New Issue
Block a user