mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-06-01 23:16:51 +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 * stream match update * update tests * linter: fix * glorious: nits + handle context cancellations * 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. * glorious: whooops * gk: nits * Leak issue and edge case * Websocket: Add SendMessageReturnResponses * whooooooopsie * gk: nitssssss * Update exchanges/stream/stream_match.go Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com> * Update exchanges/stream/stream_match_test.go Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com> * linter: appease the linter gods * gk: nits * gk: drain brain * started * more changes before merge match pr * gateio: still building out * gateio: finish spot * fix up tests in gateio * Add tests for stream package * rm unused field * glorious: nits * rn files, specifically set function names to asset and offload routing to websocket type. * linter: fix * Add futures websocket request support * gateio: integrate with IBOTExchange (cherry pick my nose) * linter: fix * glorious: nits * add counter and update gateio * fix collision issue * 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 * upgrade to upstream merge * 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 * Set correct price * 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 * fix ID bug, why I do this, I don't know. * glorious: panix * linter: things * whoops * dont need to make consecutive Unix() calls * websocket: fix potential panic on error and no responses and adding waitForResponses * rm json parser and handle in json package instead * in favour of json package unmarshalling * linter: fix * linter: fix again * * change field name OutboundRequestSignature to WrapperDefinedConnectionSignature for agnostic inbound and outbound connections. * change method name GetOutboundConnection to GetConnection for agnostic inbound and outbound connections. * drop outbound field map for improved performance just using a range and field check (less complex as well) * change field name connections to connectionToWrapper for better clarity * spells and magic and wands * glorious: nits * comparable check for signature * mv err var * glorious: nits and stuff * attempt to fix race * glorious: nits * gk: nits; engine log cleanup * gk: nits; OCD * gk: nits; move function change file names * gk: nits; 🚀 * gk: nits; convert variadic function and message inspection to interface and include a specific function for that handling so as to not need nil on every call * gk: nits; continued * gk: engine nits; rm loaded exchange * gk: nits; drop WebsocketLoginResponse * stream: Add match method EnsureMatchWithData * gk: nits; rn Inspect to IsFinal * gk: nits; rn to MessageFilter * linter: fix * gateio: update rate limit definitions (cherry-pick) * Add test and missing * Shared REST rate limit definitions with Websocket service, set lookup item to nil for systems that do not require rate limiting; add glorious nit * integrate rate limits for websocket trading spot * conform to match upstream changes * standardise names to upstream style * fix wrapper standards test when sending a auth request through a websocket connection * whoops * Update exchanges/gateio/gateio_types.go Co-authored-by: Scott <gloriousCode@users.noreply.github.com> * glorious: nits * linter: fix * linter: overload * whoops * spelling fixes on recent merge * glorious: nits * linter: fix? * glorious: nits * gk: assert errors touched * gk: unexport derive functions * gk: nitssssssss * fix test * gk: nitters v1 * gk: http status * gk/nits: Add getAssetFromFuturesPair * gk: nits single response when submitting * gk: new pair with delimiter in tests * gk: param update slice to slice of pointers * gk: add asset type in params, includes t.Context() for tests * linter: fix * linter: fix * fix merge whoopsie * glorious: nits * gk: nit * shift over to websocket package error * internal/exchange/websocket -> exchange/websocket * PEAK OCD! * appease the OCD gods * thrasher: nits --------- Co-authored-by: shazbert <ryan.oharareid@thrasher.io> Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com> Co-authored-by: Scott <gloriousCode@users.noreply.github.com>
214 lines
7.1 KiB
Go
214 lines
7.1 KiB
Go
package gateio
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
gws "github.com/gorilla/websocket"
|
|
"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/account"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
|
|
"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 websocket.Connection) error {
|
|
err := g.CurrencyPairs.IsAssetEnabled(asset.DeliveryFutures)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = conn.DialContext(ctx, &gws.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(websocketRateLimitNotNeededEPL, websocket.PingHandler{
|
|
Websocket: true,
|
|
Delay: time.Second * 5,
|
|
MessageType: gws.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,
|
|
Asset: asset.DeliveryFutures,
|
|
})
|
|
}
|
|
}
|
|
return subscriptions, nil
|
|
}
|
|
|
|
// DeliveryFuturesSubscribe sends a websocket message to stop receiving data from the channel
|
|
func (g *Gateio) DeliveryFuturesSubscribe(ctx context.Context, conn websocket.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 websocket.Connection, channelsToUnsubscribe subscription.List) error {
|
|
return g.handleSubscription(ctx, conn, unsubscribeEvent, channelsToUnsubscribe, g.generateDeliveryFuturesPayload)
|
|
}
|
|
|
|
func (g *Gateio) generateDeliveryFuturesPayload(ctx context.Context, conn websocket.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
|
|
}
|