mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 15:09:42 +00:00
okx: Remove WsResponseMultiplexer and various refactors (#1851)
* rm WsResponseMultiplexer with added fixes * linter: fix * use const and testnet ctx update * rename error to status for field name * rm verbosity for random test * gk: nits v1 * glorious/gk: nits * linter: fix * fix and consolidate this direction * fix linter * gk: nits cont * gk: nits I missed * gk: counter name change to messageIDSeq * gk/glorious: nits untested * glorious: nits and tested live endpoints * Update exchanges/okx/ws_requests.go Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io> * Update exchanges/okx/ws_requests.go Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io> * Update exchanges/okx/ws_requests.go Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io> * Update exchanges/okx/okx.go Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io> * Update exchanges/okx/okx.go Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io> * thrasher-: nits! --------- Co-authored-by: Ryan O'Hara-Reid <ryan.oharareid@thrasher.io> Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>
This commit is contained in:
@@ -2,10 +2,8 @@ package okx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
@@ -139,41 +137,6 @@ func assetTypeFromInstrumentType(instrumentType string) (asset.Item, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (ok *Okx) validatePlaceOrderParams(arg *PlaceOrderRequestParam) error {
|
||||
if arg == nil {
|
||||
return common.ErrNilPointer
|
||||
}
|
||||
if arg.InstrumentID == "" {
|
||||
return errMissingInstrumentID
|
||||
}
|
||||
if arg.AssetType == asset.Spot || arg.AssetType == asset.Margin || arg.AssetType == asset.Empty {
|
||||
arg.Side = strings.ToLower(arg.Side)
|
||||
if arg.Side != order.Buy.Lower() && arg.Side != order.Sell.Lower() {
|
||||
return fmt.Errorf("%w %s", order.ErrSideIsInvalid, arg.Side)
|
||||
}
|
||||
}
|
||||
if !slices.Contains([]string{"", TradeModeCross, TradeModeIsolated, TradeModeCash}, arg.TradeMode) {
|
||||
return fmt.Errorf("%w %s", errInvalidTradeModeValue, arg.TradeMode)
|
||||
}
|
||||
if arg.AssetType == asset.Futures || arg.AssetType == asset.PerpetualSwap {
|
||||
arg.PositionSide = strings.ToLower(arg.PositionSide)
|
||||
if !slices.Contains([]string{"long", "short"}, arg.PositionSide) {
|
||||
return fmt.Errorf("%w: `%s`, 'long' or 'short' supported", order.ErrSideIsInvalid, arg.PositionSide)
|
||||
}
|
||||
}
|
||||
arg.OrderType = strings.ToLower(arg.OrderType)
|
||||
if !slices.Contains([]string{orderMarket, orderLimit, orderPostOnly, orderFOK, orderIOC, orderOptimalLimitIOC, "mmp", "mmp_and_post_only"}, arg.OrderType) {
|
||||
return fmt.Errorf("%w: '%v'", order.ErrTypeIsInvalid, arg.OrderType)
|
||||
}
|
||||
if arg.Amount <= 0 {
|
||||
return order.ErrAmountBelowMin
|
||||
}
|
||||
if !slices.Contains([]string{"", "base_ccy", "quote_ccy"}, arg.QuantityType) {
|
||||
return errCurrencyQuantityTypeRequired
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// assetTypeString returns a string representation of asset type
|
||||
func assetTypeString(assetType asset.Item) (string, error) {
|
||||
switch assetType {
|
||||
|
||||
@@ -30,15 +30,8 @@ import (
|
||||
// Okx is the overarching type across this package
|
||||
type Okx struct {
|
||||
exchange.Base
|
||||
WsResponseMultiplexer wsRequestDataChannelsMultiplexer
|
||||
|
||||
// WsRequestSemaphore channel is used to block write operation on the websocket connection to reduce contention; a kind of bounded parallelism.
|
||||
// it is made to hold up to 20 integers so that up to 20 write operations can be called over the websocket connection at a time.
|
||||
// and when the operation is completed the thread releases (consumes) one value from the channel so that the other waiting operation can enter.
|
||||
// ok.WsRequestSemaphore <- 1
|
||||
// defer func() { <-ok.WsRequestSemaphore }()
|
||||
WsRequestSemaphore chan int
|
||||
|
||||
messageIDSeq common.Counter
|
||||
instrumentsInfoMapLock sync.Mutex
|
||||
instrumentsInfoMap map[string][]Instrument
|
||||
}
|
||||
@@ -58,15 +51,14 @@ const (
|
||||
|
||||
// PlaceOrder places an order
|
||||
func (ok *Okx) PlaceOrder(ctx context.Context, arg *PlaceOrderRequestParam) (*OrderData, error) {
|
||||
err := ok.validatePlaceOrderParams(arg)
|
||||
if err != nil {
|
||||
if err := arg.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var resp *OrderData
|
||||
err = ok.SendHTTPRequest(ctx, exchange.RestSpot, placeOrderEPL, http.MethodPost, "trade/order", &arg, &resp, request.AuthenticatedRequest)
|
||||
err := ok.SendHTTPRequest(ctx, exchange.RestSpot, placeOrderEPL, http.MethodPost, "trade/order", &arg, &resp, request.AuthenticatedRequest)
|
||||
if err != nil {
|
||||
if resp != nil && resp.StatusMessage != "" {
|
||||
return nil, fmt.Errorf("%w, error code: %s error message: %s", err, resp.StatusCode, resp.StatusMessage)
|
||||
return nil, fmt.Errorf("%w; %w", err, getStatusError(resp.StatusCode, resp.StatusMessage))
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
@@ -78,24 +70,22 @@ func (ok *Okx) PlaceMultipleOrders(ctx context.Context, args []PlaceOrderRequest
|
||||
if len(args) == 0 {
|
||||
return nil, order.ErrSubmissionIsNil
|
||||
}
|
||||
var err error
|
||||
for x := range args {
|
||||
err = ok.validatePlaceOrderParams(&args[x])
|
||||
if err != nil {
|
||||
if err := args[x].Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
var resp []OrderData
|
||||
err = ok.SendHTTPRequest(ctx, exchange.RestSpot, placeMultipleOrdersEPL, http.MethodPost, "trade/batch-orders", &args, &resp, request.AuthenticatedRequest)
|
||||
err := ok.SendHTTPRequest(ctx, exchange.RestSpot, placeMultipleOrdersEPL, http.MethodPost, "trade/batch-orders", &args, &resp, request.AuthenticatedRequest)
|
||||
if err != nil {
|
||||
if len(resp) == 0 {
|
||||
return nil, err
|
||||
}
|
||||
var errs error
|
||||
for x := range resp {
|
||||
errs = common.AppendError(errs, fmt.Errorf("error code:%s error message: %v", resp[x].StatusCode, resp[x].StatusMessage))
|
||||
errs = common.AppendError(errs, getStatusError(resp[x].StatusCode, resp[x].StatusMessage))
|
||||
}
|
||||
return nil, errs
|
||||
return nil, common.AppendError(err, errs)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
@@ -115,7 +105,7 @@ func (ok *Okx) CancelSingleOrder(ctx context.Context, arg *CancelOrderRequestPar
|
||||
err := ok.SendHTTPRequest(ctx, exchange.RestSpot, cancelOrderEPL, http.MethodPost, "trade/cancel-order", &arg, &resp, request.AuthenticatedRequest)
|
||||
if err != nil {
|
||||
if resp != nil && resp.StatusMessage != "" {
|
||||
return nil, fmt.Errorf("%w, error code: %s and error message: %s", err, resp.StatusCode, resp.StatusMessage)
|
||||
return nil, fmt.Errorf("%w; %w", err, getStatusError(resp.StatusCode, resp.StatusMessage))
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
@@ -124,7 +114,7 @@ func (ok *Okx) CancelSingleOrder(ctx context.Context, arg *CancelOrderRequestPar
|
||||
|
||||
// CancelMultipleOrders cancel incomplete orders in batches. Maximum 20 orders can be canceled at a time.
|
||||
// Request parameters should be passed in the form of an array
|
||||
func (ok *Okx) CancelMultipleOrders(ctx context.Context, args []CancelOrderRequestParam) ([]OrderData, error) {
|
||||
func (ok *Okx) CancelMultipleOrders(ctx context.Context, args []CancelOrderRequestParam) ([]*OrderData, error) {
|
||||
if len(args) == 0 {
|
||||
return nil, common.ErrEmptyParams
|
||||
}
|
||||
@@ -137,20 +127,19 @@ func (ok *Okx) CancelMultipleOrders(ctx context.Context, args []CancelOrderReque
|
||||
return nil, order.ErrOrderIDNotSet
|
||||
}
|
||||
}
|
||||
var resp []OrderData
|
||||
err := ok.SendHTTPRequest(ctx, exchange.RestSpot, cancelMultipleOrdersEPL,
|
||||
http.MethodPost, "trade/cancel-batch-orders", args, &resp, request.AuthenticatedRequest)
|
||||
var resp []*OrderData
|
||||
err := ok.SendHTTPRequest(ctx, exchange.RestSpot, cancelMultipleOrdersEPL, http.MethodPost, "trade/cancel-batch-orders", args, &resp, request.AuthenticatedRequest)
|
||||
if err != nil {
|
||||
if len(resp) == 0 {
|
||||
return nil, err
|
||||
}
|
||||
var errs error
|
||||
for x := range resp {
|
||||
if resp[x].StatusCode != "0" {
|
||||
errs = common.AppendError(errs, fmt.Errorf("error code:%s message: %v", resp[x].StatusCode, resp[x].StatusMessage))
|
||||
if resp[x].StatusCode != 0 {
|
||||
errs = common.AppendError(errs, getStatusError(resp[x].StatusCode, resp[x].StatusMessage))
|
||||
}
|
||||
}
|
||||
return nil, errs
|
||||
return nil, common.AppendError(err, errs)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
@@ -556,7 +545,7 @@ func (ok *Okx) cancelAlgoOrder(ctx context.Context, args []AlgoOrderCancelParams
|
||||
err := ok.SendHTTPRequest(ctx, exchange.RestSpot, rateLimit, http.MethodPost, route, &args, &resp, request.AuthenticatedRequest)
|
||||
if err != nil {
|
||||
if resp != nil && resp.StatusMessage != "" {
|
||||
return nil, fmt.Errorf("%w, error code: %s, error message: %s", err, resp.StatusCode, resp.StatusMessage)
|
||||
return nil, fmt.Errorf("%w; %w", err, getStatusError(resp.StatusCode, resp.StatusMessage))
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
@@ -2981,7 +2970,7 @@ func (ok *Okx) PlaceGridAlgoOrder(ctx context.Context, arg *GridAlgoOrder) (*Gri
|
||||
err := ok.SendHTTPRequest(ctx, exchange.RestSpot, gridTradingEPL, http.MethodPost, "tradingBot/grid/order-algo", &arg, &resp, request.AuthenticatedRequest)
|
||||
if err != nil {
|
||||
if resp != nil && resp.StatusMessage != "" {
|
||||
return nil, fmt.Errorf("%w, error code: %s error message: %s", err, resp.StatusCode, resp.StatusMessage)
|
||||
return nil, fmt.Errorf("%w; %w", err, getStatusError(resp.StatusCode, resp.StatusMessage))
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
@@ -3003,7 +2992,7 @@ func (ok *Okx) AmendGridAlgoOrder(ctx context.Context, arg *GridAlgoOrderAmend)
|
||||
err := ok.SendHTTPRequest(ctx, exchange.RestSpot, amendGridAlgoOrderEPL, http.MethodPost, "tradingBot/grid/amend-order-algo", &arg, &resp, request.AuthenticatedRequest)
|
||||
if err != nil {
|
||||
if resp != nil && resp.StatusMessage == "" {
|
||||
return nil, fmt.Errorf("%w, error code: %s and error message: %s", err, resp.StatusMessage, resp.StatusCode)
|
||||
return nil, fmt.Errorf("%w; %w", err, getStatusError(resp.StatusCode, resp.StatusMessage))
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
@@ -3040,7 +3029,13 @@ func (ok *Okx) StopGridAlgoOrder(ctx context.Context, arg []StopGridAlgoOrderReq
|
||||
if len(resp) == 0 {
|
||||
return nil, err
|
||||
}
|
||||
return nil, fmt.Errorf("error code:%s error message: %v", resp[0].StatusCode, resp[0].StatusMessage)
|
||||
var errs error
|
||||
for x := range resp {
|
||||
if resp[x].StatusMessage != "" {
|
||||
errs = common.AppendError(errs, getStatusError(resp[x].StatusCode, resp[x].StatusMessage))
|
||||
}
|
||||
}
|
||||
return nil, common.AppendError(err, errs)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
@@ -3718,22 +3713,22 @@ func (ok *Okx) GetUnrealizedProfitSharingDetails(ctx context.Context, instrument
|
||||
}
|
||||
|
||||
// SetFirstCopySettings set first copy settings for the certain lead trader. You need to first copy settings after stopping copying
|
||||
func (ok *Okx) SetFirstCopySettings(ctx context.Context, arg *FirstCopySettings) (*ResponseSuccess, error) {
|
||||
func (ok *Okx) SetFirstCopySettings(ctx context.Context, arg *FirstCopySettings) (*ResponseResult, error) {
|
||||
err := validateFirstCopySettings(arg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var resp *ResponseSuccess
|
||||
var resp *ResponseResult
|
||||
return resp, ok.SendHTTPRequest(ctx, exchange.RestSpot, setFirstCopySettingsEPL, http.MethodPost, "copytrading/first-copy-settings", arg, &resp, request.AuthenticatedRequest)
|
||||
}
|
||||
|
||||
// AmendCopySettings amends need to use this endpoint for amending copy settings
|
||||
func (ok *Okx) AmendCopySettings(ctx context.Context, arg *FirstCopySettings) (*ResponseSuccess, error) {
|
||||
func (ok *Okx) AmendCopySettings(ctx context.Context, arg *FirstCopySettings) (*ResponseResult, error) {
|
||||
err := validateFirstCopySettings(arg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var resp *ResponseSuccess
|
||||
var resp *ResponseResult
|
||||
return resp, ok.SendHTTPRequest(ctx, exchange.RestSpot, amendFirstCopySettingsEPL, http.MethodPost, "copytrading/amend-copy-settings", arg, &resp, request.AuthenticatedRequest)
|
||||
}
|
||||
|
||||
@@ -3757,7 +3752,7 @@ func validateFirstCopySettings(arg *FirstCopySettings) error {
|
||||
}
|
||||
|
||||
// StopCopying need to use this endpoint for amending copy settings
|
||||
func (ok *Okx) StopCopying(ctx context.Context, arg *StopCopyingParameter) (*ResponseSuccess, error) {
|
||||
func (ok *Okx) StopCopying(ctx context.Context, arg *StopCopyingParameter) (*ResponseResult, error) {
|
||||
if *arg == (StopCopyingParameter{}) {
|
||||
return nil, common.ErrEmptyParams
|
||||
}
|
||||
@@ -3767,7 +3762,7 @@ func (ok *Okx) StopCopying(ctx context.Context, arg *StopCopyingParameter) (*Res
|
||||
if arg.SubPositionCloseType == "" {
|
||||
return nil, errSubPositionCloseTypeRequired
|
||||
}
|
||||
var resp *ResponseSuccess
|
||||
var resp *ResponseResult
|
||||
return resp, ok.SendHTTPRequest(ctx, exchange.RestSpot, stopCopyingEPL, http.MethodPost, "copytrading/stop-copy-trading", arg, &resp, request.AuthenticatedRequest)
|
||||
}
|
||||
|
||||
@@ -5868,7 +5863,7 @@ func (ok *Okx) SendHTTPRequest(ctx context.Context, ep exchange.URL, f request.E
|
||||
path := endpoint + requestPath
|
||||
headers := make(map[string]string)
|
||||
headers["Content-Type"] = "application/json"
|
||||
if _, okay := ctx.Value(testNetVal).(bool); okay {
|
||||
if simulate, okay := ctx.Value(testNetVal).(bool); okay && simulate {
|
||||
headers["x-simulated-trading"] = "1"
|
||||
}
|
||||
if authenticated == request.AuthenticatedRequest {
|
||||
@@ -5905,13 +5900,16 @@ func (ok *Okx) SendHTTPRequest(ctx context.Context, ep exchange.URL, f request.E
|
||||
return err
|
||||
}
|
||||
if err == nil && resp.Code.Int64() != 0 {
|
||||
if authenticated == request.AuthenticatedRequest {
|
||||
err = request.ErrAuthRequestFailed
|
||||
}
|
||||
if resp.Msg != "" {
|
||||
return fmt.Errorf("%w error code: %d message: %s", request.ErrAuthRequestFailed, resp.Code.Int64(), resp.Msg)
|
||||
return common.AppendError(err, fmt.Errorf("error code: `%d`; message: %q", resp.Code.Int64(), resp.Msg))
|
||||
}
|
||||
if err, ok := ErrorCodes[resp.Code.String()]; ok {
|
||||
return err
|
||||
if mErr, ok := ErrorCodes[resp.Code.String()]; ok {
|
||||
return common.AppendError(err, mErr)
|
||||
}
|
||||
return fmt.Errorf("%w error code: %d", request.ErrAuthRequestFailed, resp.Code.Int64())
|
||||
return common.AppendError(err, fmt.Errorf("error code: `%d`", resp.Code.Int64()))
|
||||
}
|
||||
|
||||
// First see if resp.Data can unmarshal into a slice of result, which is true for most APIs
|
||||
@@ -5924,3 +5922,10 @@ func (ok *Okx) SendHTTPRequest(ctx context.Context, ep exchange.URL, f request.E
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getStatusError(statusCode int64, statusMessage string) error {
|
||||
if statusCode == 0 {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("status code: `%d` status message: %q", statusCode, statusMessage)
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"time"
|
||||
|
||||
gws "github.com/gorilla/websocket"
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/common/crypto"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/encoding/json"
|
||||
@@ -98,56 +97,15 @@ func (ok *Okx) WsSpreadAuth(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
base64Sign := crypto.Base64Encode(hmac)
|
||||
wsReq := WebsocketEventRequest{
|
||||
Operation: operationLogin,
|
||||
Arguments: []WebsocketLoginData{
|
||||
{
|
||||
APIKey: creds.Key,
|
||||
Passphrase: creds.ClientID,
|
||||
Timestamp: timeUnix.Unix(),
|
||||
Sign: base64Sign,
|
||||
},
|
||||
args := []WebsocketLoginData{
|
||||
{
|
||||
APIKey: creds.Key,
|
||||
Passphrase: creds.ClientID,
|
||||
Timestamp: timeUnix.Unix(),
|
||||
Sign: base64Sign,
|
||||
},
|
||||
}
|
||||
err = ok.Websocket.AuthConn.SendJSONMessage(ctx, request.UnAuth, wsReq)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
timer := time.NewTimer(ok.WebsocketResponseCheckTimeout)
|
||||
randomID, err := common.GenerateRandomString(16)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w, generating random string for incoming websocket response failed", err)
|
||||
}
|
||||
wsResponse := make(chan *wsIncomingData)
|
||||
ok.WsResponseMultiplexer.Register <- &wsRequestInfo{
|
||||
ID: randomID,
|
||||
Chan: wsResponse,
|
||||
Event: operationLogin,
|
||||
}
|
||||
ok.WsRequestSemaphore <- 1
|
||||
defer func() {
|
||||
<-ok.WsRequestSemaphore
|
||||
}()
|
||||
defer func() { ok.WsResponseMultiplexer.Unregister <- randomID }()
|
||||
for {
|
||||
select {
|
||||
case data := <-wsResponse:
|
||||
if data.Event == operationLogin && data.StatusCode == "0" {
|
||||
ok.Websocket.SetCanUseAuthenticatedEndpoints(true)
|
||||
return nil
|
||||
} else if data.Event == "error" &&
|
||||
(data.StatusCode == "60022" || data.StatusCode == "60009") {
|
||||
ok.Websocket.SetCanUseAuthenticatedEndpoints(false)
|
||||
return fmt.Errorf("authentication failed with error: %v", ErrorCodes[data.StatusCode])
|
||||
}
|
||||
continue
|
||||
case <-timer.C:
|
||||
timer.Stop()
|
||||
return fmt.Errorf("%s websocket connection: timeout waiting for response with an operation: %v",
|
||||
ok.Name,
|
||||
wsReq.Operation)
|
||||
}
|
||||
}
|
||||
return ok.SendAuthenticatedWebsocketRequest(ctx, request.Unset, "login-response", operationLogin, args, nil)
|
||||
}
|
||||
|
||||
// GenerateDefaultBusinessSubscriptions returns a list of default subscriptions to business websocket.
|
||||
@@ -213,8 +171,6 @@ func (ok *Okx) BusinessUnsubscribe(channelsToUnsubscribe subscription.List) erro
|
||||
// as of the okx, exchange this endpoint sends subscription and unsubscription messages but with a list of json objects.
|
||||
func (ok *Okx) handleBusinessSubscription(operation string, subscriptions subscription.List) error {
|
||||
wsSubscriptionReq := WSSubscriptionInformationList{Operation: operation}
|
||||
ok.WsRequestSemaphore <- 1
|
||||
defer func() { <-ok.WsRequestSemaphore }()
|
||||
var channels subscription.List
|
||||
var authChannels subscription.List
|
||||
var err error
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,8 +2,10 @@ package okx
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
@@ -81,7 +83,6 @@ var (
|
||||
errMissingExpiryTimeParameter = errors.New("missing expiry date parameter")
|
||||
errInvalidTradeModeValue = errors.New("invalid trade mode value")
|
||||
errCurrencyQuantityTypeRequired = errors.New("only base_ccy and quote_ccy quantity types are supported")
|
||||
errWebsocketStreamNotAuthenticated = errors.New("websocket stream not authenticated")
|
||||
errInvalidNewSizeOrPriceInformation = errors.New("invalid new size or price information")
|
||||
errSizeOrPriceIsRequired = errors.New("either size or price is required")
|
||||
errInvalidPriceLimit = errors.New("invalid price limit value")
|
||||
@@ -114,8 +115,6 @@ var (
|
||||
errMissingSubOrderType = errors.New("missing sub order type")
|
||||
errMissingQuantity = errors.New("invalid quantity to buy or sell")
|
||||
errAddressRequired = errors.New("address is required")
|
||||
errInvalidWebsocketEvent = errors.New("invalid websocket event")
|
||||
errMissingValidChannelInformation = errors.New("missing channel information")
|
||||
errMaxRFQOrdersToCancel = errors.New("no more than 100 RFQ cancel order parameter is allowed")
|
||||
errInvalidUnderlying = errors.New("invalid underlying")
|
||||
errInstrumentFamilyOrUnderlyingRequired = errors.New("either underlying or instrument family is required")
|
||||
@@ -127,7 +126,6 @@ var (
|
||||
errInvalidAlgoOrderType = errors.New("invalid algo order type")
|
||||
errInvalidIPAddress = errors.New("invalid ip address")
|
||||
errInvalidAPIKeyPermission = errors.New("invalid API Key permission")
|
||||
errInvalidResponseParam = errors.New("invalid response parameter, response must be non-nil pointer")
|
||||
errInvalidDuration = errors.New("invalid grid contract duration, only '7D', '30D', and '180D' are allowed")
|
||||
errInvalidProtocolType = errors.New("invalid protocol type, only 'staking' and 'defi' allowed")
|
||||
errExceedLimit = errors.New("limit exceeded")
|
||||
@@ -231,8 +229,7 @@ type OrderbookItemDetail struct {
|
||||
|
||||
// UnmarshalJSON deserializes byte data into OrderbookItemDetail instance
|
||||
func (o *OrderbookItemDetail) UnmarshalJSON(data []byte) error {
|
||||
target := [4]any{&o.DepthPrice, &o.Amount, &o.LiquidationOrders, &o.NumberOfOrders}
|
||||
return json.Unmarshal(data, &target)
|
||||
return json.Unmarshal(data, &[4]any{&o.DepthPrice, &o.Amount, &o.LiquidationOrders, &o.NumberOfOrders})
|
||||
}
|
||||
|
||||
// CandlestickHistoryItem retrieves historical candlestick charts for the index or mark price from recent years.
|
||||
@@ -248,9 +245,7 @@ type CandlestickHistoryItem struct {
|
||||
// UnmarshalJSON converts the data slice into a CandlestickHistoryItem instance.
|
||||
func (c *CandlestickHistoryItem) UnmarshalJSON(data []byte) error {
|
||||
var state string
|
||||
target := []any{&c.Timestamp, &c.OpenPrice, &c.HighestPrice, &c.LowestPrice, &c.ClosePrice, &state}
|
||||
err := json.Unmarshal(data, &target)
|
||||
if err != nil {
|
||||
if err := json.Unmarshal(data, &[6]any{&c.Timestamp, &c.OpenPrice, &c.HighestPrice, &c.LowestPrice, &c.ClosePrice, &state}); err != nil {
|
||||
return err
|
||||
}
|
||||
if state == "1" {
|
||||
@@ -274,8 +269,7 @@ type CandleStick struct {
|
||||
|
||||
// UnmarshalJSON deserializes slice of data into Candlestick structure
|
||||
func (c *CandleStick) UnmarshalJSON(data []byte) error {
|
||||
target := [7]any{&c.OpenTime, &c.OpenPrice, &c.HighestPrice, &c.LowestPrice, &c.ClosePrice, &c.Volume, &c.QuoteAssetVolume}
|
||||
return json.Unmarshal(data, &target)
|
||||
return json.Unmarshal(data, &[7]any{&c.OpenTime, &c.OpenPrice, &c.HighestPrice, &c.LowestPrice, &c.ClosePrice, &c.Volume, &c.QuoteAssetVolume})
|
||||
}
|
||||
|
||||
// TradeResponse represents the recent transaction instance
|
||||
@@ -634,8 +628,7 @@ type TakerVolume struct {
|
||||
|
||||
// UnmarshalJSON deserializes a slice of data into TakerVolume
|
||||
func (t *TakerVolume) UnmarshalJSON(data []byte) error {
|
||||
deploy := [3]any{&t.Timestamp, &t.SellVolume, &t.BuyVolume}
|
||||
return json.Unmarshal(data, &deploy)
|
||||
return json.Unmarshal(data, &[3]any{&t.Timestamp, &t.SellVolume, &t.BuyVolume})
|
||||
}
|
||||
|
||||
// MarginLendRatioItem represents margin lend ration information and creation timestamp
|
||||
@@ -646,8 +639,7 @@ type MarginLendRatioItem struct {
|
||||
|
||||
// UnmarshalJSON deserializes a slice of data into MarginLendRatio
|
||||
func (m *MarginLendRatioItem) UnmarshalJSON(data []byte) error {
|
||||
target := [2]any{&m.Timestamp, &m.MarginLendRatio}
|
||||
return json.Unmarshal(data, &target)
|
||||
return json.Unmarshal(data, &[2]any{&m.Timestamp, &m.MarginLendRatio})
|
||||
}
|
||||
|
||||
// LongShortRatio represents the ratio of users with net long vs net short positions for futures and perpetual swaps
|
||||
@@ -658,8 +650,7 @@ type LongShortRatio struct {
|
||||
|
||||
// UnmarshalJSON deserializes a slice of data into LongShortRatio
|
||||
func (l *LongShortRatio) UnmarshalJSON(data []byte) error {
|
||||
target := [2]any{&l.Timestamp, &l.MarginLendRatio}
|
||||
return json.Unmarshal(data, &target)
|
||||
return json.Unmarshal(data, &[2]any{&l.Timestamp, &l.MarginLendRatio})
|
||||
}
|
||||
|
||||
// OpenInterestVolume represents open interest and trading volume item for currencies of futures and perpetual swaps
|
||||
@@ -671,8 +662,7 @@ type OpenInterestVolume struct {
|
||||
|
||||
// UnmarshalJSON deserializes json data into OpenInterestVolume struct
|
||||
func (p *OpenInterestVolume) UnmarshalJSON(data []byte) error {
|
||||
deploy := [3]any{&p.Timestamp, &p.OpenInterest, &p.Volume}
|
||||
return json.Unmarshal(data, &deploy)
|
||||
return json.Unmarshal(data, &[3]any{&p.Timestamp, &p.OpenInterest, &p.Volume})
|
||||
}
|
||||
|
||||
// OpenInterestVolumeRatio represents open interest and trading volume ratio for currencies of futures and perpetual swaps
|
||||
@@ -684,8 +674,7 @@ type OpenInterestVolumeRatio struct {
|
||||
|
||||
// UnmarshalJSON deserializes json data into OpenInterestVolumeRatio
|
||||
func (o *OpenInterestVolumeRatio) UnmarshalJSON(data []byte) error {
|
||||
deploy := [3]any{&o.Timestamp, &o.OpenInterestRatio, &o.VolumeRatio}
|
||||
return json.Unmarshal(data, &deploy)
|
||||
return json.Unmarshal(data, &[3]any{&o.Timestamp, &o.OpenInterestRatio, &o.VolumeRatio})
|
||||
}
|
||||
|
||||
// ExpiryOpenInterestAndVolume represents open interest and trading volume of calls and puts for each upcoming expiration
|
||||
@@ -751,8 +740,7 @@ type StrikeOpenInterestAndVolume struct {
|
||||
|
||||
// UnmarshalJSON deserializes slice of byte data into StrikeOpenInterestAndVolume
|
||||
func (s *StrikeOpenInterestAndVolume) UnmarshalJSON(data []byte) error {
|
||||
target := [6]any{&s.Timestamp, &s.Strike, &s.CallOpenInterest, &s.PutOpenInterest, &s.CallVolume, &s.PutVolume}
|
||||
return json.Unmarshal(data, &target)
|
||||
return json.Unmarshal(data, &[6]any{&s.Timestamp, &s.Strike, &s.CallOpenInterest, &s.PutOpenInterest, &s.CallVolume, &s.PutVolume})
|
||||
}
|
||||
|
||||
// CurrencyTakerFlow holds the taker volume information for a single currency
|
||||
@@ -768,46 +756,93 @@ type CurrencyTakerFlow struct {
|
||||
|
||||
// UnmarshalJSON deserializes a slice of byte data into CurrencyTakerFlow
|
||||
func (c *CurrencyTakerFlow) UnmarshalJSON(data []byte) error {
|
||||
target := [7]any{&c.Timestamp, &c.CallBuyVolume, &c.CallSellVolume, &c.PutBuyVolume, &c.PutSellVolume, &c.CallBlockVolume, &c.PutBlockVolume}
|
||||
return json.Unmarshal(data, &target)
|
||||
return json.Unmarshal(data, &[7]any{&c.Timestamp, &c.CallBuyVolume, &c.CallSellVolume, &c.PutBuyVolume, &c.PutSellVolume, &c.CallBlockVolume, &c.PutBlockVolume})
|
||||
}
|
||||
|
||||
// PlaceOrderRequestParam requesting parameter for placing an order
|
||||
type PlaceOrderRequestParam struct {
|
||||
AssetType asset.Item `json:"-"`
|
||||
InstrumentID string `json:"instId"`
|
||||
TradeMode string `json:"tdMode,omitempty"` // cash isolated
|
||||
TradeMode string `json:"tdMode"` // cash isolated
|
||||
ClientOrderID string `json:"clOrdId,omitempty"`
|
||||
Currency string `json:"ccy,omitempty"` // Only applicable to cross MARGIN orders in Single-currency margin.
|
||||
OrderTag string `json:"tag,omitempty"`
|
||||
Side string `json:"side,omitempty"`
|
||||
PositionSide string `json:"posSide,omitempty"`
|
||||
OrderType string `json:"ordType,omitempty"`
|
||||
Amount float64 `json:"sz,string,omitempty"`
|
||||
Price float64 `json:"px,string,omitempty"`
|
||||
ReduceOnly bool `json:"reduceOnly,string,omitempty"`
|
||||
QuantityType string `json:"tgtCcy,omitempty"` // values base_ccy and quote_ccy
|
||||
Side string `json:"side"`
|
||||
PositionSide string `json:"posSide,omitempty"` // long/short only for FUTURES and SWAP
|
||||
OrderType string `json:"ordType"` // Time in force for the order
|
||||
Amount float64 `json:"sz,string"`
|
||||
Price float64 `json:"px,string,omitempty"` // Only applicable to limit,post_only,fok,ioc,mmp,mmp_and_post_only order.
|
||||
// Options orders
|
||||
PlaceOptionsOrder string `json:"pxUsd,omitempty"` // Place options orders in USD
|
||||
PlaceOptionsOrderOnImpliedVolatility string `json:"pxVol,omitempty"` // Place options orders based on implied volatility, where 1 represents 100%
|
||||
|
||||
ReduceOnly bool `json:"reduceOnly,string,omitempty"`
|
||||
TargetCurrency string `json:"tgtCcy,omitempty"` // values base_ccy and quote_ccy for spot market orders
|
||||
SelfTradePreventionMode string `json:"stpMode,omitempty"` // Default to cancel maker, `cancel_maker`,`cancel_taker`, `cancel_both``
|
||||
// Added in the websocket requests
|
||||
BanAmend bool `json:"banAmend,omitempty"` // Whether the SPOT Market Order size can be amended by the system.
|
||||
ExpiryTime types.Time `json:"expTime,omitzero"`
|
||||
BanAmend bool `json:"banAmend,omitempty"` // Whether the SPOT Market Order size can be amended by the system.
|
||||
}
|
||||
|
||||
// Validate validates the PlaceOrderRequestParam
|
||||
func (arg *PlaceOrderRequestParam) Validate() error {
|
||||
if arg == nil {
|
||||
return fmt.Errorf("%T: %w", arg, common.ErrNilPointer)
|
||||
}
|
||||
if arg.InstrumentID == "" {
|
||||
return errMissingInstrumentID
|
||||
}
|
||||
if arg.AssetType == asset.Spot || arg.AssetType == asset.Margin || arg.AssetType == asset.Empty {
|
||||
arg.Side = strings.ToLower(arg.Side)
|
||||
if arg.Side != order.Buy.Lower() && arg.Side != order.Sell.Lower() {
|
||||
return fmt.Errorf("%w %s", order.ErrSideIsInvalid, arg.Side)
|
||||
}
|
||||
}
|
||||
if !slices.Contains([]string{"", TradeModeCross, TradeModeIsolated, TradeModeCash}, arg.TradeMode) {
|
||||
return fmt.Errorf("%w %s", errInvalidTradeModeValue, arg.TradeMode)
|
||||
}
|
||||
if arg.AssetType == asset.Futures || arg.AssetType == asset.PerpetualSwap {
|
||||
arg.PositionSide = strings.ToLower(arg.PositionSide)
|
||||
if !slices.Contains([]string{"long", "short"}, arg.PositionSide) {
|
||||
return fmt.Errorf("%w: `%s`, 'long' or 'short' supported", order.ErrSideIsInvalid, arg.PositionSide)
|
||||
}
|
||||
}
|
||||
arg.OrderType = strings.ToLower(arg.OrderType)
|
||||
if !slices.Contains([]string{orderMarket, orderLimit, orderPostOnly, orderFOK, orderIOC, orderOptimalLimitIOC, "mmp", "mmp_and_post_only"}, arg.OrderType) {
|
||||
return fmt.Errorf("%w: '%v'", order.ErrTypeIsInvalid, arg.OrderType)
|
||||
}
|
||||
if arg.Amount <= 0 {
|
||||
return order.ErrAmountBelowMin
|
||||
}
|
||||
if !slices.Contains([]string{"", "base_ccy", "quote_ccy"}, arg.TargetCurrency) {
|
||||
return errCurrencyQuantityTypeRequired
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// OrderData response message for place, cancel, and amend an order requests.
|
||||
type OrderData struct {
|
||||
OrderID string `json:"ordId,omitempty"`
|
||||
RequestID string `json:"reqId,omitempty"`
|
||||
ClientOrderID string `json:"clOrdId,omitempty"`
|
||||
Tag string `json:"tag,omitempty"`
|
||||
StatusCode string `json:"sCode,omitempty"`
|
||||
StatusMessage string `json:"sMsg,omitempty"`
|
||||
OrderID string `json:"ordId"`
|
||||
RequestID string `json:"reqId"`
|
||||
ClientOrderID string `json:"clOrdId"`
|
||||
Tag string `json:"tag"`
|
||||
StatusCode int64 `json:"sCode,string"` // Anything above 0 is an error with an attached message
|
||||
StatusMessage string `json:"sMsg"`
|
||||
Timestamp string `json:"ts"`
|
||||
}
|
||||
|
||||
// ResponseSuccess holds responses having a status result value
|
||||
type ResponseSuccess struct {
|
||||
Result bool `json:"result"`
|
||||
func (o *OrderData) Error() error {
|
||||
return getStatusError(o.StatusCode, o.StatusMessage)
|
||||
}
|
||||
|
||||
StatusCode string `json:"sCode,omitempty"`
|
||||
StatusMessage string `json:"sMsg,omitempty"`
|
||||
// ResponseResult holds responses having a status result value
|
||||
type ResponseResult struct {
|
||||
Result bool `json:"result"`
|
||||
StatusCode int64 `json:"sCode,string"`
|
||||
StatusMessage string `json:"sMsg"`
|
||||
}
|
||||
|
||||
func (r *ResponseResult) Error() error {
|
||||
return getStatusError(r.StatusCode, r.StatusMessage)
|
||||
}
|
||||
|
||||
// CancelOrderRequestParam represents order parameters to cancel an order
|
||||
@@ -1106,7 +1141,7 @@ type AlgoOrderParams struct {
|
||||
// AlgoOrder algo order requests response
|
||||
type AlgoOrder struct {
|
||||
AlgoID string `json:"algoId"`
|
||||
StatusCode string `json:"sCode"`
|
||||
StatusCode int64 `json:"sCode,string"`
|
||||
StatusMessage string `json:"sMsg"`
|
||||
ClientOrderID string `json:"clOrdId"`
|
||||
AlgoClientOrderID string `json:"algoClOrdId"`
|
||||
@@ -2732,7 +2767,7 @@ type GridAlgoOrder struct {
|
||||
// GridAlgoOrderIDResponse represents grid algo order
|
||||
type GridAlgoOrderIDResponse struct {
|
||||
AlgoOrderID string `json:"algoId"`
|
||||
StatusCode string `json:"sCode"`
|
||||
StatusCode int64 `json:"sCode,string"`
|
||||
StatusMessage string `json:"sMsg"`
|
||||
}
|
||||
|
||||
@@ -2925,9 +2960,35 @@ type SpreadOrderParam struct {
|
||||
Tag string `json:"tag,omitempty"`
|
||||
}
|
||||
|
||||
// Validate checks if the parameters are valid
|
||||
func (arg *SpreadOrderParam) Validate() error {
|
||||
if arg == nil {
|
||||
return fmt.Errorf("%T: %w", arg, common.ErrNilPointer)
|
||||
}
|
||||
if arg.SpreadID == "" {
|
||||
return fmt.Errorf("%w, spread ID missing", errMissingInstrumentID)
|
||||
}
|
||||
if arg.OrderType == "" {
|
||||
return fmt.Errorf("%w spread order type is required", order.ErrTypeIsInvalid)
|
||||
}
|
||||
if arg.Size <= 0 {
|
||||
return order.ErrAmountBelowMin
|
||||
}
|
||||
if arg.Price <= 0 {
|
||||
return order.ErrPriceBelowMin
|
||||
}
|
||||
arg.Side = strings.ToLower(arg.Side)
|
||||
switch arg.Side {
|
||||
case order.Buy.Lower(), order.Sell.Lower():
|
||||
default:
|
||||
return fmt.Errorf("%w %s", order.ErrSideIsInvalid, arg.Side)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SpreadOrderResponse represents a spread create order response
|
||||
type SpreadOrderResponse struct {
|
||||
StatusCode string `json:"sCode"`
|
||||
StatusCode int64 `json:"sCode,string"` // Anything above 0 is an error with an attached message
|
||||
StatusMessage string `json:"sMsg"`
|
||||
ClientOrderID string `json:"clOrdId"`
|
||||
OrderID string `json:"ordId"`
|
||||
@@ -2937,6 +2998,10 @@ type SpreadOrderResponse struct {
|
||||
RequestID string `json:"reqId"`
|
||||
}
|
||||
|
||||
func (arg *SpreadOrderResponse) Error() error {
|
||||
return getStatusError(arg.StatusCode, arg.StatusMessage)
|
||||
}
|
||||
|
||||
// AmendSpreadOrderParam holds amend parameters for spread order
|
||||
type AmendSpreadOrderParam struct {
|
||||
OrderID string `json:"ordId"`
|
||||
@@ -3104,20 +3169,6 @@ type WSSubscriptionInformationList struct {
|
||||
Arguments []SubscriptionInfo `json:"args"`
|
||||
}
|
||||
|
||||
// OperationResponse holds common operation identification
|
||||
type OperationResponse struct {
|
||||
ID string `json:"id"`
|
||||
Operation string `json:"op"`
|
||||
Code string `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
||||
// WsPlaceOrderResponse place order response thought the websocket connection
|
||||
type WsPlaceOrderResponse struct {
|
||||
OperationResponse
|
||||
Data []OrderData `json:"data"`
|
||||
}
|
||||
|
||||
// SpreadOrderInfo holds spread order response information
|
||||
type SpreadOrderInfo struct {
|
||||
ClientOrderID string `json:"clOrdId"`
|
||||
@@ -3127,15 +3178,6 @@ type SpreadOrderInfo struct {
|
||||
StatusMessage string `json:"sMsg"`
|
||||
}
|
||||
|
||||
type wsRequestInfo struct {
|
||||
ID string
|
||||
Chan chan *wsIncomingData
|
||||
Event string
|
||||
Channel string
|
||||
InstrumentType string
|
||||
InstrumentID string
|
||||
}
|
||||
|
||||
type wsIncomingData struct {
|
||||
Event string `json:"event"`
|
||||
Argument SubscriptionInfo `json:"arg"`
|
||||
@@ -3148,37 +3190,6 @@ type wsIncomingData struct {
|
||||
Data json.RawMessage `json:"data"`
|
||||
}
|
||||
|
||||
// copyToPlaceOrderResponse returns WSPlaceOrderResponse struct instance
|
||||
func (w *wsIncomingData) copyToPlaceOrderResponse() (*WsPlaceOrderResponse, error) {
|
||||
if len(w.Data) == 0 {
|
||||
return nil, common.ErrNoResponse
|
||||
}
|
||||
var placeOrds []OrderData
|
||||
err := json.Unmarshal(w.Data, &placeOrds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &WsPlaceOrderResponse{
|
||||
OperationResponse: OperationResponse{
|
||||
Operation: w.Operation,
|
||||
ID: w.ID,
|
||||
},
|
||||
Data: placeOrds,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// copyResponseToInterface unmarshals the response data into the dataHolder interface.
|
||||
func (w *wsIncomingData) copyResponseToInterface(dataHolder any) error {
|
||||
rv := reflect.ValueOf(dataHolder)
|
||||
if rv.Kind() != reflect.Pointer {
|
||||
return errInvalidResponseParam
|
||||
}
|
||||
if len(w.Data) == 0 {
|
||||
return common.ErrNoResponse
|
||||
}
|
||||
return json.Unmarshal(w.Data, &[]any{dataHolder})
|
||||
}
|
||||
|
||||
// WSInstrumentResponse represents websocket instruments push message
|
||||
type WSInstrumentResponse struct {
|
||||
Argument SubscriptionInfo `json:"arg"`
|
||||
@@ -3213,17 +3224,6 @@ type WsOrderActionResponse struct {
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
||||
func (a *WsOrderActionResponse) populateFromIncomingData(incoming *wsIncomingData) error {
|
||||
if incoming == nil {
|
||||
return common.ErrNilPointer
|
||||
}
|
||||
a.ID = incoming.ID
|
||||
a.Code = incoming.StatusCode
|
||||
a.Operation = incoming.Operation
|
||||
a.Msg = incoming.Message
|
||||
return nil
|
||||
}
|
||||
|
||||
// SubscriptionOperationInput represents the account channel input data
|
||||
type SubscriptionOperationInput struct {
|
||||
Operation string `json:"op"`
|
||||
@@ -4307,24 +4307,6 @@ type APYItem struct {
|
||||
Timestamp types.Time `json:"ts"`
|
||||
}
|
||||
|
||||
// wsRequestDataChannelsMultiplexer a single multiplexer instance to multiplex websocket messages multiplexer channels
|
||||
type wsRequestDataChannelsMultiplexer struct {
|
||||
// To Synchronize incoming messages coming through the websocket channel
|
||||
WsResponseChannelsMap map[string]*wsRequestInfo
|
||||
Register chan *wsRequestInfo
|
||||
Unregister chan string
|
||||
Message chan *wsIncomingData
|
||||
shutdown chan bool
|
||||
}
|
||||
|
||||
// wsSubscriptionParameters represents toggling boolean values for subscription parameters
|
||||
type wsSubscriptionParameters struct {
|
||||
InstrumentType bool
|
||||
InstrumentID bool
|
||||
Underlying bool
|
||||
Currency bool
|
||||
}
|
||||
|
||||
// WsOrderbook5 stores the orderbook data for orderbook 5 websocket
|
||||
type WsOrderbook5 struct {
|
||||
Argument struct {
|
||||
@@ -4930,8 +4912,7 @@ type ContractTakerVolume struct {
|
||||
|
||||
// UnmarshalJSON deserializes a slice data into ContractTakerVolume
|
||||
func (c *ContractTakerVolume) UnmarshalJSON(data []byte) error {
|
||||
target := [3]any{&c.Timestamp, &c.TakerSellVolume, &c.TakerBuyVolume}
|
||||
return json.Unmarshal(data, &target)
|
||||
return json.Unmarshal(data, &[3]any{&c.Timestamp, &c.TakerSellVolume, &c.TakerBuyVolume})
|
||||
}
|
||||
|
||||
// ContractOpenInterestHistoryItem represents an open interest information for contract
|
||||
@@ -4944,8 +4925,7 @@ type ContractOpenInterestHistoryItem struct {
|
||||
|
||||
// UnmarshalJSON deserializes slice data into ContractOpenInterestHistoryItem instance
|
||||
func (c *ContractOpenInterestHistoryItem) UnmarshalJSON(data []byte) error {
|
||||
target := [4]any{&c.Timestamp, &c.OpenInterestInContract, &c.OpenInterestInCurrency, &c.OpenInterestInUSD}
|
||||
return json.Unmarshal(data, &target)
|
||||
return json.Unmarshal(data, &[4]any{&c.Timestamp, &c.OpenInterestInContract, &c.OpenInterestInCurrency, &c.OpenInterestInUSD})
|
||||
}
|
||||
|
||||
// TopTraderContractsLongShortRatio represents the timestamp and ratio information of top traders long and short accounts/positions
|
||||
@@ -4956,8 +4936,7 @@ type TopTraderContractsLongShortRatio struct {
|
||||
|
||||
// UnmarshalJSON deserializes slice data into TopTraderContractsLongShortRatio instance
|
||||
func (t *TopTraderContractsLongShortRatio) UnmarshalJSON(data []byte) error {
|
||||
target := [2]any{&t.Timestamp, &t.Ratio}
|
||||
return json.Unmarshal(data, &target)
|
||||
return json.Unmarshal(data, &[2]any{&t.Timestamp, &t.Ratio})
|
||||
}
|
||||
|
||||
// AccountInstrument represents an account instrument
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -46,7 +46,6 @@ func (ok *Okx) SetDefaults() {
|
||||
ok.Enabled = true
|
||||
ok.Verbose = true
|
||||
|
||||
ok.WsRequestSemaphore = make(chan int, 20)
|
||||
ok.API.CredentialsValidator.RequiresKey = true
|
||||
ok.API.CredentialsValidator.RequiresSecret = true
|
||||
ok.API.CredentialsValidator.RequiresClientID = true
|
||||
@@ -163,7 +162,7 @@ func (ok *Okx) SetDefaults() {
|
||||
}
|
||||
ok.Requester, err = request.New(ok.Name,
|
||||
common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout),
|
||||
request.WithLimiter(GetRateLimit()))
|
||||
request.WithLimiter(rateLimits))
|
||||
if err != nil {
|
||||
log.Errorln(log.ExchangeSys, err)
|
||||
}
|
||||
@@ -181,14 +180,6 @@ func (ok *Okx) SetDefaults() {
|
||||
ok.WebsocketResponseMaxLimit = websocketResponseMaxLimit
|
||||
ok.WebsocketResponseCheckTimeout = websocketResponseMaxLimit
|
||||
ok.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
|
||||
|
||||
ok.WsResponseMultiplexer = wsRequestDataChannelsMultiplexer{
|
||||
WsResponseChannelsMap: make(map[string]*wsRequestInfo),
|
||||
Register: make(chan *wsRequestInfo),
|
||||
Unregister: make(chan string),
|
||||
Message: make(chan *wsIncomingData),
|
||||
shutdown: make(chan bool),
|
||||
}
|
||||
}
|
||||
|
||||
// Setup takes in the supplied exchange configuration details and sets params
|
||||
@@ -218,46 +209,32 @@ func (ok *Okx) Setup(exch *config.Exchange) error {
|
||||
GenerateSubscriptions: ok.generateSubscriptions,
|
||||
Features: &ok.Features.Supports.WebsocketCapabilities,
|
||||
MaxWebsocketSubscriptionsPerConnection: 240,
|
||||
OrderbookBufferConfig: buffer.Config{
|
||||
Checksum: ok.CalculateUpdateOrderbookChecksum,
|
||||
},
|
||||
RateLimitDefinitions: ok.Requester.GetRateLimiterDefinitions(),
|
||||
OrderbookBufferConfig: buffer.Config{Checksum: ok.CalculateUpdateOrderbookChecksum},
|
||||
RateLimitDefinitions: rateLimits,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go ok.WsResponseMultiplexer.Run()
|
||||
|
||||
if err := ok.Websocket.SetupNewConnection(&websocket.ConnectionSetup{
|
||||
URL: apiWebsocketPublicURL,
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: websocketResponseMaxLimit,
|
||||
RateLimit: request.NewRateLimitWithWeight(time.Second, 2, 1),
|
||||
URL: apiWebsocketPublicURL,
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: websocketResponseMaxLimit,
|
||||
RateLimit: request.NewRateLimitWithWeight(time.Second, 2, 1),
|
||||
BespokeGenerateMessageID: func(bool) int64 { return ok.messageIDSeq.IncrementAndGet() },
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ok.Websocket.SetupNewConnection(&websocket.ConnectionSetup{
|
||||
URL: apiWebsocketPrivateURL,
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: websocketResponseMaxLimit,
|
||||
Authenticated: true,
|
||||
RateLimit: request.NewRateLimitWithWeight(time.Second, 2, 1),
|
||||
URL: apiWebsocketPrivateURL,
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: websocketResponseMaxLimit,
|
||||
Authenticated: true,
|
||||
RateLimit: request.NewRateLimitWithWeight(time.Second, 2, 1),
|
||||
BespokeGenerateMessageID: func(bool) int64 { return ok.messageIDSeq.IncrementAndGet() },
|
||||
})
|
||||
}
|
||||
|
||||
// Shutdown calls Base.Shutdown and then shuts down the response multiplexer
|
||||
func (ok *Okx) Shutdown() error {
|
||||
if err := ok.Base.Shutdown(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Must happen after the Websocket shutdown in Base.Shutdown, so there are no new blocking writes to the multiplexer
|
||||
ok.WsResponseMultiplexer.Shutdown()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetServerTime returns the current exchange server time.
|
||||
func (ok *Okx) GetServerTime(ctx context.Context, _ asset.Item) (time.Time, error) {
|
||||
t, err := ok.GetSystemTime(ctx)
|
||||
@@ -904,7 +881,7 @@ func (ok *Okx) SubmitOrder(ctx context.Context, s *order.Submit) (*order.SubmitR
|
||||
}
|
||||
var placeSpreadOrderResponse *SpreadOrderResponse
|
||||
if ok.Websocket.CanUseAuthenticatedWebsocketForWrapper() {
|
||||
placeSpreadOrderResponse, err = ok.WsPlaceSpreadOrder(ctx, spreadParam)
|
||||
placeSpreadOrderResponse, err = ok.WSPlaceSpreadOrder(ctx, spreadParam)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -925,16 +902,16 @@ func (ok *Okx) SubmitOrder(ctx context.Context, s *order.Submit) (*order.SubmitR
|
||||
switch orderTypeString {
|
||||
case orderLimit, orderMarket, orderPostOnly, orderFOK, orderIOC, orderOptimalLimitIOC, "mmp", "mmp_and_post_only":
|
||||
orderRequest := &PlaceOrderRequestParam{
|
||||
InstrumentID: pairString,
|
||||
TradeMode: tradeMode,
|
||||
Side: sideType,
|
||||
PositionSide: positionSide,
|
||||
OrderType: orderTypeString,
|
||||
Amount: amount,
|
||||
ClientOrderID: s.ClientOrderID,
|
||||
Price: s.Price,
|
||||
QuantityType: targetCurrency,
|
||||
AssetType: s.AssetType,
|
||||
InstrumentID: pairString,
|
||||
TradeMode: tradeMode,
|
||||
Side: sideType,
|
||||
PositionSide: positionSide,
|
||||
OrderType: orderTypeString,
|
||||
Amount: amount,
|
||||
ClientOrderID: s.ClientOrderID,
|
||||
Price: s.Price,
|
||||
TargetCurrency: targetCurrency,
|
||||
AssetType: s.AssetType,
|
||||
}
|
||||
switch s.Type.Lower() {
|
||||
case orderLimit, orderPostOnly, orderFOK, orderIOC:
|
||||
@@ -952,15 +929,12 @@ func (ok *Okx) SubmitOrder(ctx context.Context, s *order.Submit) (*order.SubmitR
|
||||
}
|
||||
}
|
||||
if ok.Websocket.CanUseAuthenticatedWebsocketForWrapper() {
|
||||
placeOrderResponse, err = ok.WsPlaceOrder(ctx, orderRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
placeOrderResponse, err = ok.WSPlaceOrder(ctx, orderRequest)
|
||||
} else {
|
||||
placeOrderResponse, err = ok.PlaceOrder(ctx, orderRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.DeriveSubmitResponse(placeOrderResponse.OrderID)
|
||||
case "trigger":
|
||||
@@ -1130,7 +1104,7 @@ func (ok *Okx) ModifyOrder(ctx context.Context, action *order.Modify) (*order.Mo
|
||||
NewPrice: action.Price,
|
||||
}
|
||||
if ok.Websocket.CanUseAuthenticatedWebsocketForWrapper() {
|
||||
_, err = ok.WsAmandSpreadOrder(ctx, amendSpreadOrder)
|
||||
_, err = ok.WSAmendSpreadOrder(ctx, amendSpreadOrder)
|
||||
} else {
|
||||
_, err = ok.AmendSpreadOrder(ctx, amendSpreadOrder)
|
||||
}
|
||||
@@ -1158,7 +1132,7 @@ func (ok *Okx) ModifyOrder(ctx context.Context, action *order.Modify) (*order.Mo
|
||||
ClientOrderID: action.ClientOrderID,
|
||||
}
|
||||
if ok.Websocket.CanUseAuthenticatedWebsocketForWrapper() {
|
||||
_, err = ok.WsAmendOrder(ctx, &amendRequest)
|
||||
_, err = ok.WSAmendOrder(ctx, &amendRequest)
|
||||
} else {
|
||||
_, err = ok.AmendOrder(ctx, &amendRequest)
|
||||
}
|
||||
@@ -1239,7 +1213,7 @@ func (ok *Okx) CancelOrder(ctx context.Context, ord *order.Cancel) error {
|
||||
var err error
|
||||
if ord.AssetType == asset.Spread {
|
||||
if ok.Websocket.CanUseAuthenticatedWebsocketForWrapper() {
|
||||
_, err = ok.WsCancelSpreadOrder(ctx, ord.OrderID, ord.ClientOrderID)
|
||||
_, err = ok.WSCancelSpreadOrder(ctx, ord.OrderID, ord.ClientOrderID)
|
||||
} else {
|
||||
_, err = ok.CancelSpreadOrder(ctx, ord.OrderID, ord.ClientOrderID)
|
||||
}
|
||||
@@ -1262,12 +1236,11 @@ func (ok *Okx) CancelOrder(ctx context.Context, ord *order.Cancel) error {
|
||||
ClientOrderID: ord.ClientOrderID,
|
||||
}
|
||||
if ok.Websocket.CanUseAuthenticatedWebsocketForWrapper() {
|
||||
_, err = ok.WsCancelOrder(ctx, &req)
|
||||
_, err = ok.WSCancelOrder(ctx, &req)
|
||||
} else {
|
||||
_, err = ok.CancelSingleOrder(ctx, &req)
|
||||
}
|
||||
case order.Trigger, order.OCO, order.ConditionalStop,
|
||||
order.TWAP, order.TrailingStop, order.Chase:
|
||||
case order.Trigger, order.OCO, order.ConditionalStop, order.TWAP, order.TrailingStop, order.Chase:
|
||||
var response *AlgoOrder
|
||||
response, err = ok.CancelAdvanceAlgoOrder(ctx, []AlgoOrderCancelParams{
|
||||
{
|
||||
@@ -1278,10 +1251,7 @@ func (ok *Okx) CancelOrder(ctx context.Context, ord *order.Cancel) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if response.StatusCode != "0" {
|
||||
return fmt.Errorf("sCode: %s sMessage: %s", response.StatusCode, response.StatusMessage)
|
||||
}
|
||||
return nil
|
||||
return getStatusError(response.StatusCode, response.StatusMessage)
|
||||
default:
|
||||
return fmt.Errorf("%w, order type %v", order.ErrUnsupportedOrderType, ord.Type)
|
||||
}
|
||||
@@ -1337,9 +1307,9 @@ func (ok *Okx) CancelBatchOrders(ctx context.Context, o []order.Cancel) (*order.
|
||||
}
|
||||
resp := &order.CancelBatchResponse{Status: make(map[string]string)}
|
||||
if len(cancelOrderParams) > 0 {
|
||||
var canceledOrders []OrderData
|
||||
var canceledOrders []*OrderData
|
||||
if ok.Websocket.CanUseAuthenticatedWebsocketForWrapper() {
|
||||
canceledOrders, err = ok.WsCancelMultipleOrder(ctx, cancelOrderParams)
|
||||
canceledOrders, err = ok.WSCancelMultipleOrders(ctx, cancelOrderParams)
|
||||
} else {
|
||||
canceledOrders, err = ok.CancelMultipleOrders(ctx, cancelOrderParams)
|
||||
}
|
||||
@@ -1348,7 +1318,7 @@ func (ok *Okx) CancelBatchOrders(ctx context.Context, o []order.Cancel) (*order.
|
||||
}
|
||||
for x := range canceledOrders {
|
||||
resp.Status[canceledOrders[x].OrderID] = func() string {
|
||||
if canceledOrders[x].StatusCode != "0" && canceledOrders[x].StatusCode != "2" {
|
||||
if canceledOrders[x].StatusCode != 0 {
|
||||
return ""
|
||||
}
|
||||
return order.Cancelled.String()
|
||||
@@ -1362,11 +1332,11 @@ func (ok *Okx) CancelBatchOrders(ctx context.Context, o []order.Cancel) (*order.
|
||||
return resp, nil
|
||||
}
|
||||
return nil, err
|
||||
} else if cancelationResponse.StatusCode != "0" {
|
||||
} else if cancelationResponse.StatusCode != 0 {
|
||||
if len(resp.Status) > 0 {
|
||||
return resp, nil
|
||||
}
|
||||
return resp, fmt.Errorf("sCode: %s sMessage: %s", cancelationResponse.StatusCode, cancelationResponse.StatusMessage)
|
||||
return resp, getStatusError(cancelationResponse.StatusCode, cancelationResponse.StatusMessage)
|
||||
}
|
||||
for x := range cancelAlgoOrderParams {
|
||||
resp.Status[cancelAlgoOrderParams[x].AlgoOrderID] = order.Cancelled.String()
|
||||
@@ -1454,17 +1424,17 @@ ordersLoop:
|
||||
remaining := cancelAllOrdersRequestParams
|
||||
loop := int(math.Ceil(float64(len(remaining)) / 20.0))
|
||||
for range loop {
|
||||
var response []OrderData
|
||||
var response []*OrderData
|
||||
if len(remaining) > 20 {
|
||||
if ok.Websocket.CanUseAuthenticatedWebsocketForWrapper() {
|
||||
response, err = ok.WsCancelMultipleOrder(ctx, remaining[:20])
|
||||
response, err = ok.WSCancelMultipleOrders(ctx, remaining[:20])
|
||||
} else {
|
||||
response, err = ok.CancelMultipleOrders(ctx, remaining[:20])
|
||||
}
|
||||
remaining = remaining[20:]
|
||||
} else {
|
||||
if ok.Websocket.CanUseAuthenticatedWebsocketForWrapper() {
|
||||
response, err = ok.WsCancelMultipleOrder(ctx, remaining)
|
||||
response, err = ok.WSCancelMultipleOrders(ctx, remaining)
|
||||
} else {
|
||||
response, err = ok.CancelMultipleOrders(ctx, remaining)
|
||||
}
|
||||
@@ -1475,7 +1445,7 @@ ordersLoop:
|
||||
}
|
||||
}
|
||||
for y := range response {
|
||||
if response[y].StatusCode == "0" {
|
||||
if response[y].StatusCode == 0 {
|
||||
cancelAllResponse.Status[response[y].OrderID] = order.Cancelled.String()
|
||||
} else {
|
||||
cancelAllResponse.Status[response[y].OrderID] = response[y].StatusMessage
|
||||
@@ -3020,28 +2990,3 @@ func (ok *Okx) GetCurrencyTradeURL(ctx context.Context, a asset.Item, cp currenc
|
||||
return "", fmt.Errorf("%w %v", asset.ErrNotSupported, a)
|
||||
}
|
||||
}
|
||||
|
||||
func (ok *Okx) underlyingFromInstID(instrumentType, instID string) (string, error) {
|
||||
ok.instrumentsInfoMapLock.Lock()
|
||||
defer ok.instrumentsInfoMapLock.Unlock()
|
||||
if instrumentType != "" {
|
||||
insts, okay := ok.instrumentsInfoMap[instrumentType]
|
||||
if !okay {
|
||||
return "", errInvalidInstrumentType
|
||||
}
|
||||
for a := range insts {
|
||||
if insts[a].InstrumentID == instID {
|
||||
return insts[a].Underlying, nil
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, insts := range ok.instrumentsInfoMap {
|
||||
for a := range insts {
|
||||
if insts[a].InstrumentID == instID {
|
||||
return insts[a].Underlying, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("underlying not found for instrument %s", instID)
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ const (
|
||||
getOneClickRepayHistoryEPL
|
||||
oneClickRepayCurrencyListEPL
|
||||
tradeOneClickRepayEPL
|
||||
massCancemMMPOrderEPL
|
||||
massCancelMMPOrderEPL
|
||||
getCounterpartiesEPL
|
||||
createRFQEPL
|
||||
cancelRFQEPL
|
||||
@@ -323,8 +323,7 @@ const (
|
||||
getFiatDepositPaymentMethodsEPL
|
||||
)
|
||||
|
||||
// GetRateLimit returns a RateLimit instance, which implements the request.Limiter interface.
|
||||
func GetRateLimit() request.RateLimitDefinitions {
|
||||
var rateLimits = func() request.RateLimitDefinitions {
|
||||
return request.RateLimitDefinitions{
|
||||
// Trade Endpoints
|
||||
placeOrderEPL: request.NewRateLimitWithWeight(twoSecondsInterval, 60, 1),
|
||||
@@ -358,7 +357,7 @@ func GetRateLimit() request.RateLimitDefinitions {
|
||||
getOneClickRepayHistoryEPL: request.NewRateLimitWithWeight(twoSecondsInterval, 1, 1),
|
||||
oneClickRepayCurrencyListEPL: request.NewRateLimitWithWeight(twoSecondsInterval, 1, 1),
|
||||
tradeOneClickRepayEPL: request.NewRateLimitWithWeight(twoSecondsInterval, 1, 1),
|
||||
massCancemMMPOrderEPL: request.NewRateLimitWithWeight(twoSecondsInterval, 5, 1),
|
||||
massCancelMMPOrderEPL: request.NewRateLimitWithWeight(twoSecondsInterval, 5, 1),
|
||||
|
||||
// Block Trading endpoints
|
||||
getCounterpartiesEPL: request.NewRateLimitWithWeight(twoSecondsInterval, 5, 1),
|
||||
@@ -661,4 +660,4 @@ func GetRateLimit() request.RateLimitDefinitions {
|
||||
getWithdrawalPaymentMethodsEPL: request.NewRateLimitWithWeight(oneSecondInterval, 3, 1),
|
||||
getFiatDepositPaymentMethodsEPL: request.NewRateLimitWithWeight(oneSecondInterval, 3, 1),
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -40,7 +40,7 @@ func TestRateLimit_LimitStatic(t *testing.T) {
|
||||
"getOneClickRepayHistory": getOneClickRepayHistoryEPL,
|
||||
"oneClickRepayCurrencyList": oneClickRepayCurrencyListEPL,
|
||||
"tradeOneClickRepay": tradeOneClickRepayEPL,
|
||||
"massCancemMMPOrder": massCancemMMPOrderEPL,
|
||||
"massCancelMMPOrder": massCancelMMPOrderEPL,
|
||||
"getCounterparties": getCounterpartiesEPL,
|
||||
"createRFQ": createRFQEPL,
|
||||
"cancelRFQ": cancelRFQEPL,
|
||||
@@ -267,7 +267,7 @@ func TestRateLimit_LimitStatic(t *testing.T) {
|
||||
"getUserAffilateRebateInformation": getUserAffiliateRebateInformationEPL,
|
||||
}
|
||||
|
||||
rl, err := request.New("RateLimit_Static", http.DefaultClient, request.WithLimiter(GetRateLimit()))
|
||||
rl, err := request.New("RateLimit_Static", http.DefaultClient, request.WithLimiter(rateLimits))
|
||||
require.NoError(t, err)
|
||||
|
||||
for name, tt := range testTable {
|
||||
|
||||
344
exchanges/okx/ws_requests.go
Normal file
344
exchanges/okx/ws_requests.go
Normal file
@@ -0,0 +1,344 @@
|
||||
package okx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/encoding/json"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
)
|
||||
|
||||
var (
|
||||
errInvalidWebsocketRequest = errors.New("invalid websocket request")
|
||||
errOperationFailed = errors.New("operation failed")
|
||||
errPartialSuccess = errors.New("bulk operation partially succeeded")
|
||||
errMassCancelFailed = errors.New("mass cancel failed")
|
||||
errCancelAllSpreadOrdersFailed = errors.New("cancel all spread orders failed")
|
||||
errMultipleItemsReturned = errors.New("multiple items returned")
|
||||
)
|
||||
|
||||
// WSPlaceOrder submits an order
|
||||
func (ok *Okx) WSPlaceOrder(ctx context.Context, arg *PlaceOrderRequestParam) (*OrderData, error) {
|
||||
if err := arg.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
id := strconv.FormatInt(ok.Websocket.AuthConn.GenerateMessageID(false), 10)
|
||||
|
||||
var resp []*OrderData
|
||||
if err := ok.SendAuthenticatedWebsocketRequest(ctx, placeOrderEPL, id, "order", []PlaceOrderRequestParam{*arg}, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return singleItem(resp)
|
||||
}
|
||||
|
||||
// WSPlaceMultipleOrders submits multiple orders
|
||||
func (ok *Okx) WSPlaceMultipleOrders(ctx context.Context, args []PlaceOrderRequestParam) ([]*OrderData, error) {
|
||||
if len(args) == 0 {
|
||||
return nil, fmt.Errorf("%T: %w", args, order.ErrSubmissionIsNil)
|
||||
}
|
||||
|
||||
for i := range args {
|
||||
if err := args[i].Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
id := strconv.FormatInt(ok.Websocket.AuthConn.GenerateMessageID(false), 10)
|
||||
|
||||
var resp []*OrderData
|
||||
return resp, ok.SendAuthenticatedWebsocketRequest(ctx, placeMultipleOrdersEPL, id, "batch-orders", args, &resp)
|
||||
}
|
||||
|
||||
// WSCancelOrder cancels an order
|
||||
func (ok *Okx) WSCancelOrder(ctx context.Context, arg *CancelOrderRequestParam) (*OrderData, error) {
|
||||
if arg == nil {
|
||||
return nil, fmt.Errorf("%T: %w", arg, common.ErrNilPointer)
|
||||
}
|
||||
if arg.InstrumentID == "" {
|
||||
return nil, errMissingInstrumentID
|
||||
}
|
||||
if arg.OrderID == "" && arg.ClientOrderID == "" {
|
||||
return nil, order.ErrOrderIDNotSet
|
||||
}
|
||||
|
||||
id := strconv.FormatInt(ok.Websocket.AuthConn.GenerateMessageID(false), 10)
|
||||
|
||||
var resp []*OrderData
|
||||
if err := ok.SendAuthenticatedWebsocketRequest(ctx, cancelOrderEPL, id, "cancel-order", []CancelOrderRequestParam{*arg}, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return singleItem(resp)
|
||||
}
|
||||
|
||||
// WSCancelMultipleOrders cancels multiple orders
|
||||
func (ok *Okx) WSCancelMultipleOrders(ctx context.Context, args []CancelOrderRequestParam) ([]*OrderData, error) {
|
||||
if len(args) == 0 {
|
||||
return nil, fmt.Errorf("%T: %w", args, order.ErrSubmissionIsNil)
|
||||
}
|
||||
|
||||
for i := range args {
|
||||
if args[i].InstrumentID == "" {
|
||||
return nil, errMissingInstrumentID
|
||||
}
|
||||
if args[i].OrderID == "" && args[i].ClientOrderID == "" {
|
||||
return nil, order.ErrOrderIDNotSet
|
||||
}
|
||||
}
|
||||
|
||||
id := strconv.FormatInt(ok.Websocket.AuthConn.GenerateMessageID(false), 10)
|
||||
|
||||
var resp []*OrderData
|
||||
return resp, ok.SendAuthenticatedWebsocketRequest(ctx, cancelMultipleOrdersEPL, id, "batch-cancel-orders", args, &resp)
|
||||
}
|
||||
|
||||
// WSAmendOrder amends an order
|
||||
func (ok *Okx) WSAmendOrder(ctx context.Context, arg *AmendOrderRequestParams) (*OrderData, error) {
|
||||
if arg == nil {
|
||||
return nil, fmt.Errorf("%T: %w", arg, common.ErrNilPointer)
|
||||
}
|
||||
if arg.InstrumentID == "" {
|
||||
return nil, errMissingInstrumentID
|
||||
}
|
||||
if arg.ClientOrderID == "" && arg.OrderID == "" {
|
||||
return nil, order.ErrOrderIDNotSet
|
||||
}
|
||||
if arg.NewQuantity <= 0 && arg.NewPrice <= 0 {
|
||||
return nil, errInvalidNewSizeOrPriceInformation
|
||||
}
|
||||
|
||||
id := strconv.FormatInt(ok.Websocket.AuthConn.GenerateMessageID(false), 10)
|
||||
|
||||
var resp []*OrderData
|
||||
if err := ok.SendAuthenticatedWebsocketRequest(ctx, amendOrderEPL, id, "amend-order", []AmendOrderRequestParams{*arg}, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return singleItem(resp)
|
||||
}
|
||||
|
||||
// WSAmendMultipleOrders amends multiple orders
|
||||
func (ok *Okx) WSAmendMultipleOrders(ctx context.Context, args []AmendOrderRequestParams) ([]*OrderData, error) {
|
||||
if len(args) == 0 {
|
||||
return nil, fmt.Errorf("%T: %w", args, order.ErrSubmissionIsNil)
|
||||
}
|
||||
|
||||
for x := range args {
|
||||
if args[x].InstrumentID == "" {
|
||||
return nil, errMissingInstrumentID
|
||||
}
|
||||
if args[x].ClientOrderID == "" && args[x].OrderID == "" {
|
||||
return nil, order.ErrOrderIDNotSet
|
||||
}
|
||||
if args[x].NewQuantity <= 0 && args[x].NewPrice <= 0 {
|
||||
return nil, errInvalidNewSizeOrPriceInformation
|
||||
}
|
||||
}
|
||||
|
||||
id := strconv.FormatInt(ok.Websocket.AuthConn.GenerateMessageID(false), 10)
|
||||
|
||||
var resp []*OrderData
|
||||
return resp, ok.SendAuthenticatedWebsocketRequest(ctx, amendMultipleOrdersEPL, id, "batch-amend-orders", args, &resp)
|
||||
}
|
||||
|
||||
// WSMassCancelOrders cancels all MMP pending orders of an instrument family. Only applicable to Option in Portfolio Margin mode, and MMP privilege is required.
|
||||
func (ok *Okx) WSMassCancelOrders(ctx context.Context, args []CancelMassReqParam) error {
|
||||
if len(args) == 0 {
|
||||
return fmt.Errorf("%T: %w", args, order.ErrSubmissionIsNil)
|
||||
}
|
||||
|
||||
for x := range args {
|
||||
if args[x].InstrumentType == "" {
|
||||
return fmt.Errorf("%w, instrument type can not be empty", errInvalidInstrumentType)
|
||||
}
|
||||
if args[x].InstrumentFamily == "" {
|
||||
return errInstrumentFamilyRequired
|
||||
}
|
||||
}
|
||||
|
||||
id := strconv.FormatInt(ok.Websocket.AuthConn.GenerateMessageID(false), 10)
|
||||
|
||||
var resps []*struct {
|
||||
Result bool `json:"result"`
|
||||
}
|
||||
if err := ok.SendAuthenticatedWebsocketRequest(ctx, amendOrderEPL, id, "mass-cancel", args, &resps); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := singleItem(resps)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !resp.Result {
|
||||
return errMassCancelFailed
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WSPlaceSpreadOrder submits a spread order
|
||||
func (ok *Okx) WSPlaceSpreadOrder(ctx context.Context, arg *SpreadOrderParam) (*SpreadOrderResponse, error) {
|
||||
if err := arg.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
id := strconv.FormatInt(ok.Websocket.AuthConn.GenerateMessageID(false), 10)
|
||||
|
||||
var resp []*SpreadOrderResponse
|
||||
if err := ok.SendAuthenticatedWebsocketRequest(ctx, placeSpreadOrderEPL, id, "sprd-order", []SpreadOrderParam{*arg}, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return singleItem(resp)
|
||||
}
|
||||
|
||||
// WSAmendSpreadOrder amends a spread order
|
||||
func (ok *Okx) WSAmendSpreadOrder(ctx context.Context, arg *AmendSpreadOrderParam) (*SpreadOrderResponse, error) {
|
||||
if arg == nil {
|
||||
return nil, fmt.Errorf("%T: %w", arg, common.ErrNilPointer)
|
||||
}
|
||||
if arg.OrderID == "" && arg.ClientOrderID == "" {
|
||||
return nil, order.ErrOrderIDNotSet
|
||||
}
|
||||
if arg.NewPrice == 0 && arg.NewSize == 0 {
|
||||
return nil, errSizeOrPriceIsRequired
|
||||
}
|
||||
|
||||
id := strconv.FormatInt(ok.Websocket.AuthConn.GenerateMessageID(false), 10)
|
||||
|
||||
var resp []*SpreadOrderResponse
|
||||
if err := ok.SendAuthenticatedWebsocketRequest(ctx, amendSpreadOrderEPL, id, "sprd-amend-order", []AmendSpreadOrderParam{*arg}, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return singleItem(resp)
|
||||
}
|
||||
|
||||
// WSCancelSpreadOrder cancels an incomplete spread order through the websocket connection.
|
||||
func (ok *Okx) WSCancelSpreadOrder(ctx context.Context, orderID, clientOrderID string) (*SpreadOrderResponse, error) {
|
||||
if orderID == "" && clientOrderID == "" {
|
||||
return nil, order.ErrOrderIDNotSet
|
||||
}
|
||||
|
||||
arg := make(map[string]string)
|
||||
if orderID != "" {
|
||||
arg["ordId"] = orderID
|
||||
}
|
||||
if clientOrderID != "" {
|
||||
arg["clOrdId"] = clientOrderID
|
||||
}
|
||||
|
||||
id := strconv.FormatInt(ok.Websocket.AuthConn.GenerateMessageID(false), 10)
|
||||
|
||||
var resp []*SpreadOrderResponse
|
||||
if err := ok.SendAuthenticatedWebsocketRequest(ctx, cancelSpreadOrderEPL, id, "sprd-cancel-order", []map[string]string{arg}, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return singleItem(resp)
|
||||
}
|
||||
|
||||
// WSCancelAllSpreadOrders cancels all spread orders and return success message through the websocket channel.
|
||||
func (ok *Okx) WSCancelAllSpreadOrders(ctx context.Context, spreadID string) error {
|
||||
arg := make(map[string]string, 1)
|
||||
if spreadID != "" {
|
||||
arg["sprdId"] = spreadID
|
||||
}
|
||||
|
||||
id := strconv.FormatInt(ok.Websocket.AuthConn.GenerateMessageID(false), 10)
|
||||
|
||||
var resps []*ResponseResult
|
||||
if err := ok.SendAuthenticatedWebsocketRequest(ctx, cancelAllSpreadOrderEPL, id, "sprd-mass-cancel", []map[string]string{arg}, &resps); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := singleItem(resps)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !resp.Result {
|
||||
return errCancelAllSpreadOrdersFailed
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendAuthenticatedWebsocketRequest sends a websocket request to the server
|
||||
func (ok *Okx) SendAuthenticatedWebsocketRequest(ctx context.Context, epl request.EndpointLimit, id, operation string, payload, result any) error {
|
||||
if operation == "" || payload == nil {
|
||||
return errInvalidWebsocketRequest
|
||||
}
|
||||
|
||||
outbound := &struct {
|
||||
ID string `json:"id"`
|
||||
Operation string `json:"op"`
|
||||
Arguments any `json:"args"`
|
||||
// TODO: Add ExpTime to the struct, the struct should look like this:
|
||||
// ExpTime string `json:"expTime,omitempty"` so a deadline can be set
|
||||
// Request effective deadline. Unix timestamp format in milliseconds, e.g. 1597026383085
|
||||
}{
|
||||
ID: id,
|
||||
Operation: operation,
|
||||
Arguments: payload,
|
||||
}
|
||||
|
||||
incoming, err := ok.Websocket.AuthConn.SendMessageReturnResponse(ctx, epl, id, outbound)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
intermediary := struct {
|
||||
ID string `json:"id"`
|
||||
Operation string `json:"op"`
|
||||
Code int64 `json:"code,string"`
|
||||
Message string `json:"msg"`
|
||||
Data any `json:"data"`
|
||||
InTime string `json:"inTime"`
|
||||
OutTime string `json:"outTime"`
|
||||
}{
|
||||
Data: result,
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(incoming, &intermediary); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch intermediary.Code {
|
||||
case 0:
|
||||
return nil
|
||||
case 1:
|
||||
return parseWSResponseErrors(result, errOperationFailed)
|
||||
case 2:
|
||||
return parseWSResponseErrors(result, errPartialSuccess)
|
||||
default:
|
||||
return getStatusError(intermediary.Code, intermediary.Message)
|
||||
}
|
||||
}
|
||||
|
||||
func parseWSResponseErrors(result any, err error) error {
|
||||
s := reflect.ValueOf(result).Elem()
|
||||
for i := range s.Len() {
|
||||
v := s.Index(i)
|
||||
if subErr, ok := v.Interface().(interface{ Error() error }); ok && subErr.Error() != nil {
|
||||
err = common.AppendError(err, fmt.Errorf("%s[%d]: %w", v.Type(), i+1, subErr.Error()))
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func singleItem[T any](resp []*T) (*T, error) {
|
||||
if len(resp) == 0 {
|
||||
return nil, common.ErrNoResponse
|
||||
}
|
||||
if len(resp) > 1 {
|
||||
return nil, fmt.Errorf("%w, received %d", errMultipleItemsReturned, len(resp))
|
||||
}
|
||||
return resp[0], nil
|
||||
}
|
||||
266
exchanges/okx/ws_requests_test.go
Normal file
266
exchanges/okx/ws_requests_test.go
Normal file
@@ -0,0 +1,266 @@
|
||||
package okx
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
|
||||
)
|
||||
|
||||
func TestWSPlaceOrder(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := ok.WSPlaceOrder(t.Context(), nil)
|
||||
require.ErrorIs(t, err, common.ErrNilPointer)
|
||||
|
||||
sharedtestvalues.SkipTestIfCredentialsUnset(t, ok, canManipulateRealOrders)
|
||||
|
||||
out := &PlaceOrderRequestParam{
|
||||
InstrumentID: btcusdt,
|
||||
TradeMode: TradeModeIsolated, // depending on portfolio settings this can also be TradeModeCash
|
||||
Side: "Buy",
|
||||
OrderType: "post_only",
|
||||
Amount: 0.0001,
|
||||
Price: 20000,
|
||||
Currency: "USDT",
|
||||
}
|
||||
|
||||
got, err := ok.WSPlaceOrder(request.WithVerbose(t.Context()), out)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, got)
|
||||
}
|
||||
|
||||
func TestWSPlaceMultipleOrders(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := ok.WSPlaceMultipleOrders(t.Context(), nil)
|
||||
require.ErrorIs(t, err, order.ErrSubmissionIsNil)
|
||||
|
||||
_, err = ok.WSPlaceMultipleOrders(t.Context(), []PlaceOrderRequestParam{{}})
|
||||
require.ErrorIs(t, err, errMissingInstrumentID)
|
||||
|
||||
sharedtestvalues.SkipTestIfCredentialsUnset(t, ok, canManipulateRealOrders)
|
||||
|
||||
out := PlaceOrderRequestParam{
|
||||
InstrumentID: btcusdt,
|
||||
TradeMode: TradeModeIsolated, // depending on portfolio settings this can also be TradeModeCash
|
||||
Side: "Buy",
|
||||
OrderType: "post_only",
|
||||
Amount: 0.0001,
|
||||
Price: 20000,
|
||||
Currency: "USDT",
|
||||
}
|
||||
|
||||
got, err := ok.WSPlaceMultipleOrders(request.WithVerbose(t.Context()), []PlaceOrderRequestParam{out})
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, got)
|
||||
}
|
||||
|
||||
func TestWSCancelOrder(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := ok.WSCancelOrder(t.Context(), nil)
|
||||
require.ErrorIs(t, err, common.ErrNilPointer)
|
||||
|
||||
_, err = ok.WSCancelOrder(t.Context(), &CancelOrderRequestParam{})
|
||||
require.ErrorIs(t, err, errMissingInstrumentID)
|
||||
|
||||
_, err = ok.WSCancelOrder(t.Context(), &CancelOrderRequestParam{InstrumentID: btcusdt})
|
||||
require.ErrorIs(t, err, order.ErrOrderIDNotSet)
|
||||
|
||||
sharedtestvalues.SkipTestIfCredentialsUnset(t, ok, canManipulateRealOrders)
|
||||
|
||||
got, err := ok.WSCancelOrder(request.WithVerbose(t.Context()), &CancelOrderRequestParam{InstrumentID: btcusdt, OrderID: "2341161427393388544"})
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, got)
|
||||
}
|
||||
|
||||
func TestWSCancelMultipleOrders(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := ok.WSCancelMultipleOrders(t.Context(), nil)
|
||||
require.ErrorIs(t, err, order.ErrSubmissionIsNil)
|
||||
|
||||
_, err = ok.WSCancelMultipleOrders(t.Context(), []CancelOrderRequestParam{{}})
|
||||
require.ErrorIs(t, err, errMissingInstrumentID)
|
||||
|
||||
_, err = ok.WSCancelMultipleOrders(t.Context(), []CancelOrderRequestParam{{InstrumentID: btcusdt}})
|
||||
require.ErrorIs(t, err, order.ErrOrderIDNotSet)
|
||||
|
||||
sharedtestvalues.SkipTestIfCredentialsUnset(t, ok, canManipulateRealOrders)
|
||||
|
||||
got, err := ok.WSCancelMultipleOrders(request.WithVerbose(t.Context()), []CancelOrderRequestParam{{InstrumentID: btcusdt, OrderID: "2341184920998715392"}})
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, got)
|
||||
}
|
||||
|
||||
func TestWSAmendOrder(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := ok.WSAmendOrder(t.Context(), nil)
|
||||
require.ErrorIs(t, err, common.ErrNilPointer)
|
||||
|
||||
out := &AmendOrderRequestParams{}
|
||||
_, err = ok.WSAmendOrder(t.Context(), out)
|
||||
require.ErrorIs(t, err, errMissingInstrumentID)
|
||||
|
||||
out.InstrumentID = btcusdt
|
||||
_, err = ok.WSAmendOrder(t.Context(), out)
|
||||
require.ErrorIs(t, err, order.ErrOrderIDNotSet)
|
||||
|
||||
out.OrderID = "2341200629875154944"
|
||||
_, err = ok.WSAmendOrder(t.Context(), out)
|
||||
require.ErrorIs(t, err, errInvalidNewSizeOrPriceInformation)
|
||||
|
||||
sharedtestvalues.SkipTestIfCredentialsUnset(t, ok, canManipulateRealOrders)
|
||||
|
||||
out.NewPrice = 21000
|
||||
got, err := ok.WSAmendOrder(request.WithVerbose(t.Context()), out)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, got)
|
||||
}
|
||||
|
||||
func TestWSAmendMultipleOrders(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := ok.WSAmendMultipleOrders(t.Context(), nil)
|
||||
require.ErrorIs(t, err, order.ErrSubmissionIsNil)
|
||||
|
||||
out := AmendOrderRequestParams{}
|
||||
_, err = ok.WSAmendMultipleOrders(t.Context(), []AmendOrderRequestParams{out})
|
||||
require.ErrorIs(t, err, errMissingInstrumentID)
|
||||
|
||||
out.InstrumentID = btcusdt
|
||||
_, err = ok.WSAmendMultipleOrders(t.Context(), []AmendOrderRequestParams{out})
|
||||
require.ErrorIs(t, err, order.ErrOrderIDNotSet)
|
||||
|
||||
out.OrderID = "2341200629875154944"
|
||||
_, err = ok.WSAmendMultipleOrders(t.Context(), []AmendOrderRequestParams{out})
|
||||
require.ErrorIs(t, err, errInvalidNewSizeOrPriceInformation)
|
||||
|
||||
sharedtestvalues.SkipTestIfCredentialsUnset(t, ok, canManipulateRealOrders)
|
||||
out.NewPrice = 20000
|
||||
|
||||
got, err := ok.WSAmendMultipleOrders(request.WithVerbose(t.Context()), []AmendOrderRequestParams{out})
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, got)
|
||||
}
|
||||
|
||||
func TestWSMassCancelOrders(t *testing.T) {
|
||||
t.Parallel()
|
||||
err := ok.WSMassCancelOrders(t.Context(), nil)
|
||||
require.ErrorIs(t, err, order.ErrSubmissionIsNil)
|
||||
|
||||
err = ok.WSMassCancelOrders(t.Context(), []CancelMassReqParam{{}})
|
||||
require.ErrorIs(t, err, errInvalidInstrumentType)
|
||||
|
||||
err = ok.WSMassCancelOrders(t.Context(), []CancelMassReqParam{{InstrumentType: "OPTION"}})
|
||||
require.ErrorIs(t, err, errInstrumentFamilyRequired)
|
||||
|
||||
sharedtestvalues.SkipTestIfCredentialsUnset(t, ok, canManipulateRealOrders)
|
||||
err = ok.WSMassCancelOrders(request.WithVerbose(t.Context()), []CancelMassReqParam{
|
||||
{
|
||||
InstrumentType: "OPTION",
|
||||
InstrumentFamily: "BTC-USD",
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestWSPlaceSpreadOrder(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := ok.WSPlaceSpreadOrder(t.Context(), nil)
|
||||
require.ErrorIs(t, err, common.ErrNilPointer)
|
||||
|
||||
sharedtestvalues.SkipTestIfCredentialsUnset(t, ok, canManipulateRealOrders)
|
||||
result, err := ok.WSPlaceSpreadOrder(request.WithVerbose(t.Context()), &SpreadOrderParam{
|
||||
SpreadID: "BTC-USDT_BTC-USDT-SWAP",
|
||||
ClientOrderID: "b15",
|
||||
Side: order.Buy.Lower(),
|
||||
OrderType: "limit",
|
||||
Price: 2.15,
|
||||
Size: 2,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, result)
|
||||
}
|
||||
|
||||
func TestWSAmendSpreadOrder(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := ok.WSAmendSpreadOrder(t.Context(), nil)
|
||||
require.ErrorIs(t, err, common.ErrNilPointer)
|
||||
_, err = ok.WSAmendSpreadOrder(t.Context(), &AmendSpreadOrderParam{NewSize: 2})
|
||||
require.ErrorIs(t, err, order.ErrOrderIDNotSet)
|
||||
_, err = ok.WSAmendSpreadOrder(t.Context(), &AmendSpreadOrderParam{OrderID: "2510789768709120"})
|
||||
require.ErrorIs(t, err, errSizeOrPriceIsRequired)
|
||||
|
||||
sharedtestvalues.SkipTestIfCredentialsUnset(t, ok, canManipulateRealOrders)
|
||||
result, err := ok.WSAmendSpreadOrder(request.WithVerbose(t.Context()), &AmendSpreadOrderParam{
|
||||
OrderID: "2510789768709120",
|
||||
NewSize: 2,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, result)
|
||||
}
|
||||
|
||||
func TestWSCancelSpreadOrder(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := ok.WSCancelSpreadOrder(t.Context(), "", "")
|
||||
require.ErrorIs(t, err, order.ErrOrderIDNotSet)
|
||||
sharedtestvalues.SkipTestIfCredentialsUnset(t, ok, canManipulateRealOrders)
|
||||
result, err := ok.WSCancelSpreadOrder(request.WithVerbose(t.Context()), "1234", "")
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, result)
|
||||
}
|
||||
|
||||
func TestWSCancelAllSpreadOrders(t *testing.T) {
|
||||
t.Parallel()
|
||||
sharedtestvalues.SkipTestIfCredentialsUnset(t, ok, canManipulateRealOrders)
|
||||
err := ok.WSCancelAllSpreadOrders(request.WithVerbose(t.Context()), "BTC-USDT_BTC-USDT-SWAP")
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
type mockHasError struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (m *mockHasError) Error() error {
|
||||
return m.err
|
||||
}
|
||||
|
||||
func TestParseWSResponseErrors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
require.Panics(t, func() { _ = parseWSResponseErrors(123, nil) }, "result must be a pointer")
|
||||
require.Panics(t, func() { _ = parseWSResponseErrors(&mockHasError{}, nil) }, "result must be a slice")
|
||||
|
||||
var emptySlice []*mockHasError
|
||||
require.NoError(t, parseWSResponseErrors(&emptySlice, nil))
|
||||
require.ErrorIs(t, parseWSResponseErrors(&emptySlice, errOperationFailed), errOperationFailed)
|
||||
|
||||
err1 := errors.New("error 1")
|
||||
err2 := errors.New("error 2")
|
||||
mockSlice := []*mockHasError{{err: nil}, {err: err1}, {err: err2}}
|
||||
err := parseWSResponseErrors(&mockSlice, errPartialSuccess)
|
||||
require.ErrorIs(t, err, errPartialSuccess)
|
||||
require.ErrorIs(t, err, err1)
|
||||
require.ErrorIs(t, err, err2)
|
||||
}
|
||||
|
||||
func TestSingleItem(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := singleItem([]*any(nil))
|
||||
require.ErrorIs(t, err, common.ErrNoResponse)
|
||||
_, err = singleItem([]*mockHasError{{}, {}})
|
||||
require.ErrorIs(t, err, errMultipleItemsReturned)
|
||||
|
||||
got, err := singleItem([]*mockHasError{{}})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, got)
|
||||
}
|
||||
Reference in New Issue
Block a user