mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-06-01 15:10:44 +00:00
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:
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user