Files
gocryptotrader/exchanges/ticker/ticker.go
Ryan O'Hara-Reid c6ad429827 orderbook/buffer: data integrity and resubscription pass (#910)
* orderbook/buffer: data integrity and resubscription pass

* btcmarkets: REMOVE THAT LIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIINE!!!!!!!!!!!!!!!!!

* buffer: reinstate publish, refaactor, invalidate more and comments

* buffer/orderbook: improve update and snapshot performance. Move Update type to orderbook package to util. pointer through entire function calls. (cleanup). Change action string to uint8 for easier comparison. Add parsing helper. Update current test benchmark comments.

* dispatch: change publish func to variadic id param

* dispatch: remove sender receiver wait time as this adds overhead and complexity. update tests.

* dispatch: don't create pointers for every job container

* rpcserver: fix assertion issues with data publishing change

* linter: fixes

* glorious: nits addr

* depth: change validation handling to incorporate and store err

* linter: fix more issues

* dispatch: fix race

* travis: update before fetching

* depth: wrap and return wrapped error in invalidate call and fix tests

* btcmarkets: fix commenting

* workflow: check

* workflow: check

* orderbook: check error

* buffer/depth: return invalidation error and fix tests

* gctcli: display errors on orderbook streams

* buffer: remove unused types

* orderbook/bitmex: shift function to bitmex

* orderbook: Add specific comments to unexported functions that don't have locking require locking.

* orderbook: restrict published data functionality to orderbook.Outbound interface

* common: add assertion failure helper for error

* dispatch: remove atomics, add mutex protection, remove add/remove worker, redo main tests

* dispatch: export function

* engine: revert and change sub logger to manager

* engine: remove old test

* dispatch: add common variable ;)

* btcmarket: don't overflow int in tests on 32bit systems

* ci: force 1.17.7 usage for go

* Revert "ci: force 1.17.7 usage for go"

This reverts commit af2f95563bf218cf2b9f36a9fcf3258e2c6a2d91.

* golangci: bump version add and remove linter items

* Revert "golangci: bump version add and remove linter items"

This reverts commit 3c98bffc9d030e39faca0387ea40c151df2ab06b.

* dispatch: remove unsused mutex from mux

* order: slight optimizations

* nits: glorious

* dispatch: fix regression on uuid generation and input inline with master

* linter: fix

* linter: fix

* glorious: nit - rm slice segration

* account: fix test after merge

* coinbasepro: revert change

* account: close channel instead of needing a receiver, push alert in routine to prepare for waiter.

Co-authored-by: Ryan O'Hara-Reid <ryan.oharareid@thrasher.io>
2022-05-03 12:37:08 +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
}