Binance,Okx: Add Leverage, MarginType, Positions and CollateralMode support (#1220)

* init

* surprise train commit

* basic distinctions

* the terms of binance are confusing

* renames and introduction of allocatedMargin

* add new margin funcs

* pulling out wires

* implement proper getposition stuff

* bad coding day

* investigate order manager next

* a broken mess, but a progressing one

* finally completes some usdtmargined stuff

* coinMfutures eludes me

* expand to okx

* imports fix

* completes okx wrapper implementations

* cleans and polishes before rpc implementations

* rpc setup, order manager features, exch features

* more rpc, collateral and margin things

* mini test

* looking at rpc response, expansion of features

* reorganising before the storm

* changing how futures requests work

* cleanup and tests of cli usage

* remove silly client side logic

* cleanup

* collateral package, typo fix, margin err, rpc derive

* uses convert.StringToFloat ONLY ON STRUCTS FROM THIS PR

* fix binance order history bug

* niteroos

* adds new funcs to exchange standards testing

* more post merge fixes

* fix binance

* replace simepletimeformat

* fix for merge

* merge fixes

* micro fixes

* order side now required for leverage

* fix up the rest

* global -> portfolio collateral

* Update exchanges/collateral/collateral_test.go

Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>

* adds fields and todos

* rm field redundancy

* lint fix oopsie daisy

* fixes panic, expands error and cli explanations (sorry shaz)

* ensures casing is appropriate for underlying

* Adds a shiny TODO

---------

Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>
This commit is contained in:
Scott
2023-09-26 16:16:31 +10:00
committed by GitHub
parent a2ae99ed7f
commit 5f2f6f884b
67 changed files with 11558 additions and 4475 deletions

View File

@@ -911,6 +911,9 @@ func (b *Binance) SendAuthHTTPRequest(ctx context.Context, ePath exchange.URL, m
return errors.New(errCap.Message)
}
}
if result == nil {
return nil
}
return json.Unmarshal(interim, result)
}

View File

@@ -89,7 +89,7 @@ func (b *Binance) GetFuturesOrderbook(ctx context.Context, symbol currency.Pair,
params := url.Values{}
params.Set("symbol", symbolValue)
if limit > 0 && limit <= 1000 {
if limit > 0 {
params.Set("limit", strconv.FormatInt(limit, 10))
}
@@ -156,7 +156,7 @@ func (b *Binance) GetFuturesPublicTrades(ctx context.Context, symbol currency.Pa
return resp, err
}
params.Set("symbol", symbolValue)
if limit > 0 && limit <= 1000 {
if limit > 0 {
params.Set("limit", strconv.FormatInt(limit, 10))
}
return resp, b.SendHTTPRequest(ctx, exchange.RestCoinMargined, cfuturesRecentTrades+params.Encode(), cFuturesDefaultRate, &resp)
@@ -174,7 +174,7 @@ func (b *Binance) GetFuturesHistoricalTrades(ctx context.Context, symbol currenc
if fromID != "" {
params.Set("fromID", fromID)
}
if limit > 0 && limit < 1000 {
if limit > 0 {
params.Set("limit", strconv.FormatInt(limit, 10))
}
return resp, b.SendAuthHTTPRequest(ctx, exchange.RestCoinMargined, http.MethodGet, cfuturesHistoricalTrades, params, cFuturesHistoricalTradesRate, &resp)
@@ -189,7 +189,7 @@ func (b *Binance) GetPastPublicTrades(ctx context.Context, symbol currency.Pair,
return resp, err
}
params.Set("symbol", symbolValue)
if limit > 0 && limit <= 1000 {
if limit > 0 {
params.Set("limit", strconv.FormatInt(limit, 10))
}
if fromID != 0 {
@@ -207,7 +207,7 @@ func (b *Binance) GetFuturesAggregatedTradesList(ctx context.Context, symbol cur
return resp, err
}
params.Set("symbol", symbolValue)
if limit > 0 && limit <= 1000 {
if limit > 0 {
params.Set("limit", strconv.FormatInt(limit, 10))
}
if fromID != 0 {
@@ -246,7 +246,7 @@ func (b *Binance) GetFuturesKlineData(ctx context.Context, symbol currency.Pair,
}
params.Set("symbol", symbolValue)
}
if limit > 0 && limit <= 1500 {
if limit > 0 {
params.Set("limit", strconv.FormatInt(limit, 10))
}
if !common.StringDataCompare(validFuturesIntervals, interval) {
@@ -365,7 +365,7 @@ func (b *Binance) GetContinuousKlineData(ctx context.Context, pair, contractType
return nil, errors.New("invalid contractType")
}
params.Set("contractType", contractType)
if limit > 0 && limit <= 1500 {
if limit > 0 {
params.Set("limit", strconv.FormatInt(limit, 10))
}
if !common.StringDataCompare(validFuturesIntervals, interval) {
@@ -480,7 +480,7 @@ func (b *Binance) GetContinuousKlineData(ctx context.Context, pair, contractType
func (b *Binance) GetIndexPriceKlines(ctx context.Context, pair, interval string, limit int64, startTime, endTime time.Time) ([]FuturesCandleStick, error) {
params := url.Values{}
params.Set("pair", pair)
if limit > 0 && limit <= 1500 {
if limit > 0 {
params.Set("limit", strconv.FormatInt(limit, 10))
}
if !common.StringDataCompare(validFuturesIntervals, interval) {
@@ -599,7 +599,7 @@ func (b *Binance) GetMarkPriceKline(ctx context.Context, symbol currency.Pair, i
}
params := url.Values{}
params.Set("symbol", symbolValue)
if limit > 0 && limit <= 1500 {
if limit > 0 {
params.Set("limit", strconv.FormatInt(limit, 10))
}
if !common.StringDataCompare(validFuturesIntervals, interval) {
@@ -755,7 +755,7 @@ func (b *Binance) FuturesGetFundingHistory(ctx context.Context, symbol currency.
}
params.Set("symbol", symbolValue)
}
if limit > 0 && limit < 1000 {
if limit > 0 {
params.Set("limit", strconv.FormatInt(limit, 10))
}
if !startTime.IsZero() && !endTime.IsZero() {
@@ -833,7 +833,7 @@ func (b *Binance) GetOpenInterestStats(ctx context.Context, pair, contractType,
return resp, errors.New("invalid period")
}
params.Set("period", period)
if limit > 0 && limit <= 1000 {
if limit > 0 {
params.Set("limit", strconv.FormatInt(limit, 10))
}
if !startTime.IsZero() && !endTime.IsZero() {
@@ -855,7 +855,7 @@ func (b *Binance) GetTraderFuturesAccountRatio(ctx context.Context, pair, period
return resp, errors.New("invalid period")
}
params.Set("period", period)
if limit > 0 && limit <= 1000 {
if limit > 0 {
params.Set("limit", strconv.FormatInt(limit, 10))
}
if !startTime.IsZero() && !endTime.IsZero() {
@@ -877,7 +877,7 @@ func (b *Binance) GetTraderFuturesPositionsRatio(ctx context.Context, pair, peri
return resp, errors.New("invalid period")
}
params.Set("period", period)
if limit > 0 && limit <= 1000 {
if limit > 0 {
params.Set("limit", strconv.FormatInt(limit, 10))
}
if !startTime.IsZero() && !endTime.IsZero() {
@@ -899,7 +899,7 @@ func (b *Binance) GetMarketRatio(ctx context.Context, pair, period string, limit
return resp, errors.New("invalid period")
}
params.Set("period", period)
if limit > 0 && limit <= 1000 {
if limit > 0 {
params.Set("limit", strconv.FormatInt(limit, 10))
}
if !startTime.IsZero() && !endTime.IsZero() {
@@ -921,7 +921,7 @@ func (b *Binance) GetFuturesTakerVolume(ctx context.Context, pair, contractType,
return resp, errors.New("invalid contractType")
}
params.Set("contractType", contractType)
if limit > 0 && limit <= 1000 {
if limit > 0 {
params.Set("limit", strconv.FormatInt(limit, 10))
}
if !common.StringDataCompare(validFuturesIntervals, period) {
@@ -947,7 +947,7 @@ func (b *Binance) GetFuturesBasisData(ctx context.Context, pair, contractType, p
return resp, errors.New("invalid contractType")
}
params.Set("contractType", contractType)
if limit > 0 && limit <= 1000 {
if limit > 0 {
params.Set("limit", strconv.FormatInt(limit, 10))
}
if !common.StringDataCompare(validFuturesIntervals, period) {
@@ -1198,7 +1198,7 @@ func (b *Binance) GetFuturesAllOpenOrders(ctx context.Context, symbol currency.P
}
// GetAllFuturesOrders gets all orders active cancelled or filled
func (b *Binance) GetAllFuturesOrders(ctx context.Context, symbol currency.Pair, pair string, startTime, endTime time.Time, orderID, limit int64) ([]FuturesOrderData, error) {
func (b *Binance) GetAllFuturesOrders(ctx context.Context, symbol, pair currency.Pair, startTime, endTime time.Time, orderID, limit int64) ([]FuturesOrderData, error) {
var resp []FuturesOrderData
params := url.Values{}
rateLimit := cFuturesPairOrdersRate
@@ -1210,13 +1210,13 @@ func (b *Binance) GetAllFuturesOrders(ctx context.Context, symbol currency.Pair,
}
params.Set("symbol", symbolValue)
}
if pair != "" {
params.Set("pair", pair)
if !pair.IsEmpty() {
params.Set("pair", pair.String())
}
if orderID != 0 {
params.Set("orderID", strconv.FormatInt(orderID, 10))
}
if limit > 0 && limit <= 100 {
if limit > 0 {
params.Set("limit", strconv.FormatInt(limit, 10))
}
if !startTime.IsZero() && !endTime.IsZero() {
@@ -1242,7 +1242,7 @@ func (b *Binance) GetFuturesAccountInfo(ctx context.Context) (FuturesAccountInfo
}
// FuturesChangeInitialLeverage changes initial leverage for the account
func (b *Binance) FuturesChangeInitialLeverage(ctx context.Context, symbol currency.Pair, leverage int64) (FuturesLeverageData, error) {
func (b *Binance) FuturesChangeInitialLeverage(ctx context.Context, symbol currency.Pair, leverage float64) (FuturesLeverageData, error) {
var resp FuturesLeverageData
params := url.Values{}
symbolValue, err := b.FormatSymbol(symbol, asset.CoinMarginedFutures)
@@ -1253,7 +1253,7 @@ func (b *Binance) FuturesChangeInitialLeverage(ctx context.Context, symbol curre
if leverage < 1 || leverage > 125 {
return resp, errors.New("invalid leverage")
}
params.Set("leverage", strconv.FormatInt(leverage, 10))
params.Set("leverage", strconv.FormatFloat(leverage, 'f', -1, 64))
return resp, b.SendAuthHTTPRequest(ctx, exchange.RestCoinMargined, http.MethodPost, cfuturesChangeInitialLeverage, params, cFuturesDefaultRate, &resp)
}
@@ -1274,18 +1274,20 @@ func (b *Binance) FuturesChangeMarginType(ctx context.Context, symbol currency.P
}
// ModifyIsolatedPositionMargin changes margin for an isolated position
func (b *Binance) ModifyIsolatedPositionMargin(ctx context.Context, symbol currency.Pair, positionSide, changeType string, amount float64) (GenericAuthResponse, error) {
var resp GenericAuthResponse
func (b *Binance) ModifyIsolatedPositionMargin(ctx context.Context, symbol currency.Pair, positionSide, changeType string, amount float64) (FuturesMarginUpdatedResponse, error) {
var resp FuturesMarginUpdatedResponse
params := url.Values{}
symbolValue, err := b.FormatSymbol(symbol, asset.CoinMarginedFutures)
if err != nil {
return resp, err
}
params.Set("symbol", symbolValue)
if !common.StringDataCompare(validPositionSide, positionSide) {
return resp, errors.New("invalid positionSide")
if positionSide != "" {
if !common.StringDataCompare(validPositionSide, positionSide) {
return resp, errors.New("invalid positionSide")
}
params.Set("positionSide", positionSide)
}
params.Set("positionSide", positionSide)
cType, ok := validMarginChange[changeType]
if !ok {
return resp, errors.New("invalid changeType")
@@ -1323,15 +1325,19 @@ func (b *Binance) FuturesMarginChangeHistory(ctx context.Context, symbol currenc
}
// FuturesPositionsInfo gets futures positions info
// "pair" for coinmarginedfutures in GCT terms is the pair base
// eg ADAUSD_PERP the "pair" parameter is ADAUSD
func (b *Binance) FuturesPositionsInfo(ctx context.Context, marginAsset, pair string) ([]FuturesPositionInformation, error) {
var resp []FuturesPositionInformation
params := url.Values{}
if marginAsset != "" {
params.Set("marginAsset", marginAsset)
}
if pair != "" {
params.Set("pair", pair)
}
return resp, b.SendAuthHTTPRequest(ctx, exchange.RestCoinMargined, http.MethodGet, cfuturesPositionInfo, params, cFuturesDefaultRate, &resp)
}

View File

@@ -28,6 +28,7 @@ func TestMain(m *testing.M) {
if err != nil {
log.Fatal("Binance Setup() init error", err)
}
binanceConfig.API.AuthenticatedSupport = true
binanceConfig.API.Credentials.Key = apiKey
binanceConfig.API.Credentials.Secret = apiSecret

View File

@@ -15,8 +15,10 @@ 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/collateral"
"github.com/thrasher-corp/gocryptotrader/exchanges/fundingrate"
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
"github.com/thrasher-corp/gocryptotrader/exchanges/margin"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
@@ -56,7 +58,7 @@ func getTime() (start, end time.Time) {
}
tn := time.Now()
offset := time.Hour * 24 * 30
offset := time.Hour * 24 * 6
return tn.Add(-offset), tn
}
@@ -71,6 +73,7 @@ func TestStart(t *testing.T) {
if err != nil {
t.Fatal(err)
}
testWg.Wait()
}
@@ -216,7 +219,7 @@ func TestUFuturesOrderbook(t *testing.T) {
func TestURecentTrades(t *testing.T) {
t.Parallel()
_, err := b.URecentTrades(context.Background(), currency.NewPair(currency.BTC, currency.USDT), "", 5)
_, err := b.URecentTrades(context.Background(), currency.NewPair(currency.BTC, currency.USDT), "", 1000)
if err != nil {
t.Error(err)
}
@@ -972,7 +975,7 @@ func TestGetFuturesAllOpenOrders(t *testing.T) {
func TestGetAllFuturesOrders(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, b)
_, err := b.GetAllFuturesOrders(context.Background(), currency.NewPairWithDelimiter("BTCUSD", "PERP", "_"), "", time.Time{}, time.Time{}, 0, 2)
_, err := b.GetAllFuturesOrders(context.Background(), currency.NewPairWithDelimiter("BTCUSD", "PERP", "_"), currency.EMPTYPAIR, time.Time{}, time.Time{}, 0, 2)
if err != nil {
t.Error(err)
}
@@ -1035,7 +1038,7 @@ func TestFuturesMarginChangeHistory(t *testing.T) {
func TestFuturesPositionsInfo(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, b)
_, err := b.FuturesPositionsInfo(context.Background(), "BTCUSD_PERP", "")
_, err := b.FuturesPositionsInfo(context.Background(), "BTCUSD", "")
if err != nil {
t.Error(err)
}
@@ -1106,8 +1109,8 @@ func TestGetExchangeInfo(t *testing.T) {
}
if mockTests {
serverTime := time.Date(2022, 2, 25, 3, 50, 40, int(601*time.Millisecond), time.UTC)
if !info.Servertime.Equal(serverTime) {
t.Errorf("Expected %v, got %v", serverTime, info.Servertime)
if !info.ServerTime.Equal(serverTime) {
t.Errorf("Expected %v, got %v", serverTime, info.ServerTime)
}
}
}
@@ -2378,17 +2381,23 @@ func TestBinance_FormatExchangeKlineInterval(t *testing.T) {
func TestGetRecentTrades(t *testing.T) {
t.Parallel()
bAssets := b.GetAssetTypes(false)
for i := range bAssets {
cps, err := b.GetAvailablePairs(bAssets[i])
if err != nil {
t.Error(err)
}
_, err = b.GetRecentTrades(context.Background(),
cps[0], bAssets[i])
if err != nil {
t.Error(err)
}
pair := currency.NewPair(currency.BTC, currency.USDT)
_, err := b.GetRecentTrades(context.Background(),
pair, asset.Spot)
if err != nil {
t.Error(err)
}
_, err = b.GetRecentTrades(context.Background(),
pair, asset.USDTMarginedFutures)
if err != nil {
t.Error(err)
}
pair.Base = currency.NewCode("BTCUSD")
pair.Quote = currency.PERP
_, err = b.GetRecentTrades(context.Background(),
pair, asset.CoinMarginedFutures)
if err != nil {
t.Error(err)
}
}
@@ -2728,7 +2737,6 @@ func TestFetchSpotExchangeLimits(t *testing.T) {
func TestUpdateOrderExecutionLimits(t *testing.T) {
t.Parallel()
tests := map[asset.Item]currency.Pair{
asset.Spot: currency.NewPair(currency.BTC, currency.USDT),
asset.Margin: currency.NewPair(currency.ETH, currency.BTC),
@@ -2939,6 +2947,229 @@ func TestGetUserMarginInterestHistory(t *testing.T) {
}
}
func TestSetAssetsMode(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, b)
is, err := b.GetAssetsMode(context.Background())
if !errors.Is(err, nil) {
t.Errorf("received '%v', expected '%v'", err, nil)
}
err = b.SetAssetsMode(context.Background(), !is)
if !errors.Is(err, nil) {
t.Errorf("received '%v', expected '%v'", err, nil)
}
err = b.SetAssetsMode(context.Background(), is)
if !errors.Is(err, nil) {
t.Errorf("received '%v', expected '%v'", err, nil)
}
}
func TestGetAssetsMode(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, b)
_, err := b.GetAssetsMode(context.Background())
if !errors.Is(err, nil) {
t.Errorf("received '%v', expected '%v'", err, nil)
}
}
func TestGetCollateralMode(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, b, canManipulateRealOrders)
_, err := b.GetCollateralMode(context.Background(), asset.Spot)
if !errors.Is(err, asset.ErrNotSupported) {
t.Errorf("received '%v', expected '%v'", err, asset.ErrNotSupported)
}
_, err = b.GetCollateralMode(context.Background(), asset.CoinMarginedFutures)
if !errors.Is(err, asset.ErrNotSupported) {
t.Errorf("received '%v', expected '%v'", err, asset.ErrNotSupported)
}
_, err = b.GetCollateralMode(context.Background(), asset.USDTMarginedFutures)
if !errors.Is(err, nil) {
t.Errorf("received '%v', expected '%v'", err, nil)
}
}
func TestSetCollateralMode(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, b, canManipulateRealOrders)
err := b.SetCollateralMode(context.Background(), asset.Spot, collateral.SingleMode)
if !errors.Is(err, asset.ErrNotSupported) {
t.Errorf("received '%v', expected '%v'", err, asset.ErrNotSupported)
}
err = b.SetCollateralMode(context.Background(), asset.CoinMarginedFutures, collateral.SingleMode)
if !errors.Is(err, asset.ErrNotSupported) {
t.Errorf("received '%v', expected '%v'", err, asset.ErrNotSupported)
}
err = b.SetCollateralMode(context.Background(), asset.USDTMarginedFutures, collateral.MultiMode)
if !errors.Is(err, nil) {
t.Errorf("received '%v', expected '%v'", err, nil)
}
err = b.SetCollateralMode(context.Background(), asset.USDTMarginedFutures, collateral.PortfolioMode)
if !errors.Is(err, order.ErrCollateralInvalid) {
t.Errorf("received '%v', expected '%v'", err, order.ErrCollateralInvalid)
}
}
func TestChangePositionMargin(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, b, canManipulateRealOrders)
_, err := b.ChangePositionMargin(context.Background(), &margin.PositionChangeRequest{
Pair: currency.NewBTCUSDT(),
Asset: asset.USDTMarginedFutures,
MarginType: margin.Isolated,
OriginalAllocatedMargin: 1337,
NewAllocatedMargin: 1333337,
})
if err != nil {
t.Error(err)
}
}
func TestGetPositionSummary(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, b)
bb := currency.NewBTCUSDT()
_, err := b.GetFuturesPositionSummary(context.Background(), &order.PositionSummaryRequest{
Asset: asset.USDTMarginedFutures,
Pair: bb,
})
if err != nil {
t.Error(err)
}
bb.Quote = currency.BUSD
_, err = b.GetFuturesPositionSummary(context.Background(), &order.PositionSummaryRequest{
Asset: asset.USDTMarginedFutures,
Pair: bb,
})
if err != nil {
t.Error(err)
}
p, err := currency.NewPairFromString("BTCUSD_PERP")
if err != nil {
t.Fatal(err)
}
bb.Quote = currency.USD
_, err = b.GetFuturesPositionSummary(context.Background(), &order.PositionSummaryRequest{
Asset: asset.CoinMarginedFutures,
Pair: p,
UnderlyingPair: bb,
})
if err != nil {
t.Error(err)
}
_, err = b.GetFuturesPositionSummary(context.Background(), &order.PositionSummaryRequest{
Asset: asset.Spot,
Pair: p,
UnderlyingPair: bb,
})
if !errors.Is(err, asset.ErrNotSupported) {
t.Error(err)
}
}
func TestGetFuturesPositionOrders(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, b)
_, err := b.GetFuturesPositionOrders(context.Background(), &order.PositionsRequest{
Asset: asset.USDTMarginedFutures,
Pairs: []currency.Pair{currency.NewBTCUSDT()},
StartDate: time.Now().Add(-time.Hour * 24 * 70),
RespectOrderHistoryLimits: true,
})
if err != nil {
t.Error(err)
}
p, err := currency.NewPairFromString("ADAUSD_PERP")
if err != nil {
t.Fatal(err)
}
_, err = b.GetFuturesPositionOrders(context.Background(), &order.PositionsRequest{
Asset: asset.CoinMarginedFutures,
Pairs: []currency.Pair{p},
StartDate: time.Now().Add(time.Hour * 24 * -70),
RespectOrderHistoryLimits: true,
})
if err != nil {
t.Error(err)
}
}
func TestSetMarginType(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, b, canManipulateRealOrders)
err := b.SetMarginType(context.Background(), asset.USDTMarginedFutures, currency.NewPair(currency.BTC, currency.USDT), margin.Isolated)
if !errors.Is(err, nil) {
t.Error(err)
}
p, err := currency.NewPairFromString("BTCUSD_PERP")
if err != nil {
t.Fatal(err)
}
err = b.SetMarginType(context.Background(), asset.CoinMarginedFutures, p, margin.Isolated)
if !errors.Is(err, nil) {
t.Error(err)
}
err = b.SetMarginType(context.Background(), asset.Spot, currency.NewPair(currency.BTC, currency.USDT), margin.Isolated)
if !errors.Is(err, asset.ErrNotSupported) {
t.Error(err)
}
}
func TestGetLeverage(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, b)
_, err := b.GetLeverage(context.Background(), asset.USDTMarginedFutures, currency.NewBTCUSDT(), 0, order.UnknownSide)
if err != nil {
t.Error(err)
}
p, err := currency.NewPairFromString("BTCUSD_PERP")
if err != nil {
t.Fatal(err)
}
_, err = b.GetLeverage(context.Background(), asset.CoinMarginedFutures, p, 0, order.UnknownSide)
if err != nil {
t.Error(err)
}
_, err = b.GetLeverage(context.Background(), asset.Spot, currency.NewBTCUSDT(), 0, order.UnknownSide)
if !errors.Is(err, asset.ErrNotSupported) {
t.Error(err)
}
}
func TestSetLeverage(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, b, canManipulateRealOrders)
err := b.SetLeverage(context.Background(), asset.USDTMarginedFutures, currency.NewBTCUSDT(), margin.Multi, 5, order.UnknownSide)
if err != nil {
t.Error(err)
}
p, err := currency.NewPairFromString("BTCUSD_PERP")
if err != nil {
t.Fatal(err)
}
err = b.SetLeverage(context.Background(), asset.CoinMarginedFutures, p, margin.Multi, 5, order.UnknownSide)
if err != nil {
t.Error(err)
}
err = b.SetLeverage(context.Background(), asset.Spot, p, margin.Multi, 5, order.UnknownSide)
if !errors.Is(err, asset.ErrNotSupported) {
t.Error(err)
}
}
func TestGetCryptoLoansIncomeHistory(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, b)

View File

@@ -43,7 +43,7 @@ type ExchangeInfo struct {
Code int `json:"code"`
Msg string `json:"msg"`
Timezone string `json:"timezone"`
Servertime time.Time `json:"serverTime"`
ServerTime time.Time `json:"serverTime"`
RateLimits []struct {
RateLimitType string `json:"rateLimitType"`
Interval string `json:"interval"`

View File

@@ -57,11 +57,13 @@ const (
ufuturesModifyMargin = "/fapi/v1/positionMargin"
ufuturesMarginChangeHistory = "/fapi/v1/positionMargin/history"
ufuturesPositionInfo = "/fapi/v2/positionRisk"
ufuturesCommissionRate = "/fapi/v1/commissionRate"
ufuturesAccountTradeList = "/fapi/v1/userTrades"
ufuturesIncomeHistory = "/fapi/v1/income"
ufuturesNotionalBracket = "/fapi/v1/leverageBracket"
ufuturesUsersForceOrders = "/fapi/v1/forceOrders"
ufuturesADLQuantile = "/fapi/v1/adlQuantile"
uFuturesMultiAssetsMargin = "/fapi/v1/multiAssetsMargin"
)
// UServerTime gets the server time
@@ -168,7 +170,7 @@ func (b *Binance) URecentTrades(ctx context.Context, symbol currency.Pair, fromI
if fromID != "" {
params.Set("fromID", fromID)
}
if limit > 0 && limit < 1000 {
if limit > 0 {
params.Set("limit", strconv.FormatInt(limit, 10))
}
return resp, b.SendHTTPRequest(ctx, exchange.RestUSDTMargined, ufuturesRecentTrades+params.Encode(), uFuturesDefaultRate, &resp)
@@ -186,7 +188,7 @@ func (b *Binance) UFuturesHistoricalTrades(ctx context.Context, symbol currency.
if fromID != "" {
params.Set("fromID", fromID)
}
if limit > 0 && limit < 1000 {
if limit > 0 {
params.Set("limit", strconv.FormatInt(limit, 10))
}
return resp, b.SendAuthHTTPRequest(ctx, exchange.RestUSDTMargined, http.MethodGet, ufuturesHistoricalTrades, params, uFuturesHistoricalTradesRate, &resp)
@@ -204,7 +206,7 @@ func (b *Binance) UCompressedTrades(ctx context.Context, symbol currency.Pair, f
if fromID != "" {
params.Set("fromID", fromID)
}
if limit > 0 && limit < 1000 {
if limit > 0 {
params.Set("limit", strconv.FormatInt(limit, 10))
}
if !startTime.IsZero() && !endTime.IsZero() {
@@ -229,7 +231,7 @@ func (b *Binance) UKlineData(ctx context.Context, symbol currency.Pair, interval
return nil, errors.New("invalid interval")
}
params.Set("interval", interval)
if limit > 0 && limit <= 1500 {
if limit > 0 {
params.Set("limit", strconv.FormatInt(limit, 10))
}
if !startTime.IsZero() && !endTime.IsZero() {
@@ -489,7 +491,7 @@ func (b *Binance) UOpenInterestStats(ctx context.Context, symbol currency.Pair,
return resp, errors.New("invalid period")
}
params.Set("period", period)
if limit > 0 && limit < 1000 {
if limit > 0 {
params.Set("limit", strconv.FormatInt(limit, 10))
}
if !startTime.IsZero() && !endTime.IsZero() {
@@ -515,7 +517,7 @@ func (b *Binance) UTopAcccountsLongShortRatio(ctx context.Context, symbol curren
return resp, errors.New("invalid period")
}
params.Set("period", period)
if limit > 0 && limit < 500 {
if limit > 0 {
params.Set("limit", strconv.FormatInt(limit, 10))
}
if !startTime.IsZero() && !endTime.IsZero() {
@@ -541,7 +543,7 @@ func (b *Binance) UTopPostionsLongShortRatio(ctx context.Context, symbol currenc
return resp, errors.New("invalid period")
}
params.Set("period", period)
if limit > 0 && limit < 500 {
if limit > 0 {
params.Set("limit", strconv.FormatInt(limit, 10))
}
if !startTime.IsZero() && !endTime.IsZero() {
@@ -567,7 +569,7 @@ func (b *Binance) UGlobalLongShortRatio(ctx context.Context, symbol currency.Pai
return resp, errors.New("invalid period")
}
params.Set("period", period)
if limit > 0 && limit < 500 {
if limit > 0 {
params.Set("limit", strconv.FormatInt(limit, 10))
}
if !startTime.IsZero() && !endTime.IsZero() {
@@ -593,7 +595,7 @@ func (b *Binance) UTakerBuySellVol(ctx context.Context, symbol currency.Pair, pe
return resp, errors.New("invalid period")
}
params.Set("period", period)
if limit > 0 && limit < 500 {
if limit > 0 {
params.Set("limit", strconv.FormatInt(limit, 10))
}
if !startTime.IsZero() && !endTime.IsZero() {
@@ -859,14 +861,13 @@ func (b *Binance) UAllAccountOrders(ctx context.Context, symbol currency.Pair, o
if orderID != 0 {
params.Set("orderId", strconv.FormatInt(orderID, 10))
}
if limit > 0 && limit < 1000 {
if limit > 0 {
params.Set("limit", strconv.FormatInt(limit, 10))
}
if !startTime.IsZero() && !endTime.IsZero() {
if startTime.After(endTime) {
return resp, errors.New("startTime cannot be after endTime")
}
if !startTime.IsZero() {
params.Set("startTime", strconv.FormatInt(startTime.UnixMilli(), 10))
}
if !endTime.IsZero() {
params.Set("endTime", strconv.FormatInt(endTime.UnixMilli(), 10))
}
return resp, b.SendAuthHTTPRequest(ctx, exchange.RestUSDTMargined, http.MethodGet, ufuturesAllOrders, params, uFuturesGetAllOrdersRate, &resp)
@@ -885,7 +886,7 @@ func (b *Binance) UAccountInformationV2(ctx context.Context) (UAccountInformatio
}
// UChangeInitialLeverageRequest sends a request to change account's initial leverage
func (b *Binance) UChangeInitialLeverageRequest(ctx context.Context, symbol currency.Pair, leverage int64) (UChangeInitialLeverage, error) {
func (b *Binance) UChangeInitialLeverageRequest(ctx context.Context, symbol currency.Pair, leverage float64) (UChangeInitialLeverage, error) {
var resp UChangeInitialLeverage
params := url.Values{}
symbolValue, err := b.FormatSymbol(symbol, asset.USDTMarginedFutures)
@@ -896,7 +897,7 @@ func (b *Binance) UChangeInitialLeverageRequest(ctx context.Context, symbol curr
if leverage < 1 || leverage > 125 {
return resp, errors.New("invalid leverage")
}
params.Set("leverage", strconv.FormatInt(leverage, 10))
params.Set("leverage", strconv.FormatFloat(leverage, 'f', -1, 64))
return resp, b.SendAuthHTTPRequest(ctx, exchange.RestUSDTMargined, http.MethodPost, ufuturesChangeInitialLeverage, params, uFuturesDefaultRate, &resp)
}
@@ -924,16 +925,14 @@ func (b *Binance) UModifyIsolatedPositionMarginReq(ctx context.Context, symbol c
return resp, err
}
params.Set("symbol", symbolValue)
if positionSide != "" {
if !common.StringDataCompare(validPositionSide, positionSide) {
return resp, errors.New("invalid margin changeType")
}
}
cType, ok := validMarginChange[changeType]
if !ok {
return resp, errors.New("invalid margin changeType")
}
params.Set("type", strconv.FormatInt(cType, 10))
if positionSide != "" {
params.Set("positionSide", positionSide)
}
params.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64))
return resp, b.SendAuthHTTPRequest(ctx, exchange.RestUSDTMargined, http.MethodPost, ufuturesModifyMargin, params, uFuturesDefaultRate, &resp)
}
@@ -952,7 +951,7 @@ func (b *Binance) UPositionMarginChangeHistory(ctx context.Context, symbol curre
return resp, errors.New("invalid margin changeType")
}
params.Set("type", strconv.FormatInt(cType, 10))
if limit > 0 && limit < 500 {
if limit > 0 {
params.Set("limit", strconv.FormatInt(limit, 10))
}
if !startTime.IsZero() && !endTime.IsZero() {
@@ -979,6 +978,20 @@ func (b *Binance) UPositionsInfoV2(ctx context.Context, symbol currency.Pair) ([
return resp, b.SendAuthHTTPRequest(ctx, exchange.RestUSDTMargined, http.MethodGet, ufuturesPositionInfo, params, uFuturesDefaultRate, &resp)
}
// UGetCommissionRates returns the commission rates for USDTMarginedFutures
func (b *Binance) UGetCommissionRates(ctx context.Context, symbol currency.Pair) ([]UPositionInformationV2, error) {
var resp []UPositionInformationV2
params := url.Values{}
if !symbol.IsEmpty() {
symbolValue, err := b.FormatSymbol(symbol, asset.USDTMarginedFutures)
if err != nil {
return resp, err
}
params.Set("symbol", symbolValue)
}
return resp, b.SendAuthHTTPRequest(ctx, exchange.RestUSDTMargined, http.MethodGet, ufuturesCommissionRate, params, uFuturesDefaultRate, &resp)
}
// UAccountTradesHistory gets account's trade history data for USDTMarginedFutures
func (b *Binance) UAccountTradesHistory(ctx context.Context, symbol currency.Pair, fromID string, limit int64, startTime, endTime time.Time) ([]UAccountTradeHistory, error) {
var resp []UAccountTradeHistory
@@ -991,7 +1004,7 @@ func (b *Binance) UAccountTradesHistory(ctx context.Context, symbol currency.Pai
if fromID != "" {
params.Set("fromID", fromID)
}
if limit > 0 && limit < 1000 {
if limit > 0 {
params.Set("limit", strconv.FormatInt(limit, 10))
}
if !startTime.IsZero() && !endTime.IsZero() {
@@ -1019,7 +1032,7 @@ func (b *Binance) UAccountIncomeHistory(ctx context.Context, symbol currency.Pai
}
params.Set("incomeType", incomeType)
}
if limit > 0 && limit < 1000 {
if limit > 0 {
params.Set("limit", strconv.FormatInt(limit, 10))
}
if !startTime.IsZero() && !endTime.IsZero() {
@@ -1079,7 +1092,7 @@ func (b *Binance) UAccountForcedOrders(ctx context.Context, symbol currency.Pair
}
params.Set("autoCloseType", autoCloseType)
}
if limit > 0 && limit < 1000 {
if limit > 0 {
params.Set("limit", strconv.FormatInt(limit, 10))
}
if !startTime.IsZero() && !endTime.IsZero() {
@@ -1140,3 +1153,19 @@ func (b *Binance) FetchUSDTMarginExchangeLimits(ctx context.Context) ([]order.Mi
}
return limits, nil
}
// SetAssetsMode sets the current asset margin type, true for multi, false for single
func (b *Binance) SetAssetsMode(ctx context.Context, multiMargin bool) error {
params := url.Values{
"multiAssetsMargin": {strconv.FormatBool(multiMargin)},
}
return b.SendAuthHTTPRequest(ctx, exchange.RestUSDTMargined, http.MethodPost, uFuturesMultiAssetsMargin, params, uFuturesDefaultRate, nil)
}
// GetAssetsMode returns the current asset margin type, true for multi, false for single
func (b *Binance) GetAssetsMode(ctx context.Context) (bool, error) {
var result struct {
MultiAssetsMargin bool `json:"multiAssetsMargin"`
}
return result.MultiAssetsMargin, b.SendAuthHTTPRequest(ctx, exchange.RestUSDTMargined, http.MethodGet, uFuturesMultiAssetsMargin, nil, uFuturesDefaultRate, &result)
}

View File

@@ -17,9 +17,11 @@ import (
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/account"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/collateral"
"github.com/thrasher-corp/gocryptotrader/exchanges/deposit"
"github.com/thrasher-corp/gocryptotrader/exchanges/fundingrate"
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
"github.com/thrasher-corp/gocryptotrader/exchanges/margin"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-corp/gocryptotrader/exchanges/protocol"
@@ -120,8 +122,9 @@ func (b *Binance) SetDefaults() {
}
b.Features = exchange.Features{
Supports: exchange.FeaturesSupported{
REST: true,
Websocket: true,
REST: true,
Websocket: true,
MaximumOrderHistory: kline.OneDay.Duration() * 7,
RESTCapabilities: protocol.Features{
TickerBatching: true,
TickerFetching: true,
@@ -165,6 +168,9 @@ func (b *Binance) SetDefaults() {
Intervals: true,
},
FuturesCapabilities: exchange.FuturesCapabilities{
Positions: true,
Leverage: true,
CollateralMode: true,
FundingRates: true,
FundingRateFrequency: kline.EightHour.Duration(),
},
@@ -972,6 +978,9 @@ func (b *Binance) SubmitOrder(ctx context.Context, s *order.Submit) (*order.Subm
var orderID string
status := order.New
var trades []order.TradeHistory
if s.Leverage != 0 && s.Leverage != 1 {
return nil, fmt.Errorf("%w received '%v'", order.ErrSubmitLeverageNotSupported, s.Leverage)
}
switch s.AssetType {
case asset.Spot, asset.Margin:
var sideType string
@@ -1618,7 +1627,7 @@ func (b *Binance) GetOrderHistory(ctx context.Context, req *order.MultiOrderRequ
return nil, fmt.Errorf("can only fetch orders 30 days out")
}
orderHistory, err = b.GetAllFuturesOrders(ctx,
req.Pairs[i], "", req.StartTime, req.EndTime, 0, 0)
req.Pairs[i], currency.EMPTYPAIR, req.StartTime, req.EndTime, 0, 0)
if err != nil {
return nil, err
}
@@ -1628,7 +1637,7 @@ func (b *Binance) GetOrderHistory(ctx context.Context, req *order.MultiOrderRequ
return nil, err
}
orderHistory, err = b.GetAllFuturesOrders(ctx,
req.Pairs[i], "", time.Time{}, time.Time{}, fromID, 0)
req.Pairs[i], currency.EMPTYPAIR, time.Time{}, time.Time{}, fromID, 0)
if err != nil {
return nil, err
}
@@ -2051,7 +2060,7 @@ func (b *Binance) GetServerTime(ctx context.Context, ai asset.Item) (time.Time,
if err != nil {
return time.Time{}, err
}
return info.Servertime, nil
return info.ServerTime, nil
case asset.CoinMarginedFutures:
info, err := b.FuturesExchangeInfo(ctx)
if err != nil {
@@ -2260,3 +2269,568 @@ func (b *Binance) IsPerpetualFutureCurrency(a asset.Item, cp currency.Pair) (boo
}
return false, nil
}
// SetCollateralMode sets the account's collateral mode for the asset type
func (b *Binance) SetCollateralMode(ctx context.Context, a asset.Item, collateralMode collateral.Mode) error {
if a != asset.USDTMarginedFutures {
return fmt.Errorf("%w %v", asset.ErrNotSupported, a)
}
if collateralMode != collateral.MultiMode && collateralMode != collateral.SingleMode {
return fmt.Errorf("%w %v", order.ErrCollateralInvalid, collateralMode)
}
return b.SetAssetsMode(ctx, collateralMode == collateral.MultiMode)
}
// GetCollateralMode returns the account's collateral mode for the asset type
func (b *Binance) GetCollateralMode(ctx context.Context, a asset.Item) (collateral.Mode, error) {
if a != asset.USDTMarginedFutures {
return collateral.UnknownMode, fmt.Errorf("%w %v", asset.ErrNotSupported, a)
}
isMulti, err := b.GetAssetsMode(ctx)
if err != nil {
return collateral.UnknownMode, err
}
if isMulti {
return collateral.MultiMode, nil
}
return collateral.SingleMode, nil
}
// SetMarginType sets the default margin type for when opening a new position
func (b *Binance) SetMarginType(ctx context.Context, item asset.Item, pair currency.Pair, tp margin.Type) error {
if item != asset.USDTMarginedFutures && item != asset.CoinMarginedFutures {
return fmt.Errorf("%w %v", asset.ErrNotSupported, item)
}
if !tp.Valid() {
return fmt.Errorf("%w %v", margin.ErrInvalidMarginType, tp)
}
mt, err := b.marginTypeToString(tp)
if err != nil {
return err
}
switch item {
case asset.CoinMarginedFutures:
_, err = b.FuturesChangeMarginType(ctx, pair, mt)
case asset.USDTMarginedFutures:
err = b.UChangeInitialMarginType(ctx, pair, mt)
}
if err != nil {
return err
}
return nil
}
// ChangePositionMargin will modify a position/currencies margin parameters
func (b *Binance) ChangePositionMargin(ctx context.Context, req *margin.PositionChangeRequest) (*margin.PositionChangeResponse, error) {
if req == nil {
return nil, fmt.Errorf("%w PositionChangeRequest", common.ErrNilPointer)
}
if req.Asset != asset.USDTMarginedFutures && req.Asset != asset.CoinMarginedFutures {
return nil, fmt.Errorf("%w %v", asset.ErrNotSupported, req.Asset)
}
if req.NewAllocatedMargin == 0 {
return nil, fmt.Errorf("%w %v %v", margin.ErrNewAllocatedMarginRequired, req.Asset, req.Pair)
}
if req.OriginalAllocatedMargin == 0 {
return nil, fmt.Errorf("%w %v %v", margin.ErrOriginalPositionMarginRequired, req.Asset, req.Pair)
}
if req.MarginType == margin.Multi {
return nil, fmt.Errorf("%w %v %v", margin.ErrMarginTypeUnsupported, req.Asset, req.Pair)
}
marginType := "add"
if req.NewAllocatedMargin < req.OriginalAllocatedMargin {
marginType = "reduce"
}
var side string
if req.MarginSide != "" {
side = req.MarginSide
}
var err error
switch req.Asset {
case asset.CoinMarginedFutures:
_, err = b.ModifyIsolatedPositionMargin(ctx, req.Pair, side, marginType, req.NewAllocatedMargin)
case asset.USDTMarginedFutures:
_, err = b.UModifyIsolatedPositionMarginReq(ctx, req.Pair, side, marginType, req.NewAllocatedMargin)
}
if err != nil {
return nil, err
}
return &margin.PositionChangeResponse{
Exchange: b.Name,
Pair: req.Pair,
Asset: req.Asset,
MarginType: req.MarginType,
AllocatedMargin: req.NewAllocatedMargin,
}, nil
}
// marginTypeToString converts the GCT margin type to Binance's string
func (b *Binance) marginTypeToString(mt margin.Type) (string, error) {
switch mt {
case margin.Isolated:
return margin.Isolated.Upper(), nil
case margin.Multi:
return "CROSSED", nil
}
return "", fmt.Errorf("%w %v", margin.ErrInvalidMarginType, mt)
}
// GetFuturesPositionSummary returns the account's position summary for the asset type and pair
// it can be used to calculate potential positions
func (b *Binance) GetFuturesPositionSummary(ctx context.Context, req *order.PositionSummaryRequest) (*order.PositionSummary, error) {
if req == nil {
return nil, fmt.Errorf("%w GetFuturesPositionSummary", common.ErrNilPointer)
}
if req.CalculateOffline {
return nil, common.ErrCannotCalculateOffline
}
fPair, err := b.FormatExchangeCurrency(req.Pair, req.Asset)
if err != nil {
return nil, err
}
switch req.Asset {
case asset.USDTMarginedFutures:
ai, err := b.UAccountInformationV2(ctx)
if err != nil {
return nil, err
}
collateralMode := collateral.SingleMode
if ai.MultiAssetsMargin {
collateralMode = collateral.MultiMode
}
var accountPosition *UPosition
var leverage, maintenanceMargin, initialMargin,
liquidationPrice, markPrice, positionSize,
collateralTotal, collateralUsed, collateralAvailable,
pnl, openPrice, isolatedMargin float64
for i := range ai.Positions {
if ai.Positions[i].Symbol != fPair.String() {
continue
}
accountPosition = &ai.Positions[i]
break
}
if accountPosition == nil {
return nil, fmt.Errorf("%w %v %v position info", currency.ErrCurrencyNotFound, req.Asset, req.Pair)
}
var usdtAsset, busdAsset *UAsset
for i := range ai.Assets {
if usdtAsset != nil && busdAsset != nil {
break
}
if strings.EqualFold(ai.Assets[i].Asset, currency.USDT.Item.Symbol) {
usdtAsset = &ai.Assets[i]
continue
}
if strings.EqualFold(ai.Assets[i].Asset, currency.BUSD.Item.Symbol) {
busdAsset = &ai.Assets[i]
}
}
if usdtAsset == nil && busdAsset == nil {
return nil, fmt.Errorf("%w %v %v asset info", currency.ErrCurrencyNotFound, req.Asset, req.Pair)
}
leverage = accountPosition.Leverage
openPrice = accountPosition.EntryPrice
maintenanceMargin = accountPosition.MaintenanceMargin
initialMargin = accountPosition.PositionInitialMargin
marginType := margin.Multi
if accountPosition.Isolated {
marginType = margin.Isolated
}
var c currency.Code
if collateralMode == collateral.SingleMode {
var collateralAsset *UAsset
if strings.Contains(accountPosition.Symbol, usdtAsset.Asset) {
collateralAsset = usdtAsset
} else if strings.Contains(accountPosition.Symbol, busdAsset.Asset) {
collateralAsset = busdAsset
}
collateralTotal = collateralAsset.WalletBalance
collateralAvailable = collateralAsset.AvailableBalance
pnl = collateralAsset.UnrealizedProfit
c = currency.NewCode(collateralAsset.Asset)
if marginType == margin.Multi {
isolatedMargin = collateralAsset.CrossUnPnl
collateralUsed = collateralTotal + isolatedMargin
} else {
isolatedMargin = accountPosition.IsolatedWallet
collateralUsed = isolatedMargin
}
} else if collateralMode == collateral.MultiMode {
collateralTotal = ai.TotalWalletBalance
collateralUsed = ai.TotalWalletBalance - ai.AvailableBalance
collateralAvailable = ai.AvailableBalance
pnl = accountPosition.UnrealisedProfit
}
var maintenanceMarginFraction decimal.Decimal
if collateralTotal != 0 {
maintenanceMarginFraction = decimal.NewFromFloat(maintenanceMargin).Div(decimal.NewFromFloat(collateralTotal)).Mul(decimal.NewFromInt32(100))
}
// binance so fun, some prices exclusively here
positionsInfo, err := b.UPositionsInfoV2(ctx, fPair)
if err != nil {
return nil, err
}
var relevantPosition *UPositionInformationV2
for i := range positionsInfo {
if positionsInfo[i].Symbol != fPair.String() {
continue
}
relevantPosition = &positionsInfo[i]
}
if relevantPosition == nil {
return nil, fmt.Errorf("%w %v %v", order.ErrNoPositionsFound, req.Asset, req.Pair)
}
return &order.PositionSummary{
Pair: req.Pair,
Asset: req.Asset,
MarginType: marginType,
CollateralMode: collateralMode,
Currency: c,
IsolatedMargin: decimal.NewFromFloat(isolatedMargin),
Leverage: decimal.NewFromFloat(leverage),
MaintenanceMarginRequirement: decimal.NewFromFloat(maintenanceMargin),
InitialMarginRequirement: decimal.NewFromFloat(initialMargin),
EstimatedLiquidationPrice: decimal.NewFromFloat(liquidationPrice),
CollateralUsed: decimal.NewFromFloat(collateralUsed),
MarkPrice: decimal.NewFromFloat(markPrice),
CurrentSize: decimal.NewFromFloat(positionSize),
AverageOpenPrice: decimal.NewFromFloat(openPrice),
PositionPNL: decimal.NewFromFloat(pnl),
MaintenanceMarginFraction: maintenanceMarginFraction,
FreeCollateral: decimal.NewFromFloat(collateralAvailable),
TotalCollateral: decimal.NewFromFloat(collateralTotal),
NotionalSize: decimal.NewFromFloat(positionSize).Mul(decimal.NewFromFloat(markPrice)),
}, nil
case asset.CoinMarginedFutures:
ai, err := b.GetFuturesAccountInfo(ctx)
if err != nil {
return nil, err
}
collateralMode := collateral.SingleMode
var leverage, maintenanceMargin, initialMargin,
liquidationPrice, markPrice, positionSize,
collateralTotal, collateralUsed, collateralAvailable,
pnl, openPrice, isolatedMargin float64
var accountPosition *FuturesAccountInformationPosition
for i := range ai.Positions {
if ai.Positions[i].Symbol != fPair.String() {
continue
}
accountPosition = &ai.Positions[i]
break
}
if accountPosition == nil {
return nil, fmt.Errorf("%w %v %v position info", currency.ErrCurrencyNotFound, req.Asset, req.Pair)
}
var accountAsset *FuturesAccountAsset
for i := range ai.Assets {
// TODO: utilise contract data to discern the underlying currency
// instead of having a user provide it
if ai.Assets[i].Asset != req.UnderlyingPair.Base.Upper().String() {
continue
}
accountAsset = &ai.Assets[i]
break
}
if accountAsset == nil {
return nil, fmt.Errorf("could not get asset info: %w %v %v, please verify underlying pair: '%v'", currency.ErrCurrencyNotFound, req.Asset, req.Pair, req.UnderlyingPair)
}
leverage = accountPosition.Leverage
openPrice = accountPosition.EntryPrice
maintenanceMargin = accountPosition.MaintenanceMargin
initialMargin = accountPosition.PositionInitialMargin
marginType := margin.Multi
if accountPosition.Isolated {
marginType = margin.Isolated
}
collateralTotal = accountAsset.WalletBalance
frozenBalance := decimal.NewFromFloat(accountAsset.WalletBalance).Sub(decimal.NewFromFloat(accountAsset.AvailableBalance))
collateralAvailable = accountAsset.AvailableBalance
pnl = accountAsset.UnrealizedProfit
if marginType == margin.Multi {
isolatedMargin = accountAsset.CrossUnPNL
collateralUsed = collateralTotal + isolatedMargin
} else {
isolatedMargin = accountPosition.IsolatedWallet
collateralUsed = isolatedMargin
}
// binance so fun, some prices exclusively here
positionsInfo, err := b.FuturesPositionsInfo(ctx, "", req.Pair.Base.String())
if err != nil {
return nil, err
}
if len(positionsInfo) == 0 {
return nil, fmt.Errorf("%w %v", order.ErrNoPositionsFound, fPair)
}
var relevantPosition *FuturesPositionInformation
for i := range positionsInfo {
if positionsInfo[i].Symbol != fPair.String() {
continue
}
relevantPosition = &positionsInfo[i]
}
if relevantPosition == nil {
return nil, fmt.Errorf("%w %v %v", order.ErrNoPositionsFound, req.Asset, req.Pair)
}
liquidationPrice = relevantPosition.LiquidationPrice
markPrice = relevantPosition.MarkPrice
positionSize = relevantPosition.PositionAmount
var mmf, tc decimal.Decimal
if collateralTotal != 0 {
tc = decimal.NewFromFloat(collateralTotal)
mmf = decimal.NewFromFloat(maintenanceMargin).Div(tc).Mul(decimal.NewFromInt(100))
}
return &order.PositionSummary{
Pair: req.Pair,
Asset: req.Asset,
MarginType: marginType,
CollateralMode: collateralMode,
Currency: currency.NewCode(accountAsset.Asset),
IsolatedMargin: decimal.NewFromFloat(isolatedMargin),
NotionalSize: decimal.NewFromFloat(positionSize).Mul(decimal.NewFromFloat(markPrice)),
Leverage: decimal.NewFromFloat(leverage),
MaintenanceMarginRequirement: decimal.NewFromFloat(maintenanceMargin),
InitialMarginRequirement: decimal.NewFromFloat(initialMargin),
EstimatedLiquidationPrice: decimal.NewFromFloat(liquidationPrice),
CollateralUsed: decimal.NewFromFloat(collateralUsed),
MarkPrice: decimal.NewFromFloat(markPrice),
CurrentSize: decimal.NewFromFloat(positionSize),
AverageOpenPrice: decimal.NewFromFloat(openPrice),
PositionPNL: decimal.NewFromFloat(pnl),
MaintenanceMarginFraction: mmf,
FreeCollateral: decimal.NewFromFloat(collateralAvailable),
TotalCollateral: tc,
FrozenBalance: frozenBalance,
}, nil
default:
return nil, fmt.Errorf("%w %v", asset.ErrNotSupported, req.Asset)
}
}
// GetFuturesPositionOrders returns the orders for futures positions
func (b *Binance) GetFuturesPositionOrders(ctx context.Context, req *order.PositionsRequest) ([]order.PositionResponse, error) {
if req == nil {
return nil, fmt.Errorf("%w GetFuturesPositionOrders", common.ErrNilPointer)
}
if len(req.Pairs) == 0 {
return nil, currency.ErrCurrencyPairsEmpty
}
if time.Since(req.StartDate) > b.Features.Supports.MaximumOrderHistory+time.Hour {
if req.RespectOrderHistoryLimits {
req.StartDate = time.Now().Add(-b.Features.Supports.MaximumOrderHistory)
} else {
return nil, fmt.Errorf("%w max lookup %v", order.ErrOrderHistoryTooLarge, time.Now().Add(-b.Features.Supports.MaximumOrderHistory))
}
}
if req.EndDate.IsZero() {
req.EndDate = time.Now()
}
var resp []order.PositionResponse
sd := req.StartDate
switch req.Asset {
case asset.USDTMarginedFutures:
var orderLimit = 1000
for x := range req.Pairs {
fPair, err := b.FormatExchangeCurrency(req.Pairs[x], req.Asset)
if err != nil {
return nil, err
}
result, err := b.UPositionsInfoV2(ctx, fPair)
if err != nil {
return nil, err
}
for y := range result {
currencyPosition := order.PositionResponse{
Asset: req.Asset,
Pair: req.Pairs[x],
}
for {
var orders []UFuturesOrderData
orders, err = b.UAllAccountOrders(ctx, fPair, 0, int64(orderLimit), sd, req.EndDate)
if err != nil {
return nil, err
}
for i := range orders {
if orders[i].Time.After(req.EndDate) {
continue
}
orderVars := compatibleOrderVars(orders[i].Side, orders[i].Status, orders[i].OrderType)
var mt margin.Type
mt, err = margin.StringToMarginType(result[y].MarginType)
if err != nil {
if !errors.Is(err, margin.ErrInvalidMarginType) {
return nil, err
}
}
currencyPosition.Orders = append(currencyPosition.Orders, order.Detail{
ReduceOnly: orders[i].ClosePosition,
Price: orders[i].Price,
Amount: orders[i].ExecutedQty,
TriggerPrice: orders[i].ActivatePrice,
AverageExecutedPrice: orders[i].AvgPrice,
ExecutedAmount: orders[i].ExecutedQty,
RemainingAmount: orders[i].OrigQty - orders[i].ExecutedQty,
CostAsset: req.Pairs[x].Quote,
Leverage: result[y].Leverage,
Exchange: b.Name,
OrderID: strconv.FormatInt(orders[i].OrderID, 10),
ClientOrderID: orders[i].ClientOrderID,
Type: orderVars.OrderType,
Side: orderVars.Side,
Status: orderVars.Status,
AssetType: asset.USDTMarginedFutures,
Date: orders[i].Time,
LastUpdated: orders[i].UpdateTime,
Pair: req.Pairs[x],
MarginType: mt,
})
}
if len(orders) < orderLimit {
break
}
sd = currencyPosition.Orders[len(currencyPosition.Orders)-1].Date
}
resp = append(resp, currencyPosition)
}
}
case asset.CoinMarginedFutures:
var orderLimit = 100
for x := range req.Pairs {
fPair, err := b.FormatExchangeCurrency(req.Pairs[x], req.Asset)
if err != nil {
return nil, err
}
// "pair" for coinmarginedfutures is the pair.Base
// eg ADAUSD_PERP the pair is ADAUSD
result, err := b.FuturesPositionsInfo(ctx, "", fPair.Base.String())
if err != nil {
return nil, err
}
currencyPosition := order.PositionResponse{
Asset: req.Asset,
Pair: req.Pairs[x],
}
for y := range result {
if result[y].PositionAmount == 0 {
continue
}
for {
var orders []FuturesOrderData
orders, err = b.GetAllFuturesOrders(ctx, fPair, currency.EMPTYPAIR, sd, req.EndDate, 0, int64(orderLimit))
if err != nil {
return nil, err
}
for i := range orders {
if orders[i].Time.After(req.EndDate) {
continue
}
var orderPair currency.Pair
orderPair, err = currency.NewPairFromString(orders[i].Pair)
if err != nil {
return nil, err
}
orderVars := compatibleOrderVars(orders[i].Side, orders[i].Status, orders[i].OrderType)
var mt margin.Type
mt, err = margin.StringToMarginType(result[y].MarginType)
if err != nil {
if !errors.Is(err, margin.ErrInvalidMarginType) {
return nil, err
}
}
currencyPosition.Orders = append(currencyPosition.Orders, order.Detail{
ReduceOnly: orders[i].ClosePosition,
Price: orders[i].Price,
Amount: orders[i].ExecutedQty,
TriggerPrice: orders[i].ActivatePrice,
AverageExecutedPrice: orders[i].AvgPrice,
ExecutedAmount: orders[i].ExecutedQty,
RemainingAmount: orders[i].OrigQty - orders[i].ExecutedQty,
Leverage: result[y].Leverage,
CostAsset: orderPair.Base,
Exchange: b.Name,
OrderID: strconv.FormatInt(orders[i].OrderID, 10),
ClientOrderID: orders[i].ClientOrderID,
Type: orderVars.OrderType,
Side: orderVars.Side,
Status: orderVars.Status,
AssetType: asset.CoinMarginedFutures,
Date: orders[i].Time,
LastUpdated: orders[i].UpdateTime,
Pair: req.Pairs[x],
MarginType: mt,
})
}
if len(orders) < orderLimit {
break
}
sd = currencyPosition.Orders[len(currencyPosition.Orders)-1].Date
}
resp = append(resp, currencyPosition)
}
}
default:
return nil, fmt.Errorf("%w %v", asset.ErrNotSupported, req.Asset)
}
return resp, nil
}
// SetLeverage sets the account's initial leverage for the asset type and pair
func (b *Binance) SetLeverage(ctx context.Context, item asset.Item, pair currency.Pair, _ margin.Type, amount float64, _ order.Side) error {
switch item {
case asset.USDTMarginedFutures:
_, err := b.UChangeInitialLeverageRequest(ctx, pair, amount)
return err
case asset.CoinMarginedFutures:
_, err := b.FuturesChangeInitialLeverage(ctx, pair, amount)
return err
default:
return fmt.Errorf("%w %v", asset.ErrNotSupported, item)
}
}
// GetLeverage gets the account's initial leverage for the asset type and pair
func (b *Binance) GetLeverage(ctx context.Context, item asset.Item, pair currency.Pair, _ margin.Type, _ order.Side) (float64, error) {
if pair.IsEmpty() {
return -1, currency.ErrCurrencyPairEmpty
}
switch item {
case asset.USDTMarginedFutures:
resp, err := b.UPositionsInfoV2(ctx, pair)
if err != nil {
return -1, err
}
if len(resp) == 0 {
return -1, fmt.Errorf("%w %v %v", order.ErrPositionNotFound, item, pair)
}
// leverage is the same across positions
return resp[0].Leverage, nil
case asset.CoinMarginedFutures:
resp, err := b.FuturesPositionsInfo(ctx, "", pair.Base.String())
if err != nil {
return -1, err
}
if len(resp) == 0 {
return -1, fmt.Errorf("%w %v %v", order.ErrPositionNotFound, item, pair)
}
// leverage is the same across positions
return resp[0].Leverage, nil
default:
return -1, fmt.Errorf("%w %v", asset.ErrNotSupported, item)
}
}

View File

@@ -359,12 +359,12 @@ type FuturesAccountBalanceData struct {
UpdateTime int64 `json:"updateTime"`
}
// FuturesAccountInformationPositions holds account position data
type FuturesAccountInformationPositions struct {
// FuturesAccountInformationPosition holds account position data
type FuturesAccountInformationPosition struct {
Symbol string `json:"symbol"`
Amount float64 `json:"positionAmt,string"`
InitialMargin float64 `json:"initialMargin,string"`
MaintMargin float64 `json:"maintMargin,string"`
MaintenanceMargin float64 `json:"maintMargin,string"`
UnrealizedProfit float64 `json:"unrealizedProfit,string"`
PositionInitialMargin float64 `json:"positionInitialMargin,string"`
OpenOrderInitialMargin float64 `json:"openOrderInitialMargin,string"`
@@ -380,26 +380,29 @@ type FuturesAccountInformationPositions struct {
// FuturesAccountInformation stores account information for futures account
type FuturesAccountInformation struct {
Assets []struct {
Asset string `json:"asset"`
WalletBalance float64 `json:"walletBalance,string"`
UnrealizedProfit float64 `json:"unrealizedProfit,string"`
MarginBalance float64 `json:"marginBalance,string"`
MaintMargin float64 `json:"maintMargin,string"`
InitialMargin float64 `json:"initialMargin,string"`
PositionInitialMargin float64 `json:"positionInitialMargin,string"`
OpenOrderInitialMargin float64 `json:"openOrderInitialMargin,string"`
MaxWithdrawAmount float64 `json:"maxWithdrawAmount,string"`
CrossWalletBalance float64 `json:"crossWalletBalance,string"`
CrossUnPNL float64 `json:"crossUnPnl,string"`
AvailableBalance float64 `json:"availableBalance,string"`
} `json:"assets"`
Positions []FuturesAccountInformationPositions `json:"positions"`
CanDeposit bool `json:"canDeposit"`
CanTrade bool `json:"canTrade"`
CanWithdraw bool `json:"canWithdraw"`
FeeTier int64 `json:"feeTier"`
UpdateTime time.Time `json:"updateTime"`
Assets []FuturesAccountAsset `json:"assets"`
Positions []FuturesAccountInformationPosition `json:"positions"`
CanDeposit bool `json:"canDeposit"`
CanTrade bool `json:"canTrade"`
CanWithdraw bool `json:"canWithdraw"`
FeeTier int64 `json:"feeTier"`
UpdateTime time.Time `json:"updateTime"`
}
// FuturesAccountAsset holds account asset information
type FuturesAccountAsset struct {
Asset string `json:"asset"`
WalletBalance float64 `json:"walletBalance,string"`
UnrealizedProfit float64 `json:"unrealizedProfit,string"`
MarginBalance float64 `json:"marginBalance,string"`
MaintenanceMargin float64 `json:"maintMargin,string"`
InitialMargin float64 `json:"initialMargin,string"`
PositionInitialMargin float64 `json:"positionInitialMargin,string"`
OpenOrderInitialMargin float64 `json:"openOrderInitialMargin,string"`
MaxWithdrawAmount float64 `json:"maxWithdrawAmount,string"`
CrossWalletBalance float64 `json:"crossWalletBalance,string"`
CrossUnPNL float64 `json:"crossUnPnl,string"`
AvailableBalance float64 `json:"availableBalance,string"`
}
// GenericAuthResponse is a general data response for a post auth request
@@ -408,6 +411,13 @@ type GenericAuthResponse struct {
Msg string `json:"msg"`
}
// FuturesMarginUpdatedResponse stores margin update response data
type FuturesMarginUpdatedResponse struct {
Amount float64 `json:"amount"`
Type int `json:"type"`
GenericAuthResponse
}
// FuturesLeverageData stores leverage data for futures
type FuturesLeverageData struct {
Leverage int64 `json:"leverage"`
@@ -435,18 +445,21 @@ type GetPositionMarginChangeHistoryData struct {
// FuturesPositionInformation stores futures position info
type FuturesPositionInformation struct {
Symbol string `json:"symbol"`
PositionAmount float64 `json:"positionAmt,string"`
EntryPrice float64 `json:"entryPrice,string"`
MarkPrice float64 `json:"markPrice,string"`
UnrealizedProfit float64 `json:"unRealizedProfit,string"`
LiquidationPrice float64 `json:"liquidation,string"`
Leverage int64 `json:"leverage"`
MaxQty float64 `json:"maxQty"`
MarginType string `json:"marginType"`
IsolatedMargin float64 `json:"isolatedMargin,string"`
IsAutoAddMargin bool `json:"isAutoAddMargin"`
PositionSide string `json:"positionSide"`
Symbol string `json:"symbol"`
PositionAmount float64 `json:"positionAmt,string"`
EntryPrice float64 `json:"entryPrice,string"`
MarkPrice float64 `json:"markPrice,string"`
UnRealizedProfit float64 `json:"unRealizedProfit,string"`
LiquidationPrice float64 `json:"liquidationPrice,string"`
Leverage float64 `json:"leverage,string"`
MaxQty float64 `json:"maxQty,string"`
MarginType string `json:"marginType"`
IsolatedMargin float64 `json:"isolatedMargin,string"`
IsAutoAddMargin bool `json:"isAutoAddMargin,string"`
PositionSide string `json:"positionSide"`
NotionalValue float64 `json:"notionalValue,string"`
IsolatedWallet float64 `json:"isolatedWallet,string"`
UpdateTime binanceTime `json:"updateTime"`
}
// FuturesAccountTradeList stores account trade list data

View File

@@ -92,6 +92,8 @@ const (
cFuturesAccountInformationRate
cFuturesOrderbookTickerAllRate
cFuturesOrdersDefaultRate
uFuturesMultiAssetMarginRate
uFuturesSetMultiAssetMarginRate
)
// RateLimit implements the request.Limiter interface
@@ -211,6 +213,10 @@ func (r *RateLimit) Limit(ctx context.Context, f request.EndpointLimit) error {
limiter, tokens = r.CFuturesRate, 20
case cFuturesDefaultRate:
limiter, tokens = r.CFuturesRate, 1
case uFuturesMultiAssetMarginRate:
limiter, tokens = r.UFuturesRate, 30
case uFuturesSetMultiAssetMarginRate:
limiter, tokens = r.UFuturesRate, 1
default:
limiter, tokens = r.SpotRate, 1
}

View File

@@ -53,7 +53,7 @@ func (a *ExchangeInfo) UnmarshalJSON(data []byte) error {
if err := json.Unmarshal(data, &aux); err != nil {
return err
}
a.Servertime = aux.Servertime.Time()
a.ServerTime = aux.Servertime.Time()
return nil
}
@@ -458,8 +458,8 @@ func (u *UFuturesSymbolInfo) UnmarshalJSON(data []byte) error {
}
// UnmarshalJSON deserialises the JSON info, including the timestamp
func (a *FuturesAccountInformationPositions) UnmarshalJSON(data []byte) error {
type Alias FuturesAccountInformationPositions
func (a *FuturesAccountInformationPosition) UnmarshalJSON(data []byte) error {
type Alias FuturesAccountInformationPosition
aux := &struct {
UpdateTime binanceTime `json:"updateTime"`

View File

@@ -251,49 +251,61 @@ type UAccountBalanceV2Data struct {
// UAccountInformationV2Data stores account info for ufutures
type UAccountInformationV2Data struct {
FeeTier int64 `json:"feeTier"`
CanTrade bool `json:"canTrade"`
CanDeposit bool `json:"canDeposit"`
CanWithdraw bool `json:"canWithdraw"`
UpdateTime int64 `json:"updateTime"`
TotalInitialMargin float64 `json:"totalInitialMargin,string"`
TotalMaintenance float64 `json:"totalMaintMargin,string"`
TotalWalletBalance float64 `json:"totalWalletBalance,string"`
TotalUnrealizedProfit float64 `json:"totalUnrealizedProfit,string"`
TotalMarginBalance float64 `json:"totalMarginBalance,string"`
TotalPositionInitialMargin float64 `json:"totalPositionInitialMargin,string"`
TotalOpenOrderInitialMargin float64 `json:"totalOpenOrderInitialMargin,string"`
TotalCrossWalletBalance float64 `json:"totalCrossWalletBalance,string"`
TotalCrossUnrealizedPNL float64 `json:"totalCrossUnPnl,string"`
AvailableBalance float64 `json:"availableBalance,string"`
MaxWithdrawAmount float64 `json:"maxWithdrawAmount,string"`
Assets []struct {
Asset string `json:"asset"`
WalletBalance float64 `json:"walletBalance,string"`
UnrealizedProfit float64 `json:"unrealizedProfit,string"`
MarginBalance float64 `json:"marginBalance,string"`
MaintMargin float64 `json:"maintMargin,string"`
InitialMargin float64 `json:"initialMargin,string"`
PositionInitialMargin float64 `json:"positionInitialMargin,string"`
OpenOrderInitialMargin float64 `json:"openOrderInitialMargin,string"`
CrossWalletBalance float64 `json:"crossWalletBalance,string"`
CrossUnPnl float64 `json:"crossUnPnl,string"`
AvailableBalance float64 `json:"availableBalance,string"`
MaxWithdrawAmount float64 `json:"maxWithdrawAmount,string"`
} `json:"assets"`
Positions []struct {
Symbol string `json:"symbol"`
InitialMargin float64 `json:"initialMargin,string"`
MaintenanceMargin float64 `json:"maintMargin,string"`
UnrealizedProfit float64 `json:"unrealizedProfit,string"`
PositionInitialMargin float64 `json:"positionInitialMargin,string"`
OpenOrderInitialMargin float64 `json:"openOrderInitialMargin,string"`
Leverage float64 `json:"leverage,string"`
Isolated bool `json:"isolated"`
EntryPrice float64 `json:"entryPrice,string"`
MaxNotional float64 `json:"maxNotional,string"`
PositionSide string `json:"positionSide"`
} `json:"positions"`
FeeTier int64 `json:"feeTier"`
CanTrade bool `json:"canTrade"`
CanDeposit bool `json:"canDeposit"`
CanWithdraw bool `json:"canWithdraw"`
UpdateTime int64 `json:"updateTime"`
MultiAssetsMargin bool `json:"multiAssetsMargin"`
TotalInitialMargin float64 `json:"totalInitialMargin,string"`
TotalMaintenanceMargin float64 `json:"totalMaintMargin,string"`
TotalWalletBalance float64 `json:"totalWalletBalance,string"`
TotalUnrealizedProfit float64 `json:"totalUnrealizedProfit,string"`
TotalMarginBalance float64 `json:"totalMarginBalance,string"`
TotalPositionInitialMargin float64 `json:"totalPositionInitialMargin,string"`
TotalOpenOrderInitialMargin float64 `json:"totalOpenOrderInitialMargin,string"`
TotalCrossWalletBalance float64 `json:"totalCrossWalletBalance,string"`
TotalCrossUnrealizedPNL float64 `json:"totalCrossUnPnl,string"`
AvailableBalance float64 `json:"availableBalance,string"`
MaxWithdrawAmount float64 `json:"maxWithdrawAmount,string"`
Assets []UAsset `json:"assets"`
Positions []UPosition `json:"positions"`
}
// UAsset holds account asset information
type UAsset struct {
Asset string `json:"asset"`
WalletBalance float64 `json:"walletBalance,string"`
UnrealizedProfit float64 `json:"unrealizedProfit,string"`
MarginBalance float64 `json:"marginBalance,string"`
MaintenanceMargin float64 `json:"maintMargin,string"`
InitialMargin float64 `json:"initialMargin,string"`
PositionInitialMargin float64 `json:"positionInitialMargin,string"`
OpenOrderInitialMargin float64 `json:"openOrderInitialMargin,string"`
CrossWalletBalance float64 `json:"crossWalletBalance,string"`
CrossUnPnl float64 `json:"crossUnPnl,string"`
AvailableBalance float64 `json:"availableBalance,string"`
MaxWithdrawAmount float64 `json:"maxWithdrawAmount,string"`
}
// UPosition holds account position information
type UPosition struct {
Symbol string `json:"symbol"`
InitialMargin float64 `json:"initialMargin,string"`
MaintenanceMargin float64 `json:"maintMargin,string"`
UnrealisedProfit float64 `json:"unrealizedProfit,string"`
PositionInitialMargin float64 `json:"positionInitialMargin,string"`
OpenOrderInitialMargin float64 `json:"openOrderInitialMargin,string"`
Leverage float64 `json:"leverage,string"`
Isolated bool `json:"isolated"`
IsolatedWallet float64 `json:"isolatedWallet,string"`
EntryPrice float64 `json:"entryPrice,string"`
MaxNotional float64 `json:"maxNotional,string"`
BidNotional float64 `json:"bidNotional,string"`
AskNotional float64 `json:"askNotional,string"`
PositionSide string `json:"positionSide"`
PositionAmount float64 `json:"positionAmt,string"`
UpdateTime binanceTime `json:"updateTime"`
}
// UChangeInitialLeverage stores leverage change data
@@ -321,18 +333,21 @@ type UPositionMarginChangeHistoryData struct {
// UPositionInformationV2 stores positions data
type UPositionInformationV2 struct {
EntryPrice float64 `json:"entryPrice,string"`
MarginType string `json:"marginType"`
AutoAddMarginEnabled bool `json:"isAutoAddMargin,string"`
IsolatedMargin float64 `json:"isolatedMargin,string"`
Leverage float64 `json:"leverage,string"`
LiquidationPrice float64 `json:"liquidationPrice,string"`
MarkPrice float64 `json:"markPrice,string"`
MaxNotionalValue float64 `json:"maxNotionalValue,string"`
PositionAmount float64 `json:"positionAmt,string"`
Symbol string `json:"symbol"`
UnrealizedProfit float64 `json:"unrealizedProfit,string"`
PositionSide string `json:"positionSide"`
Symbol string `json:"symbol"`
PositionAmount float64 `json:"positionAmt,string"`
EntryPrice float64 `json:"entryPrice,string"`
MarkPrice float64 `json:"markPrice,string"`
UnrealizedProfit float64 `json:"unrealizedProfit,string"`
LiquidationPrice float64 `json:"liquidationPrice,string"`
Leverage float64 `json:"leverage,string"`
MaxNotionalValue float64 `json:"maxNotionalValue,string"`
MarginType string `json:"marginType"`
IsAutoAddMargin bool `json:"isAutoAddMargin,string"`
PositionSide string `json:"positionSide"`
Notional float64 `json:"notional,string"`
IsolatedWallet float64 `json:"isolatedWallet,string"`
IsolatedMargin float64 `json:"isolatedMargin,string"`
UpdateTime binanceTime `json:"updateTime"`
}
// UAccountTradeHistory stores trade data for the users account