mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 23:16:45 +00:00
* gateio: Add websocket orderbook update manager * RM println * glorious: nit * Adds delivery futures update processing as well * Change to const value for delivery * Drop check out of order, can reinstate if required. * Adds in validation methods to ensure config changes are correct when expanding templates and return errors with correct info if not. * fix some things and add in todo when this gets updated * fix spelling * linter: fix * gk: initial nits * gk: nits shift to template only verification with funcmap, rm interface for single sub checking. * rm unused error * linter: fix * update to const frequency * gk: wrap with panic and single invocation in template, change name * gk: nits to check across stored subs with incoming subs * linter: fix * updates names, makes things slightly more efficient and adds tests * linter: fix * gk: sexc patch v2 * glorious: nits * gk: nits * Update exchanges/subscription/template.go Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com> * gk: nits * linter: make peace with linter regulations * glorious: Add TODO for future template integration * glorious: commentary nits * fix name * give me a break, have a kit kat * revert whoops * update wording on comment * revert secondary call to expand templates and update tests * misc lint: fix * Add spot orderbook update interval for 20ms, expand tests, piggy back limit/level off loaded subscription. Thanks to @thrasher- * linter/spell: fix * ai nits: drop go routine on mtx RUnlock * Update exchanges/gateio/ws_ob_update_manager.go Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com> * gk: revert to 100ms from 20ms waiting for config upgrade patch * test: fix * cranktakular: nits * strings quoted in fmt call * thrasher-: nits * Update exchanges/gateio/gateio_test.go Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com> * Update exchanges/gateio/gateio_test.go Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com> * gk: nits --------- Co-authored-by: Ryan O'Hara-Reid <ryan.oharareid@thrasher.io> Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com>
177 lines
5.2 KiB
Go
177 lines
5.2 KiB
Go
package subscription
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"maps"
|
|
"slices"
|
|
"sync"
|
|
|
|
"github.com/thrasher-corp/gocryptotrader/currency"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
|
|
)
|
|
|
|
// State constants
|
|
const (
|
|
InactiveState State = iota
|
|
SubscribingState
|
|
SubscribedState
|
|
ResubscribingState
|
|
UnsubscribingState
|
|
UnsubscribedState
|
|
)
|
|
|
|
// Channel constants
|
|
const (
|
|
TickerChannel = "ticker"
|
|
OrderbookChannel = "orderbook"
|
|
CandlesChannel = "candles"
|
|
AllOrdersChannel = "allOrders"
|
|
AllTradesChannel = "allTrades"
|
|
MyTradesChannel = "myTrades"
|
|
MyOrdersChannel = "myOrders"
|
|
MyWalletChannel = "myWallet"
|
|
MyAccountChannel = "myAccount"
|
|
HeartbeatChannel = "heartbeat"
|
|
)
|
|
|
|
// Public errors
|
|
var (
|
|
ErrNotFound = errors.New("subscription not found")
|
|
ErrNotSinglePair = errors.New("only single pair subscriptions expected")
|
|
ErrBatchingNotSupported = errors.New("subscription batching not supported")
|
|
ErrInStateAlready = errors.New("subscription already in state")
|
|
ErrInvalidState = errors.New("invalid subscription state")
|
|
ErrDuplicate = errors.New("duplicate subscription")
|
|
ErrUseConstChannelName = errors.New("must use standard channel name constants")
|
|
ErrNotSupported = errors.New("subscription channel not supported")
|
|
ErrExclusiveSubscription = errors.New("exclusive subscription detected")
|
|
ErrInvalidInterval = errors.New("invalid interval")
|
|
ErrInvalidLevel = errors.New("invalid level")
|
|
)
|
|
|
|
// State tracks the status of a subscription channel
|
|
type State uint8
|
|
|
|
// ListValidator validates a list of subscriptions, this is optionally handled through expand templates method
|
|
type ListValidator interface {
|
|
ValidateSubscriptions(List) error
|
|
}
|
|
|
|
// Subscription container for streaming subscriptions
|
|
type Subscription struct {
|
|
Enabled bool `json:"enabled"`
|
|
Key any `json:"-"`
|
|
Channel string `json:"channel,omitempty"`
|
|
Pairs currency.Pairs `json:"pairs,omitempty"`
|
|
Asset asset.Item `json:"asset,omitempty"`
|
|
Params map[string]any `json:"params,omitempty"`
|
|
Interval kline.Interval `json:"interval,omitempty"`
|
|
Levels int `json:"levels,omitempty"`
|
|
Authenticated bool `json:"authenticated,omitempty"`
|
|
QualifiedChannel string `json:"-"`
|
|
state State
|
|
m sync.RWMutex
|
|
}
|
|
|
|
// String implements Stringer, and aims to informatively and uniquely identify a subscription for errors and information
|
|
// returns a string of the subscription key by delegating to MatchableKey.String() when possible
|
|
// If the key is not a MatchableKey then both the key and an ExactKey.String() will be returned; e.g. 1137: spot MyTrades
|
|
func (s *Subscription) String() string {
|
|
key := s.EnsureKeyed()
|
|
s.m.RLock()
|
|
defer s.m.RUnlock()
|
|
if k, ok := key.(MatchableKey); ok {
|
|
return k.String()
|
|
}
|
|
return fmt.Sprintf("%v: %s", key, ExactKey{s}.String())
|
|
}
|
|
|
|
// State returns the subscription state
|
|
func (s *Subscription) State() State {
|
|
s.m.RLock()
|
|
defer s.m.RUnlock()
|
|
return s.state
|
|
}
|
|
|
|
// SetState sets the subscription state
|
|
// Errors if already in that state or the new state is not valid
|
|
func (s *Subscription) SetState(state State) error {
|
|
s.m.Lock()
|
|
defer s.m.Unlock()
|
|
if state == s.state {
|
|
return ErrInStateAlready
|
|
}
|
|
if state > UnsubscribedState {
|
|
return ErrInvalidState
|
|
}
|
|
s.state = state
|
|
return nil
|
|
}
|
|
|
|
// SetKey does what it says on the tin safely for concurrency
|
|
func (s *Subscription) SetKey(key any) {
|
|
s.m.Lock()
|
|
defer s.m.Unlock()
|
|
s.Key = key
|
|
}
|
|
|
|
// EnsureKeyed returns the subscription key
|
|
// If no key exists then ExactKey will be used
|
|
func (s *Subscription) EnsureKeyed() any {
|
|
// Juggle RLock/WLock to minimize concurrent bottleneck for hottest path
|
|
s.m.RLock()
|
|
if s.Key != nil {
|
|
defer s.m.RUnlock()
|
|
return s.Key
|
|
}
|
|
s.m.RUnlock()
|
|
s.m.Lock()
|
|
defer s.m.Unlock()
|
|
if s.Key == nil { // Ensure race hasn't updated Key whilst we swapped locks
|
|
s.Key = &ExactKey{s}
|
|
}
|
|
return s.Key
|
|
}
|
|
|
|
// Clone returns a copy of a subscription
|
|
// Key is set to nil, because most Key types contain a pointer to the subscription, and because the clone isn't added to the store yet
|
|
// QualifiedChannel is not copied because it's expected that the contributing fields will be changed
|
|
// Users should allow a default key to be assigned on AddSubscription or can SetKey as necessary
|
|
func (s *Subscription) Clone() *Subscription {
|
|
s.m.RLock()
|
|
c := &Subscription{
|
|
Key: nil,
|
|
Enabled: s.Enabled,
|
|
Channel: s.Channel,
|
|
Asset: s.Asset,
|
|
Params: maps.Clone(s.Params),
|
|
Interval: s.Interval,
|
|
Levels: s.Levels,
|
|
Authenticated: s.Authenticated,
|
|
state: s.state,
|
|
Pairs: slices.Clone(s.Pairs),
|
|
QualifiedChannel: s.QualifiedChannel,
|
|
}
|
|
s.m.RUnlock()
|
|
return c
|
|
}
|
|
|
|
// SetPairs does what it says on the tin safely for concurrency
|
|
func (s *Subscription) SetPairs(pairs currency.Pairs) {
|
|
s.m.Lock()
|
|
s.Pairs = pairs
|
|
s.m.Unlock()
|
|
}
|
|
|
|
// AddPairs does what it says on the tin safely for concurrency
|
|
func (s *Subscription) AddPairs(pairs ...currency.Pair) {
|
|
if len(pairs) == 0 {
|
|
return
|
|
}
|
|
s.m.Lock()
|
|
s.Pairs = s.Pairs.Add(pairs...)
|
|
s.m.Unlock()
|
|
}
|