mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-15 15:09:55 +00:00
* initial concept of a nice validation tester for exchanges * adds some datahandler design * expand testing * more tests and fixes * minor end of day fix for bithumb * fixes implementation issues * more test coverage and improvements, but not sure if i should continue * fix more wrapper implementations * adds error type, more fixes * changes signature, fixes implementations * fixes more wrapper implementations * one more bit * more cleanup * WOW things work? * lintle 1/1337 * mini bump * fixes all linting * neaten * GetOrderInfo+ asset pair fixes+improvements * adds new websocket test * expand ws testing * fix bug, expand tests, improve implementation * code coverage of a lot of new codes * fixes everything * reverts accidental changes * minor fixes from reviewing code * removes Bitfinex cancelBatchOrder implementation * fixes dumb baby typo for babies * mini nit fixes * so many nits to address * addresses all the nits * Titlecase * switcheroo * removes websocket testing for now * fix appveyor, minor test fix * fixes typo, re-kindles killed kode * skip binance wrapper tests when running CI * expired context, huobi okx fixes * kodespull * fix ordering * time fix because why not * fix exmo, others * hopefully this fixes all of my life's problems * last thing today * huobi, more like hypotrophy * golangci-lint, more like mypooroldknee-splint * fix huobi times by removing them * should fix okx currency issues * blocks the application * adds last little contingency for pairs * addresses most nits and new problems * lovely fixed before seeing why okx sucks * fixes issues with okx websocket * the classic receieieivaier * lintle * adds test and fixes existing tests * expands error handling messages during setup * fixes dumb okx bugs introduced * quick fix for lint and exmo * fixes nixes * fix exmo deposit issue * lint * fixes issue with extra asset runs missing * fix surprise race * all the lint and merge fixes * fixes surprise bugs in OKx * fixes issues with times and chains * fixing all the merge stuff * merge fix * rm logs and a panic potential * lovely lint lament * an easy demonstration of scenario, but not of initial purpose * put it in the bin * Revert "put it in the bin" This reverts commit 15c6490f713233d43f10957367fcbf18e3818bdd. * re-add after immediate error popup * fix mini poor test design * okx okay * merge fixes * fixes issues discovered in lovely test * I FORGOT TO COMMIT THIS * nit fixaroonaboo * forgoetten test fix * revert old okx asset intrument work * fixes * revert problems I didnt understand. update bybit * fix merge bugs * test cleanup * further improvements * reshuffle and lint * rm redundant CI_TEST by rm the CI_TEST field that is redundant * path fix * move to its own section, dont run on 32 bit + appveyor * lint * fix lbank * address nits * let it rip * fix failing test time range * niteroo boogaloo * mod tidy, use common.SimpleTimeFormat
256 lines
5.8 KiB
Go
256 lines
5.8 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")
|
|
errBidEqualsAsk = errors.New("bid equals ask this is a crossed or locked market")
|
|
errBidGreaterThanAsk = errors.New("bid greater than ask this is a crossed or locked market")
|
|
)
|
|
|
|
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) {
|
|
if exchange == "" {
|
|
return nil, errExchangeNameIsEmpty
|
|
}
|
|
if p.IsEmpty() {
|
|
return nil, currency.ErrCurrencyPairEmpty
|
|
}
|
|
if !a.IsValid() {
|
|
return nil, fmt.Errorf("%w %v", asset.ErrNotSupported, a)
|
|
}
|
|
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.Bid != 0 && p.Ask != 0 {
|
|
switch {
|
|
case p.ExchangeName == "Bitfinex" && p.AssetType == asset.MarginFunding:
|
|
// Margin funding books can be crossed see Bitfinex.
|
|
default:
|
|
if p.Bid == p.Ask {
|
|
return fmt.Errorf("%s %s %w",
|
|
p.ExchangeName,
|
|
p.Pair,
|
|
errBidEqualsAsk)
|
|
}
|
|
|
|
if p.Bid > p.Ask {
|
|
return fmt.Errorf("%s %s %w",
|
|
p.ExchangeName,
|
|
p.Pair,
|
|
errBidGreaterThanAsk)
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|