Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

exchanges/websocket: Implement subscription configuration #1394

Merged
merged 8 commits into from
Jan 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 10 additions & 22 deletions backtester/engine/backtest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (

"github.com/gofrs/uuid"
"github.com/shopspring/decimal"
"github.com/stretchr/testify/assert"
"github.com/thrasher-corp/gocryptotrader/backtester/common"
"github.com/thrasher-corp/gocryptotrader/backtester/config"
"github.com/thrasher-corp/gocryptotrader/backtester/data"
Expand Down Expand Up @@ -1882,36 +1883,23 @@ func TestExecuteStrategy(t *testing.T) {
func TestNewBacktesterFromConfigs(t *testing.T) {
t.Parallel()
_, err := NewBacktesterFromConfigs(nil, nil)
if !errors.Is(err, gctcommon.ErrNilPointer) {
t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer)
}
assert.ErrorIs(t, err, gctcommon.ErrNilPointer, "NewBacktesterFromConfigs should error on nil for both configs")

cfg, err := config.ReadStrategyConfigFromFile(filepath.Join("..", "config", "strategyexamples", "dca-api-candles.strat"))
assert.NoError(t, err, "ReadStrategyConfigFromFile should not error")

strat1 := filepath.Join("..", "config", "strategyexamples", "dca-api-candles.strat")
cfg, err := config.ReadStrategyConfigFromFile(strat1)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
dc, err := config.GenerateDefaultConfig()
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
assert.NoError(t, err, "GenerateDefaultConfig should not error")

_, err = NewBacktesterFromConfigs(cfg, nil)
if !errors.Is(err, gctcommon.ErrNilPointer) {
t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer)
}
assert.ErrorIs(t, err, gctcommon.ErrNilPointer, "NewBacktesterFromConfigs should error on nil default config")

_, err = NewBacktesterFromConfigs(nil, dc)
if !errors.Is(err, gctcommon.ErrNilPointer) {
t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer)
}
assert.ErrorIs(t, err, gctcommon.ErrNilPointer, "NewBacktesterFromConfigs should error on nil config")

bt, err := NewBacktesterFromConfigs(cfg, dc)
if !errors.Is(err, nil) {
t.Fatalf("received '%v' expected '%v'", err, nil)
}
if bt.MetaData.DateLoaded.IsZero() {
t.Errorf("received '%v' expected '%v'", bt.MetaData.DateLoaded, "a date")
if assert.NoError(t, err, "NewBacktesterFromConfigs should not error") {
assert.False(t, bt.MetaData.DateLoaded.IsZero(), "DateLoaded should have a non-zero date")
}
}

Expand Down
46 changes: 39 additions & 7 deletions cmd/documentation/config_templates/config_readme.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,21 @@

+ Contains configurations for:

- Exchanges for utilisation of a broad or minimal amount of enabled
exchanges [Example](#enable-exchange-via-config-example) for
enabling an exchange.
- Enable/Disable Exchanges. [See Example](#enable-exchange-via-config-example)

- Bank accounts for withdrawal and depositing FIAT between exchange and
your personal accounts [Example](#enable-bank-accounts-via-config-example).
your personal accounts. [See Example](#enable-bank-accounts-via-config-example)

- Portfolio to monitor online and offline accounts [Example](#enable-portfolio-via-config-example).
- Portfolio to monitor online and offline accounts. [See Example](#enable-portfolio-via-config-example)

- Currency configurations to set your foreign exchange provider accounts,
your preferred display currency, suitable FIAT currency and suitable
cryptocurrency [Example](#enable-currency-via-config-example).
cryptocurrency. [See Example](#enable-currency-via-config-example)

- Communication for utilisation of supported communication mediums e.g.
email events direct to your personal account [Example](#enable-communications-via-config-example).
email events direct to your personal account. [See Example](#enable-communications-via-config-example)

- Websocket subscription channels. [See Example](#configure-exchange-websocket-subscriptions)

# Config Examples

Expand Down Expand Up @@ -194,6 +194,38 @@ comm method and add in your contact list if available.
},
```

## Configure exchange websocket subscriptions

+ Websocket subscriptions provide a stream of data from an exchange.
Whilst subscriptions are specific to each exchange, some common examples are: candles, orderbook, ticker and allTrades.
You can configure any supported channels in your exchange, but most likely you just want to disable some of the defaults, or change the default intervals.
You cannot configure an empty list of subscriptions, instead set all of the subscriptions to enabled: false.

See the section `exchange.features.enabled.subscriptions` for configuring subscriptions:

```js
"subscriptions": [
{
"enabled": true,
"channel": "ticker"
gbjk marked this conversation as resolved.
Show resolved Hide resolved
},
{
"enabled": false,
"channel": "allTrades"
},
{
"enabled": true,
"channel": "candles",
"interval": "1m"
},
{
"enabled": true,
"channel": "orderbook",
"interval": "100ms"
}
]
```


## Configure Network Time Server

Expand Down
46 changes: 39 additions & 7 deletions config/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,21 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader

+ Contains configurations for:

- Exchanges for utilisation of a broad or minimal amount of enabled
exchanges [Example](#enable-exchange-via-config-example) for
enabling an exchange.
- Enable/Disable Exchanges. [See Example](#enable-exchange-via-config-example)
gbjk marked this conversation as resolved.
Show resolved Hide resolved

- Bank accounts for withdrawal and depositing FIAT between exchange and
your personal accounts [Example](#enable-bank-accounts-via-config-example).
your personal accounts. [See Example](#enable-bank-accounts-via-config-example)

- Portfolio to monitor online and offline accounts [Example](#enable-portfolio-via-config-example).
- Portfolio to monitor online and offline accounts. [See Example](#enable-portfolio-via-config-example)

- Currency configurations to set your foreign exchange provider accounts,
your preferred display currency, suitable FIAT currency and suitable
cryptocurrency [Example](#enable-currency-via-config-example).
cryptocurrency. [See Example](#enable-currency-via-config-example)

- Communication for utilisation of supported communication mediums e.g.
email events direct to your personal account [Example](#enable-communications-via-config-example).
email events direct to your personal account. [See Example](#enable-communications-via-config-example)

- Websocket subscription channels. [See Example](#configure-exchange-websocket-subscriptions)

# Config Examples

Expand Down Expand Up @@ -212,6 +212,38 @@ comm method and add in your contact list if available.
},
```

## Configure exchange websocket subscriptions

+ Websocket subscriptions provide a stream of data from an exchange.
Whilst subscriptions are specific to each exchange, some common examples are: candles, orderbook, ticker and allTrades.
You can configure any supported channels in your exchange, but most likely you just want to disable some of the defaults, or change the default intervals.
You cannot configure an empty list of subscriptions, instead set all of the subscriptions to enabled: false.

See the section `exchange.features.enabled.subscriptions` for configuring subscriptions:

```js
"subscriptions": [
{
"enabled": true,
"channel": "ticker"
},
{
"enabled": false,
"channel": "allTrades"
},
{
"enabled": true,
"channel": "candles",
"interval": "1m"
},
{
"enabled": true,
"channel": "orderbook",
"interval": "100ms"
}
]
```


## Configure Network Time Server

Expand Down
6 changes: 4 additions & 2 deletions config/config_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/database"
"github.com/thrasher-corp/gocryptotrader/exchanges/protocol"
"github.com/thrasher-corp/gocryptotrader/exchanges/subscription"
gctscript "github.com/thrasher-corp/gocryptotrader/gctscript/vm"
"github.com/thrasher-corp/gocryptotrader/log"
"github.com/thrasher-corp/gocryptotrader/portfolio"
Expand Down Expand Up @@ -313,8 +314,9 @@ type FeaturesEnabledConfig struct {

// FeaturesConfig stores the exchanges supported and enabled features
type FeaturesConfig struct {
Supports FeaturesSupportedConfig `json:"supports"`
Enabled FeaturesEnabledConfig `json:"enabled"`
Supports FeaturesSupportedConfig `json:"supports"`
Enabled FeaturesEnabledConfig `json:"enabled"`
Subscriptions []*subscription.Subscription `json:"subscriptions,omitempty"`
}

// APIEndpointsConfig stores the API endpoint addresses
Expand Down
8 changes: 8 additions & 0 deletions currency/currencies.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ func NewCurrenciesFromStringArray(currencies []string) Currencies {
// Currencies define a range of supported currency codes
type Currencies []Code

// Add adds a currency to the list if it doesn't exist
func (c Currencies) Add(a Code) Currencies {
if !c.Contains(a) {
c = append(c, a)
}
return c
}

// Strings returns an array of currency strings
func (c Currencies) Strings() []string {
list := make([]string, len(c))
Expand Down
12 changes: 12 additions & 0 deletions currency/currencies_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package currency
import (
"encoding/json"
"testing"

"github.com/stretchr/testify/assert"
)

func TestCurrenciesUnmarshalJSON(t *testing.T) {
Expand Down Expand Up @@ -62,3 +64,13 @@ func TestMatch(t *testing.T) {
t.Fatal("should not match")
}
}

func TestCurrenciesAdd(t *testing.T) {
c := Currencies{}
c = c.Add(BTC)
assert.Len(t, c, 1, "Should have one currency")
c = c.Add(ETH)
assert.Len(t, c, 2, "Should have two currencies")
c = c.Add(BTC)
assert.Len(t, c, 2, "Adding a duplicate should not change anything")
}
24 changes: 12 additions & 12 deletions docs/ADD_NEW_EXCHANGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -736,9 +736,9 @@ func (f *FTX) WsConnect() error {

```go
// GenerateDefaultSubscriptions generates default subscription
func (f *FTX) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) {
var subscriptions []stream.ChannelSubscription
subscriptions = append(subscriptions, stream.ChannelSubscription{
func (f *FTX) GenerateDefaultSubscriptions() ([]subscription.Subscription, error) {
var subscriptions []subscription.Subscription
subscriptions = append(subscriptions, subscription.Subscription{
Channel: wsMarkets,
})
// Ranges over available channels, pairs and asset types to produce a full
Expand All @@ -756,9 +756,9 @@ func (f *FTX) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, erro
"-")
for x := range channels {
subscriptions = append(subscriptions,
stream.ChannelSubscription{
subscription.Subscription{
Channel: channels[x],
Currency: newPair,
Pair: newPair,
Asset: assets[a],
})
}
Expand All @@ -768,7 +768,7 @@ func (f *FTX) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, erro
if f.IsWebsocketAuthenticationSupported() {
var authchan = []string{wsOrders, wsFills}
for x := range authchan {
subscriptions = append(subscriptions, stream.ChannelSubscription{
subscriptions = append(subscriptions, subscription.Subscription{
Channel: authchan[x],
})
}
Expand Down Expand Up @@ -811,7 +811,7 @@ type WsSub struct {

```go
// Subscribe sends a websocket message to receive data from the channel
func (f *FTX) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error {
func (f *FTX) Subscribe(channelsToSubscribe []subscription.Subscription) error {
// For subscriptions we try to batch as much as possible to limit the amount
// of connection usage but sometimes this is not supported on the exchange
// API.
Expand All @@ -827,13 +827,13 @@ channels:
case wsFills, wsOrders, wsMarkets:
// Authenticated wsFills && wsOrders or wsMarkets which is a channel subscription for the full set of tradable markets do not need a currency pair association.
default:
a, err := f.GetPairAssetType(channelsToSubscribe[i].Currency)
a, err := f.GetPairAssetType(channelsToSubscribe[i].Pair)
if err != nil {
errs = append(errs, err)
continue channels
}
// Ensures our outbound currency pair is formatted correctly, sometimes our configuration format is different from what our request format needs to be.
formattedPair, err := f.FormatExchangeCurrency(channelsToSubscribe[i].Currency, a)
formattedPair, err := f.FormatExchangeCurrency(channelsToSubscribe[i].Pair, a)
if err != nil {
errs = append(errs, err)
continue channels
Expand Down Expand Up @@ -1065,7 +1065,7 @@ func (f *FTX) WsAuth(ctx context.Context) error {

```go
// Unsubscribe sends a websocket message to stop receiving data from the channel
func (f *FTX) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error {
func (f *FTX) Unsubscribe(channelsToUnsubscribe []subscription.Subscription) error {
// As with subscribing we want to batch as much as possible, but sometimes this cannot be achieved due to API shortfalls.
var errs common.Errors
channels:
Expand All @@ -1076,13 +1076,13 @@ channels:
switch channelsToUnsubscribe[i].Channel {
case wsFills, wsOrders, wsMarkets:
default:
a, err := f.GetPairAssetType(channelsToUnsubscribe[i].Currency)
a, err := f.GetPairAssetType(channelsToUnsubscribe[i].Pair)
if err != nil {
errs = append(errs, err)
continue channels
}

formattedPair, err := f.FormatExchangeCurrency(channelsToUnsubscribe[i].Currency, a)
formattedPair, err := f.FormatExchangeCurrency(channelsToUnsubscribe[i].Pair, a)
if err != nil {
errs = append(errs, err)
continue channels
Expand Down
8 changes: 4 additions & 4 deletions engine/rpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -3103,10 +3103,10 @@ func (s *RPCServer) WebsocketGetSubscriptions(_ context.Context, r *gctrpc.Webso
}
payload.Subscriptions = append(payload.Subscriptions,
&gctrpc.WebsocketSubscription{
Channel: subs[i].Channel,
Currency: subs[i].Currency.String(),
Asset: subs[i].Asset.String(),
Params: string(params),
Channel: subs[i].Channel,
Pair: subs[i].Pair.String(),
Asset: subs[i].Asset.String(),
Params: string(params),
})
}
return payload, nil
Expand Down
8 changes: 8 additions & 0 deletions exchanges/binance/binance.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
Loading
Loading