mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-25 07:26:48 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -69,7 +69,7 @@ func (b *Bitfinex) SetDefaults() {
|
||||
}
|
||||
|
||||
fmt2 := currency.PairStore{
|
||||
RequestFormat: ¤cy.PairFormat{Uppercase: true, Delimiter: ":"},
|
||||
RequestFormat: ¤cy.PairFormat{Uppercase: true},
|
||||
ConfigFormat: ¤cy.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 = ":"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user