Skip to content

Commit

Permalink
Bitmex: Sub Templating
Browse files Browse the repository at this point in the history
  • Loading branch information
gbjk committed Aug 13, 2024
1 parent 8fa478b commit 90ae13e
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 82 deletions.
35 changes: 35 additions & 0 deletions exchanges/bitmex/bitmex_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ 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"
testexch "github.com/thrasher-corp/gocryptotrader/internal/testing/exchange"
testsubs "github.com/thrasher-corp/gocryptotrader/internal/testing/subscriptions"
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
)

Expand Down Expand Up @@ -1357,3 +1359,36 @@ func TestGetCurrencyTradeURL(t *testing.T) {
assert.NotEmpty(t, resp)
}
}

func TestGenerateSubscriptions(t *testing.T) {
t.Parallel()

b := new(Bitmex)
require.NoError(t, testexch.Setup(b), "Test instance Setup must not error")

p := currency.Pairs{
currency.NewPair(currency.ETH, currency.USD),
currency.NewPair(currency.BCH, currency.NewCode("Z19")),
}

exp := subscription.List{
{QualifiedChannel: bitmexWSAnnouncement, Channel: bitmexWSAnnouncement},
{QualifiedChannel: bitmexWSOrderbookL2 + ":" + p[1].String(), Channel: bitmexWSOrderbookL2, Asset: asset.Futures, Pairs: p[1:2]},
{QualifiedChannel: bitmexWSOrderbookL2 + ":" + p[0].String(), Channel: bitmexWSOrderbookL2, Asset: asset.PerpetualContract, Pairs: p[:1]},
{QualifiedChannel: bitmexWSTrade + ":" + p[1].String(), Channel: bitmexWSTrade, Asset: asset.Futures, Pairs: p[1:2]},
{QualifiedChannel: bitmexWSTrade + ":" + p[0].String(), Channel: bitmexWSTrade, Asset: asset.PerpetualContract, Pairs: p[:1]},
{QualifiedChannel: bitmexWSAffiliate, Channel: bitmexWSAffiliate, Authenticated: true},
{QualifiedChannel: bitmexWSOrder, Channel: bitmexWSOrder, Authenticated: true},
{QualifiedChannel: bitmexWSMargin, Channel: bitmexWSMargin, Authenticated: true},
{QualifiedChannel: bitmexWSPrivateNotifications, Channel: bitmexWSPrivateNotifications, Authenticated: true},
{QualifiedChannel: bitmexWSTransact, Channel: bitmexWSTransact, Authenticated: true},
{QualifiedChannel: bitmexWSWallet, Channel: bitmexWSWallet, Authenticated: true},
{QualifiedChannel: bitmexWSExecution + ":" + p[0].String(), Channel: bitmexWSExecution, Authenticated: true, Asset: asset.PerpetualContract, Pairs: p[:1]},
{QualifiedChannel: bitmexWSPosition + ":" + p[0].String(), Channel: bitmexWSPosition, Authenticated: true, Asset: asset.PerpetualContract, Pairs: p[:1]},
}

b.Websocket.SetCanUseAuthenticatedEndpoints(true)
subs, err := b.generateSubscriptions()
require.NoError(t, err, "generateSubscriptions must not error")
testsubs.EqualLists(t, exp, subs)
}
124 changes: 59 additions & 65 deletions exchanges/bitmex/bitmex_websocket.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"net/http"
"strconv"
"strings"
"text/template"
"time"

"github.com/gorilla/websocket"
Expand Down Expand Up @@ -65,9 +66,18 @@ const (
bitmexActionUpdateData = "update"
)

var subscriptionNames = map[string]string{
subscription.OrderbookChannel: bitmexWSOrderbookL2,
subscription.AllTradesChannel: bitmexWSTrade,
var defaultSubscriptions = subscription.List{
{Enabled: true, Channel: bitmexWSAnnouncement},
{Enabled: true, Channel: bitmexWSOrderbookL2, Asset: asset.All},
{Enabled: true, Channel: bitmexWSTrade, Asset: asset.All},
{Enabled: true, Channel: bitmexWSAffiliate, Authenticated: true},
{Enabled: true, Channel: bitmexWSOrder, Authenticated: true},
{Enabled: true, Channel: bitmexWSMargin, Authenticated: true},
{Enabled: true, Channel: bitmexWSPrivateNotifications, Authenticated: true},
{Enabled: true, Channel: bitmexWSTransact, Authenticated: true},
{Enabled: true, Channel: bitmexWSWallet, Authenticated: true},
{Enabled: true, Channel: bitmexWSExecution, Authenticated: true, Asset: asset.PerpetualContract},
{Enabled: true, Channel: bitmexWSPosition, Authenticated: true, Asset: asset.PerpetualContract},
}

// WsConnect initiates a new websocket connection
Expand Down Expand Up @@ -542,50 +552,16 @@ func (b *Bitmex) processOrderbook(data []OrderBookL2, action string, p currency.
return nil
}

// generateSubscriptions returns Adds default subscriptions to websocket to be handled by ManageSubscriptions()
// generateSubscriptions returns a list of subscriptions from the configured subscriptions feature
func (b *Bitmex) generateSubscriptions() (subscription.List, error) {
authed := b.Websocket.CanUseAuthenticatedEndpoints()

assetPairs := map[asset.Item]currency.Pairs{}
for _, a := range b.GetAssetTypes(true) {
p, err := b.GetEnabledPairs(a)
if err != nil {
return nil, err
}
f, err := b.GetPairFormat(a, true)
if err != nil {
return nil, err
}
assetPairs[a] = p.Format(f)
}

subs := subscription.List{}
for _, baseSub := range b.Features.Subscriptions {
if !authed && baseSub.Authenticated {
continue
}

if baseSub.Asset == asset.Empty {
// Skip pair handling for subs which don't have an asset
subs = append(subs, baseSub.Clone())
continue
}

for a, p := range assetPairs {
if baseSub.Channel == bitmexWSOrderbookL2 && a == asset.Index {
continue // There are no L2 orderbook for index assets
}
if baseSub.Asset != asset.All && baseSub.Asset != a {
continue
}
s := baseSub.Clone()
s.Asset = a
s.Pairs = p
subs = append(subs, s)
}
}
return b.Features.Subscriptions.ExpandTemplates(b)
}

return subs, nil
// GetSubscriptionTemplate returns a subscription channel template
func (b *Bitmex) GetSubscriptionTemplate(_ *subscription.Subscription) (*template.Template, error) {
return template.New("master.tmpl").Funcs(template.FuncMap{
"channelName": channelName,
}).Parse(subTplText)
}

// Subscribe subscribes to a websocket channel
Expand All @@ -594,9 +570,8 @@ func (b *Bitmex) Subscribe(subs subscription.List) error {
Command: "subscribe",
}
for _, s := range subs {
for _, p := range s.Pairs {
cName := channelName(s.Channel)
req.Arguments = append(req.Arguments, cName+":"+p.String())
for _, a := range strings.Split(s.QualifiedChannel, ",") {
req.Arguments = append(req.Arguments, a)
}
}
err := b.Websocket.Conn.SendJSONMessage(req)
Expand All @@ -611,11 +586,9 @@ func (b *Bitmex) Unsubscribe(subs subscription.List) error {
req := WebsocketRequest{
Command: "unsubscribe",
}

for _, s := range subs {
for _, p := range s.Pairs {
cName := channelName(s.Channel)
req.Arguments = append(req.Arguments, cName+":"+p.String())
for _, a := range strings.Split(s.QualifiedChannel, ",") {
req.Arguments = append(req.Arguments, a)
}
}
err := b.Websocket.Conn.SendJSONMessage(req)
Expand All @@ -625,15 +598,6 @@ func (b *Bitmex) Unsubscribe(subs subscription.List) error {
return err
}

// channelName converts global channel Names used in config of channel input into bitmex 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
}

// WebsocketSendAuth sends an authenticated subscription
func (b *Bitmex) websocketSendAuth(ctx context.Context) error {
creds, err := b.GetCredentials(ctx)
Expand All @@ -651,10 +615,10 @@ func (b *Bitmex) websocketSendAuth(ctx context.Context) error {
}
signature := crypto.HexEncodeToString(hmac)

var sendAuth WebsocketRequest
sendAuth.Command = "authKeyExpires"
sendAuth.Arguments = append(sendAuth.Arguments, creds.Key, timestamp,
signature)
sendAuth := WebsocketRequest{
Command: "authKeyExpires",
Arguments: []any{creds.Key, timestamp, signature},
}
err = b.Websocket.Conn.SendJSONMessage(sendAuth)
if err != nil {
b.Websocket.SetCanUseAuthenticatedEndpoints(false)
Expand All @@ -677,3 +641,33 @@ func (b *Bitmex) GetActionFromString(s string) (orderbook.Action, error) {
}
return 0, fmt.Errorf("%s %w", s, orderbook.ErrInvalidAction)
}

// channelName returns the correct channel name for the asset
func channelName(s *subscription.Subscription, a asset.Item) string {
switch s.Channel {
case subscription.OrderbookChannel:
if a == asset.Index {
return "" // There are no L2 orderbook for index assets
}
return bitmexWSOrderbookL2
case subscription.AllTradesChannel:
return bitmexWSTrade
}
return s.Channel
}

const subTplText = `
{{- if $.S.Asset }}
{{ range $asset, $pairs := $.AssetPairs }}
{{- with $name := channelName $.S $asset }}
{{- range $i, $p := $pairs -}}
{{- if $i -}} , {{- end -}}
{{- $name -}} : {{- $p -}}
{{- end }}
{{- end }}
{{ $.AssetSeparator }}
{{- end }}
{{- else -}}
{{ channelName $.S $.S.Asset }}
{{- end }}
`
8 changes: 5 additions & 3 deletions exchanges/bitmex/bitmex_websocket_types.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package bitmex

import "time"
import (
"time"
)

// WebsocketRequest is the main request type
type WebsocketRequest struct {
Command string `json:"op"`
Arguments []interface{} `json:"args"`
Command string `json:"op"`
Arguments []any `json:"args"`
}

// WebsocketErrorResponse main error response
Expand Down
15 changes: 1 addition & 14 deletions exchanges/bitmex/bitmex_wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ 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"
Expand Down Expand Up @@ -137,19 +136,7 @@ func (b *Bitmex) SetDefaults() {
Enabled: exchange.FeaturesEnabled{
AutoPairUpdates: true,
},
Subscriptions: subscription.List{
{Enabled: true, Channel: bitmexWSAnnouncement},
{Enabled: true, Channel: bitmexWSOrderbookL2, Asset: asset.All},
{Enabled: true, Channel: bitmexWSTrade, Asset: asset.All},
{Enabled: true, Channel: bitmexWSAffiliate, Authenticated: true},
{Enabled: true, Channel: bitmexWSOrder, Authenticated: true},
{Enabled: true, Channel: bitmexWSMargin, Authenticated: true},
{Enabled: true, Channel: bitmexWSPrivateNotifications, Authenticated: true},
{Enabled: true, Channel: bitmexWSTransact, Authenticated: true},
{Enabled: true, Channel: bitmexWSWallet, Authenticated: true},
{Enabled: true, Channel: bitmexWSExecution, Authenticated: true, Asset: asset.PerpetualContract},
{Enabled: true, Channel: bitmexWSPosition, Authenticated: true, Asset: asset.PerpetualContract},
},
Subscriptions: defaultSubscriptions.Clone(),
}

b.Requester, err = request.New(b.Name,
Expand Down

0 comments on commit 90ae13e

Please sign in to comment.