diff --git a/pkg/exchange/okex/exchange.go b/pkg/exchange/okex/exchange.go index 9658b2b5d3..3f4e240348 100644 --- a/pkg/exchange/okex/exchange.go +++ b/pkg/exchange/okex/exchange.go @@ -6,6 +6,7 @@ import ( "os" "regexp" "strconv" + "strings" "time" "github.com/pkg/errors" @@ -224,7 +225,7 @@ func (e *Exchange) QueryAccountBalances(ctx context.Context) (types.BalanceMap, return nil, fmt.Errorf("account rate limiter wait error: %w", err) } - accountBalances, err := e.client.NewGetAccountInfoRequest().Do(ctx) + accountBalances, err := e.client.NewGetAccountBalanceRequest().Do(ctx) if err != nil { return nil, err } @@ -560,6 +561,59 @@ func (e *Exchange) QueryClosedOrders( return types.SortOrdersAscending(orders), nil } +func (e *Exchange) RepayMarginAsset(ctx context.Context, asset string, amount fixedpoint.Value) error { + req := e.client.NewSpotManualBorrowRepayRequest() + req.Currency(strings.ToUpper(asset)) + req.Amount(amount.String()) + req.Side(okexapi.MarginSideRepay) + resp, err := req.Do(ctx) + if err != nil { + return err + } + + log.Infof("spot repay response: %+v", resp) + return nil +} + +func (e *Exchange) BorrowMarginAsset(ctx context.Context, asset string, amount fixedpoint.Value) error { + req := e.client.NewSpotManualBorrowRepayRequest() + req.Currency(strings.ToUpper(asset)) + req.Amount(amount.String()) + req.Side(okexapi.MarginSideBorrow) + + resp, err := req.Do(ctx) + if err != nil { + return err + } + + log.Infof("spot borrow response: %+v", resp) + return nil +} + +func (e *Exchange) QueryMarginAssetMaxBorrowable(ctx context.Context, asset string) (fixedpoint.Value, error) { + req := e.client.NewGetAccountInterestLimitsRequest() + req.Currency(asset) + + resp, err := req.Do(ctx) + if err != nil { + return fixedpoint.Zero, err + } + + log.Infof("%+v", resp) + + if len(resp) == 0 || len(resp[0].Records) == 0 { + return fixedpoint.Zero, nil + } + + for _, record := range resp[0].Records { + if strings.ToUpper(record.Currency) == asset { + return record.LoanQuota, nil + } + } + + return fixedpoint.Zero, nil +} + /* QueryTrades can query trades in last 3 months, there are no time interval limitations, as long as end_time >= start_time. okx does not provide an API to query by trade ID, so we use the bill ID to do it. The trades result is ordered by timestamp. diff --git a/pkg/exchange/okex/exchange_test.go b/pkg/exchange/okex/exchange_test.go index 8198fe74d5..e9b9a38bee 100644 --- a/pkg/exchange/okex/exchange_test.go +++ b/pkg/exchange/okex/exchange_test.go @@ -75,7 +75,6 @@ func TestExchange_SubmitOrder(t *testing.T) { assert.NoError(t, err) t.Logf("createdOrder: %+v", createdOrder) } - } func TestExchange_QueryTrades(t *testing.T) { @@ -420,3 +419,22 @@ func TestExchange_QueryTrades(t *testing.T) { assert.ErrorContains(err, ErrSymbolRequired.Error()) }) } + +func TestExchange_Margin(t *testing.T) { + key, secret, passphrase, ok := testutil.IntegrationTestWithPassphraseConfigured(t, "OKEX") + if !ok { + t.SkipNow() + return + } + + ctx := context.Background() + ex := New(key, secret, passphrase) + + t.Run("QueryMarginAssetMaxBorrowable", func(t *testing.T) { + maxBorrowable, err := ex.QueryMarginAssetMaxBorrowable(ctx, "BTC") + if assert.NoError(t, err) { + assert.NotZero(t, maxBorrowable.Float64()) + t.Logf("max borrowable: %f", maxBorrowable.Float64()) + } + }) +} diff --git a/pkg/exchange/okex/okexapi/client_test.go b/pkg/exchange/okex/okexapi/client_test.go index f9282761ac..e6426bb288 100644 --- a/pkg/exchange/okex/okexapi/client_test.go +++ b/pkg/exchange/okex/okexapi/client_test.go @@ -3,14 +3,16 @@ package okexapi import ( "context" "fmt" - "github.com/c9s/bbgo/pkg/types" - "github.com/google/uuid" - "github.com/stretchr/testify/assert" "os" "strconv" "testing" "time" + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + + "github.com/c9s/bbgo/pkg/types" + "github.com/c9s/bbgo/pkg/testutil" ) @@ -66,7 +68,7 @@ func TestClient_GetMarketTicker(t *testing.T) { func TestClient_GetAcountInfo(t *testing.T) { client := getTestClientOrSkip(t) ctx := context.Background() - req := client.NewGetAccountInfoRequest() + req := client.NewGetAccountBalanceRequest() acct, err := req.Do(ctx) assert.NoError(t, err) @@ -168,15 +170,15 @@ func TestClient_OrderHistoryWithBeforeId(t *testing.T) { orders := []OrderDetail{} beforeId := int64(0) for { - //>> [{"accFillSz":"0.00001","algoClOrdId":"","algoId":"","attachAlgoClOrdId":"","attachAlgoOrds":[],"avgPx":"48174.5","cTime":"1704957916401","cancelSource":"","cancelSourceReason":"","category":"normal","ccy":"","clOrdId":"","fee":"-0.000385396","feeCcy":"USDT","fillPx":"48174.5","fillSz":"0.00001","fillTime":"1704983881118","instId":"BTC-USDT","instType":"SPOT","lever":"","ordId":"665576973905014786","ordType":"limit","pnl":"0","posSide":"","px":"48174.5","pxType":"","pxUsd":"","pxVol":"","quickMgnType":"","rebate":"0","rebateCcy":"BTC","reduceOnly":"false","side":"sell","slOrdPx":"","slTriggerPx":"","slTriggerPxType":"","source":"","state":"filled","stpId":"","stpMode":"","sz":"0.00001","tag":"","tdMode":"cash","tgtCcy":"","tpOrdPx":"","tpTriggerPx":"","tpTriggerPxType":"","tradeId":"472610696","uTime":"1704983881135"}] - //>> [{"accFillSz":"0.00001","algoClOrdId":"","algoId":"","attachAlgoClOrdId":"","attachAlgoOrds":[],"avgPx":"48074.5","cTime":"1704957905283","cancelSource":"","cancelSourceReason":"","category":"normal","ccy":"","clOrdId":"","fee":"-0.000384596","feeCcy":"USDT","fillPx":"48074.5","fillSz":"0.00001","fillTime":"1704983824237","instId":"BTC-USDT","instType":"SPOT","lever":"","ordId":"665576927272742919","ordType":"limit","pnl":"0","posSide":"","px":"48074.5","pxType":"","pxUsd":"","pxVol":"","quickMgnType":"","rebate":"0","rebateCcy":"BTC","reduceOnly":"false","side":"sell","slOrdPx":"","slTriggerPx":"","slTriggerPxType":"","source":"","state":"filled","stpId":"","stpMode":"","sz":"0.00001","tag":"","tdMode":"cash","tgtCcy":"","tpOrdPx":"","tpTriggerPx":"","tpTriggerPxType":"","tradeId":"472601591","uTime":"1704983824240"}] - //>> [{"accFillSz":"0.00001","algoClOrdId":"","algoId":"","attachAlgoClOrdId":"","attachAlgoOrds":[],"avgPx":"48073.5","cTime":"1704957892896","cancelSource":"","cancelSourceReason":"","category":"normal","ccy":"","clOrdId":"","fee":"-0.000384588","feeCcy":"USDT","fillPx":"48073.5","fillSz":"0.00001","fillTime":"1704983824227","instId":"BTC-USDT","instType":"SPOT","lever":"","ordId":"665576875317899302","ordType":"limit","pnl":"0","posSide":"","px":"48073.5","pxType":"","pxUsd":"","pxVol":"","quickMgnType":"","rebate":"0","rebateCcy":"BTC","reduceOnly":"false","side":"sell","slOrdPx":"","slTriggerPx":"","slTriggerPxType":"","source":"","state":"filled","stpId":"","stpMode":"","sz":"0.00001","tag":"","tdMode":"cash","tgtCcy":"","tpOrdPx":"","tpTriggerPx":"","tpTriggerPxType":"","tradeId":"472601583","uTime":"1704983824230"}] - //>> [{"accFillSz":"0.00016266","algoClOrdId":"","algoId":"","attachAlgoClOrdId":"","attachAlgoOrds":[],"avgPx":"45919.8","cTime":"1704852215160","cancelSource":"","cancelSourceReason":"","category":"normal","ccy":"","clOrdId":"","fee":"-0.00000016266","feeCcy":"BTC","fillPx":"45919.8","fillSz":"0.00016266","fillTime":"1704852215162","instId":"BTC-USDT","instType":"SPOT","lever":"","ordId":"665133630767091729","ordType":"market","pnl":"0","posSide":"","px":"","pxType":"","pxUsd":"","pxVol":"","quickMgnType":"","rebate":"0","rebateCcy":"USDT","reduceOnly":"false","side":"buy","slOrdPx":"","slTriggerPx":"","slTriggerPxType":"","source":"","state":"filled","stpId":"","stpMode":"","sz":"0.00016266","tag":"","tdMode":"cash","tgtCcy":"base_ccy","tpOrdPx":"","tpTriggerPx":"","tpTriggerPxType":"","tradeId":"471113058","uTime":"1704852215163"}] - //>> [{"accFillSz":"0.00087627","algoClOrdId":"","algoId":"","attachAlgoClOrdId":"","attachAlgoOrds":[],"avgPx":"45647.6","cTime":"1704850530651","cancelSource":"","cancelSourceReason":"","category":"normal","ccy":"","clOrdId":"","fee":"-0.00000087627","feeCcy":"BTC","fillPx":"45647.6","fillSz":"0.00087627","fillTime":"1704850530652","instId":"BTC-USDT","instType":"SPOT","lever":"","ordId":"665126565424254976","ordType":"market","pnl":"0","posSide":"","px":"","pxType":"","pxUsd":"","pxVol":"","quickMgnType":"","rebate":"0","rebateCcy":"USDT","reduceOnly":"false","side":"buy","slOrdPx":"","slTriggerPx":"","slTriggerPxType":"","source":"","state":"filled","stpId":"","stpMode":"","sz":"40","tag":"","tdMode":"cash","tgtCcy":"quote_ccy","tpOrdPx":"","tpTriggerPx":"","tpTriggerPxType":"","tradeId":"471105716","uTime":"1704850530654"}] - //>> [{"accFillSz":"0.001","algoClOrdId":"","algoId":"","attachAlgoClOrdId":"","attachAlgoOrds":[],"avgPx":"45661.3","cTime":"1704850506060","cancelSource":"","cancelSourceReason":"","category":"normal","ccy":"","clOrdId":"","fee":"-0.0456613","feeCcy":"USDT","fillPx":"45661.3","fillSz":"0.001","fillTime":"1704850506061","instId":"BTC-USDT","instType":"SPOT","lever":"","ordId":"665126462282125313","ordType":"market","pnl":"0","posSide":"","px":"","pxType":"","pxUsd":"","pxVol":"","quickMgnType":"","rebate":"0","rebateCcy":"BTC","reduceOnly":"false","side":"sell","slOrdPx":"","slTriggerPx":"","slTriggerPxType":"","source":"","state":"filled","stpId":"","stpMode":"","sz":"0.001","tag":"","tdMode":"cash","tgtCcy":"base_ccy","tpOrdPx":"","tpTriggerPx":"","tpTriggerPxType":"","tradeId":"471105593","uTime":"1704850506062"}] - //>> [{"accFillSz":"0.00097361","algoClOrdId":"","algoId":"","attachAlgoClOrdId":"","attachAlgoOrds":[],"avgPx":"45743","cTime":"1704849690516","cancelSource":"","cancelSourceReason":"","category":"normal","ccy":"","clOrdId":"","fee":"-0.00000097361","feeCcy":"BTC","fillPx":"45743","fillSz":"0.00097361","fillTime":"1704849690517","instId":"BTC-USDT","instType":"SPOT","lever":"","ordId":"665123041642663944","ordType":"market","pnl":"0","posSide":"","px":"","pxType":"","pxUsd":"","pxVol":"","quickMgnType":"","rebate":"0","rebateCcy":"USDT","reduceOnly":"false","side":"buy","slOrdPx":"","slTriggerPx":"","slTriggerPxType":"","source":"","state":"filled","stpId":"","stpMode":"","sz":"0.00097361","tag":"","tdMode":"cash","tgtCcy":"base_ccy","tpOrdPx":"","tpTriggerPx":"","tpTriggerPxType":"","tradeId":"471100149","uTime":"1704849690519"}] - //>> [{"accFillSz":"0.00080894","algoClOrdId":"","algoId":"","attachAlgoClOrdId":"","attachAlgoOrds":[],"avgPx":"46728.2","cTime":"1704789666800","cancelSource":"","cancelSourceReason":"","category":"normal","ccy":"","clOrdId":"","fee":"-0.037800310108","feeCcy":"USDT","fillPx":"46728.2","fillSz":"0.00080894","fillTime":"1704789666801","instId":"BTC-USDT","instType":"SPOT","lever":"","ordId":"664871283930550273","ordType":"market","pnl":"0","posSide":"","px":"","pxType":"","pxUsd":"","pxVol":"","quickMgnType":"","rebate":"0","rebateCcy":"BTC","reduceOnly":"false","side":"sell","slOrdPx":"","slTriggerPx":"","slTriggerPxType":"","source":"","state":"filled","stpId":"","stpMode":"","sz":"37.8","tag":"","tdMode":"cash","tgtCcy":"quote_ccy","tpOrdPx":"","tpTriggerPx":"","tpTriggerPxType":"","tradeId":"470288552","uTime":"1704789666803"}] - //>> [{"accFillSz":"0.00085423","algoClOrdId":"","algoId":"","attachAlgoClOrdId":"","attachAlgoOrds":[],"avgPx":"46825.3","cTime":"1704789220044","cancelSource":"","cancelSourceReason":"","category":"normal","ccy":"","clOrdId":"","fee":"-0.00000085423","feeCcy":"BTC","fillPx":"46825.3","fillSz":"0.00085423","fillTime":"1704789220045","instId":"BTC-USDT","instType":"SPOT","lever":"","ordId":"664869410100072448","ordType":"market","pnl":"0","posSide":"","px":"","pxType":"","pxUsd":"","pxVol":"","quickMgnType":"","rebate":"0","rebateCcy":"USDT","reduceOnly":"false","side":"buy","slOrdPx":"","slTriggerPx":"","slTriggerPxType":"","source":"","state":"filled","stpId":"","stpMode":"","sz":"40","tag":"","tdMode":"cash","tgtCcy":"quote_ccy","tpOrdPx":"","tpTriggerPx":"","tpTriggerPxType":"","tradeId":"470287675","uTime":"1704789220046"}] + // >> [{"accFillSz":"0.00001","algoClOrdId":"","algoId":"","attachAlgoClOrdId":"","attachAlgoOrds":[],"avgPx":"48174.5","cTime":"1704957916401","cancelSource":"","cancelSourceReason":"","category":"normal","ccy":"","clOrdId":"","fee":"-0.000385396","feeCcy":"USDT","fillPx":"48174.5","fillSz":"0.00001","fillTime":"1704983881118","instId":"BTC-USDT","instType":"SPOT","lever":"","ordId":"665576973905014786","ordType":"limit","pnl":"0","posSide":"","px":"48174.5","pxType":"","pxUsd":"","pxVol":"","quickMgnType":"","rebate":"0","rebateCcy":"BTC","reduceOnly":"false","side":"sell","slOrdPx":"","slTriggerPx":"","slTriggerPxType":"","source":"","state":"filled","stpId":"","stpMode":"","sz":"0.00001","tag":"","tdMode":"cash","tgtCcy":"","tpOrdPx":"","tpTriggerPx":"","tpTriggerPxType":"","tradeId":"472610696","uTime":"1704983881135"}] + // >> [{"accFillSz":"0.00001","algoClOrdId":"","algoId":"","attachAlgoClOrdId":"","attachAlgoOrds":[],"avgPx":"48074.5","cTime":"1704957905283","cancelSource":"","cancelSourceReason":"","category":"normal","ccy":"","clOrdId":"","fee":"-0.000384596","feeCcy":"USDT","fillPx":"48074.5","fillSz":"0.00001","fillTime":"1704983824237","instId":"BTC-USDT","instType":"SPOT","lever":"","ordId":"665576927272742919","ordType":"limit","pnl":"0","posSide":"","px":"48074.5","pxType":"","pxUsd":"","pxVol":"","quickMgnType":"","rebate":"0","rebateCcy":"BTC","reduceOnly":"false","side":"sell","slOrdPx":"","slTriggerPx":"","slTriggerPxType":"","source":"","state":"filled","stpId":"","stpMode":"","sz":"0.00001","tag":"","tdMode":"cash","tgtCcy":"","tpOrdPx":"","tpTriggerPx":"","tpTriggerPxType":"","tradeId":"472601591","uTime":"1704983824240"}] + // >> [{"accFillSz":"0.00001","algoClOrdId":"","algoId":"","attachAlgoClOrdId":"","attachAlgoOrds":[],"avgPx":"48073.5","cTime":"1704957892896","cancelSource":"","cancelSourceReason":"","category":"normal","ccy":"","clOrdId":"","fee":"-0.000384588","feeCcy":"USDT","fillPx":"48073.5","fillSz":"0.00001","fillTime":"1704983824227","instId":"BTC-USDT","instType":"SPOT","lever":"","ordId":"665576875317899302","ordType":"limit","pnl":"0","posSide":"","px":"48073.5","pxType":"","pxUsd":"","pxVol":"","quickMgnType":"","rebate":"0","rebateCcy":"BTC","reduceOnly":"false","side":"sell","slOrdPx":"","slTriggerPx":"","slTriggerPxType":"","source":"","state":"filled","stpId":"","stpMode":"","sz":"0.00001","tag":"","tdMode":"cash","tgtCcy":"","tpOrdPx":"","tpTriggerPx":"","tpTriggerPxType":"","tradeId":"472601583","uTime":"1704983824230"}] + // >> [{"accFillSz":"0.00016266","algoClOrdId":"","algoId":"","attachAlgoClOrdId":"","attachAlgoOrds":[],"avgPx":"45919.8","cTime":"1704852215160","cancelSource":"","cancelSourceReason":"","category":"normal","ccy":"","clOrdId":"","fee":"-0.00000016266","feeCcy":"BTC","fillPx":"45919.8","fillSz":"0.00016266","fillTime":"1704852215162","instId":"BTC-USDT","instType":"SPOT","lever":"","ordId":"665133630767091729","ordType":"market","pnl":"0","posSide":"","px":"","pxType":"","pxUsd":"","pxVol":"","quickMgnType":"","rebate":"0","rebateCcy":"USDT","reduceOnly":"false","side":"buy","slOrdPx":"","slTriggerPx":"","slTriggerPxType":"","source":"","state":"filled","stpId":"","stpMode":"","sz":"0.00016266","tag":"","tdMode":"cash","tgtCcy":"base_ccy","tpOrdPx":"","tpTriggerPx":"","tpTriggerPxType":"","tradeId":"471113058","uTime":"1704852215163"}] + // >> [{"accFillSz":"0.00087627","algoClOrdId":"","algoId":"","attachAlgoClOrdId":"","attachAlgoOrds":[],"avgPx":"45647.6","cTime":"1704850530651","cancelSource":"","cancelSourceReason":"","category":"normal","ccy":"","clOrdId":"","fee":"-0.00000087627","feeCcy":"BTC","fillPx":"45647.6","fillSz":"0.00087627","fillTime":"1704850530652","instId":"BTC-USDT","instType":"SPOT","lever":"","ordId":"665126565424254976","ordType":"market","pnl":"0","posSide":"","px":"","pxType":"","pxUsd":"","pxVol":"","quickMgnType":"","rebate":"0","rebateCcy":"USDT","reduceOnly":"false","side":"buy","slOrdPx":"","slTriggerPx":"","slTriggerPxType":"","source":"","state":"filled","stpId":"","stpMode":"","sz":"40","tag":"","tdMode":"cash","tgtCcy":"quote_ccy","tpOrdPx":"","tpTriggerPx":"","tpTriggerPxType":"","tradeId":"471105716","uTime":"1704850530654"}] + // >> [{"accFillSz":"0.001","algoClOrdId":"","algoId":"","attachAlgoClOrdId":"","attachAlgoOrds":[],"avgPx":"45661.3","cTime":"1704850506060","cancelSource":"","cancelSourceReason":"","category":"normal","ccy":"","clOrdId":"","fee":"-0.0456613","feeCcy":"USDT","fillPx":"45661.3","fillSz":"0.001","fillTime":"1704850506061","instId":"BTC-USDT","instType":"SPOT","lever":"","ordId":"665126462282125313","ordType":"market","pnl":"0","posSide":"","px":"","pxType":"","pxUsd":"","pxVol":"","quickMgnType":"","rebate":"0","rebateCcy":"BTC","reduceOnly":"false","side":"sell","slOrdPx":"","slTriggerPx":"","slTriggerPxType":"","source":"","state":"filled","stpId":"","stpMode":"","sz":"0.001","tag":"","tdMode":"cash","tgtCcy":"base_ccy","tpOrdPx":"","tpTriggerPx":"","tpTriggerPxType":"","tradeId":"471105593","uTime":"1704850506062"}] + // >> [{"accFillSz":"0.00097361","algoClOrdId":"","algoId":"","attachAlgoClOrdId":"","attachAlgoOrds":[],"avgPx":"45743","cTime":"1704849690516","cancelSource":"","cancelSourceReason":"","category":"normal","ccy":"","clOrdId":"","fee":"-0.00000097361","feeCcy":"BTC","fillPx":"45743","fillSz":"0.00097361","fillTime":"1704849690517","instId":"BTC-USDT","instType":"SPOT","lever":"","ordId":"665123041642663944","ordType":"market","pnl":"0","posSide":"","px":"","pxType":"","pxUsd":"","pxVol":"","quickMgnType":"","rebate":"0","rebateCcy":"USDT","reduceOnly":"false","side":"buy","slOrdPx":"","slTriggerPx":"","slTriggerPxType":"","source":"","state":"filled","stpId":"","stpMode":"","sz":"0.00097361","tag":"","tdMode":"cash","tgtCcy":"base_ccy","tpOrdPx":"","tpTriggerPx":"","tpTriggerPxType":"","tradeId":"471100149","uTime":"1704849690519"}] + // >> [{"accFillSz":"0.00080894","algoClOrdId":"","algoId":"","attachAlgoClOrdId":"","attachAlgoOrds":[],"avgPx":"46728.2","cTime":"1704789666800","cancelSource":"","cancelSourceReason":"","category":"normal","ccy":"","clOrdId":"","fee":"-0.037800310108","feeCcy":"USDT","fillPx":"46728.2","fillSz":"0.00080894","fillTime":"1704789666801","instId":"BTC-USDT","instType":"SPOT","lever":"","ordId":"664871283930550273","ordType":"market","pnl":"0","posSide":"","px":"","pxType":"","pxUsd":"","pxVol":"","quickMgnType":"","rebate":"0","rebateCcy":"BTC","reduceOnly":"false","side":"sell","slOrdPx":"","slTriggerPx":"","slTriggerPxType":"","source":"","state":"filled","stpId":"","stpMode":"","sz":"37.8","tag":"","tdMode":"cash","tgtCcy":"quote_ccy","tpOrdPx":"","tpTriggerPx":"","tpTriggerPxType":"","tradeId":"470288552","uTime":"1704789666803"}] + // >> [{"accFillSz":"0.00085423","algoClOrdId":"","algoId":"","attachAlgoClOrdId":"","attachAlgoOrds":[],"avgPx":"46825.3","cTime":"1704789220044","cancelSource":"","cancelSourceReason":"","category":"normal","ccy":"","clOrdId":"","fee":"-0.00000085423","feeCcy":"BTC","fillPx":"46825.3","fillSz":"0.00085423","fillTime":"1704789220045","instId":"BTC-USDT","instType":"SPOT","lever":"","ordId":"664869410100072448","ordType":"market","pnl":"0","posSide":"","px":"","pxType":"","pxUsd":"","pxVol":"","quickMgnType":"","rebate":"0","rebateCcy":"USDT","reduceOnly":"false","side":"buy","slOrdPx":"","slTriggerPx":"","slTriggerPxType":"","source":"","state":"filled","stpId":"","stpMode":"","sz":"40","tag":"","tdMode":"cash","tgtCcy":"quote_ccy","tpOrdPx":"","tpTriggerPx":"","tpTriggerPxType":"","tradeId":"470287675","uTime":"1704789220046"}] c := client.NewGetOrderHistoryRequest().InstrumentID("BTC-USDT").Limit(1).Before(fmt.Sprintf("%d", beforeId)) res, err := c.Do(ctx) assert.NoError(t, err) @@ -196,15 +198,15 @@ func TestClient_OrderHistoryByTimeRange(t *testing.T) { startTime := time.Date(2023, 7, 1, 0, 0, 0, 0, time.UTC) t.Log(time.Since(startTime)) - //>> [{"accFillSz":"0.00001","algoClOrdId":"","algoId":"","attachAlgoClOrdId":"","attachAlgoOrds":[],"avgPx":"48174.5","cTime":"1704957916401","cancelSource":"","cancelSourceReason":"","category":"normal","ccy":"","clOrdId":"","fee":"-0.000385396","feeCcy":"USDT","fillPx":"48174.5","fillSz":"0.00001","fillTime":"1704983881118","instId":"BTC-USDT","instType":"SPOT","lever":"","ordId":"665576973905014786","ordType":"limit","pnl":"0","posSide":"","px":"48174.5","pxType":"","pxUsd":"","pxVol":"","quickMgnType":"","rebate":"0","rebateCcy":"BTC","reduceOnly":"false","side":"sell","slOrdPx":"","slTriggerPx":"","slTriggerPxType":"","source":"","state":"filled","stpId":"","stpMode":"","sz":"0.00001","tag":"","tdMode":"cash","tgtCcy":"","tpOrdPx":"","tpTriggerPx":"","tpTriggerPxType":"","tradeId":"472610696","uTime":"1704983881135"}] - //>> [{"accFillSz":"0.00001","algoClOrdId":"","algoId":"","attachAlgoClOrdId":"","attachAlgoOrds":[],"avgPx":"48074.5","cTime":"1704957905283","cancelSource":"","cancelSourceReason":"","category":"normal","ccy":"","clOrdId":"","fee":"-0.000384596","feeCcy":"USDT","fillPx":"48074.5","fillSz":"0.00001","fillTime":"1704983824237","instId":"BTC-USDT","instType":"SPOT","lever":"","ordId":"665576927272742919","ordType":"limit","pnl":"0","posSide":"","px":"48074.5","pxType":"","pxUsd":"","pxVol":"","quickMgnType":"","rebate":"0","rebateCcy":"BTC","reduceOnly":"false","side":"sell","slOrdPx":"","slTriggerPx":"","slTriggerPxType":"","source":"","state":"filled","stpId":"","stpMode":"","sz":"0.00001","tag":"","tdMode":"cash","tgtCcy":"","tpOrdPx":"","tpTriggerPx":"","tpTriggerPxType":"","tradeId":"472601591","uTime":"1704983824240"}] - //>> [{"accFillSz":"0.00001","algoClOrdId":"","algoId":"","attachAlgoClOrdId":"","attachAlgoOrds":[],"avgPx":"48073.5","cTime":"1704957892896","cancelSource":"","cancelSourceReason":"","category":"normal","ccy":"","clOrdId":"","fee":"-0.000384588","feeCcy":"USDT","fillPx":"48073.5","fillSz":"0.00001","fillTime":"1704983824227","instId":"BTC-USDT","instType":"SPOT","lever":"","ordId":"665576875317899302","ordType":"limit","pnl":"0","posSide":"","px":"48073.5","pxType":"","pxUsd":"","pxVol":"","quickMgnType":"","rebate":"0","rebateCcy":"BTC","reduceOnly":"false","side":"sell","slOrdPx":"","slTriggerPx":"","slTriggerPxType":"","source":"","state":"filled","stpId":"","stpMode":"","sz":"0.00001","tag":"","tdMode":"cash","tgtCcy":"","tpOrdPx":"","tpTriggerPx":"","tpTriggerPxType":"","tradeId":"472601583","uTime":"1704983824230"}] - //>> [{"accFillSz":"0.00016266","algoClOrdId":"","algoId":"","attachAlgoClOrdId":"","attachAlgoOrds":[],"avgPx":"45919.8","cTime":"1704852215160","cancelSource":"","cancelSourceReason":"","category":"normal","ccy":"","clOrdId":"","fee":"-0.00000016266","feeCcy":"BTC","fillPx":"45919.8","fillSz":"0.00016266","fillTime":"1704852215162","instId":"BTC-USDT","instType":"SPOT","lever":"","ordId":"665133630767091729","ordType":"market","pnl":"0","posSide":"","px":"","pxType":"","pxUsd":"","pxVol":"","quickMgnType":"","rebate":"0","rebateCcy":"USDT","reduceOnly":"false","side":"buy","slOrdPx":"","slTriggerPx":"","slTriggerPxType":"","source":"","state":"filled","stpId":"","stpMode":"","sz":"0.00016266","tag":"","tdMode":"cash","tgtCcy":"base_ccy","tpOrdPx":"","tpTriggerPx":"","tpTriggerPxType":"","tradeId":"471113058","uTime":"1704852215163"}] - //>> [{"accFillSz":"0.00087627","algoClOrdId":"","algoId":"","attachAlgoClOrdId":"","attachAlgoOrds":[],"avgPx":"45647.6","cTime":"1704850530651","cancelSource":"","cancelSourceReason":"","category":"normal","ccy":"","clOrdId":"","fee":"-0.00000087627","feeCcy":"BTC","fillPx":"45647.6","fillSz":"0.00087627","fillTime":"1704850530652","instId":"BTC-USDT","instType":"SPOT","lever":"","ordId":"665126565424254976","ordType":"market","pnl":"0","posSide":"","px":"","pxType":"","pxUsd":"","pxVol":"","quickMgnType":"","rebate":"0","rebateCcy":"USDT","reduceOnly":"false","side":"buy","slOrdPx":"","slTriggerPx":"","slTriggerPxType":"","source":"","state":"filled","stpId":"","stpMode":"","sz":"40","tag":"","tdMode":"cash","tgtCcy":"quote_ccy","tpOrdPx":"","tpTriggerPx":"","tpTriggerPxType":"","tradeId":"471105716","uTime":"1704850530654"}] - //>> [{"accFillSz":"0.001","algoClOrdId":"","algoId":"","attachAlgoClOrdId":"","attachAlgoOrds":[],"avgPx":"45661.3","cTime":"1704850506060","cancelSource":"","cancelSourceReason":"","category":"normal","ccy":"","clOrdId":"","fee":"-0.0456613","feeCcy":"USDT","fillPx":"45661.3","fillSz":"0.001","fillTime":"1704850506061","instId":"BTC-USDT","instType":"SPOT","lever":"","ordId":"665126462282125313","ordType":"market","pnl":"0","posSide":"","px":"","pxType":"","pxUsd":"","pxVol":"","quickMgnType":"","rebate":"0","rebateCcy":"BTC","reduceOnly":"false","side":"sell","slOrdPx":"","slTriggerPx":"","slTriggerPxType":"","source":"","state":"filled","stpId":"","stpMode":"","sz":"0.001","tag":"","tdMode":"cash","tgtCcy":"base_ccy","tpOrdPx":"","tpTriggerPx":"","tpTriggerPxType":"","tradeId":"471105593","uTime":"1704850506062"}] - //>> [{"accFillSz":"0.00097361","algoClOrdId":"","algoId":"","attachAlgoClOrdId":"","attachAlgoOrds":[],"avgPx":"45743","cTime":"1704849690516","cancelSource":"","cancelSourceReason":"","category":"normal","ccy":"","clOrdId":"","fee":"-0.00000097361","feeCcy":"BTC","fillPx":"45743","fillSz":"0.00097361","fillTime":"1704849690517","instId":"BTC-USDT","instType":"SPOT","lever":"","ordId":"665123041642663944","ordType":"market","pnl":"0","posSide":"","px":"","pxType":"","pxUsd":"","pxVol":"","quickMgnType":"","rebate":"0","rebateCcy":"USDT","reduceOnly":"false","side":"buy","slOrdPx":"","slTriggerPx":"","slTriggerPxType":"","source":"","state":"filled","stpId":"","stpMode":"","sz":"0.00097361","tag":"","tdMode":"cash","tgtCcy":"base_ccy","tpOrdPx":"","tpTriggerPx":"","tpTriggerPxType":"","tradeId":"471100149","uTime":"1704849690519"}] - //>> [{"accFillSz":"0.00080894","algoClOrdId":"","algoId":"","attachAlgoClOrdId":"","attachAlgoOrds":[],"avgPx":"46728.2","cTime":"1704789666800","cancelSource":"","cancelSourceReason":"","category":"normal","ccy":"","clOrdId":"","fee":"-0.037800310108","feeCcy":"USDT","fillPx":"46728.2","fillSz":"0.00080894","fillTime":"1704789666801","instId":"BTC-USDT","instType":"SPOT","lever":"","ordId":"664871283930550273","ordType":"market","pnl":"0","posSide":"","px":"","pxType":"","pxUsd":"","pxVol":"","quickMgnType":"","rebate":"0","rebateCcy":"BTC","reduceOnly":"false","side":"sell","slOrdPx":"","slTriggerPx":"","slTriggerPxType":"","source":"","state":"filled","stpId":"","stpMode":"","sz":"37.8","tag":"","tdMode":"cash","tgtCcy":"quote_ccy","tpOrdPx":"","tpTriggerPx":"","tpTriggerPxType":"","tradeId":"470288552","uTime":"1704789666803"}] - //>> [{"accFillSz":"0.00085423","algoClOrdId":"","algoId":"","attachAlgoClOrdId":"","attachAlgoOrds":[],"avgPx":"46825.3","cTime":"1704789220044","cancelSource":"","cancelSourceReason":"","category":"normal","ccy":"","clOrdId":"","fee":"-0.00000085423","feeCcy":"BTC","fillPx":"46825.3","fillSz":"0.00085423","fillTime":"1704789220045","instId":"BTC-USDT","instType":"SPOT","lever":"","ordId":"664869410100072448","ordType":"market","pnl":"0","posSide":"","px":"","pxType":"","pxUsd":"","pxVol":"","quickMgnType":"","rebate":"0","rebateCcy":"USDT","reduceOnly":"false","side":"buy","slOrdPx":"","slTriggerPx":"","slTriggerPxType":"","source":"","state":"filled","stpId":"","stpMode":"","sz":"40","tag":"","tdMode":"cash","tgtCcy":"quote_ccy","tpOrdPx":"","tpTriggerPx":"","tpTriggerPxType":"","tradeId":"470287675","uTime":"1704789220046"}] + // >> [{"accFillSz":"0.00001","algoClOrdId":"","algoId":"","attachAlgoClOrdId":"","attachAlgoOrds":[],"avgPx":"48174.5","cTime":"1704957916401","cancelSource":"","cancelSourceReason":"","category":"normal","ccy":"","clOrdId":"","fee":"-0.000385396","feeCcy":"USDT","fillPx":"48174.5","fillSz":"0.00001","fillTime":"1704983881118","instId":"BTC-USDT","instType":"SPOT","lever":"","ordId":"665576973905014786","ordType":"limit","pnl":"0","posSide":"","px":"48174.5","pxType":"","pxUsd":"","pxVol":"","quickMgnType":"","rebate":"0","rebateCcy":"BTC","reduceOnly":"false","side":"sell","slOrdPx":"","slTriggerPx":"","slTriggerPxType":"","source":"","state":"filled","stpId":"","stpMode":"","sz":"0.00001","tag":"","tdMode":"cash","tgtCcy":"","tpOrdPx":"","tpTriggerPx":"","tpTriggerPxType":"","tradeId":"472610696","uTime":"1704983881135"}] + // >> [{"accFillSz":"0.00001","algoClOrdId":"","algoId":"","attachAlgoClOrdId":"","attachAlgoOrds":[],"avgPx":"48074.5","cTime":"1704957905283","cancelSource":"","cancelSourceReason":"","category":"normal","ccy":"","clOrdId":"","fee":"-0.000384596","feeCcy":"USDT","fillPx":"48074.5","fillSz":"0.00001","fillTime":"1704983824237","instId":"BTC-USDT","instType":"SPOT","lever":"","ordId":"665576927272742919","ordType":"limit","pnl":"0","posSide":"","px":"48074.5","pxType":"","pxUsd":"","pxVol":"","quickMgnType":"","rebate":"0","rebateCcy":"BTC","reduceOnly":"false","side":"sell","slOrdPx":"","slTriggerPx":"","slTriggerPxType":"","source":"","state":"filled","stpId":"","stpMode":"","sz":"0.00001","tag":"","tdMode":"cash","tgtCcy":"","tpOrdPx":"","tpTriggerPx":"","tpTriggerPxType":"","tradeId":"472601591","uTime":"1704983824240"}] + // >> [{"accFillSz":"0.00001","algoClOrdId":"","algoId":"","attachAlgoClOrdId":"","attachAlgoOrds":[],"avgPx":"48073.5","cTime":"1704957892896","cancelSource":"","cancelSourceReason":"","category":"normal","ccy":"","clOrdId":"","fee":"-0.000384588","feeCcy":"USDT","fillPx":"48073.5","fillSz":"0.00001","fillTime":"1704983824227","instId":"BTC-USDT","instType":"SPOT","lever":"","ordId":"665576875317899302","ordType":"limit","pnl":"0","posSide":"","px":"48073.5","pxType":"","pxUsd":"","pxVol":"","quickMgnType":"","rebate":"0","rebateCcy":"BTC","reduceOnly":"false","side":"sell","slOrdPx":"","slTriggerPx":"","slTriggerPxType":"","source":"","state":"filled","stpId":"","stpMode":"","sz":"0.00001","tag":"","tdMode":"cash","tgtCcy":"","tpOrdPx":"","tpTriggerPx":"","tpTriggerPxType":"","tradeId":"472601583","uTime":"1704983824230"}] + // >> [{"accFillSz":"0.00016266","algoClOrdId":"","algoId":"","attachAlgoClOrdId":"","attachAlgoOrds":[],"avgPx":"45919.8","cTime":"1704852215160","cancelSource":"","cancelSourceReason":"","category":"normal","ccy":"","clOrdId":"","fee":"-0.00000016266","feeCcy":"BTC","fillPx":"45919.8","fillSz":"0.00016266","fillTime":"1704852215162","instId":"BTC-USDT","instType":"SPOT","lever":"","ordId":"665133630767091729","ordType":"market","pnl":"0","posSide":"","px":"","pxType":"","pxUsd":"","pxVol":"","quickMgnType":"","rebate":"0","rebateCcy":"USDT","reduceOnly":"false","side":"buy","slOrdPx":"","slTriggerPx":"","slTriggerPxType":"","source":"","state":"filled","stpId":"","stpMode":"","sz":"0.00016266","tag":"","tdMode":"cash","tgtCcy":"base_ccy","tpOrdPx":"","tpTriggerPx":"","tpTriggerPxType":"","tradeId":"471113058","uTime":"1704852215163"}] + // >> [{"accFillSz":"0.00087627","algoClOrdId":"","algoId":"","attachAlgoClOrdId":"","attachAlgoOrds":[],"avgPx":"45647.6","cTime":"1704850530651","cancelSource":"","cancelSourceReason":"","category":"normal","ccy":"","clOrdId":"","fee":"-0.00000087627","feeCcy":"BTC","fillPx":"45647.6","fillSz":"0.00087627","fillTime":"1704850530652","instId":"BTC-USDT","instType":"SPOT","lever":"","ordId":"665126565424254976","ordType":"market","pnl":"0","posSide":"","px":"","pxType":"","pxUsd":"","pxVol":"","quickMgnType":"","rebate":"0","rebateCcy":"USDT","reduceOnly":"false","side":"buy","slOrdPx":"","slTriggerPx":"","slTriggerPxType":"","source":"","state":"filled","stpId":"","stpMode":"","sz":"40","tag":"","tdMode":"cash","tgtCcy":"quote_ccy","tpOrdPx":"","tpTriggerPx":"","tpTriggerPxType":"","tradeId":"471105716","uTime":"1704850530654"}] + // >> [{"accFillSz":"0.001","algoClOrdId":"","algoId":"","attachAlgoClOrdId":"","attachAlgoOrds":[],"avgPx":"45661.3","cTime":"1704850506060","cancelSource":"","cancelSourceReason":"","category":"normal","ccy":"","clOrdId":"","fee":"-0.0456613","feeCcy":"USDT","fillPx":"45661.3","fillSz":"0.001","fillTime":"1704850506061","instId":"BTC-USDT","instType":"SPOT","lever":"","ordId":"665126462282125313","ordType":"market","pnl":"0","posSide":"","px":"","pxType":"","pxUsd":"","pxVol":"","quickMgnType":"","rebate":"0","rebateCcy":"BTC","reduceOnly":"false","side":"sell","slOrdPx":"","slTriggerPx":"","slTriggerPxType":"","source":"","state":"filled","stpId":"","stpMode":"","sz":"0.001","tag":"","tdMode":"cash","tgtCcy":"base_ccy","tpOrdPx":"","tpTriggerPx":"","tpTriggerPxType":"","tradeId":"471105593","uTime":"1704850506062"}] + // >> [{"accFillSz":"0.00097361","algoClOrdId":"","algoId":"","attachAlgoClOrdId":"","attachAlgoOrds":[],"avgPx":"45743","cTime":"1704849690516","cancelSource":"","cancelSourceReason":"","category":"normal","ccy":"","clOrdId":"","fee":"-0.00000097361","feeCcy":"BTC","fillPx":"45743","fillSz":"0.00097361","fillTime":"1704849690517","instId":"BTC-USDT","instType":"SPOT","lever":"","ordId":"665123041642663944","ordType":"market","pnl":"0","posSide":"","px":"","pxType":"","pxUsd":"","pxVol":"","quickMgnType":"","rebate":"0","rebateCcy":"USDT","reduceOnly":"false","side":"buy","slOrdPx":"","slTriggerPx":"","slTriggerPxType":"","source":"","state":"filled","stpId":"","stpMode":"","sz":"0.00097361","tag":"","tdMode":"cash","tgtCcy":"base_ccy","tpOrdPx":"","tpTriggerPx":"","tpTriggerPxType":"","tradeId":"471100149","uTime":"1704849690519"}] + // >> [{"accFillSz":"0.00080894","algoClOrdId":"","algoId":"","attachAlgoClOrdId":"","attachAlgoOrds":[],"avgPx":"46728.2","cTime":"1704789666800","cancelSource":"","cancelSourceReason":"","category":"normal","ccy":"","clOrdId":"","fee":"-0.037800310108","feeCcy":"USDT","fillPx":"46728.2","fillSz":"0.00080894","fillTime":"1704789666801","instId":"BTC-USDT","instType":"SPOT","lever":"","ordId":"664871283930550273","ordType":"market","pnl":"0","posSide":"","px":"","pxType":"","pxUsd":"","pxVol":"","quickMgnType":"","rebate":"0","rebateCcy":"BTC","reduceOnly":"false","side":"sell","slOrdPx":"","slTriggerPx":"","slTriggerPxType":"","source":"","state":"filled","stpId":"","stpMode":"","sz":"37.8","tag":"","tdMode":"cash","tgtCcy":"quote_ccy","tpOrdPx":"","tpTriggerPx":"","tpTriggerPxType":"","tradeId":"470288552","uTime":"1704789666803"}] + // >> [{"accFillSz":"0.00085423","algoClOrdId":"","algoId":"","attachAlgoClOrdId":"","attachAlgoOrds":[],"avgPx":"46825.3","cTime":"1704789220044","cancelSource":"","cancelSourceReason":"","category":"normal","ccy":"","clOrdId":"","fee":"-0.00000085423","feeCcy":"BTC","fillPx":"46825.3","fillSz":"0.00085423","fillTime":"1704789220045","instId":"BTC-USDT","instType":"SPOT","lever":"","ordId":"664869410100072448","ordType":"market","pnl":"0","posSide":"","px":"","pxType":"","pxUsd":"","pxVol":"","quickMgnType":"","rebate":"0","rebateCcy":"USDT","reduceOnly":"false","side":"buy","slOrdPx":"","slTriggerPx":"","slTriggerPxType":"","source":"","state":"filled","stpId":"","stpMode":"","sz":"40","tag":"","tdMode":"cash","tgtCcy":"quote_ccy","tpOrdPx":"","tpTriggerPx":"","tpTriggerPxType":"","tradeId":"470287675","uTime":"1704789220046"}] c := client.NewGetOrderHistoryRequest().InstrumentID("BTC-USDT").Limit(100).After("665576927272742919").StartTime(types.NewMillisecondTimestampFromInt(1704789220044).Time()) res, err := c.Do(ctx) assert.NoError(t, err) @@ -235,7 +237,7 @@ func TestClient_TransactionHistoryAll(t *testing.T) { if len(res) != 1 { break } - //orders = append(orders, res...) + // orders = append(orders, res...) beforeId = int64(res[0].BillId) t.Log(res[0]) } @@ -346,3 +348,94 @@ func TestClient_CandlesTicksRequest(t *testing.T) { assert.NoError(t, err) t.Log(res) } + +func TestClient_Margin(t *testing.T) { + key, secret, passphrase, ok := testutil.IntegrationTestWithPassphraseConfigured(t, "OKEX") + if !ok { + t.SkipNow() + return + } + + ctx := context.Background() + client := NewClient() + client.Auth(key, secret, passphrase) + + accountConfigResp, err := client.NewGetAccountConfigRequest().Do(ctx) + if assert.NoError(t, err) { + t.Logf("account config response: %+v", accountConfigResp) + } + + t.Run("GetAccountLeverageInfoRequest cross + ccy + instId", func(t *testing.T) { + req := client.NewGetAccountLeverageInfoRequest() + req.MarginMode(MarginModeCross) + req.Currency("BTC") + req.InstrumentId("BTC-USDT") + resp, err := req.Do(ctx) + if assert.NoError(t, err) { + t.Logf("response: %+v", resp) + } + }) + + t.Run("GetAccountLeverageInfoRequest cross + ccy", func(t *testing.T) { + req := client.NewGetAccountLeverageInfoRequest() + req.MarginMode(MarginModeCross) + req.Currency("BTC") + resp, err := req.Do(ctx) + if assert.NoError(t, err) { + t.Logf("response: %+v", resp) + } + }) + + t.Run("GetAccountMaxLoanRequest", func(t *testing.T) { + resp, err := client.NewGetAccountMaxLoanRequest(). + MarginMode(MarginModeCross). + Currency("BTC").Do(ctx) + if assert.NoError(t, err) { + t.Logf("response: %+v", resp) + } + }) + + t.Run("GetMaxAvailableSizeRequest", func(t *testing.T) { + if accountConfigResp[0].AccountLevel == 1 { + t.Logf("can not call GetMaxAvailableSizeRequest with TradeModeMargin under spot mode") + req := client.NewGetMaxAvailableSizeRequest() + resp, err := req. + TdMode(TradeModeCash). + InstrumentID("BTC-USDT"). + Do(ctx) + if assert.NoError(t, err) { + t.Logf("response: %+v", resp) + } + } else { + req := client.NewGetMaxAvailableSizeRequest() + resp, err := req. + TdMode(TradeModeCross). + InstrumentID("BTC-USDT"). + Do(ctx) + if assert.NoError(t, err) { + t.Logf("response: %+v", resp) + } + } + }) + + t.Run("borrow and repay", func(t *testing.T) { + resp, err := client.NewSpotManualBorrowRepayRequest(). + Currency("BTC"). + Side(MarginSideBorrow). + Amount("0.0001"). + Do(ctx) + if assert.NoError(t, err) { + t.Logf("borrow response: %+v", resp) + + time.Sleep(1 * time.Second) + repayResp, repayErr := client.NewSpotManualBorrowRepayRequest(). + Currency("BTC"). + Side(MarginSideRepay). + Amount("0.0001"). + Do(ctx) + if assert.NoError(t, repayErr) { + t.Logf("repay response: %+v", repayResp) + } + } + }) +} diff --git a/pkg/exchange/okex/okexapi/get_account_info_request.go b/pkg/exchange/okex/okexapi/get_account_balance_request.go similarity index 86% rename from pkg/exchange/okex/okexapi/get_account_info_request.go rename to pkg/exchange/okex/okexapi/get_account_balance_request.go index ae1d936a74..74b8a4b7f3 100644 --- a/pkg/exchange/okex/okexapi/get_account_info_request.go +++ b/pkg/exchange/okex/okexapi/get_account_balance_request.go @@ -28,13 +28,13 @@ type Account struct { Details []BalanceDetail `json:"details"` } -//go:generate GetRequest -url "/api/v5/account/balance" -type GetAccountInfoRequest -responseDataType []Account -type GetAccountInfoRequest struct { +//go:generate GetRequest -url "/api/v5/account/balance" -type GetAccountBalanceRequest -responseDataType []Account +type GetAccountBalanceRequest struct { client requestgen.AuthenticatedAPIClient } -func (c *RestClient) NewGetAccountInfoRequest() *GetAccountInfoRequest { - return &GetAccountInfoRequest{ +func (c *RestClient) NewGetAccountBalanceRequest() *GetAccountBalanceRequest { + return &GetAccountBalanceRequest{ client: c, } } diff --git a/pkg/exchange/okex/okexapi/get_account_info_request_requestgen.go b/pkg/exchange/okex/okexapi/get_account_balance_request_requestgen.go similarity index 63% rename from pkg/exchange/okex/okexapi/get_account_info_request_requestgen.go rename to pkg/exchange/okex/okexapi/get_account_balance_request_requestgen.go index b90836d196..75cf5bd95d 100644 --- a/pkg/exchange/okex/okexapi/get_account_info_request_requestgen.go +++ b/pkg/exchange/okex/okexapi/get_account_balance_request_requestgen.go @@ -1,4 +1,4 @@ -// Code generated by "requestgen -method GET -responseType .APIResponse -responseDataField Data -url /api/v5/account/balance -type GetAccountInfoRequest -responseDataType []Account"; DO NOT EDIT. +// Code generated by "requestgen -method GET -responseType .APIResponse -responseDataField Data -url /api/v5/account/balance -type GetAccountBalanceRequest -responseDataType []Account"; DO NOT EDIT. package okexapi @@ -12,7 +12,7 @@ import ( ) // GetQueryParameters builds and checks the query parameters and returns url.Values -func (g *GetAccountInfoRequest) GetQueryParameters() (url.Values, error) { +func (g *GetAccountBalanceRequest) GetQueryParameters() (url.Values, error) { var params = map[string]interface{}{} query := url.Values{} @@ -24,14 +24,14 @@ func (g *GetAccountInfoRequest) GetQueryParameters() (url.Values, error) { } // GetParameters builds and checks the parameters and return the result in a map object -func (g *GetAccountInfoRequest) GetParameters() (map[string]interface{}, error) { +func (g *GetAccountBalanceRequest) GetParameters() (map[string]interface{}, error) { var params = map[string]interface{}{} return params, nil } // GetParametersQuery converts the parameters from GetParameters into the url.Values format -func (g *GetAccountInfoRequest) GetParametersQuery() (url.Values, error) { +func (g *GetAccountBalanceRequest) GetParametersQuery() (url.Values, error) { query := url.Values{} params, err := g.GetParameters() @@ -53,7 +53,7 @@ func (g *GetAccountInfoRequest) GetParametersQuery() (url.Values, error) { } // GetParametersJSON converts the parameters from GetParameters into the JSON format -func (g *GetAccountInfoRequest) GetParametersJSON() ([]byte, error) { +func (g *GetAccountBalanceRequest) GetParametersJSON() ([]byte, error) { params, err := g.GetParameters() if err != nil { return nil, err @@ -63,13 +63,13 @@ func (g *GetAccountInfoRequest) GetParametersJSON() ([]byte, error) { } // GetSlugParameters builds and checks the slug parameters and return the result in a map object -func (g *GetAccountInfoRequest) GetSlugParameters() (map[string]interface{}, error) { +func (g *GetAccountBalanceRequest) GetSlugParameters() (map[string]interface{}, error) { var params = map[string]interface{}{} return params, nil } -func (g *GetAccountInfoRequest) applySlugsToUrl(url string, slugs map[string]string) string { +func (g *GetAccountBalanceRequest) applySlugsToUrl(url string, slugs map[string]string) string { for _k, _v := range slugs { needleRE := regexp.MustCompile(":" + _k + "\\b") url = needleRE.ReplaceAllString(url, _v) @@ -78,7 +78,7 @@ func (g *GetAccountInfoRequest) applySlugsToUrl(url string, slugs map[string]str return url } -func (g *GetAccountInfoRequest) iterateSlice(slice interface{}, _f func(it interface{})) { +func (g *GetAccountBalanceRequest) iterateSlice(slice interface{}, _f func(it interface{})) { sliceValue := reflect.ValueOf(slice) for _i := 0; _i < sliceValue.Len(); _i++ { it := sliceValue.Index(_i).Interface() @@ -86,7 +86,7 @@ func (g *GetAccountInfoRequest) iterateSlice(slice interface{}, _f func(it inter } } -func (g *GetAccountInfoRequest) isVarSlice(_v interface{}) bool { +func (g *GetAccountBalanceRequest) isVarSlice(_v interface{}) bool { rt := reflect.TypeOf(_v) switch rt.Kind() { case reflect.Slice: @@ -95,7 +95,7 @@ func (g *GetAccountInfoRequest) isVarSlice(_v interface{}) bool { return false } -func (g *GetAccountInfoRequest) GetSlugsMap() (map[string]string, error) { +func (g *GetAccountBalanceRequest) GetSlugsMap() (map[string]string, error) { slugs := map[string]string{} params, err := g.GetSlugParameters() if err != nil { @@ -110,12 +110,12 @@ func (g *GetAccountInfoRequest) GetSlugsMap() (map[string]string, error) { } // GetPath returns the request path of the API -func (g *GetAccountInfoRequest) GetPath() string { +func (g *GetAccountBalanceRequest) GetPath() string { return "/api/v5/account/balance" } // Do generates the request object and send the request object to the API endpoint -func (g *GetAccountInfoRequest) Do(ctx context.Context) ([]Account, error) { +func (g *GetAccountBalanceRequest) Do(ctx context.Context) ([]Account, error) { // no body params var params interface{} @@ -136,15 +136,29 @@ func (g *GetAccountInfoRequest) Do(ctx context.Context) ([]Account, error) { } var apiResponse APIResponse - if err := response.DecodeJSON(&apiResponse); err != nil { - return nil, err + + type responseUnmarshaler interface { + Unmarshal(data []byte) error + } + + if unmarshaler, ok := interface{}(&apiResponse).(responseUnmarshaler); ok { + if err := unmarshaler.Unmarshal(response.Body); err != nil { + return nil, err + } + } else { + // The line below checks the content type, however, some API server might not send the correct content type header, + // Hence, this is commented for backward compatibility + // response.IsJSON() + if err := response.DecodeJSON(&apiResponse); err != nil { + return nil, err + } } type responseValidator interface { Validate() error } - validator, ok := interface{}(apiResponse).(responseValidator) - if ok { + + if validator, ok := interface{}(&apiResponse).(responseValidator); ok { if err := validator.Validate(); err != nil { return nil, err } diff --git a/pkg/exchange/okex/okexapi/get_account_config_request.go b/pkg/exchange/okex/okexapi/get_account_config_request.go new file mode 100644 index 0000000000..d1d4304e1a --- /dev/null +++ b/pkg/exchange/okex/okexapi/get_account_config_request.go @@ -0,0 +1,51 @@ +package okexapi + +import ( + "github.com/c9s/requestgen" + + "github.com/c9s/bbgo/pkg/types" +) + +//go:generate -command GetRequest requestgen -method GET -responseType .APIResponse -responseDataField Data +//go:generate -command PostRequest requestgen -method POST -responseType .APIResponse -responseDataField Data + +type AccountConfig struct { + AccountLevel types.StrInt64 `json:"acctLv"` + + AccountSelfTradePreventionMode string `json:"acctStpMode"` + + AutoLoan bool `json:"autoLoan"` + ContractIsolationMode string `json:"ctIsoMode"` + EnableSpotBorrow bool `json:"enableSpotBorrow"` + GreeksType string `json:"greeksType"` + Ip string `json:"ip"` + Type string `json:"type"` + KycLv types.StrInt64 `json:"kycLv"` + Label string `json:"label"` + Level string `json:"level"` + LevelTmp string `json:"levelTmp"` + LiquidationGear string `json:"liquidationGear"` + MainUid string `json:"mainUid"` + MgnIsoMode string `json:"mgnIsoMode"` + OpAuth string `json:"opAuth"` + Perm string `json:"perm"` + PosMode string `json:"posMode"` + RoleType types.StrInt64 `json:"roleType"` + SpotBorrowAutoRepay bool `json:"spotBorrowAutoRepay"` + SpotOffsetType string `json:"spotOffsetType"` + SpotRoleType string `json:"spotRoleType"` + SpotTraderInsts []interface{} `json:"spotTraderInsts"` + TraderInsts []interface{} `json:"traderInsts"` + Uid string `json:"uid"` +} + +//go:generate GetRequest -url "/api/v5/account/config" -type GetAccountConfigRequest -responseDataType []AccountConfig +type GetAccountConfigRequest struct { + client requestgen.AuthenticatedAPIClient +} + +func (c *RestClient) NewGetAccountConfigRequest() *GetAccountConfigRequest { + return &GetAccountConfigRequest{ + client: c, + } +} diff --git a/pkg/exchange/okex/okexapi/get_account_config_request_requestgen.go b/pkg/exchange/okex/okexapi/get_account_config_request_requestgen.go new file mode 100644 index 0000000000..cd410e70c8 --- /dev/null +++ b/pkg/exchange/okex/okexapi/get_account_config_request_requestgen.go @@ -0,0 +1,171 @@ +// Code generated by "requestgen -method GET -responseType .APIResponse -responseDataField Data -url /api/v5/account/config -type GetAccountConfigRequest -responseDataType []AccountConfig"; DO NOT EDIT. + +package okexapi + +import ( + "context" + "encoding/json" + "fmt" + "net/url" + "reflect" + "regexp" +) + +// GetQueryParameters builds and checks the query parameters and returns url.Values +func (g *GetAccountConfigRequest) GetQueryParameters() (url.Values, error) { + var params = map[string]interface{}{} + + query := url.Values{} + for _k, _v := range params { + query.Add(_k, fmt.Sprintf("%v", _v)) + } + + return query, nil +} + +// GetParameters builds and checks the parameters and return the result in a map object +func (g *GetAccountConfigRequest) GetParameters() (map[string]interface{}, error) { + var params = map[string]interface{}{} + + return params, nil +} + +// GetParametersQuery converts the parameters from GetParameters into the url.Values format +func (g *GetAccountConfigRequest) GetParametersQuery() (url.Values, error) { + query := url.Values{} + + params, err := g.GetParameters() + if err != nil { + return query, err + } + + for _k, _v := range params { + if g.isVarSlice(_v) { + g.iterateSlice(_v, func(it interface{}) { + query.Add(_k+"[]", fmt.Sprintf("%v", it)) + }) + } else { + query.Add(_k, fmt.Sprintf("%v", _v)) + } + } + + return query, nil +} + +// GetParametersJSON converts the parameters from GetParameters into the JSON format +func (g *GetAccountConfigRequest) GetParametersJSON() ([]byte, error) { + params, err := g.GetParameters() + if err != nil { + return nil, err + } + + return json.Marshal(params) +} + +// GetSlugParameters builds and checks the slug parameters and return the result in a map object +func (g *GetAccountConfigRequest) GetSlugParameters() (map[string]interface{}, error) { + var params = map[string]interface{}{} + + return params, nil +} + +func (g *GetAccountConfigRequest) applySlugsToUrl(url string, slugs map[string]string) string { + for _k, _v := range slugs { + needleRE := regexp.MustCompile(":" + _k + "\\b") + url = needleRE.ReplaceAllString(url, _v) + } + + return url +} + +func (g *GetAccountConfigRequest) iterateSlice(slice interface{}, _f func(it interface{})) { + sliceValue := reflect.ValueOf(slice) + for _i := 0; _i < sliceValue.Len(); _i++ { + it := sliceValue.Index(_i).Interface() + _f(it) + } +} + +func (g *GetAccountConfigRequest) isVarSlice(_v interface{}) bool { + rt := reflect.TypeOf(_v) + switch rt.Kind() { + case reflect.Slice: + return true + } + return false +} + +func (g *GetAccountConfigRequest) GetSlugsMap() (map[string]string, error) { + slugs := map[string]string{} + params, err := g.GetSlugParameters() + if err != nil { + return slugs, nil + } + + for _k, _v := range params { + slugs[_k] = fmt.Sprintf("%v", _v) + } + + return slugs, nil +} + +// GetPath returns the request path of the API +func (g *GetAccountConfigRequest) GetPath() string { + return "/api/v5/account/config" +} + +// Do generates the request object and send the request object to the API endpoint +func (g *GetAccountConfigRequest) Do(ctx context.Context) ([]AccountConfig, error) { + + // no body params + var params interface{} + query := url.Values{} + + var apiURL string + + apiURL = g.GetPath() + + req, err := g.client.NewAuthenticatedRequest(ctx, "GET", apiURL, query, params) + if err != nil { + return nil, err + } + + response, err := g.client.SendRequest(req) + if err != nil { + return nil, err + } + + var apiResponse APIResponse + + type responseUnmarshaler interface { + Unmarshal(data []byte) error + } + + if unmarshaler, ok := interface{}(&apiResponse).(responseUnmarshaler); ok { + if err := unmarshaler.Unmarshal(response.Body); err != nil { + return nil, err + } + } else { + // The line below checks the content type, however, some API server might not send the correct content type header, + // Hence, this is commented for backward compatibility + // response.IsJSON() + if err := response.DecodeJSON(&apiResponse); err != nil { + return nil, err + } + } + + type responseValidator interface { + Validate() error + } + + if validator, ok := interface{}(&apiResponse).(responseValidator); ok { + if err := validator.Validate(); err != nil { + return nil, err + } + } + var data []AccountConfig + if err := json.Unmarshal(apiResponse.Data, &data); err != nil { + return nil, err + } + return data, nil +} diff --git a/pkg/exchange/okex/okexapi/get_account_interest_limits_request.go b/pkg/exchange/okex/okexapi/get_account_interest_limits_request.go new file mode 100644 index 0000000000..664f513b16 --- /dev/null +++ b/pkg/exchange/okex/okexapi/get_account_interest_limits_request.go @@ -0,0 +1,55 @@ +package okexapi + +import ( + "github.com/c9s/requestgen" + + "github.com/c9s/bbgo/pkg/fixedpoint" + "github.com/c9s/bbgo/pkg/types" +) + +//go:generate -command GetRequest requestgen -method GET -responseType .APIResponse -responseDataField Data +//go:generate -command PostRequest requestgen -method POST -responseType .APIResponse -responseDataField Data + +type BorrowInterestLimit struct { + Debt fixedpoint.Value `json:"debt"` + Interest fixedpoint.Value `json:"interest"` + LoanAlloc fixedpoint.Value `json:"loanAlloc"` + NextDiscountTime types.MillisecondTimestamp `json:"nextDiscountTime"` + NextInterestTime types.MillisecondTimestamp `json:"nextInterestTime"` + Records []struct { + Currency string `json:"ccy"` + + // AvailableLoad = Available amount for current account (Within the locked quota) + AvailLoan fixedpoint.Value `json:"availLoan"` + AvgRate fixedpoint.Value `json:"avgRate"` + Interest fixedpoint.Value `json:"interest"` + + // LoanQuota = Borrow limit of master account + // If loan allocation has been assigned, then it is the borrow limit of the current trading account + LoanQuota fixedpoint.Value `json:"loanQuota"` + PosLoan fixedpoint.Value `json:"posLoan"` + Rate fixedpoint.Value `json:"rate"` + + SurplusLimit fixedpoint.Value `json:"surplusLmt"` + SurplusLimitDetails struct { + AllAcctRemainingQuota fixedpoint.Value `json:"allAcctRemainingQuota"` + CurAcctRemainingQuota fixedpoint.Value `json:"curAcctRemainingQuota"` + PlatRemainingQuota fixedpoint.Value `json:"platRemainingQuota"` + } `json:"surplusLmtDetails"` + UsedLimit fixedpoint.Value `json:"usedLmt"` + UsedLoan fixedpoint.Value `json:"usedLoan"` + } `json:"records"` +} + +//go:generate GetRequest -url "/api/v5/account/interest-limits" -type GetAccountInterestLimitsRequest -responseDataType []BorrowInterestLimit -rateLimiter 1+20/2s +type GetAccountInterestLimitsRequest struct { + client requestgen.AuthenticatedAPIClient + + currency *string `param:"ccy"` +} + +func (c *RestClient) NewGetAccountInterestLimitsRequest() *GetAccountInterestLimitsRequest { + return &GetAccountInterestLimitsRequest{ + client: c, + } +} diff --git a/pkg/exchange/okex/okexapi/get_account_interest_limits_request_requestgen.go b/pkg/exchange/okex/okexapi/get_account_interest_limits_request_requestgen.go new file mode 100644 index 0000000000..79027fa578 --- /dev/null +++ b/pkg/exchange/okex/okexapi/get_account_interest_limits_request_requestgen.go @@ -0,0 +1,193 @@ +// Code generated by "requestgen -method GET -responseType .APIResponse -responseDataField Data -url /api/v5/account/interest-limits -type GetAccountInterestLimitsRequest -responseDataType []BorrowInterestLimit -rateLimiter 1+20/2s"; DO NOT EDIT. + +package okexapi + +import ( + "context" + "encoding/json" + "fmt" + "golang.org/x/time/rate" + "net/url" + "reflect" + "regexp" +) + +var GetAccountInterestLimitsRequestLimiter = rate.NewLimiter(10, 1) + +func (g *GetAccountInterestLimitsRequest) Currency(currency string) *GetAccountInterestLimitsRequest { + g.currency = ¤cy + return g +} + +// GetQueryParameters builds and checks the query parameters and returns url.Values +func (g *GetAccountInterestLimitsRequest) GetQueryParameters() (url.Values, error) { + var params = map[string]interface{}{} + + query := url.Values{} + for _k, _v := range params { + query.Add(_k, fmt.Sprintf("%v", _v)) + } + + return query, nil +} + +// GetParameters builds and checks the parameters and return the result in a map object +func (g *GetAccountInterestLimitsRequest) GetParameters() (map[string]interface{}, error) { + var params = map[string]interface{}{} + // check currency field -> json key ccy + if g.currency != nil { + currency := *g.currency + + // assign parameter of currency + params["ccy"] = currency + } else { + } + + return params, nil +} + +// GetParametersQuery converts the parameters from GetParameters into the url.Values format +func (g *GetAccountInterestLimitsRequest) GetParametersQuery() (url.Values, error) { + query := url.Values{} + + params, err := g.GetParameters() + if err != nil { + return query, err + } + + for _k, _v := range params { + if g.isVarSlice(_v) { + g.iterateSlice(_v, func(it interface{}) { + query.Add(_k+"[]", fmt.Sprintf("%v", it)) + }) + } else { + query.Add(_k, fmt.Sprintf("%v", _v)) + } + } + + return query, nil +} + +// GetParametersJSON converts the parameters from GetParameters into the JSON format +func (g *GetAccountInterestLimitsRequest) GetParametersJSON() ([]byte, error) { + params, err := g.GetParameters() + if err != nil { + return nil, err + } + + return json.Marshal(params) +} + +// GetSlugParameters builds and checks the slug parameters and return the result in a map object +func (g *GetAccountInterestLimitsRequest) GetSlugParameters() (map[string]interface{}, error) { + var params = map[string]interface{}{} + + return params, nil +} + +func (g *GetAccountInterestLimitsRequest) applySlugsToUrl(url string, slugs map[string]string) string { + for _k, _v := range slugs { + needleRE := regexp.MustCompile(":" + _k + "\\b") + url = needleRE.ReplaceAllString(url, _v) + } + + return url +} + +func (g *GetAccountInterestLimitsRequest) iterateSlice(slice interface{}, _f func(it interface{})) { + sliceValue := reflect.ValueOf(slice) + for _i := 0; _i < sliceValue.Len(); _i++ { + it := sliceValue.Index(_i).Interface() + _f(it) + } +} + +func (g *GetAccountInterestLimitsRequest) isVarSlice(_v interface{}) bool { + rt := reflect.TypeOf(_v) + switch rt.Kind() { + case reflect.Slice: + return true + } + return false +} + +func (g *GetAccountInterestLimitsRequest) GetSlugsMap() (map[string]string, error) { + slugs := map[string]string{} + params, err := g.GetSlugParameters() + if err != nil { + return slugs, nil + } + + for _k, _v := range params { + slugs[_k] = fmt.Sprintf("%v", _v) + } + + return slugs, nil +} + +// GetPath returns the request path of the API +func (g *GetAccountInterestLimitsRequest) GetPath() string { + return "/api/v5/account/interest-limits" +} + +// Do generates the request object and send the request object to the API endpoint +func (g *GetAccountInterestLimitsRequest) Do(ctx context.Context) ([]BorrowInterestLimit, error) { + if err := GetAccountInterestLimitsRequestLimiter.Wait(ctx); err != nil { + return nil, err + } + + // empty params for GET operation + var params interface{} + query, err := g.GetParametersQuery() + if err != nil { + return nil, err + } + + var apiURL string + + apiURL = g.GetPath() + + req, err := g.client.NewAuthenticatedRequest(ctx, "GET", apiURL, query, params) + if err != nil { + return nil, err + } + + response, err := g.client.SendRequest(req) + if err != nil { + return nil, err + } + + var apiResponse APIResponse + + type responseUnmarshaler interface { + Unmarshal(data []byte) error + } + + if unmarshaler, ok := interface{}(&apiResponse).(responseUnmarshaler); ok { + if err := unmarshaler.Unmarshal(response.Body); err != nil { + return nil, err + } + } else { + // The line below checks the content type, however, some API server might not send the correct content type header, + // Hence, this is commented for backward compatibility + // response.IsJSON() + if err := response.DecodeJSON(&apiResponse); err != nil { + return nil, err + } + } + + type responseValidator interface { + Validate() error + } + + if validator, ok := interface{}(&apiResponse).(responseValidator); ok { + if err := validator.Validate(); err != nil { + return nil, err + } + } + var data []BorrowInterestLimit + if err := json.Unmarshal(apiResponse.Data, &data); err != nil { + return nil, err + } + return data, nil +} diff --git a/pkg/exchange/okex/okexapi/get_account_leverage_info.go b/pkg/exchange/okex/okexapi/get_account_leverage_info.go index f667bba8bd..015faa169e 100644 --- a/pkg/exchange/okex/okexapi/get_account_leverage_info.go +++ b/pkg/exchange/okex/okexapi/get_account_leverage_info.go @@ -24,6 +24,8 @@ type GetAccountLeverageInfoRequest struct { currency *string `param:"ccy"` marginMode MarginMode `param:"mgnMode"` + + instrumentId *string `param:"instId"` } func (c *RestClient) NewGetAccountLeverageInfoRequest() *GetAccountLeverageInfoRequest { diff --git a/pkg/exchange/okex/okexapi/get_account_leverage_info_request_requestgen.go b/pkg/exchange/okex/okexapi/get_account_leverage_info_request_requestgen.go index 042e696cd3..7aea32cff5 100644 --- a/pkg/exchange/okex/okexapi/get_account_leverage_info_request_requestgen.go +++ b/pkg/exchange/okex/okexapi/get_account_leverage_info_request_requestgen.go @@ -1,4 +1,4 @@ -// Code generated by "requestgen -method GET -responseType .APIResponse -responseDataField Data -url /api/v5/account/leverage-info -type GetAccountLeverageInfoRequest -responseDataType []LeverageInfo"; DO NOT EDIT. +// Code generated by "requestgen -method GET -responseType .APIResponse -responseDataField Data -url /api/v5/account/leverage-info -type GetAccountLeverageInfoRequest -responseDataType []LeverageInfo -rateLimiter 1+20/2s"; DO NOT EDIT. package okexapi @@ -6,11 +6,15 @@ import ( "context" "encoding/json" "fmt" + log "github.com/sirupsen/logrus" + "golang.org/x/time/rate" "net/url" "reflect" "regexp" ) +var GetAccountLeverageInfoRequestLimiter = rate.NewLimiter(10, 1) + func (g *GetAccountLeverageInfoRequest) Currency(currency string) *GetAccountLeverageInfoRequest { g.currency = ¤cy return g @@ -21,6 +25,11 @@ func (g *GetAccountLeverageInfoRequest) MarginMode(marginMode MarginMode) *GetAc return g } +func (g *GetAccountLeverageInfoRequest) InstrumentId(instrumentId string) *GetAccountLeverageInfoRequest { + g.instrumentId = &instrumentId + return g +} + // GetQueryParameters builds and checks the query parameters and returns url.Values func (g *GetAccountLeverageInfoRequest) GetQueryParameters() (url.Values, error) { var params = map[string]interface{}{} @@ -49,6 +58,14 @@ func (g *GetAccountLeverageInfoRequest) GetParameters() (map[string]interface{}, // assign parameter of marginMode params["mgnMode"] = marginMode + // check instrumentId field -> json key instId + if g.instrumentId != nil { + instrumentId := *g.instrumentId + + // assign parameter of instrumentId + params["instId"] = instrumentId + } else { + } return params, nil } @@ -139,6 +156,9 @@ func (g *GetAccountLeverageInfoRequest) GetPath() string { // Do generates the request object and send the request object to the API endpoint func (g *GetAccountLeverageInfoRequest) Do(ctx context.Context) ([]LeverageInfo, error) { + if err := GetAccountLeverageInfoRequestLimiter.Wait(ctx); err != nil { + return nil, err + } // empty params for GET operation var params interface{} @@ -156,6 +176,8 @@ func (g *GetAccountLeverageInfoRequest) Do(ctx context.Context) ([]LeverageInfo, return nil, err } + log.Infof("req: %+v", req) + response, err := g.client.SendRequest(req) if err != nil { return nil, err diff --git a/pkg/exchange/okex/okexapi/get_account_max_loan_request.go b/pkg/exchange/okex/okexapi/get_account_max_loan_request.go new file mode 100644 index 0000000000..7dd9afa31f --- /dev/null +++ b/pkg/exchange/okex/okexapi/get_account_max_loan_request.go @@ -0,0 +1,36 @@ +package okexapi + +import ( + "github.com/c9s/requestgen" + + "github.com/c9s/bbgo/pkg/fixedpoint" +) + +//go:generate -command GetRequest requestgen -method GET -responseType .APIResponse -responseDataField Data +//go:generate -command PostRequest requestgen -method POST -responseType .APIResponse -responseDataField Data + +type MaxLoanResponse struct { + InstId string `json:"instId"` + MgnMode MarginMode `json:"mgnMode"` + MgnCcy string `json:"mgnCcy"` + + MaxLoan fixedpoint.Value `json:"maxLoan"` + Ccy string `json:"ccy"` + Side MarginSide `json:"side"` +} + +//go:generate GetRequest -url "/api/v5/account/max-loan" -type GetAccountMaxLoanRequest -responseDataType []MaxLoanResponse -rateLimiter 1+20/2s +type GetAccountMaxLoanRequest struct { + client requestgen.AuthenticatedAPIClient + + instrumentId *string `param:"instId"` + currency *string `param:"ccy"` + marginCurrency *string `param:"mgnCcy"` + marginMode MarginMode `param:"mgnMode"` +} + +func (c *RestClient) NewGetAccountMaxLoanRequest() *GetAccountMaxLoanRequest { + return &GetAccountMaxLoanRequest{ + client: c, + } +} diff --git a/pkg/exchange/okex/okexapi/get_account_max_loan_request_requestgen.go b/pkg/exchange/okex/okexapi/get_account_max_loan_request_requestgen.go new file mode 100644 index 0000000000..46f8a5ce01 --- /dev/null +++ b/pkg/exchange/okex/okexapi/get_account_max_loan_request_requestgen.go @@ -0,0 +1,229 @@ +// Code generated by "requestgen -method GET -responseType .APIResponse -responseDataField Data -url /api/v5/account/max-loan -type GetAccountMaxLoanRequest -responseDataType []MaxLoanResponse -rateLimiter 1+20/2s"; DO NOT EDIT. + +package okexapi + +import ( + "context" + "encoding/json" + "fmt" + "golang.org/x/time/rate" + "net/url" + "reflect" + "regexp" +) + +var GetAccountMaxLoanRequestLimiter = rate.NewLimiter(10, 1) + +func (g *GetAccountMaxLoanRequest) InstrumentId(instrumentId string) *GetAccountMaxLoanRequest { + g.instrumentId = &instrumentId + return g +} + +func (g *GetAccountMaxLoanRequest) Currency(currency string) *GetAccountMaxLoanRequest { + g.currency = ¤cy + return g +} + +func (g *GetAccountMaxLoanRequest) MarginCurrency(marginCurrency string) *GetAccountMaxLoanRequest { + g.marginCurrency = &marginCurrency + return g +} + +func (g *GetAccountMaxLoanRequest) MarginMode(marginMode MarginMode) *GetAccountMaxLoanRequest { + g.marginMode = marginMode + return g +} + +// GetQueryParameters builds and checks the query parameters and returns url.Values +func (g *GetAccountMaxLoanRequest) GetQueryParameters() (url.Values, error) { + var params = map[string]interface{}{} + + query := url.Values{} + for _k, _v := range params { + query.Add(_k, fmt.Sprintf("%v", _v)) + } + + return query, nil +} + +// GetParameters builds and checks the parameters and return the result in a map object +func (g *GetAccountMaxLoanRequest) GetParameters() (map[string]interface{}, error) { + var params = map[string]interface{}{} + // check instrumentId field -> json key instId + if g.instrumentId != nil { + instrumentId := *g.instrumentId + + // assign parameter of instrumentId + params["instId"] = instrumentId + } else { + } + // check currency field -> json key ccy + if g.currency != nil { + currency := *g.currency + + // assign parameter of currency + params["ccy"] = currency + } else { + } + // check marginCurrency field -> json key mgnCcy + if g.marginCurrency != nil { + marginCurrency := *g.marginCurrency + + // assign parameter of marginCurrency + params["mgnCcy"] = marginCurrency + } else { + } + // check marginMode field -> json key mgnMode + marginMode := g.marginMode + + // assign parameter of marginMode + params["mgnMode"] = marginMode + + return params, nil +} + +// GetParametersQuery converts the parameters from GetParameters into the url.Values format +func (g *GetAccountMaxLoanRequest) GetParametersQuery() (url.Values, error) { + query := url.Values{} + + params, err := g.GetParameters() + if err != nil { + return query, err + } + + for _k, _v := range params { + if g.isVarSlice(_v) { + g.iterateSlice(_v, func(it interface{}) { + query.Add(_k+"[]", fmt.Sprintf("%v", it)) + }) + } else { + query.Add(_k, fmt.Sprintf("%v", _v)) + } + } + + return query, nil +} + +// GetParametersJSON converts the parameters from GetParameters into the JSON format +func (g *GetAccountMaxLoanRequest) GetParametersJSON() ([]byte, error) { + params, err := g.GetParameters() + if err != nil { + return nil, err + } + + return json.Marshal(params) +} + +// GetSlugParameters builds and checks the slug parameters and return the result in a map object +func (g *GetAccountMaxLoanRequest) GetSlugParameters() (map[string]interface{}, error) { + var params = map[string]interface{}{} + + return params, nil +} + +func (g *GetAccountMaxLoanRequest) applySlugsToUrl(url string, slugs map[string]string) string { + for _k, _v := range slugs { + needleRE := regexp.MustCompile(":" + _k + "\\b") + url = needleRE.ReplaceAllString(url, _v) + } + + return url +} + +func (g *GetAccountMaxLoanRequest) iterateSlice(slice interface{}, _f func(it interface{})) { + sliceValue := reflect.ValueOf(slice) + for _i := 0; _i < sliceValue.Len(); _i++ { + it := sliceValue.Index(_i).Interface() + _f(it) + } +} + +func (g *GetAccountMaxLoanRequest) isVarSlice(_v interface{}) bool { + rt := reflect.TypeOf(_v) + switch rt.Kind() { + case reflect.Slice: + return true + } + return false +} + +func (g *GetAccountMaxLoanRequest) GetSlugsMap() (map[string]string, error) { + slugs := map[string]string{} + params, err := g.GetSlugParameters() + if err != nil { + return slugs, nil + } + + for _k, _v := range params { + slugs[_k] = fmt.Sprintf("%v", _v) + } + + return slugs, nil +} + +// GetPath returns the request path of the API +func (g *GetAccountMaxLoanRequest) GetPath() string { + return "/api/v5/account/max-loan" +} + +// Do generates the request object and send the request object to the API endpoint +func (g *GetAccountMaxLoanRequest) Do(ctx context.Context) ([]MaxLoanResponse, error) { + if err := GetAccountMaxLoanRequestLimiter.Wait(ctx); err != nil { + return nil, err + } + + // empty params for GET operation + var params interface{} + query, err := g.GetParametersQuery() + if err != nil { + return nil, err + } + + var apiURL string + + apiURL = g.GetPath() + + req, err := g.client.NewAuthenticatedRequest(ctx, "GET", apiURL, query, params) + if err != nil { + return nil, err + } + + response, err := g.client.SendRequest(req) + if err != nil { + return nil, err + } + + var apiResponse APIResponse + + type responseUnmarshaler interface { + Unmarshal(data []byte) error + } + + if unmarshaler, ok := interface{}(&apiResponse).(responseUnmarshaler); ok { + if err := unmarshaler.Unmarshal(response.Body); err != nil { + return nil, err + } + } else { + // The line below checks the content type, however, some API server might not send the correct content type header, + // Hence, this is commented for backward compatibility + // response.IsJSON() + if err := response.DecodeJSON(&apiResponse); err != nil { + return nil, err + } + } + + type responseValidator interface { + Validate() error + } + + if validator, ok := interface{}(&apiResponse).(responseValidator); ok { + if err := validator.Validate(); err != nil { + return nil, err + } + } + var data []MaxLoanResponse + if err := json.Unmarshal(apiResponse.Data, &data); err != nil { + return nil, err + } + return data, nil +} diff --git a/pkg/exchange/okex/okexapi/get_max_available_size_request.go b/pkg/exchange/okex/okexapi/get_max_available_size_request.go new file mode 100644 index 0000000000..36c53c0d2e --- /dev/null +++ b/pkg/exchange/okex/okexapi/get_max_available_size_request.go @@ -0,0 +1,33 @@ +package okexapi + +import ( + "github.com/c9s/requestgen" + + "github.com/c9s/bbgo/pkg/fixedpoint" +) + +//go:generate -command GetRequest requestgen -method GET -responseType .APIResponse -responseDataField Data +//go:generate -command PostRequest requestgen -method POST -responseType .APIResponse -responseDataField Data + +type MaxAvailableResponse struct { + InstId string `json:"instId"` + AvailableBuy fixedpoint.Value `json:"availBuy"` + AvailableSell fixedpoint.Value `json:"availSell"` +} + +//go:generate GetRequest -url "/api/v5/account/max-avail-size" -type GetMaxAvailableSizeRequest -responseDataType []MaxAvailableResponse -rateLimiter 1+20/2s +type GetMaxAvailableSizeRequest struct { + client requestgen.AuthenticatedAPIClient + + instrumentID string `param:"instId"` + + currency *string `param:"ccy"` + + tdMode TradeMode `param:"tdMode"` +} + +func (c *RestClient) NewGetMaxAvailableSizeRequest() *GetMaxAvailableSizeRequest { + return &GetMaxAvailableSizeRequest{ + client: c, + } +} diff --git a/pkg/exchange/okex/okexapi/get_max_available_size_request_requestgen.go b/pkg/exchange/okex/okexapi/get_max_available_size_request_requestgen.go new file mode 100644 index 0000000000..f5eb481f42 --- /dev/null +++ b/pkg/exchange/okex/okexapi/get_max_available_size_request_requestgen.go @@ -0,0 +1,224 @@ +// Code generated by "requestgen -method GET -responseType .APIResponse -responseDataField Data -url /api/v5/account/max-avail-size -type GetMaxAvailableSizeRequest -responseDataType []MaxAvailableResponse -rateLimiter 1+20/2s"; DO NOT EDIT. + +package okexapi + +import ( + "context" + "encoding/json" + "fmt" + "golang.org/x/time/rate" + "net/url" + "reflect" + "regexp" +) + +var GetMaxAvailableSizeRequestLimiter = rate.NewLimiter(10, 1) + +func (g *GetMaxAvailableSizeRequest) InstrumentID(instrumentID string) *GetMaxAvailableSizeRequest { + g.instrumentID = instrumentID + return g +} + +func (g *GetMaxAvailableSizeRequest) Currency(currency string) *GetMaxAvailableSizeRequest { + g.currency = ¤cy + return g +} + +func (g *GetMaxAvailableSizeRequest) TdMode(tdMode TradeMode) *GetMaxAvailableSizeRequest { + g.tdMode = tdMode + return g +} + +// GetQueryParameters builds and checks the query parameters and returns url.Values +func (g *GetMaxAvailableSizeRequest) GetQueryParameters() (url.Values, error) { + var params = map[string]interface{}{} + + query := url.Values{} + for _k, _v := range params { + query.Add(_k, fmt.Sprintf("%v", _v)) + } + + return query, nil +} + +// GetParameters builds and checks the parameters and return the result in a map object +func (g *GetMaxAvailableSizeRequest) GetParameters() (map[string]interface{}, error) { + var params = map[string]interface{}{} + // check instrumentID field -> json key instId + instrumentID := g.instrumentID + + // assign parameter of instrumentID + params["instId"] = instrumentID + // check currency field -> json key ccy + if g.currency != nil { + currency := *g.currency + + // assign parameter of currency + params["ccy"] = currency + } else { + } + // check tdMode field -> json key tdMode + tdMode := g.tdMode + + // TEMPLATE check-valid-values + switch tdMode { + case TradeModeCash, TradeModeIsolated, TradeModeCross: + params["tdMode"] = tdMode + + default: + return nil, fmt.Errorf("tdMode value %v is invalid", tdMode) + + } + // END TEMPLATE check-valid-values + + // assign parameter of tdMode + params["tdMode"] = tdMode + + return params, nil +} + +// GetParametersQuery converts the parameters from GetParameters into the url.Values format +func (g *GetMaxAvailableSizeRequest) GetParametersQuery() (url.Values, error) { + query := url.Values{} + + params, err := g.GetParameters() + if err != nil { + return query, err + } + + for _k, _v := range params { + if g.isVarSlice(_v) { + g.iterateSlice(_v, func(it interface{}) { + query.Add(_k+"[]", fmt.Sprintf("%v", it)) + }) + } else { + query.Add(_k, fmt.Sprintf("%v", _v)) + } + } + + return query, nil +} + +// GetParametersJSON converts the parameters from GetParameters into the JSON format +func (g *GetMaxAvailableSizeRequest) GetParametersJSON() ([]byte, error) { + params, err := g.GetParameters() + if err != nil { + return nil, err + } + + return json.Marshal(params) +} + +// GetSlugParameters builds and checks the slug parameters and return the result in a map object +func (g *GetMaxAvailableSizeRequest) GetSlugParameters() (map[string]interface{}, error) { + var params = map[string]interface{}{} + + return params, nil +} + +func (g *GetMaxAvailableSizeRequest) applySlugsToUrl(url string, slugs map[string]string) string { + for _k, _v := range slugs { + needleRE := regexp.MustCompile(":" + _k + "\\b") + url = needleRE.ReplaceAllString(url, _v) + } + + return url +} + +func (g *GetMaxAvailableSizeRequest) iterateSlice(slice interface{}, _f func(it interface{})) { + sliceValue := reflect.ValueOf(slice) + for _i := 0; _i < sliceValue.Len(); _i++ { + it := sliceValue.Index(_i).Interface() + _f(it) + } +} + +func (g *GetMaxAvailableSizeRequest) isVarSlice(_v interface{}) bool { + rt := reflect.TypeOf(_v) + switch rt.Kind() { + case reflect.Slice: + return true + } + return false +} + +func (g *GetMaxAvailableSizeRequest) GetSlugsMap() (map[string]string, error) { + slugs := map[string]string{} + params, err := g.GetSlugParameters() + if err != nil { + return slugs, nil + } + + for _k, _v := range params { + slugs[_k] = fmt.Sprintf("%v", _v) + } + + return slugs, nil +} + +// GetPath returns the request path of the API +func (g *GetMaxAvailableSizeRequest) GetPath() string { + return "/api/v5/account/max-avail-size" +} + +// Do generates the request object and send the request object to the API endpoint +func (g *GetMaxAvailableSizeRequest) Do(ctx context.Context) ([]MaxAvailableResponse, error) { + if err := GetMaxAvailableSizeRequestLimiter.Wait(ctx); err != nil { + return nil, err + } + + // empty params for GET operation + var params interface{} + query, err := g.GetParametersQuery() + if err != nil { + return nil, err + } + + var apiURL string + + apiURL = g.GetPath() + + req, err := g.client.NewAuthenticatedRequest(ctx, "GET", apiURL, query, params) + if err != nil { + return nil, err + } + + response, err := g.client.SendRequest(req) + if err != nil { + return nil, err + } + + var apiResponse APIResponse + + type responseUnmarshaler interface { + Unmarshal(data []byte) error + } + + if unmarshaler, ok := interface{}(&apiResponse).(responseUnmarshaler); ok { + if err := unmarshaler.Unmarshal(response.Body); err != nil { + return nil, err + } + } else { + // The line below checks the content type, however, some API server might not send the correct content type header, + // Hence, this is commented for backward compatibility + // response.IsJSON() + if err := response.DecodeJSON(&apiResponse); err != nil { + return nil, err + } + } + + type responseValidator interface { + Validate() error + } + + if validator, ok := interface{}(&apiResponse).(responseValidator); ok { + if err := validator.Validate(); err != nil { + return nil, err + } + } + var data []MaxAvailableResponse + if err := json.Unmarshal(apiResponse.Data, &data); err != nil { + return nil, err + } + return data, nil +} diff --git a/pkg/exchange/okex/okexapi/set_account_level.go b/pkg/exchange/okex/okexapi/set_account_level_request.go similarity index 100% rename from pkg/exchange/okex/okexapi/set_account_level.go rename to pkg/exchange/okex/okexapi/set_account_level_request.go diff --git a/pkg/exchange/okex/okexapi/set_account_leverage.go b/pkg/exchange/okex/okexapi/set_account_leverage_request.go similarity index 100% rename from pkg/exchange/okex/okexapi/set_account_leverage.go rename to pkg/exchange/okex/okexapi/set_account_leverage_request.go diff --git a/pkg/exchange/okex/okexapi/set_auto_repay.go b/pkg/exchange/okex/okexapi/set_auto_repay_request.go similarity index 100% rename from pkg/exchange/okex/okexapi/set_auto_repay.go rename to pkg/exchange/okex/okexapi/set_auto_repay_request.go diff --git a/pkg/exchange/okex/okexapi/spot_manual_borrow_repay.go b/pkg/exchange/okex/okexapi/spot_manual_borrow_repay.go index 5abd08f19c..6425751e88 100644 --- a/pkg/exchange/okex/okexapi/spot_manual_borrow_repay.go +++ b/pkg/exchange/okex/okexapi/spot_manual_borrow_repay.go @@ -9,20 +9,29 @@ import ( //go:generate -command GetRequest requestgen -method GET -responseType .APIResponse -responseDataField Data //go:generate -command PostRequest requestgen -method POST -responseType .APIResponse -responseDataField Data +type MarginSide string + +const ( + MarginSideBorrow MarginSide = "borrow" + MarginSideRepay MarginSide = "repay" +) + type SpotBorrowRepayResponse struct { Currency string `json:"ccy"` - Side string `json:"side"` + Side MarginSide `json:"side"` Amount fixedpoint.Value `json:"amt"` } -//go:generate PostRequest -url "/api/v5/account/set-leverage" -type SpotManualBorrowRepayRequest -responseDataType []SpotBorrowRepayResponse -rateLimiter 1+20/2s +//go:generate PostRequest -url "/api/v5/account/spot-manual-borrow-repay" -type SpotManualBorrowRepayRequest -responseDataType []SpotBorrowRepayResponse -rateLimiter 1+20/2s type SpotManualBorrowRepayRequest struct { client requestgen.AuthenticatedAPIClient + currency string `param:"ccy"` + // side = borrow or repay - side string `param:"side"` + side MarginSide `param:"side"` - amount string `param:"amount"` + amount string `param:"amt"` } func (c *RestClient) NewSpotManualBorrowRepayRequest() *SpotManualBorrowRepayRequest { diff --git a/pkg/exchange/okex/okexapi/spot_manual_borrow_repay_request_requestgen.go b/pkg/exchange/okex/okexapi/spot_manual_borrow_repay_request_requestgen.go index de0dcc8118..7993be631d 100644 --- a/pkg/exchange/okex/okexapi/spot_manual_borrow_repay_request_requestgen.go +++ b/pkg/exchange/okex/okexapi/spot_manual_borrow_repay_request_requestgen.go @@ -1,4 +1,4 @@ -// Code generated by "requestgen -method POST -responseType .APIResponse -responseDataField Data -url /api/v5/account/set-leverage -type SpotManualBorrowRepayRequest -responseDataType []SpotBorrowRepayResponse"; DO NOT EDIT. +// Code generated by "requestgen -method POST -responseType .APIResponse -responseDataField Data -url /api/v5/account/spot-manual-borrow-repay -type SpotManualBorrowRepayRequest -responseDataType []SpotBorrowRepayResponse -rateLimiter 1+20/2s"; DO NOT EDIT. package okexapi @@ -6,12 +6,20 @@ import ( "context" "encoding/json" "fmt" + "golang.org/x/time/rate" "net/url" "reflect" "regexp" ) -func (s *SpotManualBorrowRepayRequest) Side(side string) *SpotManualBorrowRepayRequest { +var SpotManualBorrowRepayRequestLimiter = rate.NewLimiter(10, 1) + +func (s *SpotManualBorrowRepayRequest) Currency(currency string) *SpotManualBorrowRepayRequest { + s.currency = currency + return s +} + +func (s *SpotManualBorrowRepayRequest) Side(side MarginSide) *SpotManualBorrowRepayRequest { s.side = side return s } @@ -36,16 +44,32 @@ func (s *SpotManualBorrowRepayRequest) GetQueryParameters() (url.Values, error) // GetParameters builds and checks the parameters and return the result in a map object func (s *SpotManualBorrowRepayRequest) GetParameters() (map[string]interface{}, error) { var params = map[string]interface{}{} + // check currency field -> json key ccy + currency := s.currency + + // assign parameter of currency + params["ccy"] = currency // check side field -> json key side side := s.side + // TEMPLATE check-valid-values + switch side { + case MarginSideBorrow, MarginSideRepay: + params["side"] = side + + default: + return nil, fmt.Errorf("side value %v is invalid", side) + + } + // END TEMPLATE check-valid-values + // assign parameter of side params["side"] = side - // check amount field -> json key amount + // check amount field -> json key amt amount := s.amount // assign parameter of amount - params["amount"] = amount + params["amt"] = amount return params, nil } @@ -131,11 +155,14 @@ func (s *SpotManualBorrowRepayRequest) GetSlugsMap() (map[string]string, error) // GetPath returns the request path of the API func (s *SpotManualBorrowRepayRequest) GetPath() string { - return "/api/v5/account/set-leverage" + return "/api/v5/account/spot-manual-borrow-repay" } // Do generates the request object and send the request object to the API endpoint func (s *SpotManualBorrowRepayRequest) Do(ctx context.Context) ([]SpotBorrowRepayResponse, error) { + if err := SpotManualBorrowRepayRequestLimiter.Wait(ctx); err != nil { + return nil, err + } params, err := s.GetParameters() if err != nil {