exchanges/websocket: Implement subscription configuration (#1394)

* Websockets: Move Subscription to its own package

This allows the small type to be imported from both `config` and from
`stream` without an import cycle, so we don't have to repeat ourselves

* Subs: Renamed Currency to Pair

This was being mis-used through much of the code, and since we're
already touching everything, we might as well fix it

* Websockets: Add Subscription configuration

* Binance: Add subscription configuration

* Kucoin: Subscription configuration

* Simplify GenerateDefaultSubs
* Improve TestGenSubs coverage
* Test Candle Sub generation
* Support Candle intervals
* Full responsibility for formatting Channel name on GenerateDefaultSubs
  OR consumer of Subscribe
* Simplify generatePayloads as a result
* Fix test coverage of asset types in processMarketSnapshot

* Exchanges: Abstract ParallelChanOp

* Tests: Generic ws mock instances

* Kucoin: Fix intermittent conflict in test currs

Use isolated test instance for `TestGetOpenInterest`.

`TestGetOpenInterest` would occassionally change pairs before
GenerateDefault Subs.
This commit is contained in:
Gareth Kirwan
2024-01-24 05:54:07 +01:00
committed by GitHub
parent 301551ac20
commit e007f69f7c
67 changed files with 3705 additions and 3167 deletions

View File

@@ -23,6 +23,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
"github.com/thrasher-corp/gocryptotrader/exchanges/subscription"
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
"github.com/thrasher-corp/gocryptotrader/exchanges/trade"
)
@@ -624,7 +625,7 @@ func (g *Gateio) processCrossMarginLoans(data []byte) error {
}
// GenerateDefaultSubscriptions returns default subscriptions
func (g *Gateio) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) {
func (g *Gateio) GenerateDefaultSubscriptions() ([]subscription.Subscription, error) {
channelsToSubscribe := defaultSubscriptions
if g.Websocket.CanUseAuthenticatedEndpoints() {
channelsToSubscribe = append(channelsToSubscribe, []string{
@@ -637,7 +638,7 @@ func (g *Gateio) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, e
channelsToSubscribe = append(channelsToSubscribe, spotTradesChannel)
}
var subscriptions []stream.ChannelSubscription
var subscriptions []subscription.Subscription
var err error
for i := range channelsToSubscribe {
var pairs []currency.Pair
@@ -677,11 +678,11 @@ func (g *Gateio) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, e
return nil, err
}
subscriptions = append(subscriptions, stream.ChannelSubscription{
Channel: channelsToSubscribe[i],
Currency: fpair.Upper(),
Asset: assetType,
Params: params,
subscriptions = append(subscriptions, subscription.Subscription{
Channel: channelsToSubscribe[i],
Pair: fpair.Upper(),
Asset: assetType,
Params: params,
})
}
}
@@ -689,7 +690,7 @@ func (g *Gateio) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, e
}
// handleSubscription sends a websocket message to receive data from the channel
func (g *Gateio) handleSubscription(event string, channelsToSubscribe []stream.ChannelSubscription) error {
func (g *Gateio) handleSubscription(event string, channelsToSubscribe []subscription.Subscription) error {
payloads, err := g.generatePayload(event, channelsToSubscribe)
if err != nil {
return err
@@ -719,7 +720,7 @@ func (g *Gateio) handleSubscription(event string, channelsToSubscribe []stream.C
return errs
}
func (g *Gateio) generatePayload(event string, channelsToSubscribe []stream.ChannelSubscription) ([]WsInput, error) {
func (g *Gateio) generatePayload(event string, channelsToSubscribe []subscription.Subscription) ([]WsInput, error) {
if len(channelsToSubscribe) == 0 {
return nil, errors.New("cannot generate payload, no channels supplied")
}
@@ -737,8 +738,8 @@ func (g *Gateio) generatePayload(event string, channelsToSubscribe []stream.Chan
for i := range channelsToSubscribe {
var auth *WsAuthInput
timestamp := time.Now()
channelsToSubscribe[i].Currency.Delimiter = currency.UnderscoreDelimiter
params := []string{channelsToSubscribe[i].Currency.String()}
channelsToSubscribe[i].Pair.Delimiter = currency.UnderscoreDelimiter
params := []string{channelsToSubscribe[i].Pair.String()}
switch channelsToSubscribe[i].Channel {
case spotOrderbookChannel:
interval, okay := channelsToSubscribe[i].Params["interval"].(kline.Interval)
@@ -836,12 +837,12 @@ func (g *Gateio) generatePayload(event string, channelsToSubscribe []stream.Chan
}
// Subscribe sends a websocket message to stop receiving data from the channel
func (g *Gateio) Subscribe(channelsToUnsubscribe []stream.ChannelSubscription) error {
func (g *Gateio) Subscribe(channelsToUnsubscribe []subscription.Subscription) error {
return g.handleSubscription("subscribe", channelsToUnsubscribe)
}
// Unsubscribe sends a websocket message to stop receiving data from the channel
func (g *Gateio) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error {
func (g *Gateio) Unsubscribe(channelsToUnsubscribe []subscription.Subscription) error {
return g.handleSubscription("unsubscribe", channelsToUnsubscribe)
}

View File

@@ -16,6 +16,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
"github.com/thrasher-corp/gocryptotrader/exchanges/subscription"
"github.com/thrasher-corp/gocryptotrader/log"
)
@@ -140,7 +141,7 @@ func (g *Gateio) wsFunnelDeliveryFuturesConnectionData(ws stream.Connection) {
}
// GenerateDeliveryFuturesDefaultSubscriptions returns delivery futures default subscriptions params.
func (g *Gateio) GenerateDeliveryFuturesDefaultSubscriptions() ([]stream.ChannelSubscription, error) {
func (g *Gateio) GenerateDeliveryFuturesDefaultSubscriptions() ([]subscription.Subscription, error) {
_, err := g.GetCredentials(context.Background())
if err != nil {
g.Websocket.SetCanUseAuthenticatedEndpoints(false)
@@ -158,7 +159,7 @@ func (g *Gateio) GenerateDeliveryFuturesDefaultSubscriptions() ([]stream.Channel
if err != nil {
return nil, err
}
var subscriptions []stream.ChannelSubscription
var subscriptions []subscription.Subscription
for i := range channelsToSubscribe {
for j := range pairs {
params := make(map[string]interface{})
@@ -173,10 +174,10 @@ func (g *Gateio) GenerateDeliveryFuturesDefaultSubscriptions() ([]stream.Channel
if err != nil {
return nil, err
}
subscriptions = append(subscriptions, stream.ChannelSubscription{
Channel: channelsToSubscribe[i],
Currency: fpair.Upper(),
Params: params,
subscriptions = append(subscriptions, subscription.Subscription{
Channel: channelsToSubscribe[i],
Pair: fpair.Upper(),
Params: params,
})
}
}
@@ -184,17 +185,17 @@ func (g *Gateio) GenerateDeliveryFuturesDefaultSubscriptions() ([]stream.Channel
}
// DeliveryFuturesSubscribe sends a websocket message to stop receiving data from the channel
func (g *Gateio) DeliveryFuturesSubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error {
func (g *Gateio) DeliveryFuturesSubscribe(channelsToUnsubscribe []subscription.Subscription) error {
return g.handleDeliveryFuturesSubscription("subscribe", channelsToUnsubscribe)
}
// DeliveryFuturesUnsubscribe sends a websocket message to stop receiving data from the channel
func (g *Gateio) DeliveryFuturesUnsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error {
func (g *Gateio) DeliveryFuturesUnsubscribe(channelsToUnsubscribe []subscription.Subscription) error {
return g.handleDeliveryFuturesSubscription("unsubscribe", channelsToUnsubscribe)
}
// handleDeliveryFuturesSubscription sends a websocket message to receive data from the channel
func (g *Gateio) handleDeliveryFuturesSubscription(event string, channelsToSubscribe []stream.ChannelSubscription) error {
func (g *Gateio) handleDeliveryFuturesSubscription(event string, channelsToSubscribe []subscription.Subscription) error {
payloads, err := g.generateDeliveryFuturesPayload(event, channelsToSubscribe)
if err != nil {
return err
@@ -228,7 +229,7 @@ func (g *Gateio) handleDeliveryFuturesSubscription(event string, channelsToSubsc
return errs
}
func (g *Gateio) generateDeliveryFuturesPayload(event string, channelsToSubscribe []stream.ChannelSubscription) ([2][]WsInput, error) {
func (g *Gateio) generateDeliveryFuturesPayload(event string, channelsToSubscribe []subscription.Subscription) ([2][]WsInput, error) {
if len(channelsToSubscribe) == 0 {
return [2][]WsInput{}, errors.New("cannot generate payload, no channels supplied")
}
@@ -245,7 +246,7 @@ func (g *Gateio) generateDeliveryFuturesPayload(event string, channelsToSubscrib
var auth *WsAuthInput
timestamp := time.Now()
var params []string
params = []string{channelsToSubscribe[i].Currency.String()}
params = []string{channelsToSubscribe[i].Pair.String()}
if g.Websocket.CanUseAuthenticatedEndpoints() {
switch channelsToSubscribe[i].Channel {
case futuresOrdersChannel, futuresUserTradesChannel,
@@ -309,7 +310,7 @@ func (g *Gateio) generateDeliveryFuturesPayload(event string, channelsToSubscrib
params = append(params, intervalString)
}
}
if strings.HasPrefix(channelsToSubscribe[i].Currency.Quote.Upper().String(), "USDT") {
if strings.HasPrefix(channelsToSubscribe[i].Pair.Quote.Upper().String(), "USDT") {
payloads[0] = append(payloads[0], WsInput{
ID: g.Websocket.Conn.GenerateMessageID(false),
Event: event,

View File

@@ -20,6 +20,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
"github.com/thrasher-corp/gocryptotrader/exchanges/subscription"
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
"github.com/thrasher-corp/gocryptotrader/exchanges/trade"
"github.com/thrasher-corp/gocryptotrader/log"
@@ -121,7 +122,7 @@ func (g *Gateio) WsFuturesConnect() error {
}
// GenerateFuturesDefaultSubscriptions returns default subscriptions information.
func (g *Gateio) GenerateFuturesDefaultSubscriptions() ([]stream.ChannelSubscription, error) {
func (g *Gateio) GenerateFuturesDefaultSubscriptions() ([]subscription.Subscription, error) {
channelsToSubscribe := defaultFuturesSubscriptions
if g.Websocket.CanUseAuthenticatedEndpoints() {
channelsToSubscribe = append(channelsToSubscribe,
@@ -134,7 +135,7 @@ func (g *Gateio) GenerateFuturesDefaultSubscriptions() ([]stream.ChannelSubscrip
if err != nil {
return nil, err
}
subscriptions := make([]stream.ChannelSubscription, len(channelsToSubscribe)*len(pairs))
subscriptions := make([]subscription.Subscription, len(channelsToSubscribe)*len(pairs))
count := 0
for i := range channelsToSubscribe {
for j := range pairs {
@@ -153,10 +154,10 @@ func (g *Gateio) GenerateFuturesDefaultSubscriptions() ([]stream.ChannelSubscrip
if err != nil {
return nil, err
}
subscriptions[count] = stream.ChannelSubscription{
Channel: channelsToSubscribe[i],
Currency: fpair.Upper(),
Params: params,
subscriptions[count] = subscription.Subscription{
Channel: channelsToSubscribe[i],
Pair: fpair.Upper(),
Params: params,
}
count++
}
@@ -165,12 +166,12 @@ func (g *Gateio) GenerateFuturesDefaultSubscriptions() ([]stream.ChannelSubscrip
}
// FuturesSubscribe sends a websocket message to stop receiving data from the channel
func (g *Gateio) FuturesSubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error {
func (g *Gateio) FuturesSubscribe(channelsToUnsubscribe []subscription.Subscription) error {
return g.handleFuturesSubscription("subscribe", channelsToUnsubscribe)
}
// FuturesUnsubscribe sends a websocket message to stop receiving data from the channel
func (g *Gateio) FuturesUnsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error {
func (g *Gateio) FuturesUnsubscribe(channelsToUnsubscribe []subscription.Subscription) error {
return g.handleFuturesSubscription("unsubscribe", channelsToUnsubscribe)
}
@@ -269,7 +270,7 @@ func (g *Gateio) wsHandleFuturesData(respRaw []byte, assetType asset.Item) error
}
// handleFuturesSubscription sends a websocket message to receive data from the channel
func (g *Gateio) handleFuturesSubscription(event string, channelsToSubscribe []stream.ChannelSubscription) error {
func (g *Gateio) handleFuturesSubscription(event string, channelsToSubscribe []subscription.Subscription) error {
payloads, err := g.generateFuturesPayload(event, channelsToSubscribe)
if err != nil {
return err
@@ -306,7 +307,7 @@ func (g *Gateio) handleFuturesSubscription(event string, channelsToSubscribe []s
return nil
}
func (g *Gateio) generateFuturesPayload(event string, channelsToSubscribe []stream.ChannelSubscription) ([2][]WsInput, error) {
func (g *Gateio) generateFuturesPayload(event string, channelsToSubscribe []subscription.Subscription) ([2][]WsInput, error) {
if len(channelsToSubscribe) == 0 {
return [2][]WsInput{}, errors.New("cannot generate payload, no channels supplied")
}
@@ -323,7 +324,7 @@ func (g *Gateio) generateFuturesPayload(event string, channelsToSubscribe []stre
var auth *WsAuthInput
timestamp := time.Now()
var params []string
params = []string{channelsToSubscribe[i].Currency.String()}
params = []string{channelsToSubscribe[i].Pair.String()}
if g.Websocket.CanUseAuthenticatedEndpoints() {
switch channelsToSubscribe[i].Channel {
case futuresOrdersChannel, futuresUserTradesChannel,
@@ -387,7 +388,7 @@ func (g *Gateio) generateFuturesPayload(event string, channelsToSubscribe []stre
params = append(params, intervalString)
}
}
if strings.HasPrefix(channelsToSubscribe[i].Currency.Quote.Upper().String(), "USDT") {
if strings.HasPrefix(channelsToSubscribe[i].Pair.Quote.Upper().String(), "USDT") {
payloads[0] = append(payloads[0], WsInput{
ID: g.Websocket.Conn.GenerateMessageID(false),
Event: event,

View File

@@ -20,6 +20,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
"github.com/thrasher-corp/gocryptotrader/exchanges/subscription"
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
"github.com/thrasher-corp/gocryptotrader/exchanges/trade"
"github.com/thrasher-corp/gocryptotrader/log"
@@ -104,7 +105,7 @@ func (g *Gateio) WsOptionsConnect() error {
}
// GenerateOptionsDefaultSubscriptions generates list of channel subscriptions for options asset type.
func (g *Gateio) GenerateOptionsDefaultSubscriptions() ([]stream.ChannelSubscription, error) {
func (g *Gateio) GenerateOptionsDefaultSubscriptions() ([]subscription.Subscription, error) {
channelsToSubscribe := defaultOptionsSubscriptions
var userID int64
if g.Websocket.CanUseAuthenticatedEndpoints() {
@@ -129,7 +130,7 @@ func (g *Gateio) GenerateOptionsDefaultSubscriptions() ([]stream.ChannelSubscrip
}
}
getEnabledPairs:
var subscriptions []stream.ChannelSubscription
var subscriptions []subscription.Subscription
pairs, err := g.GetEnabledPairs(asset.Options)
if err != nil {
return nil, err
@@ -162,17 +163,17 @@ getEnabledPairs:
if err != nil {
return nil, err
}
subscriptions = append(subscriptions, stream.ChannelSubscription{
Channel: channelsToSubscribe[i],
Currency: fpair.Upper(),
Params: params,
subscriptions = append(subscriptions, subscription.Subscription{
Channel: channelsToSubscribe[i],
Pair: fpair.Upper(),
Params: params,
})
}
}
return subscriptions, nil
}
func (g *Gateio) generateOptionsPayload(event string, channelsToSubscribe []stream.ChannelSubscription) ([]WsInput, error) {
func (g *Gateio) generateOptionsPayload(event string, channelsToSubscribe []subscription.Subscription) ([]WsInput, error) {
if len(channelsToSubscribe) == 0 {
return nil, errors.New("cannot generate payload, no channels supplied")
}
@@ -189,7 +190,7 @@ func (g *Gateio) generateOptionsPayload(event string, channelsToSubscribe []stre
optionsUnderlyingPriceChannel,
optionsUnderlyingCandlesticksChannel:
var uly currency.Pair
uly, err = g.GetUnderlyingFromCurrencyPair(channelsToSubscribe[i].Currency)
uly, err = g.GetUnderlyingFromCurrencyPair(channelsToSubscribe[i].Pair)
if err != nil {
return nil, err
}
@@ -197,8 +198,8 @@ func (g *Gateio) generateOptionsPayload(event string, channelsToSubscribe []stre
case optionsBalancesChannel:
// options.balance channel does not require underlying or contract
default:
channelsToSubscribe[i].Currency.Delimiter = currency.UnderscoreDelimiter
params = append(params, channelsToSubscribe[i].Currency.String())
channelsToSubscribe[i].Pair.Delimiter = currency.UnderscoreDelimiter
params = append(params, channelsToSubscribe[i].Pair.String())
}
switch channelsToSubscribe[i].Channel {
case optionsOrderbookChannel:
@@ -298,17 +299,17 @@ func (g *Gateio) wsReadOptionsConnData() {
}
// OptionsSubscribe sends a websocket message to stop receiving data for asset type options
func (g *Gateio) OptionsSubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error {
func (g *Gateio) OptionsSubscribe(channelsToUnsubscribe []subscription.Subscription) error {
return g.handleOptionsSubscription("subscribe", channelsToUnsubscribe)
}
// OptionsUnsubscribe sends a websocket message to stop receiving data for asset type options
func (g *Gateio) OptionsUnsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error {
func (g *Gateio) OptionsUnsubscribe(channelsToUnsubscribe []subscription.Subscription) error {
return g.handleOptionsSubscription("unsubscribe", channelsToUnsubscribe)
}
// handleOptionsSubscription sends a websocket message to receive data from the channel
func (g *Gateio) handleOptionsSubscription(event string, channelsToSubscribe []stream.ChannelSubscription) error {
func (g *Gateio) handleOptionsSubscription(event string, channelsToSubscribe []subscription.Subscription) error {
payloads, err := g.generateOptionsPayload(event, channelsToSubscribe)
if err != nil {
return err