mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-30 07:26:46 +00:00
* gateio: Add multi asset websocket support WIP. * meow * Add tests and shenanigans * integrate flushing and for enabling/disabling pairs from rpc shenanigans * some changes * linter: fixes strikes again. * Change name ConnectionAssociation -> ConnectionCandidate for better clarity on purpose. Change connections map to point to candidate to track subscriptions for future dynamic connections holder and drop struct ConnectionDetails. * Add subscription tests (state functional) * glorious:nits + proxy handling * Spelling * linter: fixerino * instead of nil, dont do nil. * clean up nils * cya nils * don't need to set URL or check if its running * stop ping handler routine leak * * Fix bug where reader routine on error that is not a disconnection error but websocket frame error or anything really makes the reader routine return and then connection never cycles and the buffer gets filled. * Handle reconnection via an errors.Is check which is simpler and in that scope allow for quick disconnect reconnect without waiting for connection cycle. * Dial now uses code from DialContext but just calls context.Background() * Don't allow reader to return on parse binary response error. Just output error and return a non nil response * Allow rollback on connect on any error across all connections * fix shadow jutsu * glorious/gk: nitters - adds in ws mock server * linter: fix * fix deadlock on connection as the previous channel had no reader and would hang connection reader for eternity. * gk: nits * Leak issue and edge case * gk: nits * gk: drain brain * glorious: nits * Update exchanges/stream/websocket.go Co-authored-by: Scott <gloriousCode@users.noreply.github.com> * glorious: nits * add tests * linter: fix * After merge * Add error connection info * Fix edge case where it does not reconnect made by an already closed connection * stream coverage * glorious: nits * glorious: nits removed asset error handling in stream package * linter: fix * rm block * Add basic readme * fix asset enabled flush cycle for multi connection * spella: fix * linter: fix * Add glorious suggestions, fix some race thing * reinstate name before any routine gets spawned * stop on error in mock tests * glorious: nits * glorious: nits found in CI build * Add test for drain, bumped wait times as there seems to be something happening on macos CI builds, used context.WithTimeout because its instant. * mutex across shutdown and connect for protection * lint: fix * test time withoffset, reinstate stop * fix whoops * const trafficCheckInterval; rm testmain * y * fix lint * bump time check window * stream: fix intermittant test failures while testing routines and remove code that is not needed. * spells * cant do what I did * protect race due to routine. * update testURL * use mock websocket connection instead of test URL's * linter: fix * remove url because its throwing errors on CI builds * connections drop all the time, don't need to worry about not being able to echo back ws data as it can be easily reviewed _test file side. * remove another superfluous url thats not really set up for this * spawn overwatch routine when there is no errors, inline checker instead of waiting for a time period, add sleep inline with echo handler as this is really quick and wanted to ensure that latency is handing correctly * linter: fixerino uperino * glorious: panix * linter: things * whoops * defer lock and use functions that don't require locking in SetProxyAddress * lint: fix * thrasher: nits --------- Co-authored-by: shazbert <ryan.oharareid@thrasher.io> Co-authored-by: Scott <gloriousCode@users.noreply.github.com>
214 lines
7.0 KiB
Go
214 lines
7.0 KiB
Go
package gateio
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/gorilla/websocket"
|
|
"github.com/thrasher-corp/gocryptotrader/currency"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/account"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/subscription"
|
|
)
|
|
|
|
const (
|
|
// delivery real trading urls
|
|
deliveryRealUSDTTradingURL = "wss://fx-ws.gateio.ws/v4/ws/delivery/usdt"
|
|
deliveryRealBTCTradingURL = "wss://fx-ws.gateio.ws/v4/ws/delivery/btc"
|
|
|
|
// delivery testnet urls
|
|
deliveryTestNetBTCTradingURL = "wss://fx-ws-testnet.gateio.ws/v4/ws/delivery/btc"
|
|
deliveryTestNetUSDTTradingURL = "wss://fx-ws-testnet.gateio.ws/v4/ws/delivery/usdt"
|
|
)
|
|
|
|
var defaultDeliveryFuturesSubscriptions = []string{
|
|
futuresTickersChannel,
|
|
futuresTradesChannel,
|
|
futuresOrderbookChannel,
|
|
futuresCandlesticksChannel,
|
|
}
|
|
|
|
var fetchedFuturesCurrencyPairSnapshotOrderbook = make(map[string]bool)
|
|
|
|
// WsDeliveryFuturesConnect initiates a websocket connection for delivery futures account
|
|
func (g *Gateio) WsDeliveryFuturesConnect(ctx context.Context, conn stream.Connection) error {
|
|
err := g.CurrencyPairs.IsAssetEnabled(asset.DeliveryFutures)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = conn.DialContext(ctx, &websocket.Dialer{}, http.Header{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
pingMessage, err := json.Marshal(WsInput{
|
|
ID: conn.GenerateMessageID(false),
|
|
Time: time.Now().Unix(), // TODO: Func for dynamic time as this will be the same time for every ping message.
|
|
Channel: futuresPingChannel,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
conn.SetupPingHandler(request.Unset, stream.PingHandler{
|
|
Websocket: true,
|
|
Delay: time.Second * 5,
|
|
MessageType: websocket.PingMessage,
|
|
Message: pingMessage,
|
|
})
|
|
return nil
|
|
}
|
|
|
|
// GenerateDeliveryFuturesDefaultSubscriptions returns delivery futures default subscriptions params.
|
|
func (g *Gateio) GenerateDeliveryFuturesDefaultSubscriptions() (subscription.List, error) {
|
|
_, err := g.GetCredentials(context.Background())
|
|
if err != nil {
|
|
g.Websocket.SetCanUseAuthenticatedEndpoints(false)
|
|
}
|
|
channelsToSubscribe := defaultDeliveryFuturesSubscriptions
|
|
if g.Websocket.CanUseAuthenticatedEndpoints() {
|
|
channelsToSubscribe = append(channelsToSubscribe, futuresOrdersChannel, futuresUserTradesChannel, futuresBalancesChannel)
|
|
}
|
|
|
|
pairs, err := g.GetEnabledPairs(asset.DeliveryFutures)
|
|
if err != nil {
|
|
if errors.Is(err, asset.ErrNotEnabled) {
|
|
return nil, nil // no enabled pairs, subscriptions require an associated pair.
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
var subscriptions subscription.List
|
|
for i := range channelsToSubscribe {
|
|
for j := range pairs {
|
|
params := make(map[string]any)
|
|
switch channelsToSubscribe[i] {
|
|
case futuresOrderbookChannel:
|
|
params["limit"] = 20
|
|
params["interval"] = "0"
|
|
case futuresCandlesticksChannel:
|
|
params["interval"] = kline.FiveMin
|
|
}
|
|
fPair, err := g.FormatExchangeCurrency(pairs[j], asset.DeliveryFutures)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
subscriptions = append(subscriptions, &subscription.Subscription{
|
|
Channel: channelsToSubscribe[i],
|
|
Pairs: currency.Pairs{fPair.Upper()},
|
|
Params: params,
|
|
})
|
|
}
|
|
}
|
|
return subscriptions, nil
|
|
}
|
|
|
|
// DeliveryFuturesSubscribe sends a websocket message to stop receiving data from the channel
|
|
func (g *Gateio) DeliveryFuturesSubscribe(ctx context.Context, conn stream.Connection, channelsToUnsubscribe subscription.List) error {
|
|
return g.handleSubscription(ctx, conn, subscribeEvent, channelsToUnsubscribe, g.generateDeliveryFuturesPayload)
|
|
}
|
|
|
|
// DeliveryFuturesUnsubscribe sends a websocket message to stop receiving data from the channel
|
|
func (g *Gateio) DeliveryFuturesUnsubscribe(ctx context.Context, conn stream.Connection, channelsToUnsubscribe subscription.List) error {
|
|
return g.handleSubscription(ctx, conn, unsubscribeEvent, channelsToUnsubscribe, g.generateDeliveryFuturesPayload)
|
|
}
|
|
|
|
func (g *Gateio) generateDeliveryFuturesPayload(ctx context.Context, conn stream.Connection, event string, channelsToSubscribe subscription.List) ([]WsInput, error) {
|
|
if len(channelsToSubscribe) == 0 {
|
|
return nil, errors.New("cannot generate payload, no channels supplied")
|
|
}
|
|
var creds *account.Credentials
|
|
var err error
|
|
if g.Websocket.CanUseAuthenticatedEndpoints() {
|
|
creds, err = g.GetCredentials(ctx)
|
|
if err != nil {
|
|
g.Websocket.SetCanUseAuthenticatedEndpoints(false)
|
|
}
|
|
}
|
|
outbound := make([]WsInput, 0, len(channelsToSubscribe))
|
|
for i := range channelsToSubscribe {
|
|
if len(channelsToSubscribe[i].Pairs) != 1 {
|
|
return nil, subscription.ErrNotSinglePair
|
|
}
|
|
var auth *WsAuthInput
|
|
timestamp := time.Now()
|
|
var params []string
|
|
params = []string{channelsToSubscribe[i].Pairs[0].String()}
|
|
if g.Websocket.CanUseAuthenticatedEndpoints() {
|
|
switch channelsToSubscribe[i].Channel {
|
|
case futuresOrdersChannel, futuresUserTradesChannel,
|
|
futuresLiquidatesChannel, futuresAutoDeleveragesChannel,
|
|
futuresAutoPositionCloseChannel, futuresBalancesChannel,
|
|
futuresReduceRiskLimitsChannel, futuresPositionsChannel,
|
|
futuresAutoOrdersChannel:
|
|
value, ok := channelsToSubscribe[i].Params["user"].(string)
|
|
if ok {
|
|
params = append([]string{value}, params...)
|
|
}
|
|
var sigTemp string
|
|
sigTemp, err = g.generateWsSignature(creds.Secret, event, channelsToSubscribe[i].Channel, timestamp.Unix())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
auth = &WsAuthInput{
|
|
Method: "api_key",
|
|
Key: creds.Key,
|
|
Sign: sigTemp,
|
|
}
|
|
}
|
|
}
|
|
frequency, okay := channelsToSubscribe[i].Params["frequency"].(kline.Interval)
|
|
if okay {
|
|
var frequencyString string
|
|
frequencyString, err = g.GetIntervalString(frequency)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
params = append(params, frequencyString)
|
|
}
|
|
levelString, okay := channelsToSubscribe[i].Params["level"].(string)
|
|
if okay {
|
|
params = append(params, levelString)
|
|
}
|
|
limit, okay := channelsToSubscribe[i].Params["limit"].(int)
|
|
if okay {
|
|
params = append(params, strconv.Itoa(limit))
|
|
}
|
|
accuracy, okay := channelsToSubscribe[i].Params["accuracy"].(string)
|
|
if okay {
|
|
params = append(params, accuracy)
|
|
}
|
|
switch channelsToSubscribe[i].Channel {
|
|
case futuresCandlesticksChannel:
|
|
interval, okay := channelsToSubscribe[i].Params["interval"].(kline.Interval)
|
|
if okay {
|
|
var intervalString string
|
|
intervalString, err = g.GetIntervalString(interval)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
params = append([]string{intervalString}, params...)
|
|
}
|
|
case futuresOrderbookChannel:
|
|
intervalString, okay := channelsToSubscribe[i].Params["interval"].(string)
|
|
if okay {
|
|
params = append(params, intervalString)
|
|
}
|
|
}
|
|
outbound = append(outbound, WsInput{
|
|
ID: conn.GenerateMessageID(false),
|
|
Event: event,
|
|
Channel: channelsToSubscribe[i].Channel,
|
|
Payload: params,
|
|
Auth: auth,
|
|
Time: timestamp.Unix(),
|
|
})
|
|
}
|
|
return outbound, nil
|
|
}
|