mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-06-02 07:26:53 +00:00
BTSE: Various fixes (#1550)
* Common: DriveBy sanitisation of vars * BTSE: Unify to exchange OrderLimits * BTSE: Remove SeedAssets and test GetOrderExcutionLimit * BTSE: Fix handling for K_* pairs In addition to the M_ pairs we previously handled, BTSE has now introduced K_* pairs (K_SATS-USD*). This change moves the handling over to look for an exponent, and moves the filtering to happen on all market data. The original MarketSummary is still availiable, but I can't see any of our current use-cases wanting to get the unfiltered list * BTSE: Fix marketSummary futures field BTSE returns no futures field for futures api marketInfo, and the documentation for the futures api shows returning false. When we know we asked for futures, it makes the data flow much saner if we can trust this field and not have to track what asset we asked for * BTSE: Abstract marketPair.Pair() * BTSE: Fix UpdateTicker symbol format
This commit is contained in:
@@ -68,8 +68,9 @@ func (b *BTSE) FetchFundingHistory(ctx context.Context, symbol string) (map[stri
|
||||
return resp, b.SendHTTPRequest(ctx, exchange.RestFutures, http.MethodGet, btseFuturesFunding+params.Encode(), &resp, false, queryFunc)
|
||||
}
|
||||
|
||||
// GetMarketSummary stores market summary data
|
||||
func (b *BTSE) GetMarketSummary(ctx context.Context, symbol string, spot bool) (MarketSummary, error) {
|
||||
// GetRawMarketSummary returns an unfiltered list of market pairs
|
||||
// Consider using the wrapper function GetMarketSummary instead
|
||||
func (b *BTSE) GetRawMarketSummary(ctx context.Context, symbol string, spot bool) (MarketSummary, error) {
|
||||
var m MarketSummary
|
||||
path := btseMarketOverview
|
||||
if symbol != "" {
|
||||
@@ -619,23 +620,7 @@ func parseOrderTime(timeStr string) (time.Time, error) {
|
||||
return time.Parse(time.DateTime, timeStr)
|
||||
}
|
||||
|
||||
// MillionPairs returns a map of symbol names which have a IsMillion equivalent
|
||||
func (m *MarketSummary) MillionPairs() map[string]bool {
|
||||
pairs := map[string]bool{}
|
||||
for _, s := range *m {
|
||||
if s.Active && s.HasLiquidity() && s.IsMillions() {
|
||||
pairs[strings.TrimPrefix(s.Symbol, "M_")] = true
|
||||
}
|
||||
}
|
||||
return pairs
|
||||
}
|
||||
|
||||
// HasLiquidity returns if a market pair has a bid or ask != 0
|
||||
func (m *MarketPair) HasLiquidity() bool {
|
||||
return m.LowestAsk != 0 || m.HighestBid != 0
|
||||
}
|
||||
|
||||
// IsMillions returns if a market pair represents a million of Base / Quote
|
||||
func (m *MarketPair) IsMillions() bool {
|
||||
return strings.HasPrefix(m.Symbol, "M_")
|
||||
}
|
||||
|
||||
@@ -144,6 +144,10 @@ func TestFormatExchangeKlineInterval(t *testing.T) {
|
||||
|
||||
func TestGetHistoricCandles(t *testing.T) {
|
||||
t.Parallel()
|
||||
r := b.Requester
|
||||
b := new(BTSE) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes
|
||||
require.NoError(t, testexch.Setup(b), "Test exchange Setup must not error")
|
||||
b.Requester = r
|
||||
start := time.Now().AddDate(0, 0, -3)
|
||||
_, err := b.GetHistoricCandles(context.Background(), spotPair, asset.Spot, kline.OneHour, start, time.Now())
|
||||
assert.NoError(t, err, "GetHistoricCandles should not error")
|
||||
@@ -154,6 +158,10 @@ func TestGetHistoricCandles(t *testing.T) {
|
||||
|
||||
func TestGetHistoricCandlesExtended(t *testing.T) {
|
||||
t.Parallel()
|
||||
r := b.Requester
|
||||
b := new(BTSE) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes
|
||||
require.NoError(t, testexch.Setup(b), "Test exchange Setup must not error")
|
||||
b.Requester = r
|
||||
err := b.CurrencyPairs.StorePairs(asset.Futures, currency.Pairs{futuresPair}, true)
|
||||
assert.NoError(t, err, "StorePairs should not error")
|
||||
|
||||
@@ -542,80 +550,28 @@ func TestMatchType(t *testing.T) {
|
||||
assert.True(t, ret, "matchType should match")
|
||||
}
|
||||
|
||||
func TestSeedOrderSizeLimits(t *testing.T) {
|
||||
t.Parallel()
|
||||
err := b.seedOrderSizeLimits(context.Background())
|
||||
assert.NoError(t, err, "seedOrderSizeLimits should not error")
|
||||
}
|
||||
|
||||
func TestOrderSizeLimits(t *testing.T) {
|
||||
t.Parallel()
|
||||
seedOrderSizeLimitMap()
|
||||
_, ok := OrderSizeLimits(spotPair.String())
|
||||
assert.True(t, ok, "OrderSizeLimits should find BTC-USD")
|
||||
|
||||
_, ok = OrderSizeLimits("XRP-GARBAGE")
|
||||
assert.False(t, ok, "OrderSizeLimits should not find XRP-GARBAGE until the next bull market")
|
||||
}
|
||||
|
||||
func seedOrderSizeLimitMap() {
|
||||
testOrderSizeLimits := []struct {
|
||||
name string
|
||||
o OrderSizeLimit
|
||||
}{
|
||||
{
|
||||
name: "XRP-USD",
|
||||
o: OrderSizeLimit{
|
||||
MinSizeIncrement: 1,
|
||||
MinOrderSize: 1,
|
||||
MaxOrderSize: 1000000,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "LTC-USD",
|
||||
o: OrderSizeLimit{
|
||||
MinSizeIncrement: 0.01,
|
||||
MinOrderSize: 0.01,
|
||||
MaxOrderSize: 5000,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "BTC-USD",
|
||||
o: OrderSizeLimit{
|
||||
MinSizeIncrement: 0.0001,
|
||||
MinOrderSize: 1,
|
||||
MaxOrderSize: 1000000,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
orderSizeLimitMap.Range(func(key interface{}, _ interface{}) bool {
|
||||
orderSizeLimitMap.Delete(key)
|
||||
return true
|
||||
})
|
||||
|
||||
for x := range testOrderSizeLimits {
|
||||
orderSizeLimitMap.Store(testOrderSizeLimits[x].name, testOrderSizeLimits[x].o)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithinLimits(t *testing.T) {
|
||||
// TestUpdateOrderExecutionLimits exercises UpdateOrderExecutionLimits
|
||||
func TestUpdateOrderExecutionLimits(t *testing.T) {
|
||||
t.Parallel()
|
||||
testexch.UpdatePairsOnce(t, b)
|
||||
seedOrderSizeLimitMap()
|
||||
p, _ := currency.NewPairDelimiter("XRP-USD", "-")
|
||||
assert.NoError(t, b.withinLimits(p, 1.0), "withinLimits should not error")
|
||||
assert.NoError(t, b.withinLimits(p, 5.0000001), "withinLimits should not error")
|
||||
assert.NoError(t, b.withinLimits(p, 100), "withinLimits should not error")
|
||||
assert.NoError(t, b.withinLimits(p, 10.1), "withinLimits should not error")
|
||||
for _, a := range b.GetAssetTypes(false) {
|
||||
err := b.UpdateOrderExecutionLimits(context.Background(), a)
|
||||
require.NoErrorf(t, err, "UpdateOrderExecutionLimits must not error for %s", a)
|
||||
|
||||
p.Base = currency.LTC
|
||||
assert.NoError(t, b.withinLimits(p, 10), "withinLimits should not error")
|
||||
assert.ErrorIs(t, b.withinLimits(p, 0.009), order.ErrAmountBelowMin, "withinLimits should error correctly")
|
||||
pairs, err := b.GetAvailablePairs(a)
|
||||
require.NoErrorf(t, err, "GetAvailablePairs must not error for %s", a)
|
||||
require.NotEmpty(t, pairs, "GetAvailablePairs must return some pairs")
|
||||
|
||||
p.Base = currency.BTC
|
||||
assert.NoError(t, b.withinLimits(p, 10), "withinLimits should not error")
|
||||
assert.ErrorIs(t, b.withinLimits(p, 0.001), order.ErrAmountBelowMin, "withinLimits should error correctly")
|
||||
for _, p := range pairs {
|
||||
limits, err := b.GetOrderExecutionLimits(a, p)
|
||||
require.NoErrorf(t, err, "GetOrderExecutionLimits must not error for %s %s", a, p)
|
||||
assert.Positivef(t, limits.MinimumBaseAmount, "MinimumBaseAmount must be positive for %s %s", a, p)
|
||||
assert.Positivef(t, limits.MaximumBaseAmount, "MaximumBaseAmount must be positive for %s %s", a, p)
|
||||
assert.Positivef(t, limits.AmountStepIncrementSize, "AmountStepIncrementSize must be positive for %s %s", a, p)
|
||||
assert.Positivef(t, limits.MinPrice, "MinPrice must be positive for %s %s", a, p)
|
||||
assert.Positivef(t, limits.PriceStepIncrementSize, "PriceStepIncrementSize must be positive for %s %s", a, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRecentTrades(t *testing.T) {
|
||||
@@ -718,7 +674,11 @@ func TestIsPerpetualFutureCurrency(t *testing.T) {
|
||||
|
||||
func TestGetOpenInterest(t *testing.T) {
|
||||
t.Parallel()
|
||||
r := b.Requester
|
||||
b := new(BTSE) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes
|
||||
require.NoError(t, testexch.Setup(b), "Test exchange Setup must not error")
|
||||
testexch.UpdatePairsOnce(t, b)
|
||||
b.Requester = r
|
||||
cp1 := currency.NewPair(currency.BTC, currency.PFC)
|
||||
cp2 := currency.NewPair(currency.ETH, currency.PFC)
|
||||
sharedtestvalues.SetupCurrencyPairsForExchangeAsset(t, b, asset.Futures, futuresPair, cp1, cp2)
|
||||
@@ -769,3 +729,23 @@ func TestGetCurrencyTradeURL(t *testing.T) {
|
||||
assert.NotEmpty(t, resp)
|
||||
}
|
||||
}
|
||||
|
||||
// TestStripExponent exercises StripExponent
|
||||
func TestStripExponent(t *testing.T) {
|
||||
t.Parallel()
|
||||
s, err := (&MarketPair{Symbol: "BTC-ETH"}).StripExponent()
|
||||
assert.NoError(t, err, "Should not error on a symbol without exponent")
|
||||
assert.Empty(t, s, "Should return an empty symbol without exponent")
|
||||
|
||||
for _, p := range []string{"B", "M", "K"} {
|
||||
s, err = (&MarketPair{Symbol: p + "_BTC-ETH"}).StripExponent()
|
||||
assert.NoError(t, err, "Should not error on a symbol with exponent")
|
||||
assert.Equal(t, "BTC-ETH", s, "Should return the symbol without the exponent")
|
||||
}
|
||||
|
||||
_, err = (&MarketPair{Symbol: "Z_BTC-ETH"}).StripExponent()
|
||||
assert.ErrorIs(t, err, errInvalidPairSymbol, "Should error on a symbol with unknown exponent")
|
||||
|
||||
_, err = (&MarketPair{Symbol: "M_BTC_ETH"}).StripExponent()
|
||||
assert.ErrorIs(t, err, errInvalidPairSymbol, "Should error on a symbol with too many underscores")
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package btse
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -354,16 +353,6 @@ type ErrorResponse struct {
|
||||
Status int `json:"status"`
|
||||
}
|
||||
|
||||
// OrderSizeLimit holds accepted minimum, maximum, and size increment when submitting new orders
|
||||
type OrderSizeLimit struct {
|
||||
MinOrderSize float64
|
||||
MaxOrderSize float64
|
||||
MinSizeIncrement float64
|
||||
}
|
||||
|
||||
// orderSizeLimitMap map of OrderSizeLimit per currency
|
||||
var orderSizeLimitMap sync.Map
|
||||
|
||||
// WsSubscriptionAcknowledgement contains successful subscription messages
|
||||
type WsSubscriptionAcknowledgement struct {
|
||||
Channel []string `json:"channel"`
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -33,6 +32,11 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
|
||||
)
|
||||
|
||||
// Private Errors
|
||||
var (
|
||||
errInvalidPairSymbol = errors.New("invalid currency pair symbol")
|
||||
)
|
||||
|
||||
// SetDefaults sets the basic defaults for BTSE
|
||||
func (b *BTSE) SetDefaults() {
|
||||
b.Name = "BTSE"
|
||||
@@ -193,11 +197,6 @@ func (b *BTSE) Setup(exch *config.Exchange) error {
|
||||
return err
|
||||
}
|
||||
|
||||
err = b.seedOrderSizeLimits(context.TODO())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return b.Websocket.SetupNewConnection(stream.ConnectionSetup{
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
@@ -210,41 +209,16 @@ func (b *BTSE) FetchTradablePairs(ctx context.Context, a asset.Item) (currency.P
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var errs error
|
||||
pairs := make(currency.Pairs, 0, len(m))
|
||||
mPairs := m.MillionPairs()
|
||||
for _, l := range m {
|
||||
if !l.Active || !l.HasLiquidity() ||
|
||||
(a == asset.Spot && !l.IsMarketOpenToSpot) { // Skip OTC assets only tradable on web UI
|
||||
continue
|
||||
}
|
||||
if mPairs[l.Symbol] {
|
||||
// BTSE lists M_ symbols for very small pairs, in millions. For those listings, we want to take the M_ listing in preference
|
||||
// to the native listing, since they're often going to appear as locked markets due to size (bid == ask, e.g. 0.0000000003)
|
||||
continue
|
||||
}
|
||||
baseCurr := l.Base
|
||||
var quoteCurr string
|
||||
if a == asset.Futures {
|
||||
s := strings.Split(l.Symbol, l.Base) // e.g. RUNEPFC for RUNE-USD futures pair
|
||||
if len(s) <= 1 {
|
||||
continue
|
||||
}
|
||||
quoteCurr = s[1]
|
||||
for _, marketInfo := range m {
|
||||
if pair, err := marketInfo.Pair(); err != nil {
|
||||
errs = common.AppendError(errs, fmt.Errorf("%s: %w", marketInfo.Symbol, err))
|
||||
} else {
|
||||
s := strings.Split(l.Symbol, currency.DashDelimiter)
|
||||
if len(s) != 2 {
|
||||
continue
|
||||
}
|
||||
baseCurr = s[0]
|
||||
quoteCurr = s[1]
|
||||
pairs = append(pairs, pair)
|
||||
}
|
||||
pair, err := currency.NewPairFromStrings(baseCurr, quoteCurr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pairs = append(pairs, pair)
|
||||
}
|
||||
return pairs, nil
|
||||
return pairs, errs
|
||||
}
|
||||
|
||||
// UpdateTradablePairs updates the exchanges available pairs and stores
|
||||
@@ -305,7 +279,11 @@ func (b *BTSE) UpdateTicker(ctx context.Context, p currency.Pair, a asset.Item)
|
||||
if !b.SupportsAsset(a) {
|
||||
return nil, fmt.Errorf("%w %v", asset.ErrNotSupported, a)
|
||||
}
|
||||
ticks, err := b.GetMarketSummary(ctx, p.String(), a == asset.Spot)
|
||||
symbol, err := b.FormatSymbol(p, a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ticks, err := b.GetMarketSummary(ctx, symbol, a == asset.Spot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -458,23 +436,6 @@ func (b *BTSE) GetAccountFundingHistory(_ context.Context) ([]exchange.FundingHi
|
||||
return nil, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
func (b *BTSE) withinLimits(pair currency.Pair, amount float64) error {
|
||||
val, found := OrderSizeLimits(pair.String())
|
||||
if !found {
|
||||
return fmt.Errorf("%w for pair %v", order.ErrExchangeLimitNotLoaded, pair)
|
||||
}
|
||||
if math.Mod(amount, val.MinSizeIncrement) < 0 {
|
||||
return fmt.Errorf("%w %v %v %v", order.ErrAmountBelowMin, pair, amount, val.MinSizeIncrement)
|
||||
}
|
||||
if amount < val.MinOrderSize {
|
||||
return fmt.Errorf("%w %v %v %v", order.ErrAmountBelowMin, pair, amount, val.MinOrderSize)
|
||||
}
|
||||
if amount > val.MaxOrderSize {
|
||||
return fmt.Errorf("%w %v %v %v", order.ErrAmountExceedsMax, pair, amount, val.MinSizeIncrement)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetWithdrawalsHistory returns previous withdrawals data
|
||||
func (b *BTSE) GetWithdrawalsHistory(_ context.Context, _ currency.Code, _ asset.Item) ([]exchange.WithdrawalHistory, error) {
|
||||
return nil, common.ErrFunctionNotSupported
|
||||
@@ -543,10 +504,6 @@ func (b *BTSE) SubmitOrder(ctx context.Context, s *order.Submit) (*order.SubmitR
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = b.withinLimits(fPair, s.Amount)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r, err := b.CreateOrder(ctx,
|
||||
s.ClientID, 0.0,
|
||||
@@ -1077,45 +1034,6 @@ func (b *BTSE) GetHistoricCandlesExtended(ctx context.Context, pair currency.Pai
|
||||
return req.ProcessResponse(timeSeries)
|
||||
}
|
||||
|
||||
func (b *BTSE) seedOrderSizeLimits(ctx context.Context) error {
|
||||
pairs, err := b.GetMarketSummary(ctx, "", true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for x := range pairs {
|
||||
tempValues := OrderSizeLimit{
|
||||
MinOrderSize: pairs[x].MinOrderSize,
|
||||
MaxOrderSize: pairs[x].MaxOrderSize,
|
||||
MinSizeIncrement: pairs[x].MinSizeIncrement,
|
||||
}
|
||||
orderSizeLimitMap.Store(pairs[x].Symbol, tempValues)
|
||||
}
|
||||
|
||||
pairs, err = b.GetMarketSummary(ctx, "", false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for x := range pairs {
|
||||
tempValues := OrderSizeLimit{
|
||||
MinOrderSize: pairs[x].MinOrderSize,
|
||||
MaxOrderSize: pairs[x].MaxOrderSize,
|
||||
MinSizeIncrement: pairs[x].MinSizeIncrement,
|
||||
}
|
||||
orderSizeLimitMap.Store(pairs[x].Symbol, tempValues)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// OrderSizeLimits looks up currency pair in orderSizeLimitMap and returns OrderSizeLimit
|
||||
func OrderSizeLimits(pair string) (limits OrderSizeLimit, found bool) {
|
||||
resp, ok := orderSizeLimitMap.Load(pair)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
val, ok := resp.(OrderSizeLimit)
|
||||
return val, ok
|
||||
}
|
||||
|
||||
// GetServerTime returns the current exchange server time.
|
||||
func (b *BTSE) GetServerTime(ctx context.Context, _ asset.Item) (time.Time, error) {
|
||||
st, err := b.GetCurrentServerTime(ctx)
|
||||
@@ -1125,6 +1043,95 @@ func (b *BTSE) GetServerTime(ctx context.Context, _ asset.Item) (time.Time, erro
|
||||
return st.ISO, nil
|
||||
}
|
||||
|
||||
// ExponentPairs returns a map of symbol names which have a Exponent equivalent
|
||||
// e.g. PIT-USD will be returned if M_PIT-USD exists, and SATS-USD if K_SATS-USD exists
|
||||
func (m *MarketSummary) ExponentPairs() (map[string]bool, error) {
|
||||
pairs := map[string]bool{}
|
||||
var errs error
|
||||
for _, s := range *m {
|
||||
if s.Active && s.HasLiquidity() {
|
||||
if symbol, err := s.StripExponent(); err != nil {
|
||||
errs = common.AppendError(errs, err)
|
||||
} else if symbol != "" {
|
||||
pairs[symbol] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return pairs, errs
|
||||
}
|
||||
|
||||
// StripExponent returns the symbol without a exponent prefix; e.g. B_, M_, K_
|
||||
// Returns an empty string if no exponent prefix is found
|
||||
// Errors if there's too many underscores, or if the exponent is not recognised
|
||||
func (m *MarketPair) StripExponent() (string, error) {
|
||||
parts := strings.Split(m.Symbol, "_")
|
||||
switch len(parts) {
|
||||
case 1:
|
||||
return "", nil
|
||||
case 2:
|
||||
switch parts[0] {
|
||||
case "B", "M", "K":
|
||||
return parts[1], nil
|
||||
}
|
||||
}
|
||||
return "", errInvalidPairSymbol
|
||||
}
|
||||
|
||||
// Pair returns the currency Pair for a MarketPair
|
||||
func (m *MarketPair) Pair() (currency.Pair, error) {
|
||||
baseCurr := m.Base
|
||||
var quoteCurr string
|
||||
if m.Futures {
|
||||
s := strings.Split(m.Symbol, m.Base) // e.g. RUNEPFC for RUNE-USD futures pair
|
||||
if len(s) <= 1 {
|
||||
return currency.EMPTYPAIR, errInvalidPairSymbol
|
||||
}
|
||||
quoteCurr = s[1]
|
||||
} else {
|
||||
s := strings.Split(m.Symbol, currency.DashDelimiter)
|
||||
if len(s) != 2 {
|
||||
return currency.EMPTYPAIR, errInvalidPairSymbol
|
||||
}
|
||||
baseCurr = s[0]
|
||||
quoteCurr = s[1]
|
||||
}
|
||||
return currency.NewPairFromStrings(baseCurr, quoteCurr)
|
||||
}
|
||||
|
||||
// GetMarketSummary returns filtered market pair details; Specifically:
|
||||
// - Pairs which aren't active are removed
|
||||
// - Pairs which don't have liquidity are removed
|
||||
// - OTC pairs only traded on web UI are removed
|
||||
// - Pairs with an exponent counterpart pair are removed
|
||||
// BTSE lists M_ symbols for very small pairs, in millions. For those listings, we want to take the M_ listing in preference
|
||||
// to the native listing, since they're often going to appear as locked markets due to size (bid == ask, e.g. 0.0000000003)
|
||||
func (b *BTSE) GetMarketSummary(ctx context.Context, symbol string, spot bool) (MarketSummary, error) {
|
||||
m, err := b.GetRawMarketSummary(ctx, symbol, spot)
|
||||
if err != nil {
|
||||
return m, err
|
||||
}
|
||||
ePairs, err := m.ExponentPairs()
|
||||
if err != nil {
|
||||
return m, err
|
||||
}
|
||||
filtered := make(MarketSummary, 0, len(m))
|
||||
for _, l := range m {
|
||||
if !l.Active || !l.HasLiquidity() || (spot && !l.IsMarketOpenToSpot) { // Skip OTC assets only tradable on web UI
|
||||
continue
|
||||
}
|
||||
if ePairs[l.Symbol] { // Skip pair with an exponent sibling
|
||||
continue
|
||||
}
|
||||
if !spot {
|
||||
// BTSE API for futures does not return futures field at all, and the docs show it coming back as false
|
||||
// Much easier for our data flow if we can trust this field
|
||||
l.Futures = true
|
||||
}
|
||||
filtered = append(filtered, l)
|
||||
}
|
||||
return filtered, nil
|
||||
}
|
||||
|
||||
// GetFuturesContractDetails returns details about futures contracts
|
||||
func (b *BTSE) GetFuturesContractDetails(ctx context.Context, item asset.Item) ([]futures.Contract, error) {
|
||||
if !item.IsFutures() {
|
||||
@@ -1265,8 +1272,33 @@ func (b *BTSE) IsPerpetualFutureCurrency(a asset.Item, p currency.Pair) (bool, e
|
||||
}
|
||||
|
||||
// UpdateOrderExecutionLimits updates order execution limits
|
||||
func (b *BTSE) UpdateOrderExecutionLimits(_ context.Context, _ asset.Item) error {
|
||||
return common.ErrNotYetImplemented
|
||||
func (b *BTSE) UpdateOrderExecutionLimits(ctx context.Context, a asset.Item) error {
|
||||
summary, err := b.GetMarketSummary(ctx, "", a == asset.Spot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var errs error
|
||||
limits := make([]order.MinMaxLevel, 0, len(summary))
|
||||
for _, marketInfo := range summary {
|
||||
p, err := marketInfo.Pair() //nolint:govet // Deliberately shadow err
|
||||
if err != nil {
|
||||
errs = common.AppendError(err, fmt.Errorf("%s: %w", p, err))
|
||||
continue
|
||||
}
|
||||
limits = append(limits, order.MinMaxLevel{
|
||||
Pair: p,
|
||||
Asset: a,
|
||||
MinimumBaseAmount: marketInfo.MinOrderSize,
|
||||
MaximumBaseAmount: marketInfo.MaxOrderSize,
|
||||
AmountStepIncrementSize: marketInfo.MinSizeIncrement,
|
||||
MinPrice: marketInfo.MinValidPrice,
|
||||
PriceStepIncrementSize: marketInfo.MinPriceIncrement,
|
||||
})
|
||||
}
|
||||
if err = b.LoadLimits(limits); err != nil {
|
||||
errs = common.AppendError(errs, err)
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
// GetOpenInterest returns the open interest rate for a given asset pair
|
||||
|
||||
Reference in New Issue
Block a user