Files
gocryptotrader/exchanges/okx/okx_business_websocket.go
Samuael A. 3f534a15f1 cmd/exchange_template, exchanges: Update templates and propogate to exchanges (#1777)
* Added TimeInForce type and updated related files

* Linter issue fix and minor coinbasepro type update

* Bitrex consts update

* added unit test and minor changes in bittrex

* Unit tests update

* Fix minor linter issues

* Update TestStringToTimeInForce unit test

* Exchange test template change

* A different approach

* fix conflict with gateio timeInForce

* minor exchange template update

* Minor fix to test_files template

* Update order tests

* Complete updating the order unit tests

* Updating exchange wrapper and test template files

* update kucoin and deribit wrapper to match the time in force change

* minor comment update

* fix time-in-force related test errors

* linter issue fix

* ADD_NEW_EXCHANGE documentation update

* time in force constants, functions and unit tests update

* shift tif policies to TimeInForce

* Update time-in-force, related functions, and unit tests

* fix linter issue and time-in-force processing

* added a good till crossing tif value

* order type fix and fix related tim-in-force entries

* update time-in-force unmarshaling and unit test

* consistency guideline added

* fix time-in-force error in gateio

* linter issue fix

* update based on review comments

* add unit test and fix missing issues

* minor fix and added benchmark unit test

* change GTT to GTC for limit

* fix linter issue

* added time-in-force value to place order param

* fix minor issues based on review comment and move tif code to separate files

* update on exchanges linked to time-in-force

* resolve missing review comments

* minor linter issues fix

* added time-in-force handler and update timeInForce parametered endpoint

* minor fixes based on review

* nits fix

* update based on review

* linter fix

* rm getTimeInForce func and minor change to time-in-force

* minor change

* update based on review comments

* wrappers and time-in-force calling approach

* minor change

* update gateio string to timeInForce conversion and unit test

* update exchange template

* update wrapper template file

* policy comments, and template files update

* rename all exchange types name to Exchange

* update on template files and template generation

* templates and generation code and other updates

* linter issue fix

* added subscriptions and websocket templates

* update ADD_NEW_EXCHANGE.md with recent binance functions and implementations

* rename template files and update unit tests

* minor template and unit test fix

* rename templates and fix on unit tests

* update on template files and documentation

* removed unnecessary tag fix and update templates

* fix Add_NEW_EXCHANGE.md doc file

* formatting, comments, and error checks update on template files

* rename exchange receivers to e and ex for consistency

* rename unit test exchange receiver and minor updates

* linter issues fix

* fix deribit issue and minor style update

* fix test issues caused by receiver change

* raname local variables exchange declaration variables

* update templates comments

* update templates and related comments

* renamed ex to e

* update template comments

* toggle WS to false to improve coverage

* template comments update

* added test coverage to Ws enabled and minor changes

---------

Co-authored-by: Samuel Reid <43227667+cranktakular@users.noreply.github.com>
2025-07-17 10:46:36 +10:00

244 lines
7.4 KiB
Go

package okx
import (
"context"
"encoding/base64"
"fmt"
"net/http"
"strconv"
"time"
gws "github.com/gorilla/websocket"
"github.com/thrasher-corp/gocryptotrader/common/crypto"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/encoding/json"
"github.com/thrasher-corp/gocryptotrader/exchange/websocket"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
"github.com/thrasher-corp/gocryptotrader/exchanges/subscription"
"github.com/thrasher-corp/gocryptotrader/log"
)
const (
// okxBusinessWebsocketURL
okxBusinessWebsocketURL = "wss://ws.okx.com:8443/ws/v5/business"
)
var (
// defaultBusinessSubscribedChannels list of channels which are subscribed by default
defaultBusinessSubscribedChannels = []string{
okxSpreadPublicTrades,
okxSpreadOrderbook,
okxSpreadPublicTicker,
channelPublicStrucBlockTrades,
channelPublicBlockTrades,
channelBlockTickers,
}
// defaultBusinessAuthChannels list of authenticated channels
defaultBusinessAuthChannels = []string{
okxSpreadOrders,
okxSpreadTrades,
}
)
// WsConnectBusiness connects to a business websocket channel.
func (e *Exchange) WsConnectBusiness(ctx context.Context) error {
if !e.Websocket.IsEnabled() || !e.IsEnabled() {
return websocket.ErrWebsocketNotEnabled
}
var dialer gws.Dialer
dialer.ReadBufferSize = 8192
dialer.WriteBufferSize = 8192
e.Websocket.Conn.SetURL(okxBusinessWebsocketURL)
err := e.Websocket.Conn.Dial(ctx, &dialer, http.Header{})
if err != nil {
return err
}
e.Websocket.Wg.Add(1)
go e.wsReadData(ctx, e.Websocket.Conn)
if e.Verbose {
log.Debugf(log.ExchangeSys, "Successful connection to %v\n",
e.Websocket.GetWebsocketURL())
}
e.Websocket.Conn.SetupPingHandler(request.UnAuth, websocket.PingHandler{
MessageType: gws.TextMessage,
Message: pingMsg,
Delay: time.Second * 20,
})
if e.Websocket.CanUseAuthenticatedEndpoints() {
err = e.WsSpreadAuth(ctx)
if err != nil {
log.Errorf(log.ExchangeSys, "Error connecting auth socket: %s\n", err.Error())
e.Websocket.SetCanUseAuthenticatedEndpoints(false)
}
}
return nil
}
// WsSpreadAuth will connect to Okx's Private websocket connection and Authenticate with a login payload.
func (e *Exchange) WsSpreadAuth(ctx context.Context) error {
if !e.Websocket.CanUseAuthenticatedEndpoints() {
return fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", e.Name)
}
creds, err := e.GetCredentials(ctx)
if err != nil {
return err
}
e.Websocket.SetCanUseAuthenticatedEndpoints(true)
ts := time.Now().Unix()
signPath := "/users/self/verify"
hmac, err := crypto.GetHMAC(crypto.HashSHA256,
[]byte(strconv.FormatInt(ts, 10)+http.MethodGet+signPath),
[]byte(creds.Secret),
)
if err != nil {
return err
}
args := []WebsocketLoginData{
{
APIKey: creds.Key,
Passphrase: creds.ClientID,
Timestamp: ts,
Sign: base64.StdEncoding.EncodeToString(hmac),
},
}
return e.SendAuthenticatedWebsocketRequest(ctx, request.Unset, "login-response", operationLogin, args, nil)
}
// GenerateDefaultBusinessSubscriptions returns a list of default subscriptions to business websocket.
func (e *Exchange) GenerateDefaultBusinessSubscriptions() ([]subscription.Subscription, error) {
var subs []string
var subscriptions []subscription.Subscription
subs = append(subs, defaultBusinessSubscribedChannels...)
if e.Websocket.CanUseAuthenticatedEndpoints() {
subs = append(subs, defaultBusinessAuthChannels...)
}
for c := range subs {
switch subs[c] {
case okxSpreadOrders,
okxSpreadTrades,
okxSpreadOrderbookLevel1,
okxSpreadOrderbook,
okxSpreadPublicTrades,
okxSpreadPublicTicker:
pairs, err := e.GetEnabledPairs(asset.Spread)
if err != nil {
return nil, err
}
for p := range pairs {
subscriptions = append(subscriptions, subscription.Subscription{
Channel: subs[c],
Asset: asset.Spread,
Pairs: []currency.Pair{pairs[p]},
})
}
case channelPublicBlockTrades,
channelBlockTickers:
pairs, err := e.GetEnabledPairs(asset.PerpetualSwap)
if err != nil {
return nil, err
}
for p := range pairs {
subscriptions = append(subscriptions, subscription.Subscription{
Channel: subs[c],
Asset: asset.PerpetualSwap,
Pairs: []currency.Pair{pairs[p]},
})
}
default:
subscriptions = append(subscriptions, subscription.Subscription{
Channel: subs[c],
})
}
}
return subscriptions, nil
}
// BusinessSubscribe sends a websocket subscription request to several channels to receive data.
func (e *Exchange) BusinessSubscribe(ctx context.Context, channelsToSubscribe subscription.List) error {
return e.handleBusinessSubscription(ctx, operationSubscribe, channelsToSubscribe)
}
// BusinessUnsubscribe sends a websocket unsubscription request to several channels to receive data.
func (e *Exchange) BusinessUnsubscribe(ctx context.Context, channelsToUnsubscribe subscription.List) error {
return e.handleBusinessSubscription(ctx, operationUnsubscribe, channelsToUnsubscribe)
}
// handleBusinessSubscription sends a subscription and unsubscription information thought the business websocket endpoint.
// as of the okx, exchange this endpoint sends subscription and unsubscription messages but with a list of json objects.
func (e *Exchange) handleBusinessSubscription(ctx context.Context, operation string, subscriptions subscription.List) error {
wsSubscriptionReq := WSSubscriptionInformationList{Operation: operation}
var channels subscription.List
var authChannels subscription.List
var err error
for i := 0; i < len(subscriptions); i++ {
arg := SubscriptionInfo{
Channel: subscriptions[i].Channel,
}
var instrumentFamily, spreadID string
var instrumentID currency.Pair
switch arg.Channel {
case okxSpreadOrders,
okxSpreadTrades,
okxSpreadOrderbookLevel1,
okxSpreadOrderbook,
okxSpreadPublicTrades,
okxSpreadPublicTicker:
spreadID = subscriptions[i].Pairs[0].String()
case channelPublicBlockTrades,
channelBlockTickers:
instrumentID = subscriptions[i].Pairs[0]
}
instrumentFamilyInterface, okay := subscriptions[i].Params["instFamily"]
if okay {
instrumentFamily, _ = instrumentFamilyInterface.(string)
}
arg.InstrumentFamily = instrumentFamily
arg.SpreadID = spreadID
arg.InstrumentID = instrumentID
var chunk []byte
channels = append(channels, subscriptions[i])
wsSubscriptionReq.Arguments = append(wsSubscriptionReq.Arguments, arg)
chunk, err = json.Marshal(wsSubscriptionReq)
if err != nil {
return err
}
if len(chunk) > maxConnByteLen {
i--
err = e.Websocket.Conn.SendJSONMessage(ctx, request.UnAuth, wsSubscriptionReq)
if err != nil {
return err
}
if operation == operationUnsubscribe {
err = e.Websocket.RemoveSubscriptions(e.Websocket.Conn, channels...)
} else {
err = e.Websocket.AddSuccessfulSubscriptions(e.Websocket.Conn, channels...)
}
if err != nil {
return err
}
channels = subscription.List{}
wsSubscriptionReq.Arguments = []SubscriptionInfo{}
continue
}
}
err = e.Websocket.Conn.SendJSONMessage(ctx, request.UnAuth, wsSubscriptionReq)
if err != nil {
return err
}
if operation == operationUnsubscribe {
channels = append(channels, authChannels...)
err = e.Websocket.RemoveSubscriptions(e.Websocket.Conn, channels...)
} else {
channels = append(channels, authChannels...)
err = e.Websocket.AddSuccessfulSubscriptions(e.Websocket.Conn, channels...)
}
return err
}