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:
Scott
2023-11-03 10:01:32 +10:00
committed by GitHub
parent f9437dbd08
commit 70690d9a04
78 changed files with 4088 additions and 527 deletions

View File

@@ -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{}

View File

@@ -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)
}

View File

@@ -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

View File

@@ -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

View File

@@ -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"`