mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 23:16:45 +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:
@@ -51,6 +51,7 @@ const (
|
||||
binary = "binary"
|
||||
perpetualContract = "perpetualcontract"
|
||||
perpetualSwap = "perpetualswap"
|
||||
swap = "swap"
|
||||
futures = "futures"
|
||||
deliveryFutures = "delivery"
|
||||
upsideProfitContract = "upsideprofitcontract"
|
||||
@@ -190,7 +191,7 @@ func New(input string) (Item, error) {
|
||||
return Binary, nil
|
||||
case perpetualContract:
|
||||
return PerpetualContract, nil
|
||||
case perpetualSwap:
|
||||
case perpetualSwap, swap:
|
||||
return PerpetualSwap, nil
|
||||
case futures:
|
||||
return Futures, nil
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -174,7 +174,7 @@ func (by *Bybit) GetTrades(ctx context.Context, symbol string, limit int64) ([]T
|
||||
params.Set("symbol", symbol)
|
||||
|
||||
strLimit := "60" // default limit
|
||||
if limit > 0 && limit < 60 {
|
||||
if limit > 0 {
|
||||
strLimit = strconv.FormatInt(limit, 10)
|
||||
}
|
||||
params.Set("limit", strLimit)
|
||||
|
||||
@@ -131,7 +131,7 @@ func (by *Bybit) GetFuturesKlineData(ctx context.Context, symbol currency.Pair,
|
||||
}
|
||||
params.Set("symbol", symbolValue)
|
||||
|
||||
if limit > 0 && limit <= 200 {
|
||||
if limit > 0 {
|
||||
params.Set("limit", strconv.FormatInt(limit, 10))
|
||||
}
|
||||
if !common.StringDataCompare(validFuturesIntervals, interval) {
|
||||
@@ -178,7 +178,7 @@ func (by *Bybit) GetPublicTrades(ctx context.Context, symbol currency.Pair, limi
|
||||
return resp.Data, err
|
||||
}
|
||||
params.Set("symbol", symbolValue)
|
||||
if limit > 0 && limit <= 1000 {
|
||||
if limit > 0 {
|
||||
params.Set("limit", strconv.FormatInt(limit, 10))
|
||||
}
|
||||
|
||||
@@ -208,7 +208,7 @@ func (by *Bybit) GetMarkPriceKline(ctx context.Context, symbol currency.Pair, in
|
||||
return resp.Data, err
|
||||
}
|
||||
params.Set("symbol", symbolValue)
|
||||
if limit > 0 && limit <= 200 {
|
||||
if limit > 0 {
|
||||
params.Set("limit", strconv.FormatInt(limit, 10))
|
||||
}
|
||||
if !common.StringDataCompare(validFuturesIntervals, interval) {
|
||||
@@ -237,7 +237,7 @@ func (by *Bybit) GetIndexPriceKline(ctx context.Context, symbol currency.Pair, i
|
||||
return resp.Data, err
|
||||
}
|
||||
params.Set("symbol", symbolValue)
|
||||
if limit > 0 && limit <= 200 {
|
||||
if limit > 0 {
|
||||
params.Set("limit", strconv.FormatInt(limit, 10))
|
||||
}
|
||||
if !common.StringDataCompare(validFuturesIntervals, interval) {
|
||||
@@ -266,7 +266,7 @@ func (by *Bybit) GetPremiumIndexPriceKline(ctx context.Context, symbol currency.
|
||||
return resp.Data, err
|
||||
}
|
||||
params.Set("symbol", symbolValue)
|
||||
if limit > 0 && limit <= 200 {
|
||||
if limit > 0 {
|
||||
params.Set("limit", strconv.FormatInt(limit, 10))
|
||||
}
|
||||
if !common.StringDataCompare(validFuturesIntervals, interval) {
|
||||
@@ -295,7 +295,7 @@ func (by *Bybit) GetOpenInterest(ctx context.Context, symbol currency.Pair, peri
|
||||
return resp.Data, err
|
||||
}
|
||||
params.Set("symbol", symbolValue)
|
||||
if limit > 0 && limit <= 200 {
|
||||
if limit > 0 {
|
||||
params.Set("limit", strconv.FormatInt(limit, 10))
|
||||
}
|
||||
if !common.StringDataCompare(validFuturesPeriods, period) {
|
||||
@@ -320,7 +320,7 @@ func (by *Bybit) GetLatestBigDeal(ctx context.Context, symbol currency.Pair, lim
|
||||
return resp.Data, err
|
||||
}
|
||||
params.Set("symbol", symbolValue)
|
||||
if limit > 0 && limit <= 1000 {
|
||||
if limit > 0 {
|
||||
params.Set("limit", strconv.FormatInt(limit, 10))
|
||||
}
|
||||
|
||||
@@ -341,7 +341,7 @@ func (by *Bybit) GetAccountRatio(ctx context.Context, symbol currency.Pair, peri
|
||||
return resp.Data, err
|
||||
}
|
||||
params.Set("symbol", symbolValue)
|
||||
if limit > 0 && limit <= 500 {
|
||||
if limit > 0 {
|
||||
params.Set("limit", strconv.FormatInt(limit, 10))
|
||||
}
|
||||
if !common.StringDataCompare(validFuturesPeriods, period) {
|
||||
@@ -491,7 +491,7 @@ func (by *Bybit) GetActiveCoinFuturesOrders(ctx context.Context, symbol currency
|
||||
if direction != "" {
|
||||
params.Set("direction", direction)
|
||||
}
|
||||
if limit > 0 && limit <= 50 {
|
||||
if limit > 0 {
|
||||
params.Set("limit", strconv.FormatInt(limit, 10))
|
||||
}
|
||||
if cursor != "" {
|
||||
@@ -709,7 +709,7 @@ func (by *Bybit) GetConditionalCoinFuturesOrders(ctx context.Context, symbol cur
|
||||
if direction != "" {
|
||||
params.Set("direction", direction)
|
||||
}
|
||||
if limit > 0 && limit <= 50 {
|
||||
if limit > 0 {
|
||||
params.Set("limit", strconv.FormatInt(limit, 10))
|
||||
}
|
||||
if cursor != "" {
|
||||
@@ -999,7 +999,7 @@ func (by *Bybit) GetCoinTradeRecords(ctx context.Context, symbol currency.Pair,
|
||||
if page != 0 {
|
||||
params.Set("page", strconv.FormatInt(page, 10))
|
||||
}
|
||||
if limit > 0 && limit <= 200 {
|
||||
if limit > 0 {
|
||||
params.Set("limit", strconv.FormatInt(limit, 10))
|
||||
}
|
||||
|
||||
@@ -1036,7 +1036,7 @@ func (by *Bybit) GetClosedCoinTrades(ctx context.Context, symbol currency.Pair,
|
||||
if page > 0 && page <= 50 {
|
||||
params.Set("page", strconv.FormatInt(page, 10))
|
||||
}
|
||||
if limit > 0 && limit <= 50 {
|
||||
if limit > 0 {
|
||||
params.Set("limit", strconv.FormatInt(limit, 10))
|
||||
}
|
||||
|
||||
@@ -1247,7 +1247,7 @@ func (by *Bybit) GetWalletFundRecords(ctx context.Context, startDate, endDate, c
|
||||
if page != 0 {
|
||||
params.Set("page", strconv.FormatInt(page, 10))
|
||||
}
|
||||
if limit > 0 && limit <= 50 {
|
||||
if limit > 0 {
|
||||
params.Set("limit", strconv.FormatInt(limit, 10))
|
||||
}
|
||||
|
||||
@@ -1280,7 +1280,7 @@ func (by *Bybit) GetWalletWithdrawalRecords(ctx context.Context, startDate, endD
|
||||
if page != 0 {
|
||||
params.Set("page", strconv.FormatInt(page, 10))
|
||||
}
|
||||
if limit > 0 && limit <= 50 {
|
||||
if limit > 0 {
|
||||
params.Set("limit", strconv.FormatInt(limit, 10))
|
||||
}
|
||||
|
||||
@@ -1303,7 +1303,7 @@ func (by *Bybit) GetAssetExchangeRecords(ctx context.Context, direction string,
|
||||
if from != 0 {
|
||||
params.Set("from", strconv.FormatInt(from, 10))
|
||||
}
|
||||
if limit > 0 && limit <= 50 {
|
||||
if limit > 0 {
|
||||
params.Set("limit", strconv.FormatInt(limit, 10))
|
||||
}
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ func (by *Bybit) GetActiveFuturesOrders(ctx context.Context, symbol currency.Pai
|
||||
if direction != "" {
|
||||
params.Set("direction", direction)
|
||||
}
|
||||
if limit > 0 && limit <= 50 {
|
||||
if limit > 0 {
|
||||
params.Set("limit", strconv.FormatInt(limit, 10))
|
||||
}
|
||||
if cursor != "" {
|
||||
@@ -345,7 +345,7 @@ func (by *Bybit) GetConditionalFuturesOrders(ctx context.Context, symbol currenc
|
||||
if direction != "" {
|
||||
params.Set("direction", direction)
|
||||
}
|
||||
if limit > 0 && limit <= 50 {
|
||||
if limit > 0 {
|
||||
params.Set("limit", strconv.FormatInt(limit, 10))
|
||||
}
|
||||
if cursor != "" {
|
||||
@@ -583,8 +583,8 @@ func (by *Bybit) SetTradingAndStop(ctx context.Context, positionMode int64, symb
|
||||
return resp.Result, by.SendAuthHTTPRequest(ctx, exchange.RestFutures, http.MethodPost, futuresSetTradingStop, params, nil, &resp, futuresSetTradingStopRate)
|
||||
}
|
||||
|
||||
// SetLeverage sets leverage
|
||||
func (by *Bybit) SetLeverage(ctx context.Context, symbol currency.Pair, buyLeverage, sellLeverage float64) (float64, error) {
|
||||
// SetLeverageLevel sets leverage
|
||||
func (by *Bybit) SetLeverageLevel(ctx context.Context, symbol currency.Pair, buyLeverage, sellLeverage float64) (float64, error) {
|
||||
resp := struct {
|
||||
Result float64 `json:"result"`
|
||||
Error
|
||||
@@ -685,7 +685,7 @@ func (by *Bybit) GetTradeRecords(ctx context.Context, symbol currency.Pair, orde
|
||||
if page != 0 {
|
||||
params.Set("page", strconv.FormatInt(page, 10))
|
||||
}
|
||||
if limit > 0 && limit <= 200 {
|
||||
if limit > 0 {
|
||||
params.Set("limit", strconv.FormatInt(limit, 10))
|
||||
}
|
||||
|
||||
@@ -721,7 +721,7 @@ func (by *Bybit) GetClosedTrades(ctx context.Context, symbol currency.Pair, exec
|
||||
if page > 0 && page <= 50 {
|
||||
params.Set("page", strconv.FormatInt(page, 10))
|
||||
}
|
||||
if limit > 0 && limit <= 50 {
|
||||
if limit > 0 {
|
||||
params.Set("limit", strconv.FormatInt(limit, 10))
|
||||
}
|
||||
|
||||
|
||||
@@ -1870,7 +1870,7 @@ func TestSetLeverage(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = b.SetLeverage(context.Background(), pair, 10, 10)
|
||||
_, err = b.SetLeverageLevel(context.Background(), pair, 10, 10)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ func (by *Bybit) GetUSDTFuturesKlineData(ctx context.Context, symbol currency.Pa
|
||||
}
|
||||
params.Set("symbol", symbolValue)
|
||||
|
||||
if limit > 0 && limit <= 200 {
|
||||
if limit > 0 {
|
||||
params.Set("limit", strconv.FormatInt(limit, 10))
|
||||
}
|
||||
if !common.StringDataCompare(validFuturesIntervals, interval) {
|
||||
@@ -101,7 +101,7 @@ func (by *Bybit) GetUSDTPublicTrades(ctx context.Context, symbol currency.Pair,
|
||||
return resp.Data, err
|
||||
}
|
||||
params.Set("symbol", symbolValue)
|
||||
if limit > 0 && limit <= 1000 {
|
||||
if limit > 0 {
|
||||
params.Set("limit", strconv.FormatInt(limit, 10))
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ func (by *Bybit) GetUSDTMarkPriceKline(ctx context.Context, symbol currency.Pair
|
||||
return resp.Data, err
|
||||
}
|
||||
params.Set("symbol", symbolValue)
|
||||
if limit > 0 && limit <= 200 {
|
||||
if limit > 0 {
|
||||
params.Set("limit", strconv.FormatInt(limit, 10))
|
||||
}
|
||||
if !common.StringDataCompare(validFuturesIntervals, interval) {
|
||||
@@ -151,7 +151,7 @@ func (by *Bybit) GetUSDTIndexPriceKline(ctx context.Context, symbol currency.Pai
|
||||
return resp.Data, err
|
||||
}
|
||||
params.Set("symbol", symbolValue)
|
||||
if limit > 0 && limit <= 200 {
|
||||
if limit > 0 {
|
||||
params.Set("limit", strconv.FormatInt(limit, 10))
|
||||
}
|
||||
if !common.StringDataCompare(validFuturesIntervals, interval) {
|
||||
@@ -180,7 +180,7 @@ func (by *Bybit) GetUSDTPremiumIndexPriceKline(ctx context.Context, symbol curre
|
||||
return resp.Data, err
|
||||
}
|
||||
params.Set("symbol", symbolValue)
|
||||
if limit > 0 && limit <= 200 {
|
||||
if limit > 0 {
|
||||
params.Set("limit", strconv.FormatInt(limit, 10))
|
||||
}
|
||||
if !common.StringDataCompare(validFuturesIntervals, interval) {
|
||||
@@ -327,7 +327,7 @@ func (by *Bybit) GetActiveUSDTFuturesOrders(ctx context.Context, symbol currency
|
||||
if page > 0 && page <= 50 {
|
||||
params.Set("page", strconv.FormatInt(page, 10))
|
||||
}
|
||||
if limit > 0 && limit <= 50 {
|
||||
if limit > 0 {
|
||||
params.Set("limit", strconv.FormatInt(limit, 10))
|
||||
}
|
||||
if orderID != "" {
|
||||
@@ -559,7 +559,7 @@ func (by *Bybit) GetConditionalUSDTFuturesOrders(ctx context.Context, symbol cur
|
||||
if direction != "" {
|
||||
params.Set("order", direction)
|
||||
}
|
||||
if limit > 0 && limit <= 50 {
|
||||
if limit > 0 {
|
||||
params.Set("limit", strconv.FormatInt(limit, 10))
|
||||
}
|
||||
if page != 0 {
|
||||
@@ -940,7 +940,7 @@ func (by *Bybit) GetUSDTTradeRecords(ctx context.Context, symbol currency.Pair,
|
||||
if page != 0 {
|
||||
params.Set("page", strconv.FormatInt(page, 10))
|
||||
}
|
||||
if limit > 0 && limit <= 200 {
|
||||
if limit > 0 {
|
||||
params.Set("limit", strconv.FormatInt(limit, 10))
|
||||
}
|
||||
return resp.Data.Trades, by.SendAuthHTTPRequest(ctx, exchange.RestUSDTMargined, http.MethodGet, ufuturesGetTrades, params, nil, &resp, uFuturesGetTradesRate)
|
||||
@@ -975,7 +975,7 @@ func (by *Bybit) GetClosedUSDTTrades(ctx context.Context, symbol currency.Pair,
|
||||
if page > 0 && page <= 50 {
|
||||
params.Set("page", strconv.FormatInt(page, 10))
|
||||
}
|
||||
if limit > 0 && limit <= 50 {
|
||||
if limit > 0 {
|
||||
params.Set("limit", strconv.FormatInt(limit, 10))
|
||||
}
|
||||
return resp.Data.Trades, by.SendAuthHTTPRequest(ctx, exchange.RestUSDTMargined, http.MethodGet, ufuturesGetClosedTrades, params, nil, &resp, uFuturesGetClosedTradesRate)
|
||||
|
||||
@@ -115,7 +115,7 @@ func (by *Bybit) GetUSDCContracts(ctx context.Context, symbol currency.Pair, dir
|
||||
if direction != "" {
|
||||
params.Set("direction", direction)
|
||||
}
|
||||
if limit > 0 && limit <= 200 {
|
||||
if limit > 0 {
|
||||
params.Set("limit", strconv.FormatInt(limit, 10))
|
||||
}
|
||||
|
||||
@@ -169,7 +169,7 @@ func (by *Bybit) GetUSDCKlines(ctx context.Context, symbol currency.Pair, period
|
||||
}
|
||||
params.Set("startTime", strconv.FormatInt(startTime.Unix(), 10))
|
||||
|
||||
if limit > 0 && limit <= 200 {
|
||||
if limit > 0 {
|
||||
params.Set("limit", strconv.FormatInt(limit, 10))
|
||||
}
|
||||
return resp.Data, by.SendHTTPRequest(ctx, exchange.RestUSDCMargined, common.EncodeURLValues(usdcfuturesGetKlines, params), usdcPublicRate, &resp)
|
||||
@@ -202,7 +202,7 @@ func (by *Bybit) GetUSDCMarkPriceKlines(ctx context.Context, symbol currency.Pai
|
||||
}
|
||||
params.Set("startTime", strconv.FormatInt(startTime.Unix(), 10))
|
||||
|
||||
if limit > 0 && limit <= 200 {
|
||||
if limit > 0 {
|
||||
params.Set("limit", strconv.FormatInt(limit, 10))
|
||||
}
|
||||
return resp.Data, by.SendHTTPRequest(ctx, exchange.RestUSDCMargined, common.EncodeURLValues(usdcfuturesGetMarkPriceKlines, params), usdcPublicRate, &resp)
|
||||
@@ -235,7 +235,7 @@ func (by *Bybit) GetUSDCIndexPriceKlines(ctx context.Context, symbol currency.Pa
|
||||
}
|
||||
params.Set("startTime", strconv.FormatInt(startTime.Unix(), 10))
|
||||
|
||||
if limit > 0 && limit <= 200 {
|
||||
if limit > 0 {
|
||||
params.Set("limit", strconv.FormatInt(limit, 10))
|
||||
}
|
||||
return resp.Data, by.SendHTTPRequest(ctx, exchange.RestUSDCMargined, common.EncodeURLValues(usdcfuturesGetIndexPriceKlines, params), usdcPublicRate, &resp)
|
||||
@@ -268,7 +268,7 @@ func (by *Bybit) GetUSDCPremiumIndexKlines(ctx context.Context, symbol currency.
|
||||
}
|
||||
params.Set("startTime", strconv.FormatInt(startTime.Unix(), 10))
|
||||
|
||||
if limit > 0 && limit <= 200 {
|
||||
if limit > 0 {
|
||||
params.Set("limit", strconv.FormatInt(limit, 10))
|
||||
}
|
||||
return resp.Data, by.SendHTTPRequest(ctx, exchange.RestUSDCMargined, common.EncodeURLValues(usdcfuturesGetPremiumIndexKlines, params), usdcPublicRate, &resp)
|
||||
@@ -296,7 +296,7 @@ func (by *Bybit) GetUSDCOpenInterest(ctx context.Context, symbol currency.Pair,
|
||||
}
|
||||
params.Set("period", period)
|
||||
|
||||
if limit > 0 && limit <= 200 {
|
||||
if limit > 0 {
|
||||
params.Set("limit", strconv.FormatInt(limit, 10))
|
||||
}
|
||||
return resp.Data, by.SendHTTPRequest(ctx, exchange.RestUSDCMargined, common.EncodeURLValues(usdcfuturesGetOpenInterest, params), usdcPublicRate, &resp)
|
||||
@@ -319,7 +319,7 @@ func (by *Bybit) GetUSDCLargeOrders(ctx context.Context, symbol currency.Pair, l
|
||||
}
|
||||
params.Set("symbol", symbolValue)
|
||||
|
||||
if limit > 0 && limit <= 100 {
|
||||
if limit > 0 {
|
||||
params.Set("limit", strconv.FormatInt(limit, 10))
|
||||
}
|
||||
return resp.Data, by.SendHTTPRequest(ctx, exchange.RestUSDCMargined, common.EncodeURLValues(usdcfuturesGetLargeOrders, params), usdcPublicRate, &resp)
|
||||
@@ -347,7 +347,7 @@ func (by *Bybit) GetUSDCAccountRatio(ctx context.Context, symbol currency.Pair,
|
||||
}
|
||||
params.Set("period", period)
|
||||
|
||||
if limit > 0 && limit <= 500 {
|
||||
if limit > 0 {
|
||||
params.Set("limit", strconv.FormatInt(limit, 10))
|
||||
}
|
||||
return resp.Data, by.SendHTTPRequest(ctx, exchange.RestUSDCMargined, common.EncodeURLValues(usdcfuturesGetAccountRatio, params), usdcPublicRate, &resp)
|
||||
@@ -378,7 +378,7 @@ func (by *Bybit) GetUSDCLatestTrades(ctx context.Context, symbol currency.Pair,
|
||||
params.Set("symbol", symbolValue)
|
||||
}
|
||||
|
||||
if limit > 0 && limit <= 500 {
|
||||
if limit > 0 {
|
||||
params.Set("limit", strconv.FormatInt(limit, 10))
|
||||
}
|
||||
return resp.Result.Data, by.SendHTTPRequest(ctx, exchange.RestUSDCMargined, common.EncodeURLValues(usdcfuturesGetLatestTrades, params), usdcPublicRate, &resp)
|
||||
@@ -749,7 +749,7 @@ func (by *Bybit) GetUSDCTradeHistory(ctx context.Context, symbol currency.Pair,
|
||||
req["direction"] = direction
|
||||
}
|
||||
|
||||
if limit > 0 && limit <= 50 {
|
||||
if limit > 0 {
|
||||
req["limit"] = strconv.FormatInt(limit, 10)
|
||||
}
|
||||
|
||||
@@ -792,7 +792,7 @@ func (by *Bybit) GetUSDCTransactionLog(ctx context.Context, startTime, endTime t
|
||||
req["direction"] = direction
|
||||
}
|
||||
|
||||
if limit > 0 && limit <= 50 {
|
||||
if limit > 0 {
|
||||
req["limit"] = strconv.FormatInt(limit, 10)
|
||||
}
|
||||
|
||||
@@ -875,7 +875,7 @@ func (by *Bybit) GetUSDCPosition(ctx context.Context, symbol currency.Pair, cate
|
||||
req["direction"] = direction
|
||||
}
|
||||
|
||||
if limit > 0 && limit <= 50 {
|
||||
if limit > 0 {
|
||||
req["limit"] = strconv.FormatInt(limit, 10)
|
||||
}
|
||||
|
||||
@@ -939,7 +939,7 @@ func (by *Bybit) GetUSDCSettlementHistory(ctx context.Context, symbol currency.P
|
||||
req["direction"] = direction
|
||||
}
|
||||
|
||||
if limit > 0 && limit <= 50 {
|
||||
if limit > 0 {
|
||||
req["limit"] = strconv.FormatInt(limit, 10)
|
||||
}
|
||||
|
||||
|
||||
71
exchanges/collateral/collateral.go
Normal file
71
exchanges/collateral/collateral.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package collateral
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Valid returns whether the collateral mode is valid
|
||||
func (t Mode) Valid() bool {
|
||||
return t != UnsetMode && supportedCollateralModes&t == t
|
||||
}
|
||||
|
||||
// UnmarshalJSON converts json into collateral mode
|
||||
func (t *Mode) UnmarshalJSON(d []byte) error {
|
||||
var mode string
|
||||
err := json.Unmarshal(d, &mode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*t, err = StringToMode(mode)
|
||||
return err
|
||||
}
|
||||
|
||||
// String returns the string representation of the collateral mode in lowercase
|
||||
// the absence of a lower func should hopefully highlight that String is lower
|
||||
func (t Mode) String() string {
|
||||
switch t {
|
||||
case UnsetMode:
|
||||
return unsetCollateralStr
|
||||
case SingleMode:
|
||||
return singleCollateralStr
|
||||
case MultiMode:
|
||||
return multiCollateralStr
|
||||
case PortfolioMode:
|
||||
return portfolioCollateralStr
|
||||
case UnknownMode:
|
||||
return unknownCollateralStr
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Upper returns the upper case string representation of the collateral mode
|
||||
func (t Mode) Upper() string {
|
||||
return strings.ToUpper(t.String())
|
||||
}
|
||||
|
||||
// IsValidCollateralModeString checks to see if the supplied string is a valid collateral mode
|
||||
func IsValidCollateralModeString(m string) bool {
|
||||
switch strings.ToLower(m) {
|
||||
case singleCollateralStr, multiCollateralStr, portfolioCollateralStr, unsetCollateralStr:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// StringToMode converts a string to a collateral mode
|
||||
// doesn't error, just returns unknown if the string is not recognised
|
||||
func StringToMode(m string) (Mode, error) {
|
||||
switch strings.ToLower(m) {
|
||||
case singleCollateralStr:
|
||||
return SingleMode, nil
|
||||
case multiCollateralStr:
|
||||
return MultiMode, nil
|
||||
case portfolioCollateralStr:
|
||||
return PortfolioMode, nil
|
||||
case "":
|
||||
return UnsetMode, nil
|
||||
}
|
||||
return UnknownMode, fmt.Errorf("%w %v", ErrInvalidCollateralMode, m)
|
||||
}
|
||||
180
exchanges/collateral/collateral_test.go
Normal file
180
exchanges/collateral/collateral_test.go
Normal file
@@ -0,0 +1,180 @@
|
||||
package collateral
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestValidCollateralType(t *testing.T) {
|
||||
t.Parallel()
|
||||
if !SingleMode.Valid() {
|
||||
t.Fatal("expected 'true', received 'false'")
|
||||
}
|
||||
if !MultiMode.Valid() {
|
||||
t.Fatal("expected 'true', received 'false'")
|
||||
}
|
||||
if !PortfolioMode.Valid() {
|
||||
t.Fatal("expected 'true', received 'false'")
|
||||
}
|
||||
if UnsetMode.Valid() {
|
||||
t.Fatal("expected 'false', received 'true'")
|
||||
}
|
||||
if UnknownMode.Valid() {
|
||||
t.Fatal("expected 'false', received 'true'")
|
||||
}
|
||||
if Mode(137).Valid() {
|
||||
t.Fatal("expected 'false', received 'true'")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalJSONCollateralType(t *testing.T) {
|
||||
t.Parallel()
|
||||
type martian struct {
|
||||
M Mode `json:"collateral"`
|
||||
}
|
||||
|
||||
var alien martian
|
||||
jason := []byte(`{"collateral":"single"}`)
|
||||
err := json.Unmarshal(jason, &alien)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if alien.M != SingleMode {
|
||||
t.Errorf("received '%v' expected 'single'", alien.M)
|
||||
}
|
||||
|
||||
jason = []byte(`{"collateral":"multi"}`)
|
||||
err = json.Unmarshal(jason, &alien)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if alien.M != MultiMode {
|
||||
t.Errorf("received '%v' expected 'Multi'", alien.M)
|
||||
}
|
||||
|
||||
jason = []byte(`{"collateral":"portfolio"}`)
|
||||
err = json.Unmarshal(jason, &alien)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if alien.M != PortfolioMode {
|
||||
t.Errorf("received '%v' expected 'Portfolio'", alien.M)
|
||||
}
|
||||
|
||||
jason = []byte(`{"collateral":"hello moto"}`)
|
||||
err = json.Unmarshal(jason, &alien)
|
||||
if !errors.Is(err, ErrInvalidCollateralMode) {
|
||||
t.Error(err)
|
||||
}
|
||||
if alien.M != UnknownMode {
|
||||
t.Errorf("received '%v' expected 'UnknownMode'", alien.M)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringCollateralType(t *testing.T) {
|
||||
t.Parallel()
|
||||
if UnknownMode.String() != unknownCollateralStr {
|
||||
t.Errorf("received '%v' expected '%v'", UnknownMode.String(), unknownCollateralStr)
|
||||
}
|
||||
if SingleMode.String() != singleCollateralStr {
|
||||
t.Errorf("received '%v' expected '%v'", SingleMode.String(), singleCollateralStr)
|
||||
}
|
||||
if MultiMode.String() != multiCollateralStr {
|
||||
t.Errorf("received '%v' expected '%v'", MultiMode.String(), multiCollateralStr)
|
||||
}
|
||||
if PortfolioMode.String() != portfolioCollateralStr {
|
||||
t.Errorf("received '%v' expected '%v'", PortfolioMode.String(), portfolioCollateralStr)
|
||||
}
|
||||
if UnsetMode.String() != unsetCollateralStr {
|
||||
t.Errorf("received '%v' expected '%v'", UnsetMode.String(), unsetCollateralStr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpperCollateralType(t *testing.T) {
|
||||
t.Parallel()
|
||||
if UnknownMode.Upper() != strings.ToUpper(unknownCollateralStr) {
|
||||
t.Errorf("received '%v' expected '%v'", UnknownMode.Upper(), strings.ToUpper(unknownCollateralStr))
|
||||
}
|
||||
if SingleMode.Upper() != strings.ToUpper(singleCollateralStr) {
|
||||
t.Errorf("received '%v' expected '%v'", SingleMode.Upper(), strings.ToUpper(singleCollateralStr))
|
||||
}
|
||||
if MultiMode.Upper() != strings.ToUpper(multiCollateralStr) {
|
||||
t.Errorf("received '%v' expected '%v'", MultiMode.Upper(), strings.ToUpper(multiCollateralStr))
|
||||
}
|
||||
if PortfolioMode.Upper() != strings.ToUpper(portfolioCollateralStr) {
|
||||
t.Errorf("received '%v' expected '%v'", PortfolioMode.Upper(), strings.ToUpper(portfolioCollateralStr))
|
||||
}
|
||||
if UnsetMode.Upper() != strings.ToUpper(unsetCollateralStr) {
|
||||
t.Errorf("received '%v' expected '%v'", UnsetMode.Upper(), strings.ToUpper(unsetCollateralStr))
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsValidCollateralTypeString(t *testing.T) {
|
||||
t.Parallel()
|
||||
if IsValidCollateralModeString("lol") {
|
||||
t.Fatal("expected 'false', received 'true'")
|
||||
}
|
||||
if !IsValidCollateralModeString("single") {
|
||||
t.Fatal("expected 'true', received 'false'")
|
||||
}
|
||||
if !IsValidCollateralModeString("multi") {
|
||||
t.Fatal("expected 'true', received 'false'")
|
||||
}
|
||||
if !IsValidCollateralModeString("portfolio") {
|
||||
t.Fatal("expected 'true', received 'false'")
|
||||
}
|
||||
if !IsValidCollateralModeString("unset") {
|
||||
t.Fatal("expected 'true', received 'false'")
|
||||
}
|
||||
if IsValidCollateralModeString("") {
|
||||
t.Fatal("expected 'false', received 'true'")
|
||||
}
|
||||
if IsValidCollateralModeString("unknown") {
|
||||
t.Fatal("expected 'false', received 'true'")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringToCollateralType(t *testing.T) {
|
||||
t.Parallel()
|
||||
resp, err := StringToMode("lol")
|
||||
if !errors.Is(err, ErrInvalidCollateralMode) {
|
||||
t.Error(err)
|
||||
}
|
||||
if resp != UnknownMode {
|
||||
t.Errorf("received '%v' expected '%v'", resp, UnknownMode)
|
||||
}
|
||||
|
||||
resp, err = StringToMode("")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if resp != UnsetMode {
|
||||
t.Errorf("received '%v' expected '%v'", resp, UnsetMode)
|
||||
}
|
||||
|
||||
resp, err = StringToMode("single")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if resp != SingleMode {
|
||||
t.Errorf("received '%v' expected '%v'", resp, SingleMode)
|
||||
}
|
||||
|
||||
resp, err = StringToMode("multi")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if resp != MultiMode {
|
||||
t.Errorf("received '%v' expected '%v'", resp, MultiMode)
|
||||
}
|
||||
|
||||
resp, err = StringToMode("portfolio")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if resp != PortfolioMode {
|
||||
t.Errorf("received '%v' expected '%v'", resp, PortfolioMode)
|
||||
}
|
||||
}
|
||||
85
exchanges/collateral/collateral_types.go
Normal file
85
exchanges/collateral/collateral_types.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package collateral
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
)
|
||||
|
||||
// Mode defines the different collateral types supported by exchanges
|
||||
// For example, FTX had a global collateral pool
|
||||
// Binance has either singular position collateral calculation
|
||||
// or cross aka asset level collateral calculation
|
||||
type Mode uint8
|
||||
|
||||
const (
|
||||
// UnsetMode is the default value
|
||||
UnsetMode Mode = 0
|
||||
// SingleMode has allocated collateral per position
|
||||
SingleMode Mode = 1 << (iota - 1)
|
||||
// MultiMode has collateral allocated across the whole asset
|
||||
MultiMode
|
||||
// PortfolioMode has collateral allocated across account
|
||||
PortfolioMode
|
||||
// UnknownMode has collateral allocated in an unknown manner at present, but is not unset
|
||||
UnknownMode
|
||||
)
|
||||
|
||||
const (
|
||||
unsetCollateralStr = "unset"
|
||||
singleCollateralStr = "single"
|
||||
multiCollateralStr = "multi"
|
||||
portfolioCollateralStr = "portfolio"
|
||||
unknownCollateralStr = "unknown"
|
||||
)
|
||||
|
||||
// ErrInvalidCollateralMode is returned when converting invalid string to collateral mode
|
||||
var ErrInvalidCollateralMode = errors.New("invalid collateral mode")
|
||||
|
||||
var supportedCollateralModes = SingleMode | MultiMode | PortfolioMode
|
||||
|
||||
// ByPosition shows how much collateral is used
|
||||
// from positions
|
||||
type ByPosition struct {
|
||||
PositionCurrency currency.Pair
|
||||
Size decimal.Decimal
|
||||
OpenOrderSize decimal.Decimal
|
||||
PositionSize decimal.Decimal
|
||||
MarkPrice decimal.Decimal
|
||||
RequiredMargin decimal.Decimal
|
||||
CollateralUsed decimal.Decimal
|
||||
}
|
||||
|
||||
// ByCurrency individual collateral contribution
|
||||
// along with what the potentially scaled collateral
|
||||
// currency it is represented as
|
||||
// eg in Bybit ScaledCurrency is USDC
|
||||
type ByCurrency struct {
|
||||
Currency currency.Code
|
||||
SkipContribution bool
|
||||
TotalFunds decimal.Decimal
|
||||
AvailableForUseAsCollateral decimal.Decimal
|
||||
CollateralContribution decimal.Decimal
|
||||
AdditionalCollateralUsed decimal.Decimal
|
||||
FairMarketValue decimal.Decimal
|
||||
Weighting decimal.Decimal
|
||||
ScaledCurrency currency.Code
|
||||
UnrealisedPNL decimal.Decimal
|
||||
ScaledUsed decimal.Decimal
|
||||
ScaledUsedBreakdown *UsedBreakdown
|
||||
Error error
|
||||
}
|
||||
|
||||
// UsedBreakdown provides a detailed
|
||||
// breakdown of where collateral is currently being allocated
|
||||
type UsedBreakdown struct {
|
||||
LockedInStakes decimal.Decimal
|
||||
LockedInNFTBids decimal.Decimal
|
||||
LockedInFeeVoucher decimal.Decimal
|
||||
LockedInSpotMarginFundingOffers decimal.Decimal
|
||||
LockedInSpotOrders decimal.Decimal
|
||||
LockedAsCollateral decimal.Decimal
|
||||
UsedInPositions decimal.Decimal
|
||||
UsedInSpotMarginBorrows decimal.Decimal
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"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/currencystate"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/fundingrate"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
|
||||
@@ -1458,81 +1459,17 @@ func (b *Base) GetAvailableTransferChains(_ context.Context, _ currency.Code) ([
|
||||
return nil, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// CalculatePNL is an overridable function to allow PNL to be calculated on an
|
||||
// open position
|
||||
// It will also determine whether the position is considered to be liquidated
|
||||
// For live trading, an overriding function may wish to confirm the liquidation by
|
||||
// requesting the status of the asset
|
||||
func (b *Base) CalculatePNL(context.Context, *order.PNLCalculatorRequest) (*order.PNLResult, error) {
|
||||
return nil, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// ScaleCollateral is an overridable function to determine how much
|
||||
// collateral is usable in futures positions
|
||||
func (b *Base) ScaleCollateral(context.Context, *order.CollateralCalculator) (*order.CollateralByCurrency, error) {
|
||||
return nil, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// CalculateTotalCollateral takes in n collateral calculators to determine an overall
|
||||
// standing in a singular currency
|
||||
func (b *Base) CalculateTotalCollateral(_ context.Context, _ *order.TotalCollateralCalculator) (*order.TotalCollateralResponse, error) {
|
||||
return nil, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// GetCollateralCurrencyForContract returns the collateral currency for an asset and contract pair
|
||||
func (b *Base) GetCollateralCurrencyForContract(_ asset.Item, _ currency.Pair) (currency.Code, asset.Item, error) {
|
||||
return currency.Code{}, asset.Empty, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// GetCurrencyForRealisedPNL returns where to put realised PNL
|
||||
// example 1: Bybit universal margin PNL is paid out in USD to your spot wallet
|
||||
// example 2: Binance coin margined futures pays returns using the same currency eg BTC
|
||||
func (b *Base) GetCurrencyForRealisedPNL(_ asset.Item, _ currency.Pair) (currency.Code, asset.Item, error) {
|
||||
return currency.Code{}, asset.Empty, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// HasAssetTypeAccountSegregation returns if the accounts are divided into asset
|
||||
// types instead of just being denoted as spot holdings.
|
||||
func (b *Base) HasAssetTypeAccountSegregation() bool {
|
||||
return b.Features.Supports.RESTCapabilities.HasAssetTypeAccountSegregation
|
||||
}
|
||||
|
||||
// GetMarginRatesHistory returns the margin rate history for the supplied currency
|
||||
func (b *Base) GetMarginRatesHistory(context.Context, *margin.RateHistoryRequest) (*margin.RateHistoryResponse, error) {
|
||||
return nil, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// GetPositionSummary returns stats for a future position
|
||||
func (b *Base) GetPositionSummary(context.Context, *order.PositionSummaryRequest) (*order.PositionSummary, error) {
|
||||
return nil, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// GetFundingPaymentDetails returns funding payment details for a future for a specific time period
|
||||
func (b *Base) GetFundingPaymentDetails(context.Context, *fundingrate.RatesRequest) (*fundingrate.Rates, error) {
|
||||
return nil, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// GetFuturesPositions returns futures positions for all currencies
|
||||
func (b *Base) GetFuturesPositions(context.Context, *order.PositionsRequest) ([]order.PositionDetails, error) {
|
||||
return nil, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// GetLatestFundingRate returns the latest funding rate based on request data
|
||||
func (b *Base) GetLatestFundingRate(context.Context, *fundingrate.LatestRateRequest) (*fundingrate.LatestRateResponse, error) {
|
||||
return nil, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// GetFundingRates returns funding rates based on request data
|
||||
func (b *Base) GetFundingRates(context.Context, *fundingrate.RatesRequest) (*fundingrate.Rates, error) {
|
||||
return nil, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// IsPerpetualFutureCurrency ensures a given asset and currency is a perpetual future
|
||||
// differs by exchange
|
||||
func (b *Base) IsPerpetualFutureCurrency(asset.Item, currency.Pair) (bool, error) {
|
||||
return false, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// GetKlineRequest returns a helper for the fetching of candle/kline data for
|
||||
// a single request within a pre-determined time window.
|
||||
func (b *Base) GetKlineRequest(pair currency.Pair, a asset.Item, interval kline.Interval, start, end time.Time, fixedAPICandleLength bool) (*kline.Request, error) {
|
||||
@@ -1688,3 +1625,109 @@ func (b *Base) GetStandardConfig() (*config.Exchange, error) {
|
||||
|
||||
return exchCfg, nil
|
||||
}
|
||||
|
||||
// Futures section
|
||||
|
||||
// CalculatePNL is an overridable function to allow PNL to be calculated on an
|
||||
// open position
|
||||
// It will also determine whether the position is considered to be liquidated
|
||||
// For live trading, an overriding function may wish to confirm the liquidation by
|
||||
// requesting the status of the asset
|
||||
func (b *Base) CalculatePNL(context.Context, *order.PNLCalculatorRequest) (*order.PNLResult, error) {
|
||||
return nil, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// ScaleCollateral is an overridable function to determine how much
|
||||
// collateral is usable in futures positions
|
||||
func (b *Base) ScaleCollateral(context.Context, *order.CollateralCalculator) (*collateral.ByCurrency, error) {
|
||||
return nil, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// CalculateTotalCollateral takes in n collateral calculators to determine an overall
|
||||
// standing in a singular currency
|
||||
func (b *Base) CalculateTotalCollateral(_ context.Context, _ *order.TotalCollateralCalculator) (*order.TotalCollateralResponse, error) {
|
||||
return nil, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// GetCollateralCurrencyForContract returns the collateral currency for an asset and contract pair
|
||||
func (b *Base) GetCollateralCurrencyForContract(_ asset.Item, _ currency.Pair) (currency.Code, asset.Item, error) {
|
||||
return currency.Code{}, asset.Empty, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// GetCurrencyForRealisedPNL returns where to put realised PNL
|
||||
// example 1: Bybit universal margin PNL is paid out in USD to your spot wallet
|
||||
// example 2: Binance coin margined futures pays returns using the same currency eg BTC
|
||||
func (b *Base) GetCurrencyForRealisedPNL(_ asset.Item, _ currency.Pair) (currency.Code, asset.Item, error) {
|
||||
return currency.Code{}, asset.Empty, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// GetMarginRatesHistory returns the margin rate history for the supplied currency
|
||||
func (b *Base) GetMarginRatesHistory(context.Context, *margin.RateHistoryRequest) (*margin.RateHistoryResponse, error) {
|
||||
return nil, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// GetFuturesPositionSummary returns stats for a future position
|
||||
func (b *Base) GetFuturesPositionSummary(context.Context, *order.PositionSummaryRequest) (*order.PositionSummary, error) {
|
||||
return nil, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// GetFundingPaymentDetails returns funding payment details for a future for a specific time period
|
||||
func (b *Base) GetFundingPaymentDetails(context.Context, *fundingrate.RatesRequest) (*fundingrate.Rates, error) {
|
||||
return nil, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// GetFuturesPositions returns futures positions for all currencies
|
||||
func (b *Base) GetFuturesPositions(context.Context, *order.PositionsRequest) ([]order.PositionDetails, error) {
|
||||
return nil, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// GetFuturesPositionOrders returns futures positions orders
|
||||
func (b *Base) GetFuturesPositionOrders(context.Context, *order.PositionsRequest) ([]order.PositionResponse, error) {
|
||||
return nil, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// GetLatestFundingRate returns the latest funding rate based on request data
|
||||
func (b *Base) GetLatestFundingRate(context.Context, *fundingrate.LatestRateRequest) (*fundingrate.LatestRateResponse, error) {
|
||||
return nil, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// GetFundingRates returns funding rates based on request data
|
||||
func (b *Base) GetFundingRates(context.Context, *fundingrate.RatesRequest) (*fundingrate.Rates, error) {
|
||||
return nil, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// IsPerpetualFutureCurrency ensures a given asset and currency is a perpetual future
|
||||
// differs by exchange
|
||||
func (b *Base) IsPerpetualFutureCurrency(asset.Item, currency.Pair) (bool, error) {
|
||||
return false, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// SetCollateralMode sets the account's collateral mode for the asset type
|
||||
func (b *Base) SetCollateralMode(_ context.Context, _ asset.Item, _ collateral.Mode) error {
|
||||
return common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// GetCollateralMode returns the account's collateral mode for the asset type
|
||||
func (b *Base) GetCollateralMode(_ context.Context, _ asset.Item) (collateral.Mode, error) {
|
||||
return 0, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// SetMarginType sets the account's margin type for the asset type
|
||||
func (b *Base) SetMarginType(_ context.Context, _ asset.Item, _ currency.Pair, _ margin.Type) error {
|
||||
return common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// ChangePositionMargin changes the margin type for a position
|
||||
func (b *Base) ChangePositionMargin(_ context.Context, _ *margin.PositionChangeRequest) (*margin.PositionChangeResponse, error) {
|
||||
return nil, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// SetLeverage sets the account's initial leverage for the asset type and pair
|
||||
func (b *Base) SetLeverage(_ context.Context, _ asset.Item, _ currency.Pair, _ margin.Type, _ float64, _ order.Side) error {
|
||||
return common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// GetLeverage gets the account's initial leverage for the asset type and pair
|
||||
func (b *Base) GetLeverage(_ context.Context, _ asset.Item, _ currency.Pair, _ margin.Type, _ order.Side) (float64, error) {
|
||||
return -1, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
@@ -12,7 +12,10 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/config"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/collateral"
|
||||
"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/protocol"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
|
||||
@@ -2505,7 +2508,7 @@ func TestGetMarginRateHistory(t *testing.T) {
|
||||
func TestGetPositionSummary(t *testing.T) {
|
||||
t.Parallel()
|
||||
var b Base
|
||||
if _, err := b.GetPositionSummary(context.Background(), nil); !errors.Is(err, common.ErrNotYetImplemented) {
|
||||
if _, err := b.GetFuturesPositionSummary(context.Background(), nil); !errors.Is(err, common.ErrNotYetImplemented) {
|
||||
t.Errorf("received: %v, expected: %v", err, common.ErrNotYetImplemented)
|
||||
}
|
||||
}
|
||||
@@ -2513,7 +2516,7 @@ func TestGetPositionSummary(t *testing.T) {
|
||||
func TestGetFuturesPositions(t *testing.T) {
|
||||
t.Parallel()
|
||||
var b Base
|
||||
if _, err := b.GetFuturesPositions(context.Background(), nil); !errors.Is(err, common.ErrNotYetImplemented) {
|
||||
if _, err := b.GetFuturesPositionOrders(context.Background(), nil); !errors.Is(err, common.ErrNotYetImplemented) {
|
||||
t.Errorf("received: %v, expected: %v", err, common.ErrNotYetImplemented)
|
||||
}
|
||||
}
|
||||
@@ -2954,6 +2957,60 @@ func TestGetKlineExtendedRequest(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetCollateralMode(t *testing.T) {
|
||||
t.Parallel()
|
||||
b := Base{}
|
||||
err := b.SetCollateralMode(context.Background(), asset.Spot, collateral.SingleMode)
|
||||
if !errors.Is(err, common.ErrNotYetImplemented) {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCollateralMode(t *testing.T) {
|
||||
t.Parallel()
|
||||
b := Base{}
|
||||
_, err := b.GetCollateralMode(context.Background(), asset.Spot)
|
||||
if !errors.Is(err, common.ErrNotYetImplemented) {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetMarginType(t *testing.T) {
|
||||
t.Parallel()
|
||||
b := Base{}
|
||||
err := b.SetMarginType(context.Background(), asset.Spot, currency.NewBTCUSD(), margin.Multi)
|
||||
if !errors.Is(err, common.ErrNotYetImplemented) {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChangePositionMargin(t *testing.T) {
|
||||
t.Parallel()
|
||||
b := Base{}
|
||||
_, err := b.ChangePositionMargin(context.Background(), nil)
|
||||
if !errors.Is(err, common.ErrNotYetImplemented) {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetLeverage(t *testing.T) {
|
||||
t.Parallel()
|
||||
b := Base{}
|
||||
err := b.SetLeverage(context.Background(), asset.Spot, currency.NewBTCUSD(), margin.Multi, 1, order.UnknownSide)
|
||||
if !errors.Is(err, common.ErrNotYetImplemented) {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetLeverage(t *testing.T) {
|
||||
t.Parallel()
|
||||
b := Base{}
|
||||
_, err := b.GetLeverage(context.Background(), asset.Spot, currency.NewBTCUSD(), margin.Multi, order.UnknownSide)
|
||||
if !errors.Is(err, common.ErrNotYetImplemented) {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureOnePairEnabled(t *testing.T) {
|
||||
t.Parallel()
|
||||
b := Base{Name: "test"}
|
||||
|
||||
@@ -178,13 +178,20 @@ type FeaturesSupported struct {
|
||||
// FuturesCapabilities stores the exchange's futures capabilities
|
||||
type FuturesCapabilities struct {
|
||||
FundingRates bool
|
||||
MaximumFundingRateHistory time.Duration
|
||||
FundingRateFrequency time.Duration
|
||||
Positions bool
|
||||
OrderManagerPositionTracking bool
|
||||
Collateral bool
|
||||
CollateralMode bool
|
||||
Leverage bool
|
||||
MaximumFundingRateHistory time.Duration
|
||||
FundingRateFrequency time.Duration
|
||||
}
|
||||
|
||||
// MarginCapabilities stores the exchange's margin capabilities
|
||||
type MarginCapabilities struct {
|
||||
SetMarginType bool
|
||||
ChangePositionMargin bool
|
||||
GetMarginRateHistory bool
|
||||
}
|
||||
|
||||
// Endpoints stores running url endpoints for exchanges
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"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/currencystate"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/deposit"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/fundingrate"
|
||||
@@ -31,6 +32,8 @@ type IBotExchange interface {
|
||||
Shutdown() error
|
||||
GetName() string
|
||||
SetEnabled(bool)
|
||||
GetEnabledFeatures() FeaturesEnabled
|
||||
GetSupportedFeatures() FeaturesSupported
|
||||
FetchTicker(ctx context.Context, p currency.Pair, a asset.Item) (*ticker.Price, error)
|
||||
UpdateTicker(ctx context.Context, p currency.Pair, a asset.Item) (*ticker.Price, error)
|
||||
UpdateTickers(ctx context.Context, a asset.Item) error
|
||||
@@ -92,6 +95,7 @@ type IBotExchange interface {
|
||||
OrderManagement
|
||||
CurrencyStateManagement
|
||||
FuturesManagement
|
||||
MarginManagement
|
||||
}
|
||||
|
||||
// OrderManagement defines functionality for order management
|
||||
@@ -141,14 +145,26 @@ type FunctionalityChecker interface {
|
||||
|
||||
// FuturesManagement manages futures orders, pnl and collateral calculations
|
||||
type FuturesManagement interface {
|
||||
GetPositionSummary(context.Context, *order.PositionSummaryRequest) (*order.PositionSummary, error)
|
||||
ScaleCollateral(ctx context.Context, calculator *order.CollateralCalculator) (*order.CollateralByCurrency, error)
|
||||
ScaleCollateral(ctx context.Context, calculator *order.CollateralCalculator) (*collateral.ByCurrency, error)
|
||||
CalculateTotalCollateral(context.Context, *order.TotalCollateralCalculator) (*order.TotalCollateralResponse, error)
|
||||
GetFuturesPositions(context.Context, *order.PositionsRequest) ([]order.PositionDetails, error)
|
||||
GetFundingRates(context.Context, *fundingrate.RatesRequest) (*fundingrate.Rates, error)
|
||||
GetLatestFundingRate(context.Context, *fundingrate.LatestRateRequest) (*fundingrate.LatestRateResponse, error)
|
||||
IsPerpetualFutureCurrency(asset.Item, currency.Pair) (bool, error)
|
||||
GetCollateralCurrencyForContract(asset.Item, currency.Pair) (currency.Code, asset.Item, error)
|
||||
GetMarginRatesHistory(context.Context, *margin.RateHistoryRequest) (*margin.RateHistoryResponse, error)
|
||||
order.PNLCalculation
|
||||
|
||||
GetFuturesPositionSummary(context.Context, *order.PositionSummaryRequest) (*order.PositionSummary, error)
|
||||
GetFuturesPositionOrders(context.Context, *order.PositionsRequest) ([]order.PositionResponse, error)
|
||||
SetCollateralMode(ctx context.Context, item asset.Item, mode collateral.Mode) error
|
||||
GetCollateralMode(ctx context.Context, item asset.Item) (collateral.Mode, error)
|
||||
SetLeverage(ctx context.Context, item asset.Item, pair currency.Pair, marginType margin.Type, amount float64, orderSide order.Side) error
|
||||
GetLeverage(ctx context.Context, item asset.Item, pair currency.Pair, marginType margin.Type, orderSide order.Side) (float64, error)
|
||||
}
|
||||
|
||||
// MarginManagement manages margin positions and rates
|
||||
type MarginManagement interface {
|
||||
SetMarginType(ctx context.Context, item asset.Item, pair currency.Pair, tp margin.Type) error
|
||||
ChangePositionMargin(ctx context.Context, change *margin.PositionChangeRequest) (*margin.PositionChangeResponse, error)
|
||||
GetMarginRatesHistory(context.Context, *margin.RateHistoryRequest) (*margin.RateHistoryResponse, error)
|
||||
}
|
||||
|
||||
67
exchanges/margin/margin.go
Normal file
67
exchanges/margin/margin.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package margin
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Valid returns whether the margin type is valid
|
||||
func (t Type) Valid() bool {
|
||||
return t != Unset && supported&t == t
|
||||
}
|
||||
|
||||
// UnmarshalJSON converts json into margin type
|
||||
func (t *Type) UnmarshalJSON(d []byte) error {
|
||||
var marginType string
|
||||
err := json.Unmarshal(d, &marginType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*t, err = StringToMarginType(marginType)
|
||||
return err
|
||||
}
|
||||
|
||||
// String returns the string representation of the margin type in lowercase
|
||||
// the absence of a lower func should hopefully highlight that String is lower
|
||||
func (t Type) String() string {
|
||||
switch t {
|
||||
case Unset:
|
||||
return unsetStr
|
||||
case Isolated:
|
||||
return isolatedStr
|
||||
case Multi:
|
||||
return multiStr
|
||||
case Unknown:
|
||||
return unknownStr
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Upper returns the upper case string representation of the margin type
|
||||
func (t Type) Upper() string {
|
||||
return strings.ToUpper(t.String())
|
||||
}
|
||||
|
||||
// IsValidString checks to see if the supplied string is a valid margin type
|
||||
func IsValidString(m string) bool {
|
||||
switch strings.ToLower(m) {
|
||||
case isolatedStr, multiStr, unsetStr, crossedStr, crossStr:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// StringToMarginType converts a string to a margin type
|
||||
// doesn't error, just returns unknown if the string is not recognised
|
||||
func StringToMarginType(m string) (Type, error) {
|
||||
switch strings.ToLower(m) {
|
||||
case isolatedStr:
|
||||
return Isolated, nil
|
||||
case multiStr, crossedStr, crossStr:
|
||||
return Multi, nil
|
||||
case "":
|
||||
return Unset, nil
|
||||
}
|
||||
return Unknown, fmt.Errorf("%w %v", ErrInvalidMarginType, m)
|
||||
}
|
||||
162
exchanges/margin/margin_test.go
Normal file
162
exchanges/margin/margin_test.go
Normal file
@@ -0,0 +1,162 @@
|
||||
package margin
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestValid(t *testing.T) {
|
||||
t.Parallel()
|
||||
if !Isolated.Valid() {
|
||||
t.Fatal("expected 'true', received 'false'")
|
||||
}
|
||||
if !Multi.Valid() {
|
||||
t.Fatal("expected 'true', received 'false'")
|
||||
}
|
||||
if Unset.Valid() {
|
||||
t.Fatal("expected 'false', received 'true'")
|
||||
}
|
||||
if Unknown.Valid() {
|
||||
t.Fatal("expected 'false', received 'true'")
|
||||
}
|
||||
if Type(137).Valid() {
|
||||
t.Fatal("expected 'false', received 'true'")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalJSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
type martian struct {
|
||||
M Type `json:"margin"`
|
||||
}
|
||||
|
||||
var alien martian
|
||||
jason := []byte(`{"margin":"isolated"}`)
|
||||
err := json.Unmarshal(jason, &alien)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if alien.M != Isolated {
|
||||
t.Errorf("received '%v' expected 'isolated'", alien.M)
|
||||
}
|
||||
|
||||
jason = []byte(`{"margin":"cross"}`)
|
||||
err = json.Unmarshal(jason, &alien)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if alien.M != Multi {
|
||||
t.Errorf("received '%v' expected 'Multi'", alien.M)
|
||||
}
|
||||
|
||||
jason = []byte(`{"margin":"hello moto"}`)
|
||||
err = json.Unmarshal(jason, &alien)
|
||||
if !errors.Is(err, ErrInvalidMarginType) {
|
||||
t.Error(err)
|
||||
}
|
||||
if alien.M != Unknown {
|
||||
t.Errorf("received '%v' expected 'isolated'", alien.M)
|
||||
}
|
||||
}
|
||||
|
||||
func TestString(t *testing.T) {
|
||||
t.Parallel()
|
||||
if Unknown.String() != unknownStr {
|
||||
t.Errorf("received '%v' expected '%v'", Unknown.String(), unknownStr)
|
||||
}
|
||||
if Isolated.String() != isolatedStr {
|
||||
t.Errorf("received '%v' expected '%v'", Isolated.String(), isolatedStr)
|
||||
}
|
||||
if Multi.String() != multiStr {
|
||||
t.Errorf("received '%v' expected '%v'", Multi.String(), multiStr)
|
||||
}
|
||||
if Unset.String() != unsetStr {
|
||||
t.Errorf("received '%v' expected '%v'", Unset.String(), unsetStr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpper(t *testing.T) {
|
||||
t.Parallel()
|
||||
if Unknown.Upper() != strings.ToUpper(unknownStr) {
|
||||
t.Errorf("received '%v' expected '%v'", Unknown.String(), strings.ToUpper(unknownStr))
|
||||
}
|
||||
if Isolated.Upper() != strings.ToUpper(isolatedStr) {
|
||||
t.Errorf("received '%v' expected '%v'", Isolated.String(), strings.ToUpper(isolatedStr))
|
||||
}
|
||||
if Multi.Upper() != strings.ToUpper(multiStr) {
|
||||
t.Errorf("received '%v' expected '%v'", Multi.String(), strings.ToUpper(multiStr))
|
||||
}
|
||||
if Unset.Upper() != strings.ToUpper(unsetStr) {
|
||||
t.Errorf("received '%v' expected '%v'", Unset.String(), strings.ToUpper(unsetStr))
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsValidString(t *testing.T) {
|
||||
t.Parallel()
|
||||
if IsValidString("lol") {
|
||||
t.Fatal("expected 'false', received 'true'")
|
||||
}
|
||||
if !IsValidString("isolated") {
|
||||
t.Fatal("expected 'true', received 'false'")
|
||||
}
|
||||
if !IsValidString("cross") {
|
||||
t.Fatal("expected 'true', received 'false'")
|
||||
}
|
||||
if !IsValidString("multi") {
|
||||
t.Fatal("expected 'true', received 'false'")
|
||||
}
|
||||
if !IsValidString("unset") {
|
||||
t.Fatal("expected 'true', received 'false'")
|
||||
}
|
||||
if IsValidString("") {
|
||||
t.Fatal("expected 'false', received 'true'")
|
||||
}
|
||||
if IsValidString("unknown") {
|
||||
t.Fatal("expected 'false', received 'true'")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringToMarginType(t *testing.T) {
|
||||
t.Parallel()
|
||||
resp, err := StringToMarginType("lol")
|
||||
if !errors.Is(err, ErrInvalidMarginType) {
|
||||
t.Error(err)
|
||||
}
|
||||
if resp != Unknown {
|
||||
t.Errorf("received '%v' expected '%v'", resp, Unknown)
|
||||
}
|
||||
|
||||
resp, err = StringToMarginType("")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if resp != Unset {
|
||||
t.Errorf("received '%v' expected '%v'", resp, Unset)
|
||||
}
|
||||
|
||||
resp, err = StringToMarginType("cross")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if resp != Multi {
|
||||
t.Errorf("received '%v' expected '%v'", resp, Multi)
|
||||
}
|
||||
|
||||
resp, err = StringToMarginType("multi")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if resp != Multi {
|
||||
t.Errorf("received '%v' expected '%v'", resp, Multi)
|
||||
}
|
||||
|
||||
resp, err = StringToMarginType("isolated")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if resp != Isolated {
|
||||
t.Errorf("received '%v' expected '%v'", resp, Isolated)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package margin
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
@@ -8,6 +9,17 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrInvalidMarginType returned when the margin type is invalid
|
||||
ErrInvalidMarginType = errors.New("invalid margin type")
|
||||
// ErrMarginTypeUnsupported returned when the margin type is unsupported
|
||||
ErrMarginTypeUnsupported = errors.New("unsupported margin type")
|
||||
// ErrNewAllocatedMarginRequired returned when the new allocated margin is missing and is required
|
||||
ErrNewAllocatedMarginRequired = errors.New("new allocated margin required")
|
||||
// ErrOriginalPositionMarginRequired is returned when original position margin is empty and is required
|
||||
ErrOriginalPositionMarginRequired = errors.New("original allocated margin required")
|
||||
)
|
||||
|
||||
// RateHistoryRequest is used to request a funding rate
|
||||
type RateHistoryRequest struct {
|
||||
Exchange string
|
||||
@@ -32,6 +44,55 @@ type RateHistoryRequest struct {
|
||||
Rates []Rate
|
||||
}
|
||||
|
||||
// PositionChangeRequest used for wrapper functions to change margin fields for a position
|
||||
type PositionChangeRequest struct {
|
||||
// Required fields
|
||||
Exchange string
|
||||
Pair currency.Pair
|
||||
Asset asset.Item
|
||||
// Optional fields depending on desired outcome/exchange requirements
|
||||
MarginType Type
|
||||
OriginalAllocatedMargin float64
|
||||
NewAllocatedMargin float64
|
||||
MarginSide string
|
||||
}
|
||||
|
||||
// PositionChangeResponse holds response data for margin change requests
|
||||
type PositionChangeResponse struct {
|
||||
Exchange string
|
||||
Pair currency.Pair
|
||||
Asset asset.Item
|
||||
AllocatedMargin float64
|
||||
MarginType Type
|
||||
}
|
||||
|
||||
// Type defines the different margin types supported by exchanges
|
||||
type Type uint8
|
||||
|
||||
// Margin types
|
||||
const (
|
||||
// Unset is the default value
|
||||
Unset = Type(0)
|
||||
// Isolated means a margin trade is isolated from other margin trades
|
||||
Isolated Type = 1 << (iota - 1)
|
||||
// Multi means a margin trade is not isolated from other margin trades
|
||||
// it can sometimes be referred to as "cross"
|
||||
Multi
|
||||
// Unknown is an unknown margin type but is not unset
|
||||
Unknown
|
||||
)
|
||||
|
||||
var supported = Isolated | Multi
|
||||
|
||||
const (
|
||||
unsetStr = "unset"
|
||||
isolatedStr = "isolated"
|
||||
multiStr = "multi"
|
||||
crossedStr = "crossed"
|
||||
crossStr = "cross"
|
||||
unknownStr = "unknown"
|
||||
)
|
||||
|
||||
// RateHistoryResponse has the funding rate details
|
||||
type RateHistoryResponse struct {
|
||||
Rates []Rate
|
||||
|
||||
@@ -284,6 +284,7 @@ var (
|
||||
errMissingValidGreeksType = errors.New("missing valid greeks type")
|
||||
errMissingIsolatedMarginTradingSetting = errors.New("missing isolated margin trading setting, isolated margin trading settings automatic:Auto transfers autonomy:Manual transfers")
|
||||
errInvalidOrderSide = errors.New("invalid order side")
|
||||
errOrderSideRequired = errors.New("order side required")
|
||||
errInvalidCounterParties = errors.New("missing counter parties")
|
||||
errInvalidLegs = errors.New("no legs are provided")
|
||||
errMissingRFQIDANDClientSuppliedRFQID = errors.New("missing rfq id or client supplied rfq id")
|
||||
@@ -338,6 +339,7 @@ var (
|
||||
errInvalidProtocolType = errors.New("invalid protocol type, only 'staking' and 'defi' allowed")
|
||||
errExceedLimit = errors.New("limit exceeded")
|
||||
errOnlyThreeMonthsSupported = errors.New("only three months of trade data retrieval supported")
|
||||
errOnlyOneResponseExpected = errors.New("one response item expected")
|
||||
errNoInstrumentFound = errors.New("no instrument found")
|
||||
)
|
||||
|
||||
@@ -1859,9 +1861,9 @@ func (ok *Okx) GetConvertHistory(ctx context.Context, before, after time.Time, l
|
||||
|
||||
/********************************** Account endpoints ***************************************************/
|
||||
|
||||
// GetNonZeroBalances retrieves a list of assets (with non-zero balance), remaining balance, and available amount in the trading account.
|
||||
// AccountBalance retrieves a list of assets (with non-zero balance), remaining balance, and available amount in the trading account.
|
||||
// Interest-free quota and discount rates are public data and not displayed on the account interface.
|
||||
func (ok *Okx) GetNonZeroBalances(ctx context.Context, currency string) ([]Account, error) {
|
||||
func (ok *Okx) AccountBalance(ctx context.Context, currency string) ([]Account, error) {
|
||||
var resp []Account
|
||||
params := url.Values{}
|
||||
if currency != "" {
|
||||
@@ -2010,26 +2012,12 @@ func (ok *Okx) SetPositionMode(ctx context.Context, positionMode string) (string
|
||||
return "", errNoValidResponseFromServer
|
||||
}
|
||||
|
||||
// SetLeverage sets a leverage setting for instrument id.
|
||||
func (ok *Okx) SetLeverage(ctx context.Context, arg SetLeverageInput) (*SetLeverageResponse, error) {
|
||||
// SetLeverageRate sets a leverage setting for instrument id.
|
||||
func (ok *Okx) SetLeverageRate(ctx context.Context, arg SetLeverageInput) (*SetLeverageResponse, error) {
|
||||
if arg.InstrumentID == "" && arg.Currency == "" {
|
||||
return nil, errors.New("either instrument id or currency is required")
|
||||
}
|
||||
if arg.Leverage < 0 {
|
||||
return nil, errors.New("missing leverage")
|
||||
}
|
||||
if arg.InstrumentID == "" && arg.MarginMode == TradeModeIsolated {
|
||||
return nil, errors.New("only can be cross if ccy is passed")
|
||||
}
|
||||
if arg.MarginMode != TradeModeCross && arg.MarginMode != TradeModeIsolated {
|
||||
return nil, errors.New("only applicable to \"isolated\" margin mode of FUTURES/SWAP allowed")
|
||||
}
|
||||
arg.PositionSide = strings.ToLower(arg.PositionSide)
|
||||
if arg.PositionSide != positionSideLong &&
|
||||
arg.PositionSide != positionSideShort &&
|
||||
arg.MarginMode == "isolated" {
|
||||
return nil, errors.New("\"long\" \"short\" Only applicable to isolated margin mode of FUTURES/SWAP")
|
||||
}
|
||||
var resp []SetLeverageResponse
|
||||
err := ok.SendHTTPRequest(ctx, exchange.RestSpot, setLeverageEPL, http.MethodPost, accountSetLeverage, &arg, &resp, true)
|
||||
if err != nil {
|
||||
@@ -2107,7 +2095,7 @@ func (ok *Okx) IncreaseDecreaseMargin(ctx context.Context, arg IncreaseDecreaseM
|
||||
return nil, errors.New("missing valid amount")
|
||||
}
|
||||
var resp []IncreaseDecreaseMargin
|
||||
err := ok.SendHTTPRequest(ctx, exchange.RestSpot, increaseOrDecreaseMarginEPL, http.MethodGet, accountPositionMarginBalance, &arg, &resp, true)
|
||||
err := ok.SendHTTPRequest(ctx, exchange.RestSpot, increaseOrDecreaseMarginEPL, http.MethodPost, accountPositionMarginBalance, &arg, &resp, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -2117,8 +2105,8 @@ func (ok *Okx) IncreaseDecreaseMargin(ctx context.Context, arg IncreaseDecreaseM
|
||||
return nil, errNoValidResponseFromServer
|
||||
}
|
||||
|
||||
// GetLeverage retrieves leverage data for different instrument id or margin mode.
|
||||
func (ok *Okx) GetLeverage(ctx context.Context, instrumentID, marginMode string) ([]LeverageResponse, error) {
|
||||
// GetLeverageRate retrieves leverage data for different instrument id or margin mode.
|
||||
func (ok *Okx) GetLeverageRate(ctx context.Context, instrumentID, marginMode string) ([]LeverageResponse, error) {
|
||||
params := url.Values{}
|
||||
if instrumentID != "" {
|
||||
params.Set("instId", instrumentID)
|
||||
@@ -3461,10 +3449,8 @@ func (ok *Okx) GetFundingRateHistory(ctx context.Context, instrumentID string, b
|
||||
if !after.IsZero() {
|
||||
params.Set("after", strconv.FormatInt(after.UnixMilli(), 10))
|
||||
}
|
||||
if limit > 0 && limit <= 100 {
|
||||
if limit > 0 {
|
||||
params.Set("limit", strconv.FormatInt(limit, 10))
|
||||
} else if limit > 100 {
|
||||
return nil, errLimitValueExceedsMaxOf100
|
||||
}
|
||||
var resp []FundingRateResponse
|
||||
return resp, ok.SendHTTPRequest(ctx, exchange.RestSpot, getFundingRateHistoryEPL, http.MethodGet, common.EncodeURLValues(publicFundingRateHistory, params), nil, &resp, false)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1237,72 +1237,72 @@ type Account struct {
|
||||
|
||||
// AccountDetail account detail information.
|
||||
type AccountDetail struct {
|
||||
AvailableBalance okxNumericalValue `json:"availBal"`
|
||||
AvailableEquity okxNumericalValue `json:"availEq"`
|
||||
CashBalance okxNumericalValue `json:"cashBal"` // Cash Balance
|
||||
Currency string `json:"ccy"`
|
||||
CrossLiab okxNumericalValue `json:"crossLiab"`
|
||||
DiscountEquity okxNumericalValue `json:"disEq"`
|
||||
EquityOfCurrency okxNumericalValue `json:"eq"`
|
||||
EquityUsd okxNumericalValue `json:"eqUsd"`
|
||||
FrozenBalance okxNumericalValue `json:"frozenBal"`
|
||||
Interest okxNumericalValue `json:"interest"`
|
||||
IsoEquity okxNumericalValue `json:"isoEq"`
|
||||
IsolatedLiabilities okxNumericalValue `json:"isoLiab"`
|
||||
IsoUpl okxNumericalValue `json:"isoUpl"` // Isolated unrealized profit and loss of the currency applicable to Single-currency margin and Multi-currency margin and Portfolio margin
|
||||
LiabilitiesOfCurrency okxNumericalValue `json:"liab"`
|
||||
MaxLoan okxNumericalValue `json:"maxLoan"`
|
||||
MarginRatio okxNumericalValue `json:"mgnRatio"` // Equity of the currency
|
||||
NotionalLever okxNumericalValue `json:"notionalLever"` // Leverage of the currency applicable to Single-currency margin
|
||||
OpenOrdersMarginFrozen okxNumericalValue `json:"ordFrozen"`
|
||||
Twap okxNumericalValue `json:"twap"`
|
||||
UpdateTime okxUnixMilliTime `json:"uTime"`
|
||||
UnrealizedProfit okxNumericalValue `json:"upl"`
|
||||
UnrealizedCurrencyLiabilities okxNumericalValue `json:"uplLiab"`
|
||||
StrategyEquity okxNumericalValue `json:"stgyEq"` // strategy equity
|
||||
TotalEquity okxNumericalValue `json:"totalEq"` // Total equity in USD level
|
||||
AvailableBalance convert.StringToFloat64 `json:"availBal"`
|
||||
AvailableEquity convert.StringToFloat64 `json:"availEq"`
|
||||
CashBalance convert.StringToFloat64 `json:"cashBal"` // Cash Balance
|
||||
Currency string `json:"ccy"`
|
||||
CrossLiab convert.StringToFloat64 `json:"crossLiab"`
|
||||
DiscountEquity convert.StringToFloat64 `json:"disEq"`
|
||||
EquityOfCurrency convert.StringToFloat64 `json:"eq"`
|
||||
EquityUsd convert.StringToFloat64 `json:"eqUsd"`
|
||||
FrozenBalance convert.StringToFloat64 `json:"frozenBal"`
|
||||
Interest convert.StringToFloat64 `json:"interest"`
|
||||
IsoEquity convert.StringToFloat64 `json:"isoEq"`
|
||||
IsolatedLiabilities convert.StringToFloat64 `json:"isoLiab"`
|
||||
IsoUpl convert.StringToFloat64 `json:"isoUpl"` // Isolated unrealized profit and loss of the currency applicable to Single-currency margin and Multi-currency margin and Portfolio margin
|
||||
LiabilitiesOfCurrency convert.StringToFloat64 `json:"liab"`
|
||||
MaxLoan convert.StringToFloat64 `json:"maxLoan"`
|
||||
MarginRatio convert.StringToFloat64 `json:"mgnRatio"` // Equity of the currency
|
||||
NotionalLever convert.StringToFloat64 `json:"notionalLever"` // Leverage of the currency applicable to Single-currency margin
|
||||
OpenOrdersMarginFrozen convert.StringToFloat64 `json:"ordFrozen"`
|
||||
Twap convert.StringToFloat64 `json:"twap"`
|
||||
UpdateTime okxUnixMilliTime `json:"uTime"`
|
||||
UnrealizedProfit convert.StringToFloat64 `json:"upl"`
|
||||
UnrealizedCurrencyLiabilities convert.StringToFloat64 `json:"uplLiab"`
|
||||
StrategyEquity convert.StringToFloat64 `json:"stgyEq"` // strategy equity
|
||||
TotalEquity convert.StringToFloat64 `json:"totalEq"` // Total equity in USD level. Appears unused
|
||||
}
|
||||
|
||||
// AccountPosition account position.
|
||||
type AccountPosition struct {
|
||||
AutoDeleveraging string `json:"adl"` // Auto-deleveraging (ADL) indicator Divided into 5 levels, from 1 to 5, the smaller the number, the weaker the adl intensity.
|
||||
AvailablePosition string `json:"availPos"` // Position that can be closed Only applicable to MARGIN, FUTURES/SWAP in the long-short mode, OPTION in Simple and isolated OPTION in margin Account.
|
||||
AveragePrice string `json:"avgPx"`
|
||||
CreationTime okxUnixMilliTime `json:"cTime"`
|
||||
Currency string `json:"ccy"`
|
||||
DeltaBS string `json:"deltaBS"` // delta:Black-Scholes Greeks in dollars,only applicable to OPTION
|
||||
DeltaPA string `json:"deltaPA"` // delta:Greeks in coins,only applicable to OPTION
|
||||
GammaBS string `json:"gammaBS"` // gamma:Black-Scholes Greeks in dollars,only applicable to OPTION
|
||||
GammaPA string `json:"gammaPA"` // gamma:Greeks in coins,only applicable to OPTION
|
||||
InitialMarginRequirement string `json:"imr"` // Initial margin requirement, only applicable to cross.
|
||||
InstrumentID string `json:"instId"`
|
||||
InstrumentType string `json:"instType"`
|
||||
Interest string `json:"interest"`
|
||||
USDPrice string `json:"usdPx"`
|
||||
LastTradePrice string `json:"last"`
|
||||
Leverage string `json:"lever"` // Leverage, not applicable to OPTION seller
|
||||
Liabilities string `json:"liab"` // Liabilities, only applicable to MARGIN.
|
||||
LiabilitiesCurrency string `json:"liabCcy"` // Liabilities currency, only applicable to MARGIN.
|
||||
LiquidationPrice string `json:"liqPx"` // Estimated liquidation price Not applicable to OPTION
|
||||
MarkPx string `json:"markPx"`
|
||||
Margin string `json:"margin"`
|
||||
MgnMode string `json:"mgnMode"`
|
||||
MgnRatio string `json:"mgnRatio"`
|
||||
MaintenanceMarginRequirement string `json:"mmr"` // Maintenance margin requirement in USD level Applicable to Multi-currency margin and Portfolio margin
|
||||
NotionalUsd string `json:"notionalUsd"` // Quality of Positions -- usd
|
||||
OptionValue string `json:"optVal"` // Option Value, only application to position.
|
||||
QuantityOfPosition string `json:"pos"` // Quantity of positions,In the mode of autonomous transfer from position to position, after the deposit is transferred, a position with pos of 0 will be generated
|
||||
PositionCurrency string `json:"posCcy"`
|
||||
PositionID string `json:"posId"`
|
||||
PositionSide string `json:"posSide"`
|
||||
ThetaBS string `json:"thetaBS"` // theta:Black-Scholes Greeks in dollars,only applicable to OPTION
|
||||
ThetaPA string `json:"thetaPA"` // theta:Greeks in coins,only applicable to OPTION
|
||||
TradeID string `json:"tradeId"`
|
||||
UpdatedTime okxUnixMilliTime `json:"uTime"` // Latest time position was adjusted,
|
||||
Upl float64 `json:"upl,string,omitempty"` // Unrealized profit and loss
|
||||
UPLRatio float64 `json:"uplRatio,string,omitempty"` // Unrealized profit and loss ratio
|
||||
VegaBS string `json:"vegaBS"` // vega:Black-Scholes Greeks in dollars,only applicable to OPTION
|
||||
VegaPA string `json:"vegaPA"` // vega:Greeks in coins,only applicable to OPTION
|
||||
AutoDeleveraging string `json:"adl"` // Auto-deleveraging (ADL) indicator Divided into 5 levels, from 1 to 5, the smaller the number, the weaker the adl intensity.
|
||||
AvailablePosition string `json:"availPos"` // Position that can be closed Only applicable to MARGIN, FUTURES/SWAP in the long-short mode, OPTION in Simple and isolated OPTION in margin Account.
|
||||
AveragePrice convert.StringToFloat64 `json:"avgPx"`
|
||||
CreationTime okxUnixMilliTime `json:"cTime"`
|
||||
Currency string `json:"ccy"`
|
||||
DeltaBS string `json:"deltaBS"` // delta:Black-Scholes Greeks in dollars,only applicable to OPTION
|
||||
DeltaPA string `json:"deltaPA"` // delta:Greeks in coins,only applicable to OPTION
|
||||
GammaBS string `json:"gammaBS"` // gamma:Black-Scholes Greeks in dollars,only applicable to OPTION
|
||||
GammaPA string `json:"gammaPA"` // gamma:Greeks in coins,only applicable to OPTION
|
||||
InitialMarginRequirement convert.StringToFloat64 `json:"imr"` // Initial margin requirement, only applicable to cross.
|
||||
InstrumentID string `json:"instId"`
|
||||
InstrumentType asset.Item `json:"instType"`
|
||||
Interest convert.StringToFloat64 `json:"interest"`
|
||||
USDPrice convert.StringToFloat64 `json:"usdPx"`
|
||||
LastTradePrice convert.StringToFloat64 `json:"last"`
|
||||
Leverage convert.StringToFloat64 `json:"lever"` // Leverage, not applicable to OPTION seller
|
||||
Liabilities string `json:"liab"` // Liabilities, only applicable to MARGIN.
|
||||
LiabilitiesCurrency string `json:"liabCcy"` // Liabilities currency, only applicable to MARGIN.
|
||||
LiquidationPrice convert.StringToFloat64 `json:"liqPx"` // Estimated liquidation price Not applicable to OPTION
|
||||
MarkPrice convert.StringToFloat64 `json:"markPx"`
|
||||
Margin convert.StringToFloat64 `json:"margin"`
|
||||
MarginMode string `json:"mgnMode"`
|
||||
MarginRatio convert.StringToFloat64 `json:"mgnRatio"`
|
||||
MaintenanceMarginRequirement convert.StringToFloat64 `json:"mmr"` // Maintenance margin requirement in USD level Applicable to Multi-currency margin and Portfolio margin
|
||||
NotionalUsd convert.StringToFloat64 `json:"notionalUsd"` // Quality of Positions -- usd
|
||||
OptionValue convert.StringToFloat64 `json:"optVal"` // Option Value, only application to position.
|
||||
QuantityOfPosition convert.StringToFloat64 `json:"pos"` // Quantity of positions,In the mode of autonomous transfer from position to position, after the deposit is transferred, a position with pos of 0 will be generated
|
||||
PositionCurrency string `json:"posCcy"`
|
||||
PositionID string `json:"posId"`
|
||||
PositionSide string `json:"posSide"`
|
||||
ThetaBS string `json:"thetaBS"` // theta:Black-Scholes Greeks in dollars,only applicable to OPTION
|
||||
ThetaPA string `json:"thetaPA"` // theta:Greeks in coins,only applicable to OPTION
|
||||
TradeID string `json:"tradeId"`
|
||||
UpdatedTime okxUnixMilliTime `json:"uTime"` // Latest time position was adjusted,
|
||||
UPNL convert.StringToFloat64 `json:"upl"` // Unrealized profit and loss
|
||||
UPLRatio convert.StringToFloat64 `json:"uplRatio"` // Unrealized profit and loss ratio
|
||||
VegaBS string `json:"vegaBS"` // vega:Black-Scholes Greeks in dollars,only applicable to OPTION
|
||||
VegaPA string `json:"vegaPA"` // vega:Greeks in coins,only applicable to OPTION
|
||||
|
||||
// PushTime added feature in the websocket push data.
|
||||
|
||||
@@ -1421,19 +1421,19 @@ type PositionMode struct {
|
||||
|
||||
// SetLeverageInput represents set leverage request input
|
||||
type SetLeverageInput struct {
|
||||
Leverage int `json:"lever,string"` // set leverage for isolated
|
||||
MarginMode string `json:"mgnMode"` // Margin Mode "cross" and "isolated"
|
||||
InstrumentID string `json:"instId,omitempty"` // Optional:
|
||||
Currency string `json:"ccy,omitempty"` // Optional:
|
||||
PositionSide string `json:"posSide,omitempty"`
|
||||
Leverage float64 `json:"lever,string"` // set leverage for isolated
|
||||
MarginMode string `json:"mgnMode"` // Margin Mode "cross" and "isolated"
|
||||
InstrumentID string `json:"instId,omitempty"` // Optional:
|
||||
Currency string `json:"ccy,omitempty"` // Optional:
|
||||
PositionSide string `json:"posSide,omitempty"`
|
||||
}
|
||||
|
||||
// SetLeverageResponse represents set leverage response
|
||||
type SetLeverageResponse struct {
|
||||
Leverage string `json:"lever"`
|
||||
MarginMode string `json:"mgnMode"` // Margin Mode "cross" and "isolated"
|
||||
InstrumentID string `json:"instId"`
|
||||
PositionSide string `json:"posSide"` // "long", "short", and "net"
|
||||
Leverage okxNumericalValue `json:"lever"`
|
||||
MarginMode string `json:"mgnMode"` // Margin Mode "cross" and "isolated"
|
||||
InstrumentID string `json:"instId"`
|
||||
PositionSide string `json:"posSide"` // "long", "short", and "net"
|
||||
}
|
||||
|
||||
// MaximumBuyAndSell get maximum buy , sell amount or open amount
|
||||
@@ -1464,20 +1464,20 @@ type IncreaseDecreaseMarginInput struct {
|
||||
|
||||
// IncreaseDecreaseMargin represents increase or decrease the margin of the isolated position response
|
||||
type IncreaseDecreaseMargin struct {
|
||||
Amt string `json:"amt"`
|
||||
Ccy string `json:"ccy"`
|
||||
InstrumentID string `json:"instId"`
|
||||
Leverage string `json:"leverage"`
|
||||
PosSide string `json:"posSide"`
|
||||
Type string `json:"type"`
|
||||
Amount okxNumericalValue `json:"amt"`
|
||||
Ccy string `json:"ccy"`
|
||||
InstrumentID string `json:"instId"`
|
||||
Leverage okxNumericalValue `json:"leverage"`
|
||||
PosSide string `json:"posSide"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// LeverageResponse instrument id leverage response.
|
||||
type LeverageResponse struct {
|
||||
InstrumentID string `json:"instId"`
|
||||
MarginMode string `json:"mgnMode"`
|
||||
PositionSide string `json:"posSide"`
|
||||
Leverage uint `json:"lever,string"`
|
||||
InstrumentID string `json:"instId"`
|
||||
MarginMode string `json:"mgnMode"`
|
||||
PositionSide string `json:"posSide"`
|
||||
Leverage okxNumericalValue `json:"lever"`
|
||||
}
|
||||
|
||||
// MaximumLoanInstrument represents maximum loan of an instrument id.
|
||||
|
||||
@@ -11,15 +11,18 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/config"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
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"
|
||||
@@ -68,11 +71,13 @@ func (ok *Okx) SetDefaults() {
|
||||
ok.API.CredentialsValidator.RequiresKey = true
|
||||
ok.API.CredentialsValidator.RequiresSecret = true
|
||||
ok.API.CredentialsValidator.RequiresClientID = true
|
||||
pairFormat := ¤cy.PairFormat{
|
||||
|
||||
cpf := ¤cy.PairFormat{
|
||||
Delimiter: currency.DashDelimiter,
|
||||
Uppercase: true,
|
||||
}
|
||||
err := ok.SetGlobalPairsManager(pairFormat, pairFormat, asset.Spot, asset.Futures, asset.PerpetualSwap, asset.Options, asset.Margin)
|
||||
|
||||
err := ok.SetGlobalPairsManager(cpf, cpf, asset.Spot, asset.Futures, asset.PerpetualSwap, asset.Options, asset.Margin)
|
||||
if err != nil {
|
||||
log.Errorln(log.ExchangeSys, err)
|
||||
}
|
||||
@@ -80,8 +85,9 @@ func (ok *Okx) SetDefaults() {
|
||||
// Fill out the capabilities/features that the exchange supports
|
||||
ok.Features = exchange.Features{
|
||||
Supports: exchange.FeaturesSupported{
|
||||
REST: true,
|
||||
Websocket: true,
|
||||
REST: true,
|
||||
Websocket: true,
|
||||
MaximumOrderHistory: kline.OneDay.Duration() * 90,
|
||||
RESTCapabilities: protocol.Features{
|
||||
TickerFetching: true,
|
||||
OrderbookFetching: true,
|
||||
@@ -123,6 +129,9 @@ func (ok *Okx) SetDefaults() {
|
||||
},
|
||||
WithdrawPermissions: exchange.AutoWithdrawCrypto,
|
||||
FuturesCapabilities: exchange.FuturesCapabilities{
|
||||
Positions: true,
|
||||
Leverage: true,
|
||||
CollateralMode: true,
|
||||
FundingRates: true,
|
||||
MaximumFundingRateHistory: kline.ThreeMonth.Duration(),
|
||||
FundingRateFrequency: kline.EightHour.Duration(),
|
||||
@@ -360,14 +369,14 @@ func (ok *Okx) UpdateOrderExecutionLimits(ctx context.Context, a asset.Item) err
|
||||
|
||||
// UpdateTicker updates and returns the ticker for a currency pair
|
||||
func (ok *Okx) UpdateTicker(ctx context.Context, p currency.Pair, a asset.Item) (*ticker.Price, error) {
|
||||
format, err := ok.GetPairFormat(a, false)
|
||||
pairFormat, err := ok.GetPairFormat(a, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if p.IsEmpty() {
|
||||
return nil, currency.ErrCurrencyPairEmpty
|
||||
}
|
||||
instrumentID := format.Format(p)
|
||||
instrumentID := pairFormat.Format(p)
|
||||
if !ok.SupportsAsset(a) {
|
||||
return nil, fmt.Errorf("%w: %v", asset.ErrNotSupported, a)
|
||||
}
|
||||
@@ -502,14 +511,14 @@ func (ok *Okx) UpdateOrderbook(ctx context.Context, pair currency.Pair, assetTyp
|
||||
return nil, err
|
||||
}
|
||||
var instrumentID string
|
||||
format, err := ok.GetPairFormat(assetType, false)
|
||||
pairFormat, err := ok.GetPairFormat(assetType, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !pair.IsPopulated() {
|
||||
return nil, errIncompleteCurrencyPair
|
||||
}
|
||||
instrumentID = format.Format(pair)
|
||||
instrumentID = pairFormat.Format(pair)
|
||||
orderbookNew, err = ok.GetOrderBookDepth(ctx, instrumentID, 400)
|
||||
if err != nil {
|
||||
return book, err
|
||||
@@ -552,7 +561,7 @@ func (ok *Okx) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (acc
|
||||
if !ok.SupportsAsset(assetType) {
|
||||
return info, fmt.Errorf("%w: %v", asset.ErrNotSupported, assetType)
|
||||
}
|
||||
accountBalances, err := ok.GetNonZeroBalances(ctx, "")
|
||||
accountBalances, err := ok.AccountBalance(ctx, "")
|
||||
if err != nil {
|
||||
return info, err
|
||||
}
|
||||
@@ -660,15 +669,14 @@ func (ok *Okx) GetWithdrawalsHistory(ctx context.Context, c currency.Code, _ ass
|
||||
|
||||
// GetRecentTrades returns the most recent trades for a currency and asset
|
||||
func (ok *Okx) GetRecentTrades(ctx context.Context, p currency.Pair, assetType asset.Item) ([]trade.Data, error) {
|
||||
format, err := ok.GetPairFormat(assetType, false)
|
||||
pairFormat, err := ok.GetPairFormat(assetType, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if p.IsEmpty() {
|
||||
return nil, currency.ErrCurrencyPairEmpty
|
||||
}
|
||||
|
||||
instrumentID := format.Format(p)
|
||||
instrumentID := pairFormat.Format(p)
|
||||
tradeData, err := ok.GetTrades(ctx, instrumentID, 1000)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -708,7 +716,7 @@ func (ok *Okx) GetHistoricTrades(ctx context.Context, p currency.Pair, assetType
|
||||
return nil, errOnlyThreeMonthsSupported
|
||||
}
|
||||
const limit = 100
|
||||
format, err := ok.GetPairFormat(assetType, false)
|
||||
pairFormat, err := ok.GetPairFormat(assetType, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -716,7 +724,7 @@ func (ok *Okx) GetHistoricTrades(ctx context.Context, p currency.Pair, assetType
|
||||
return nil, currency.ErrCurrencyPairEmpty
|
||||
}
|
||||
var resp []trade.Data
|
||||
instrumentID := format.Format(p)
|
||||
instrumentID := pairFormat.Format(p)
|
||||
tradeIDEnd := ""
|
||||
allTrades:
|
||||
for {
|
||||
@@ -774,17 +782,17 @@ func (ok *Okx) SubmitOrder(ctx context.Context, s *order.Submit) (*order.SubmitR
|
||||
if s.Amount <= 0 {
|
||||
return nil, fmt.Errorf("amount, or size (sz) of quantity to buy or sell hast to be greater than zero ")
|
||||
}
|
||||
format, err := ok.GetPairFormat(s.AssetType, false)
|
||||
pairFormat, err := ok.GetPairFormat(s.AssetType, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if s.Pair.IsEmpty() {
|
||||
return nil, currency.ErrCurrencyPairEmpty
|
||||
}
|
||||
instrumentID := format.Format(s.Pair)
|
||||
var tradeMode string
|
||||
if s.AssetType != asset.Margin {
|
||||
tradeMode = "cash"
|
||||
instrumentID := pairFormat.Format(s.Pair)
|
||||
tradeMode := ok.marginTypeToString(s.MarginType)
|
||||
if s.Leverage != 0 && s.Leverage != 1 {
|
||||
return nil, fmt.Errorf("%w received '%v'", order.ErrSubmitLeverageNotSupported, s.Leverage)
|
||||
}
|
||||
var sideType string
|
||||
if s.Side.IsLong() {
|
||||
@@ -846,6 +854,17 @@ func (ok *Okx) SubmitOrder(ctx context.Context, s *order.Submit) (*order.SubmitR
|
||||
return s.DeriveSubmitResponse(placeOrderResponse.OrderID)
|
||||
}
|
||||
|
||||
func (ok *Okx) marginTypeToString(m margin.Type) string {
|
||||
switch m {
|
||||
case margin.Isolated:
|
||||
return "isolated"
|
||||
case margin.Multi:
|
||||
return "cross"
|
||||
default:
|
||||
return "cash"
|
||||
}
|
||||
}
|
||||
|
||||
// ModifyOrder will allow of changing orderbook placement and limit to market conversion
|
||||
func (ok *Okx) ModifyOrder(ctx context.Context, action *order.Modify) (*order.ModifyResponse, error) {
|
||||
if err := action.Validate(); err != nil {
|
||||
@@ -855,14 +874,14 @@ func (ok *Okx) ModifyOrder(ctx context.Context, action *order.Modify) (*order.Mo
|
||||
if math.Trunc(action.Amount) != action.Amount {
|
||||
return nil, errors.New("okx contract amount can not be decimal")
|
||||
}
|
||||
format, err := ok.GetPairFormat(action.AssetType, false)
|
||||
pairFormat, err := ok.GetPairFormat(action.AssetType, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if action.Pair.IsEmpty() {
|
||||
return nil, currency.ErrCurrencyPairEmpty
|
||||
}
|
||||
instrumentID := format.Format(action.Pair)
|
||||
instrumentID := pairFormat.Format(action.Pair)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -891,14 +910,14 @@ func (ok *Okx) CancelOrder(ctx context.Context, ord *order.Cancel) error {
|
||||
if !ok.SupportsAsset(ord.AssetType) {
|
||||
return fmt.Errorf("%w: %v", asset.ErrNotSupported, ord.AssetType)
|
||||
}
|
||||
format, err := ok.GetPairFormat(ord.AssetType, false)
|
||||
pairFormat, err := ok.GetPairFormat(ord.AssetType, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ord.Pair.IsEmpty() {
|
||||
return currency.ErrCurrencyPairEmpty
|
||||
}
|
||||
instrumentID := format.Format(ord.Pair)
|
||||
instrumentID := pairFormat.Format(ord.Pair)
|
||||
req := CancelOrderRequestParam{
|
||||
InstrumentID: instrumentID,
|
||||
OrderID: ord.OrderID,
|
||||
@@ -921,7 +940,6 @@ func (ok *Okx) CancelBatchOrders(ctx context.Context, o []order.Cancel) (*order.
|
||||
}
|
||||
cancelOrderParams := make([]CancelOrderRequestParam, len(o))
|
||||
var err error
|
||||
var format currency.PairFormat
|
||||
for x := range o {
|
||||
ord := o[x]
|
||||
err = ord.Validate(ord.StandardCancel())
|
||||
@@ -931,16 +949,17 @@ func (ok *Okx) CancelBatchOrders(ctx context.Context, o []order.Cancel) (*order.
|
||||
if !ok.SupportsAsset(ord.AssetType) {
|
||||
return nil, fmt.Errorf("%w: %v", asset.ErrNotSupported, ord.AssetType)
|
||||
}
|
||||
format, err = ok.GetPairFormat(ord.AssetType, true)
|
||||
|
||||
var instrumentID string
|
||||
var pairFormat currency.PairFormat
|
||||
pairFormat, err = ok.GetPairFormat(ord.AssetType, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !ord.Pair.IsPopulated() {
|
||||
return nil, errIncompleteCurrencyPair
|
||||
}
|
||||
instrumentID = format.Format(ord.Pair)
|
||||
instrumentID = pairFormat.Format(ord.Pair)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1078,12 +1097,17 @@ func (ok *Okx) GetOrderInfo(ctx context.Context, orderID string, pair currency.P
|
||||
return nil, err
|
||||
}
|
||||
|
||||
format, err := ok.GetPairFormat(assetType, false)
|
||||
pairFormat, err := ok.GetPairFormat(assetType, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
instrumentID := format.Format(pair)
|
||||
if !pair.IsPopulated() {
|
||||
return nil, errIncompleteCurrencyPair
|
||||
}
|
||||
instrumentID := pairFormat.Format(pair)
|
||||
if !ok.SupportsAsset(assetType) {
|
||||
return nil, fmt.Errorf("%w: %v", asset.ErrNotSupported, assetType)
|
||||
}
|
||||
orderDetail, err := ok.GetOrderDetail(ctx, &OrderDetailRequestParam{
|
||||
InstrumentID: instrumentID,
|
||||
OrderID: orderID,
|
||||
@@ -1712,3 +1736,386 @@ func (ok *Okx) GetFundingRates(ctx context.Context, r *fundingrate.RatesRequest)
|
||||
func (ok *Okx) IsPerpetualFutureCurrency(a asset.Item, _ currency.Pair) (bool, error) {
|
||||
return a == asset.PerpetualSwap, nil
|
||||
}
|
||||
|
||||
// SetMarginType sets the default margin type for when opening a new position
|
||||
// okx allows this to be set with an order, however this sets a default
|
||||
func (ok *Okx) SetMarginType(_ context.Context, _ asset.Item, _ currency.Pair, _ margin.Type) error {
|
||||
return fmt.Errorf("%w margin type is set per order", common.ErrFunctionNotSupported)
|
||||
}
|
||||
|
||||
// SetCollateralMode sets the collateral type for your account
|
||||
func (ok *Okx) SetCollateralMode(_ context.Context, _ asset.Item, _ collateral.Mode) error {
|
||||
return fmt.Errorf("%w must be set via website", common.ErrFunctionNotSupported)
|
||||
}
|
||||
|
||||
// GetCollateralMode returns the collateral type for your account
|
||||
func (ok *Okx) GetCollateralMode(ctx context.Context, item asset.Item) (collateral.Mode, error) {
|
||||
if !ok.SupportsAsset(item) {
|
||||
return 0, fmt.Errorf("%w: %v", asset.ErrNotSupported, item)
|
||||
}
|
||||
cfg, err := ok.GetAccountConfiguration(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch cfg[0].AccountLevel {
|
||||
case 1:
|
||||
if item != asset.Spot {
|
||||
return 0, fmt.Errorf("%w %v", asset.ErrNotSupported, item)
|
||||
}
|
||||
fallthrough
|
||||
case 2:
|
||||
return collateral.SingleMode, nil
|
||||
case 3:
|
||||
return collateral.MultiMode, nil
|
||||
case 4:
|
||||
return collateral.PortfolioMode, nil
|
||||
default:
|
||||
return collateral.UnknownMode, fmt.Errorf("%w %v", order.ErrCollateralInvalid, cfg[0].AccountLevel)
|
||||
}
|
||||
}
|
||||
|
||||
// ChangePositionMargin will modify a position/currencies margin parameters
|
||||
func (ok *Okx) ChangePositionMargin(ctx context.Context, req *margin.PositionChangeRequest) (*margin.PositionChangeResponse, error) {
|
||||
if req == nil {
|
||||
return nil, fmt.Errorf("%w PositionChangeRequest", common.ErrNilPointer)
|
||||
}
|
||||
if !ok.SupportsAsset(req.Asset) {
|
||||
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, margin.ErrOriginalPositionMarginRequired
|
||||
}
|
||||
if req.MarginType != margin.Isolated {
|
||||
return nil, fmt.Errorf("%w %v", margin.ErrMarginTypeUnsupported, req.MarginType)
|
||||
}
|
||||
pairFormat, err := ok.GetPairFormat(req.Asset, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fPair := req.Pair.Format(pairFormat)
|
||||
marginType := "add"
|
||||
amt := req.NewAllocatedMargin - req.OriginalAllocatedMargin
|
||||
if req.NewAllocatedMargin < req.OriginalAllocatedMargin {
|
||||
marginType = "reduce"
|
||||
amt = req.OriginalAllocatedMargin - req.NewAllocatedMargin
|
||||
}
|
||||
if req.MarginSide == "" {
|
||||
req.MarginSide = "net"
|
||||
}
|
||||
r := IncreaseDecreaseMarginInput{
|
||||
InstrumentID: fPair.String(),
|
||||
PositionSide: req.MarginSide,
|
||||
Type: marginType,
|
||||
Amount: amt,
|
||||
}
|
||||
|
||||
if req.Asset == asset.Margin {
|
||||
r.Currency = req.Pair.Base.Item.Symbol
|
||||
}
|
||||
|
||||
resp, err := ok.IncreaseDecreaseMargin(ctx, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &margin.PositionChangeResponse{
|
||||
Exchange: ok.Name,
|
||||
Pair: req.Pair,
|
||||
Asset: req.Asset,
|
||||
AllocatedMargin: resp.Amount.Float64(),
|
||||
MarginType: req.MarginType,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetFuturesPositionSummary returns position summary details for an active position
|
||||
func (ok *Okx) GetFuturesPositionSummary(ctx context.Context, req *order.PositionSummaryRequest) (*order.PositionSummary, error) {
|
||||
if req == nil {
|
||||
return nil, fmt.Errorf("%w PositionSummaryRequest", common.ErrNilPointer)
|
||||
}
|
||||
if req.CalculateOffline {
|
||||
return nil, common.ErrCannotCalculateOffline
|
||||
}
|
||||
if !ok.SupportsAsset(req.Asset) || !req.Asset.IsFutures() {
|
||||
return nil, fmt.Errorf("%w %v", asset.ErrNotSupported, req.Asset)
|
||||
}
|
||||
fPair, err := ok.FormatExchangeCurrency(req.Pair, req.Asset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
instrumentType := ok.GetInstrumentTypeFromAssetItem(req.Asset)
|
||||
positionSummaries, err := ok.GetPositions(ctx, instrumentType, fPair.String(), "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var positionSummary *AccountPosition
|
||||
for i := range positionSummaries {
|
||||
if positionSummaries[i].QuantityOfPosition.Float64() <= 0 {
|
||||
continue
|
||||
}
|
||||
positionSummary = &positionSummaries[i]
|
||||
break
|
||||
}
|
||||
if positionSummary == nil {
|
||||
return nil, fmt.Errorf("%w, received '%v', no positions found", errOnlyOneResponseExpected, len(positionSummaries))
|
||||
}
|
||||
marginMode := margin.Isolated
|
||||
if positionSummary.MarginMode == "cross" {
|
||||
marginMode = margin.Multi
|
||||
}
|
||||
|
||||
acc, err := ok.AccountBalance(ctx, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(acc) != 1 {
|
||||
return nil, fmt.Errorf("%w, received '%v'", errOnlyOneResponseExpected, len(acc))
|
||||
}
|
||||
var (
|
||||
freeCollateral, totalCollateral, equityOfCurrency, frozenBalance,
|
||||
availableEquity, cashBalance, discountEquity,
|
||||
equityUSD, totalEquity, isolatedEquity, isolatedLiabilities,
|
||||
isolatedUnrealisedProfit, notionalLeverage,
|
||||
strategyEquity decimal.Decimal
|
||||
)
|
||||
|
||||
for i := range acc[0].Details {
|
||||
if acc[0].Details[i].Currency != positionSummary.Currency {
|
||||
continue
|
||||
}
|
||||
freeCollateral = acc[0].Details[i].AvailableBalance.Decimal()
|
||||
frozenBalance = acc[0].Details[i].FrozenBalance.Decimal()
|
||||
totalCollateral = freeCollateral.Add(frozenBalance)
|
||||
equityOfCurrency = acc[0].Details[i].EquityOfCurrency.Decimal()
|
||||
availableEquity = acc[0].Details[i].AvailableEquity.Decimal()
|
||||
cashBalance = acc[0].Details[i].CashBalance.Decimal()
|
||||
discountEquity = acc[0].Details[i].DiscountEquity.Decimal()
|
||||
equityUSD = acc[0].Details[i].EquityUsd.Decimal()
|
||||
totalEquity = acc[0].Details[i].TotalEquity.Decimal()
|
||||
isolatedEquity = acc[0].Details[i].IsoEquity.Decimal()
|
||||
isolatedLiabilities = acc[0].Details[i].IsolatedLiabilities.Decimal()
|
||||
isolatedUnrealisedProfit = acc[0].Details[i].IsoUpl.Decimal()
|
||||
notionalLeverage = acc[0].Details[i].NotionalLever.Decimal()
|
||||
strategyEquity = acc[0].Details[i].StrategyEquity.Decimal()
|
||||
|
||||
break
|
||||
}
|
||||
collateralMode, err := ok.GetCollateralMode(ctx, req.Asset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &order.PositionSummary{
|
||||
Pair: req.Pair,
|
||||
Asset: req.Asset,
|
||||
MarginType: marginMode,
|
||||
CollateralMode: collateralMode,
|
||||
Currency: currency.NewCode(positionSummary.Currency),
|
||||
AvailableEquity: availableEquity,
|
||||
CashBalance: cashBalance,
|
||||
DiscountEquity: discountEquity,
|
||||
EquityUSD: equityUSD,
|
||||
IsolatedEquity: isolatedEquity,
|
||||
IsolatedLiabilities: isolatedLiabilities,
|
||||
IsolatedUPL: isolatedUnrealisedProfit,
|
||||
NotionalLeverage: notionalLeverage,
|
||||
TotalEquity: totalEquity,
|
||||
StrategyEquity: strategyEquity,
|
||||
IsolatedMargin: positionSummary.Margin.Decimal(),
|
||||
NotionalSize: positionSummary.NotionalUsd.Decimal(),
|
||||
Leverage: positionSummary.Leverage.Decimal(),
|
||||
MaintenanceMarginRequirement: positionSummary.MaintenanceMarginRequirement.Decimal(),
|
||||
InitialMarginRequirement: positionSummary.InitialMarginRequirement.Decimal(),
|
||||
EstimatedLiquidationPrice: positionSummary.LiquidationPrice.Decimal(),
|
||||
CollateralUsed: positionSummary.Margin.Decimal(),
|
||||
MarkPrice: positionSummary.MarkPrice.Decimal(),
|
||||
CurrentSize: positionSummary.QuantityOfPosition.Decimal(), // TODO: add field(s) for contract amount vs quote amount
|
||||
AverageOpenPrice: positionSummary.AveragePrice.Decimal(),
|
||||
PositionPNL: positionSummary.UPNL.Decimal(),
|
||||
MaintenanceMarginFraction: positionSummary.MarginRatio.Decimal(),
|
||||
FreeCollateral: freeCollateral,
|
||||
TotalCollateral: totalCollateral,
|
||||
FrozenBalance: frozenBalance,
|
||||
EquityOfCurrency: equityOfCurrency,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetFuturesPositionOrders returns the orders for futures positions
|
||||
func (ok *Okx) GetFuturesPositionOrders(ctx context.Context, req *order.PositionsRequest) ([]order.PositionResponse, error) {
|
||||
if req == nil {
|
||||
return nil, fmt.Errorf("%w PositionSummaryRequest", common.ErrNilPointer)
|
||||
}
|
||||
if !ok.SupportsAsset(req.Asset) || !req.Asset.IsFutures() {
|
||||
return nil, fmt.Errorf("%w %v", asset.ErrNotSupported, req.Asset)
|
||||
}
|
||||
if time.Since(req.StartDate) > ok.Features.Supports.MaximumOrderHistory {
|
||||
if req.RespectOrderHistoryLimits {
|
||||
req.StartDate = time.Now().Add(-ok.Features.Supports.MaximumOrderHistory)
|
||||
} else {
|
||||
return nil, fmt.Errorf("%w max lookup %v", order.ErrOrderHistoryTooLarge, time.Now().Add(-ok.Features.Supports.MaximumOrderHistory))
|
||||
}
|
||||
}
|
||||
if err := common.StartEndTimeCheck(req.StartDate, req.EndDate); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp := make([]order.PositionResponse, len(req.Pairs))
|
||||
for i := range req.Pairs {
|
||||
fPair, err := ok.FormatExchangeCurrency(req.Pairs[i], req.Asset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
instrumentType := ok.GetInstrumentTypeFromAssetItem(req.Asset)
|
||||
resp[i] = order.PositionResponse{
|
||||
Pair: req.Pairs[i],
|
||||
Asset: req.Asset,
|
||||
}
|
||||
|
||||
var positions []OrderDetail
|
||||
historyRequest := &OrderHistoryRequestParams{
|
||||
OrderListRequestParams: OrderListRequestParams{
|
||||
InstrumentType: instrumentType,
|
||||
InstrumentID: fPair.String(),
|
||||
Start: req.StartDate,
|
||||
End: req.EndDate,
|
||||
},
|
||||
}
|
||||
if time.Since(req.StartDate) <= time.Hour*24*7 {
|
||||
positions, err = ok.Get7DayOrderHistory(ctx, historyRequest)
|
||||
} else {
|
||||
positions, err = ok.Get3MonthOrderHistory(ctx, historyRequest)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for j := range positions {
|
||||
if req.Pairs[i].String() != positions[j].InstrumentID {
|
||||
continue
|
||||
}
|
||||
var orderStatus order.Status
|
||||
orderStatus, err = order.StringToOrderStatus(strings.ToUpper(positions[j].State))
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys, "%s %v", ok.Name, err)
|
||||
}
|
||||
orderSide := positions[j].Side
|
||||
var oType order.Type
|
||||
oType, err = ok.OrderTypeFromString(positions[j].OrderType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
orderAmount := positions[j].Size
|
||||
if positions[j].QuantityType == "quote_ccy" {
|
||||
// Size is quote amount.
|
||||
orderAmount /= positions[j].AveragePrice
|
||||
}
|
||||
|
||||
remainingAmount := float64(0)
|
||||
if orderStatus != order.Filled {
|
||||
remainingAmount = orderAmount.Float64() - positions[j].AccumulatedFillSize.Float64()
|
||||
}
|
||||
resp[i].Orders = append(resp[i].Orders, order.Detail{
|
||||
Price: positions[j].Price.Float64(),
|
||||
AverageExecutedPrice: positions[j].AveragePrice.Float64(),
|
||||
Amount: orderAmount.Float64(), // TODO: add field(s) for contract amount vs quote amount
|
||||
ExecutedAmount: positions[j].AccumulatedFillSize.Float64(),
|
||||
RemainingAmount: remainingAmount,
|
||||
Fee: positions[j].TransactionFee.Float64(),
|
||||
FeeAsset: currency.NewCode(positions[j].FeeCurrency),
|
||||
Exchange: ok.Name,
|
||||
OrderID: positions[j].OrderID,
|
||||
ClientOrderID: positions[j].ClientSupplierOrderID,
|
||||
Type: oType,
|
||||
Side: orderSide,
|
||||
Status: orderStatus,
|
||||
AssetType: req.Asset,
|
||||
Date: positions[j].CreationTime,
|
||||
LastUpdated: positions[j].UpdateTime,
|
||||
Pair: req.Pairs[i],
|
||||
Cost: positions[j].AveragePrice.Float64() * positions[j].AccumulatedFillSize.Float64(),
|
||||
CostAsset: currency.NewCode(positions[j].RebateCurrency),
|
||||
})
|
||||
}
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// SetLeverage sets the account's initial leverage for the asset type and pair
|
||||
func (ok *Okx) SetLeverage(ctx context.Context, item asset.Item, pair currency.Pair, marginType margin.Type, amount float64, orderSide order.Side) error {
|
||||
posSide := "net"
|
||||
switch item {
|
||||
case asset.Futures, asset.PerpetualSwap:
|
||||
if marginType == margin.Isolated {
|
||||
switch {
|
||||
case orderSide == order.UnknownSide:
|
||||
return errOrderSideRequired
|
||||
case orderSide.IsLong():
|
||||
posSide = "long"
|
||||
case orderSide.IsShort():
|
||||
posSide = "short"
|
||||
default:
|
||||
return fmt.Errorf("%w %v requires long/short", errInvalidOrderSide, orderSide)
|
||||
}
|
||||
}
|
||||
fallthrough
|
||||
case asset.Margin, asset.Options:
|
||||
instrumentID, err := ok.FormatSymbol(pair, item)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
marginMode := ok.marginTypeToString(marginType)
|
||||
_, err = ok.SetLeverageRate(ctx, SetLeverageInput{
|
||||
Leverage: amount,
|
||||
MarginMode: marginMode,
|
||||
InstrumentID: instrumentID,
|
||||
PositionSide: posSide,
|
||||
})
|
||||
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 (ok *Okx) GetLeverage(ctx context.Context, item asset.Item, pair currency.Pair, marginType margin.Type, orderSide order.Side) (float64, error) {
|
||||
var inspectLeverage bool
|
||||
switch item {
|
||||
case asset.Futures, asset.PerpetualSwap:
|
||||
if marginType == margin.Isolated {
|
||||
switch {
|
||||
case orderSide == order.UnknownSide:
|
||||
return 0, errOrderSideRequired
|
||||
case orderSide.IsLong(), orderSide.IsShort():
|
||||
inspectLeverage = true
|
||||
default:
|
||||
return 0, fmt.Errorf("%w %v requires long/short", errInvalidOrderSide, orderSide)
|
||||
}
|
||||
}
|
||||
fallthrough
|
||||
case asset.Margin, asset.Options:
|
||||
instrumentID, err := ok.FormatSymbol(pair, item)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
marginMode := ok.marginTypeToString(marginType)
|
||||
lev, err := ok.GetLeverageRate(ctx, instrumentID, marginMode)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
if len(lev) == 0 {
|
||||
return -1, fmt.Errorf("%w %v %v %s", order.ErrPositionNotFound, item, pair, marginType)
|
||||
}
|
||||
if inspectLeverage {
|
||||
for i := range lev {
|
||||
if lev[i].PositionSide == orderSide.Lower() {
|
||||
return lev[i].Leverage.Float64(), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// leverage is the same across positions
|
||||
return lev[0].Leverage.Float64(), nil
|
||||
default:
|
||||
return -1, fmt.Errorf("%w %v", asset.ErrNotSupported, item)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,9 @@ import (
|
||||
"github.com/shopspring/decimal"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"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/margin"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -35,6 +37,8 @@ var (
|
||||
ErrNoPositionsFound = errors.New("no positions found")
|
||||
// ErrGetFundingDataRequired is returned when requesting funding rate data without the prerequisite
|
||||
ErrGetFundingDataRequired = errors.New("getfundingdata is a prerequisite")
|
||||
// ErrOrderHistoryTooLarge is returned when you lookup order history, but with too early a start date
|
||||
ErrOrderHistoryTooLarge = errors.New("order history start date too long ago")
|
||||
|
||||
errExchangeNameEmpty = errors.New("exchange name empty")
|
||||
errExchangeNameMismatch = errors.New("exchange name mismatch")
|
||||
@@ -65,57 +69,12 @@ type TotalCollateralResponse struct {
|
||||
TotalValueOfPositiveSpotBalances decimal.Decimal
|
||||
CollateralContributedByPositiveSpotBalances decimal.Decimal
|
||||
UsedCollateral decimal.Decimal
|
||||
UsedBreakdown *UsedCollateralBreakdown
|
||||
UsedBreakdown *collateral.UsedBreakdown
|
||||
AvailableCollateral decimal.Decimal
|
||||
AvailableMaintenanceCollateral decimal.Decimal
|
||||
UnrealisedPNL decimal.Decimal
|
||||
BreakdownByCurrency []CollateralByCurrency
|
||||
BreakdownOfPositions []CollateralByPosition
|
||||
}
|
||||
|
||||
// CollateralByPosition shows how much collateral is used
|
||||
// from positions
|
||||
type CollateralByPosition struct {
|
||||
PositionCurrency currency.Pair
|
||||
Size decimal.Decimal
|
||||
OpenOrderSize decimal.Decimal
|
||||
PositionSize decimal.Decimal
|
||||
MarkPrice decimal.Decimal
|
||||
RequiredMargin decimal.Decimal
|
||||
CollateralUsed decimal.Decimal
|
||||
}
|
||||
|
||||
// CollateralByCurrency individual collateral contribution
|
||||
// along with what the potentially scaled collateral
|
||||
// currency it is represented as
|
||||
// eg in Bybit ScaledCurrency is USDC
|
||||
type CollateralByCurrency struct {
|
||||
Currency currency.Code
|
||||
SkipContribution bool
|
||||
TotalFunds decimal.Decimal
|
||||
AvailableForUseAsCollateral decimal.Decimal
|
||||
CollateralContribution decimal.Decimal
|
||||
AdditionalCollateralUsed decimal.Decimal
|
||||
FairMarketValue decimal.Decimal
|
||||
Weighting decimal.Decimal
|
||||
ScaledCurrency currency.Code
|
||||
UnrealisedPNL decimal.Decimal
|
||||
ScaledUsed decimal.Decimal
|
||||
ScaledUsedBreakdown *UsedCollateralBreakdown
|
||||
Error error
|
||||
}
|
||||
|
||||
// UsedCollateralBreakdown provides a detailed
|
||||
// breakdown of where collateral is currently being allocated
|
||||
type UsedCollateralBreakdown struct {
|
||||
LockedInStakes decimal.Decimal
|
||||
LockedInNFTBids decimal.Decimal
|
||||
LockedInFeeVoucher decimal.Decimal
|
||||
LockedInSpotMarginFundingOffers decimal.Decimal
|
||||
LockedInSpotOrders decimal.Decimal
|
||||
LockedAsCollateral decimal.Decimal
|
||||
UsedInPositions decimal.Decimal
|
||||
UsedInSpotMarginBorrows decimal.Decimal
|
||||
BreakdownByCurrency []collateral.ByCurrency
|
||||
BreakdownOfPositions []collateral.ByPosition
|
||||
}
|
||||
|
||||
// PositionController manages all futures orders
|
||||
@@ -310,38 +269,27 @@ type Position struct {
|
||||
type PositionSummaryRequest struct {
|
||||
Asset asset.Item
|
||||
Pair currency.Pair
|
||||
// UnderlyingPair is optional if the exchange requires it for a contract like BTCUSDT-13333337
|
||||
UnderlyingPair currency.Pair
|
||||
|
||||
// offline calculation requirements below
|
||||
CalculateOffline bool
|
||||
Direction Side
|
||||
FreeCollateral decimal.Decimal
|
||||
TotalCollateral decimal.Decimal
|
||||
OpeningPrice decimal.Decimal
|
||||
CurrentPrice decimal.Decimal
|
||||
OpeningSize decimal.Decimal
|
||||
CurrentSize decimal.Decimal
|
||||
CollateralUsed decimal.Decimal
|
||||
NotionalPrice decimal.Decimal
|
||||
Leverage decimal.Decimal
|
||||
MaxLeverageForAccount decimal.Decimal
|
||||
TotalAccountValue decimal.Decimal
|
||||
TotalOpenPositionNotional decimal.Decimal
|
||||
}
|
||||
|
||||
// PositionSummary returns basic details on an open position
|
||||
type PositionSummary struct {
|
||||
MaintenanceMarginRequirement decimal.Decimal
|
||||
InitialMarginRequirement decimal.Decimal
|
||||
EstimatedLiquidationPrice decimal.Decimal
|
||||
CollateralUsed decimal.Decimal
|
||||
MarkPrice decimal.Decimal
|
||||
CurrentSize decimal.Decimal
|
||||
BreakEvenPrice decimal.Decimal
|
||||
AverageOpenPrice decimal.Decimal
|
||||
RecentPNL decimal.Decimal
|
||||
MarginFraction decimal.Decimal
|
||||
FreeCollateral decimal.Decimal
|
||||
TotalCollateral decimal.Decimal
|
||||
// EstimatePosition if enabled, can be used to calculate a new position
|
||||
EstimatePosition bool
|
||||
// These fields are also used for offline calculation
|
||||
OpeningPrice decimal.Decimal
|
||||
OpeningSize decimal.Decimal
|
||||
Leverage decimal.Decimal
|
||||
Direction Side
|
||||
TotalAccountValue decimal.Decimal
|
||||
}
|
||||
|
||||
// PositionDetails are used to track open positions
|
||||
@@ -359,4 +307,56 @@ type PositionsRequest struct {
|
||||
Asset asset.Item
|
||||
Pairs currency.Pairs
|
||||
StartDate time.Time
|
||||
EndDate time.Time
|
||||
// RespectOrderHistoryLimits is designed for the order manager
|
||||
// it allows for orders to be tracked if the start date in the config is
|
||||
// beyond the allowable limits by the API, rather than returning an error
|
||||
RespectOrderHistoryLimits bool
|
||||
}
|
||||
|
||||
// PositionResponse are used to track open positions
|
||||
// in the order manager
|
||||
type PositionResponse struct {
|
||||
Pair currency.Pair
|
||||
Asset asset.Item
|
||||
Orders []Detail
|
||||
}
|
||||
|
||||
// PositionSummary returns basic details on an open position
|
||||
type PositionSummary struct {
|
||||
Pair currency.Pair
|
||||
Asset asset.Item
|
||||
MarginType margin.Type
|
||||
CollateralMode collateral.Mode
|
||||
// The currency in which the values are quoted against. Isn't always pair.Quote
|
||||
// eg BTC-USDC-230929's quote in GCT is 230929, but the currency should be USDC
|
||||
Currency currency.Code
|
||||
|
||||
AvailableEquity decimal.Decimal
|
||||
CashBalance decimal.Decimal
|
||||
DiscountEquity decimal.Decimal
|
||||
EquityUSD decimal.Decimal
|
||||
IsolatedEquity decimal.Decimal
|
||||
IsolatedLiabilities decimal.Decimal
|
||||
IsolatedUPL decimal.Decimal
|
||||
NotionalLeverage decimal.Decimal
|
||||
TotalEquity decimal.Decimal
|
||||
StrategyEquity decimal.Decimal
|
||||
|
||||
IsolatedMargin decimal.Decimal
|
||||
NotionalSize decimal.Decimal
|
||||
Leverage decimal.Decimal
|
||||
MaintenanceMarginRequirement decimal.Decimal
|
||||
InitialMarginRequirement decimal.Decimal
|
||||
EstimatedLiquidationPrice decimal.Decimal
|
||||
CollateralUsed decimal.Decimal
|
||||
MarkPrice decimal.Decimal
|
||||
CurrentSize decimal.Decimal
|
||||
AverageOpenPrice decimal.Decimal
|
||||
PositionPNL decimal.Decimal
|
||||
MaintenanceMarginFraction decimal.Decimal
|
||||
FreeCollateral decimal.Decimal
|
||||
TotalCollateral decimal.Decimal
|
||||
FrozenBalance decimal.Decimal
|
||||
EquityOfCurrency decimal.Decimal
|
||||
}
|
||||
|
||||
@@ -820,7 +820,7 @@ func TestStringToOrderSide(t *testing.T) {
|
||||
{"any", AnySide, nil},
|
||||
{"ANY", AnySide, nil},
|
||||
{"aNy", AnySide, nil},
|
||||
{"woahMan", UnknownSide, errUnrecognisedOrderSide},
|
||||
{"woahMan", UnknownSide, ErrSideIsInvalid},
|
||||
}
|
||||
for i := range cases {
|
||||
testData := &cases[i]
|
||||
@@ -1340,8 +1340,8 @@ func TestValidationOnOrderTypes(t *testing.T) {
|
||||
|
||||
getOrders.AssetType = asset.Spot
|
||||
err = getOrders.Validate()
|
||||
if !errors.Is(err, errUnrecognisedOrderSide) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errUnrecognisedOrderSide)
|
||||
if !errors.Is(err, ErrSideIsInvalid) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, ErrSideIsInvalid)
|
||||
}
|
||||
|
||||
getOrders.Side = AnySide
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/margin"
|
||||
)
|
||||
|
||||
// var error definitions
|
||||
@@ -19,10 +20,12 @@ var (
|
||||
ErrPairIsEmpty = errors.New("order pair is empty")
|
||||
ErrAssetNotSet = errors.New("order asset type is not set")
|
||||
ErrSideIsInvalid = errors.New("order side is invalid")
|
||||
ErrCollateralInvalid = errors.New("collateral type is invalid")
|
||||
ErrTypeIsInvalid = errors.New("order type is invalid")
|
||||
ErrAmountIsInvalid = errors.New("order amount is equal or less than zero")
|
||||
ErrPriceMustBeSetIfLimitOrder = errors.New("order price must be set if limit order type is desired")
|
||||
ErrOrderIDNotSet = errors.New("order id or client order id is not set")
|
||||
ErrSubmitLeverageNotSupported = errors.New("leverage is not supported via order submission")
|
||||
ErrClientOrderIDNotSupported = errors.New("client order id not supported")
|
||||
ErrUnsupportedOrderType = errors.New("unsupported order type")
|
||||
// ErrNoRates is returned when no margin rates are returned when they are expected
|
||||
@@ -64,6 +67,9 @@ type Submit struct {
|
||||
TriggerPrice float64
|
||||
ClientID string // TODO: Shift to credentials
|
||||
ClientOrderID string
|
||||
// MarginType such as isolated or cross margin for when an exchange
|
||||
// supports margin type definition when submitting an order eg okx
|
||||
MarginType margin.Type
|
||||
// RetrieveFees use if an API submit order response does not return fees
|
||||
// enabling this will perform additional request(s) to retrieve them
|
||||
// and set it in the SubmitResponse
|
||||
@@ -103,6 +109,7 @@ type SubmitResponse struct {
|
||||
Fee float64
|
||||
FeeAsset currency.Code
|
||||
Cost float64
|
||||
MarginType margin.Type
|
||||
}
|
||||
|
||||
// Modify contains all properties of an order
|
||||
@@ -191,6 +198,7 @@ type Detail struct {
|
||||
CloseTime time.Time
|
||||
LastUpdated time.Time
|
||||
Pair currency.Pair
|
||||
MarginType margin.Type
|
||||
Trades []TradeHistory
|
||||
}
|
||||
|
||||
@@ -226,6 +234,7 @@ type Cancel struct {
|
||||
Side Side
|
||||
AssetType asset.Item
|
||||
Pair currency.Pair
|
||||
MarginType margin.Type
|
||||
}
|
||||
|
||||
// CancelAllResponse returns the status from attempting to
|
||||
|
||||
@@ -35,7 +35,6 @@ var (
|
||||
ErrOrderNotFound = errors.New("order not found")
|
||||
|
||||
errTimeInForceConflict = errors.New("multiple time in force options applied")
|
||||
errUnrecognisedOrderSide = errors.New("unrecognised order side")
|
||||
errUnrecognisedOrderType = errors.New("unrecognised order type")
|
||||
errUnrecognisedOrderStatus = errors.New("unrecognised order status")
|
||||
errExchangeNameUnset = errors.New("exchange name unset")
|
||||
@@ -476,6 +475,7 @@ func (s *Submit) DeriveSubmitResponse(orderID string) (*SubmitResponse, error) {
|
||||
TriggerPrice: s.TriggerPrice,
|
||||
ClientID: s.ClientID,
|
||||
ClientOrderID: s.ClientOrderID,
|
||||
MarginType: s.MarginType,
|
||||
|
||||
LastUpdated: time.Now(),
|
||||
Date: time.Now(),
|
||||
@@ -1056,7 +1056,7 @@ func StringToOrderSide(side string) (Side, error) {
|
||||
case AnySide.String():
|
||||
return AnySide, nil
|
||||
default:
|
||||
return UnknownSide, fmt.Errorf("'%s' %w", side, errUnrecognisedOrderSide)
|
||||
return UnknownSide, fmt.Errorf("'%s' %w", side, ErrSideIsInvalid)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1210,7 +1210,7 @@ func (g *MultiOrderRequest) Validate(opt ...validate.Checker) error {
|
||||
}
|
||||
|
||||
if g.Side == UnknownSide {
|
||||
return errUnrecognisedOrderSide
|
||||
return ErrSideIsInvalid
|
||||
}
|
||||
|
||||
if g.Type == UnknownType {
|
||||
|
||||
Reference in New Issue
Block a user