Bitfinex: filter security asset type from spot trading pairs (#1191)

* bitfinex: filter security pairs from spot trading

* asset: add securities type

* bitfinex: remove print

* linter: fix

* gomod tidy

* glorious: nits

* bitfinex: just some orderbook fun and added a pass through for bitfinex funding rate when rate == 0

* bitfinex: move update function from testmain to localised test

* test: fix up

* glorious:nits

* bitfinex; fix casing test

---------

Co-authored-by: Ryan O'Hara-Reid <ryan.oharareid@thrasher.io>
This commit is contained in:
Ryan O'Hara-Reid
2023-06-05 11:53:07 +10:00
committed by GitHub
parent 86a540173d
commit d086c45b0a
7 changed files with 192 additions and 1187 deletions

View File

@@ -17,6 +17,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/common/crypto"
"github.com/thrasher-corp/gocryptotrader/currency"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
@@ -78,7 +79,12 @@ const (
bitfinexMarginInfo = "margin_infos"
bitfinexDepositMethod = "conf/pub:map:tx:method"
bitfinexDepositAddress = "auth/w/deposit/address"
bitfinexMarginPairs = "conf/pub:list:pair:margin"
bitfinexMarginPairs = "conf/pub:list:pair:margin"
bitfinexSpotPairs = "conf/pub:list:pair:exchange"
bitfinexMarginFundingPairs = "conf/pub:list:currency"
bitfinexFuturesPairs = "conf/pub:list:pair:futures" // TODO: Implement
bitfinexSecuritiesPairs = "conf/pub:list:pair:securities" // TODO: Implement
// Bitfinex platform status values
// When the platform is marked in maintenance mode bots should stop trading
@@ -419,10 +425,60 @@ func (b *Bitfinex) GetV2Balances(ctx context.Context) ([]WalletDataV2, error) {
return resp, nil
}
// GetMarginPairs gets pairs that allow margin trading
func (b *Bitfinex) GetMarginPairs(ctx context.Context) ([]string, error) {
// GetPairs gets pairs for different assets
func (b *Bitfinex) GetPairs(ctx context.Context, a asset.Item) ([]string, error) {
switch a {
case asset.Spot:
list, err := b.GetSiteListConfigData(ctx, bitfinexSpotPairs)
if err != nil {
return nil, err
}
filter, err := b.GetSiteListConfigData(ctx, bitfinexSecuritiesPairs)
if err != nil {
return nil, err
}
filtered := make([]string, 0, len(list))
for x := range list {
if common.StringDataCompare(filter, list[x]) {
continue
}
filtered = append(filtered, list[x])
}
return filtered, nil
case asset.Margin:
return b.GetSiteListConfigData(ctx, bitfinexMarginPairs)
case asset.Futures:
return b.GetSiteListConfigData(ctx, bitfinexFuturesPairs)
case asset.MarginFunding:
funding, err := b.GetTickerBatch(ctx)
if err != nil {
return nil, err
}
var pairs []string
for key := range funding {
symbol := key[1:]
if key[0] != 'f' || strings.Contains(symbol, ":") || len(symbol) > 6 {
continue
}
pairs = append(pairs, symbol)
}
return pairs, nil
default:
return nil, fmt.Errorf("%v GetPairs: %v %w", b.Name, a, asset.ErrNotSupported)
}
}
// GetSiteListConfigData returns site configuration data by pub:{Action}:{Object}:{Detail}
// string sets.
// NOTE: See https://docs.bitfinex.com/reference/rest-public-conf
// ALSO: This only accesses the lists not the maps.
func (b *Bitfinex) GetSiteListConfigData(ctx context.Context, set string) ([]string, error) {
if set == "" {
return nil, errSetCannotBeEmpty
}
var resp [][]string
path := bitfinexAPIVersion2 + bitfinexMarginPairs
path := bitfinexAPIVersion2 + set
err := b.SendHTTPRequest(ctx, exchange.RestSpot, path, &resp, status)
if err != nil {
return nil, err

View File

@@ -21,6 +21,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
)
@@ -61,6 +62,7 @@ func TestMain(m *testing.M) {
b.API.AuthenticatedWebsocketSupport = true
}
b.WebsocketSubdChannels = make(map[int]WebsocketChanInfo)
os.Exit(m.Run())
}
@@ -139,11 +141,20 @@ func TestGetDerivativeStatusInfo(t *testing.T) {
}
}
func TestGetMarginPairs(t *testing.T) {
func TestGetPairs(t *testing.T) {
t.Parallel()
_, err := b.GetMarginPairs(context.Background())
if err != nil {
t.Error(err)
_, err := b.GetPairs(context.Background(), asset.Binary)
if !errors.Is(err, asset.ErrNotSupported) {
t.Fatalf("received: '%v' but expected: '%v'", err, asset.ErrNotSupported)
}
assets := b.GetAssetTypes(false)
for x := range assets {
_, err := b.GetPairs(context.Background(), assets[x])
if err != nil {
t.Error(err)
}
}
}
@@ -417,6 +428,8 @@ func TestNewOrder(t *testing.T) {
}
func TestUpdateTicker(t *testing.T) {
t.Parallel()
pair, err := currency.NewPairFromString("BTCUSD")
if err != nil {
t.Fatal(err)
@@ -429,9 +442,37 @@ func TestUpdateTicker(t *testing.T) {
}
func TestUpdateTickers(t *testing.T) {
err := b.UpdateTickers(context.Background(), asset.Spot)
t.Parallel()
err := b.UpdateTradablePairs(context.Background(), false)
if err != nil {
t.Error(err)
t.Fatal(err)
}
assets := b.GetAssetTypes(false)
for x := range assets {
var avail []currency.Pair
avail, err = b.GetAvailablePairs(assets[x])
if err != nil {
t.Fatal(err)
}
err = b.CurrencyPairs.StorePairs(assets[x], avail, true)
if err != nil {
t.Fatal(err)
}
err = b.UpdateTickers(context.Background(), assets[x])
if err != nil {
t.Fatal(err)
}
for y := range avail {
_, err = ticker.GetTicker(b.Name, avail[y], assets[x])
if err != nil {
t.Fatal(err)
}
}
}
}
@@ -1109,13 +1150,6 @@ func TestWsCancelOffer(t *testing.T) {
}
}
func TestUpdateTradablePairs(t *testing.T) {
err := b.UpdateTradablePairs(context.Background(), false)
if err != nil {
t.Error(err)
}
}
func TestWsSubscribedResponse(t *testing.T) {
pressXToJSON := `{"event":"subscribed","channel":"ticker","chanId":224555,"symbol":"tBTCUSD","pair":"BTCUSD"}`
err := b.wsHandleData([]byte(pressXToJSON))
@@ -1374,7 +1408,7 @@ func TestFixCasing(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if ret != "tBTC:USD" {
if ret != "tBTCUSD" {
t.Errorf("unexpected result: %v", ret)
}
pair, err = currency.NewPairFromString("BTCUSD")
@@ -1697,3 +1731,21 @@ func TestAccetableMethodStore(t *testing.T) {
t.Error("incorrect values")
}
}
func TestGetSiteListConfigData(t *testing.T) {
t.Parallel()
_, err := b.GetSiteListConfigData(context.Background(), "")
if !errors.Is(err, errSetCannotBeEmpty) {
t.Fatalf("received: %v, expected: %v", err, errSetCannotBeEmpty)
}
pairs, err := b.GetSiteListConfigData(context.Background(), bitfinexSecuritiesPairs)
if !errors.Is(err, nil) {
t.Fatalf("received: %v, expected: %v", err, nil)
}
if len(pairs) == 0 {
t.Fatal("expected pairs")
}
}

View File

@@ -10,7 +10,10 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
)
var errTypeAssert = errors.New("type assertion failed")
var (
errTypeAssert = errors.New("type assertion failed")
errSetCannotBeEmpty = errors.New("set cannot be empty")
)
// AccountV2Data stores account v2 data
type AccountV2Data struct {

View File

@@ -69,7 +69,7 @@ func (b *Bitfinex) SetDefaults() {
}
fmt2 := currency.PairStore{
RequestFormat: &currency.PairFormat{Uppercase: true, Delimiter: ":"},
RequestFormat: &currency.PairFormat{Uppercase: true},
ConfigFormat: &currency.PairFormat{Uppercase: true, Delimiter: ":"},
}
@@ -85,6 +85,7 @@ func (b *Bitfinex) SetDefaults() {
if err != nil {
log.Errorln(log.ExchangeSys, err)
}
// TODO: Implement Futures and Securities asset types.
b.Features = exchange.Features{
Supports: exchange.FeaturesSupported{
@@ -278,49 +279,27 @@ func (b *Bitfinex) Run(ctx context.Context) {
// FetchTradablePairs returns a list of the exchanges tradable pairs
func (b *Bitfinex) FetchTradablePairs(ctx context.Context, a asset.Item) (currency.Pairs, error) {
items, err := b.GetTickerBatch(ctx)
items, err := b.GetPairs(ctx, a)
if err != nil {
return nil, err
}
pairs := make([]currency.Pair, 0, len(items))
var pair currency.Pair
switch a {
case asset.Spot:
for k := range items {
if !strings.HasPrefix(k, "t") {
continue
}
pair, err = currency.NewPairFromString(k[1:])
if err != nil {
return nil, err
}
pairs = append(pairs, pair)
pairs := make(currency.Pairs, 0, len(items))
for x := range items {
if strings.Contains(items[x], "TEST") {
continue
}
case asset.Margin:
for k := range items {
if !strings.Contains(k, ":") {
continue
}
pair, err = currency.NewPairFromStrings(k[1:], "")
if err != nil {
return nil, err
}
pairs = append(pairs, pair)
var pair currency.Pair
if a == asset.MarginFunding {
pair, err = currency.NewPairFromStrings(items[x], "")
} else {
pair, err = currency.NewPairFromString(items[x])
}
case asset.MarginFunding:
for k := range items {
if !strings.HasPrefix(k, "f") {
continue
}
pair, err = currency.NewPairFromString(k[1:])
if err != nil {
return nil, err
}
pairs = append(pairs, pair)
if err != nil {
return nil, err
}
default:
return nil, errors.New("asset type not supported by this endpoint")
pairs = append(pairs, pair)
}
return pairs, nil
}
@@ -345,7 +324,7 @@ func (b *Bitfinex) UpdateTradablePairs(ctx context.Context, forceUpdate bool) er
// UpdateTickers updates the ticker for all currency pairs of a given asset type
func (b *Bitfinex) UpdateTickers(ctx context.Context, a asset.Item) error {
enabledPairs, err := b.GetEnabledPairs(a)
enabled, err := b.GetEnabledPairs(a)
if err != nil {
return err
}
@@ -355,23 +334,20 @@ func (b *Bitfinex) UpdateTickers(ctx context.Context, a asset.Item) error {
return err
}
for k, v := range tickerNew {
pair, err := currency.NewPairFromString(k[1:]) // Remove prefix
for key, val := range tickerNew {
pair, err := enabled.DeriveFrom(strings.Replace(key, ":", "", 1)[1:])
if err != nil {
return err
}
if !enabledPairs.Contains(pair, true) {
// GetTickerBatch returns all pairs in call across all asset types.
continue
}
err = ticker.ProcessTicker(&ticker.Price{
Last: v.Last,
High: v.High,
Low: v.Low,
Bid: v.Bid,
Ask: v.Ask,
Volume: v.Volume,
Last: val.Last,
High: val.High,
Low: val.Low,
Bid: val.Bid,
Ask: val.Ask,
Volume: val.Volume,
Pair: pair,
AssetType: a,
ExchangeName: b.Name})
@@ -442,8 +418,8 @@ func (b *Bitfinex) UpdateOrderbook(ctx context.Context, p currency.Pair, assetTy
if assetType == asset.MarginFunding {
prefix = "f"
}
var orderbookNew Orderbook
orderbookNew, err = b.GetOrderbook(ctx, prefix+fPair.String(), "R0", 100)
orderbookNew, err := b.GetOrderbook(ctx, prefix+fPair.String(), "R0", 100)
if err != nil {
return o, err
}
@@ -1055,8 +1031,8 @@ func (b *Bitfinex) AuthenticateWebsocket(ctx context.Context) error {
// appendOptionalDelimiter ensures that a delimiter is present for long character currencies
func (b *Bitfinex) appendOptionalDelimiter(p *currency.Pair) {
if len(p.Quote.String()) > 3 ||
len(p.Base.String()) > 3 {
if (len(p.Base.String()) > 3 && len(p.Quote.String()) > 0) ||
len(p.Quote.String()) > 3 {
p.Delimiter = ":"
}
}

View File

@@ -239,11 +239,11 @@ func (b *Base) Verify() error {
len(b.Bids),
len(b.Asks))
}
err := checkAlignment(b.Bids, b.IsFundingRate, b.PriceDuplication, b.IDAlignment, dsc)
err := checkAlignment(b.Bids, b.IsFundingRate, b.PriceDuplication, b.IDAlignment, dsc, b.Exchange)
if err != nil {
return fmt.Errorf(bidLoadBookFailure, b.Exchange, b.Pair, b.Asset, err)
}
err = checkAlignment(b.Asks, b.IsFundingRate, b.PriceDuplication, b.IDAlignment, asc)
err = checkAlignment(b.Asks, b.IsFundingRate, b.PriceDuplication, b.IDAlignment, asc, b.Exchange)
if err != nil {
return fmt.Errorf(askLoadBookFailure, b.Exchange, b.Pair, b.Asset, err)
}
@@ -271,11 +271,16 @@ var dsc = func(current Item, previous Item) error {
}
// checkAlignment validates full orderbook
func checkAlignment(depth Items, fundingRate, priceDuplication, isIDAligned bool, c checker) error {
func checkAlignment(depth Items, fundingRate, priceDuplication, isIDAligned bool, c checker, exch string) error {
for i := range depth {
if depth[i].Price == 0 {
return errPriceNotSet
switch {
case exch == "Bitfinex" && fundingRate: /* funding rate can be 0 it seems on Bitfinex */
default:
return errPriceNotSet
}
}
if depth[i].Amount <= 0 {
return errAmountInvalid
}

View File

@@ -721,3 +721,26 @@ func BenchmarkCopySlice(b *testing.B) {
copy(cpy, s)
}
}
func TestCheckAlignment(t *testing.T) {
t.Parallel()
itemWithFunding := Items{
{
Amount: 1337,
Price: 0,
Period: 1337,
},
}
err := checkAlignment(itemWithFunding, true, true, false, dsc, "Bitfinex")
if err != nil {
t.Error(err)
}
err = checkAlignment(itemWithFunding, false, true, false, dsc, "Bitfinex")
if !errors.Is(err, errPriceNotSet) {
t.Fatalf("received: %v but expected: %v", err, errPriceNotSet)
}
err = checkAlignment(itemWithFunding, true, true, false, dsc, "Binance")
if !errors.Is(err, errPriceNotSet) {
t.Fatalf("received: %v but expected: %v", err, errPriceNotSet)
}
}

1110
go.sum

File diff suppressed because it is too large Load Diff