Merge pull request #1593 from c9s/c9s/xalign-add-test-cases

FIX: [xalign] add more complex test case for xalign strategy
This commit is contained in:
c9s 2024-03-19 16:07:57 +08:00 committed by GitHub
commit d58461d1cf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 245 additions and 6 deletions

View File

@ -659,6 +659,10 @@ func (session *ExchangeSession) Markets() types.MarketMap {
return session.markets return session.markets
} }
func (session *ExchangeSession) SetMarkets(markets types.MarketMap) {
session.markets = markets
}
func (session *ExchangeSession) OrderStore(symbol string) (store *core.OrderStore, ok bool) { func (session *ExchangeSession) OrderStore(symbol string) (store *core.OrderStore, ok bool) {
store, ok = session.orderStores[symbol] store, ok = session.orderStores[symbol]
return store, ok return store, ok

View File

@ -138,15 +138,16 @@ func (s *Strategy) selectSessionForCurrency(
} }
// check both fromQuoteCurrency/currency and currency/fromQuoteCurrency // check both fromQuoteCurrency/currency and currency/fromQuoteCurrency
reversed := false
baseCurrency := currency baseCurrency := currency
quoteCurrency := fromQuoteCurrency quoteCurrency := fromQuoteCurrency
symbol := currency + fromQuoteCurrency symbol := currency + quoteCurrency
market, ok := session.Market(symbol) market, ok := session.Market(symbol)
if !ok { if !ok {
// for TWD in USDT/TWD market, buy TWD means sell USDT // for TWD in USDT/TWD market, buy TWD means sell USDT
baseCurrency = fromQuoteCurrency baseCurrency = fromQuoteCurrency
quoteCurrency = currency quoteCurrency = currency
symbol = fromQuoteCurrency + currency symbol = baseCurrency + currency
market, ok = session.Market(symbol) market, ok = session.Market(symbol)
if !ok { if !ok {
continue continue
@ -154,6 +155,7 @@ func (s *Strategy) selectSessionForCurrency(
// reverse side // reverse side
side = side.Reverse() side = side.Reverse()
reversed = true
} }
ticker, err := session.Exchange.QueryTicker(ctx, symbol) ticker, err := session.Exchange.QueryTicker(ctx, symbol)
@ -169,9 +171,16 @@ func (s *Strategy) selectSessionForCurrency(
q := changeQuantity.Abs() q := changeQuantity.Abs()
// a fast filtering // a fast filtering
if q.Compare(market.MinQuantity) < 0 { if reversed {
log.Debugf("skip dust quantity: %f", q.Float64()) if q.Compare(market.MinNotional) < 0 {
continue log.Debugf("skip dust notional: %f", q.Float64())
continue
}
} else {
if q.Compare(market.MinQuantity) < 0 {
log.Debugf("skip dust quantity: %f", q.Float64())
continue
}
} }
log.Infof("%s changeQuantity: %f ticker: %+v market: %+v", symbol, changeQuantity.Float64(), ticker, market) log.Infof("%s changeQuantity: %f ticker: %+v market: %+v", symbol, changeQuantity.Float64(), ticker, market)
@ -193,7 +202,13 @@ func (s *Strategy) selectSessionForCurrency(
continue continue
} }
requiredQuoteAmount := q.Mul(price) requiredQuoteAmount := fixedpoint.Zero
if reversed {
requiredQuoteAmount = q
} else {
requiredQuoteAmount = q.Mul(price)
}
requiredQuoteAmount = requiredQuoteAmount.Round(market.PricePrecision, fixedpoint.Up) requiredQuoteAmount = requiredQuoteAmount.Round(market.PricePrecision, fixedpoint.Up)
if requiredQuoteAmount.Compare(quoteBalance.Available) > 0 { if requiredQuoteAmount.Compare(quoteBalance.Available) > 0 {
log.Warnf("required quote amount %f > quote balance %v, skip", requiredQuoteAmount.Float64(), quoteBalance) log.Warnf("required quote amount %f > quote balance %v, skip", requiredQuoteAmount.Float64(), quoteBalance)
@ -243,6 +258,10 @@ func (s *Strategy) selectSessionForCurrency(
price = ticker.Sell price = ticker.Sell
} }
if reversed {
q = q.Div(price)
}
baseBalance, ok := session.Account.Balance(baseCurrency) baseBalance, ok := session.Account.Balance(baseCurrency)
if !ok { if !ok {
continue continue

View File

@ -0,0 +1,216 @@
//go:build !dnum
package xalign
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"
"github.com/c9s/bbgo/pkg/bbgo"
"github.com/c9s/bbgo/pkg/fixedpoint"
. "github.com/c9s/bbgo/pkg/testing/testhelper"
"github.com/c9s/bbgo/pkg/types"
"github.com/c9s/bbgo/pkg/types/mocks"
)
// cat ~/.bbgo/cache/max-markets.json | jq '.[] | select(.symbol == "USDTTWD")'
func getTestMarkets() types.MarketMap {
return map[string]types.Market{
"ETHBTC": {
Exchange: types.ExchangeMax,
Symbol: "ETHBTC",
LocalSymbol: "ETHBTC",
PricePrecision: 6,
VolumePrecision: 4,
BaseCurrency: "ETH",
QuoteCurrency: "BTC",
MinNotional: Number(0.00030000),
MinAmount: Number(0.00030000),
MinQuantity: Number(0.00460000),
StepSize: Number(0.00010000),
TickSize: Number(0.00000100),
},
"BTCUSDT": {
Exchange: types.ExchangeMax,
Symbol: "BTCUSDT",
LocalSymbol: "BTCUSDT",
PricePrecision: 2,
VolumePrecision: 6,
BaseCurrency: "BTC",
QuoteCurrency: "USDT",
MinNotional: Number(8.00000000),
MinAmount: Number(8.00000000),
MinQuantity: Number(0.00030000),
StepSize: Number(0.00000100),
TickSize: Number(0.01000000),
},
"BTCTWD": {
Exchange: types.ExchangeMax,
Symbol: "BTCTWD",
LocalSymbol: "BTCTWD",
PricePrecision: 1,
VolumePrecision: 8,
BaseCurrency: "BTC",
QuoteCurrency: "TWD",
MinNotional: Number(250.00000000),
MinAmount: Number(250.00000000),
MinQuantity: Number(0.00030000),
StepSize: Number(0.00000001),
TickSize: Number(0.01000000),
},
"ETHUSDT": {
Exchange: types.ExchangeMax,
Symbol: "ETHUSDT",
LocalSymbol: "ETHUSDT",
PricePrecision: 2,
VolumePrecision: 6,
BaseCurrency: "ETH",
QuoteCurrency: "USDT",
MinNotional: Number(8.00000000),
MinAmount: Number(8.00000000),
MinQuantity: Number(0.00460000),
StepSize: Number(0.00001000),
TickSize: Number(0.01000000),
},
"ETHTWD": {
Exchange: types.ExchangeMax,
Symbol: "ETHTWD",
LocalSymbol: "ETHTWD",
PricePrecision: 1,
VolumePrecision: 6,
BaseCurrency: "ETH",
QuoteCurrency: "TWD",
MinNotional: Number(250.00000000),
MinAmount: Number(250.00000000),
MinQuantity: Number(0.00460000),
StepSize: Number(0.00000100),
TickSize: Number(0.10000000),
},
"USDTTWD": {
Exchange: types.ExchangeMax,
Symbol: "USDTTWD",
LocalSymbol: "USDTTWD",
PricePrecision: 3,
VolumePrecision: 2,
BaseCurrency: "USDT",
QuoteCurrency: "TWD",
MinNotional: Number(250.00000000),
MinAmount: Number(250.00000000),
MinQuantity: Number(8.00000000),
StepSize: Number(0.01000000),
TickSize: Number(0.00100000),
},
}
}
func TestStrategy(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
ctx := context.Background()
s := &Strategy{
ExpectedBalances: map[string]fixedpoint.Value{
"TWD": Number(10_000),
},
PreferredQuoteCurrencies: &QuoteCurrencyPreference{
Buy: []string{"TWD", "USDT"},
Sell: []string{"USDT"},
},
PreferredSessions: []string{"max"},
UseTakerOrder: true,
}
testMarkets := getTestMarkets()
t.Run("buy TWD", func(t *testing.T) {
mockEx := mocks.NewMockExchange(mockCtrl)
mockEx.EXPECT().QueryTicker(ctx, "USDTTWD").Return(&types.Ticker{
Buy: Number(32.0),
Sell: Number(33.0),
}, nil)
account := types.NewAccount()
account.AddBalance("TWD", Number(20_000))
account.AddBalance("USDT", Number(80_000))
session := &bbgo.ExchangeSession{
Exchange: mockEx,
Account: account,
}
session.SetMarkets(testMarkets)
sessions := map[string]*bbgo.ExchangeSession{}
sessions["max"] = session
_, submitOrder := s.selectSessionForCurrency(ctx, sessions, "TWD", Number(70_000))
assert.NotNil(t, submitOrder)
assert.Equal(t, types.SideTypeSell, submitOrder.Side)
assert.Equal(t, Number(32).String(), submitOrder.Price.String())
assert.Equal(t, "2187.5", submitOrder.Quantity.String(), "70_000 / 32 best bid = 2187.5")
})
t.Run("sell TWD", func(t *testing.T) {
mockEx := mocks.NewMockExchange(mockCtrl)
mockEx.EXPECT().QueryTicker(ctx, "USDTTWD").Return(&types.Ticker{
Buy: Number(32.0),
Sell: Number(33.0),
}, nil)
account := types.NewAccount()
account.AddBalance("TWD", Number(20_000))
account.AddBalance("USDT", Number(80_000))
session := &bbgo.ExchangeSession{
Exchange: mockEx,
Account: account,
}
session.SetMarkets(testMarkets)
sessions := map[string]*bbgo.ExchangeSession{}
sessions["max"] = session
_, submitOrder := s.selectSessionForCurrency(ctx, sessions, "TWD", Number(-10_000))
assert.NotNil(t, submitOrder)
assert.Equal(t, types.SideTypeBuy, submitOrder.Side)
assert.Equal(t, Number(33).String(), submitOrder.Price.String())
assert.Equal(t, "303.03", submitOrder.Quantity.String(), "10_000 / 33 best ask = 303.0303030303")
})
t.Run("buy BTC with USDT instead of TWD", func(t *testing.T) {
mockEx := mocks.NewMockExchange(mockCtrl)
mockEx.EXPECT().QueryTicker(ctx, "BTCTWD").Return(&types.Ticker{
Sell: Number(36000.0 * 32),
Buy: Number(35000.0 * 31),
}, nil)
mockEx.EXPECT().QueryTicker(ctx, "BTCUSDT").Return(&types.Ticker{
Sell: Number(36000.0),
Buy: Number(35000.0),
}, nil)
account := types.NewAccount()
account.AddBalance("BTC", Number(0.955))
account.AddBalance("TWD", Number(60_000))
account.AddBalance("USDT", Number(80_000))
// 36000.0 * 32 * 0.045
session := &bbgo.ExchangeSession{
Exchange: mockEx,
Account: account,
}
session.SetMarkets(testMarkets)
sessions := map[string]*bbgo.ExchangeSession{}
sessions["max"] = session
_, submitOrder := s.selectSessionForCurrency(ctx, sessions, "BTC", Number(0.045))
assert.NotNil(t, submitOrder)
assert.Equal(t, types.SideTypeBuy, submitOrder.Side)
assert.Equal(t, "36000", submitOrder.Price.String())
assert.Equal(t, "0.045", submitOrder.Quantity.String())
})
}