diff --git a/account.go b/account.go index 82625fc..fbd19e5 100644 --- a/account.go +++ b/account.go @@ -1,7 +1,7 @@ package kumex import ( - "encoding/json" + "github.com/json-iterator/go" "net/http" ) @@ -65,12 +65,12 @@ func (as *ApiService) SubApiKeys(apiKey, subName string) (*ApiResponse, error) { type SubApiKeysModel []*SubApiKeyModel type SubApiKeyModel struct { - SubName string `json:"subName"` - Remark string `json:"remark"` - ApiKey string `json:"apiKey"` - Permission string `json:"permission"` - IpWhitelist string `json:"ipWhitelist"` - CreatedAt json.Number `json:"createdAt"` + SubName string `json:"subName"` + Remark string `json:"remark"` + ApiKey string `json:"apiKey"` + Permission string `json:"permission"` + IpWhitelist string `json:"ipWhitelist"` + CreatedAt jsoniter.Number `json:"createdAt"` } // CreateSubApiKey This endpoint can be used to create Futures APIs for sub-accounts. @@ -80,14 +80,14 @@ func (as *ApiService) CreateSubApiKey(p map[string]string) (*ApiResponse, error) } type CreateSubApiKeyRes struct { - SubName string `json:"subName"` - Remark string `json:"remark"` - ApiKey string `json:"apiKey"` - Permission string `json:"permission"` - IpWhitelist string `json:"ipWhitelist"` - CreatedAt json.Number `json:"createdAt"` - ApiSecret string `json:"apiSecret"` - Passphrase string `json:"passphrase"` + SubName string `json:"subName"` + Remark string `json:"remark"` + ApiKey string `json:"apiKey"` + Permission string `json:"permission"` + IpWhitelist string `json:"ipWhitelist"` + CreatedAt jsoniter.Number `json:"createdAt"` + ApiSecret string `json:"apiSecret"` + Passphrase string `json:"passphrase"` } // ModifySubApiKey TThis endpoint can be used to modify sub-account Futures APIs. @@ -130,24 +130,24 @@ func (as *ApiService) SubAccountsBalance(currency string) (*ApiResponse, error) type SubAccountBalanceModel struct { Summary struct { - AccountEquityTotal json.Number `json:"accountEquityTotal"` - UnrealisedPNLTotal json.Number `json:"unrealisedPNLTotal"` - MarginBalanceTotal json.Number `json:"marginBalanceTotal"` - PositionMarginTotal json.Number `json:"positionMarginTotal"` - OrderMarginTotal json.Number `json:"orderMarginTotal"` - FrozenFundsTotal json.Number `json:"frozenFundsTotal"` - AvailableBalanceTotal json.Number `json:"availableBalanceTotal"` - Currency string `json:"currency"` + AccountEquityTotal jsoniter.Number `json:"accountEquityTotal"` + UnrealisedPNLTotal jsoniter.Number `json:"unrealisedPNLTotal"` + MarginBalanceTotal jsoniter.Number `json:"marginBalanceTotal"` + PositionMarginTotal jsoniter.Number `json:"positionMarginTotal"` + OrderMarginTotal jsoniter.Number `json:"orderMarginTotal"` + FrozenFundsTotal jsoniter.Number `json:"frozenFundsTotal"` + AvailableBalanceTotal jsoniter.Number `json:"availableBalanceTotal"` + Currency string `json:"currency"` } `json:"summary"` Accounts []struct { - AccountName string `json:"accountName"` - AccountEquity json.Number `json:"accountEquity"` - UnrealisedPNL json.Number `json:"unrealisedPNL"` - MarginBalance json.Number `json:"marginBalance"` - PositionMargin json.Number `json:"positionMargin"` - OrderMargin json.Number `json:"orderMargin"` - FrozenFunds json.Number `json:"frozenFunds"` - AvailableBalance json.Number `json:"availableBalance"` - Currency string `json:"currency"` + AccountName string `json:"accountName"` + AccountEquity jsoniter.Number `json:"accountEquity"` + UnrealisedPNL jsoniter.Number `json:"unrealisedPNL"` + MarginBalance jsoniter.Number `json:"marginBalance"` + PositionMargin jsoniter.Number `json:"positionMargin"` + OrderMargin jsoniter.Number `json:"orderMargin"` + FrozenFunds jsoniter.Number `json:"frozenFunds"` + AvailableBalance jsoniter.Number `json:"availableBalance"` + Currency string `json:"currency"` } `json:"accounts"` } diff --git a/account_test.go b/account_test.go index 47e116d..ce3e34f 100644 --- a/account_test.go +++ b/account_test.go @@ -5,11 +5,10 @@ import ( ) func TestApiService_AccountOverview(t *testing.T) { - t.SkipNow() s := NewApiServiceFromEnv() p := map[string]string{} - p["currency"] = "XBT" + p["currency"] = "USDT" rsp, err := s.AccountOverview(p) if err != nil { diff --git a/funding.go b/funding.go index ba52683..b0ed28a 100644 --- a/funding.go +++ b/funding.go @@ -46,3 +46,18 @@ func (as *ApiService) FundingRatesTimeRange(symbol, from, to string) (*ApiRespon }) return as.Call(req) } + +type TradeFeesV1Resp struct { + Symbol string `json:"symbol"` + TakerFeeRate string `json:"takerFeeRate"` + MakerFeeRate string `json:"makerFeeRate"` +} + +// TradeFeesV1 This interface is for the actual fee rate of the trading pair. +// The fee rate of your sub-account is the same as that of the master account. +func (as *ApiService) TradeFeesV1(symbol string) (*ApiResponse, error) { + req := NewRequest(http.MethodGet, "/api/v1/trade-fees", map[string]string{ + "symbol": symbol, + }) + return as.Call(req) +} diff --git a/funding_test.go b/funding_test.go index 4bb93eb..3c09e03 100644 --- a/funding_test.go +++ b/funding_test.go @@ -47,3 +47,17 @@ func TestApiService_FundingRatesTimeRange(t *testing.T) { t.Log(ToJsonString(o)) } } + +func TestApiService_TradeFeesV1(t *testing.T) { + s := NewApiServiceFromEnv() + rsp, err := s.TradeFeesV1("XBTUSDTM") + if err != nil { + t.Fatal(err) + } + + os := TradeFeesV1Resp{} + if err := rsp.ReadData(&os); err != nil { + t.Fatal(err) + } + t.Log(ToJsonString(os)) +} diff --git a/go.mod b/go.mod index ad40cc6..3b54dfb 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,7 @@ module github.com/Kucoin/kucoin-futures-go-sdk require ( + github.com/google/go-querystring v1.1.0 // indirect github.com/gorilla/websocket v1.4.0 github.com/json-iterator/go v1.1.12 github.com/pkg/errors v0.8.1 diff --git a/go.sum b/go.sum index 6a4faf9..bfd5c24 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,9 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= @@ -25,3 +28,4 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/helper.go b/helper.go index db4f181..df9008e 100644 --- a/helper.go +++ b/helper.go @@ -1,7 +1,7 @@ package kumex import ( - "encoding/json" + "github.com/json-iterator/go" "strconv" ) @@ -12,7 +12,7 @@ func IntToString(i int64) string { // ToJsonString converts any value to JSON string. func ToJsonString(v interface{}) string { - b, err := json.Marshal(v) + b, err := jsoniter.Marshal(v) if err != nil { return "" } diff --git a/http.go b/http.go index 91696c7..9855e3d 100644 --- a/http.go +++ b/http.go @@ -32,7 +32,7 @@ type Request struct { } // NewRequest creates a instance of Request. -func NewRequest(method, path string, params map[string]string) *Request { +func NewRequest(method, path string, params interface{}) *Request { r := &Request{ Method: method, Path: path, @@ -51,17 +51,21 @@ func NewRequest(method, path string, params map[string]string) *Request { return r } -func (r *Request) addParams(params map[string]string) { +func (r *Request) addParams(p interface{}) { + if p == nil { + return + } switch r.Method { case http.MethodGet, http.MethodDelete: - for key, value := range params { + if v, ok := p.(url.Values); ok { + r.Query = v + return + } + for key, value := range p.(map[string]string) { r.Query.Add(key, value) } default: - if params == nil { - return - } - b, err := jsoniter.Marshal(params) + b, err := jsoniter.Marshal(p) if err != nil { log.Panic("Cannot marshal params to JSON string:", err.Error()) } diff --git a/market.go b/market.go index a289066..297809a 100644 --- a/market.go +++ b/market.go @@ -1,7 +1,7 @@ package kumex import ( - "encoding/json" + "github.com/json-iterator/go" "net/http" ) @@ -230,7 +230,7 @@ func (as *ApiService) FundingRate(Symbol string) (*ApiResponse, error) { } type TradeStatisticsModel struct { - TurnoverOf24h json.Number `json:"turnoverOf24h"` + TurnoverOf24h jsoniter.Number `json:"turnoverOf24h"` } // TradeStatistics Get 24h trade statistics. diff --git a/order.go b/order.go index 205c5e1..7e73126 100644 --- a/order.go +++ b/order.go @@ -13,6 +13,12 @@ func (as *ApiService) CreateOrder(params map[string]string) (*ApiResponse, error return as.Call(req) } +// CreateOrderTest places a new order. +func (as *ApiService) CreateOrderTest(params map[string]string) (*ApiResponse, error) { + req := NewRequest(http.MethodPost, "/api/v1/orders/test", params) + return as.Call(req) +} + // A CancelOrderResultModel represents the result of CancelOrder(). type CancelOrderResultModel struct { CancelledOrderIds []string `json:"cancelledOrderIds"` @@ -76,7 +82,7 @@ type OrderModel struct { PostOnly bool `json:"postOnly"` Hidden bool `json:"hidden"` IceBerg bool `json:"iceberg"` - VisibleSize string `json:"visibleSize"` + VisibleSize int64 `json:"visibleSize"` Leverage string `json:"leverage"` ForceHold bool `json:"forceHold"` CloseOrder bool `json:"closeOrder"` @@ -134,3 +140,44 @@ func (as *ApiService) CancelOrderClientId(clientOid, symbol string) (*ApiRespons req := NewRequest(http.MethodDelete, "/api/v1/orders/client-order/"+clientOid, p) return as.Call(req) } + +type CreateOrderReq struct { + // BASE PARAMETERS + ClientOid string `json:"clientOid"` + Side string `json:"side"` + Symbol string `json:"symbol,omitempty"` + Leverage string `json:"leverage,omitempty"` + Type string `json:"type,omitempty"` + Remark string `json:"remark,omitempty"` + Stop string `json:"stop,omitempty"` + StopPrice string `json:"stopPrice,omitempty"` + StopPriceType string `json:"stopPriceType,omitempty"` + ReduceOnly string `json:"reduceOnly,omitempty"` + CloseOrder string `json:"closeOrder,omitempty"` + ForceHold string `json:"forceHold,omitempty"` + Stp string `json:"stp,omitempty"` + + // MARKET ORDER PARAMETERS + Size string `json:"size,omitempty"` + + // LIMIT ORDER PARAMETERS + Price string `json:"price,omitempty"` + TimeInForce string `json:"timeInForce,omitempty"` + PostOnly bool `json:"postOnly,omitempty"` + Hidden bool `json:"hidden,omitempty"` + IceBerg bool `json:"iceberg,omitempty"` + VisibleSize string `json:"visibleSize,omitempty"` +} + +type CreateOrderRes struct { + OrderId string `json:"orderId"` + ClientOid string `json:"clientOid"` + Symbol string `json:"symbol"` +} +type CreateMultiOrdersRes []*CreateOrderRes + +// CreateMultiOrders places multi order. +func (as *ApiService) CreateMultiOrders(p []*CreateOrderReq) (*ApiResponse, error) { + req := NewRequest(http.MethodPost, "/api/v1/orders/multi", p) + return as.Call(req) +} diff --git a/order_test.go b/order_test.go index 1c3d473..418a491 100644 --- a/order_test.go +++ b/order_test.go @@ -258,3 +258,38 @@ func TestApiService_CancelOrderClientId(t *testing.T) { } t.Log(ToJsonString(o)) } + +func TestApiService_CreateMultiOrders(t *testing.T) { + + s := NewApiServiceFromEnv() + p := make([]*CreateOrderReq, 0) + p = append(p, &CreateOrderReq{ + ClientOid: IntToString(time.Now().UnixNano()), + Side: "buy", + Symbol: "XBTUSDTM", + Leverage: "1", + Type: "limit", + Size: "1", + Price: "0.3", + }) + + p = append(p, &CreateOrderReq{ + ClientOid: IntToString(time.Now().UnixNano()), + Side: "buy", + Symbol: "XBTUSDTM", + Leverage: "1", + Type: "limit", + Size: "1", + Price: "0.2", + }) + + rsp, err := s.CreateMultiOrders(p) + if err != nil { + t.Fatal(err) + } + o := &CreateMultiOrdersRes{} + if err := rsp.ReadData(o); err != nil { + t.Fatal(err) + } + t.Log(ToJsonString(o)) +} diff --git a/pagination.go b/pagination.go index 87a821d..b32faf3 100644 --- a/pagination.go +++ b/pagination.go @@ -1,6 +1,8 @@ package kumex -import "encoding/json" +import ( + "github.com/json-iterator/go" +) // A PaginationParam represents the pagination parameters `currentPage` `pageSize` in a request . type PaginationParam struct { @@ -15,14 +17,14 @@ func (p *PaginationParam) ReadParam(params map[string]string) { // A PaginationModel represents the pagination in a response. type PaginationModel struct { - CurrentPage int64 `json:"currentPage"` - PageSize int64 `json:"pageSize"` - TotalNum int64 `json:"totalNum"` - TotalPage int64 `json:"totalPage"` - RawItems json.RawMessage `json:"items"` // delay parsing + CurrentPage int64 `json:"currentPage"` + PageSize int64 `json:"pageSize"` + TotalNum int64 `json:"totalNum"` + TotalPage int64 `json:"totalPage"` + RawItems jsoniter.RawMessage `json:"items"` // delay parsing } // ReadItems read the `items` into v. func (p *PaginationModel) ReadItems(v interface{}) error { - return json.Unmarshal(p.RawItems, v) + return jsoniter.Unmarshal(p.RawItems, v) } diff --git a/position.go b/position.go index 01bf751..86dbf19 100644 --- a/position.go +++ b/position.go @@ -1,6 +1,7 @@ package kumex import ( + "github.com/google/go-querystring/query" "net/http" ) @@ -79,3 +80,69 @@ func (as *ApiService) DepositMargin(params map[string]string) (*ApiResponse, err req := NewRequest(http.MethodPost, "/api/v1/position/margin/deposit-margin", params) return as.Call(req) } + +// MaxWithdrawMarginV1 This interface can query the maximum amount of margin that the current position supports withdrawal. +func (as *ApiService) MaxWithdrawMarginV1(symbol string) (*ApiResponse, error) { + req := NewRequest(http.MethodGet, "/api/v1/margin/maxWithdrawMargin", map[string]string{"symbol": symbol}) + return as.Call(req) +} + +type WithdrawMarginV1Req struct { + Symbol string `json:"symbol"` + WithdrawAmount string `json:"withdrawAmount"` +} + +// WithdrawMarginV1 Remove Margin Manually +func (as *ApiService) WithdrawMarginV1(r *WithdrawMarginV1Req) (*ApiResponse, error) { + req := NewRequest(http.MethodPost, "/api/v1/margin/withdrawMargin", r) + return as.Call(req) +} + +type GetPositionsHistoryV1Req struct { + Symbol string `url:"symbol,omitempty"` + From int64 `url:"from,omitempty"` + To int64 `url:"to,omitempty"` + Limit int `url:"limit,omitempty"` + PageID int `url:"pageId,omitempty"` +} + +type PositionsHistoryItem struct { + CloseID string `json:"closeId"` + PositionID string `json:"positionId"` + UID int64 `json:"uid"` + UserID string `json:"userId"` + Symbol string `json:"symbol"` + SettleCurrency string `json:"settleCurrency"` + Leverage string `json:"leverage"` + Type string `json:"type"` + Side *string `json:"side"` + CloseSize *float64 `json:"closeSize"` + PNL string `json:"pnl"` + RealisedGrossCost string `json:"realisedGrossCost"` + WithdrawPNL string `json:"withdrawPnl"` + ROE *float64 `json:"roe"` + TradeFee string `json:"tradeFee"` + FundingFee string `json:"fundingFee"` + OpenTime int64 `json:"openTime"` + CloseTime int64 `json:"closeTime"` + OpenPrice *float64 `json:"openPrice"` + ClosePrice *float64 `json:"closePrice"` +} + +type GetPositionsHistoryV1Resp struct { + CurrentPage int `json:"currentPage"` + PageSize int `json:"pageSize"` + TotalNum int `json:"totalNum"` + TotalPage int `json:"totalPage"` + Items []PositionsHistoryItem `json:"items"` +} + +// GetPositionsHistoryV1 This interface can query position history information records +func (as *ApiService) GetPositionsHistoryV1(r *GetPositionsHistoryV1Req) (*ApiResponse, error) { + v, err := query.Values(r) + if err != nil { + return nil, err + } + req := NewRequest(http.MethodGet, "/api/v1/history-positions", v) + return as.Call(req) +} diff --git a/position_test.go b/position_test.go index 7748487..483edaa 100644 --- a/position_test.go +++ b/position_test.go @@ -106,3 +106,52 @@ func TestApiService_DepositMargin(t *testing.T) { t.Error("Empty key 'id'") } } + +func TestApiService_MaxWithdrawMarginV1(t *testing.T) { + s := NewApiServiceFromEnv() + + rsp, err := s.MaxWithdrawMarginV1("XBTUSDTM") + if err != nil { + t.Fatal(err) + } + var data string + if err := rsp.ReadData(&data); err != nil { + t.Fatal(err) + } + t.Log(data) +} + +func TestApiService_WithdrawMarginV1(t *testing.T) { + + s := NewApiServiceFromEnv() + + r := &WithdrawMarginV1Req{ + Symbol: "XBTUSDTM", + WithdrawAmount: "0.1", + } + rsp, err := s.WithdrawMarginV1(r) + if err != nil { + t.Fatal(err) + } + var data string + if err := rsp.ReadData(&data); err != nil { + t.Fatal(err) + } + t.Log(data) +} +func TestApiService_GetPositionsHistoryV1(t *testing.T) { + s := NewApiServiceFromEnv() + + r := &GetPositionsHistoryV1Req{ + Symbol: "PEPEUSDTM", + } + rsp, err := s.GetPositionsHistoryV1(r) + if err != nil { + t.Fatal(err) + } + data := GetPositionsHistoryV1Resp{} + if err := rsp.ReadData(&data); err != nil { + t.Fatal(err) + } + t.Log(ToJsonString(data)) +} diff --git a/transfer.go b/transfer.go index cab1e58..8e87ca1 100644 --- a/transfer.go +++ b/transfer.go @@ -1,7 +1,7 @@ package kumex import ( - "encoding/json" + "github.com/json-iterator/go" "math/big" "net/http" ) @@ -88,23 +88,23 @@ func (as *ApiService) TransferOutV3(currency, recAccountType, amount string) (*A } type TransferOutV3Res struct { - ApplyId string `json:"applyId"` - BizNo string `json:"bizNo"` - PayAccountType string `json:"payAccountType"` - PayTag string `json:"payTag"` - Remark string `json:"remark"` - RecAccountType string `json:"recAccountType"` - RecTag string `json:"recTag"` - RecRemark string `json:"recRemark"` - RecSystem string `json:"recSystem"` - Status string `json:"status"` - Currency string `json:"currency"` - Amount string `json:"amount"` - Fee string `json:"fee"` - Sn big.Int `json:"sn"` - Reason string `json:"reason"` - CreatedAt json.Number `json:"createdAt"` - UpdatedAt json.Number `json:"updatedAt"` + ApplyId string `json:"applyId"` + BizNo string `json:"bizNo"` + PayAccountType string `json:"payAccountType"` + PayTag string `json:"payTag"` + Remark string `json:"remark"` + RecAccountType string `json:"recAccountType"` + RecTag string `json:"recTag"` + RecRemark string `json:"recRemark"` + RecSystem string `json:"recSystem"` + Status string `json:"status"` + Currency string `json:"currency"` + Amount string `json:"amount"` + Fee string `json:"fee"` + Sn big.Int `json:"sn"` + Reason string `json:"reason"` + CreatedAt jsoniter.Number `json:"createdAt"` + UpdatedAt jsoniter.Number `json:"updatedAt"` } // TransferIn The amount to be transferred will be deducted from the KuCoin Futures Account. diff --git a/websocket.go b/websocket.go index 1ee480a..01848da 100644 --- a/websocket.go +++ b/websocket.go @@ -2,7 +2,6 @@ package kumex import ( "crypto/tls" - "encoding/json" "fmt" "math/rand" "net/http" @@ -11,6 +10,7 @@ import ( "time" "github.com/gorilla/websocket" + "github.com/json-iterator/go" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -123,15 +123,15 @@ func NewUnsubscribeMessage(topic string, privateChannel bool) *WebSocketUnsubscr // A WebSocketDownstreamMessage represents a message from the WebSocket server to client. type WebSocketDownstreamMessage struct { *WebSocketMessage - Sn string `json:"sn"` - Topic string `json:"topic"` - Subject string `json:"subject"` - RawData json.RawMessage `json:"data"` + Sn string `json:"sn"` + Topic string `json:"topic"` + Subject string `json:"subject"` + RawData jsoniter.RawMessage `json:"data"` } // ReadData read the data in channel. func (m *WebSocketDownstreamMessage) ReadData(v interface{}) error { - return json.Unmarshal(m.RawData, v) + return jsoniter.Unmarshal(m.RawData, v) } // A WebSocketClient represents a connection to WebSocket server.