mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-24 15:10:19 +00:00
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:
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/subscription"
|
||||
)
|
||||
|
||||
// Binance is the overarching type across the Binance package
|
||||
@@ -107,6 +108,13 @@ var (
|
||||
errEitherLoanOrCollateralAmountsMustBeSet = errors.New("either loan or collateral amounts must be set")
|
||||
)
|
||||
|
||||
var subscriptionNames = map[string]string{
|
||||
subscription.TickerChannel: "ticker",
|
||||
subscription.OrderbookChannel: "depth",
|
||||
subscription.CandlesChannel: "kline",
|
||||
subscription.AllTradesChannel: "trade",
|
||||
}
|
||||
|
||||
// GetExchangeInfo returns exchange information. Check binance_types for more
|
||||
// information
|
||||
func (b *Binance) GetExchangeInfo(ctx context.Context) (ExchangeInfo, error) {
|
||||
|
||||
@@ -24,6 +24,8 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/margin"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/subscription"
|
||||
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
|
||||
)
|
||||
|
||||
@@ -2007,7 +2009,7 @@ func TestWsTickerUpdate(t *testing.T) {
|
||||
func TestWsKlineUpdate(t *testing.T) {
|
||||
t.Parallel()
|
||||
pressXToJSON := []byte(`{"stream":"btcusdt@kline_1m","data":{
|
||||
"e": "kline",
|
||||
"e": "kline",
|
||||
"E": 123456789,
|
||||
"s": "BNBBTC",
|
||||
"k": {
|
||||
@@ -2404,15 +2406,63 @@ func TestSeedLocalCache(t *testing.T) {
|
||||
|
||||
func TestGenerateSubscriptions(t *testing.T) {
|
||||
t.Parallel()
|
||||
expected := []subscription.Subscription{}
|
||||
pairs, err := b.GetEnabledPairs(asset.Spot)
|
||||
assert.NoError(t, err, "GetEnabledPairs should not error")
|
||||
for _, p := range pairs {
|
||||
for _, c := range []string{"kline_1m", "depth@100ms", "ticker", "trade"} {
|
||||
expected = append(expected, subscription.Subscription{
|
||||
Channel: p.Format(currency.PairFormat{Delimiter: "", Uppercase: false}).String() + "@" + c,
|
||||
Pair: p,
|
||||
Asset: asset.Spot,
|
||||
})
|
||||
}
|
||||
}
|
||||
subs, err := b.GenerateSubscriptions()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(subs) == 0 {
|
||||
t.Fatal("unexpected subscription length")
|
||||
assert.NoError(t, err, "GenerateSubscriptions should not error")
|
||||
if assert.Len(t, subs, len(expected), "Should have the correct number of subs") {
|
||||
assert.ElementsMatch(t, subs, expected, "Should get the correct subscriptions")
|
||||
}
|
||||
}
|
||||
|
||||
func TestChannelName(t *testing.T) {
|
||||
_, err := channelName(&subscription.Subscription{Channel: "Wobbegongs"})
|
||||
assert.ErrorIs(t, err, stream.ErrSubscriptionNotSupported, "Invalid channel name should return ErrSubNotSupported")
|
||||
assert.ErrorContains(t, err, "Wobbegong", "Invalid channel name error should contain at least one shark")
|
||||
|
||||
n, err := channelName(&subscription.Subscription{Channel: subscription.TickerChannel})
|
||||
assert.NoError(t, err, "Ticker channel should not error")
|
||||
assert.Equal(t, "ticker", n, "Ticker channel name should be correct")
|
||||
|
||||
n, err = channelName(&subscription.Subscription{Channel: subscription.AllTradesChannel})
|
||||
assert.NoError(t, err, "AllTrades channel should not error")
|
||||
assert.Equal(t, "trade", n, "Trades channel name should be correct")
|
||||
|
||||
n, err = channelName(&subscription.Subscription{Channel: subscription.OrderbookChannel})
|
||||
assert.NoError(t, err, "Orderbook channel should not error")
|
||||
assert.Equal(t, "depth@0s", n, "Orderbook with no update rate should return 0s") // It's not channelName's job to supply defaults
|
||||
|
||||
n, err = channelName(&subscription.Subscription{Channel: subscription.OrderbookChannel, Interval: kline.Interval(time.Second)})
|
||||
assert.NoError(t, err, "Orderbook channel should not error")
|
||||
assert.Equal(t, "depth@1000ms", n, "Orderbook with 1s update rate should 1000ms")
|
||||
|
||||
n, err = channelName(&subscription.Subscription{Channel: subscription.OrderbookChannel, Interval: kline.HundredMilliseconds})
|
||||
assert.NoError(t, err, "Orderbook channel should not error")
|
||||
assert.Equal(t, "depth@100ms", n, "Orderbook with update rate should return it in the depth channel name")
|
||||
|
||||
n, err = channelName(&subscription.Subscription{Channel: subscription.OrderbookChannel, Interval: kline.HundredMilliseconds, Levels: 5})
|
||||
assert.NoError(t, err, "Orderbook channel should not error")
|
||||
assert.Equal(t, "depth@5@100ms", n, "Orderbook with Level should return it in the depth channel name")
|
||||
|
||||
n, err = channelName(&subscription.Subscription{Channel: subscription.CandlesChannel, Interval: kline.FifteenMin})
|
||||
assert.NoError(t, err, "Candles channel should not error")
|
||||
assert.Equal(t, "kline_15m", n, "Candles with interval should return it in the depth channel name")
|
||||
|
||||
n, err = channelName(&subscription.Subscription{Channel: subscription.CandlesChannel})
|
||||
assert.NoError(t, err, "Candles channel should not error")
|
||||
assert.Equal(t, "kline_0s", n, "Candles with no interval should return 0s") // It's not channelName's job to supply defaults
|
||||
}
|
||||
|
||||
var websocketDepthUpdate = []byte(`{"E":1608001030784,"U":7145637266,"a":[["19455.19000000","0.59490200"],["19455.37000000","0.00000000"],["19456.11000000","0.00000000"],["19456.16000000","0.00000000"],["19458.67000000","0.06400000"],["19460.73000000","0.05139800"],["19461.43000000","0.00000000"],["19464.59000000","0.00000000"],["19466.03000000","0.45000000"],["19466.36000000","0.00000000"],["19508.67000000","0.00000000"],["19572.96000000","0.00217200"],["24386.00000000","0.00256600"]],"b":[["19455.18000000","2.94649200"],["19453.15000000","0.01233600"],["19451.18000000","0.00000000"],["19446.85000000","0.11427900"],["19446.74000000","0.00000000"],["19446.73000000","0.00000000"],["19444.45000000","0.14937800"],["19426.75000000","0.00000000"],["19416.36000000","0.36052100"]],"e":"depthUpdate","s":"BTCUSDT","u":7145637297}`)
|
||||
|
||||
func TestProcessUpdate(t *testing.T) {
|
||||
|
||||
@@ -16,6 +16,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"
|
||||
@@ -548,35 +549,59 @@ func (b *Binance) UpdateLocalBuffer(wsdp *WebsocketDepthStream) (bool, error) {
|
||||
}
|
||||
|
||||
// GenerateSubscriptions generates the default subscription set
|
||||
func (b *Binance) GenerateSubscriptions() ([]stream.ChannelSubscription, error) {
|
||||
var channels = []string{"@ticker", "@trade", "@kline_1m", "@depth@100ms"}
|
||||
var subscriptions []stream.ChannelSubscription
|
||||
assets := b.GetAssetTypes(true)
|
||||
for x := range assets {
|
||||
if assets[x] == asset.Spot {
|
||||
pairs, err := b.GetEnabledPairs(assets[x])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for y := range pairs {
|
||||
for z := range channels {
|
||||
lp := pairs[y].Lower()
|
||||
lp.Delimiter = ""
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
Channel: lp.String() + channels[z],
|
||||
Currency: pairs[y],
|
||||
Asset: assets[x],
|
||||
})
|
||||
}
|
||||
}
|
||||
func (b *Binance) GenerateSubscriptions() ([]subscription.Subscription, error) {
|
||||
var channels = make([]string, 0, len(b.Features.Subscriptions))
|
||||
for i := range b.Features.Subscriptions {
|
||||
name, err := channelName(b.Features.Subscriptions[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
channels = append(channels, name)
|
||||
}
|
||||
var subscriptions []subscription.Subscription
|
||||
pairs, err := b.GetEnabledPairs(asset.Spot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for y := range pairs {
|
||||
for z := range channels {
|
||||
lp := pairs[y].Lower()
|
||||
lp.Delimiter = ""
|
||||
subscriptions = append(subscriptions, subscription.Subscription{
|
||||
Channel: lp.String() + "@" + channels[z],
|
||||
Pair: pairs[y],
|
||||
Asset: asset.Spot,
|
||||
})
|
||||
}
|
||||
}
|
||||
return subscriptions, nil
|
||||
}
|
||||
|
||||
// channelName converts a Subscription Config into binance format channel suffix
|
||||
func channelName(s *subscription.Subscription) (string, error) {
|
||||
name, ok := subscriptionNames[s.Channel]
|
||||
if !ok {
|
||||
return name, fmt.Errorf("%w: %s", stream.ErrSubscriptionNotSupported, s.Channel)
|
||||
}
|
||||
|
||||
switch s.Channel {
|
||||
case subscription.OrderbookChannel:
|
||||
if s.Levels != 0 {
|
||||
name += "@" + strconv.Itoa(s.Levels)
|
||||
}
|
||||
if s.Interval.Duration() == time.Second {
|
||||
name += "@1000ms"
|
||||
} else {
|
||||
name += "@" + s.Interval.Short()
|
||||
}
|
||||
case subscription.CandlesChannel:
|
||||
name += "_" + s.Interval.Short()
|
||||
}
|
||||
return name, nil
|
||||
}
|
||||
|
||||
// Subscribe subscribes to a set of channels
|
||||
func (b *Binance) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error {
|
||||
func (b *Binance) Subscribe(channelsToSubscribe []subscription.Subscription) error {
|
||||
payload := WsPayload{
|
||||
Method: "SUBSCRIBE",
|
||||
}
|
||||
@@ -601,7 +626,7 @@ func (b *Binance) Subscribe(channelsToSubscribe []stream.ChannelSubscription) er
|
||||
}
|
||||
|
||||
// Unsubscribe unsubscribes from a set of channels
|
||||
func (b *Binance) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error {
|
||||
func (b *Binance) Unsubscribe(channelsToUnsubscribe []subscription.Subscription) error {
|
||||
payload := WsPayload{
|
||||
Method: "UNSUBSCRIBE",
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/stream/buffer"
|
||||
"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"
|
||||
@@ -211,6 +212,12 @@ func (b *Binance) SetDefaults() {
|
||||
GlobalResultLimit: 1000,
|
||||
},
|
||||
},
|
||||
Subscriptions: []*subscription.Subscription{
|
||||
{Enabled: true, Channel: subscription.TickerChannel},
|
||||
{Enabled: true, Channel: subscription.AllTradesChannel},
|
||||
{Enabled: true, Channel: subscription.CandlesChannel, Interval: kline.OneMin},
|
||||
{Enabled: true, Channel: subscription.OrderbookChannel, Interval: kline.HundredMilliseconds},
|
||||
},
|
||||
}
|
||||
|
||||
b.Requester, err = request.New(b.Name,
|
||||
|
||||
@@ -16,6 +16,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"
|
||||
@@ -539,9 +540,9 @@ func (bi *Binanceus) UpdateLocalBuffer(wsdp *WebsocketDepthStream) (bool, error)
|
||||
}
|
||||
|
||||
// GenerateSubscriptions generates the default subscription set
|
||||
func (bi *Binanceus) GenerateSubscriptions() ([]stream.ChannelSubscription, error) {
|
||||
func (bi *Binanceus) GenerateSubscriptions() ([]subscription.Subscription, error) {
|
||||
var channels = []string{"@ticker", "@trade", "@kline_1m", "@depth@100ms"}
|
||||
var subscriptions []stream.ChannelSubscription
|
||||
var subscriptions []subscription.Subscription
|
||||
|
||||
pairs, err := bi.GetEnabledPairs(asset.Spot)
|
||||
if err != nil {
|
||||
@@ -557,10 +558,10 @@ subs:
|
||||
log.Warnf(log.WebsocketMgr, "BinanceUS has 1024 subscription limit, only subscribing within limit. Requested %v", len(pairs)*len(channels))
|
||||
break subs
|
||||
}
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
Channel: lp.String() + channels[z],
|
||||
Currency: pairs[y],
|
||||
Asset: asset.Spot,
|
||||
subscriptions = append(subscriptions, subscription.Subscription{
|
||||
Channel: lp.String() + channels[z],
|
||||
Pair: pairs[y],
|
||||
Asset: asset.Spot,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -569,7 +570,7 @@ subs:
|
||||
}
|
||||
|
||||
// Subscribe subscribes to a set of channels
|
||||
func (bi *Binanceus) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error {
|
||||
func (bi *Binanceus) Subscribe(channelsToSubscribe []subscription.Subscription) error {
|
||||
payload := WebsocketPayload{
|
||||
Method: "SUBSCRIBE",
|
||||
}
|
||||
@@ -594,7 +595,7 @@ func (bi *Binanceus) Subscribe(channelsToSubscribe []stream.ChannelSubscription)
|
||||
}
|
||||
|
||||
// Unsubscribe unsubscribes from a set of channels
|
||||
func (bi *Binanceus) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error {
|
||||
func (bi *Binanceus) Unsubscribe(channelsToUnsubscribe []subscription.Subscription) error {
|
||||
payload := WebsocketPayload{
|
||||
Method: "UNSUBSCRIBE",
|
||||
}
|
||||
|
||||
@@ -101,9 +101,9 @@ const (
|
||||
bitfinexChecksumFlag = 131072
|
||||
bitfinexWsSequenceFlag = 65536
|
||||
|
||||
// CandlesTimeframeKey configures the timeframe in stream.ChannelSubscription.Params
|
||||
// CandlesTimeframeKey configures the timeframe in subscription.Subscription.Params
|
||||
CandlesTimeframeKey = "_timeframe"
|
||||
// CandlesPeriodKey configures the aggregated period in stream.ChannelSubscription.Params
|
||||
// CandlesPeriodKey configures the aggregated period in subscription.Subscription.Params
|
||||
CandlesPeriodKey = "_period"
|
||||
)
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -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"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
@@ -505,7 +506,7 @@ func (b *Bitfinex) handleWSSubscribed(respRaw []byte) error {
|
||||
|
||||
chanID, err := jsonparser.GetInt(respRaw, "chanId")
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %w 'chanId': %w; Channel: %s Pair: %s", stream.ErrSubscriptionFailure, errParsingWSField, err, c.Channel, c.Currency)
|
||||
return fmt.Errorf("%w: %w 'chanId': %w; Channel: %s Pair: %s", stream.ErrSubscriptionFailure, errParsingWSField, err, c.Channel, c.Pair)
|
||||
}
|
||||
|
||||
// Note: chanID's int type avoids conflicts with the string type subID key because of the type difference
|
||||
@@ -515,7 +516,7 @@ func (b *Bitfinex) handleWSSubscribed(respRaw []byte) error {
|
||||
b.Websocket.AddSuccessfulSubscriptions(*c)
|
||||
|
||||
if b.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "%s Subscribed to Channel: %s Pair: %s ChannelID: %d\n", b.Name, c.Channel, c.Currency, chanID)
|
||||
log.Debugf(log.ExchangeSys, "%s Subscribed to Channel: %s Pair: %s ChannelID: %d\n", b.Name, c.Channel, c.Pair, chanID)
|
||||
}
|
||||
if !b.Websocket.Match.IncomingWithData("subscribe:"+subID, respRaw) {
|
||||
return fmt.Errorf("%v channel subscribe listener not found", subID)
|
||||
@@ -523,7 +524,7 @@ func (b *Bitfinex) handleWSSubscribed(respRaw []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bitfinex) handleWSChannelUpdate(c *stream.ChannelSubscription, eventType string, d []interface{}) error {
|
||||
func (b *Bitfinex) handleWSChannelUpdate(c *subscription.Subscription, eventType string, d []interface{}) error {
|
||||
if eventType == wsChecksum {
|
||||
return b.handleWSChecksum(c, d)
|
||||
}
|
||||
@@ -546,7 +547,7 @@ func (b *Bitfinex) handleWSChannelUpdate(c *stream.ChannelSubscription, eventTyp
|
||||
return fmt.Errorf("%s unhandled channel update: %s", b.Name, c.Channel)
|
||||
}
|
||||
|
||||
func (b *Bitfinex) handleWSChecksum(c *stream.ChannelSubscription, d []interface{}) error {
|
||||
func (b *Bitfinex) handleWSChecksum(c *subscription.Subscription, d []interface{}) error {
|
||||
var token int
|
||||
if f, ok := d[2].(float64); !ok {
|
||||
return common.GetTypeAssertError("float64", d[2], "checksum")
|
||||
@@ -577,7 +578,7 @@ func (b *Bitfinex) handleWSChecksum(c *stream.ChannelSubscription, d []interface
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bitfinex) handleWSBookUpdate(c *stream.ChannelSubscription, d []interface{}) error {
|
||||
func (b *Bitfinex) handleWSBookUpdate(c *subscription.Subscription, d []interface{}) error {
|
||||
var newOrderbook []WebsocketBook
|
||||
obSnapBundle, ok := d[1].([]interface{})
|
||||
if !ok {
|
||||
@@ -631,7 +632,7 @@ func (b *Bitfinex) handleWSBookUpdate(c *stream.ChannelSubscription, d []interfa
|
||||
Amount: rateAmount})
|
||||
}
|
||||
}
|
||||
if err := b.WsInsertSnapshot(c.Currency, c.Asset, newOrderbook, fundingRate); err != nil {
|
||||
if err := b.WsInsertSnapshot(c.Pair, c.Asset, newOrderbook, fundingRate); err != nil {
|
||||
return fmt.Errorf("inserting snapshot error: %s",
|
||||
err)
|
||||
}
|
||||
@@ -663,7 +664,7 @@ func (b *Bitfinex) handleWSBookUpdate(c *stream.ChannelSubscription, d []interfa
|
||||
Amount: amountRate})
|
||||
}
|
||||
|
||||
if err := b.WsUpdateOrderbook(c, c.Currency, c.Asset, newOrderbook, int64(sequenceNo), fundingRate); err != nil {
|
||||
if err := b.WsUpdateOrderbook(c, c.Pair, c.Asset, newOrderbook, int64(sequenceNo), fundingRate); err != nil {
|
||||
return fmt.Errorf("updating orderbook error: %s",
|
||||
err)
|
||||
}
|
||||
@@ -672,7 +673,7 @@ func (b *Bitfinex) handleWSBookUpdate(c *stream.ChannelSubscription, d []interfa
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bitfinex) handleWSCandleUpdate(c *stream.ChannelSubscription, d []interface{}) error {
|
||||
func (b *Bitfinex) handleWSCandleUpdate(c *subscription.Subscription, d []interface{}) error {
|
||||
candleBundle, ok := d[1].([]interface{})
|
||||
if !ok || len(candleBundle) == 0 {
|
||||
return nil
|
||||
@@ -711,7 +712,7 @@ func (b *Bitfinex) handleWSCandleUpdate(c *stream.ChannelSubscription, d []inter
|
||||
}
|
||||
klineData.Exchange = b.Name
|
||||
klineData.AssetType = c.Asset
|
||||
klineData.Pair = c.Currency
|
||||
klineData.Pair = c.Pair
|
||||
b.Websocket.DataHandler <- klineData
|
||||
}
|
||||
case float64:
|
||||
@@ -740,13 +741,13 @@ func (b *Bitfinex) handleWSCandleUpdate(c *stream.ChannelSubscription, d []inter
|
||||
}
|
||||
klineData.Exchange = b.Name
|
||||
klineData.AssetType = c.Asset
|
||||
klineData.Pair = c.Currency
|
||||
klineData.Pair = c.Pair
|
||||
b.Websocket.DataHandler <- klineData
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bitfinex) handleWSTickerUpdate(c *stream.ChannelSubscription, d []interface{}) error {
|
||||
func (b *Bitfinex) handleWSTickerUpdate(c *subscription.Subscription, d []interface{}) error {
|
||||
tickerData, ok := d[1].([]interface{})
|
||||
if !ok {
|
||||
return errors.New("type assertion for tickerData")
|
||||
@@ -754,7 +755,7 @@ func (b *Bitfinex) handleWSTickerUpdate(c *stream.ChannelSubscription, d []inter
|
||||
|
||||
t := &ticker.Price{
|
||||
AssetType: c.Asset,
|
||||
Pair: c.Currency,
|
||||
Pair: c.Pair,
|
||||
ExchangeName: b.Name,
|
||||
}
|
||||
|
||||
@@ -819,7 +820,7 @@ func (b *Bitfinex) handleWSTickerUpdate(c *stream.ChannelSubscription, d []inter
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bitfinex) handleWSTradesUpdate(c *stream.ChannelSubscription, eventType string, d []interface{}) error {
|
||||
func (b *Bitfinex) handleWSTradesUpdate(c *subscription.Subscription, eventType string, d []interface{}) error {
|
||||
if !b.IsSaveTradeDataEnabled() {
|
||||
return nil
|
||||
}
|
||||
@@ -935,7 +936,7 @@ func (b *Bitfinex) handleWSTradesUpdate(c *stream.ChannelSubscription, eventType
|
||||
}
|
||||
trades[i] = trade.Data{
|
||||
TID: strconv.FormatInt(tradeHolder[i].ID, 10),
|
||||
CurrencyPair: c.Currency,
|
||||
CurrencyPair: c.Pair,
|
||||
Timestamp: time.UnixMilli(tradeHolder[i].Timestamp),
|
||||
Price: price,
|
||||
Amount: newAmount,
|
||||
@@ -1508,7 +1509,7 @@ func (b *Bitfinex) WsInsertSnapshot(p currency.Pair, assetType asset.Item, books
|
||||
|
||||
// WsUpdateOrderbook updates the orderbook list, removing and adding to the
|
||||
// orderbook sides
|
||||
func (b *Bitfinex) WsUpdateOrderbook(c *stream.ChannelSubscription, p currency.Pair, assetType asset.Item, book []WebsocketBook, sequenceNo int64, fundingRate bool) error {
|
||||
func (b *Bitfinex) WsUpdateOrderbook(c *subscription.Subscription, p currency.Pair, assetType asset.Item, book []WebsocketBook, sequenceNo int64, fundingRate bool) error {
|
||||
orderbookUpdate := orderbook.Update{
|
||||
Asset: assetType,
|
||||
Pair: p,
|
||||
@@ -1602,8 +1603,8 @@ func (b *Bitfinex) WsUpdateOrderbook(c *stream.ChannelSubscription, p currency.P
|
||||
// resubOrderbook resubscribes the orderbook after a consistency error, probably a failed checksum,
|
||||
// which forces a fresh snapshot. If we don't do this the orderbook will keep erroring and drifting.
|
||||
// Flushing the orderbook happens immediately, but the ReSub itself is a go routine to avoid blocking the WS data channel
|
||||
func (b *Bitfinex) resubOrderbook(c *stream.ChannelSubscription) {
|
||||
if err := b.Websocket.Orderbook.FlushOrderbook(c.Currency, c.Asset); err != nil {
|
||||
func (b *Bitfinex) resubOrderbook(c *subscription.Subscription) {
|
||||
if err := b.Websocket.Orderbook.FlushOrderbook(c.Pair, c.Asset); err != nil {
|
||||
log.Errorf(log.ExchangeSys, "%s error flushing orderbook: %v", b.Name, err)
|
||||
}
|
||||
|
||||
@@ -1616,10 +1617,10 @@ func (b *Bitfinex) resubOrderbook(c *stream.ChannelSubscription) {
|
||||
}
|
||||
|
||||
// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions()
|
||||
func (b *Bitfinex) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) {
|
||||
func (b *Bitfinex) GenerateDefaultSubscriptions() ([]subscription.Subscription, error) {
|
||||
var channels = []string{wsBook, wsTrades, wsTicker, wsCandles}
|
||||
|
||||
var subscriptions []stream.ChannelSubscription
|
||||
var subscriptions []subscription.Subscription
|
||||
assets := b.GetAssetTypes(true)
|
||||
for i := range assets {
|
||||
if !b.IsAssetWebsocketSupported(assets[i]) {
|
||||
@@ -1642,11 +1643,11 @@ func (b *Bitfinex) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription,
|
||||
params[CandlesPeriodKey] = "30"
|
||||
}
|
||||
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
Channel: channels[j],
|
||||
Currency: enabledPairs[k],
|
||||
Params: params,
|
||||
Asset: assets[i],
|
||||
subscriptions = append(subscriptions, subscription.Subscription{
|
||||
Channel: channels[j],
|
||||
Pair: enabledPairs[k],
|
||||
Params: params,
|
||||
Asset: assets[i],
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1664,47 +1665,26 @@ func (b *Bitfinex) ConfigureWS() error {
|
||||
}
|
||||
|
||||
// Subscribe sends a websocket message to receive data from channels
|
||||
func (b *Bitfinex) Subscribe(channels []stream.ChannelSubscription) error {
|
||||
return b.parallelChanOp(channels, b.subscribeToChan)
|
||||
func (b *Bitfinex) Subscribe(channels []subscription.Subscription) error {
|
||||
return b.ParallelChanOp(channels, b.subscribeToChan, 1)
|
||||
}
|
||||
|
||||
// Unsubscribe sends a websocket message to stop receiving data from channels
|
||||
func (b *Bitfinex) Unsubscribe(channels []stream.ChannelSubscription) error {
|
||||
return b.parallelChanOp(channels, b.unsubscribeFromChan)
|
||||
}
|
||||
|
||||
// parallelChanOp performs a single method call in parallel across streams and waits to return any errors
|
||||
func (b *Bitfinex) parallelChanOp(channels []stream.ChannelSubscription, m func(*stream.ChannelSubscription) error) error {
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(len(channels))
|
||||
errC := make(chan error, len(channels))
|
||||
|
||||
for i := range channels {
|
||||
go func(c *stream.ChannelSubscription) {
|
||||
defer wg.Done()
|
||||
if err := m(c); err != nil {
|
||||
errC <- err
|
||||
}
|
||||
}(&channels[i])
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
close(errC)
|
||||
|
||||
var errs error
|
||||
for err := range errC {
|
||||
errs = common.AppendError(errs, err)
|
||||
}
|
||||
|
||||
return errs
|
||||
func (b *Bitfinex) Unsubscribe(channels []subscription.Subscription) error {
|
||||
return b.ParallelChanOp(channels, b.unsubscribeFromChan, 1)
|
||||
}
|
||||
|
||||
// subscribeToChan handles a single subscription and parses the result
|
||||
// on success it adds the subscription to the websocket
|
||||
func (b *Bitfinex) subscribeToChan(c *stream.ChannelSubscription) error {
|
||||
req, err := subscribeReq(c)
|
||||
func (b *Bitfinex) subscribeToChan(chans []subscription.Subscription) error {
|
||||
if len(chans) != 1 {
|
||||
return errors.New("subscription batching limited to 1")
|
||||
}
|
||||
|
||||
c := chans[0]
|
||||
req, err := subscribeReq(&c)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %w; Channel: %s Pair: %s", stream.ErrSubscriptionFailure, err, c.Channel, c.Currency)
|
||||
return fmt.Errorf("%w: %w; Channel: %s Pair: %s", stream.ErrSubscriptionFailure, err, c.Channel, c.Pair)
|
||||
}
|
||||
|
||||
// subId is a single round-trip identifier that provides linking sub requests to chanIDs
|
||||
@@ -1716,22 +1696,22 @@ func (b *Bitfinex) subscribeToChan(c *stream.ChannelSubscription) error {
|
||||
// Otherwise we might drop the first messages after the subscribed resp
|
||||
c.Key = subID // Note subID string type avoids conflicts with later chanID key
|
||||
|
||||
c.State = stream.ChannelSubscribing
|
||||
err = b.Websocket.AddSubscription(c)
|
||||
c.State = subscription.SubscribingState
|
||||
err = b.Websocket.AddSubscription(&c)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w Channel: %s Pair: %s Error: %w", stream.ErrSubscriptionFailure, c.Channel, c.Currency, err)
|
||||
return fmt.Errorf("%w Channel: %s Pair: %s Error: %w", stream.ErrSubscriptionFailure, c.Channel, c.Pair, err)
|
||||
}
|
||||
|
||||
// Always remove the temporary subscription keyed by subID
|
||||
defer b.Websocket.RemoveSubscriptions(*c)
|
||||
defer b.Websocket.RemoveSubscriptions(c)
|
||||
|
||||
respRaw, err := b.Websocket.Conn.SendMessageReturnResponse("subscribe:"+subID, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %w; Channel: %s Pair: %s", stream.ErrSubscriptionFailure, err, c.Channel, c.Currency)
|
||||
return fmt.Errorf("%w: %w; Channel: %s Pair: %s", stream.ErrSubscriptionFailure, err, c.Channel, c.Pair)
|
||||
}
|
||||
|
||||
if err = b.getErrResp(respRaw); err != nil {
|
||||
wErr := fmt.Errorf("%w: %w; Channel: %s Pair: %s", stream.ErrSubscriptionFailure, err, c.Channel, c.Currency)
|
||||
wErr := fmt.Errorf("%w: %w; Channel: %s Pair: %s", stream.ErrSubscriptionFailure, err, c.Channel, c.Pair)
|
||||
b.Websocket.DataHandler <- wErr
|
||||
return wErr
|
||||
}
|
||||
@@ -1740,7 +1720,7 @@ func (b *Bitfinex) subscribeToChan(c *stream.ChannelSubscription) error {
|
||||
}
|
||||
|
||||
// subscribeReq returns a map of request params for subscriptions
|
||||
func subscribeReq(c *stream.ChannelSubscription) (map[string]interface{}, error) {
|
||||
func subscribeReq(c *subscription.Subscription) (map[string]interface{}, error) {
|
||||
req := map[string]interface{}{
|
||||
"event": "subscribe",
|
||||
"channel": c.Channel,
|
||||
@@ -1763,13 +1743,13 @@ func subscribeReq(c *stream.ChannelSubscription) (map[string]interface{}, error)
|
||||
prefix = "f"
|
||||
}
|
||||
|
||||
needsDelimiter := c.Currency.Len() > 6
|
||||
needsDelimiter := c.Pair.Len() > 6
|
||||
|
||||
var formattedPair string
|
||||
if needsDelimiter {
|
||||
formattedPair = c.Currency.Format(currency.PairFormat{Uppercase: true, Delimiter: ":"}).String()
|
||||
formattedPair = c.Pair.Format(currency.PairFormat{Uppercase: true, Delimiter: ":"}).String()
|
||||
} else {
|
||||
formattedPair = currency.PairFormat{Uppercase: true}.Format(c.Currency)
|
||||
formattedPair = currency.PairFormat{Uppercase: true}.Format(c.Pair)
|
||||
}
|
||||
|
||||
if c.Channel == wsCandles {
|
||||
@@ -1796,7 +1776,11 @@ func subscribeReq(c *stream.ChannelSubscription) (map[string]interface{}, error)
|
||||
}
|
||||
|
||||
// unsubscribeFromChan sends a websocket message to stop receiving data from a channel
|
||||
func (b *Bitfinex) unsubscribeFromChan(c *stream.ChannelSubscription) error {
|
||||
func (b *Bitfinex) unsubscribeFromChan(chans []subscription.Subscription) error {
|
||||
if len(chans) != 1 {
|
||||
return errors.New("subscription batching limited to 1")
|
||||
}
|
||||
c := chans[0]
|
||||
chanID, ok := c.Key.(int)
|
||||
if !ok {
|
||||
return common.GetTypeAssertError("int", c.Key, "chanID")
|
||||
@@ -1818,7 +1802,7 @@ func (b *Bitfinex) unsubscribeFromChan(c *stream.ChannelSubscription) error {
|
||||
return wErr
|
||||
}
|
||||
|
||||
b.Websocket.RemoveSubscriptions(*c)
|
||||
b.Websocket.RemoveSubscriptions(c)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"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"
|
||||
)
|
||||
@@ -167,9 +168,9 @@ func (b *Bithumb) wsHandleData(respRaw []byte) error {
|
||||
}
|
||||
|
||||
// GenerateSubscriptions generates the default subscription set
|
||||
func (b *Bithumb) GenerateSubscriptions() ([]stream.ChannelSubscription, error) {
|
||||
func (b *Bithumb) GenerateSubscriptions() ([]subscription.Subscription, error) {
|
||||
var channels = []string{"ticker", "transaction", "orderbookdepth"}
|
||||
var subscriptions []stream.ChannelSubscription
|
||||
var subscriptions []subscription.Subscription
|
||||
pairs, err := b.GetEnabledPairs(asset.Spot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -182,10 +183,10 @@ func (b *Bithumb) GenerateSubscriptions() ([]stream.ChannelSubscription, error)
|
||||
|
||||
for x := range pairs {
|
||||
for y := range channels {
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
Channel: channels[y],
|
||||
Currency: pairs[x].Format(pFmt),
|
||||
Asset: asset.Spot,
|
||||
subscriptions = append(subscriptions, subscription.Subscription{
|
||||
Channel: channels[y],
|
||||
Pair: pairs[x].Format(pFmt),
|
||||
Asset: asset.Spot,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -193,7 +194,7 @@ func (b *Bithumb) GenerateSubscriptions() ([]stream.ChannelSubscription, error)
|
||||
}
|
||||
|
||||
// Subscribe subscribes to a set of channels
|
||||
func (b *Bithumb) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error {
|
||||
func (b *Bithumb) Subscribe(channelsToSubscribe []subscription.Subscription) error {
|
||||
subs := make(map[string]*WsSubscribe)
|
||||
for i := range channelsToSubscribe {
|
||||
s, ok := subs[channelsToSubscribe[i].Channel]
|
||||
@@ -203,7 +204,7 @@ func (b *Bithumb) Subscribe(channelsToSubscribe []stream.ChannelSubscription) er
|
||||
}
|
||||
subs[channelsToSubscribe[i].Channel] = s
|
||||
}
|
||||
s.Symbols = append(s.Symbols, channelsToSubscribe[i].Currency)
|
||||
s.Symbols = append(s.Symbols, channelsToSubscribe[i].Pair)
|
||||
}
|
||||
|
||||
tSub, ok := subs["ticker"]
|
||||
|
||||
@@ -17,6 +17,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/trade"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
)
|
||||
@@ -544,9 +545,9 @@ func (b *Bitmex) processOrderbook(data []OrderBookL2, action string, p currency.
|
||||
}
|
||||
|
||||
// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions()
|
||||
func (b *Bitmex) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) {
|
||||
func (b *Bitmex) GenerateDefaultSubscriptions() ([]subscription.Subscription, error) {
|
||||
channels := []string{bitmexWSOrderbookL2, bitmexWSTrade}
|
||||
subscriptions := []stream.ChannelSubscription{
|
||||
subscriptions := []subscription.Subscription{
|
||||
{
|
||||
Channel: bitmexWSAnnouncement,
|
||||
},
|
||||
@@ -568,10 +569,10 @@ func (b *Bitmex) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, e
|
||||
// There are no L2 orderbook for index assets
|
||||
continue
|
||||
}
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
Channel: channels[z] + ":" + pFmt.Format(contracts[y]),
|
||||
Currency: contracts[y],
|
||||
Asset: assets[x],
|
||||
subscriptions = append(subscriptions, subscription.Subscription{
|
||||
Channel: channels[z] + ":" + pFmt.Format(contracts[y]),
|
||||
Pair: contracts[y],
|
||||
Asset: assets[x],
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -580,7 +581,7 @@ func (b *Bitmex) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, e
|
||||
}
|
||||
|
||||
// GenerateAuthenticatedSubscriptions Adds authenticated subscriptions to websocket to be handled by ManageSubscriptions()
|
||||
func (b *Bitmex) GenerateAuthenticatedSubscriptions() ([]stream.ChannelSubscription, error) {
|
||||
func (b *Bitmex) GenerateAuthenticatedSubscriptions() ([]subscription.Subscription, error) {
|
||||
if !b.Websocket.CanUseAuthenticatedEndpoints() {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -596,7 +597,7 @@ func (b *Bitmex) GenerateAuthenticatedSubscriptions() ([]stream.ChannelSubscript
|
||||
channels := []string{bitmexWSExecution,
|
||||
bitmexWSPosition,
|
||||
}
|
||||
subscriptions := []stream.ChannelSubscription{
|
||||
subscriptions := []subscription.Subscription{
|
||||
{
|
||||
Channel: bitmexWSAffiliate,
|
||||
},
|
||||
@@ -618,10 +619,10 @@ func (b *Bitmex) GenerateAuthenticatedSubscriptions() ([]stream.ChannelSubscript
|
||||
}
|
||||
for i := range channels {
|
||||
for j := range contracts {
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
Channel: channels[i] + ":" + pFmt.Format(contracts[j]),
|
||||
Currency: contracts[j],
|
||||
Asset: asset.PerpetualContract,
|
||||
subscriptions = append(subscriptions, subscription.Subscription{
|
||||
Channel: channels[i] + ":" + pFmt.Format(contracts[j]),
|
||||
Pair: contracts[j],
|
||||
Asset: asset.PerpetualContract,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -629,7 +630,7 @@ func (b *Bitmex) GenerateAuthenticatedSubscriptions() ([]stream.ChannelSubscript
|
||||
}
|
||||
|
||||
// Subscribe subscribes to a websocket channel
|
||||
func (b *Bitmex) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error {
|
||||
func (b *Bitmex) Subscribe(channelsToSubscribe []subscription.Subscription) error {
|
||||
var subscriber WebsocketRequest
|
||||
subscriber.Command = "subscribe"
|
||||
for i := range channelsToSubscribe {
|
||||
@@ -645,7 +646,7 @@ func (b *Bitmex) Subscribe(channelsToSubscribe []stream.ChannelSubscription) err
|
||||
}
|
||||
|
||||
// Unsubscribe sends a websocket message to stop receiving data from the channel
|
||||
func (b *Bitmex) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error {
|
||||
func (b *Bitmex) Unsubscribe(channelsToUnsubscribe []subscription.Subscription) error {
|
||||
var unsubscriber WebsocketRequest
|
||||
unsubscriber.Command = "unsubscribe"
|
||||
|
||||
|
||||
@@ -18,6 +18,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/trade"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
)
|
||||
@@ -230,30 +231,30 @@ func (b *Bitstamp) handleWSOrder(wsResp *websocketResponse, msg []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bitstamp) generateDefaultSubscriptions() ([]stream.ChannelSubscription, error) {
|
||||
func (b *Bitstamp) generateDefaultSubscriptions() ([]subscription.Subscription, error) {
|
||||
enabledCurrencies, err := b.GetEnabledPairs(asset.Spot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var subscriptions []stream.ChannelSubscription
|
||||
var subscriptions []subscription.Subscription
|
||||
for i := range enabledCurrencies {
|
||||
p, err := b.FormatExchangeCurrency(enabledCurrencies[i], asset.Spot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for j := range defaultSubChannels {
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
Channel: defaultSubChannels[j] + "_" + p.String(),
|
||||
Asset: asset.Spot,
|
||||
Currency: p,
|
||||
subscriptions = append(subscriptions, subscription.Subscription{
|
||||
Channel: defaultSubChannels[j] + "_" + p.String(),
|
||||
Asset: asset.Spot,
|
||||
Pair: p,
|
||||
})
|
||||
}
|
||||
if b.Websocket.CanUseAuthenticatedEndpoints() {
|
||||
for j := range defaultAuthSubChannels {
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
Channel: defaultAuthSubChannels[j] + "_" + p.String(),
|
||||
Asset: asset.Spot,
|
||||
Currency: p,
|
||||
subscriptions = append(subscriptions, subscription.Subscription{
|
||||
Channel: defaultAuthSubChannels[j] + "_" + p.String(),
|
||||
Asset: asset.Spot,
|
||||
Pair: p,
|
||||
Params: map[string]interface{}{
|
||||
"auth": struct{}{},
|
||||
},
|
||||
@@ -265,7 +266,7 @@ func (b *Bitstamp) generateDefaultSubscriptions() ([]stream.ChannelSubscription,
|
||||
}
|
||||
|
||||
// Subscribe sends a websocket message to receive data from the channel
|
||||
func (b *Bitstamp) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error {
|
||||
func (b *Bitstamp) Subscribe(channelsToSubscribe []subscription.Subscription) error {
|
||||
var errs error
|
||||
var auth *WebsocketAuthResponse
|
||||
|
||||
@@ -303,7 +304,7 @@ func (b *Bitstamp) Subscribe(channelsToSubscribe []stream.ChannelSubscription) e
|
||||
}
|
||||
|
||||
// Unsubscribe sends a websocket message to stop receiving data from the channel
|
||||
func (b *Bitstamp) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error {
|
||||
func (b *Bitstamp) Unsubscribe(channelsToUnsubscribe []subscription.Subscription) error {
|
||||
var errs error
|
||||
for i := range channelsToUnsubscribe {
|
||||
req := websocketEventRequest{
|
||||
|
||||
@@ -19,6 +19,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"
|
||||
@@ -324,26 +325,26 @@ func (b *BTCMarkets) wsHandleData(respRaw []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *BTCMarkets) generateDefaultSubscriptions() ([]stream.ChannelSubscription, error) {
|
||||
func (b *BTCMarkets) generateDefaultSubscriptions() ([]subscription.Subscription, error) {
|
||||
var channels = []string{wsOB, tick, tradeEndPoint}
|
||||
enabledCurrencies, err := b.GetEnabledPairs(asset.Spot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var subscriptions []stream.ChannelSubscription
|
||||
var subscriptions []subscription.Subscription
|
||||
for i := range channels {
|
||||
for j := range enabledCurrencies {
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
Channel: channels[i],
|
||||
Currency: enabledCurrencies[j],
|
||||
Asset: asset.Spot,
|
||||
subscriptions = append(subscriptions, subscription.Subscription{
|
||||
Channel: channels[i],
|
||||
Pair: enabledCurrencies[j],
|
||||
Asset: asset.Spot,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if b.Websocket.CanUseAuthenticatedEndpoints() {
|
||||
for i := range authChannels {
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
subscriptions = append(subscriptions, subscription.Subscription{
|
||||
Channel: authChannels[i],
|
||||
})
|
||||
}
|
||||
@@ -352,7 +353,7 @@ func (b *BTCMarkets) generateDefaultSubscriptions() ([]stream.ChannelSubscriptio
|
||||
}
|
||||
|
||||
// Subscribe sends a websocket message to receive data from the channel
|
||||
func (b *BTCMarkets) Subscribe(subs []stream.ChannelSubscription) error {
|
||||
func (b *BTCMarkets) Subscribe(subs []subscription.Subscription) error {
|
||||
var payload WsSubscribe
|
||||
if len(subs) > 1 {
|
||||
// TODO: Expand this to stream package as this assumes that we are doing
|
||||
@@ -369,10 +370,10 @@ func (b *BTCMarkets) Subscribe(subs []stream.ChannelSubscription) error {
|
||||
authenticate = true
|
||||
}
|
||||
payload.Channels = append(payload.Channels, subs[i].Channel)
|
||||
if subs[i].Currency.IsEmpty() {
|
||||
if subs[i].Pair.IsEmpty() {
|
||||
continue
|
||||
}
|
||||
pair := subs[i].Currency.String()
|
||||
pair := subs[i].Pair.String()
|
||||
if common.StringDataCompare(payload.MarketIDs, pair) {
|
||||
continue
|
||||
}
|
||||
@@ -407,18 +408,18 @@ func (b *BTCMarkets) Subscribe(subs []stream.ChannelSubscription) error {
|
||||
}
|
||||
|
||||
// Unsubscribe sends a websocket message to manage and remove a subscription.
|
||||
func (b *BTCMarkets) Unsubscribe(subs []stream.ChannelSubscription) error {
|
||||
func (b *BTCMarkets) Unsubscribe(subs []subscription.Subscription) error {
|
||||
payload := WsSubscribe{
|
||||
MessageType: removeSubscription,
|
||||
ClientType: clientType,
|
||||
}
|
||||
for i := range subs {
|
||||
payload.Channels = append(payload.Channels, subs[i].Channel)
|
||||
if subs[i].Currency.IsEmpty() {
|
||||
if subs[i].Pair.IsEmpty() {
|
||||
continue
|
||||
}
|
||||
|
||||
pair := subs[i].Currency.String()
|
||||
pair := subs[i].Pair.String()
|
||||
if common.StringDataCompare(payload.MarketIDs, pair) {
|
||||
continue
|
||||
}
|
||||
@@ -436,10 +437,10 @@ func (b *BTCMarkets) Unsubscribe(subs []stream.ChannelSubscription) error {
|
||||
// ReSubscribeSpecificOrderbook removes the subscription and the subscribes
|
||||
// again to fetch a new snapshot in the event of a de-sync event.
|
||||
func (b *BTCMarkets) ReSubscribeSpecificOrderbook(pair currency.Pair) error {
|
||||
sub := []stream.ChannelSubscription{{
|
||||
Channel: wsOB,
|
||||
Currency: pair,
|
||||
Asset: asset.Spot,
|
||||
sub := []subscription.Subscription{{
|
||||
Channel: wsOB,
|
||||
Pair: pair,
|
||||
Asset: asset.Spot,
|
||||
}}
|
||||
if err := b.Unsubscribe(sub); err != nil {
|
||||
return err
|
||||
|
||||
@@ -17,6 +17,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/trade"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
)
|
||||
@@ -360,24 +361,24 @@ func (b *BTSE) orderbookFilter(price, amount float64) bool {
|
||||
}
|
||||
|
||||
// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions()
|
||||
func (b *BTSE) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) {
|
||||
func (b *BTSE) GenerateDefaultSubscriptions() ([]subscription.Subscription, error) {
|
||||
var channels = []string{"orderBookL2Api:%s_0", "tradeHistory:%s"}
|
||||
pairs, err := b.GetEnabledPairs(asset.Spot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var subscriptions []stream.ChannelSubscription
|
||||
var subscriptions []subscription.Subscription
|
||||
if b.Websocket.CanUseAuthenticatedEndpoints() {
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
subscriptions = append(subscriptions, subscription.Subscription{
|
||||
Channel: "notificationApi",
|
||||
})
|
||||
}
|
||||
for i := range channels {
|
||||
for j := range pairs {
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
Channel: fmt.Sprintf(channels[i], pairs[j]),
|
||||
Currency: pairs[j],
|
||||
Asset: asset.Spot,
|
||||
subscriptions = append(subscriptions, subscription.Subscription{
|
||||
Channel: fmt.Sprintf(channels[i], pairs[j]),
|
||||
Pair: pairs[j],
|
||||
Asset: asset.Spot,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -385,7 +386,7 @@ func (b *BTSE) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, err
|
||||
}
|
||||
|
||||
// Subscribe sends a websocket message to receive data from the channel
|
||||
func (b *BTSE) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error {
|
||||
func (b *BTSE) Subscribe(channelsToSubscribe []subscription.Subscription) error {
|
||||
var sub wsSub
|
||||
sub.Operation = "subscribe"
|
||||
for i := range channelsToSubscribe {
|
||||
@@ -400,7 +401,7 @@ func (b *BTSE) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error
|
||||
}
|
||||
|
||||
// Unsubscribe sends a websocket message to stop receiving data from the channel
|
||||
func (b *BTSE) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error {
|
||||
func (b *BTSE) Unsubscribe(channelsToUnsubscribe []subscription.Subscription) error {
|
||||
var unSub wsSub
|
||||
unSub.Operation = "unsubscribe"
|
||||
for i := range channelsToUnsubscribe {
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/subscription"
|
||||
)
|
||||
|
||||
// WsInverseConnect connects to inverse websocket feed
|
||||
@@ -31,8 +32,8 @@ func (by *Bybit) WsInverseConnect() error {
|
||||
}
|
||||
|
||||
// GenerateInverseDefaultSubscriptions generates default subscription
|
||||
func (by *Bybit) GenerateInverseDefaultSubscriptions() ([]stream.ChannelSubscription, error) {
|
||||
var subscriptions []stream.ChannelSubscription
|
||||
func (by *Bybit) GenerateInverseDefaultSubscriptions() ([]subscription.Subscription, error) {
|
||||
var subscriptions []subscription.Subscription
|
||||
var channels = []string{chanOrderbook, chanPublicTrade, chanPublicTicker}
|
||||
pairs, err := by.GetEnabledPairs(asset.CoinMarginedFutures)
|
||||
if err != nil {
|
||||
@@ -41,10 +42,10 @@ func (by *Bybit) GenerateInverseDefaultSubscriptions() ([]stream.ChannelSubscrip
|
||||
for z := range pairs {
|
||||
for x := range channels {
|
||||
subscriptions = append(subscriptions,
|
||||
stream.ChannelSubscription{
|
||||
Channel: channels[x],
|
||||
Currency: pairs[z],
|
||||
Asset: asset.CoinMarginedFutures,
|
||||
subscription.Subscription{
|
||||
Channel: channels[x],
|
||||
Pair: pairs[z],
|
||||
Asset: asset.CoinMarginedFutures,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -52,16 +53,16 @@ func (by *Bybit) GenerateInverseDefaultSubscriptions() ([]stream.ChannelSubscrip
|
||||
}
|
||||
|
||||
// InverseSubscribe sends a subscription message to linear public channels.
|
||||
func (by *Bybit) InverseSubscribe(channelSubscriptions []stream.ChannelSubscription) error {
|
||||
func (by *Bybit) InverseSubscribe(channelSubscriptions []subscription.Subscription) error {
|
||||
return by.handleInversePayloadSubscription("subscribe", channelSubscriptions)
|
||||
}
|
||||
|
||||
// InverseUnsubscribe sends an unsubscription messages through linear public channels.
|
||||
func (by *Bybit) InverseUnsubscribe(channelSubscriptions []stream.ChannelSubscription) error {
|
||||
func (by *Bybit) InverseUnsubscribe(channelSubscriptions []subscription.Subscription) error {
|
||||
return by.handleInversePayloadSubscription("unsubscribe", channelSubscriptions)
|
||||
}
|
||||
|
||||
func (by *Bybit) handleInversePayloadSubscription(operation string, channelSubscriptions []stream.ChannelSubscription) error {
|
||||
func (by *Bybit) handleInversePayloadSubscription(operation string, channelSubscriptions []subscription.Subscription) error {
|
||||
payloads, err := by.handleSubscriptions(asset.CoinMarginedFutures, operation, channelSubscriptions)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/subscription"
|
||||
)
|
||||
|
||||
// WsLinearConnect connects to linear a websocket feed
|
||||
@@ -40,8 +41,8 @@ func (by *Bybit) WsLinearConnect() error {
|
||||
}
|
||||
|
||||
// GenerateLinearDefaultSubscriptions generates default subscription
|
||||
func (by *Bybit) GenerateLinearDefaultSubscriptions() ([]stream.ChannelSubscription, error) {
|
||||
var subscriptions []stream.ChannelSubscription
|
||||
func (by *Bybit) GenerateLinearDefaultSubscriptions() ([]subscription.Subscription, error) {
|
||||
var subscriptions []subscription.Subscription
|
||||
var channels = []string{chanOrderbook, chanPublicTrade, chanPublicTicker}
|
||||
pairs, err := by.GetEnabledPairs(asset.USDTMarginedFutures)
|
||||
if err != nil {
|
||||
@@ -60,10 +61,10 @@ func (by *Bybit) GenerateLinearDefaultSubscriptions() ([]stream.ChannelSubscript
|
||||
for p := range linearPairMap[a] {
|
||||
for x := range channels {
|
||||
subscriptions = append(subscriptions,
|
||||
stream.ChannelSubscription{
|
||||
Channel: channels[x],
|
||||
Currency: pairs[p],
|
||||
Asset: a,
|
||||
subscription.Subscription{
|
||||
Channel: channels[x],
|
||||
Pair: pairs[p],
|
||||
Asset: a,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -72,16 +73,16 @@ func (by *Bybit) GenerateLinearDefaultSubscriptions() ([]stream.ChannelSubscript
|
||||
}
|
||||
|
||||
// LinearSubscribe sends a subscription message to linear public channels.
|
||||
func (by *Bybit) LinearSubscribe(channelSubscriptions []stream.ChannelSubscription) error {
|
||||
func (by *Bybit) LinearSubscribe(channelSubscriptions []subscription.Subscription) error {
|
||||
return by.handleLinearPayloadSubscription("subscribe", channelSubscriptions)
|
||||
}
|
||||
|
||||
// LinearUnsubscribe sends an unsubscription messages through linear public channels.
|
||||
func (by *Bybit) LinearUnsubscribe(channelSubscriptions []stream.ChannelSubscription) error {
|
||||
func (by *Bybit) LinearUnsubscribe(channelSubscriptions []subscription.Subscription) error {
|
||||
return by.handleLinearPayloadSubscription("unsubscribe", channelSubscriptions)
|
||||
}
|
||||
|
||||
func (by *Bybit) handleLinearPayloadSubscription(operation string, channelSubscriptions []stream.ChannelSubscription) error {
|
||||
func (by *Bybit) handleLinearPayloadSubscription(operation string, channelSubscriptions []subscription.Subscription) error {
|
||||
payloads, err := by.handleSubscriptions(asset.USDTMarginedFutures, operation, channelSubscriptions)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/subscription"
|
||||
)
|
||||
|
||||
// WsOptionsConnect connects to options a websocket feed
|
||||
@@ -38,8 +39,8 @@ func (by *Bybit) WsOptionsConnect() error {
|
||||
}
|
||||
|
||||
// GenerateOptionsDefaultSubscriptions generates default subscription
|
||||
func (by *Bybit) GenerateOptionsDefaultSubscriptions() ([]stream.ChannelSubscription, error) {
|
||||
var subscriptions []stream.ChannelSubscription
|
||||
func (by *Bybit) GenerateOptionsDefaultSubscriptions() ([]subscription.Subscription, error) {
|
||||
var subscriptions []subscription.Subscription
|
||||
var channels = []string{chanOrderbook, chanPublicTrade, chanPublicTicker}
|
||||
pairs, err := by.GetEnabledPairs(asset.Options)
|
||||
if err != nil {
|
||||
@@ -48,10 +49,10 @@ func (by *Bybit) GenerateOptionsDefaultSubscriptions() ([]stream.ChannelSubscrip
|
||||
for z := range pairs {
|
||||
for x := range channels {
|
||||
subscriptions = append(subscriptions,
|
||||
stream.ChannelSubscription{
|
||||
Channel: channels[x],
|
||||
Currency: pairs[z],
|
||||
Asset: asset.Options,
|
||||
subscription.Subscription{
|
||||
Channel: channels[x],
|
||||
Pair: pairs[z],
|
||||
Asset: asset.Options,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -59,16 +60,16 @@ func (by *Bybit) GenerateOptionsDefaultSubscriptions() ([]stream.ChannelSubscrip
|
||||
}
|
||||
|
||||
// OptionSubscribe sends a subscription message to options public channels.
|
||||
func (by *Bybit) OptionSubscribe(channelSubscriptions []stream.ChannelSubscription) error {
|
||||
func (by *Bybit) OptionSubscribe(channelSubscriptions []subscription.Subscription) error {
|
||||
return by.handleOptionsPayloadSubscription("subscribe", channelSubscriptions)
|
||||
}
|
||||
|
||||
// OptionUnsubscribe sends an unsubscription messages through options public channels.
|
||||
func (by *Bybit) OptionUnsubscribe(channelSubscriptions []stream.ChannelSubscription) error {
|
||||
func (by *Bybit) OptionUnsubscribe(channelSubscriptions []subscription.Subscription) error {
|
||||
return by.handleOptionsPayloadSubscription("unsubscribe", channelSubscriptions)
|
||||
}
|
||||
|
||||
func (by *Bybit) handleOptionsPayloadSubscription(operation string, channelSubscriptions []stream.ChannelSubscription) error {
|
||||
func (by *Bybit) handleOptionsPayloadSubscription(operation string, channelSubscriptions []subscription.Subscription) error {
|
||||
payloads, err := by.handleSubscriptions(asset.Options, operation, channelSubscriptions)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -19,6 +19,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"
|
||||
)
|
||||
@@ -133,11 +134,11 @@ func (by *Bybit) WsAuth(ctx context.Context) error {
|
||||
}
|
||||
|
||||
// Subscribe sends a websocket message to receive data from the channel
|
||||
func (by *Bybit) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error {
|
||||
func (by *Bybit) Subscribe(channelsToSubscribe []subscription.Subscription) error {
|
||||
return by.handleSpotSubscription("subscribe", channelsToSubscribe)
|
||||
}
|
||||
|
||||
func (by *Bybit) handleSubscriptions(assetType asset.Item, operation string, channelsToSubscribe []stream.ChannelSubscription) ([]SubscriptionArgument, error) {
|
||||
func (by *Bybit) handleSubscriptions(assetType asset.Item, operation string, channelsToSubscribe []subscription.Subscription) ([]SubscriptionArgument, error) {
|
||||
var args []SubscriptionArgument
|
||||
arg := SubscriptionArgument{
|
||||
Operation: operation,
|
||||
@@ -167,15 +168,15 @@ func (by *Bybit) handleSubscriptions(assetType asset.Item, operation string, cha
|
||||
for i := range channelsToSubscribe {
|
||||
switch channelsToSubscribe[i].Channel {
|
||||
case chanOrderbook:
|
||||
arg.Arguments = append(arg.Arguments, fmt.Sprintf("%s.%d.%s", channelsToSubscribe[i].Channel, 50, channelsToSubscribe[i].Currency.Format(pairFormat).String()))
|
||||
arg.Arguments = append(arg.Arguments, fmt.Sprintf("%s.%d.%s", channelsToSubscribe[i].Channel, 50, channelsToSubscribe[i].Pair.Format(pairFormat).String()))
|
||||
case chanPublicTrade, chanPublicTicker, chanLiquidation, chanLeverageTokenTicker, chanLeverageTokenNav:
|
||||
arg.Arguments = append(arg.Arguments, channelsToSubscribe[i].Channel+"."+channelsToSubscribe[i].Currency.Format(pairFormat).String())
|
||||
arg.Arguments = append(arg.Arguments, channelsToSubscribe[i].Channel+"."+channelsToSubscribe[i].Pair.Format(pairFormat).String())
|
||||
case chanKline, chanLeverageTokenKline:
|
||||
interval, err := intervalToString(kline.FiveMin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
arg.Arguments = append(arg.Arguments, channelsToSubscribe[i].Channel+"."+interval+"."+channelsToSubscribe[i].Currency.Format(pairFormat).String())
|
||||
arg.Arguments = append(arg.Arguments, channelsToSubscribe[i].Channel+"."+interval+"."+channelsToSubscribe[i].Pair.Format(pairFormat).String())
|
||||
case chanPositions, chanExecution, chanOrder, chanWallet, chanGreeks, chanDCP:
|
||||
if chanMap[channelsToSubscribe[i].Channel]&selectedChannels > 0 {
|
||||
continue
|
||||
@@ -203,11 +204,11 @@ func (by *Bybit) handleSubscriptions(assetType asset.Item, operation string, cha
|
||||
}
|
||||
|
||||
// Unsubscribe sends a websocket message to stop receiving data from the channel
|
||||
func (by *Bybit) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error {
|
||||
func (by *Bybit) Unsubscribe(channelsToUnsubscribe []subscription.Subscription) error {
|
||||
return by.handleSpotSubscription("unsubscribe", channelsToUnsubscribe)
|
||||
}
|
||||
|
||||
func (by *Bybit) handleSpotSubscription(operation string, channelsToSubscribe []stream.ChannelSubscription) error {
|
||||
func (by *Bybit) handleSpotSubscription(operation string, channelsToSubscribe []subscription.Subscription) error {
|
||||
payloads, err := by.handleSubscriptions(asset.Spot, operation, channelsToSubscribe)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -238,8 +239,8 @@ func (by *Bybit) handleSpotSubscription(operation string, channelsToSubscribe []
|
||||
}
|
||||
|
||||
// GenerateDefaultSubscriptions generates default subscription
|
||||
func (by *Bybit) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) {
|
||||
var subscriptions []stream.ChannelSubscription
|
||||
func (by *Bybit) GenerateDefaultSubscriptions() ([]subscription.Subscription, error) {
|
||||
var subscriptions []subscription.Subscription
|
||||
var channels = []string{
|
||||
chanPublicTicker,
|
||||
chanOrderbook,
|
||||
@@ -265,17 +266,17 @@ func (by *Bybit) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, e
|
||||
chanDCP,
|
||||
chanWallet:
|
||||
subscriptions = append(subscriptions,
|
||||
stream.ChannelSubscription{
|
||||
subscription.Subscription{
|
||||
Channel: channels[x],
|
||||
Asset: asset.Spot,
|
||||
})
|
||||
default:
|
||||
for z := range pairs {
|
||||
subscriptions = append(subscriptions,
|
||||
stream.ChannelSubscription{
|
||||
Channel: channels[x],
|
||||
Currency: pairs[z],
|
||||
Asset: asset.Spot,
|
||||
subscription.Subscription{
|
||||
Channel: channels[x],
|
||||
Pair: pairs[z],
|
||||
Asset: asset.Spot,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/subscription"
|
||||
"github.com/thrasher-corp/gocryptotrader/portfolio/banking"
|
||||
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
|
||||
)
|
||||
@@ -704,10 +705,10 @@ func TestWsAuth(t *testing.T) {
|
||||
}
|
||||
go c.wsReadData()
|
||||
|
||||
err = c.Subscribe([]stream.ChannelSubscription{
|
||||
err = c.Subscribe([]subscription.Subscription{
|
||||
{
|
||||
Channel: "user",
|
||||
Currency: testPair,
|
||||
Channel: "user",
|
||||
Pair: testPair,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -19,6 +19,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"
|
||||
)
|
||||
@@ -365,32 +366,32 @@ func (c *CoinbasePro) ProcessUpdate(update *WebsocketL2Update) error {
|
||||
}
|
||||
|
||||
// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions()
|
||||
func (c *CoinbasePro) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) {
|
||||
func (c *CoinbasePro) GenerateDefaultSubscriptions() ([]subscription.Subscription, error) {
|
||||
var channels = []string{"heartbeat",
|
||||
"level2_batch", /*Other orderbook feeds require authentication. This is batched in 50ms lots.*/
|
||||
"ticker",
|
||||
"user",
|
||||
"matches"}
|
||||
enabledCurrencies, err := c.GetEnabledPairs(asset.Spot)
|
||||
enabledPairs, err := c.GetEnabledPairs(asset.Spot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var subscriptions []stream.ChannelSubscription
|
||||
var subscriptions []subscription.Subscription
|
||||
for i := range channels {
|
||||
if (channels[i] == "user" || channels[i] == "full") &&
|
||||
!c.IsWebsocketAuthenticationSupported() {
|
||||
continue
|
||||
}
|
||||
for j := range enabledCurrencies {
|
||||
fPair, err := c.FormatExchangeCurrency(enabledCurrencies[j],
|
||||
for j := range enabledPairs {
|
||||
fPair, err := c.FormatExchangeCurrency(enabledPairs[j],
|
||||
asset.Spot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
Channel: channels[i],
|
||||
Currency: fPair,
|
||||
Asset: asset.Spot,
|
||||
subscriptions = append(subscriptions, subscription.Subscription{
|
||||
Channel: channels[i],
|
||||
Pair: fPair,
|
||||
Asset: asset.Spot,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -398,7 +399,7 @@ func (c *CoinbasePro) GenerateDefaultSubscriptions() ([]stream.ChannelSubscripti
|
||||
}
|
||||
|
||||
// Subscribe sends a websocket message to receive data from the channel
|
||||
func (c *CoinbasePro) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error {
|
||||
func (c *CoinbasePro) Subscribe(channelsToSubscribe []subscription.Subscription) error {
|
||||
var creds *account.Credentials
|
||||
var err error
|
||||
if c.IsWebsocketAuthenticationSupported() {
|
||||
@@ -413,7 +414,7 @@ func (c *CoinbasePro) Subscribe(channelsToSubscribe []stream.ChannelSubscription
|
||||
}
|
||||
productIDs := make([]string, 0, len(channelsToSubscribe))
|
||||
for i := range channelsToSubscribe {
|
||||
p := channelsToSubscribe[i].Currency.String()
|
||||
p := channelsToSubscribe[i].Pair.String()
|
||||
if p != "" && !common.StringDataCompare(productIDs, p) {
|
||||
// get all unique productIDs in advance as we generate by channels
|
||||
productIDs = append(productIDs, p)
|
||||
@@ -459,13 +460,13 @@ subscriptions:
|
||||
}
|
||||
|
||||
// Unsubscribe sends a websocket message to stop receiving data from the channel
|
||||
func (c *CoinbasePro) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error {
|
||||
func (c *CoinbasePro) Unsubscribe(channelsToUnsubscribe []subscription.Subscription) error {
|
||||
unsubscribe := WebsocketSubscribe{
|
||||
Type: "unsubscribe",
|
||||
}
|
||||
productIDs := make([]string, 0, len(channelsToUnsubscribe))
|
||||
for i := range channelsToUnsubscribe {
|
||||
p := channelsToUnsubscribe[i].Currency.String()
|
||||
p := channelsToUnsubscribe[i].Pair.String()
|
||||
if p != "" && !common.StringDataCompare(productIDs, p) {
|
||||
// get all unique productIDs in advance as we generate by channels
|
||||
productIDs = append(productIDs, p)
|
||||
|
||||
@@ -18,6 +18,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"
|
||||
@@ -597,19 +598,19 @@ func (c *COINUT) WsProcessOrderbookUpdate(update *WsOrderbookUpdate) error {
|
||||
}
|
||||
|
||||
// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions()
|
||||
func (c *COINUT) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) {
|
||||
func (c *COINUT) GenerateDefaultSubscriptions() ([]subscription.Subscription, error) {
|
||||
var channels = []string{"inst_tick", "inst_order_book", "inst_trade"}
|
||||
var subscriptions []stream.ChannelSubscription
|
||||
enabledCurrencies, err := c.GetEnabledPairs(asset.Spot)
|
||||
var subscriptions []subscription.Subscription
|
||||
enabledPairs, err := c.GetEnabledPairs(asset.Spot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := range channels {
|
||||
for j := range enabledCurrencies {
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
Channel: channels[i],
|
||||
Currency: enabledCurrencies[j],
|
||||
Asset: asset.Spot,
|
||||
for j := range enabledPairs {
|
||||
subscriptions = append(subscriptions, subscription.Subscription{
|
||||
Channel: channels[i],
|
||||
Pair: enabledPairs[j],
|
||||
Asset: asset.Spot,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -617,10 +618,10 @@ func (c *COINUT) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, e
|
||||
}
|
||||
|
||||
// Subscribe sends a websocket message to receive data from the channel
|
||||
func (c *COINUT) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error {
|
||||
func (c *COINUT) Subscribe(channelsToSubscribe []subscription.Subscription) error {
|
||||
var errs error
|
||||
for i := range channelsToSubscribe {
|
||||
fPair, err := c.FormatExchangeCurrency(channelsToSubscribe[i].Currency, asset.Spot)
|
||||
fPair, err := c.FormatExchangeCurrency(channelsToSubscribe[i].Pair, asset.Spot)
|
||||
if err != nil {
|
||||
errs = common.AppendError(errs, err)
|
||||
continue
|
||||
@@ -646,10 +647,10 @@ func (c *COINUT) Subscribe(channelsToSubscribe []stream.ChannelSubscription) err
|
||||
}
|
||||
|
||||
// Unsubscribe sends a websocket message to stop receiving data from the channel
|
||||
func (c *COINUT) Unsubscribe(channelToUnsubscribe []stream.ChannelSubscription) error {
|
||||
func (c *COINUT) Unsubscribe(channelToUnsubscribe []subscription.Subscription) error {
|
||||
var errs error
|
||||
for i := range channelToUnsubscribe {
|
||||
fPair, err := c.FormatExchangeCurrency(channelToUnsubscribe[i].Currency, asset.Spot)
|
||||
fPair, err := c.FormatExchangeCurrency(channelToUnsubscribe[i].Pair, asset.Spot)
|
||||
if err != nil {
|
||||
errs = common.AppendError(errs, err)
|
||||
continue
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
@@ -28,6 +29,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/protocol"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"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"
|
||||
@@ -61,6 +63,7 @@ var (
|
||||
errAssetConfigFormatIsNil = errors.New("asset type config format is nil")
|
||||
errSetDefaultsNotCalled = errors.New("set defaults not called")
|
||||
errExchangeIsNil = errors.New("exchange is nil")
|
||||
errBatchSizeZero = errors.New("batch size cannot be 0")
|
||||
)
|
||||
|
||||
// SetRequester sets the instance of the requester
|
||||
@@ -157,10 +160,38 @@ func (b *Base) SetFeatureDefaults() {
|
||||
b.SetFillsFeedStatus(b.Config.Features.Enabled.FillsFeed)
|
||||
}
|
||||
|
||||
b.SetSubscriptionsFromConfig()
|
||||
|
||||
b.Features.Enabled.AutoPairUpdates = b.Config.Features.Enabled.AutoPairUpdates
|
||||
}
|
||||
}
|
||||
|
||||
// SetSubscriptionsFromConfig sets the subscriptions from config
|
||||
// If the subscriptions config is empty then Config will be updated from the exchange subscriptions,
|
||||
// allowing e.SetDefaults to set default subscriptions for an exchange to update user's config
|
||||
// Subscriptions not Enabled are skipped, meaning that e.Features.Subscriptions only contains Enabled subscriptions
|
||||
func (b *Base) SetSubscriptionsFromConfig() {
|
||||
b.settingsMutex.Lock()
|
||||
defer b.settingsMutex.Unlock()
|
||||
if len(b.Config.Features.Subscriptions) == 0 {
|
||||
b.Config.Features.Subscriptions = b.Features.Subscriptions
|
||||
return
|
||||
}
|
||||
b.Features.Subscriptions = []*subscription.Subscription{}
|
||||
for _, s := range b.Config.Features.Subscriptions {
|
||||
if s.Enabled {
|
||||
b.Features.Subscriptions = append(b.Features.Subscriptions, s)
|
||||
}
|
||||
}
|
||||
if b.Verbose {
|
||||
names := make([]string, 0, len(b.Features.Subscriptions))
|
||||
for _, s := range b.Features.Subscriptions {
|
||||
names = append(names, s.Channel)
|
||||
}
|
||||
log.Debugf(log.ExchangeSys, "Set %v 'Subscriptions' to %v", b.Name, strings.Join(names, ", "))
|
||||
}
|
||||
}
|
||||
|
||||
// SupportsRESTTickerBatchUpdates returns whether or not the
|
||||
// exchange supports REST batch ticker fetching
|
||||
func (b *Base) SupportsRESTTickerBatchUpdates() bool {
|
||||
@@ -1135,7 +1166,7 @@ func (b *Base) FlushWebsocketChannels() error {
|
||||
|
||||
// SubscribeToWebsocketChannels appends to ChannelsToSubscribe
|
||||
// which lets websocket.manageSubscriptions handle subscribing
|
||||
func (b *Base) SubscribeToWebsocketChannels(channels []stream.ChannelSubscription) error {
|
||||
func (b *Base) SubscribeToWebsocketChannels(channels []subscription.Subscription) error {
|
||||
if b.Websocket == nil {
|
||||
return common.ErrFunctionNotSupported
|
||||
}
|
||||
@@ -1144,7 +1175,7 @@ func (b *Base) SubscribeToWebsocketChannels(channels []stream.ChannelSubscriptio
|
||||
|
||||
// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe
|
||||
// which lets websocket.manageSubscriptions handle unsubscribing
|
||||
func (b *Base) UnsubscribeToWebsocketChannels(channels []stream.ChannelSubscription) error {
|
||||
func (b *Base) UnsubscribeToWebsocketChannels(channels []subscription.Subscription) error {
|
||||
if b.Websocket == nil {
|
||||
return common.ErrFunctionNotSupported
|
||||
}
|
||||
@@ -1152,7 +1183,7 @@ func (b *Base) UnsubscribeToWebsocketChannels(channels []stream.ChannelSubscript
|
||||
}
|
||||
|
||||
// GetSubscriptions returns a copied list of subscriptions
|
||||
func (b *Base) GetSubscriptions() ([]stream.ChannelSubscription, error) {
|
||||
func (b *Base) GetSubscriptions() ([]subscription.Subscription, error) {
|
||||
if b.Websocket == nil {
|
||||
return nil, common.ErrFunctionNotSupported
|
||||
}
|
||||
@@ -1811,3 +1842,37 @@ func (b *Base) IsPairEnabled(pair currency.Pair, a asset.Item) (bool, error) {
|
||||
func (b *Base) GetOpenInterest(context.Context, ...key.PairAsset) ([]futures.OpenInterest, error) {
|
||||
return nil, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// ParallelChanOp performs a single method call in parallel across streams and waits to return any errors
|
||||
func (b *Base) ParallelChanOp(channels []subscription.Subscription, m func([]subscription.Subscription) error, batchSize int) error {
|
||||
wg := sync.WaitGroup{}
|
||||
errC := make(chan error, len(channels))
|
||||
if batchSize == 0 {
|
||||
return errBatchSizeZero
|
||||
}
|
||||
|
||||
var j int
|
||||
for i := 0; i < len(channels); i += batchSize {
|
||||
j += batchSize
|
||||
if j >= len(channels) {
|
||||
j = len(channels)
|
||||
}
|
||||
wg.Add(1)
|
||||
go func(c []subscription.Subscription) {
|
||||
defer wg.Done()
|
||||
if err := m(c); err != nil {
|
||||
errC <- err
|
||||
}
|
||||
}(channels[i:j])
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
close(errC)
|
||||
|
||||
var errs error
|
||||
for err := range errC {
|
||||
errs = common.AppendError(errs, err)
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/protocol"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"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/portfolio/banking"
|
||||
)
|
||||
@@ -1262,8 +1263,8 @@ func TestSetupDefaults(t *testing.T) {
|
||||
DefaultURL: "ws://something.com",
|
||||
RunningURL: "ws://something.com",
|
||||
Connector: func() error { return nil },
|
||||
GenerateSubscriptions: func() ([]stream.ChannelSubscription, error) { return []stream.ChannelSubscription{}, nil },
|
||||
Subscriber: func(cs []stream.ChannelSubscription) error { return nil },
|
||||
GenerateSubscriptions: func() ([]subscription.Subscription, error) { return []subscription.Subscription{}, nil },
|
||||
Subscriber: func(cs []subscription.Subscription) error { return nil },
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -1611,8 +1612,8 @@ func TestIsWebsocketEnabled(t *testing.T) {
|
||||
DefaultURL: "ws://something.com",
|
||||
RunningURL: "ws://something.com",
|
||||
Connector: func() error { return nil },
|
||||
GenerateSubscriptions: func() ([]stream.ChannelSubscription, error) { return nil, nil },
|
||||
Subscriber: func(cs []stream.ChannelSubscription) error { return nil },
|
||||
GenerateSubscriptions: func() ([]subscription.Subscription, error) { return nil, nil },
|
||||
Subscriber: func(cs []subscription.Subscription) error { return nil },
|
||||
})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
@@ -3269,3 +3270,63 @@ func TestGetCachedOpenInterest(t *testing.T) {
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
// TestSetSubscriptionsFromConfig tests the setting and loading of subscriptions from config and exchange defaults
|
||||
func TestSetSubscriptionsFromConfig(t *testing.T) {
|
||||
t.Parallel()
|
||||
b := Base{
|
||||
Config: &config.Exchange{
|
||||
Features: &config.FeaturesConfig{},
|
||||
},
|
||||
}
|
||||
subs := []*subscription.Subscription{
|
||||
{Channel: subscription.CandlesChannel, Interval: kline.OneDay, Enabled: true},
|
||||
}
|
||||
b.Features.Subscriptions = subs
|
||||
b.SetSubscriptionsFromConfig()
|
||||
assert.ElementsMatch(t, subs, b.Config.Features.Subscriptions, "Config Subscriptions should be updated")
|
||||
assert.ElementsMatch(t, subs, b.Features.Subscriptions, "Subscriptions should be the same")
|
||||
|
||||
subs = []*subscription.Subscription{
|
||||
{Channel: subscription.OrderbookChannel, Interval: kline.OneDay, Enabled: true},
|
||||
}
|
||||
b.Config.Features.Subscriptions = subs
|
||||
b.SetSubscriptionsFromConfig()
|
||||
assert.ElementsMatch(t, subs, b.Features.Subscriptions, "Subscriptions should be updated from Config")
|
||||
assert.ElementsMatch(t, subs, b.Config.Features.Subscriptions, "Config Subscriptions should be the same")
|
||||
}
|
||||
|
||||
// TestParallelChanOp unit tests the helper func ParallelChanOp
|
||||
func TestParallelChanOp(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := []subscription.Subscription{
|
||||
{Channel: "red"},
|
||||
{Channel: "blue"},
|
||||
{Channel: "violent"},
|
||||
{Channel: "spin"},
|
||||
{Channel: "charm"},
|
||||
}
|
||||
run := make(chan struct{}, len(c)*2)
|
||||
b := Base{}
|
||||
errC := make(chan error, 1)
|
||||
go func() {
|
||||
errC <- b.ParallelChanOp(c, func(c []subscription.Subscription) error {
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
run <- struct{}{}
|
||||
switch c[0].Channel {
|
||||
case "spin", "violent":
|
||||
return errors.New(c[0].Channel)
|
||||
}
|
||||
return nil
|
||||
}, 1)
|
||||
}()
|
||||
f := func(ct *assert.CollectT) {
|
||||
if assert.Len(ct, errC, 1, "Should eventually have an error") {
|
||||
err := <-errC
|
||||
assert.ErrorContains(ct, err, "violent", "Should get a violent error")
|
||||
assert.ErrorContains(ct, err, "spin", "Should get a spin error")
|
||||
}
|
||||
}
|
||||
assert.EventuallyWithT(t, f, 500*time.Millisecond, 50*time.Millisecond, "ParallelChanOp should complete within 500ms not 5*300ms")
|
||||
assert.Len(t, run, len(c), "Every channel was run to completion")
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/protocol"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/subscription"
|
||||
)
|
||||
|
||||
// Endpoint authentication types
|
||||
@@ -149,8 +150,9 @@ type WithdrawalHistory struct {
|
||||
// Features stores the supported and enabled features
|
||||
// for the exchange
|
||||
type Features struct {
|
||||
Supports FeaturesSupported
|
||||
Enabled FeaturesEnabled
|
||||
Supports FeaturesSupported
|
||||
Enabled FeaturesEnabled
|
||||
Subscriptions []*subscription.Subscription
|
||||
}
|
||||
|
||||
// FeaturesEnabled stores the exchange enabled features
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -21,6 +21,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/trade"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
)
|
||||
@@ -62,7 +63,7 @@ func (g *Gemini) WsConnect() error {
|
||||
}
|
||||
|
||||
// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions()
|
||||
func (g *Gemini) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) {
|
||||
func (g *Gemini) GenerateDefaultSubscriptions() ([]subscription.Subscription, error) {
|
||||
// See gemini_types.go for more subscription/candle vars
|
||||
var channels = []string{
|
||||
marketDataLevel2,
|
||||
@@ -74,13 +75,13 @@ func (g *Gemini) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, e
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var subscriptions []stream.ChannelSubscription
|
||||
var subscriptions []subscription.Subscription
|
||||
for x := range channels {
|
||||
for y := range pairs {
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
Channel: channels[x],
|
||||
Currency: pairs[y],
|
||||
Asset: asset.Spot,
|
||||
subscriptions = append(subscriptions, subscription.Subscription{
|
||||
Channel: channels[x],
|
||||
Pair: pairs[y],
|
||||
Asset: asset.Spot,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -88,7 +89,7 @@ func (g *Gemini) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, e
|
||||
}
|
||||
|
||||
// Subscribe sends a websocket message to receive data from the channel
|
||||
func (g *Gemini) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error {
|
||||
func (g *Gemini) Subscribe(channelsToSubscribe []subscription.Subscription) error {
|
||||
channels := make([]string, 0, len(channelsToSubscribe))
|
||||
for x := range channelsToSubscribe {
|
||||
if common.StringDataCompareInsensitive(channels, channelsToSubscribe[x].Channel) {
|
||||
@@ -99,10 +100,10 @@ func (g *Gemini) Subscribe(channelsToSubscribe []stream.ChannelSubscription) err
|
||||
|
||||
var pairs currency.Pairs
|
||||
for x := range channelsToSubscribe {
|
||||
if pairs.Contains(channelsToSubscribe[x].Currency, true) {
|
||||
if pairs.Contains(channelsToSubscribe[x].Pair, true) {
|
||||
continue
|
||||
}
|
||||
pairs = append(pairs, channelsToSubscribe[x].Currency)
|
||||
pairs = append(pairs, channelsToSubscribe[x].Pair)
|
||||
}
|
||||
|
||||
fmtPairs, err := g.FormatExchangeCurrencies(pairs, asset.Spot)
|
||||
@@ -132,7 +133,7 @@ func (g *Gemini) Subscribe(channelsToSubscribe []stream.ChannelSubscription) err
|
||||
}
|
||||
|
||||
// Unsubscribe sends a websocket message to stop receiving data from the channel
|
||||
func (g *Gemini) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error {
|
||||
func (g *Gemini) Unsubscribe(channelsToUnsubscribe []subscription.Subscription) error {
|
||||
channels := make([]string, 0, len(channelsToUnsubscribe))
|
||||
for x := range channelsToUnsubscribe {
|
||||
if common.StringDataCompareInsensitive(channels, channelsToUnsubscribe[x].Channel) {
|
||||
@@ -143,10 +144,10 @@ func (g *Gemini) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription)
|
||||
|
||||
var pairs currency.Pairs
|
||||
for x := range channelsToUnsubscribe {
|
||||
if pairs.Contains(channelsToUnsubscribe[x].Currency, true) {
|
||||
if pairs.Contains(channelsToUnsubscribe[x].Pair, true) {
|
||||
continue
|
||||
}
|
||||
pairs = append(pairs, channelsToUnsubscribe[x].Currency)
|
||||
pairs = append(pairs, channelsToUnsubscribe[x].Pair)
|
||||
}
|
||||
|
||||
fmtPairs, err := g.FormatExchangeCurrencies(pairs, asset.Spot)
|
||||
|
||||
@@ -18,6 +18,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"
|
||||
@@ -465,15 +466,15 @@ func (h *HitBTC) WsProcessOrderbookUpdate(update *WsOrderbook) error {
|
||||
}
|
||||
|
||||
// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions()
|
||||
func (h *HitBTC) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) {
|
||||
func (h *HitBTC) GenerateDefaultSubscriptions() ([]subscription.Subscription, error) {
|
||||
var channels = []string{"subscribeTicker",
|
||||
"subscribeOrderbook",
|
||||
"subscribeTrades",
|
||||
"subscribeCandles"}
|
||||
|
||||
var subscriptions []stream.ChannelSubscription
|
||||
var subscriptions []subscription.Subscription
|
||||
if h.Websocket.CanUseAuthenticatedEndpoints() {
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
subscriptions = append(subscriptions, subscription.Subscription{
|
||||
Channel: "subscribeReports",
|
||||
})
|
||||
}
|
||||
@@ -489,10 +490,10 @@ func (h *HitBTC) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, e
|
||||
}
|
||||
|
||||
enabledCurrencies[j].Delimiter = ""
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
Channel: channels[i],
|
||||
Currency: fPair,
|
||||
Asset: asset.Spot,
|
||||
subscriptions = append(subscriptions, subscription.Subscription{
|
||||
Channel: channels[i],
|
||||
Pair: fPair,
|
||||
Asset: asset.Spot,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -500,7 +501,7 @@ func (h *HitBTC) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, e
|
||||
}
|
||||
|
||||
// Subscribe sends a websocket message to receive data from the channel
|
||||
func (h *HitBTC) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error {
|
||||
func (h *HitBTC) Subscribe(channelsToSubscribe []subscription.Subscription) error {
|
||||
var errs error
|
||||
for i := range channelsToSubscribe {
|
||||
subscribe := WsRequest{
|
||||
@@ -508,8 +509,8 @@ func (h *HitBTC) Subscribe(channelsToSubscribe []stream.ChannelSubscription) err
|
||||
ID: h.Websocket.Conn.GenerateMessageID(false),
|
||||
}
|
||||
|
||||
if channelsToSubscribe[i].Currency.String() != "" {
|
||||
subscribe.Params.Symbol = channelsToSubscribe[i].Currency.String()
|
||||
if channelsToSubscribe[i].Pair.String() != "" {
|
||||
subscribe.Params.Symbol = channelsToSubscribe[i].Pair.String()
|
||||
}
|
||||
if strings.EqualFold(channelsToSubscribe[i].Channel, "subscribeTrades") {
|
||||
subscribe.Params.Limit = 100
|
||||
@@ -532,7 +533,7 @@ func (h *HitBTC) Subscribe(channelsToSubscribe []stream.ChannelSubscription) err
|
||||
}
|
||||
|
||||
// Unsubscribe sends a websocket message to stop receiving data from the channel
|
||||
func (h *HitBTC) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error {
|
||||
func (h *HitBTC) Unsubscribe(channelsToUnsubscribe []subscription.Subscription) error {
|
||||
var errs error
|
||||
for i := range channelsToUnsubscribe {
|
||||
unsubscribeChannel := strings.Replace(channelsToUnsubscribe[i].Channel,
|
||||
@@ -545,7 +546,7 @@ func (h *HitBTC) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription)
|
||||
Method: unsubscribeChannel,
|
||||
}
|
||||
|
||||
unsubscribe.Params.Symbol = channelsToUnsubscribe[i].Currency.String()
|
||||
unsubscribe.Params.Symbol = channelsToUnsubscribe[i].Pair.String()
|
||||
if strings.EqualFold(unsubscribeChannel, "unsubscribeTrades") {
|
||||
unsubscribe.Params.Limit = 100
|
||||
} else if strings.EqualFold(unsubscribeChannel, "unsubscribeCandles") {
|
||||
|
||||
@@ -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"
|
||||
@@ -514,15 +515,15 @@ func (h *HUOBI) WsProcessOrderbook(update *WsDepth, symbol string) error {
|
||||
}
|
||||
|
||||
// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions()
|
||||
func (h *HUOBI) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) {
|
||||
func (h *HUOBI) GenerateDefaultSubscriptions() ([]subscription.Subscription, error) {
|
||||
var channels = []string{wsMarketKline,
|
||||
wsMarketDepth,
|
||||
wsMarketTrade,
|
||||
wsMarketTicker}
|
||||
var subscriptions []stream.ChannelSubscription
|
||||
var subscriptions []subscription.Subscription
|
||||
if h.Websocket.CanUseAuthenticatedEndpoints() {
|
||||
channels = append(channels, "orders.%v", "orders.%v.update")
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
subscriptions = append(subscriptions, subscription.Subscription{
|
||||
Channel: "accounts",
|
||||
})
|
||||
}
|
||||
@@ -535,9 +536,9 @@ func (h *HUOBI) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, er
|
||||
enabledCurrencies[j].Delimiter = ""
|
||||
channel := fmt.Sprintf(channels[i],
|
||||
enabledCurrencies[j].Lower().String())
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
Channel: channel,
|
||||
Currency: enabledCurrencies[j],
|
||||
subscriptions = append(subscriptions, subscription.Subscription{
|
||||
Channel: channel,
|
||||
Pair: enabledCurrencies[j],
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -545,7 +546,7 @@ func (h *HUOBI) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, er
|
||||
}
|
||||
|
||||
// Subscribe sends a websocket message to receive data from the channel
|
||||
func (h *HUOBI) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error {
|
||||
func (h *HUOBI) Subscribe(channelsToSubscribe []subscription.Subscription) error {
|
||||
var creds *account.Credentials
|
||||
if h.Websocket.CanUseAuthenticatedEndpoints() {
|
||||
var err error
|
||||
@@ -585,7 +586,7 @@ func (h *HUOBI) Subscribe(channelsToSubscribe []stream.ChannelSubscription) erro
|
||||
}
|
||||
|
||||
// Unsubscribe sends a websocket message to stop receiving data from the channel
|
||||
func (h *HUOBI) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error {
|
||||
func (h *HUOBI) Unsubscribe(channelsToUnsubscribe []subscription.Subscription) error {
|
||||
var creds *account.Credentials
|
||||
if h.Websocket.CanUseAuthenticatedEndpoints() {
|
||||
var err error
|
||||
|
||||
@@ -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/portfolio/withdraw"
|
||||
@@ -71,9 +72,9 @@ type IBotExchange interface {
|
||||
EnableRateLimiter() error
|
||||
GetServerTime(ctx context.Context, ai asset.Item) (time.Time, error)
|
||||
GetWebsocket() (*stream.Websocket, error)
|
||||
SubscribeToWebsocketChannels(channels []stream.ChannelSubscription) error
|
||||
UnsubscribeToWebsocketChannels(channels []stream.ChannelSubscription) error
|
||||
GetSubscriptions() ([]stream.ChannelSubscription, error)
|
||||
SubscribeToWebsocketChannels(channels []subscription.Subscription) error
|
||||
UnsubscribeToWebsocketChannels(channels []subscription.Subscription) error
|
||||
GetSubscriptions() ([]subscription.Subscription, error)
|
||||
FlushWebsocketChannels() error
|
||||
AuthenticateWebsocket(ctx context.Context) error
|
||||
GetOrderExecutionLimits(a asset.Item, cp currency.Pair) (order.MinMaxLevel, error)
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package kline
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -143,6 +145,31 @@ func (i Interval) Short() string {
|
||||
return s
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface for Intervals
|
||||
// It does not validate the duration is aligned, only that it is a parsable duration
|
||||
func (i *Interval) UnmarshalJSON(text []byte) error {
|
||||
text = bytes.Trim(text, `"`)
|
||||
if len(bytes.TrimLeft(text, `0123456789`)) > 0 { // contains non-numerics, ParseDuration can handle errors
|
||||
d, err := time.ParseDuration(string(text))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*i = Interval(d)
|
||||
} else {
|
||||
n, err := strconv.ParseInt(string(text), 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*i = Interval(n)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalText implements the TextMarshaler interface for Intervals
|
||||
func (i Interval) MarshalText() ([]byte, error) {
|
||||
return []byte(i.Short()), nil
|
||||
}
|
||||
|
||||
// addPadding inserts padding time aligned when exchanges do not supply all data
|
||||
// when there is no activity in a certain time interval.
|
||||
// Start defines the request start and due to potential no activity from this
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/database"
|
||||
@@ -1396,3 +1397,18 @@ func TestGetIntervalResultLimit(t *testing.T) {
|
||||
t.Errorf("received '%v' expected '%v'", limit, 1337)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalJSON(t *testing.T) {
|
||||
i := new(Interval)
|
||||
err := i.UnmarshalJSON([]byte(`"3m"`))
|
||||
assert.NoError(t, err, "UnmarshalJSON should not error")
|
||||
assert.Equal(t, time.Minute*3, i.Duration(), "Interval should have correct value")
|
||||
err = i.UnmarshalJSON([]byte(`"15s"`))
|
||||
assert.NoError(t, err, "UnmarshalJSON should not error")
|
||||
assert.Equal(t, time.Second*15, i.Duration(), "Interval should have correct value")
|
||||
err = i.UnmarshalJSON([]byte(`720000000000`))
|
||||
assert.NoError(t, err, "UnmarshalJSON should not error")
|
||||
assert.Equal(t, time.Minute*12, i.Duration(), "Interval should have correct value")
|
||||
err = i.UnmarshalJSON([]byte(`"6hedgehogs"`))
|
||||
assert.ErrorContains(t, err, "unknown unit", "UnmarshalJSON should error")
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/subscription"
|
||||
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
|
||||
)
|
||||
|
||||
@@ -1245,10 +1246,10 @@ func setupWsTests(t *testing.T) {
|
||||
// TestWebsocketSubscribe tests returning a message with an id
|
||||
func TestWebsocketSubscribe(t *testing.T) {
|
||||
setupWsTests(t)
|
||||
err := k.Subscribe([]stream.ChannelSubscription{
|
||||
err := k.Subscribe([]subscription.Subscription{
|
||||
{
|
||||
Channel: defaultSubscribedChannels[0],
|
||||
Currency: currency.NewPairWithDelimiter("XBT", "USD", "/"),
|
||||
Channel: defaultSubscribedChannels[0],
|
||||
Pair: currency.NewPairWithDelimiter("XBT", "USD", "/"),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/subscription"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -497,11 +497,11 @@ type WithdrawStatusResponse struct {
|
||||
|
||||
// WebsocketSubscriptionEventRequest handles WS subscription events
|
||||
type WebsocketSubscriptionEventRequest struct {
|
||||
Event string `json:"event"` // subscribe
|
||||
RequestID int64 `json:"reqid,omitempty"` // Optional, client originated ID reflected in response message.
|
||||
Pairs []string `json:"pair,omitempty"` // Array of currency pairs (pair1,pair2,pair3).
|
||||
Subscription WebsocketSubscriptionData `json:"subscription,omitempty"`
|
||||
Channels []stream.ChannelSubscription `json:"-"` // Keeps track of associated subscriptions in batched outgoings
|
||||
Event string `json:"event"` // subscribe
|
||||
RequestID int64 `json:"reqid,omitempty"` // Optional, client originated ID reflected in response message.
|
||||
Pairs []string `json:"pair,omitempty"` // Array of currency pairs (pair1,pair2,pair3).
|
||||
Subscription WebsocketSubscriptionData `json:"subscription,omitempty"`
|
||||
Channels []subscription.Subscription `json:"-"` // Keeps track of associated subscriptions in batched outgoings
|
||||
}
|
||||
|
||||
// WebsocketBaseEventRequest Just has an "event" property
|
||||
|
||||
@@ -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"
|
||||
@@ -846,7 +847,7 @@ func (k *Kraken) wsProcessOrderBook(channelData *WebsocketChannelData, data map[
|
||||
if err != nil {
|
||||
outbound := channelData.Pair // Format required "XBT/USD"
|
||||
outbound.Delimiter = "/"
|
||||
go func(resub *stream.ChannelSubscription) {
|
||||
go func(resub *subscription.Subscription) {
|
||||
// This was locking the main websocket reader routine and a
|
||||
// backlog occurred. So put this into it's own go routine.
|
||||
errResub := k.Websocket.ResubscribeToChannel(resub)
|
||||
@@ -856,10 +857,10 @@ func (k *Kraken) wsProcessOrderBook(channelData *WebsocketChannelData, data map[
|
||||
resub,
|
||||
errResub)
|
||||
}
|
||||
}(&stream.ChannelSubscription{
|
||||
Channel: krakenWsOrderbook,
|
||||
Currency: outbound,
|
||||
Asset: asset.Spot,
|
||||
}(&subscription.Subscription{
|
||||
Channel: krakenWsOrderbook,
|
||||
Pair: outbound,
|
||||
Asset: asset.Spot,
|
||||
})
|
||||
return err
|
||||
}
|
||||
@@ -1209,25 +1210,25 @@ func (k *Kraken) wsProcessCandles(channelData *WebsocketChannelData, data []inte
|
||||
}
|
||||
|
||||
// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions()
|
||||
func (k *Kraken) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) {
|
||||
enabledCurrencies, err := k.GetEnabledPairs(asset.Spot)
|
||||
func (k *Kraken) GenerateDefaultSubscriptions() ([]subscription.Subscription, error) {
|
||||
enabledPairs, err := k.GetEnabledPairs(asset.Spot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var subscriptions []stream.ChannelSubscription
|
||||
var subscriptions []subscription.Subscription
|
||||
for i := range defaultSubscribedChannels {
|
||||
for j := range enabledCurrencies {
|
||||
enabledCurrencies[j].Delimiter = "/"
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
Channel: defaultSubscribedChannels[i],
|
||||
Currency: enabledCurrencies[j],
|
||||
Asset: asset.Spot,
|
||||
for j := range enabledPairs {
|
||||
enabledPairs[j].Delimiter = "/"
|
||||
subscriptions = append(subscriptions, subscription.Subscription{
|
||||
Channel: defaultSubscribedChannels[i],
|
||||
Pair: enabledPairs[j],
|
||||
Asset: asset.Spot,
|
||||
})
|
||||
}
|
||||
}
|
||||
if k.Websocket.CanUseAuthenticatedEndpoints() {
|
||||
for i := range authenticatedChannels {
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
subscriptions = append(subscriptions, subscription.Subscription{
|
||||
Channel: authenticatedChannels[i],
|
||||
})
|
||||
}
|
||||
@@ -1236,7 +1237,7 @@ func (k *Kraken) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, e
|
||||
}
|
||||
|
||||
// Subscribe sends a websocket message to receive data from the channel
|
||||
func (k *Kraken) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error {
|
||||
func (k *Kraken) Subscribe(channelsToSubscribe []subscription.Subscription) error {
|
||||
var subscriptions = make(map[string]*[]WebsocketSubscriptionEventRequest)
|
||||
channels:
|
||||
for i := range channelsToSubscribe {
|
||||
@@ -1247,7 +1248,7 @@ channels:
|
||||
}
|
||||
|
||||
for j := range *s {
|
||||
(*s)[j].Pairs = append((*s)[j].Pairs, channelsToSubscribe[i].Currency.String())
|
||||
(*s)[j].Pairs = append((*s)[j].Pairs, channelsToSubscribe[i].Pair.String())
|
||||
(*s)[j].Channels = append((*s)[j].Channels, channelsToSubscribe[i])
|
||||
continue channels
|
||||
}
|
||||
@@ -1263,8 +1264,8 @@ channels:
|
||||
if channelsToSubscribe[i].Channel == "book" {
|
||||
outbound.Subscription.Depth = krakenWsOrderbookDepth
|
||||
}
|
||||
if !channelsToSubscribe[i].Currency.IsEmpty() {
|
||||
outbound.Pairs = []string{channelsToSubscribe[i].Currency.String()}
|
||||
if !channelsToSubscribe[i].Pair.IsEmpty() {
|
||||
outbound.Pairs = []string{channelsToSubscribe[i].Pair.String()}
|
||||
}
|
||||
if common.StringDataContains(authenticatedChannels, channelsToSubscribe[i].Channel) {
|
||||
outbound.Subscription.Token = authToken
|
||||
@@ -1298,14 +1299,14 @@ channels:
|
||||
}
|
||||
|
||||
// Unsubscribe sends a websocket message to stop receiving data from the channel
|
||||
func (k *Kraken) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error {
|
||||
func (k *Kraken) Unsubscribe(channelsToUnsubscribe []subscription.Subscription) error {
|
||||
var unsubs []WebsocketSubscriptionEventRequest
|
||||
channels:
|
||||
for x := range channelsToUnsubscribe {
|
||||
for y := range unsubs {
|
||||
if unsubs[y].Subscription.Name == channelsToUnsubscribe[x].Channel {
|
||||
unsubs[y].Pairs = append(unsubs[y].Pairs,
|
||||
channelsToUnsubscribe[x].Currency.String())
|
||||
channelsToUnsubscribe[x].Pair.String())
|
||||
unsubs[y].Channels = append(unsubs[y].Channels,
|
||||
channelsToUnsubscribe[x])
|
||||
continue channels
|
||||
@@ -1325,7 +1326,7 @@ channels:
|
||||
|
||||
unsub := WebsocketSubscriptionEventRequest{
|
||||
Event: krakenWsUnsubscribe,
|
||||
Pairs: []string{channelsToUnsubscribe[x].Currency.String()},
|
||||
Pairs: []string{channelsToUnsubscribe[x].Pair.String()},
|
||||
Subscription: WebsocketSubscriptionData{
|
||||
Name: channelsToUnsubscribe[x].Channel,
|
||||
Depth: depth,
|
||||
|
||||
@@ -6,11 +6,13 @@ import (
|
||||
"errors"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/common/key"
|
||||
"github.com/thrasher-corp/gocryptotrader/config"
|
||||
@@ -24,9 +26,10 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/margin"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/stream/buffer"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/subscription"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
testexch "github.com/thrasher-corp/gocryptotrader/internal/testing/exchange"
|
||||
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
|
||||
)
|
||||
|
||||
@@ -1974,10 +1977,133 @@ func TestPushData(t *testing.T) {
|
||||
sharedtestvalues.TestFixtureToDataHandler(t, ku, n, "testdata/wsHandleData.json", ku.wsHandleData)
|
||||
}
|
||||
|
||||
func verifySubs(tb testing.TB, subs []subscription.Subscription, a asset.Item, prefix string, expected ...string) {
|
||||
tb.Helper()
|
||||
var sub *subscription.Subscription
|
||||
for i, s := range subs {
|
||||
if s.Asset == a && strings.HasPrefix(s.Channel, prefix) {
|
||||
if len(expected) == 1 && !strings.Contains(s.Channel, expected[0]) {
|
||||
continue
|
||||
}
|
||||
if sub != nil {
|
||||
assert.Failf(tb, "Too many subs with prefix", "Asset %s; Prefix %s", a.String(), prefix)
|
||||
return
|
||||
}
|
||||
sub = &subs[i]
|
||||
}
|
||||
}
|
||||
if assert.NotNil(tb, sub, "Should find a sub for asset %s with prefix %s for %s", a.String(), prefix, strings.Join(expected, ", ")) {
|
||||
suffix := strings.TrimPrefix(sub.Channel, prefix)
|
||||
if len(expected) == 0 {
|
||||
assert.Empty(tb, suffix, "Sub for asset %s with prefix %s should have no symbol suffix", a.String(), prefix)
|
||||
} else {
|
||||
currs := strings.Split(suffix, ",")
|
||||
assert.ElementsMatch(tb, currs, expected, "Currencies should match in sub for asset %s with prefix %s", a.String(), prefix)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pairs for Subscription tests:
|
||||
// Only in Spot: BTC-USDT, ETH-USDT
|
||||
// In Both: ETH-BTC, LTC-USDT
|
||||
// Only in Margin: XMR-BTC, SOL-USDC
|
||||
|
||||
func TestGenerateDefaultSubscriptions(t *testing.T) {
|
||||
t.Parallel()
|
||||
if _, err := ku.GenerateDefaultSubscriptions(); err != nil {
|
||||
t.Error(err)
|
||||
|
||||
subs, err := ku.GenerateDefaultSubscriptions()
|
||||
assert.NoError(t, err, "GenerateDefaultSubscriptions should not error")
|
||||
|
||||
assert.Len(t, subs, 12, "Should generate the correct number of subs when not logged in")
|
||||
for _, p := range []string{"ticker", "match", "level2"} {
|
||||
verifySubs(t, subs, asset.Spot, "/market/"+p+":", "BTC-USDT", "ETH-USDT", "LTC-USDT", "ETH-BTC")
|
||||
verifySubs(t, subs, asset.Margin, "/market/"+p+":", "SOL-USDC", "XMR-BTC")
|
||||
}
|
||||
for _, c := range []string{"ETHUSDCM", "XBTUSDCM", "SOLUSDTM"} {
|
||||
verifySubs(t, subs, asset.Futures, "/contractMarket/tickerV2:", c)
|
||||
verifySubs(t, subs, asset.Futures, "/contractMarket/level2Depth50:", c)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateAuthSubscriptions(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create a parallel safe Kucoin to mess with
|
||||
nu := new(Kucoin)
|
||||
nu.Base.Features = ku.Base.Features
|
||||
assert.NoError(t, nu.CurrencyPairs.Load(&ku.CurrencyPairs), "Loading Pairs should not error")
|
||||
nu.Websocket = sharedtestvalues.NewTestWebsocket()
|
||||
nu.Websocket.SetCanUseAuthenticatedEndpoints(true)
|
||||
|
||||
subs, err := nu.GenerateDefaultSubscriptions()
|
||||
assert.NoError(t, err, "GenerateDefaultSubscriptions with Auth should not error")
|
||||
assert.Len(t, subs, 25, "Should generate the correct number of subs when logged in")
|
||||
for _, p := range []string{"ticker", "match", "level2"} {
|
||||
verifySubs(t, subs, asset.Spot, "/market/"+p+":", "BTC-USDT", "ETH-USDT", "LTC-USDT", "ETH-BTC")
|
||||
verifySubs(t, subs, asset.Margin, "/market/"+p+":", "SOL-USDC", "XMR-BTC")
|
||||
}
|
||||
for _, c := range []string{"ETHUSDCM", "XBTUSDCM", "SOLUSDTM"} {
|
||||
verifySubs(t, subs, asset.Futures, "/contractMarket/tickerV2:", c)
|
||||
verifySubs(t, subs, asset.Futures, "/contractMarket/level2Depth50:", c)
|
||||
}
|
||||
for _, c := range []string{"SOL", "BTC", "XMR", "LTC", "USDC", "USDT", "ETH"} {
|
||||
verifySubs(t, subs, asset.Margin, "/margin/loan:", c)
|
||||
}
|
||||
verifySubs(t, subs, asset.Spot, "/account/balance")
|
||||
verifySubs(t, subs, asset.Margin, "/margin/position")
|
||||
verifySubs(t, subs, asset.Margin, "/margin/fundingBook:", "SOL", "BTC", "XMR", "LTC", "USDT", "USDC", "ETH")
|
||||
verifySubs(t, subs, asset.Futures, "/contractAccount/wallet")
|
||||
verifySubs(t, subs, asset.Futures, "/contractMarket/advancedOrders")
|
||||
verifySubs(t, subs, asset.Futures, "/contractMarket/tradeOrders")
|
||||
}
|
||||
|
||||
func TestGenerateCandleSubscription(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create a parallel safe Kucoin to mess with
|
||||
nu := new(Kucoin)
|
||||
nu.Base.Features = ku.Base.Features
|
||||
nu.Websocket = sharedtestvalues.NewTestWebsocket()
|
||||
assert.NoError(t, nu.CurrencyPairs.Load(&ku.CurrencyPairs), "Loading Pairs should not error")
|
||||
|
||||
nu.Features.Subscriptions = []*subscription.Subscription{
|
||||
{Channel: subscription.CandlesChannel, Interval: kline.FourHour},
|
||||
}
|
||||
|
||||
subs, err := nu.GenerateDefaultSubscriptions()
|
||||
assert.NoError(t, err, "GenerateDefaultSubscriptions with Candles should not error")
|
||||
|
||||
assert.Len(t, subs, 6, "Should generate the correct number of subs for candles")
|
||||
for _, c := range []string{"BTC-USDT", "ETH-USDT", "LTC-USDT", "ETH-BTC"} {
|
||||
verifySubs(t, subs, asset.Spot, "/market/candles:", c+"_4hour")
|
||||
}
|
||||
for _, c := range []string{"SOL-USDC", "XMR-BTC"} {
|
||||
verifySubs(t, subs, asset.Margin, "/market/candles:", c+"_4hour")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateMarketSubscription(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create a parallel safe Kucoin to mess with
|
||||
nu := new(Kucoin)
|
||||
nu.Base.Features = ku.Base.Features
|
||||
nu.Websocket = sharedtestvalues.NewTestWebsocket()
|
||||
assert.NoError(t, nu.CurrencyPairs.Load(&ku.CurrencyPairs), "Loading Pairs should not error")
|
||||
|
||||
nu.Features.Subscriptions = []*subscription.Subscription{
|
||||
{Channel: marketSnapshotChannel},
|
||||
}
|
||||
|
||||
subs, err := nu.GenerateDefaultSubscriptions()
|
||||
assert.NoError(t, err, "GenerateDefaultSubscriptions with MarketSnapshot should not error")
|
||||
|
||||
assert.Len(t, subs, 7, "Should generate the correct number of subs for snapshot")
|
||||
for _, c := range []string{"BTC", "ETH", "LTC", "USDT"} {
|
||||
verifySubs(t, subs, asset.Spot, "/market/snapshot:", c)
|
||||
}
|
||||
for _, c := range []string{"SOL", "USDC", "XMR"} {
|
||||
verifySubs(t, subs, asset.Margin, "/market/snapshot:", c)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2155,21 +2281,6 @@ func TestCancelAllOrders(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeneratePayloads(t *testing.T) {
|
||||
t.Parallel()
|
||||
subscriptions, err := ku.GenerateDefaultSubscriptions()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
payload, err := ku.generatePayloads(subscriptions, "subscribe")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if len(payload) != len(subscriptions) {
|
||||
t.Error("derived payload is not same as generated channel subscription instances")
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
subUserResponseJSON = `{"userId":"635002438793b80001dcc8b3", "uid":62356, "subName":"margin01", "status":2, "type":4, "access":"Margin", "createdAt":1666187844000, "remarks":null }`
|
||||
positionSettlementPushData = `{"userId": "xbc453tg732eba53a88ggyt8c", "topic": "/contract/position:XBTUSDM", "subject": "position.settlement", "data": { "fundingTime": 1551770400000, "qty": 100, "markPrice": 3610.85, "fundingRate": -0.002966, "fundingFee": -296, "ts": 1547697294838004923, "settleCurrency": "XBT" } }`
|
||||
@@ -2361,6 +2472,7 @@ func TestProcessMarketSnapshot(t *testing.T) {
|
||||
n := new(Kucoin)
|
||||
sharedtestvalues.TestFixtureToDataHandler(t, ku, n, "testdata/wsMarketSnapshot.json", n.wsHandleData)
|
||||
seen := 0
|
||||
seenAssetTypes := map[asset.Item]int{}
|
||||
for reading := true; reading; {
|
||||
select {
|
||||
default:
|
||||
@@ -2370,33 +2482,37 @@ func TestProcessMarketSnapshot(t *testing.T) {
|
||||
switch v := resp.(type) {
|
||||
case *ticker.Price:
|
||||
switch seen {
|
||||
// spot only
|
||||
case 1:
|
||||
assert.Equal(t, time.UnixMilli(1698740324415), v.LastUpdated, "datetime")
|
||||
assert.Equal(t, 0.00001402100000000000, v.High, "high")
|
||||
assert.Equal(t, 0.000012508, v.Last, "lastTradedPrice")
|
||||
assert.Equal(t, 0.00001129200000000000, v.Low, "low")
|
||||
assert.Equal(t, asset.Margin, v.AssetType, "AssetType")
|
||||
assert.Equal(t, time.UnixMilli(1700555342007), v.LastUpdated, "datetime")
|
||||
assert.Equal(t, 0.004445, v.High, "high")
|
||||
assert.Equal(t, 0.004415, v.Last, "lastTradedPrice")
|
||||
assert.Equal(t, 0.004191, v.Low, "low")
|
||||
assert.Equal(t, currency.NewPairWithDelimiter("XMR", "BTC", "-"), v.Pair, "symbol")
|
||||
assert.Equal(t, 28474.47280000000000000000, v.Volume, "volume")
|
||||
assert.Equal(t, 0.37038038297340000000, v.QuoteVolume, "volValue")
|
||||
// margin only
|
||||
case 2:
|
||||
assert.Equal(t, time.UnixMilli(1698740324483), v.LastUpdated, "datetime")
|
||||
assert.Equal(t, 0.00000039450000000000, v.High, "high")
|
||||
assert.Equal(t, 0.0000003897, v.Last, "lastTradedPrice")
|
||||
assert.Equal(t, 0.00000034200000000000, v.Low, "low")
|
||||
assert.Equal(t, 13097.3357, v.Volume, "volume")
|
||||
assert.Equal(t, 57.44552981, v.QuoteVolume, "volValue")
|
||||
case 2, 3:
|
||||
assert.Equal(t, time.UnixMilli(1700555340197), v.LastUpdated, "datetime")
|
||||
assert.Contains(t, []asset.Item{asset.Spot, asset.Margin}, v.AssetType, "AssetType is Spot or Margin")
|
||||
seenAssetTypes[v.AssetType]++
|
||||
assert.Equal(t, seenAssetTypes[v.AssetType], 1, "Each Asset Type is sent only once per unique snapshot")
|
||||
assert.Equal(t, 0.054846, v.High, "high")
|
||||
assert.Equal(t, 0.053778, v.Last, "lastTradedPrice")
|
||||
assert.Equal(t, 0.05364, v.Low, "low")
|
||||
assert.Equal(t, currency.NewPairWithDelimiter("ETH", "BTC", "-"), v.Pair, "symbol")
|
||||
assert.Equal(t, 316078.69700000000000000000, v.Volume, "volume")
|
||||
assert.Equal(t, 0.11768519138877000000, v.QuoteVolume, "volValue")
|
||||
// both margin and spot
|
||||
case 3, 4:
|
||||
assert.Equal(t, time.UnixMilli(1698740324437), v.LastUpdated, "datetime")
|
||||
assert.Equal(t, 0.00008486000000000000, v.High, "high")
|
||||
assert.Equal(t, 0.00008318, v.Last, "lastTradedPrice")
|
||||
assert.Equal(t, 0.00007152000000000000, v.Low, "low")
|
||||
assert.Equal(t, 2958.3139116, v.Volume, "volume")
|
||||
assert.Equal(t, 160.7847672784213, v.QuoteVolume, "volValue")
|
||||
case 4:
|
||||
assert.Equal(t, asset.Spot, v.AssetType, "AssetType")
|
||||
assert.Equal(t, time.UnixMilli(1700555342151), v.LastUpdated, "datetime")
|
||||
assert.Equal(t, 37750.0, v.High, "high")
|
||||
assert.Equal(t, 37366.8, v.Last, "lastTradedPrice")
|
||||
assert.Equal(t, 36700.0, v.Low, "low")
|
||||
assert.Equal(t, currency.NewPairWithDelimiter("BTC", "USDT", "-"), v.Pair, "symbol")
|
||||
assert.Equal(t, 17062.45450000000000000000, v.Volume, "volume")
|
||||
assert.Equal(t, 1.33076678861000000000, v.QuoteVolume, "volValue")
|
||||
assert.Equal(t, 2900.37846402, v.Volume, "volume")
|
||||
assert.Equal(t, 108210331.34015164, v.QuoteVolume, "volValue")
|
||||
default:
|
||||
t.Errorf("Got an unexpected *ticker.Price: %v", v)
|
||||
}
|
||||
case error:
|
||||
t.Error(v)
|
||||
@@ -2410,13 +2526,11 @@ func TestProcessMarketSnapshot(t *testing.T) {
|
||||
|
||||
func TestSubscribeMarketSnapshot(t *testing.T) {
|
||||
t.Parallel()
|
||||
s := []stream.ChannelSubscription{
|
||||
{Channel: marketTickerSnapshotForCurrencyChannel,
|
||||
Currency: currency.Pair{Base: currency.BTC}},
|
||||
}
|
||||
err := ku.Subscribe(s)
|
||||
setupWS()
|
||||
err := ku.Subscribe([]subscription.Subscription{{Channel: marketSymbolSnapshotChannel, Pair: currency.Pair{Base: currency.BTC}}})
|
||||
assert.NoError(t, err, "Subscribe to MarketSnapshot should not error")
|
||||
}
|
||||
|
||||
func TestSeedLocalCache(t *testing.T) {
|
||||
t.Parallel()
|
||||
pair, err := currency.NewPairFromString("ETH-USDT")
|
||||
@@ -2599,14 +2713,18 @@ func TestUpdateOrderExecutionLimits(t *testing.T) {
|
||||
|
||||
func TestGetOpenInterest(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := ku.GetOpenInterest(context.Background(), key.PairAsset{
|
||||
|
||||
nu := new(Kucoin)
|
||||
require.NoError(t, testexch.TestInstance(nu), "TestInstance setup should not error")
|
||||
|
||||
_, err := nu.GetOpenInterest(context.Background(), key.PairAsset{
|
||||
Base: currency.ETH.Item,
|
||||
Quote: currency.USDT.Item,
|
||||
Asset: asset.USDTMarginedFutures,
|
||||
})
|
||||
assert.ErrorIs(t, err, asset.ErrNotSupported)
|
||||
|
||||
resp, err := ku.GetOpenInterest(context.Background(), key.PairAsset{
|
||||
resp, err := nu.GetOpenInterest(context.Background(), key.PairAsset{
|
||||
Base: futuresTradablePair.Base.Item,
|
||||
Quote: futuresTradablePair.Quote.Item,
|
||||
Asset: asset.Futures,
|
||||
@@ -2615,8 +2733,8 @@ func TestGetOpenInterest(t *testing.T) {
|
||||
assert.NotEmpty(t, resp)
|
||||
|
||||
cp1 := currency.NewPair(currency.ETH, currency.USDTM)
|
||||
sharedtestvalues.SetupCurrencyPairsForExchangeAsset(t, ku, asset.Futures, cp1)
|
||||
resp, err = ku.GetOpenInterest(context.Background(),
|
||||
sharedtestvalues.SetupCurrencyPairsForExchangeAsset(t, nu, asset.Futures, cp1)
|
||||
resp, err = nu.GetOpenInterest(context.Background(),
|
||||
key.PairAsset{
|
||||
Base: futuresTradablePair.Base.Item,
|
||||
Quote: futuresTradablePair.Quote.Item,
|
||||
@@ -2631,7 +2749,7 @@ func TestGetOpenInterest(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, resp)
|
||||
|
||||
resp, err = ku.GetOpenInterest(context.Background())
|
||||
resp, err = nu.GetOpenInterest(context.Background())
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, resp)
|
||||
}
|
||||
|
||||
@@ -34,6 +34,8 @@ var (
|
||||
errSizeOrFundIsRequired = errors.New("at least one required among size and funds")
|
||||
errInvalidLeverage = errors.New("invalid leverage value")
|
||||
errInvalidClientOrderID = errors.New("no client order ID supplied, this endpoint requires a UUID or similar string")
|
||||
errInvalidMsgType = errors.New("message type field not valid")
|
||||
errSubscriptionPairRequired = errors.New("pair required for manual subscriptions")
|
||||
|
||||
subAccountRegExp = regexp.MustCompile("^[a-zA-Z0-9]{7-32}$")
|
||||
subAccountPassphraseRegExp = regexp.MustCompile("^[a-zA-Z0-9]{7-24}$")
|
||||
|
||||
@@ -11,17 +11,18 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/buger/jsonparser"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"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/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"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"
|
||||
@@ -34,21 +35,20 @@ const (
|
||||
privateBullets = "/v1/bullet-private"
|
||||
|
||||
// spot channels
|
||||
marketTickerChannel = "/market/ticker:%s" // /market/ticker:{symbol},{symbol}...
|
||||
marketAllTickersChannel = "/market/ticker:all"
|
||||
marketTickerSnapshotChannel = "/market/snapshot:%s" // /market/snapshot:{symbol}
|
||||
marketTickerSnapshotForCurrencyChannel = "/market/snapshot:" // /market/snapshot:{market} <--- market represents a currency
|
||||
marketOrderbookLevel2Channels = "/market/level2:%s" // /market/level2:{symbol},{symbol}...
|
||||
marketOrderbookLevel2to5Channel = "/spotMarket/level2Depth5:%s" // /spotMarket/level2Depth5:{symbol},{symbol}...
|
||||
marketOrderbokLevel2To50Channel = "/spotMarket/level2Depth50:%s" // /spotMarket/level2Depth50:{symbol},{symbol}...
|
||||
marketCandlesChannel = "/market/candles:%s_%s" // /market/candles:{symbol}_{type}
|
||||
marketMatchChannel = "/market/match:%s" // /market/match:{symbol},{symbol}...
|
||||
indexPriceIndicatorChannel = "/indicator/index:%s" // /indicator/index:{symbol0},{symbol1}..
|
||||
markPriceIndicatorChannel = "/indicator/markPrice:%s" // /indicator/markPrice:{symbol0},{symbol1}...
|
||||
marginFundingbookChangeChannel = "/margin/fundingBook:%s" // /margin/fundingBook:{currency0},{currency1}...
|
||||
|
||||
// Private channel
|
||||
marketAllTickersChannel = "/market/ticker:all"
|
||||
marketTickerChannel = "/market/ticker:%s" // /market/ticker:{symbol},{symbol}...
|
||||
marketSymbolSnapshotChannel = "/market/snapshot:%s" // /market/snapshot:{symbol}
|
||||
marketSnapshotChannel = "/market/snapshot:%v" // /market/snapshot:{market} <--- market represents a currency
|
||||
marketOrderbookLevel2Channels = "/market/level2:%s" // /market/level2:{pair},{pair}...
|
||||
marketOrderbookLevel2to5Channel = "/spotMarket/level2Depth5:%s" // /spotMarket/level2Depth5:{symbol},{symbol}...
|
||||
marketOrderbokLevel2To50Channel = "/spotMarket/level2Depth50:%s" // /spotMarket/level2Depth50:{symbol},{symbol}...
|
||||
marketCandlesChannel = "/market/candles:%s_%s" // /market/candles:{symbol}_{interval}
|
||||
marketMatchChannel = "/market/match:%s" // /market/match:{symbol},{symbol}...
|
||||
indexPriceIndicatorChannel = "/indicator/index:%s" // /indicator/index:{symbol0},{symbol1}..
|
||||
markPriceIndicatorChannel = "/indicator/markPrice:%s" // /indicator/markPrice:{symbol0},{symbol1}...
|
||||
marginFundingbookChangeChannel = "/margin/fundingBook:%s" // /margin/fundingBook:{currency0},{currency1}...
|
||||
|
||||
// Private channels
|
||||
privateSpotTradeOrders = "/spotMarket/tradeOrders"
|
||||
accountBalanceChannel = "/account/balance"
|
||||
marginPositionChannel = "/margin/position"
|
||||
@@ -56,7 +56,6 @@ const (
|
||||
spotMarketAdvancedChannel = "/spotMarket/advancedOrders"
|
||||
|
||||
// futures channels
|
||||
|
||||
futuresTickerV2Channel = "/contractMarket/tickerV2:%s" // /contractMarket/tickerV2:{symbol}
|
||||
futuresTickerChannel = "/contractMarket/ticker:%s" // /contractMarket/ticker:{symbol}
|
||||
futuresOrderbookLevel2Channel = "/contractMarket/level2:%s" // /contractMarket/level2:{symbol}
|
||||
@@ -68,7 +67,6 @@ const (
|
||||
futuresTrasactionStatisticsTimerEventChannel = "/contractMarket/snapshot:%s" // /contractMarket/snapshot:{symbol}
|
||||
|
||||
// futures private channels
|
||||
|
||||
futuresTradeOrdersBySymbolChannel = "/contractMarket/tradeOrders:%s" // /contractMarket/tradeOrders:{symbol}
|
||||
futuresTradeOrderChannel = "/contractMarket/tradeOrders"
|
||||
futuresStopOrdersLifecycleEventChannel = "/contractMarket/advancedOrders"
|
||||
@@ -76,6 +74,14 @@ const (
|
||||
futuresPositionChangeEventChannel = "/contract/position:%s" // /contract/position:{symbol}
|
||||
)
|
||||
|
||||
var subscriptionNames = map[string]string{
|
||||
subscription.TickerChannel: marketTickerChannel,
|
||||
subscription.OrderbookChannel: marketOrderbookLevel2Channels,
|
||||
subscription.CandlesChannel: marketCandlesChannel,
|
||||
subscription.AllTradesChannel: marketMatchChannel,
|
||||
// No equivalents for: AllOrders, MyTrades, MyOrders
|
||||
}
|
||||
|
||||
var (
|
||||
// maxWSUpdateBuffer defines max websocket updates to apply when an
|
||||
// orderbook is initially fetched
|
||||
@@ -88,22 +94,6 @@ var (
|
||||
maxWSOrderbookWorkers = 10
|
||||
)
|
||||
|
||||
var requiredSubscriptionIDS map[string]bool
|
||||
var requiredSubscriptionIDSLock sync.Mutex
|
||||
|
||||
// checkRequiredSubscriptionID check whether the id included in the required subscription ids list.
|
||||
func (ku *Kucoin) checkRequiredSubscriptionID(id string) bool {
|
||||
if len(requiredSubscriptionIDS) > 0 {
|
||||
if requiredSubscriptionIDS[id] {
|
||||
requiredSubscriptionIDSLock.Lock()
|
||||
delete(requiredSubscriptionIDS, id)
|
||||
requiredSubscriptionIDSLock.Unlock()
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// WsConnect creates a new websocket connection.
|
||||
func (ku *Kucoin) WsConnect() error {
|
||||
if !ku.Websocket.IsEnabled() || !ku.IsEnabled() {
|
||||
@@ -208,17 +198,16 @@ func (ku *Kucoin) wsHandleData(respData []byte) error {
|
||||
err := json.Unmarshal(respData, &resp)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if resp.ID != "" {
|
||||
if ku.checkRequiredSubscriptionID(resp.ID) {
|
||||
if !ku.Websocket.Match.IncomingWithData(resp.ID, respData) {
|
||||
return fmt.Errorf("can not match subscription message with signature ID:%s", resp.ID)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if resp.Type == "pong" || resp.Type == "welcome" {
|
||||
return nil
|
||||
}
|
||||
if resp.ID != "" {
|
||||
if !ku.Websocket.Match.IncomingWithData("msgID:"+resp.ID, respData) {
|
||||
return fmt.Errorf("message listener not found: %s", resp.ID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
topicInfo := strings.Split(resp.Topic, ":")
|
||||
switch {
|
||||
case strings.HasPrefix(marketAllTickersChannel, topicInfo[0]),
|
||||
@@ -230,8 +219,7 @@ func (ku *Kucoin) wsHandleData(respData []byte) error {
|
||||
instruments = topicInfo[1]
|
||||
}
|
||||
return ku.processTicker(resp.Data, instruments)
|
||||
case strings.HasPrefix(marketTickerSnapshotChannel, topicInfo[0]) ||
|
||||
strings.HasPrefix(marketTickerSnapshotForCurrencyChannel, topicInfo[0]):
|
||||
case strings.HasPrefix(marketSymbolSnapshotChannel, topicInfo[0]):
|
||||
return ku.processMarketSnapshot(resp.Data)
|
||||
case strings.HasPrefix(marketOrderbookLevel2Channels, topicInfo[0]):
|
||||
return ku.processOrderbookWithDepth(respData, topicInfo[1])
|
||||
@@ -916,396 +904,250 @@ func (ku *Kucoin) processMarketSnapshot(respData []byte) error {
|
||||
}
|
||||
|
||||
// Subscribe sends a websocket message to receive data from the channel
|
||||
func (ku *Kucoin) Subscribe(subscriptions []stream.ChannelSubscription) error {
|
||||
func (ku *Kucoin) Subscribe(subscriptions []subscription.Subscription) error {
|
||||
return ku.handleSubscriptions(subscriptions, "subscribe")
|
||||
}
|
||||
|
||||
// Unsubscribe sends a websocket message to stop receiving data from the channel
|
||||
func (ku *Kucoin) Unsubscribe(subscriptions []stream.ChannelSubscription) error {
|
||||
func (ku *Kucoin) Unsubscribe(subscriptions []subscription.Subscription) error {
|
||||
return ku.handleSubscriptions(subscriptions, "unsubscribe")
|
||||
}
|
||||
|
||||
func (ku *Kucoin) handleSubscriptions(subscriptions []stream.ChannelSubscription, operation string) error {
|
||||
if requiredSubscriptionIDS == nil {
|
||||
requiredSubscriptionIDS = map[string]bool{}
|
||||
}
|
||||
payloads, err := ku.generatePayloads(subscriptions, operation)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var errs error
|
||||
for x := range payloads {
|
||||
err = ku.Websocket.Conn.SendJSONMessage(payloads[x])
|
||||
if err != nil {
|
||||
errs = common.AppendError(errs, err)
|
||||
continue
|
||||
func (ku *Kucoin) expandManualSubscriptions(in []subscription.Subscription) ([]subscription.Subscription, error) {
|
||||
subs := make([]subscription.Subscription, 0, len(in))
|
||||
for i := range in {
|
||||
if isSymbolChannel(in[i].Channel) {
|
||||
if in[i].Pair.IsEmpty() {
|
||||
return nil, errSubscriptionPairRequired
|
||||
}
|
||||
a := in[i].Asset
|
||||
if !a.IsValid() {
|
||||
a = getChannelsAssetType(in[i].Channel)
|
||||
}
|
||||
assetPairs := map[asset.Item]currency.Pairs{a: {in[i].Pair}}
|
||||
n, err := ku.expandSubscription(&in[i], assetPairs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
subs = append(subs, n...)
|
||||
} else {
|
||||
subs = append(subs, in[i])
|
||||
}
|
||||
}
|
||||
return subs, nil
|
||||
}
|
||||
|
||||
func (ku *Kucoin) handleSubscriptions(subs []subscription.Subscription, operation string) error {
|
||||
var errs error
|
||||
subs, errs = ku.expandManualSubscriptions(subs)
|
||||
for i := range subs {
|
||||
msgID := strconv.FormatInt(ku.Websocket.Conn.GenerateMessageID(false), 10)
|
||||
req := WsSubscriptionInput{
|
||||
ID: msgID,
|
||||
Type: operation,
|
||||
Topic: subs[i].Channel,
|
||||
PrivateChannel: subs[i].Authenticated,
|
||||
Response: true,
|
||||
}
|
||||
if respRaw, err := ku.Websocket.Conn.SendMessageReturnResponse("msgID:"+msgID, req); err != nil {
|
||||
errs = common.AppendError(errs, err)
|
||||
} else {
|
||||
rType, err := jsonparser.GetUnsafeString(respRaw, "type")
|
||||
switch {
|
||||
case err != nil:
|
||||
errs = common.AppendError(errs, err)
|
||||
case rType != "ack":
|
||||
errs = common.AppendError(errs, fmt.Errorf("%w: %s from %s", errInvalidMsgType, rType, respRaw))
|
||||
default:
|
||||
ku.Websocket.AddSuccessfulSubscriptions(subs[i])
|
||||
if ku.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "%s Subscribed to Channel: %s", ku.Name, subs[i].Channel)
|
||||
}
|
||||
}
|
||||
}
|
||||
ku.Websocket.AddSuccessfulSubscriptions(subscriptions[x])
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
// getChannelsAssetType returns the asset type to which the subscription channel belongs to
|
||||
// or returns an error otherwise.
|
||||
func (ku *Kucoin) getChannelsAssetType(channelName string) (asset.Item, error) {
|
||||
// getChannelsAssetType returns the asset type to which the subscription channel belongs to or asset.Empty
|
||||
func getChannelsAssetType(channelName string) asset.Item {
|
||||
switch channelName {
|
||||
case futuresTickerV2Channel, futuresTickerChannel, futuresOrderbookLevel2Channel, futuresExecutionDataChannel, futuresOrderbookLevel2Depth5Channel, futuresOrderbookLevel2Depth50Channel, futuresContractMarketDataChannel, futuresSystemAnnouncementChannel, futuresTrasactionStatisticsTimerEventChannel, futuresTradeOrdersBySymbolChannel, futuresTradeOrderChannel, futuresStopOrdersLifecycleEventChannel, futuresAccountBalanceEventChannel, futuresPositionChangeEventChannel:
|
||||
return asset.Futures, nil
|
||||
return asset.Futures
|
||||
case marketTickerChannel, marketAllTickersChannel,
|
||||
marketTickerSnapshotChannel, marketTickerSnapshotForCurrencyChannel,
|
||||
marketSnapshotChannel, marketSymbolSnapshotChannel,
|
||||
marketOrderbookLevel2Channels, marketOrderbookLevel2to5Channel,
|
||||
marketOrderbokLevel2To50Channel, marketCandlesChannel,
|
||||
marketMatchChannel, indexPriceIndicatorChannel,
|
||||
markPriceIndicatorChannel, marginFundingbookChangeChannel,
|
||||
privateSpotTradeOrders, accountBalanceChannel,
|
||||
marginPositionChannel, marginLoanChannel,
|
||||
spotMarketAdvancedChannel:
|
||||
return asset.Spot, nil
|
||||
marketMatchChannel, indexPriceIndicatorChannel, markPriceIndicatorChannel,
|
||||
privateSpotTradeOrders, accountBalanceChannel, spotMarketAdvancedChannel:
|
||||
return asset.Spot
|
||||
case marginFundingbookChangeChannel, marginPositionChannel, marginLoanChannel:
|
||||
return asset.Margin
|
||||
default:
|
||||
return asset.Empty, errors.New("channel not supported")
|
||||
return asset.Empty
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateDefaultSubscriptions Adds default subscriptions to websocket.
|
||||
func (ku *Kucoin) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) {
|
||||
channels := []string{}
|
||||
if ku.CurrencyPairs.IsAssetEnabled(asset.Spot) == nil || ku.CurrencyPairs.IsAssetEnabled(asset.Margin) == nil {
|
||||
channels = append(channels,
|
||||
marketTickerChannel,
|
||||
marketMatchChannel,
|
||||
marketOrderbookLevel2Channels)
|
||||
}
|
||||
if ku.CurrencyPairs.IsAssetEnabled(asset.Margin) == nil {
|
||||
channels = append(channels,
|
||||
marginFundingbookChangeChannel)
|
||||
}
|
||||
if ku.CurrencyPairs.IsAssetEnabled(asset.Futures) == nil {
|
||||
channels = append(channels,
|
||||
futuresTickerV2Channel,
|
||||
futuresOrderbookLevel2Depth50Channel)
|
||||
}
|
||||
var subscriptions []stream.ChannelSubscription
|
||||
if ku.Websocket.CanUseAuthenticatedEndpoints() {
|
||||
if ku.CurrencyPairs.IsAssetEnabled(asset.Spot) == nil {
|
||||
channels = append(channels,
|
||||
accountBalanceChannel,
|
||||
)
|
||||
}
|
||||
if ku.CurrencyPairs.IsAssetEnabled(asset.Margin) == nil {
|
||||
channels = append(channels,
|
||||
marginPositionChannel,
|
||||
marginLoanChannel,
|
||||
)
|
||||
}
|
||||
if ku.CurrencyPairs.IsAssetEnabled(asset.Futures) == nil {
|
||||
channels = append(channels,
|
||||
// futures authenticated channels
|
||||
futuresTradeOrdersBySymbolChannel,
|
||||
futuresTradeOrderChannel,
|
||||
futuresStopOrdersLifecycleEventChannel,
|
||||
futuresAccountBalanceEventChannel)
|
||||
func (ku *Kucoin) GenerateDefaultSubscriptions() ([]subscription.Subscription, error) {
|
||||
assetPairs := map[asset.Item]currency.Pairs{}
|
||||
for _, a := range ku.GetAssetTypes(false) {
|
||||
if p, err := ku.GetEnabledPairs(a); err == nil {
|
||||
assetPairs[a] = p
|
||||
} else {
|
||||
assetPairs[a] = currency.Pairs{} // err is probably that Asset isn't enabled, but we don't care about errors of any type
|
||||
}
|
||||
}
|
||||
var err error
|
||||
var spotPairs currency.Pairs
|
||||
if ku.CurrencyPairs.IsAssetEnabled(asset.Spot) == nil {
|
||||
spotPairs, err = ku.GetEnabledPairs(asset.Spot)
|
||||
authed := ku.Websocket.CanUseAuthenticatedEndpoints()
|
||||
subscriptions := []subscription.Subscription{}
|
||||
for _, s := range ku.Features.Subscriptions {
|
||||
if !authed && s.Authenticated {
|
||||
continue
|
||||
}
|
||||
subs, err := ku.expandSubscription(s, assetPairs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
var marginPairs currency.Pairs
|
||||
if ku.CurrencyPairs.IsAssetEnabled(asset.Margin) == nil {
|
||||
marginPairs, err = ku.GetEnabledPairs(asset.Margin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var futuresPairs currency.Pairs
|
||||
if ku.CurrencyPairs.IsAssetEnabled(asset.Futures) == nil {
|
||||
futuresPairs, err = ku.GetEnabledPairs(asset.Futures)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
marginLoanCurrencyCheckMap := map[currency.Code]bool{}
|
||||
for x := range channels {
|
||||
switch channels[x] {
|
||||
case accountBalanceChannel, marginPositionChannel,
|
||||
futuresTradeOrderChannel, futuresStopOrdersLifecycleEventChannel,
|
||||
spotMarketAdvancedChannel, privateSpotTradeOrders,
|
||||
marketAllTickersChannel, futuresSystemAnnouncementChannel,
|
||||
futuresAccountBalanceEventChannel:
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
Channel: channels[x],
|
||||
})
|
||||
case marketTickerSnapshotChannel,
|
||||
marketOrderbookLevel2Channels,
|
||||
marketTickerSnapshotForCurrencyChannel,
|
||||
marketOrderbookLevel2to5Channel,
|
||||
marketOrderbokLevel2To50Channel,
|
||||
marketTickerChannel:
|
||||
subscribedPairsMap := map[string]bool{}
|
||||
for b := range spotPairs {
|
||||
if okay := subscribedPairsMap[spotPairs[b].String()]; okay {
|
||||
continue
|
||||
}
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
Channel: channels[x],
|
||||
Asset: asset.Spot,
|
||||
Currency: spotPairs[b],
|
||||
})
|
||||
subscribedPairsMap[spotPairs[b].String()] = true
|
||||
}
|
||||
for b := range marginPairs {
|
||||
if okay := subscribedPairsMap[marginPairs[b].String()]; okay {
|
||||
continue
|
||||
}
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
Channel: channels[x],
|
||||
Asset: asset.Margin,
|
||||
Currency: marginPairs[b],
|
||||
})
|
||||
subscribedPairsMap[marginPairs[b].String()] = true
|
||||
}
|
||||
case indexPriceIndicatorChannel,
|
||||
markPriceIndicatorChannel,
|
||||
marketMatchChannel:
|
||||
pairs := currency.Pairs{}
|
||||
for p := range spotPairs {
|
||||
pairs = pairs.Add(spotPairs[p])
|
||||
}
|
||||
for p := range marginPairs {
|
||||
pairs = pairs.Add(marginPairs[p])
|
||||
}
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
Channel: channels[x],
|
||||
Asset: asset.Spot,
|
||||
Params: map[string]interface{}{"symbols": pairs.Join()},
|
||||
})
|
||||
case marketCandlesChannel:
|
||||
subscribedPairsMap := map[string]bool{}
|
||||
for p := range spotPairs {
|
||||
if okay := subscribedPairsMap[spotPairs[p].String()]; okay {
|
||||
continue
|
||||
}
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
Channel: channels[x],
|
||||
Asset: asset.Spot,
|
||||
Currency: spotPairs[p],
|
||||
Params: map[string]interface{}{"interval": kline.FifteenMin},
|
||||
})
|
||||
subscribedPairsMap[spotPairs[p].String()] = true
|
||||
}
|
||||
for p := range marginPairs {
|
||||
if okay := subscribedPairsMap[marginPairs[p].String()]; okay {
|
||||
continue
|
||||
}
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
Channel: channels[x],
|
||||
Asset: asset.Margin,
|
||||
Currency: marginPairs[p],
|
||||
Params: map[string]interface{}{"interval": kline.FifteenMin},
|
||||
})
|
||||
subscribedPairsMap[marginPairs[p].String()] = true
|
||||
}
|
||||
case marginLoanChannel:
|
||||
for b := range marginPairs {
|
||||
if !marginLoanCurrencyCheckMap[marginPairs[b].Quote] {
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
Channel: channels[x],
|
||||
Currency: currency.Pair{Base: marginPairs[b].Quote},
|
||||
})
|
||||
marginLoanCurrencyCheckMap[marginPairs[b].Quote] = true
|
||||
}
|
||||
if !marginLoanCurrencyCheckMap[marginPairs[b].Base] {
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
Channel: channels[x],
|
||||
Currency: currency.Pair{Base: marginPairs[b].Base},
|
||||
})
|
||||
marginLoanCurrencyCheckMap[marginPairs[b].Base] = true
|
||||
}
|
||||
}
|
||||
case marginFundingbookChangeChannel:
|
||||
currencyExist := map[currency.Code]bool{}
|
||||
for b := range marginPairs {
|
||||
okay := currencyExist[marginPairs[b].Base]
|
||||
if !okay {
|
||||
currencyExist[marginPairs[b].Base] = true
|
||||
}
|
||||
okay = currencyExist[marginPairs[b].Quote]
|
||||
if !okay {
|
||||
currencyExist[marginPairs[b].Quote] = true
|
||||
}
|
||||
}
|
||||
var currencies string
|
||||
for b := range currencyExist {
|
||||
currencies += b.String() + ","
|
||||
}
|
||||
currencies = strings.TrimSuffix(currencies, ",")
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
Channel: channels[x],
|
||||
Params: map[string]interface{}{"currencies": currencies},
|
||||
})
|
||||
case futuresTickerV2Channel,
|
||||
futuresTickerChannel,
|
||||
futuresExecutionDataChannel,
|
||||
futuresOrderbookLevel2Channel,
|
||||
futuresOrderbookLevel2Depth5Channel,
|
||||
futuresOrderbookLevel2Depth50Channel,
|
||||
futuresContractMarketDataChannel,
|
||||
futuresTradeOrdersBySymbolChannel,
|
||||
futuresPositionChangeEventChannel,
|
||||
futuresTrasactionStatisticsTimerEventChannel:
|
||||
for b := range futuresPairs {
|
||||
futuresPairs[b], err = ku.FormatExchangeCurrency(futuresPairs[b], asset.Futures)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
Channel: channels[x],
|
||||
Asset: asset.Futures,
|
||||
Currency: futuresPairs[b],
|
||||
})
|
||||
}
|
||||
}
|
||||
subscriptions = append(subscriptions, subs...)
|
||||
}
|
||||
return subscriptions, nil
|
||||
}
|
||||
|
||||
func (ku *Kucoin) generatePayloads(subscriptions []stream.ChannelSubscription, operation string) ([]WsSubscriptionInput, error) {
|
||||
payloads := make([]WsSubscriptionInput, 0, len(subscriptions))
|
||||
marketTickerSnapshotForCurrencyChannelCurrencyFilter := map[currency.Code]int{}
|
||||
for x := range subscriptions {
|
||||
var err error
|
||||
var a asset.Item
|
||||
a, err = ku.getChannelsAssetType(subscriptions[x].Channel)
|
||||
// expandSubscription takes a subscription and expands it across the relevant assets and pairs passed in
|
||||
func (ku *Kucoin) expandSubscription(baseSub *subscription.Subscription, assetPairs map[asset.Item]currency.Pairs) ([]subscription.Subscription, error) {
|
||||
var subscriptions = []subscription.Subscription{}
|
||||
if baseSub == nil {
|
||||
return nil, common.ErrNilPointer
|
||||
}
|
||||
s := *baseSub
|
||||
s.Channel = channelName(s.Channel)
|
||||
if !s.Asset.IsValid() {
|
||||
s.Asset = getChannelsAssetType(s.Channel)
|
||||
}
|
||||
switch {
|
||||
case s.Channel == marginLoanChannel:
|
||||
for _, c := range assetPairs[asset.Margin].GetCurrencies() {
|
||||
i := s
|
||||
i.Channel = fmt.Sprintf(s.Channel, c)
|
||||
subscriptions = append(subscriptions, i)
|
||||
}
|
||||
case s.Channel == marketCandlesChannel:
|
||||
interval, err := ku.intervalToString(s.Interval)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !subscriptions[x].Currency.IsEmpty() {
|
||||
subscriptions[x].Currency, err = ku.FormatExchangeCurrency(subscriptions[x].Currency, a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
subs := spotOrMarginPairSubs(assetPairs, &s, false, interval)
|
||||
subscriptions = append(subscriptions, subs...)
|
||||
case s.Channel == marginFundingbookChangeChannel:
|
||||
s.Channel = fmt.Sprintf(s.Channel, assetPairs[asset.Margin].GetCurrencies().Join())
|
||||
subscriptions = append(subscriptions, s)
|
||||
case s.Channel == marketSnapshotChannel:
|
||||
subs, err := spotOrMarginCurrencySubs(assetPairs, &s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if subscriptions[x].Asset == asset.Futures {
|
||||
subscriptions[x].Currency, err = ku.FormatExchangeCurrency(subscriptions[x].Currency, asset.Futures)
|
||||
subscriptions = append(subscriptions, subs...)
|
||||
case getChannelsAssetType(s.Channel) == asset.Futures && isSymbolChannel(s.Channel):
|
||||
for _, p := range assetPairs[asset.Futures] {
|
||||
c, err := ku.FormatExchangeCurrency(p, asset.Futures)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
i := s
|
||||
i.Channel = fmt.Sprintf(s.Channel, c)
|
||||
subscriptions = append(subscriptions, i)
|
||||
}
|
||||
switch subscriptions[x].Channel {
|
||||
case marketTickerChannel,
|
||||
marketOrderbookLevel2Channels,
|
||||
marketOrderbookLevel2to5Channel,
|
||||
marketOrderbokLevel2To50Channel,
|
||||
indexPriceIndicatorChannel,
|
||||
marketMatchChannel,
|
||||
markPriceIndicatorChannel:
|
||||
symbols, okay := subscriptions[x].Params["symbols"].(string)
|
||||
if !okay {
|
||||
if subscriptions[x].Currency.IsEmpty() {
|
||||
return nil, errors.New("symbols not passed")
|
||||
}
|
||||
symbols = subscriptions[x].Currency.String()
|
||||
case isSymbolChannel(s.Channel):
|
||||
// Subscriptions which can use a single comma-separated sub per asset
|
||||
subs := spotOrMarginPairSubs(assetPairs, &s, true)
|
||||
subscriptions = append(subscriptions, subs...)
|
||||
default:
|
||||
subscriptions = append(subscriptions, s)
|
||||
}
|
||||
return subscriptions, nil
|
||||
}
|
||||
|
||||
// isSymbolChannel returns true it this channel path ends in a formatting %s to accept a Symbol
|
||||
func isSymbolChannel(c string) bool {
|
||||
return strings.HasSuffix(c, "%s") || strings.HasSuffix(c, "%v")
|
||||
}
|
||||
|
||||
// channelName converts global channel Names used in config of channel input into kucoin channel names
|
||||
// returns the name unchanged if no match is found
|
||||
func channelName(name string) string {
|
||||
if s, ok := subscriptionNames[name]; ok {
|
||||
return s
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
// spotOrMarginPairSubs accepts a map of pairs and a template subscription and returns a list of subscriptions for Spot and Margin pairs
|
||||
// If there's a Spot subscription, it won't be added again as a Margin subscription
|
||||
// If joined param is true then one subscription per asset type with the currencies comma delimited
|
||||
func spotOrMarginPairSubs(assetPairs map[asset.Item]currency.Pairs, b *subscription.Subscription, join bool, fmtArgs ...any) []subscription.Subscription {
|
||||
subs := []subscription.Subscription{}
|
||||
add := func(a asset.Item, pairs currency.Pairs) {
|
||||
if len(pairs) == 0 {
|
||||
return
|
||||
}
|
||||
s := *b
|
||||
s.Asset = a
|
||||
if join {
|
||||
f := append([]any{pairs.Join()}, fmtArgs...)
|
||||
s.Channel = fmt.Sprintf(b.Channel, f...)
|
||||
subs = append(subs, s)
|
||||
} else {
|
||||
for i := range pairs {
|
||||
f := append([]any{pairs[i].String()}, fmtArgs...)
|
||||
s.Channel = fmt.Sprintf(b.Channel, f...)
|
||||
subs = append(subs, s)
|
||||
}
|
||||
payloads = append(payloads, WsSubscriptionInput{
|
||||
ID: strconv.FormatInt(ku.Websocket.Conn.GenerateMessageID(false), 10),
|
||||
Type: operation,
|
||||
Topic: fmt.Sprintf(subscriptions[x].Channel, symbols),
|
||||
Response: true,
|
||||
})
|
||||
case marketAllTickersChannel,
|
||||
privateSpotTradeOrders,
|
||||
accountBalanceChannel,
|
||||
marginPositionChannel,
|
||||
spotMarketAdvancedChannel,
|
||||
futuresTradeOrderChannel,
|
||||
futuresStopOrdersLifecycleEventChannel,
|
||||
futuresAccountBalanceEventChannel, futuresSystemAnnouncementChannel:
|
||||
input := WsSubscriptionInput{
|
||||
ID: strconv.FormatInt(ku.Websocket.Conn.GenerateMessageID(false), 10),
|
||||
Type: operation,
|
||||
Topic: subscriptions[x].Channel,
|
||||
Response: true,
|
||||
}
|
||||
switch subscriptions[x].Channel {
|
||||
case futuresTradeOrderChannel,
|
||||
futuresStopOrdersLifecycleEventChannel,
|
||||
futuresAccountBalanceEventChannel,
|
||||
privateSpotTradeOrders,
|
||||
accountBalanceChannel,
|
||||
marginPositionChannel,
|
||||
spotMarketAdvancedChannel:
|
||||
input.PrivateChannel = true
|
||||
}
|
||||
payloads = append(payloads, input)
|
||||
case marketTickerSnapshotChannel, futuresPositionChangeEventChannel,
|
||||
futuresTradeOrdersBySymbolChannel, futuresTrasactionStatisticsTimerEventChannel,
|
||||
futuresContractMarketDataChannel, futuresOrderbookLevel2Depth50Channel,
|
||||
futuresOrderbookLevel2Depth5Channel, futuresExecutionDataChannel,
|
||||
futuresOrderbookLevel2Channel, futuresTickerChannel,
|
||||
futuresTickerV2Channel: // Symbols
|
||||
item := WsSubscriptionInput{
|
||||
ID: strconv.FormatInt(ku.Websocket.Conn.GenerateMessageID(false), 10),
|
||||
Type: operation,
|
||||
Topic: fmt.Sprintf(subscriptions[x].Channel, subscriptions[x].Currency.String()),
|
||||
Response: true,
|
||||
}
|
||||
switch subscriptions[x].Channel {
|
||||
case futuresPositionChangeEventChannel,
|
||||
futuresTradeOrdersBySymbolChannel:
|
||||
item.PrivateChannel = true
|
||||
}
|
||||
payloads = append(payloads, item)
|
||||
case marketTickerSnapshotForCurrencyChannel,
|
||||
marginLoanChannel:
|
||||
// 3 means the Currency is used by both switch cases
|
||||
// 2 means the currency is used by channel = marginLoanChannel
|
||||
// 1 if used by marketTickerSnapshotForCurrencyChannel
|
||||
if stat := marketTickerSnapshotForCurrencyChannelCurrencyFilter[subscriptions[x].Currency.Base]; stat == 3 || (stat == 2 && subscriptions[x].Channel == marginLoanChannel) || stat == 1 {
|
||||
continue
|
||||
}
|
||||
input := WsSubscriptionInput{}
|
||||
if subscriptions[x].Channel == marginLoanChannel {
|
||||
input.PrivateChannel = true
|
||||
marketTickerSnapshotForCurrencyChannelCurrencyFilter[subscriptions[x].Currency.Base] += 2
|
||||
} else {
|
||||
marketTickerSnapshotForCurrencyChannelCurrencyFilter[subscriptions[x].Currency.Base]++
|
||||
subscriptions[x].Channel += "%s"
|
||||
}
|
||||
input.ID = strconv.FormatInt(ku.Websocket.Conn.GenerateMessageID(false), 10)
|
||||
input.Type = operation
|
||||
input.Topic = fmt.Sprintf(subscriptions[x].Channel, subscriptions[x].Currency.Base.Upper().String())
|
||||
input.Response = true
|
||||
payloads = append(payloads, input)
|
||||
case marketCandlesChannel:
|
||||
interval, err := ku.intervalToString(subscriptions[x].Params["interval"].(kline.Interval))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
payloads = append(payloads, WsSubscriptionInput{
|
||||
ID: strconv.FormatInt(ku.Websocket.Conn.GenerateMessageID(false), 10),
|
||||
Type: operation,
|
||||
Topic: fmt.Sprintf(subscriptions[x].Channel, subscriptions[x].Currency.Upper().String(), interval),
|
||||
Response: true,
|
||||
})
|
||||
case marginFundingbookChangeChannel:
|
||||
currencies, okay := subscriptions[x].Params["currencies"].(string)
|
||||
if !okay {
|
||||
return nil, errors.New("currencies not passed")
|
||||
}
|
||||
payloads = append(payloads, WsSubscriptionInput{
|
||||
ID: strconv.FormatInt(ku.Websocket.Conn.GenerateMessageID(false), 10),
|
||||
Type: operation,
|
||||
Topic: fmt.Sprintf(subscriptions[x].Channel, currencies),
|
||||
Response: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
return payloads, nil
|
||||
|
||||
add(asset.Spot, assetPairs[asset.Spot])
|
||||
|
||||
marginPairs := currency.Pairs{}
|
||||
for _, p := range assetPairs[asset.Margin] {
|
||||
if !assetPairs[asset.Spot].Contains(p, false) {
|
||||
marginPairs = marginPairs.Add(p)
|
||||
}
|
||||
}
|
||||
add(asset.Margin, marginPairs)
|
||||
|
||||
return subs
|
||||
}
|
||||
|
||||
// spotOrMarginCurrencySubs accepts a map of pairs and a template subscription and returns a list of subscriptions for every currency in Spot and Margin pairs
|
||||
// If there's a Spot subscription, it won't be added again as a Margin subscription
|
||||
func spotOrMarginCurrencySubs(assetPairs map[asset.Item]currency.Pairs, b *subscription.Subscription) ([]subscription.Subscription, error) {
|
||||
if b == nil {
|
||||
return nil, common.ErrNilPointer
|
||||
}
|
||||
subs := []subscription.Subscription{}
|
||||
add := func(a asset.Item, currs currency.Currencies) {
|
||||
if len(currs) == 0 {
|
||||
return
|
||||
}
|
||||
s := *b
|
||||
s.Asset = a
|
||||
for _, c := range currs {
|
||||
s.Channel = fmt.Sprintf(b.Channel, c)
|
||||
subs = append(subs, s)
|
||||
}
|
||||
}
|
||||
|
||||
add(asset.Spot, assetPairs[asset.Spot].GetCurrencies())
|
||||
|
||||
marginCurrencies := currency.Currencies{}
|
||||
for _, c := range assetPairs[asset.Margin].GetCurrencies() {
|
||||
if !assetPairs[asset.Spot].ContainsCurrency(c) {
|
||||
marginCurrencies = marginCurrencies.Add(c)
|
||||
}
|
||||
}
|
||||
add(asset.Margin, marginCurrencies)
|
||||
|
||||
return subs, nil
|
||||
}
|
||||
|
||||
// orderbookManager defines a way of managing and maintaining synchronisation
|
||||
|
||||
@@ -29,6 +29,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/stream/buffer"
|
||||
"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"
|
||||
@@ -163,6 +164,21 @@ func (ku *Kucoin) SetDefaults() {
|
||||
GlobalResultLimit: 1500,
|
||||
},
|
||||
},
|
||||
Subscriptions: []*subscription.Subscription{
|
||||
// Where we can we use generic names
|
||||
{Enabled: true, Channel: subscription.TickerChannel}, // marketTickerChannel
|
||||
{Enabled: true, Channel: subscription.AllTradesChannel}, // marketMatchChannel
|
||||
{Enabled: true, Channel: subscription.OrderbookChannel, Interval: kline.HundredMilliseconds}, // marketOrderbookLevel2Channels
|
||||
{Enabled: true, Channel: futuresTickerV2Channel},
|
||||
{Enabled: true, Channel: futuresOrderbookLevel2Depth50Channel},
|
||||
{Enabled: true, Channel: marginFundingbookChangeChannel, Authenticated: true},
|
||||
{Enabled: true, Channel: accountBalanceChannel, Authenticated: true},
|
||||
{Enabled: true, Channel: marginPositionChannel, Authenticated: true},
|
||||
{Enabled: true, Channel: marginLoanChannel, Authenticated: true},
|
||||
{Enabled: true, Channel: futuresTradeOrderChannel, Authenticated: true},
|
||||
{Enabled: true, Channel: futuresStopOrdersLifecycleEventChannel, Authenticated: true},
|
||||
{Enabled: true, Channel: futuresAccountBalanceEventChannel, Authenticated: true},
|
||||
},
|
||||
}
|
||||
ku.Requester, err = request.New(ku.Name,
|
||||
common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout),
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{"type":"message","topic":"/market/snapshot:BTC","subject":"trade.snapshot","data":{"sequence":1698740324504,"data":{"averagePrice":0.00001164,"baseCurrency":"XMR","board":0,"buy":0.00001252,"changePrice":0.00000104800000000000,"changeRate":0.0914,"close":0.000012508,"datetime":1698740324415,"high":0.00001402100000000000,"lastTradedPrice":0.000012508,"low":0.00001129200000000000,"makerCoefficient":2.000000,"makerFeeRate":0.001,"marginTrade":false,"mark":0,"market":"BTC","marketChange1h":{"changeRate":0,"high":0,"low":0,"open":0,"vol":0,"volValue":0},"marketChange24h":{"changePrice":0.00000104800000000000,"changeRate":0.0914,"high":0.00001402100000000000,"low":0.00001129200000000000,"open":0.00001146000000000000,"vol":28474.47280000000000000000,"volValue":0.37038038297340000000},"marketChange4h":{"changePrice":0.00000009600000000000,"changeRate":0.0077,"high":0.00001308400000000000,"low":0.00001241200000000000,"open":0.00001241200000000000,"vol":7090.00000000000000000000,"volValue":0.08885800028840000000},"markets":["BTC"],"open":0.00001146000000000000,"quoteCurrency":"BTC","sell":0.000013191,"sort":100,"symbol":"XMR-BTC","symbolCode":"XMR-BTC","takerCoefficient":2.000000,"takerFeeRate":0.001,"trading":true,"vol":28474.47280000000000000000,"volValue":0.37038038297340000000}}}
|
||||
{"type":"message","topic":"/market/snapshot:BTC","subject":"trade.snapshot","data":{"sequence":1698740324488,"data":{"averagePrice":0.00000037,"baseCurrency":"ETH","board":0,"buy":0.0000003641,"changePrice":0.00000004770000000000,"changeRate":0.1394,"close":0.0000003897,"datetime":1698740324483,"high":0.00000039450000000000,"lastTradedPrice":0.0000003897,"low":0.00000034200000000000,"makerCoefficient":2.000000,"makerFeeRate":0.001,"marginTrade":false,"mark":0,"market":"BTC","marketChange1h":{"changeRate":0,"high":0,"low":0,"open":0,"vol":0,"volValue":0},"marketChange24h":{"changePrice":0.00000004770000000000,"changeRate":0.1394,"high":0.00000039450000000000,"low":0.00000034200000000000,"open":0.00000034200000000000,"vol":316078.69700000000000000000,"volValue":0.11768519138877000000},"marketChange4h":{"changePrice":0.00000003290000000000,"changeRate":0.0922,"high":0.00000038970000000000,"low":0.00000035680000000000,"open":0.00000035680000000000,"vol":2309.46880000000000000000,"volValue":0.00089999999136000000},"markets":["BTC"],"open":0.00000034200000000000,"quoteCurrency":"BTC","sell":0.0000004022,"sort":100,"symbol":"ETH-BTC","symbolCode":"ETH-BTC","takerCoefficient":2.000000,"takerFeeRate":0.001,"trading":true,"vol":316078.69700000000000000000,"volValue":0.11768519138877000000}}}
|
||||
{"type":"message","topic":"/market/snapshot:BTC","subject":"trade.snapshot","data":{"sequence":1698740324508,"data":{"averagePrice":0.00007307,"baseCurrency":"BTC","board":0,"buy":0.00008388,"changePrice":0.00001166000000000000,"changeRate":0.1630,"close":0.00008318,"datetime":1698740324437,"high":0.00008486000000000000,"lastTradedPrice":0.00008318,"low":0.00007152000000000000,"makerCoefficient":1.000000,"makerFeeRate":0.001,"marginTrade":false,"mark":0,"market":"USDT","marketChange1h":{"changePrice":-0.00000116000000000000,"changeRate":-0.0137,"high":0.00008434000000000000,"low":0.00008318000000000000,"open":0.00008434000000000000,"vol":189.33430000000000000000,"volValue":0.01578748292300000000},"marketChange24h":{"changePrice":0.00001166000000000000,"changeRate":0.1630,"high":0.00008486000000000000,"low":0.00007152000000000000,"open":0.00007152000000000000,"vol":17062.45450000000000000000,"volValue":1.33076678861000000000},"marketChange4h":{"changePrice":0.00000143000000000000,"changeRate":0.0174,"high":0.00008486000000000000,"low":0.00008175000000000000,"open":0.00008175000000000000,"vol":1752.55690000000000000000,"volValue":0.14543003812900000000},"markets":["BTC"],"open":0.00007152000000000000,"quoteCurrency":"USDT","sell":0.00008421,"sort":100,"symbol":"BTC-USDT","symbolCode":"BTC-USDT","takerCoefficient":1.000000,"takerFeeRate":0.001,"trading":true,"vol":17062.45450000000000000000,"volValue":1.33076678861000000000}}}
|
||||
{"type":"message","topic":"/market/snapshot:BTC","subject":"trade.snapshot","data":{"sequence":"459320318","data":{"averagePrice":0.00442293,"baseCurrency":"XMR","board":1,"buy":0.004411,"changePrice":0.00000000000000000000,"changeRate":0.0000,"close":0.004415,"datetime":1700555342007,"high":0.00444500000000000000,"lastTradedPrice":0.004415,"low":0.00419100000000000000,"makerCoefficient":1.000000,"makerFeeRate":0.001,"marginTrade":true,"mark":0,"market":"BTC","marketChange1h":{"changePrice":-0.00000200000000000000,"changeRate":-0.0004,"high":0.00443600000000000000,"low":0.00441300000000000000,"open":0.00441700000000000000,"vol":505.11910000000000000000,"volValue":2.23457327520000000000},"marketChange24h":{"changePrice":0.00000000000000000000,"changeRate":0.0000,"high":0.00444500000000000000,"low":0.00419100000000000000,"open":0.00441500000000000000,"vol":13097.33570000000000000000,"volValue":57.44552981000000000000},"marketChange4h":{"changePrice":0.00001100000000000000,"changeRate":0.0024,"high":0.00443600000000000000,"low":0.00439300000000000000,"open":0.00440400000000000000,"vol":2124.84330000000000000000,"volValue":9.37472351370000000000},"markets":["BTC"],"open":0.00441500000000000000,"quoteCurrency":"BTC","sell":0.004415,"sort":100,"symbol":"XMR-BTC","symbolCode":"XMR-BTC","takerCoefficient":1.000000,"takerFeeRate":0.001,"trading":true,"vol":13097.33570000000000000000,"volValue":57.44552981000000000000}}}
|
||||
{"type":"message","topic":"/market/snapshot:BTC","subject":"trade.snapshot","data":{"sequence":"692562428","data":{"averagePrice":0.05414932,"baseCurrency":"ETH","board":1,"buy":0.053778,"changePrice":-0.00045800000000000000,"changeRate":-0.0084,"close":0.053778,"datetime":1700555340197,"high":0.05484600000000000000,"lastTradedPrice":0.053778,"low":0.05364000000000000000,"makerCoefficient":1.000000,"makerFeeRate":0.001,"marginTrade":true,"mark":0,"market":"BTC","marketChange1h":{"changePrice":-0.00008000000000000000,"changeRate":-0.0014,"high":0.05387400000000000000,"low":0.05371000000000000000,"open":0.05385800000000000000,"vol":63.72190510000000000000,"volValue":3.42879155215990000000},"marketChange24h":{"changePrice":-0.00045800000000000000,"changeRate":-0.0084,"high":0.05484600000000000000,"low":0.05364000000000000000,"open":0.05423600000000000000,"vol":2958.31391160000000000000,"volValue":160.78476727842130000000},"marketChange4h":{"changePrice":-0.00002700000000000000,"changeRate":-0.0005,"high":0.05399000000000000000,"low":0.05371000000000000000,"open":0.05380500000000000000,"vol":166.22099950000000000000,"volValue":8.95534024043750000000},"markets":["BTC","Shanghai-Upgrade"],"open":0.05423600000000000000,"quoteCurrency":"BTC","sell":0.053779,"sort":100,"symbol":"ETH-BTC","symbolCode":"ETH-BTC","takerCoefficient":1.000000,"takerFeeRate":0.001,"trading":true,"vol":2958.31391160000000000000,"volValue":160.78476727842130000000}}}
|
||||
{"type":"message","topic":"/market/snapshot:BTC","subject":"trade.snapshot","data":{"sequence":"9860735911","data":{"averagePrice":37110.27939304,"baseCurrency":"BTC","board":1,"buy":37366.7,"changePrice":171.50000000000000000000,"changeRate":0.0046,"close":37366.8,"datetime":1700555342151,"high":37750.00000000000000000000,"lastTradedPrice":37366.8,"low":36700.00000000000000000000,"makerCoefficient":1.000000,"makerFeeRate":0.001,"marginTrade":true,"mark":0,"market":"USDS","marketChange1h":{"changePrice":105.60000000000000000000,"changeRate":0.0028,"high":37366.80000000000000000000,"low":37232.30000000000000000000,"open":37261.20000000000000000000,"vol":52.88854739000000000000,"volValue":1972678.83173137400000000000},"marketChange24h":{"changePrice":171.50000000000000000000,"changeRate":0.0046,"high":37750.00000000000000000000,"low":36700.00000000000000000000,"open":37195.30000000000000000000,"vol":2900.37846402000000000000,"volValue":108210331.34015163900000000000},"marketChange4h":{"changePrice":-94.70000000000000000000,"changeRate":-0.0025,"high":37476.40000000000000000000,"low":37232.30000000000000000000,"open":37461.50000000000000000000,"vol":263.14059486000000000000,"volValue":9829529.74388805200000000000},"markets":["USDS"],"open":37195.30000000000000000000,"quoteCurrency":"USDT","sell":37366.8,"sort":100,"symbol":"BTC-USDT","symbolCode":"BTC-USDT","takerCoefficient":1.000000,"takerFeeRate":0.001,"trading":true,"vol":2900.37846402000000000000,"volValue":108210331.34015163900000000000}}}
|
||||
|
||||
@@ -21,6 +21,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"
|
||||
@@ -581,9 +582,9 @@ func (o *Okcoin) wsProcessOrderbook(respRaw []byte, obChannel string) error {
|
||||
// ReSubscribeSpecificOrderbook removes the subscription and the subscribes
|
||||
// again to fetch a new snapshot in the event of a de-sync event.
|
||||
func (o *Okcoin) ReSubscribeSpecificOrderbook(obChannel string, p currency.Pair) error {
|
||||
subscription := []stream.ChannelSubscription{{
|
||||
Channel: obChannel,
|
||||
Currency: p,
|
||||
subscription := []subscription.Subscription{{
|
||||
Channel: obChannel,
|
||||
Pair: p,
|
||||
}}
|
||||
if err := o.Unsubscribe(subscription); err != nil {
|
||||
return err
|
||||
@@ -763,8 +764,8 @@ func (o *Okcoin) CalculateOrderbookUpdateChecksum(orderbookData *orderbook.Base)
|
||||
|
||||
// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be
|
||||
// handled by ManageSubscriptions()
|
||||
func (o *Okcoin) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) {
|
||||
var subscriptions []stream.ChannelSubscription
|
||||
func (o *Okcoin) GenerateDefaultSubscriptions() ([]subscription.Subscription, error) {
|
||||
var subscriptions []subscription.Subscription
|
||||
pairs, err := o.GetEnabledPairs(asset.Spot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -787,7 +788,7 @@ func (o *Okcoin) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, e
|
||||
for s := range channels {
|
||||
switch channels[s] {
|
||||
case wsInstruments:
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
subscriptions = append(subscriptions, subscription.Subscription{
|
||||
Channel: channels[s],
|
||||
Asset: asset.Spot,
|
||||
})
|
||||
@@ -798,20 +799,20 @@ func (o *Okcoin) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, e
|
||||
wsCandle5m, wsCandle3m, wsCandle1m, wsCandle3Mutc, wsCandle1Mutc, wsCandle1Wutc, wsCandle1Dutc,
|
||||
wsCandle2Dutc, wsCandle3Dutc, wsCandle5Dutc, wsCandle12Hutc, wsCandle6Hutc:
|
||||
for p := range pairs {
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
Channel: channels[s],
|
||||
Currency: pairs[p],
|
||||
subscriptions = append(subscriptions, subscription.Subscription{
|
||||
Channel: channels[s],
|
||||
Pair: pairs[p],
|
||||
})
|
||||
}
|
||||
case wsStatus:
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
subscriptions = append(subscriptions, subscription.Subscription{
|
||||
Channel: channels[s],
|
||||
})
|
||||
case wsAccount:
|
||||
currenciesMap := map[currency.Code]bool{}
|
||||
for p := range pairs {
|
||||
if reserved, okay := currenciesMap[pairs[p].Base]; !okay && !reserved {
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
subscriptions = append(subscriptions, subscription.Subscription{
|
||||
Channel: channels[s],
|
||||
Params: map[string]interface{}{
|
||||
"ccy": pairs[p].Base,
|
||||
@@ -822,7 +823,7 @@ func (o *Okcoin) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, e
|
||||
}
|
||||
for p := range pairs {
|
||||
if reserved, okay := currenciesMap[pairs[p].Quote]; !okay && !reserved {
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
subscriptions = append(subscriptions, subscription.Subscription{
|
||||
Channel: channels[s],
|
||||
Params: map[string]interface{}{
|
||||
"ccy": pairs[p].Quote,
|
||||
@@ -833,10 +834,10 @@ func (o *Okcoin) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, e
|
||||
}
|
||||
case wsOrder, wsOrdersAlgo, wsAlgoAdvance:
|
||||
for p := range pairs {
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
Channel: channels[s],
|
||||
Currency: pairs[p],
|
||||
Asset: asset.Spot,
|
||||
subscriptions = append(subscriptions, subscription.Subscription{
|
||||
Channel: channels[s],
|
||||
Pair: pairs[p],
|
||||
Asset: asset.Spot,
|
||||
})
|
||||
}
|
||||
default:
|
||||
@@ -847,23 +848,23 @@ func (o *Okcoin) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, e
|
||||
}
|
||||
|
||||
// Subscribe sends a websocket message to receive data from the channel
|
||||
func (o *Okcoin) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error {
|
||||
func (o *Okcoin) Subscribe(channelsToSubscribe []subscription.Subscription) error {
|
||||
return o.handleSubscriptions("subscribe", channelsToSubscribe)
|
||||
}
|
||||
|
||||
// Unsubscribe sends a websocket message to stop receiving data from the channel
|
||||
func (o *Okcoin) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error {
|
||||
func (o *Okcoin) Unsubscribe(channelsToUnsubscribe []subscription.Subscription) error {
|
||||
return o.handleSubscriptions("unsubscribe", channelsToUnsubscribe)
|
||||
}
|
||||
|
||||
func (o *Okcoin) handleSubscriptions(operation string, subs []stream.ChannelSubscription) error {
|
||||
func (o *Okcoin) handleSubscriptions(operation string, subs []subscription.Subscription) error {
|
||||
subscriptionRequest := WebsocketEventRequest{Operation: operation, Arguments: []map[string]string{}}
|
||||
authRequest := WebsocketEventRequest{Operation: operation, Arguments: []map[string]string{}}
|
||||
temp := WebsocketEventRequest{Operation: operation, Arguments: []map[string]string{}}
|
||||
authTemp := WebsocketEventRequest{Operation: operation, Arguments: []map[string]string{}}
|
||||
var err error
|
||||
var channels []stream.ChannelSubscription
|
||||
var authChannels []stream.ChannelSubscription
|
||||
var channels []subscription.Subscription
|
||||
var authChannels []subscription.Subscription
|
||||
for i := 0; i < len(subs); i++ {
|
||||
authenticatedChannelSubscription := isAuthenticatedChannel(subs[i].Channel)
|
||||
// Temp type to evaluate max byte len after a marshal on batched unsubs
|
||||
@@ -890,8 +891,8 @@ func (o *Okcoin) handleSubscriptions(operation string, subs []stream.ChannelSubs
|
||||
if subs[i].Asset != asset.Empty {
|
||||
argument["instType"] = strings.ToUpper(subs[i].Asset.String())
|
||||
}
|
||||
if !subs[i].Currency.IsEmpty() {
|
||||
argument["instId"] = subs[i].Currency.String()
|
||||
if !subs[i].Pair.IsEmpty() {
|
||||
argument["instId"] = subs[i].Pair.String()
|
||||
}
|
||||
if authenticatedChannelSubscription {
|
||||
authTemp.Arguments = append(authTemp.Arguments, argument)
|
||||
|
||||
@@ -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"
|
||||
@@ -347,24 +348,24 @@ func (ok *Okx) wsReadData(ws stream.Connection) {
|
||||
}
|
||||
|
||||
// Subscribe sends a websocket subscription request to several channels to receive data.
|
||||
func (ok *Okx) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error {
|
||||
func (ok *Okx) Subscribe(channelsToSubscribe []subscription.Subscription) error {
|
||||
return ok.handleSubscription(operationSubscribe, channelsToSubscribe)
|
||||
}
|
||||
|
||||
// Unsubscribe sends a websocket unsubscription request to several channels to receive data.
|
||||
func (ok *Okx) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error {
|
||||
func (ok *Okx) Unsubscribe(channelsToUnsubscribe []subscription.Subscription) error {
|
||||
return ok.handleSubscription(operationUnsubscribe, channelsToUnsubscribe)
|
||||
}
|
||||
|
||||
// handleSubscription sends a subscription and unsubscription information thought the websocket endpoint.
|
||||
// as of the okx, exchange this endpoint sends subscription and unsubscription messages but with a list of json objects.
|
||||
func (ok *Okx) handleSubscription(operation string, subscriptions []stream.ChannelSubscription) error {
|
||||
func (ok *Okx) handleSubscription(operation string, subscriptions []subscription.Subscription) error {
|
||||
request := WSSubscriptionInformationList{Operation: operation}
|
||||
authRequests := WSSubscriptionInformationList{Operation: operation}
|
||||
ok.WsRequestSemaphore <- 1
|
||||
defer func() { <-ok.WsRequestSemaphore }()
|
||||
var channels []stream.ChannelSubscription
|
||||
var authChannels []stream.ChannelSubscription
|
||||
var channels []subscription.Subscription
|
||||
var authChannels []subscription.Subscription
|
||||
var err error
|
||||
var format currency.PairFormat
|
||||
for i := 0; i < len(subscriptions); i++ {
|
||||
@@ -431,10 +432,10 @@ func (ok *Okx) handleSubscription(operation string, subscriptions []stream.Chann
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if subscriptions[i].Currency.Base.String() == "" || subscriptions[i].Currency.Quote.String() == "" {
|
||||
if subscriptions[i].Pair.Base.String() == "" || subscriptions[i].Pair.Quote.String() == "" {
|
||||
return errIncompleteCurrencyPair
|
||||
}
|
||||
instrumentID = format.Format(subscriptions[i].Currency)
|
||||
instrumentID = format.Format(subscriptions[i].Pair)
|
||||
}
|
||||
}
|
||||
if arg.Channel == okxChannelInstruments ||
|
||||
@@ -454,7 +455,7 @@ func (ok *Okx) handleSubscription(operation string, subscriptions []stream.Chann
|
||||
arg.Channel == okxChannelAlgoOrders ||
|
||||
arg.Channel == okxChannelEstimatedPrice ||
|
||||
arg.Channel == okxChannelOptSummary {
|
||||
underlying, _ = ok.GetUnderlying(subscriptions[i].Currency, subscriptions[i].Asset)
|
||||
underlying, _ = ok.GetUnderlying(subscriptions[i].Pair, subscriptions[i].Asset)
|
||||
}
|
||||
arg.InstrumentID = instrumentID
|
||||
arg.Underlying = underlying
|
||||
@@ -482,7 +483,7 @@ func (ok *Okx) handleSubscription(operation string, subscriptions []stream.Chann
|
||||
} else {
|
||||
ok.Websocket.AddSuccessfulSubscriptions(channels...)
|
||||
}
|
||||
authChannels = []stream.ChannelSubscription{}
|
||||
authChannels = []subscription.Subscription{}
|
||||
authRequests.Arguments = []SubscriptionInfo{}
|
||||
}
|
||||
} else {
|
||||
@@ -504,7 +505,7 @@ func (ok *Okx) handleSubscription(operation string, subscriptions []stream.Chann
|
||||
} else {
|
||||
ok.Websocket.AddSuccessfulSubscriptions(channels...)
|
||||
}
|
||||
channels = []stream.ChannelSubscription{}
|
||||
channels = []subscription.Subscription{}
|
||||
request.Arguments = []SubscriptionInfo{}
|
||||
continue
|
||||
}
|
||||
@@ -833,11 +834,11 @@ func (ok *Okx) wsProcessOrderBooks(data []byte) error {
|
||||
}
|
||||
if err != nil {
|
||||
if errors.Is(err, errInvalidChecksum) {
|
||||
err = ok.Subscribe([]stream.ChannelSubscription{
|
||||
err = ok.Subscribe([]subscription.Subscription{
|
||||
{
|
||||
Channel: response.Argument.Channel,
|
||||
Asset: assets[0],
|
||||
Currency: pair,
|
||||
Channel: response.Argument.Channel,
|
||||
Asset: assets[0],
|
||||
Pair: pair,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
@@ -1297,8 +1298,8 @@ func (ok *Okx) wsProcessTickers(data []byte) error {
|
||||
}
|
||||
|
||||
// GenerateDefaultSubscriptions returns a list of default subscription message.
|
||||
func (ok *Okx) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) {
|
||||
var subscriptions []stream.ChannelSubscription
|
||||
func (ok *Okx) GenerateDefaultSubscriptions() ([]subscription.Subscription, error) {
|
||||
var subscriptions []subscription.Subscription
|
||||
assets := ok.GetAssetTypes(true)
|
||||
subs := make([]string, 0, len(defaultSubscribedChannels)+len(defaultAuthChannels))
|
||||
subs = append(subs, defaultSubscribedChannels...)
|
||||
@@ -1309,7 +1310,7 @@ func (ok *Okx) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, err
|
||||
switch subs[c] {
|
||||
case okxChannelOrders:
|
||||
for x := range assets {
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
subscriptions = append(subscriptions, subscription.Subscription{
|
||||
Channel: subs[c],
|
||||
Asset: assets[x],
|
||||
})
|
||||
@@ -1321,15 +1322,15 @@ func (ok *Okx) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, err
|
||||
return nil, err
|
||||
}
|
||||
for p := range pairs {
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
Channel: subs[c],
|
||||
Asset: assets[x],
|
||||
Currency: pairs[p],
|
||||
subscriptions = append(subscriptions, subscription.Subscription{
|
||||
Channel: subs[c],
|
||||
Asset: assets[x],
|
||||
Pair: pairs[p],
|
||||
})
|
||||
}
|
||||
}
|
||||
default:
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
subscriptions = append(subscriptions, subscription.Subscription{
|
||||
Channel: subs[c],
|
||||
})
|
||||
}
|
||||
|
||||
@@ -19,6 +19,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"
|
||||
@@ -540,36 +541,36 @@ func (p *Poloniex) WsProcessOrderbookUpdate(sequenceNumber float64, data []inter
|
||||
}
|
||||
|
||||
// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions()
|
||||
func (p *Poloniex) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) {
|
||||
enabledCurrencies, err := p.GetEnabledPairs(asset.Spot)
|
||||
func (p *Poloniex) GenerateDefaultSubscriptions() ([]subscription.Subscription, error) {
|
||||
enabledPairs, err := p.GetEnabledPairs(asset.Spot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
subscriptions := make([]stream.ChannelSubscription, 0, len(enabledCurrencies))
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
subscriptions := make([]subscription.Subscription, 0, len(enabledPairs))
|
||||
subscriptions = append(subscriptions, subscription.Subscription{
|
||||
Channel: strconv.FormatInt(wsTickerDataID, 10),
|
||||
})
|
||||
|
||||
if p.IsWebsocketAuthenticationSupported() {
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
subscriptions = append(subscriptions, subscription.Subscription{
|
||||
Channel: strconv.FormatInt(wsAccountNotificationID, 10),
|
||||
})
|
||||
}
|
||||
|
||||
for j := range enabledCurrencies {
|
||||
enabledCurrencies[j].Delimiter = currency.UnderscoreDelimiter
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
Channel: "orderbook",
|
||||
Currency: enabledCurrencies[j],
|
||||
Asset: asset.Spot,
|
||||
for j := range enabledPairs {
|
||||
enabledPairs[j].Delimiter = currency.UnderscoreDelimiter
|
||||
subscriptions = append(subscriptions, subscription.Subscription{
|
||||
Channel: "orderbook",
|
||||
Pair: enabledPairs[j],
|
||||
Asset: asset.Spot,
|
||||
})
|
||||
}
|
||||
return subscriptions, nil
|
||||
}
|
||||
|
||||
// Subscribe sends a websocket message to receive data from the channel
|
||||
func (p *Poloniex) Subscribe(sub []stream.ChannelSubscription) error {
|
||||
func (p *Poloniex) Subscribe(sub []subscription.Subscription) error {
|
||||
var creds *account.Credentials
|
||||
if p.IsWebsocketAuthenticationSupported() {
|
||||
var err error
|
||||
@@ -598,7 +599,7 @@ channels:
|
||||
sub[i].Channel):
|
||||
subscriptionRequest.Channel = wsTickerDataID
|
||||
default:
|
||||
subscriptionRequest.Channel = sub[i].Currency.String()
|
||||
subscriptionRequest.Channel = sub[i].Pair.String()
|
||||
}
|
||||
|
||||
err := p.Websocket.Conn.SendJSONMessage(subscriptionRequest)
|
||||
@@ -616,7 +617,7 @@ channels:
|
||||
}
|
||||
|
||||
// Unsubscribe sends a websocket message to stop receiving data from the channel
|
||||
func (p *Poloniex) Unsubscribe(unsub []stream.ChannelSubscription) error {
|
||||
func (p *Poloniex) Unsubscribe(unsub []subscription.Subscription) error {
|
||||
var creds *account.Credentials
|
||||
if p.IsWebsocketAuthenticationSupported() {
|
||||
var err error
|
||||
@@ -645,7 +646,7 @@ channels:
|
||||
unsub[i].Channel):
|
||||
unsubscriptionRequest.Channel = wsTickerDataID
|
||||
default:
|
||||
unsubscriptionRequest.Channel = unsub[i].Currency.String()
|
||||
unsubscriptionRequest.Channel = unsub[i].Pair.String()
|
||||
}
|
||||
err := p.Websocket.Conn.SendJSONMessage(unsubscriptionRequest)
|
||||
if err != nil {
|
||||
|
||||
@@ -18,6 +18,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/portfolio/withdraw"
|
||||
@@ -267,7 +268,7 @@ func (c *CustomEx) SupportsREST() bool {
|
||||
}
|
||||
|
||||
// GetSubscriptions is a mock method for CustomEx
|
||||
func (c *CustomEx) GetSubscriptions() ([]stream.ChannelSubscription, error) {
|
||||
func (c *CustomEx) GetSubscriptions() ([]subscription.Subscription, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -322,12 +323,12 @@ func (c *CustomEx) SupportsWebsocket() bool {
|
||||
}
|
||||
|
||||
// SubscribeToWebsocketChannels is a mock method for CustomEx
|
||||
func (c *CustomEx) SubscribeToWebsocketChannels(_ []stream.ChannelSubscription) error {
|
||||
func (c *CustomEx) SubscribeToWebsocketChannels(_ []subscription.Subscription) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnsubscribeToWebsocketChannels is a mock method for CustomEx
|
||||
func (c *CustomEx) UnsubscribeToWebsocketChannels(_ []stream.ChannelSubscription) error {
|
||||
func (c *CustomEx) UnsubscribeToWebsocketChannels(_ []subscription.Subscription) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/subscription"
|
||||
)
|
||||
|
||||
// This package is only to be referenced in test files
|
||||
@@ -61,8 +62,8 @@ func NewTestWebsocket() *stream.Websocket {
|
||||
ToRoutine: make(chan interface{}, 1000),
|
||||
TrafficAlert: make(chan struct{}),
|
||||
ReadMessageErrors: make(chan error),
|
||||
Subscribe: make(chan []stream.ChannelSubscription, 10),
|
||||
Unsubscribe: make(chan []stream.ChannelSubscription, 10),
|
||||
Subscribe: make(chan []subscription.Subscription, 10),
|
||||
Unsubscribe: make(chan []subscription.Subscription, 10),
|
||||
Match: stream.NewMatch(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,33 +31,6 @@ type Response struct {
|
||||
Raw []byte
|
||||
}
|
||||
|
||||
// DefaultChannelKey is the fallback key for AddSuccessfulSubscriptions
|
||||
type DefaultChannelKey struct {
|
||||
Channel string
|
||||
Currency currency.Pair
|
||||
Asset asset.Item
|
||||
}
|
||||
|
||||
// ChannelState tracks the status of a subscription channel
|
||||
type ChannelState uint8
|
||||
|
||||
const (
|
||||
ChannelStateUnknown ChannelState = iota // ChannelStateUnknown means subscription state is not registered, but doesn't imply Inactive
|
||||
ChannelSubscribing // ChannelSubscribing means channel is in the process of subscribing
|
||||
ChannelSubscribed // ChannelSubscribed means the channel has finished a successful and acknowledged subscription
|
||||
ChannelUnsubscribing // ChannelUnsubscribing means the channel has started to unsubscribe, but not yet confirmed
|
||||
)
|
||||
|
||||
// ChannelSubscription container for streaming subscription channels
|
||||
type ChannelSubscription struct {
|
||||
Key any
|
||||
Channel string
|
||||
Currency currency.Pair
|
||||
Asset asset.Item
|
||||
Params map[string]interface{}
|
||||
State ChannelState
|
||||
}
|
||||
|
||||
// ConnectionSetup defines variables for an individual stream connection
|
||||
type ConnectionSetup struct {
|
||||
ResponseCheckTimeout time.Duration
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/config"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/subscription"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
)
|
||||
|
||||
@@ -30,6 +31,8 @@ var (
|
||||
ErrSubscribedAlready = errors.New("duplicate subscription")
|
||||
// ErrSubscriptionFailure defines an error when a subscription fails
|
||||
ErrSubscriptionFailure = errors.New("subscription failure")
|
||||
// ErrSubscriptionNotSupported defines an error when a subscription channel is not supported by an exchange
|
||||
ErrSubscriptionNotSupported = errors.New("subscription channel not supported ")
|
||||
// ErrUnsubscribeFailure defines an error when a unsubscribe fails
|
||||
ErrUnsubscribeFailure = errors.New("unsubscribe failure")
|
||||
// ErrChannelInStateAlready defines an error when a subscription channel is already in a new state
|
||||
@@ -79,8 +82,8 @@ func New() *Websocket {
|
||||
ToRoutine: make(chan interface{}, defaultJobBuffer),
|
||||
TrafficAlert: make(chan struct{}),
|
||||
ReadMessageErrors: make(chan error),
|
||||
Subscribe: make(chan []ChannelSubscription),
|
||||
Unsubscribe: make(chan []ChannelSubscription),
|
||||
Subscribe: make(chan []subscription.Subscription),
|
||||
Unsubscribe: make(chan []subscription.Subscription),
|
||||
Match: NewMatch(),
|
||||
}
|
||||
}
|
||||
@@ -869,9 +872,9 @@ func (w *Websocket) GetName() string {
|
||||
|
||||
// GetChannelDifference finds the difference between the subscribed channels
|
||||
// and the new subscription list when pairs are disabled or enabled.
|
||||
func (w *Websocket) GetChannelDifference(genSubs []ChannelSubscription) (sub, unsub []ChannelSubscription) {
|
||||
func (w *Websocket) GetChannelDifference(genSubs []subscription.Subscription) (sub, unsub []subscription.Subscription) {
|
||||
w.subscriptionMutex.RLock()
|
||||
unsubMap := make(map[any]ChannelSubscription, len(w.subscriptions))
|
||||
unsubMap := make(map[any]subscription.Subscription, len(w.subscriptions))
|
||||
for k, c := range w.subscriptions {
|
||||
unsubMap[k] = *c
|
||||
}
|
||||
@@ -894,7 +897,7 @@ func (w *Websocket) GetChannelDifference(genSubs []ChannelSubscription) (sub, un
|
||||
}
|
||||
|
||||
// UnsubscribeChannels unsubscribes from a websocket channel
|
||||
func (w *Websocket) UnsubscribeChannels(channels []ChannelSubscription) error {
|
||||
func (w *Websocket) UnsubscribeChannels(channels []subscription.Subscription) error {
|
||||
if len(channels) == 0 {
|
||||
return fmt.Errorf("%s websocket: %w", w.exchangeName, errNoSubscriptionsSupplied)
|
||||
}
|
||||
@@ -912,16 +915,16 @@ func (w *Websocket) UnsubscribeChannels(channels []ChannelSubscription) error {
|
||||
}
|
||||
|
||||
// ResubscribeToChannel resubscribes to channel
|
||||
func (w *Websocket) ResubscribeToChannel(subscribedChannel *ChannelSubscription) error {
|
||||
err := w.UnsubscribeChannels([]ChannelSubscription{*subscribedChannel})
|
||||
func (w *Websocket) ResubscribeToChannel(subscribedChannel *subscription.Subscription) error {
|
||||
err := w.UnsubscribeChannels([]subscription.Subscription{*subscribedChannel})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return w.SubscribeToChannels([]ChannelSubscription{*subscribedChannel})
|
||||
return w.SubscribeToChannels([]subscription.Subscription{*subscribedChannel})
|
||||
}
|
||||
|
||||
// SubscribeToChannels appends supplied channels to channelsToSubscribe
|
||||
func (w *Websocket) SubscribeToChannels(channels []ChannelSubscription) error {
|
||||
func (w *Websocket) SubscribeToChannels(channels []subscription.Subscription) error {
|
||||
if err := w.checkSubscriptions(channels); err != nil {
|
||||
return fmt.Errorf("%s websocket: %w", w.exchangeName, common.AppendError(ErrSubscriptionFailure, err))
|
||||
}
|
||||
@@ -933,7 +936,7 @@ func (w *Websocket) SubscribeToChannels(channels []ChannelSubscription) error {
|
||||
|
||||
// AddSubscription adds a subscription to the subscription lists
|
||||
// Unlike AddSubscriptions this method will error if the subscription already exists
|
||||
func (w *Websocket) AddSubscription(c *ChannelSubscription) error {
|
||||
func (w *Websocket) AddSubscription(c *subscription.Subscription) error {
|
||||
w.subscriptionMutex.Lock()
|
||||
defer w.subscriptionMutex.Unlock()
|
||||
if w.subscriptions == nil {
|
||||
@@ -952,7 +955,7 @@ func (w *Websocket) AddSubscription(c *ChannelSubscription) error {
|
||||
|
||||
// SetSubscriptionState sets an existing subscription state
|
||||
// returns an error if the subscription is not found, or the new state is already set
|
||||
func (w *Websocket) SetSubscriptionState(c *ChannelSubscription, state ChannelState) error {
|
||||
func (w *Websocket) SetSubscriptionState(c *subscription.Subscription, state subscription.State) error {
|
||||
w.subscriptionMutex.Lock()
|
||||
defer w.subscriptionMutex.Unlock()
|
||||
if w.subscriptions == nil {
|
||||
@@ -966,7 +969,7 @@ func (w *Websocket) SetSubscriptionState(c *ChannelSubscription, state ChannelSt
|
||||
if state == p.State {
|
||||
return ErrChannelInStateAlready
|
||||
}
|
||||
if state > ChannelUnsubscribing {
|
||||
if state > subscription.UnsubscribingState {
|
||||
return errInvalidChannelState
|
||||
}
|
||||
p.State = state
|
||||
@@ -975,7 +978,7 @@ func (w *Websocket) SetSubscriptionState(c *ChannelSubscription, state ChannelSt
|
||||
|
||||
// AddSuccessfulSubscriptions adds subscriptions to the subscription lists that
|
||||
// has been successfully subscribed
|
||||
func (w *Websocket) AddSuccessfulSubscriptions(channels ...ChannelSubscription) {
|
||||
func (w *Websocket) AddSuccessfulSubscriptions(channels ...subscription.Subscription) {
|
||||
w.subscriptionMutex.Lock()
|
||||
defer w.subscriptionMutex.Unlock()
|
||||
if w.subscriptions == nil {
|
||||
@@ -984,13 +987,13 @@ func (w *Websocket) AddSuccessfulSubscriptions(channels ...ChannelSubscription)
|
||||
for _, cN := range channels {
|
||||
c := cN // cN is an iteration var; Not safe to make a pointer to
|
||||
key := c.EnsureKeyed()
|
||||
c.State = ChannelSubscribed
|
||||
c.State = subscription.SubscribedState
|
||||
w.subscriptions[key] = &c
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveSubscriptions removes subscriptions from the subscription list
|
||||
func (w *Websocket) RemoveSubscriptions(channels ...ChannelSubscription) {
|
||||
func (w *Websocket) RemoveSubscriptions(channels ...subscription.Subscription) {
|
||||
w.subscriptionMutex.Lock()
|
||||
defer w.subscriptionMutex.Unlock()
|
||||
if w.subscriptions == nil {
|
||||
@@ -1002,22 +1005,9 @@ func (w *Websocket) RemoveSubscriptions(channels ...ChannelSubscription) {
|
||||
}
|
||||
}
|
||||
|
||||
// EnsureKeyed sets the default key on a channel if it doesn't have one
|
||||
// Returns key for convenience
|
||||
func (c *ChannelSubscription) EnsureKeyed() any {
|
||||
if c.Key == nil {
|
||||
c.Key = DefaultChannelKey{
|
||||
Channel: c.Channel,
|
||||
Asset: c.Asset,
|
||||
Currency: c.Currency,
|
||||
}
|
||||
}
|
||||
return c.Key
|
||||
}
|
||||
|
||||
// GetSubscription returns a pointer to a copy of the subscription at the key provided
|
||||
// returns nil if no subscription is at that key or the key is nil
|
||||
func (w *Websocket) GetSubscription(key any) *ChannelSubscription {
|
||||
func (w *Websocket) GetSubscription(key any) *subscription.Subscription {
|
||||
if key == nil || w == nil || w.subscriptions == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -1031,10 +1021,10 @@ func (w *Websocket) GetSubscription(key any) *ChannelSubscription {
|
||||
}
|
||||
|
||||
// GetSubscriptions returns a new slice of the subscriptions
|
||||
func (w *Websocket) GetSubscriptions() []ChannelSubscription {
|
||||
func (w *Websocket) GetSubscriptions() []subscription.Subscription {
|
||||
w.subscriptionMutex.RLock()
|
||||
defer w.subscriptionMutex.RUnlock()
|
||||
subs := make([]ChannelSubscription, 0, len(w.subscriptions))
|
||||
subs := make([]subscription.Subscription, 0, len(w.subscriptions))
|
||||
for _, c := range w.subscriptions {
|
||||
subs = append(subs, *c)
|
||||
}
|
||||
@@ -1082,7 +1072,7 @@ func checkWebsocketURL(s string) error {
|
||||
|
||||
// checkSubscriptions checks subscriptions against the max subscription limit
|
||||
// and if the subscription already exists.
|
||||
func (w *Websocket) checkSubscriptions(subs []ChannelSubscription) error {
|
||||
func (w *Websocket) checkSubscriptions(subs []subscription.Subscription) error {
|
||||
if len(subs) == 0 {
|
||||
return errNoSubscriptionsSupplied
|
||||
}
|
||||
|
||||
@@ -20,8 +20,8 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/thrasher-corp/gocryptotrader/config"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/protocol"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/subscription"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -73,10 +73,10 @@ var defaultSetup = &WebsocketSetup{
|
||||
DefaultURL: "testDefaultURL",
|
||||
RunningURL: "wss://testRunningURL",
|
||||
Connector: func() error { return nil },
|
||||
Subscriber: func(_ []ChannelSubscription) error { return nil },
|
||||
Unsubscriber: func(_ []ChannelSubscription) error { return nil },
|
||||
GenerateSubscriptions: func() ([]ChannelSubscription, error) {
|
||||
return []ChannelSubscription{
|
||||
Subscriber: func(_ []subscription.Subscription) error { return nil },
|
||||
Unsubscriber: func(_ []subscription.Subscription) error { return nil },
|
||||
GenerateSubscriptions: func() ([]subscription.Subscription, error) {
|
||||
return []subscription.Subscription{
|
||||
{Channel: "TestSub"},
|
||||
{Channel: "TestSub2", Key: "purple"},
|
||||
{Channel: "TestSub3", Key: testSubKey{"mauve"}},
|
||||
@@ -156,20 +156,20 @@ func TestSetup(t *testing.T) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errWebsocketSubscriberUnset)
|
||||
}
|
||||
|
||||
websocketSetup.Subscriber = func([]ChannelSubscription) error { return nil }
|
||||
websocketSetup.Subscriber = func([]subscription.Subscription) error { return nil }
|
||||
websocketSetup.Features.Unsubscribe = true
|
||||
err = w.Setup(websocketSetup)
|
||||
if !errors.Is(err, errWebsocketUnsubscriberUnset) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errWebsocketUnsubscriberUnset)
|
||||
}
|
||||
|
||||
websocketSetup.Unsubscriber = func([]ChannelSubscription) error { return nil }
|
||||
websocketSetup.Unsubscriber = func([]subscription.Subscription) error { return nil }
|
||||
err = w.Setup(websocketSetup)
|
||||
if !errors.Is(err, errWebsocketSubscriptionsGeneratorUnset) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errWebsocketSubscriptionsGeneratorUnset)
|
||||
}
|
||||
|
||||
websocketSetup.GenerateSubscriptions = func() ([]ChannelSubscription, error) { return nil, nil }
|
||||
websocketSetup.GenerateSubscriptions = func() ([]subscription.Subscription, error) { return nil, nil }
|
||||
err = w.Setup(websocketSetup)
|
||||
if !errors.Is(err, errDefaultURLIsEmpty) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errDefaultURLIsEmpty)
|
||||
@@ -504,11 +504,11 @@ func TestSubscribeUnsubscribe(t *testing.T) {
|
||||
ws := *New()
|
||||
assert.NoError(t, ws.Setup(defaultSetup), "WS Setup should not error")
|
||||
|
||||
fnSub := func(subs []ChannelSubscription) error {
|
||||
fnSub := func(subs []subscription.Subscription) error {
|
||||
ws.AddSuccessfulSubscriptions(subs...)
|
||||
return nil
|
||||
}
|
||||
fnUnsub := func(unsubs []ChannelSubscription) error {
|
||||
fnUnsub := func(unsubs []subscription.Subscription) error {
|
||||
ws.RemoveSubscriptions(unsubs...)
|
||||
return nil
|
||||
}
|
||||
@@ -522,7 +522,7 @@ func TestSubscribeUnsubscribe(t *testing.T) {
|
||||
assert.Nil(t, ws.GetSubscription(42), "GetSubscription on empty internal map should return")
|
||||
assert.NoError(t, ws.SubscribeToChannels(subs), "Basic Subscribing should not error")
|
||||
assert.Len(t, ws.GetSubscriptions(), 4, "Should have 4 subscriptions")
|
||||
byDefKey := ws.GetSubscription(DefaultChannelKey{Channel: "TestSub"})
|
||||
byDefKey := ws.GetSubscription(subscription.DefaultKey{Channel: "TestSub"})
|
||||
if assert.NotNil(t, byDefKey, "GetSubscription by default key should find a channel") {
|
||||
assert.Equal(t, "TestSub", byDefKey.Channel, "GetSubscription by default key should return a pointer a copy of the right channel")
|
||||
assert.NotSame(t, byDefKey, ws.subscriptions["TestSub"], "GetSubscription returns a fresh pointer")
|
||||
@@ -556,18 +556,18 @@ func TestResubscribe(t *testing.T) {
|
||||
err = ws.Setup(defaultSetup)
|
||||
assert.NoError(t, err, "WS Setup should not error")
|
||||
|
||||
fnSub := func(subs []ChannelSubscription) error {
|
||||
fnSub := func(subs []subscription.Subscription) error {
|
||||
ws.AddSuccessfulSubscriptions(subs...)
|
||||
return nil
|
||||
}
|
||||
fnUnsub := func(unsubs []ChannelSubscription) error {
|
||||
fnUnsub := func(unsubs []subscription.Subscription) error {
|
||||
ws.RemoveSubscriptions(unsubs...)
|
||||
return nil
|
||||
}
|
||||
ws.Subscriber = fnSub
|
||||
ws.Unsubscriber = fnUnsub
|
||||
|
||||
channel := []ChannelSubscription{{Channel: "resubTest"}}
|
||||
channel := []subscription.Subscription{{Channel: "resubTest"}}
|
||||
|
||||
assert.ErrorIs(t, ws.ResubscribeToChannel(&channel[0]), ErrSubscriptionNotFound, "Resubscribe should error when channel isn't subscribed yet")
|
||||
assert.NoError(t, ws.SubscribeToChannels(channel), "Subscribe should not error")
|
||||
@@ -579,25 +579,25 @@ func TestSubscriptionState(t *testing.T) {
|
||||
t.Parallel()
|
||||
ws := New()
|
||||
|
||||
c := &ChannelSubscription{Key: 42, Channel: "Gophers", State: ChannelSubscribing}
|
||||
assert.ErrorIs(t, ws.SetSubscriptionState(c, ChannelUnsubscribing), ErrSubscriptionNotFound, "Setting an imaginary sub should error")
|
||||
c := &subscription.Subscription{Key: 42, Channel: "Gophers", State: subscription.SubscribingState}
|
||||
assert.ErrorIs(t, ws.SetSubscriptionState(c, subscription.UnsubscribingState), ErrSubscriptionNotFound, "Setting an imaginary sub should error")
|
||||
|
||||
assert.NoError(t, ws.AddSubscription(c), "Adding first subscription should not error")
|
||||
found := ws.GetSubscription(42)
|
||||
assert.NotNil(t, found, "Should find the subscription")
|
||||
assert.Equal(t, ChannelSubscribing, found.State, "Subscription should be Subscribing")
|
||||
assert.Equal(t, subscription.SubscribingState, found.State, "Subscription should be Subscribing")
|
||||
assert.ErrorIs(t, ws.AddSubscription(c), ErrSubscribedAlready, "Adding an already existing sub should error")
|
||||
assert.ErrorIs(t, ws.SetSubscriptionState(c, ChannelSubscribing), ErrChannelInStateAlready, "Setting Same state should error")
|
||||
assert.ErrorIs(t, ws.SetSubscriptionState(c, ChannelUnsubscribing+1), errInvalidChannelState, "Setting an invalid state should error")
|
||||
assert.ErrorIs(t, ws.SetSubscriptionState(c, subscription.SubscribingState), ErrChannelInStateAlready, "Setting Same state should error")
|
||||
assert.ErrorIs(t, ws.SetSubscriptionState(c, subscription.UnsubscribingState+1), errInvalidChannelState, "Setting an invalid state should error")
|
||||
|
||||
ws.AddSuccessfulSubscriptions(*c)
|
||||
found = ws.GetSubscription(42)
|
||||
assert.NotNil(t, found, "Should find the subscription")
|
||||
assert.Equal(t, found.State, ChannelSubscribed, "Subscription should be subscribed state")
|
||||
assert.Equal(t, found.State, subscription.SubscribedState, "Subscription should be subscribed state")
|
||||
|
||||
assert.NoError(t, ws.SetSubscriptionState(c, ChannelUnsubscribing), "Setting Unsub state should not error")
|
||||
assert.NoError(t, ws.SetSubscriptionState(c, subscription.UnsubscribingState), "Setting Unsub state should not error")
|
||||
found = ws.GetSubscription(42)
|
||||
assert.Equal(t, found.State, ChannelUnsubscribing, "Subscription should be unsubscribing state")
|
||||
assert.Equal(t, found.State, subscription.UnsubscribingState, "Subscription should be unsubscribing state")
|
||||
}
|
||||
|
||||
// TestRemoveSubscriptions tests removing a subscription
|
||||
@@ -605,7 +605,7 @@ func TestRemoveSubscriptions(t *testing.T) {
|
||||
t.Parallel()
|
||||
ws := New()
|
||||
|
||||
c := &ChannelSubscription{Key: 42, Channel: "Unite!"}
|
||||
c := &subscription.Subscription{Key: 42, Channel: "Unite!"}
|
||||
assert.NoError(t, ws.AddSubscription(c), "Adding first subscription should not error")
|
||||
assert.NotNil(t, ws.GetSubscription(42), "Added subscription should be findable")
|
||||
|
||||
@@ -668,35 +668,6 @@ func TestGetSubscriptions(t *testing.T) {
|
||||
assert.Equal(t, "hello3", w.GetSubscriptions()[0].Channel, "GetSubscriptions should return the correct channel details")
|
||||
}
|
||||
|
||||
// TestEnsureKeyed logic test
|
||||
func TestEnsureKeyed(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := ChannelSubscription{
|
||||
Channel: "candles",
|
||||
Asset: asset.Spot,
|
||||
Currency: currency.NewPair(currency.BTC, currency.USDT),
|
||||
}
|
||||
k1, ok := c.EnsureKeyed().(DefaultChannelKey)
|
||||
if assert.True(t, ok, "EnsureKeyed should return a DefaultChannelKey") {
|
||||
assert.Exactly(t, k1, c.Key, "EnsureKeyed should set the same key")
|
||||
assert.Equal(t, k1.Channel, c.Channel, "DefaultChannelKey channel should be correct")
|
||||
assert.Equal(t, k1.Asset, c.Asset, "DefaultChannelKey asset should be correct")
|
||||
assert.Equal(t, k1.Currency, c.Currency, "DefaultChannelKey currency should be correct")
|
||||
}
|
||||
type platypus string
|
||||
c = ChannelSubscription{
|
||||
Key: platypus("Gerald"),
|
||||
Channel: "orderbook",
|
||||
Asset: asset.Margin,
|
||||
Currency: currency.NewPair(currency.ETH, currency.USDC),
|
||||
}
|
||||
k2, ok := c.EnsureKeyed().(platypus)
|
||||
if assert.True(t, ok, "EnsureKeyed should return a platypus") {
|
||||
assert.Exactly(t, k2, c.Key, "EnsureKeyed should set the same key")
|
||||
assert.EqualValues(t, "Gerald", k2, "key should have the correct value")
|
||||
}
|
||||
}
|
||||
|
||||
// TestSetCanUseAuthenticatedEndpoints logic test
|
||||
func TestSetCanUseAuthenticatedEndpoints(t *testing.T) {
|
||||
t.Parallel()
|
||||
@@ -1076,7 +1047,7 @@ func TestGetChannelDifference(t *testing.T) {
|
||||
t.Parallel()
|
||||
web := Websocket{}
|
||||
|
||||
newChans := []ChannelSubscription{
|
||||
newChans := []subscription.Subscription{
|
||||
{
|
||||
Channel: "Test1",
|
||||
},
|
||||
@@ -1093,7 +1064,7 @@ func TestGetChannelDifference(t *testing.T) {
|
||||
|
||||
web.AddSuccessfulSubscriptions(subs...)
|
||||
|
||||
flushedSubs := []ChannelSubscription{
|
||||
flushedSubs := []subscription.Subscription{
|
||||
{
|
||||
Channel: "Test2",
|
||||
},
|
||||
@@ -1103,7 +1074,7 @@ func TestGetChannelDifference(t *testing.T) {
|
||||
assert.Len(t, subs, 0, "Should get the correct number of subs")
|
||||
assert.Len(t, unsubs, 2, "Should get the correct number of unsubs")
|
||||
|
||||
flushedSubs = []ChannelSubscription{
|
||||
flushedSubs = []subscription.Subscription{
|
||||
{
|
||||
Channel: "Test2",
|
||||
},
|
||||
@@ -1126,23 +1097,23 @@ func TestGetChannelDifference(t *testing.T) {
|
||||
// GenSubs defines a theoretical exchange with pair management
|
||||
type GenSubs struct {
|
||||
EnabledPairs currency.Pairs
|
||||
subscribos []ChannelSubscription
|
||||
unsubscribos []ChannelSubscription
|
||||
subscribos []subscription.Subscription
|
||||
unsubscribos []subscription.Subscription
|
||||
}
|
||||
|
||||
// generateSubs default subs created from the enabled pairs list
|
||||
func (g *GenSubs) generateSubs() ([]ChannelSubscription, error) {
|
||||
superduperchannelsubs := make([]ChannelSubscription, len(g.EnabledPairs))
|
||||
func (g *GenSubs) generateSubs() ([]subscription.Subscription, error) {
|
||||
superduperchannelsubs := make([]subscription.Subscription, len(g.EnabledPairs))
|
||||
for i := range g.EnabledPairs {
|
||||
superduperchannelsubs[i] = ChannelSubscription{
|
||||
Channel: "TEST:" + strconv.FormatInt(int64(i), 10),
|
||||
Currency: g.EnabledPairs[i],
|
||||
superduperchannelsubs[i] = subscription.Subscription{
|
||||
Channel: "TEST:" + strconv.FormatInt(int64(i), 10),
|
||||
Pair: g.EnabledPairs[i],
|
||||
}
|
||||
}
|
||||
return superduperchannelsubs, nil
|
||||
}
|
||||
|
||||
func (g *GenSubs) SUBME(subs []ChannelSubscription) error {
|
||||
func (g *GenSubs) SUBME(subs []subscription.Subscription) error {
|
||||
if len(subs) == 0 {
|
||||
return errors.New("WOW")
|
||||
}
|
||||
@@ -1150,7 +1121,7 @@ func (g *GenSubs) SUBME(subs []ChannelSubscription) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *GenSubs) UNSUBME(unsubs []ChannelSubscription) error {
|
||||
func (g *GenSubs) UNSUBME(unsubs []subscription.Subscription) error {
|
||||
if len(unsubs) == 0 {
|
||||
return errors.New("WOW")
|
||||
}
|
||||
@@ -1197,19 +1168,19 @@ func TestFlushChannels(t *testing.T) {
|
||||
// this to an unconnected state
|
||||
}
|
||||
|
||||
problemFunc := func() ([]ChannelSubscription, error) {
|
||||
problemFunc := func() ([]subscription.Subscription, error) {
|
||||
return nil, errors.New("problems")
|
||||
}
|
||||
|
||||
noSub := func() ([]ChannelSubscription, error) {
|
||||
noSub := func() ([]subscription.Subscription, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Disable pair and flush system
|
||||
newgen.EnabledPairs = []currency.Pair{
|
||||
currency.NewPair(currency.BTC, currency.AUD)}
|
||||
web.GenerateSubs = func() ([]ChannelSubscription, error) {
|
||||
return []ChannelSubscription{{Channel: "test"}}, nil
|
||||
web.GenerateSubs = func() ([]subscription.Subscription, error) {
|
||||
return []subscription.Subscription{{Channel: "test"}}, nil
|
||||
}
|
||||
err = web.FlushChannels()
|
||||
if err != nil {
|
||||
@@ -1256,14 +1227,14 @@ func TestFlushChannels(t *testing.T) {
|
||||
web.subscriptionMutex.Lock()
|
||||
web.subscriptions = subscriptionMap{
|
||||
41: {
|
||||
Key: 41,
|
||||
Channel: "match channel",
|
||||
Currency: currency.NewPair(currency.BTC, currency.AUD),
|
||||
Key: 41,
|
||||
Channel: "match channel",
|
||||
Pair: currency.NewPair(currency.BTC, currency.AUD),
|
||||
},
|
||||
42: {
|
||||
Key: 42,
|
||||
Channel: "unsub channel",
|
||||
Currency: currency.NewPair(currency.THETA, currency.USDT),
|
||||
Key: 42,
|
||||
Channel: "unsub channel",
|
||||
Pair: currency.NewPair(currency.THETA, currency.USDT),
|
||||
},
|
||||
}
|
||||
web.subscriptionMutex.Unlock()
|
||||
@@ -1309,10 +1280,10 @@ func TestEnable(t *testing.T) {
|
||||
connector: connect,
|
||||
Wg: new(sync.WaitGroup),
|
||||
ShutdownC: make(chan struct{}),
|
||||
GenerateSubs: func() ([]ChannelSubscription, error) {
|
||||
return []ChannelSubscription{{Channel: "test"}}, nil
|
||||
GenerateSubs: func() ([]subscription.Subscription, error) {
|
||||
return []subscription.Subscription{{Channel: "test"}}, nil
|
||||
},
|
||||
Subscriber: func(cs []ChannelSubscription) error { return nil },
|
||||
Subscriber: func(cs []subscription.Subscription) error { return nil },
|
||||
}
|
||||
|
||||
err := web.Enable()
|
||||
@@ -1466,7 +1437,7 @@ func TestCheckSubscriptions(t *testing.T) {
|
||||
|
||||
ws.MaxSubscriptionsPerConnection = 1
|
||||
|
||||
err = ws.checkSubscriptions([]ChannelSubscription{{}, {}})
|
||||
err = ws.checkSubscriptions([]subscription.Subscription{{}, {}})
|
||||
if !errors.Is(err, errSubscriptionsExceedsLimit) {
|
||||
t.Fatalf("received: %v, but expected: %v", err, errSubscriptionsExceedsLimit)
|
||||
}
|
||||
@@ -1474,12 +1445,12 @@ func TestCheckSubscriptions(t *testing.T) {
|
||||
ws.MaxSubscriptionsPerConnection = 2
|
||||
|
||||
ws.subscriptions = subscriptionMap{42: {Key: 42, Channel: "test"}}
|
||||
err = ws.checkSubscriptions([]ChannelSubscription{{Key: 42, Channel: "test"}})
|
||||
err = ws.checkSubscriptions([]subscription.Subscription{{Key: 42, Channel: "test"}})
|
||||
if !errors.Is(err, errChannelAlreadySubscribed) {
|
||||
t.Fatalf("received: %v, but expected: %v", err, errChannelAlreadySubscribed)
|
||||
}
|
||||
|
||||
err = ws.checkSubscriptions([]ChannelSubscription{{}})
|
||||
err = ws.checkSubscriptions([]subscription.Subscription{{}})
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: %v, but expected: %v", err, nil)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/fill"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/protocol"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/stream/buffer"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/subscription"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/trade"
|
||||
)
|
||||
|
||||
@@ -22,7 +23,7 @@ const (
|
||||
UnhandledMessage = " - Unhandled websocket message: "
|
||||
)
|
||||
|
||||
type subscriptionMap map[any]*ChannelSubscription
|
||||
type subscriptionMap map[any]*subscription.Subscription
|
||||
|
||||
// Websocket defines a return type for websocket connections via the interface
|
||||
// wrapper for routine processing
|
||||
@@ -50,18 +51,18 @@ type Websocket struct {
|
||||
|
||||
subscriptionMutex sync.RWMutex
|
||||
subscriptions subscriptionMap
|
||||
Subscribe chan []ChannelSubscription
|
||||
Unsubscribe chan []ChannelSubscription
|
||||
Subscribe chan []subscription.Subscription
|
||||
Unsubscribe chan []subscription.Subscription
|
||||
|
||||
// Subscriber function for package defined websocket subscriber
|
||||
// functionality
|
||||
Subscriber func([]ChannelSubscription) error
|
||||
Subscriber func([]subscription.Subscription) error
|
||||
// Unsubscriber function for packaged defined websocket unsubscriber
|
||||
// functionality
|
||||
Unsubscriber func([]ChannelSubscription) error
|
||||
Unsubscriber func([]subscription.Subscription) error
|
||||
// GenerateSubs function for package defined websocket generate
|
||||
// subscriptions functionality
|
||||
GenerateSubs func() ([]ChannelSubscription, error)
|
||||
GenerateSubs func() ([]subscription.Subscription, error)
|
||||
|
||||
DataHandler chan interface{}
|
||||
ToRoutine chan interface{}
|
||||
@@ -108,9 +109,9 @@ type WebsocketSetup struct {
|
||||
RunningURL string
|
||||
RunningURLAuth string
|
||||
Connector func() error
|
||||
Subscriber func([]ChannelSubscription) error
|
||||
Unsubscriber func([]ChannelSubscription) error
|
||||
GenerateSubscriptions func() ([]ChannelSubscription, error)
|
||||
Subscriber func([]subscription.Subscription) error
|
||||
Unsubscriber func([]subscription.Subscription) error
|
||||
GenerateSubscriptions func() ([]subscription.Subscription, error)
|
||||
Features *protocol.Features
|
||||
|
||||
// Local orderbook buffer config values
|
||||
|
||||
92
exchanges/subscription/subscription.go
Normal file
92
exchanges/subscription/subscription.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package subscription
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
|
||||
)
|
||||
|
||||
// DefaultKey is the fallback key for AddSuccessfulSubscriptions
|
||||
type DefaultKey struct {
|
||||
Channel string
|
||||
Pair currency.Pair
|
||||
Asset asset.Item
|
||||
}
|
||||
|
||||
// State tracks the status of a subscription channel
|
||||
type State uint8
|
||||
|
||||
const (
|
||||
UnknownState State = iota // UnknownState subscription state is not registered, but doesn't imply Inactive
|
||||
SubscribingState // SubscribingState means channel is in the process of subscribing
|
||||
SubscribedState // SubscribedState means the channel has finished a successful and acknowledged subscription
|
||||
UnsubscribingState // UnsubscribingState means the channel has started to unsubscribe, but not yet confirmed
|
||||
|
||||
TickerChannel = "ticker" // TickerChannel Subscription Type
|
||||
OrderbookChannel = "orderbook" // OrderbookChannel Subscription Type
|
||||
CandlesChannel = "candles" // CandlesChannel Subscription Type
|
||||
AllOrdersChannel = "allOrders" // AllOrdersChannel Subscription Type
|
||||
AllTradesChannel = "allTrades" // AllTradesChannel Subscription Type
|
||||
MyTradesChannel = "myTrades" // MyTradesChannel Subscription Type
|
||||
MyOrdersChannel = "myOrders" // MyOrdersChannel Subscription Type
|
||||
)
|
||||
|
||||
// Subscription container for streaming subscriptions
|
||||
type Subscription struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Key any `json:"-"`
|
||||
Channel string `json:"channel,omitempty"`
|
||||
Pair currency.Pair `json:"pair,omitempty"`
|
||||
Asset asset.Item `json:"asset,omitempty"`
|
||||
Params map[string]interface{} `json:"params,omitempty"`
|
||||
State State `json:"-"`
|
||||
Interval kline.Interval `json:"interval,omitempty"`
|
||||
Levels int `json:"levels,omitempty"`
|
||||
Authenticated bool `json:"authenticated,omitempty"`
|
||||
}
|
||||
|
||||
// MarshalJSON generates a JSON representation of a Subscription, specifically for config writing
|
||||
// The only reason it exists is to avoid having to make Pair a pointer, since that would be generally painful
|
||||
// If Pair becomes a pointer, this method is redundant and should be removed
|
||||
func (s *Subscription) MarshalJSON() ([]byte, error) {
|
||||
// None of the usual type embedding tricks seem to work for not emitting an nil Pair
|
||||
// The embedded type's Pair always fills the empty value
|
||||
type MaybePair struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Channel string `json:"channel,omitempty"`
|
||||
Asset asset.Item `json:"asset,omitempty"`
|
||||
Params map[string]interface{} `json:"params,omitempty"`
|
||||
Interval kline.Interval `json:"interval,omitempty"`
|
||||
Levels int `json:"levels,omitempty"`
|
||||
Authenticated bool `json:"authenticated,omitempty"`
|
||||
Pair *currency.Pair `json:"pair,omitempty"`
|
||||
}
|
||||
|
||||
k := MaybePair{s.Enabled, s.Channel, s.Asset, s.Params, s.Interval, s.Levels, s.Authenticated, nil}
|
||||
if s.Pair != currency.EMPTYPAIR {
|
||||
k.Pair = &s.Pair
|
||||
}
|
||||
|
||||
return json.Marshal(k)
|
||||
}
|
||||
|
||||
// String implements the Stringer interface for Subscription, giving a human representation of the subscription
|
||||
func (s *Subscription) String() string {
|
||||
return fmt.Sprintf("%s %s %s", s.Channel, s.Asset, s.Pair)
|
||||
}
|
||||
|
||||
// EnsureKeyed sets the default key on a channel if it doesn't have one
|
||||
// Returns key for convenience
|
||||
func (s *Subscription) EnsureKeyed() any {
|
||||
if s.Key == nil {
|
||||
s.Key = DefaultKey{
|
||||
Channel: s.Channel,
|
||||
Asset: s.Asset,
|
||||
Pair: s.Pair,
|
||||
}
|
||||
}
|
||||
return s.Key
|
||||
}
|
||||
60
exchanges/subscription/subscription_test.go
Normal file
60
exchanges/subscription/subscription_test.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package subscription
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
|
||||
)
|
||||
|
||||
// TestEnsureKeyed logic test
|
||||
func TestEnsureKeyed(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := Subscription{
|
||||
Channel: "candles",
|
||||
Asset: asset.Spot,
|
||||
Pair: currency.NewPair(currency.BTC, currency.USDT),
|
||||
}
|
||||
k1, ok := c.EnsureKeyed().(DefaultKey)
|
||||
if assert.True(t, ok, "EnsureKeyed should return a DefaultKey") {
|
||||
assert.Exactly(t, k1, c.Key, "EnsureKeyed should set the same key")
|
||||
assert.Equal(t, k1.Channel, c.Channel, "DefaultKey channel should be correct")
|
||||
assert.Equal(t, k1.Asset, c.Asset, "DefaultKey asset should be correct")
|
||||
assert.Equal(t, k1.Pair, c.Pair, "DefaultKey currency should be correct")
|
||||
}
|
||||
type platypus string
|
||||
c = Subscription{
|
||||
Key: platypus("Gerald"),
|
||||
Channel: "orderbook",
|
||||
Asset: asset.Margin,
|
||||
Pair: currency.NewPair(currency.ETH, currency.USDC),
|
||||
}
|
||||
k2, ok := c.EnsureKeyed().(platypus)
|
||||
if assert.True(t, ok, "EnsureKeyed should return a platypus") {
|
||||
assert.Exactly(t, k2, c.Key, "EnsureKeyed should set the same key")
|
||||
assert.EqualValues(t, "Gerald", k2, "key should have the correct value")
|
||||
}
|
||||
}
|
||||
|
||||
// TestMarshalling logic test
|
||||
func TestMarshaling(t *testing.T) {
|
||||
t.Parallel()
|
||||
j, err := json.Marshal(&Subscription{Channel: CandlesChannel})
|
||||
assert.NoError(t, err, "Marshalling should not error")
|
||||
assert.Equal(t, `{"enabled":false,"channel":"candles"}`, string(j), "Marshalling should be clean and concise")
|
||||
|
||||
j, err = json.Marshal(&Subscription{Enabled: true, Channel: OrderbookChannel, Interval: kline.FiveMin, Levels: 4})
|
||||
assert.NoError(t, err, "Marshalling should not error")
|
||||
assert.Equal(t, `{"enabled":true,"channel":"orderbook","interval":"5m","levels":4}`, string(j), "Marshalling should be clean and concise")
|
||||
|
||||
j, err = json.Marshal(&Subscription{Enabled: true, Channel: OrderbookChannel, Interval: kline.FiveMin, Levels: 4, Pair: currency.NewPair(currency.BTC, currency.USDT)})
|
||||
assert.NoError(t, err, "Marshalling should not error")
|
||||
assert.Equal(t, `{"enabled":true,"channel":"orderbook","interval":"5m","levels":4,"pair":"BTCUSDT"}`, string(j), "Marshalling should be clean and concise")
|
||||
|
||||
j, err = json.Marshal(&Subscription{Enabled: true, Channel: MyTradesChannel, Authenticated: true})
|
||||
assert.NoError(t, err, "Marshalling should not error")
|
||||
assert.Equal(t, `{"enabled":true,"channel":"myTrades","authenticated":true}`, string(j), "Marshalling should be clean and concise")
|
||||
}
|
||||
@@ -19,6 +19,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"
|
||||
)
|
||||
@@ -273,10 +274,10 @@ func (z *ZB) wsHandleData(respRaw []byte) error {
|
||||
}
|
||||
|
||||
// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions()
|
||||
func (z *ZB) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) {
|
||||
var subscriptions []stream.ChannelSubscription
|
||||
func (z *ZB) GenerateDefaultSubscriptions() ([]subscription.Subscription, error) {
|
||||
var subscriptions []subscription.Subscription
|
||||
// market configuration is its own channel
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
subscriptions = append(subscriptions, subscription.Subscription{
|
||||
Channel: "markets",
|
||||
})
|
||||
channels := []string{"%s_ticker", "%s_depth", "%s_trades"}
|
||||
@@ -288,10 +289,10 @@ func (z *ZB) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error
|
||||
for i := range channels {
|
||||
for j := range enabledCurrencies {
|
||||
enabledCurrencies[j].Delimiter = ""
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
Channel: fmt.Sprintf(channels[i], enabledCurrencies[j].Lower().String()),
|
||||
Currency: enabledCurrencies[j].Lower(),
|
||||
Asset: asset.Spot,
|
||||
subscriptions = append(subscriptions, subscription.Subscription{
|
||||
Channel: fmt.Sprintf(channels[i], enabledCurrencies[j].Lower().String()),
|
||||
Pair: enabledCurrencies[j].Lower(),
|
||||
Asset: asset.Spot,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -299,7 +300,7 @@ func (z *ZB) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error
|
||||
}
|
||||
|
||||
// Subscribe sends a websocket message to receive data from the channel
|
||||
func (z *ZB) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error {
|
||||
func (z *ZB) Subscribe(channelsToSubscribe []subscription.Subscription) error {
|
||||
var errs error
|
||||
for i := range channelsToSubscribe {
|
||||
subscriptionRequest := Subscription{
|
||||
|
||||
Reference in New Issue
Block a user