Files
gocryptotrader/exchanges/ticker/ticker.go
Scott 62a528a064 Bugfix/improvement: Orderbook/ticker update processing (#364)
* Greatly increases efficiency of web socket orderbook processing by minimising multiple map lookups. Changes buffer to use pointers. Ensures orderbook benchmarks are on equal footing and set correctly. Removes data race by not setting waitgroup adds inside go routine causing wait and add to happen simultaneously. Updates ticker and orderbook service to use a pointer var instead of map lookups when setting data.

* Removes misguided comment

* Removes waitgroups and goroutines for updating bids and asks for non-id orderbook updates via websocket. Updates benchmarks to be consistent
2019-10-04 16:24:29 +10:00

209 lines
5.5 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"
)
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()
}
// SubscribeTicker subcribes 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.RLock()
defer service.RUnlock()
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 subcribes to all tickers on an exchange
func SubscribeToExchangeTickers(exchange string) (dispatch.Pipe, error) {
exchange = strings.ToLower(exchange)
service.RLock()
defer service.RUnlock()
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, tickerType asset.Item) (Price, error) {
exchange = strings.ToLower(exchange)
service.RLock()
defer service.RUnlock()
if service.Tickers[exchange] == nil {
return Price{}, fmt.Errorf("no tickers for %s exchange", exchange)
}
if service.Tickers[exchange][p.Base.Item] == nil {
return Price{}, fmt.Errorf("no tickers associated with base currency %s",
p.Base)
}
if service.Tickers[exchange][p.Base.Item][p.Quote.Item] == nil {
return Price{}, fmt.Errorf("no tickers associated with quote currency %s",
p.Quote)
}
if service.Tickers[exchange][p.Base.Item][p.Quote.Item][tickerType] == nil {
return Price{}, fmt.Errorf("no tickers associated with asset type %s",
tickerType)
}
return service.Tickers[exchange][p.Base.Item][p.Quote.Item][tickerType].Price, nil
}
// ProcessTicker processes incoming tickers, creating or updating the Tickers
// list
func ProcessTicker(exchangeName string, tickerNew *Price, assetType asset.Item) error {
if exchangeName == "" {
return fmt.Errorf(errExchangeNameUnset)
}
tickerNew.ExchangeName = strings.ToLower(exchangeName)
if tickerNew.Pair.IsEmpty() {
return fmt.Errorf("%s %s", exchangeName, errPairNotSet)
}
if assetType == "" {
return fmt.Errorf("%s %s %s", exchangeName,
tickerNew.Pair,
errAssetTypeNotSet)
}
tickerNew.AssetType = assetType
if tickerNew.LastUpdated.IsZero() {
tickerNew.LastUpdated = time.Now()
}
return service.Update(tickerNew)
}
// Update updates ticker price
func (s *Service) Update(p *Price) error {
var ids []uuid.UUID
s.Lock()
switch {
case s.Tickers[p.ExchangeName] == nil:
s.Tickers[p.ExchangeName] = make(map[*currency.Item]map[*currency.Item]map[asset.Item]*Ticker)
s.Tickers[p.ExchangeName][p.Pair.Base.Item] = make(map[*currency.Item]map[asset.Item]*Ticker)
s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item] = make(map[asset.Item]*Ticker)
err := s.SetItemID(p)
if err != nil {
s.Unlock()
return err
}
case s.Tickers[p.ExchangeName][p.Pair.Base.Item] == nil:
s.Tickers[p.ExchangeName][p.Pair.Base.Item] = make(map[*currency.Item]map[asset.Item]*Ticker)
s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item] = make(map[asset.Item]*Ticker)
err := s.SetItemID(p)
if err != nil {
s.Unlock()
return err
}
case s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item] == nil:
s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item] = make(map[asset.Item]*Ticker)
err := s.SetItemID(p)
if err != nil {
s.Unlock()
return err
}
case s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType] == nil:
err := s.SetItemID(p)
if err != nil {
s.Unlock()
return err
}
default:
ticker := s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType]
ticker.Last = p.Last
ticker.High = p.High
ticker.Low = p.Low
ticker.Bid = p.Bid
ticker.Ask = p.Ask
ticker.Volume = p.Volume
ticker.QuoteVolume = p.QuoteVolume
ticker.PriceATH = p.PriceATH
ticker.Open = p.Open
ticker.Close = p.Close
ticker.LastUpdated = p.LastUpdated
ids = ticker.Assoc
ids = append(ids, ticker.Main)
}
s.Unlock()
return s.mux.Publish(ids, p)
}
// SetItemID retrieves and sets dispatch mux publish IDs
func (s *Service) SetItemID(p *Price) error {
if p == nil {
return errors.New(errTickerPriceIsNil)
}
ids, err := s.GetAssociations(p)
if err != nil {
return err
}
singleID, err := s.mux.GetID()
if err != nil {
return err
}
s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType] = &Ticker{Price: *p,
Main: singleID,
Assoc: ids}
return nil
}
// GetAssociations links a singular book with it's dispatch associations
func (s *Service) GetAssociations(p *Price) ([]uuid.UUID, error) {
if p == nil || *p == (Price{}) {
return nil, errors.New(errTickerPriceIsNil)
}
var ids []uuid.UUID
exchangeID, ok := s.Exchange[p.ExchangeName]
if !ok {
var err error
exchangeID, err = s.mux.GetID()
if err != nil {
return nil, err
}
s.Exchange[p.ExchangeName] = exchangeID
}
ids = append(ids, exchangeID)
return ids, nil
}