Files
gocryptotrader/exchanges/ticker/ticker.go
Adrian Gallagher 68588560e3 CI: Bump go version, linters and fix minor issues (#1010)
* Bump golang, golangci-lint versions and fix issues

* Add -fno-stack-protector

* Fix AppVeyor golangci-lint ver

* Nitters

* Nitters round 2
2022-08-17 11:37:22 +10:00

224 lines
5.0 KiB
Go

package ticker
import (
"errors"
"fmt"
"strings"
"time"
"github.com/gofrs/uuid"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/dispatch"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
)
var (
errInvalidTicker = errors.New("invalid ticker")
errTickerNotFound = errors.New("ticker not found")
errExchangeNameIsEmpty = errors.New("exchange name is empty")
)
func init() {
service = new(Service)
service.Tickers = make(map[string]map[*currency.Item]map[*currency.Item]map[asset.Item]*Ticker)
service.Exchange = make(map[string]uuid.UUID)
service.mux = dispatch.GetNewMux(nil)
}
// SubscribeTicker subscribes to a ticker and returns a communication channel to
// stream new ticker updates
func SubscribeTicker(exchange string, p currency.Pair, a asset.Item) (dispatch.Pipe, error) {
exchange = strings.ToLower(exchange)
service.mu.Lock()
defer service.mu.Unlock()
tick, ok := service.Tickers[exchange][p.Base.Item][p.Quote.Item][a]
if !ok {
return dispatch.Pipe{}, fmt.Errorf("ticker item not found for %s %s %s",
exchange,
p,
a)
}
return service.mux.Subscribe(tick.Main)
}
// SubscribeToExchangeTickers subscribes to all tickers on an exchange
func SubscribeToExchangeTickers(exchange string) (dispatch.Pipe, error) {
exchange = strings.ToLower(exchange)
service.mu.Lock()
defer service.mu.Unlock()
id, ok := service.Exchange[exchange]
if !ok {
return dispatch.Pipe{}, fmt.Errorf("%s exchange tickers not found",
exchange)
}
return service.mux.Subscribe(id)
}
// GetTicker checks and returns a requested ticker if it exists
func GetTicker(exchange string, p currency.Pair, a asset.Item) (*Price, error) {
exchange = strings.ToLower(exchange)
service.mu.Lock()
defer service.mu.Unlock()
m1, ok := service.Tickers[exchange]
if !ok {
return nil, fmt.Errorf("no tickers for %s exchange", exchange)
}
m2, ok := m1[p.Base.Item]
if !ok {
return nil, fmt.Errorf("no tickers associated with base currency %s",
p.Base)
}
m3, ok := m2[p.Quote.Item]
if !ok {
return nil, fmt.Errorf("no tickers associated with quote currency %s",
p.Quote)
}
t, ok := m3[a]
if !ok {
return nil, fmt.Errorf("no tickers associated with asset type %s",
a)
}
cpy := t.Price // Don't let external functions have access to underlying
return &cpy, nil
}
// FindLast searches for a currency pair and returns the first available
func FindLast(p currency.Pair, a asset.Item) (float64, error) {
service.mu.Lock()
defer service.mu.Unlock()
for _, m1 := range service.Tickers {
m2, ok := m1[p.Base.Item]
if !ok {
continue
}
m3, ok := m2[p.Quote.Item]
if !ok {
continue
}
t, ok := m3[a]
if !ok {
continue
}
if t.Last == 0 {
return 0, errInvalidTicker
}
return t.Last, nil
}
return 0, fmt.Errorf("%w %s %s", errTickerNotFound, p, a)
}
// ProcessTicker processes incoming tickers, creating or updating the Tickers
// list
func ProcessTicker(p *Price) error {
if p == nil {
return errors.New(errTickerPriceIsNil)
}
if p.ExchangeName == "" {
return fmt.Errorf(ErrExchangeNameUnset)
}
if p.Pair.IsEmpty() {
return fmt.Errorf("%s %s", p.ExchangeName, errPairNotSet)
}
if p.AssetType == asset.Empty {
return fmt.Errorf("%s %s %s",
p.ExchangeName,
p.Pair,
errAssetTypeNotSet)
}
if p.LastUpdated.IsZero() {
p.LastUpdated = time.Now()
}
return service.update(p)
}
// update updates ticker price
func (s *Service) update(p *Price) error {
name := strings.ToLower(p.ExchangeName)
s.mu.Lock()
m1, ok := service.Tickers[name]
if !ok {
m1 = make(map[*currency.Item]map[*currency.Item]map[asset.Item]*Ticker)
service.Tickers[name] = m1
}
m2, ok := m1[p.Pair.Base.Item]
if !ok {
m2 = make(map[*currency.Item]map[asset.Item]*Ticker)
m1[p.Pair.Base.Item] = m2
}
m3, ok := m2[p.Pair.Quote.Item]
if !ok {
m3 = make(map[asset.Item]*Ticker)
m2[p.Pair.Quote.Item] = m3
}
t, ok := m3[p.AssetType]
if !ok || t == nil {
newTicker := &Ticker{}
err := s.setItemID(newTicker, p, name)
if err != nil {
s.mu.Unlock()
return err
}
m3[p.AssetType] = newTicker
s.mu.Unlock()
return nil
}
t.Price = *p
//nolint: gocritic
ids := append(t.Assoc, t.Main)
s.mu.Unlock()
return s.mux.Publish(p, ids...)
}
// setItemID retrieves and sets dispatch mux publish IDs
func (s *Service) setItemID(t *Ticker, p *Price, exch string) error {
ids, err := s.getAssociations(exch)
if err != nil {
return err
}
singleID, err := s.mux.GetID()
if err != nil {
return err
}
t.Price = *p
t.Main = singleID
t.Assoc = ids
return nil
}
// getAssociations links a singular book with it's dispatch associations
func (s *Service) getAssociations(exch string) ([]uuid.UUID, error) {
if exch == "" {
return nil, errExchangeNameIsEmpty
}
var ids []uuid.UUID
exchangeID, ok := s.Exchange[exch]
if !ok {
var err error
exchangeID, err = s.mux.GetID()
if err != nil {
return nil, err
}
s.Exchange[exch] = exchangeID
}
ids = append(ids, exchangeID)
return ids, nil
}