exchanges/futures: Implement open interest (#1417)

* adds open interest to exchanges

* ADDS TESTING YEAH

* New endpoints, BTSE, RPCS, cached

* slight design change, begin gateio

You will need to get cached for
each exchange that supports it

* gateio, huobi, rpc

* fix up kraken, cache retrieval

* okx, gateio

* finalising all implementations and tests

* definitely my final ever commit on this

* Well, well, well

* final v2

* quick fix of bug

* test coverage, assert notempty, test helper

Added a new testhelper for currency
management because its very annoying
in a parallel test setting which wastes
so much space otherwise

* minimises REST requests for Open Interest

* types.Number merge misses

* Minimises Kraken REST calls

* len change, value -> pointer receiver

* further fixup

* fixes gateio, batch calculates open interest

* single gateio, lint const fixes

* rejig and more thorough oi for huobi

* formatting expansion

* minor fix for handling expiring contracts

* rm unused Binance strings

* add bybit support, fix bybit issues

* oopsie doopsie, dont look at my whoopsie

* Fix issue, remove feature

* move an irrelevant function for the pr

* mini bybit upgrades

* fixes cli request bug
This commit is contained in:
Scott
2024-01-12 15:27:35 +11:00
committed by GitHub
parent 614042110a
commit b71bf1f3d1
62 changed files with 22660 additions and 10095 deletions

View File

@@ -14,12 +14,16 @@ import (
)
var (
// ErrNoTickerFound is when a ticker is not found
ErrNoTickerFound = errors.New("no ticker found")
// ErrBidEqualsAsk error for locked markets
ErrBidEqualsAsk = errors.New("bid equals ask this is a crossed or locked market")
ErrBidEqualsAsk = errors.New("bid equals ask this is a crossed or locked market")
errInvalidTicker = errors.New("invalid ticker")
errTickerNotFound = errors.New("ticker not found")
errExchangeNameIsEmpty = errors.New("exchange name is empty")
errBidGreaterThanAsk = errors.New("bid greater than ask this is a crossed or locked market")
errExchangeNotFound = errors.New("exchange not found")
)
func init() {
@@ -85,14 +89,41 @@ func GetTicker(exchange string, p currency.Pair, a asset.Item) (*Price, error) {
Asset: a,
}]
if !ok {
return nil, fmt.Errorf("no tickers associated with asset type %s %s %s",
exchange, p, a)
return nil, fmt.Errorf("%w %s %s %s",
ErrNoTickerFound, exchange, p, a)
}
cpy := tick.Price // Don't let external functions have access to underlying
return &cpy, nil
}
// GetExchangeTickers returns all tickers for a given exchange
func GetExchangeTickers(exchange string) ([]*Price, error) {
return service.getExchangeTickers(exchange)
}
func (s *Service) getExchangeTickers(exchange string) ([]*Price, error) {
if exchange == "" {
return nil, errExchangeNameIsEmpty
}
exchange = strings.ToLower(exchange)
s.mu.Lock()
defer s.mu.Unlock()
_, ok := s.Exchange[exchange]
if !ok {
return nil, fmt.Errorf("%w %v", errExchangeNotFound, exchange)
}
tickers := make([]*Price, 0, len(s.Tickers))
for k, v := range s.Tickers {
if k.Exchange != exchange {
continue
}
cpy := v.Price // Don't let external functions have access to underlying
tickers = append(tickers, &cpy)
}
return tickers, 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()
@@ -206,7 +237,7 @@ func (s *Service) setItemID(t *Ticker, p *Price, exch string) error {
return nil
}
// getAssociations links a singular book with it's dispatch associations
// getAssociations links a singular book with its dispatch associations
func (s *Service) getAssociations(exch string) ([]uuid.UUID, error) {
if exch == "" {
return nil, errExchangeNameIsEmpty

View File

@@ -10,7 +10,9 @@ import (
"testing"
"time"
"github.com/gofrs/uuid"
"github.com/stretchr/testify/assert"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/dispatch"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
@@ -448,3 +450,44 @@ func TestGetAssociation(t *testing.T) {
service.mux = cpyMux
}
func TestGetExchangeTickersPublic(t *testing.T) {
_, err := GetExchangeTickers("")
assert.ErrorIs(t, err, errExchangeNameIsEmpty)
}
func TestGetExchangeTickers(t *testing.T) {
t.Parallel()
s := Service{
Tickers: make(map[key.ExchangePairAsset]*Ticker),
Exchange: make(map[string]uuid.UUID),
}
_, err := s.getExchangeTickers("")
assert.ErrorIs(t, err, errExchangeNameIsEmpty)
_, err = s.getExchangeTickers("test")
assert.ErrorIs(t, err, errExchangeNotFound)
s.Tickers[key.ExchangePairAsset{
Exchange: "test",
Base: currency.XBT.Item,
Quote: currency.DOGE.Item,
Asset: asset.Futures,
}] = &Ticker{
Price: Price{
Pair: currency.NewPair(currency.XBT, currency.DOGE),
ExchangeName: "test",
AssetType: asset.Futures,
OpenInterest: 1337,
},
}
s.Exchange["test"] = uuid.Must(uuid.NewV4())
resp, err := s.getExchangeTickers("test")
assert.NoError(t, err)
if len(resp) != 1 {
t.Fatal("unexpected length")
}
assert.Equal(t, resp[0].OpenInterest, 1337.0)
}

View File

@@ -44,6 +44,7 @@ type Price struct {
PriceATH float64 `json:"PriceATH"`
Open float64 `json:"Open"`
Close float64 `json:"Close"`
OpenInterest float64 `json:"OpenInterest"`
Pair currency.Pair `json:"Pair"`
ExchangeName string `json:"exchangeName"`
AssetType asset.Item `json:"assetType"`