867 lines
27 KiB
Go
867 lines
27 KiB
Go
|
package bybit
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"strconv"
|
||
|
"testing"
|
||
|
"time"
|
||
|
|
||
|
"git.qtrade.icu/lychiyu/qbtrade/pkg/exchange/bybit/bybitapi"
|
||
|
v3 "git.qtrade.icu/lychiyu/qbtrade/pkg/exchange/bybit/bybitapi/v3"
|
||
|
"git.qtrade.icu/lychiyu/qbtrade/pkg/fixedpoint"
|
||
|
"git.qtrade.icu/lychiyu/qbtrade/pkg/types"
|
||
|
"github.com/stretchr/testify/assert"
|
||
|
)
|
||
|
|
||
|
func TestToGlobalMarket(t *testing.T) {
|
||
|
// sample:
|
||
|
//{
|
||
|
// "Symbol": "BTCUSDT",
|
||
|
// "BaseCoin": "BTC",
|
||
|
// "QuoteCoin": "USDT",
|
||
|
// "Innovation": 0,
|
||
|
// "Status": "Trading",
|
||
|
// "MarginTrading": "both",
|
||
|
// "LotSizeFilter": {
|
||
|
// "BasePrecision": 0.000001,
|
||
|
// "QuotePrecision": 0.00000001,
|
||
|
// "MinOrderQty": 0.000048,
|
||
|
// "MaxOrderQty": 71.73956243,
|
||
|
// "MinOrderAmt": 1,
|
||
|
// "MaxOrderAmt": 2000000
|
||
|
// },
|
||
|
// "PriceFilter": {
|
||
|
// "TickSize": 0.01
|
||
|
// }
|
||
|
//}
|
||
|
inst := bybitapi.Instrument{
|
||
|
Symbol: "BTCUSDT",
|
||
|
BaseCoin: "BTC",
|
||
|
QuoteCoin: "USDT",
|
||
|
Innovation: "0",
|
||
|
Status: bybitapi.StatusTrading,
|
||
|
MarginTrading: "both",
|
||
|
LotSizeFilter: struct {
|
||
|
BasePrecision fixedpoint.Value `json:"basePrecision"`
|
||
|
QuotePrecision fixedpoint.Value `json:"quotePrecision"`
|
||
|
MinOrderQty fixedpoint.Value `json:"minOrderQty"`
|
||
|
MaxOrderQty fixedpoint.Value `json:"maxOrderQty"`
|
||
|
MinOrderAmt fixedpoint.Value `json:"minOrderAmt"`
|
||
|
MaxOrderAmt fixedpoint.Value `json:"maxOrderAmt"`
|
||
|
}{
|
||
|
BasePrecision: fixedpoint.NewFromFloat(0.000001),
|
||
|
QuotePrecision: fixedpoint.NewFromFloat(0.00000001),
|
||
|
MinOrderQty: fixedpoint.NewFromFloat(0.000048),
|
||
|
MaxOrderQty: fixedpoint.NewFromFloat(71.73956243),
|
||
|
MinOrderAmt: fixedpoint.NewFromInt(1),
|
||
|
MaxOrderAmt: fixedpoint.NewFromInt(2000000),
|
||
|
},
|
||
|
PriceFilter: struct {
|
||
|
TickSize fixedpoint.Value `json:"tickSize"`
|
||
|
}{
|
||
|
TickSize: fixedpoint.NewFromFloat(0.01),
|
||
|
},
|
||
|
}
|
||
|
|
||
|
exp := types.Market{
|
||
|
Symbol: inst.Symbol,
|
||
|
LocalSymbol: inst.Symbol,
|
||
|
PricePrecision: 8,
|
||
|
VolumePrecision: 6,
|
||
|
QuoteCurrency: inst.QuoteCoin,
|
||
|
BaseCurrency: inst.BaseCoin,
|
||
|
MinNotional: inst.LotSizeFilter.MinOrderAmt,
|
||
|
MinAmount: inst.LotSizeFilter.MinOrderAmt,
|
||
|
MinQuantity: inst.LotSizeFilter.MinOrderQty,
|
||
|
MaxQuantity: inst.LotSizeFilter.MaxOrderQty,
|
||
|
StepSize: inst.LotSizeFilter.BasePrecision,
|
||
|
MinPrice: inst.LotSizeFilter.MinOrderAmt,
|
||
|
MaxPrice: inst.LotSizeFilter.MaxOrderAmt,
|
||
|
TickSize: inst.PriceFilter.TickSize,
|
||
|
}
|
||
|
|
||
|
assert.Equal(t, toGlobalMarket(inst), exp)
|
||
|
}
|
||
|
|
||
|
func TestToGlobalTicker(t *testing.T) {
|
||
|
// sample
|
||
|
//{
|
||
|
// "symbol": "BTCUSDT",
|
||
|
// "bid1Price": "28995.98",
|
||
|
// "bid1Size": "4.741552",
|
||
|
// "ask1Price": "28995.99",
|
||
|
// "ask1Size": "0.16075",
|
||
|
// "lastPrice": "28994",
|
||
|
// "prevPrice24h": "29900",
|
||
|
// "price24hPcnt": "-0.0303",
|
||
|
// "highPrice24h": "30344.78",
|
||
|
// "lowPrice24h": "28948.87",
|
||
|
// "turnover24h": "184705500.13172874",
|
||
|
// "volume24h": "6240.807096",
|
||
|
// "usdIndexPrice": "28977.82001643"
|
||
|
//}
|
||
|
ticker := bybitapi.Ticker{
|
||
|
Symbol: "BTCUSDT",
|
||
|
Bid1Price: fixedpoint.NewFromFloat(28995.98),
|
||
|
Bid1Size: fixedpoint.NewFromFloat(4.741552),
|
||
|
Ask1Price: fixedpoint.NewFromFloat(28995.99),
|
||
|
Ask1Size: fixedpoint.NewFromFloat(0.16075),
|
||
|
LastPrice: fixedpoint.NewFromFloat(28994),
|
||
|
PrevPrice24H: fixedpoint.NewFromFloat(29900),
|
||
|
Price24HPcnt: fixedpoint.NewFromFloat(-0.0303),
|
||
|
HighPrice24H: fixedpoint.NewFromFloat(30344.78),
|
||
|
LowPrice24H: fixedpoint.NewFromFloat(28948.87),
|
||
|
Turnover24H: fixedpoint.NewFromFloat(184705500.13172874),
|
||
|
Volume24H: fixedpoint.NewFromFloat(6240.807096),
|
||
|
UsdIndexPrice: fixedpoint.NewFromFloat(28977.82001643),
|
||
|
}
|
||
|
|
||
|
timeNow := time.Now()
|
||
|
|
||
|
exp := types.Ticker{
|
||
|
Time: timeNow,
|
||
|
Volume: ticker.Volume24H,
|
||
|
Last: ticker.LastPrice,
|
||
|
Open: ticker.PrevPrice24H,
|
||
|
High: ticker.HighPrice24H,
|
||
|
Low: ticker.LowPrice24H,
|
||
|
Buy: ticker.Bid1Price,
|
||
|
Sell: ticker.Ask1Price,
|
||
|
}
|
||
|
|
||
|
assert.Equal(t, toGlobalTicker(ticker, timeNow), exp)
|
||
|
}
|
||
|
|
||
|
func Test_processMarketBuyQuantity(t *testing.T) {
|
||
|
t.Run("websocket event", func(t *testing.T) {
|
||
|
t.Run("Market/Buy/OrderStatusPartiallyFilled", func(t *testing.T) {
|
||
|
o := bybitapi.Order{
|
||
|
Qty: fixedpoint.NewFromFloat(5),
|
||
|
OrderType: bybitapi.OrderTypeMarket,
|
||
|
Side: bybitapi.SideBuy,
|
||
|
CumExecValue: fixedpoint.NewFromFloat(200),
|
||
|
CumExecQty: fixedpoint.NewFromFloat(2),
|
||
|
OrderStatus: bybitapi.OrderStatusPartiallyFilled,
|
||
|
}
|
||
|
res, err := processMarketBuyQuantity(o)
|
||
|
assert.NoError(t, err)
|
||
|
assert.Equal(t, o.Qty.Div(o.CumExecValue.Div(o.CumExecQty)), res)
|
||
|
})
|
||
|
|
||
|
t.Run("Market/Buy/OrderStatusPartiallyFilledCanceled", func(t *testing.T) {
|
||
|
o := bybitapi.Order{
|
||
|
Qty: fixedpoint.NewFromFloat(5),
|
||
|
OrderType: bybitapi.OrderTypeMarket,
|
||
|
Side: bybitapi.SideBuy,
|
||
|
CumExecValue: fixedpoint.NewFromFloat(200),
|
||
|
CumExecQty: fixedpoint.NewFromFloat(2),
|
||
|
OrderStatus: bybitapi.OrderStatusPartiallyFilled,
|
||
|
}
|
||
|
res, err := processMarketBuyQuantity(o)
|
||
|
assert.NoError(t, err)
|
||
|
assert.Equal(t, o.Qty.Div(o.CumExecValue.Div(o.CumExecQty)), res)
|
||
|
})
|
||
|
|
||
|
t.Run("Market/Buy/OrderStatusFilled", func(t *testing.T) {
|
||
|
o := bybitapi.Order{
|
||
|
Qty: fixedpoint.NewFromFloat(5),
|
||
|
OrderType: bybitapi.OrderTypeMarket,
|
||
|
Side: bybitapi.SideBuy,
|
||
|
CumExecValue: fixedpoint.NewFromFloat(200),
|
||
|
CumExecQty: fixedpoint.NewFromFloat(2),
|
||
|
OrderStatus: bybitapi.OrderStatusFilled,
|
||
|
}
|
||
|
res, err := processMarketBuyQuantity(o)
|
||
|
assert.NoError(t, err)
|
||
|
assert.Equal(t, o.CumExecQty, res)
|
||
|
})
|
||
|
|
||
|
t.Run("Market/Buy/OrderStatusCreated", func(t *testing.T) {
|
||
|
o := bybitapi.Order{
|
||
|
Qty: fixedpoint.NewFromFloat(5),
|
||
|
OrderType: bybitapi.OrderTypeMarket,
|
||
|
Side: bybitapi.SideBuy,
|
||
|
CumExecValue: fixedpoint.NewFromFloat(200),
|
||
|
CumExecQty: fixedpoint.NewFromFloat(2),
|
||
|
OrderStatus: bybitapi.OrderStatusCreated,
|
||
|
}
|
||
|
res, err := processMarketBuyQuantity(o)
|
||
|
assert.NoError(t, err)
|
||
|
assert.Equal(t, fixedpoint.Zero, res)
|
||
|
})
|
||
|
|
||
|
t.Run("Market/Buy/OrderStatusNew", func(t *testing.T) {
|
||
|
o := bybitapi.Order{
|
||
|
Qty: fixedpoint.NewFromFloat(5),
|
||
|
OrderType: bybitapi.OrderTypeMarket,
|
||
|
Side: bybitapi.SideBuy,
|
||
|
CumExecValue: fixedpoint.NewFromFloat(200),
|
||
|
CumExecQty: fixedpoint.NewFromFloat(2),
|
||
|
OrderStatus: bybitapi.OrderStatusNew,
|
||
|
}
|
||
|
res, err := processMarketBuyQuantity(o)
|
||
|
assert.NoError(t, err)
|
||
|
assert.Equal(t, fixedpoint.Zero, res)
|
||
|
})
|
||
|
|
||
|
t.Run("Market/Buy/OrderStatusRejected", func(t *testing.T) {
|
||
|
o := bybitapi.Order{
|
||
|
Qty: fixedpoint.NewFromFloat(5),
|
||
|
OrderType: bybitapi.OrderTypeMarket,
|
||
|
Side: bybitapi.SideBuy,
|
||
|
CumExecValue: fixedpoint.NewFromFloat(200),
|
||
|
CumExecQty: fixedpoint.NewFromFloat(2),
|
||
|
OrderStatus: bybitapi.OrderStatusRejected,
|
||
|
}
|
||
|
res, err := processMarketBuyQuantity(o)
|
||
|
assert.NoError(t, err)
|
||
|
assert.Equal(t, fixedpoint.Zero, res)
|
||
|
})
|
||
|
|
||
|
t.Run("Market/Buy/OrderStatusCanceled", func(t *testing.T) {
|
||
|
o := bybitapi.Order{
|
||
|
Qty: fixedpoint.NewFromFloat(5),
|
||
|
OrderType: bybitapi.OrderTypeMarket,
|
||
|
Side: bybitapi.SideBuy,
|
||
|
CumExecValue: fixedpoint.NewFromFloat(200),
|
||
|
CumExecQty: fixedpoint.NewFromFloat(2),
|
||
|
OrderStatus: bybitapi.OrderStatusCancelled,
|
||
|
}
|
||
|
res, err := processMarketBuyQuantity(o)
|
||
|
assert.NoError(t, err)
|
||
|
assert.Equal(t, o.Qty, res)
|
||
|
})
|
||
|
|
||
|
t.Run("Market/Buy/Unexpected status", func(t *testing.T) {
|
||
|
o := bybitapi.Order{
|
||
|
Qty: fixedpoint.NewFromFloat(5),
|
||
|
OrderType: bybitapi.OrderTypeMarket,
|
||
|
Side: bybitapi.SideBuy,
|
||
|
CumExecValue: fixedpoint.NewFromFloat(200),
|
||
|
CumExecQty: fixedpoint.NewFromFloat(2),
|
||
|
OrderStatus: bybitapi.OrderStatus("unexpected"),
|
||
|
}
|
||
|
res, err := processMarketBuyQuantity(o)
|
||
|
assert.Error(t, err)
|
||
|
assert.Equal(t, fmt.Errorf("unexpected order status: %s", o.OrderStatus), err)
|
||
|
assert.Equal(t, fixedpoint.Zero, res)
|
||
|
})
|
||
|
|
||
|
t.Run("Market/Buy/CumExecQty zero", func(t *testing.T) {
|
||
|
o := bybitapi.Order{
|
||
|
Qty: fixedpoint.NewFromFloat(5),
|
||
|
OrderType: bybitapi.OrderTypeMarket,
|
||
|
Side: bybitapi.SideBuy,
|
||
|
CumExecValue: fixedpoint.NewFromFloat(200),
|
||
|
CumExecQty: fixedpoint.Zero,
|
||
|
OrderStatus: bybitapi.OrderStatusPartiallyFilled,
|
||
|
}
|
||
|
res, err := processMarketBuyQuantity(o)
|
||
|
assert.Error(t, err)
|
||
|
assert.Equal(t, fmt.Errorf("CumExecQty shouldn't be zero"), err)
|
||
|
assert.Equal(t, fixedpoint.Zero, res)
|
||
|
})
|
||
|
|
||
|
t.Run("Market/Sell", func(t *testing.T) {
|
||
|
o := bybitapi.Order{
|
||
|
Qty: fixedpoint.NewFromFloat(5.55),
|
||
|
OrderType: bybitapi.OrderTypeMarket,
|
||
|
Side: bybitapi.SideSell,
|
||
|
}
|
||
|
res, err := processMarketBuyQuantity(o)
|
||
|
assert.NoError(t, err)
|
||
|
assert.Equal(t, o.Qty, res)
|
||
|
})
|
||
|
|
||
|
t.Run("Limit/Buy", func(t *testing.T) {
|
||
|
o := bybitapi.Order{
|
||
|
Qty: fixedpoint.NewFromFloat(5.55),
|
||
|
OrderType: bybitapi.OrderTypeLimit,
|
||
|
Side: bybitapi.SideBuy,
|
||
|
}
|
||
|
res, err := processMarketBuyQuantity(o)
|
||
|
assert.NoError(t, err)
|
||
|
assert.Equal(t, o.Qty, res)
|
||
|
})
|
||
|
|
||
|
t.Run("Limit/Sell", func(t *testing.T) {
|
||
|
o := bybitapi.Order{
|
||
|
Qty: fixedpoint.NewFromFloat(5.55),
|
||
|
OrderType: bybitapi.OrderTypeLimit,
|
||
|
Side: bybitapi.SideSell,
|
||
|
}
|
||
|
res, err := processMarketBuyQuantity(o)
|
||
|
assert.NoError(t, err)
|
||
|
assert.Equal(t, o.Qty, res)
|
||
|
})
|
||
|
})
|
||
|
|
||
|
t.Run("Restful API", func(t *testing.T) {
|
||
|
t.Run("Market/Buy/OrderStatusPartiallyFilled", func(t *testing.T) {
|
||
|
o := bybitapi.Order{
|
||
|
Qty: fixedpoint.NewFromFloat(200),
|
||
|
OrderType: bybitapi.OrderTypeMarket,
|
||
|
Side: bybitapi.SideBuy,
|
||
|
AvgPrice: fixedpoint.NewFromFloat(25000),
|
||
|
OrderStatus: bybitapi.OrderStatusPartiallyFilled,
|
||
|
}
|
||
|
res, err := processMarketBuyQuantity(o)
|
||
|
assert.NoError(t, err)
|
||
|
assert.Equal(t, o.Qty.Div(o.AvgPrice), res)
|
||
|
})
|
||
|
|
||
|
t.Run("Market/Buy/OrderStatusPartiallyFilledCanceled", func(t *testing.T) {
|
||
|
o := bybitapi.Order{
|
||
|
Qty: fixedpoint.NewFromFloat(200),
|
||
|
OrderType: bybitapi.OrderTypeMarket,
|
||
|
Side: bybitapi.SideBuy,
|
||
|
AvgPrice: fixedpoint.NewFromFloat(25000),
|
||
|
OrderStatus: bybitapi.OrderStatusPartiallyFilledCanceled,
|
||
|
CumExecQty: fixedpoint.NewFromFloat(0.002),
|
||
|
}
|
||
|
res, err := processMarketBuyQuantity(o)
|
||
|
assert.NoError(t, err)
|
||
|
assert.Equal(t, o.CumExecQty, res)
|
||
|
})
|
||
|
|
||
|
t.Run("Market/Buy/OrderStatusFilled", func(t *testing.T) {
|
||
|
o := bybitapi.Order{
|
||
|
Qty: fixedpoint.NewFromFloat(200),
|
||
|
OrderType: bybitapi.OrderTypeMarket,
|
||
|
Side: bybitapi.SideBuy,
|
||
|
AvgPrice: fixedpoint.NewFromFloat(25000),
|
||
|
OrderStatus: bybitapi.OrderStatusFilled,
|
||
|
CumExecQty: fixedpoint.NewFromFloat(0.002),
|
||
|
}
|
||
|
res, err := processMarketBuyQuantity(o)
|
||
|
assert.NoError(t, err)
|
||
|
assert.Equal(t, o.CumExecQty, res)
|
||
|
})
|
||
|
|
||
|
t.Run("Market/Buy/OrderStatusCreated", func(t *testing.T) {
|
||
|
o := bybitapi.Order{
|
||
|
Qty: fixedpoint.NewFromFloat(200),
|
||
|
OrderType: bybitapi.OrderTypeMarket,
|
||
|
Side: bybitapi.SideBuy,
|
||
|
AvgPrice: fixedpoint.NewFromFloat(25000),
|
||
|
OrderStatus: bybitapi.OrderStatusCreated,
|
||
|
CumExecQty: fixedpoint.NewFromFloat(0.002),
|
||
|
}
|
||
|
res, err := processMarketBuyQuantity(o)
|
||
|
assert.NoError(t, err)
|
||
|
assert.Equal(t, fixedpoint.Zero, res)
|
||
|
})
|
||
|
|
||
|
t.Run("Market/Buy/OrderStatusNew", func(t *testing.T) {
|
||
|
o := bybitapi.Order{
|
||
|
Qty: fixedpoint.NewFromFloat(200),
|
||
|
OrderType: bybitapi.OrderTypeMarket,
|
||
|
Side: bybitapi.SideBuy,
|
||
|
AvgPrice: fixedpoint.NewFromFloat(25000),
|
||
|
OrderStatus: bybitapi.OrderStatusNew,
|
||
|
CumExecQty: fixedpoint.NewFromFloat(0.002),
|
||
|
}
|
||
|
res, err := processMarketBuyQuantity(o)
|
||
|
assert.NoError(t, err)
|
||
|
assert.Equal(t, fixedpoint.Zero, res)
|
||
|
})
|
||
|
|
||
|
t.Run("Market/Buy/OrderStatusRejected", func(t *testing.T) {
|
||
|
o := bybitapi.Order{
|
||
|
Qty: fixedpoint.NewFromFloat(200),
|
||
|
OrderType: bybitapi.OrderTypeMarket,
|
||
|
Side: bybitapi.SideBuy,
|
||
|
AvgPrice: fixedpoint.NewFromFloat(25000),
|
||
|
OrderStatus: bybitapi.OrderStatusRejected,
|
||
|
CumExecQty: fixedpoint.NewFromFloat(0.002),
|
||
|
}
|
||
|
res, err := processMarketBuyQuantity(o)
|
||
|
assert.NoError(t, err)
|
||
|
assert.Equal(t, fixedpoint.Zero, res)
|
||
|
})
|
||
|
|
||
|
t.Run("Market/Buy/OrderStatusCanceled", func(t *testing.T) {
|
||
|
o := bybitapi.Order{
|
||
|
Qty: fixedpoint.NewFromFloat(200),
|
||
|
OrderType: bybitapi.OrderTypeMarket,
|
||
|
Side: bybitapi.SideBuy,
|
||
|
AvgPrice: fixedpoint.NewFromFloat(25000),
|
||
|
OrderStatus: bybitapi.OrderStatusCancelled,
|
||
|
CumExecQty: fixedpoint.NewFromFloat(0.002),
|
||
|
}
|
||
|
res, err := processMarketBuyQuantity(o)
|
||
|
assert.NoError(t, err)
|
||
|
assert.Equal(t, o.Qty, res)
|
||
|
})
|
||
|
|
||
|
t.Run("Market/Buy/AvgPrice zero", func(t *testing.T) {
|
||
|
o := bybitapi.Order{
|
||
|
Qty: fixedpoint.NewFromFloat(200),
|
||
|
OrderType: bybitapi.OrderTypeMarket,
|
||
|
Side: bybitapi.SideBuy,
|
||
|
AvgPrice: fixedpoint.Zero,
|
||
|
OrderStatus: bybitapi.OrderStatusPartiallyFilled,
|
||
|
CumExecQty: fixedpoint.NewFromFloat(0.002),
|
||
|
}
|
||
|
res, err := processMarketBuyQuantity(o)
|
||
|
assert.Error(t, err)
|
||
|
assert.Equal(t, fmt.Errorf("AvgPrice shouldn't be zero"), err)
|
||
|
assert.Equal(t, fixedpoint.Zero, res)
|
||
|
})
|
||
|
|
||
|
t.Run("Market/Sell", func(t *testing.T) {
|
||
|
o := bybitapi.Order{
|
||
|
Qty: fixedpoint.NewFromFloat(5.55),
|
||
|
OrderType: bybitapi.OrderTypeMarket,
|
||
|
Side: bybitapi.SideSell,
|
||
|
}
|
||
|
res, err := processMarketBuyQuantity(o)
|
||
|
assert.NoError(t, err)
|
||
|
assert.Equal(t, o.Qty, res)
|
||
|
})
|
||
|
|
||
|
t.Run("Limit/Buy", func(t *testing.T) {
|
||
|
o := bybitapi.Order{
|
||
|
Qty: fixedpoint.NewFromFloat(5.55),
|
||
|
OrderType: bybitapi.OrderTypeLimit,
|
||
|
Side: bybitapi.SideBuy,
|
||
|
}
|
||
|
res, err := processMarketBuyQuantity(o)
|
||
|
assert.NoError(t, err)
|
||
|
assert.Equal(t, o.Qty, res)
|
||
|
})
|
||
|
|
||
|
t.Run("Limit/Sell", func(t *testing.T) {
|
||
|
o := bybitapi.Order{
|
||
|
Qty: fixedpoint.NewFromFloat(5.55),
|
||
|
OrderType: bybitapi.OrderTypeLimit,
|
||
|
Side: bybitapi.SideSell,
|
||
|
}
|
||
|
res, err := processMarketBuyQuantity(o)
|
||
|
assert.NoError(t, err)
|
||
|
assert.Equal(t, o.Qty, res)
|
||
|
})
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func TestToGlobalOrder(t *testing.T) {
|
||
|
// sample: partialFilled
|
||
|
//{
|
||
|
// "OrderId": 1472539279335923200,
|
||
|
// "OrderLinkId": 1690276361150,
|
||
|
// "BlockTradeId": null,
|
||
|
// "Symbol": "DOTUSDT",
|
||
|
// "Price": 7.278,
|
||
|
// "Qty": 0.8,
|
||
|
// "Side": "Sell",
|
||
|
// "IsLeverage": 0,
|
||
|
// "PositionIdx": 0,
|
||
|
// "OrderStatus": "PartiallyFilled",
|
||
|
// "CancelType": "UNKNOWN",
|
||
|
// "RejectReason": null,
|
||
|
// "AvgPrice": 7.278,
|
||
|
// "LeavesQty": 0,
|
||
|
// "LeavesValue": 0,
|
||
|
// "CumExecQty": 0.5,
|
||
|
// "CumExecValue": 0,
|
||
|
// "CumExecFee": 0,
|
||
|
// "TimeInForce": "GTC",
|
||
|
// "OrderType": "Limit",
|
||
|
// "StopOrderType": null,
|
||
|
// "OrderIv": null,
|
||
|
// "TriggerPrice": 0,
|
||
|
// "TakeProfit": 0,
|
||
|
// "StopLoss": 0,
|
||
|
// "TpTriggerBy": null,
|
||
|
// "SlTriggerBy": null,
|
||
|
// "TriggerDirection": 0,
|
||
|
// "TriggerBy": null,
|
||
|
// "LastPriceOnCreated": null,
|
||
|
// "ReduceOnly": false,
|
||
|
// "CloseOnTrigger": false,
|
||
|
// "SmpType": "None",
|
||
|
// "SmpGroup": 0,
|
||
|
// "SmpOrderId": null,
|
||
|
// "TpslMode": null,
|
||
|
// "TpLimitPrice": null,
|
||
|
// "SlLimitPrice": null,
|
||
|
// "PlaceType": null,
|
||
|
// "CreatedTime": "2023-07-25 17:12:41.325 +0800 CST",
|
||
|
// "UpdatedTime": "2023-07-25 17:12:57.868 +0800 CST"
|
||
|
//}
|
||
|
timeNow := time.Now()
|
||
|
openOrder := bybitapi.Order{
|
||
|
OrderId: "1472539279335923200",
|
||
|
OrderLinkId: "1690276361150",
|
||
|
BlockTradeId: "",
|
||
|
Symbol: "DOTUSDT",
|
||
|
Price: fixedpoint.NewFromFloat(7.278),
|
||
|
Qty: fixedpoint.NewFromFloat(0.8),
|
||
|
Side: bybitapi.SideSell,
|
||
|
IsLeverage: "0",
|
||
|
PositionIdx: 0,
|
||
|
OrderStatus: bybitapi.OrderStatusPartiallyFilled,
|
||
|
CancelType: "UNKNOWN",
|
||
|
RejectReason: "",
|
||
|
AvgPrice: fixedpoint.NewFromFloat(7.728),
|
||
|
LeavesQty: fixedpoint.NewFromFloat(0),
|
||
|
LeavesValue: fixedpoint.NewFromFloat(0),
|
||
|
CumExecQty: fixedpoint.NewFromFloat(0.5),
|
||
|
CumExecValue: fixedpoint.NewFromFloat(0),
|
||
|
CumExecFee: fixedpoint.NewFromFloat(0),
|
||
|
TimeInForce: "GTC",
|
||
|
OrderType: bybitapi.OrderTypeLimit,
|
||
|
StopOrderType: "",
|
||
|
OrderIv: "",
|
||
|
TriggerPrice: fixedpoint.NewFromFloat(0),
|
||
|
TakeProfit: fixedpoint.NewFromFloat(0),
|
||
|
StopLoss: fixedpoint.NewFromFloat(0),
|
||
|
TpTriggerBy: "",
|
||
|
SlTriggerBy: "",
|
||
|
TriggerDirection: 0,
|
||
|
TriggerBy: "",
|
||
|
LastPriceOnCreated: "",
|
||
|
ReduceOnly: false,
|
||
|
CloseOnTrigger: false,
|
||
|
SmpType: "None",
|
||
|
SmpGroup: 0,
|
||
|
SmpOrderId: "",
|
||
|
TpslMode: "",
|
||
|
TpLimitPrice: "",
|
||
|
SlLimitPrice: "",
|
||
|
PlaceType: "",
|
||
|
CreatedTime: types.MillisecondTimestamp(timeNow),
|
||
|
UpdatedTime: types.MillisecondTimestamp(timeNow),
|
||
|
}
|
||
|
side, err := toGlobalSideType(openOrder.Side)
|
||
|
assert.NoError(t, err)
|
||
|
orderType, err := toGlobalOrderType(openOrder.OrderType)
|
||
|
assert.NoError(t, err)
|
||
|
tif, err := toGlobalTimeInForce(openOrder.TimeInForce)
|
||
|
assert.NoError(t, err)
|
||
|
status, err := toGlobalOrderStatus(openOrder.OrderStatus, openOrder.Side, openOrder.OrderType)
|
||
|
assert.NoError(t, err)
|
||
|
orderIdNum, err := strconv.ParseUint(openOrder.OrderId, 10, 64)
|
||
|
assert.NoError(t, err)
|
||
|
|
||
|
exp := types.Order{
|
||
|
SubmitOrder: types.SubmitOrder{
|
||
|
ClientOrderID: openOrder.OrderLinkId,
|
||
|
Symbol: openOrder.Symbol,
|
||
|
Side: side,
|
||
|
Type: orderType,
|
||
|
Quantity: openOrder.Qty,
|
||
|
Price: openOrder.Price,
|
||
|
TimeInForce: tif,
|
||
|
},
|
||
|
Exchange: types.ExchangeBybit,
|
||
|
OrderID: orderIdNum,
|
||
|
UUID: openOrder.OrderId,
|
||
|
Status: status,
|
||
|
ExecutedQuantity: openOrder.CumExecQty,
|
||
|
IsWorking: status == types.OrderStatusNew || status == types.OrderStatusPartiallyFilled,
|
||
|
CreationTime: types.Time(openOrder.CreatedTime),
|
||
|
UpdateTime: types.Time(openOrder.UpdatedTime),
|
||
|
IsFutures: false,
|
||
|
IsMargin: false,
|
||
|
IsIsolated: false,
|
||
|
}
|
||
|
res, err := toGlobalOrder(openOrder)
|
||
|
assert.NoError(t, err)
|
||
|
assert.Equal(t, res, &exp)
|
||
|
}
|
||
|
|
||
|
func TestToGlobalSideType(t *testing.T) {
|
||
|
res, err := toGlobalSideType(bybitapi.SideBuy)
|
||
|
assert.NoError(t, err)
|
||
|
assert.Equal(t, types.SideTypeBuy, res)
|
||
|
|
||
|
res, err = toGlobalSideType(bybitapi.SideSell)
|
||
|
assert.NoError(t, err)
|
||
|
assert.Equal(t, types.SideTypeSell, res)
|
||
|
|
||
|
res, err = toGlobalSideType("GG")
|
||
|
assert.Error(t, err)
|
||
|
}
|
||
|
|
||
|
func TestToGlobalOrderType(t *testing.T) {
|
||
|
res, err := toGlobalOrderType(bybitapi.OrderTypeMarket)
|
||
|
assert.NoError(t, err)
|
||
|
assert.Equal(t, types.OrderTypeMarket, res)
|
||
|
|
||
|
res, err = toGlobalOrderType(bybitapi.OrderTypeLimit)
|
||
|
assert.NoError(t, err)
|
||
|
assert.Equal(t, types.OrderTypeLimit, res)
|
||
|
|
||
|
res, err = toGlobalOrderType("GG")
|
||
|
assert.Error(t, err)
|
||
|
}
|
||
|
|
||
|
func TestToGlobalTimeInForce(t *testing.T) {
|
||
|
res, err := toGlobalTimeInForce(bybitapi.TimeInForceGTC)
|
||
|
assert.NoError(t, err)
|
||
|
assert.Equal(t, types.TimeInForceGTC, res)
|
||
|
|
||
|
res, err = toGlobalTimeInForce(bybitapi.TimeInForceIOC)
|
||
|
assert.NoError(t, err)
|
||
|
assert.Equal(t, types.TimeInForceIOC, res)
|
||
|
|
||
|
res, err = toGlobalTimeInForce(bybitapi.TimeInForceFOK)
|
||
|
assert.NoError(t, err)
|
||
|
assert.Equal(t, types.TimeInForceFOK, res)
|
||
|
|
||
|
res, err = toGlobalTimeInForce("GG")
|
||
|
assert.Error(t, err)
|
||
|
}
|
||
|
|
||
|
func Test_toGlobalOrderStatus(t *testing.T) {
|
||
|
t.Run("market/buy", func(t *testing.T) {
|
||
|
res, err := toGlobalOrderStatus(bybitapi.OrderStatusPartiallyFilledCanceled, bybitapi.SideBuy, bybitapi.OrderTypeMarket)
|
||
|
assert.NoError(t, err)
|
||
|
assert.Equal(t, types.OrderStatusFilled, res)
|
||
|
})
|
||
|
|
||
|
t.Run("limit/buy", func(t *testing.T) {
|
||
|
res, err := toGlobalOrderStatus(bybitapi.OrderStatusPartiallyFilledCanceled, bybitapi.SideBuy, bybitapi.OrderTypeLimit)
|
||
|
assert.NoError(t, err)
|
||
|
assert.Equal(t, types.OrderStatusCanceled, res)
|
||
|
})
|
||
|
|
||
|
t.Run("limit/sell", func(t *testing.T) {
|
||
|
res, err := toGlobalOrderStatus(bybitapi.OrderStatusPartiallyFilledCanceled, bybitapi.SideSell, bybitapi.OrderTypeLimit)
|
||
|
assert.NoError(t, err)
|
||
|
assert.Equal(t, types.OrderStatusCanceled, res)
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func Test_processOtherOrderStatus(t *testing.T) {
|
||
|
t.Run("New", func(t *testing.T) {
|
||
|
res, err := processOtherOrderStatus(bybitapi.OrderStatusNew)
|
||
|
assert.NoError(t, err)
|
||
|
assert.Equal(t, types.OrderStatusNew, res)
|
||
|
|
||
|
res, err = processOtherOrderStatus(bybitapi.OrderStatusActive)
|
||
|
assert.NoError(t, err)
|
||
|
assert.Equal(t, types.OrderStatusNew, res)
|
||
|
})
|
||
|
|
||
|
t.Run("Filled", func(t *testing.T) {
|
||
|
res, err := processOtherOrderStatus(bybitapi.OrderStatusFilled)
|
||
|
assert.NoError(t, err)
|
||
|
assert.Equal(t, types.OrderStatusFilled, res)
|
||
|
})
|
||
|
|
||
|
t.Run("PartiallyFilled", func(t *testing.T) {
|
||
|
res, err := processOtherOrderStatus(bybitapi.OrderStatusPartiallyFilled)
|
||
|
assert.NoError(t, err)
|
||
|
assert.Equal(t, types.OrderStatusPartiallyFilled, res)
|
||
|
})
|
||
|
|
||
|
t.Run("OrderStatusCanceled", func(t *testing.T) {
|
||
|
res, err := processOtherOrderStatus(bybitapi.OrderStatusCancelled)
|
||
|
assert.NoError(t, err)
|
||
|
assert.Equal(t, types.OrderStatusCanceled, res)
|
||
|
|
||
|
res, err = processOtherOrderStatus(bybitapi.OrderStatusDeactivated)
|
||
|
assert.NoError(t, err)
|
||
|
assert.Equal(t, types.OrderStatusCanceled, res)
|
||
|
})
|
||
|
|
||
|
t.Run("OrderStatusRejected", func(t *testing.T) {
|
||
|
res, err := processOtherOrderStatus(bybitapi.OrderStatusRejected)
|
||
|
assert.NoError(t, err)
|
||
|
assert.Equal(t, types.OrderStatusRejected, res)
|
||
|
})
|
||
|
|
||
|
t.Run("OrderStatusPartiallyFilledCanceled", func(t *testing.T) {
|
||
|
res, err := processOtherOrderStatus(bybitapi.OrderStatusPartiallyFilledCanceled)
|
||
|
assert.Equal(t, types.OrderStatus(bybitapi.OrderStatusPartiallyFilledCanceled), res)
|
||
|
assert.Error(t, err)
|
||
|
assert.Equal(t, fmt.Errorf("unexpected order status: %s", bybitapi.OrderStatusPartiallyFilledCanceled), err)
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func Test_toLocalOrderType(t *testing.T) {
|
||
|
orderType, err := toLocalOrderType(types.OrderTypeLimit)
|
||
|
assert.NoError(t, err)
|
||
|
assert.Equal(t, bybitapi.OrderTypeLimit, orderType)
|
||
|
|
||
|
orderType, err = toLocalOrderType(types.OrderTypeMarket)
|
||
|
assert.NoError(t, err)
|
||
|
assert.Equal(t, bybitapi.OrderTypeMarket, orderType)
|
||
|
|
||
|
orderType, err = toLocalOrderType("wrong type")
|
||
|
assert.Equal(t, fmt.Errorf("order type wrong type not supported"), err)
|
||
|
assert.Equal(t, bybitapi.OrderType(""), orderType)
|
||
|
}
|
||
|
|
||
|
func Test_toLocalSide(t *testing.T) {
|
||
|
side, err := toLocalSide(types.SideTypeSell)
|
||
|
assert.NoError(t, err)
|
||
|
assert.Equal(t, bybitapi.SideSell, side)
|
||
|
|
||
|
side, err = toLocalSide(types.SideTypeBuy)
|
||
|
assert.NoError(t, err)
|
||
|
assert.Equal(t, bybitapi.SideBuy, side)
|
||
|
|
||
|
side, err = toLocalSide("wrong side")
|
||
|
assert.Equal(t, fmt.Errorf("side type %s not supported", "wrong side"), err)
|
||
|
assert.Equal(t, bybitapi.Side(""), side)
|
||
|
}
|
||
|
|
||
|
func Test_toGlobalTrade(t *testing.T) {
|
||
|
/* sample: trade
|
||
|
{
|
||
|
"Symbol":"BTCUSDT",
|
||
|
"Id":"1474200510090276864",
|
||
|
"OrderId":"1474200270671015936",
|
||
|
"TradeId":"2100000000031181772",
|
||
|
"OrderPrice":"27628",
|
||
|
"OrderQty":"0.007959",
|
||
|
"ExecFee":"0.21989125",
|
||
|
"FeeTokenId":"USDT",
|
||
|
"CreatTime":"2023-07-28 00:13:15.457 +0800 CST",
|
||
|
"IsBuyer":"1",
|
||
|
"IsMaker":"0",
|
||
|
"MatchOrderId":"5760912963729109504",
|
||
|
"MakerRebate":"0",
|
||
|
"ExecutionTime":"2023-07-28 00:13:15.463 +0800 CST",
|
||
|
"BlockTradeId": "",
|
||
|
}
|
||
|
*/
|
||
|
timeNow := time.Now()
|
||
|
trade := v3.Trade{
|
||
|
Symbol: "DOTUSDT",
|
||
|
Id: "1474200510090276864",
|
||
|
OrderId: "1474200270671015936",
|
||
|
TradeId: "2100000000031181772",
|
||
|
OrderPrice: fixedpoint.NewFromFloat(27628),
|
||
|
OrderQty: fixedpoint.NewFromFloat(0.007959),
|
||
|
ExecFee: fixedpoint.NewFromFloat(0.21989125),
|
||
|
FeeTokenId: "USDT",
|
||
|
CreatTime: types.MillisecondTimestamp(timeNow),
|
||
|
IsBuyer: "0",
|
||
|
IsMaker: "0",
|
||
|
MatchOrderId: "5760912963729109504",
|
||
|
MakerRebate: fixedpoint.NewFromFloat(0),
|
||
|
ExecutionTime: types.MillisecondTimestamp(timeNow),
|
||
|
BlockTradeId: "",
|
||
|
}
|
||
|
|
||
|
s, err := toV3Buyer(trade.IsBuyer)
|
||
|
assert.NoError(t, err)
|
||
|
m, err := toV3Maker(trade.IsMaker)
|
||
|
assert.NoError(t, err)
|
||
|
orderIdNum, err := strconv.ParseUint(trade.OrderId, 10, 64)
|
||
|
assert.NoError(t, err)
|
||
|
tradeId, err := strconv.ParseUint(trade.TradeId, 10, 64)
|
||
|
assert.NoError(t, err)
|
||
|
|
||
|
exp := types.Trade{
|
||
|
ID: tradeId,
|
||
|
OrderID: orderIdNum,
|
||
|
Exchange: types.ExchangeBybit,
|
||
|
Price: trade.OrderPrice,
|
||
|
Quantity: trade.OrderQty,
|
||
|
QuoteQuantity: trade.OrderPrice.Mul(trade.OrderQty),
|
||
|
Symbol: trade.Symbol,
|
||
|
Side: s,
|
||
|
IsBuyer: s == types.SideTypeBuy,
|
||
|
IsMaker: m,
|
||
|
Time: types.Time(timeNow),
|
||
|
Fee: trade.ExecFee,
|
||
|
FeeCurrency: trade.FeeTokenId,
|
||
|
IsMargin: false,
|
||
|
IsFutures: false,
|
||
|
IsIsolated: false,
|
||
|
}
|
||
|
res, err := v3ToGlobalTrade(trade)
|
||
|
assert.NoError(t, err)
|
||
|
assert.Equal(t, res, &exp)
|
||
|
}
|
||
|
|
||
|
func Test_toGlobalKLines(t *testing.T) {
|
||
|
symbol := "BTCUSDT"
|
||
|
interval := types.Interval15m
|
||
|
|
||
|
resp := bybitapi.KLinesResponse{
|
||
|
Symbol: symbol,
|
||
|
List: []bybitapi.KLine{
|
||
|
/*
|
||
|
[
|
||
|
{
|
||
|
"StartTime": "2023-08-08 17:30:00 +0800 CST",
|
||
|
"OpenPrice": 29045.3,
|
||
|
"HighPrice": 29228.56,
|
||
|
"LowPrice": 29045.3,
|
||
|
"ClosePrice": 29228.56,
|
||
|
"Volume": 9.265593,
|
||
|
"TurnOver": 270447.43520753
|
||
|
},
|
||
|
{
|
||
|
"StartTime": "2023-08-08 17:15:00 +0800 CST",
|
||
|
"OpenPrice": 29167.33,
|
||
|
"HighPrice": 29229.08,
|
||
|
"LowPrice": 29000,
|
||
|
"ClosePrice": 29045.3,
|
||
|
"Volume": 9.295508,
|
||
|
"TurnOver": 270816.87513775
|
||
|
}
|
||
|
]
|
||
|
*/
|
||
|
{
|
||
|
StartTime: types.NewMillisecondTimestampFromInt(1691486100000),
|
||
|
Open: fixedpoint.NewFromFloat(29045.3),
|
||
|
High: fixedpoint.NewFromFloat(29228.56),
|
||
|
Low: fixedpoint.NewFromFloat(29045.3),
|
||
|
Close: fixedpoint.NewFromFloat(29228.56),
|
||
|
Volume: fixedpoint.NewFromFloat(9.265593),
|
||
|
TurnOver: fixedpoint.NewFromFloat(270447.43520753),
|
||
|
},
|
||
|
{
|
||
|
StartTime: types.NewMillisecondTimestampFromInt(1691487000000),
|
||
|
Open: fixedpoint.NewFromFloat(29167.33),
|
||
|
High: fixedpoint.NewFromFloat(29229.08),
|
||
|
Low: fixedpoint.NewFromFloat(29000),
|
||
|
Close: fixedpoint.NewFromFloat(29045.3),
|
||
|
Volume: fixedpoint.NewFromFloat(9.295508),
|
||
|
TurnOver: fixedpoint.NewFromFloat(270816.87513775),
|
||
|
},
|
||
|
},
|
||
|
Category: bybitapi.CategorySpot,
|
||
|
}
|
||
|
|
||
|
expKlines := []types.KLine{
|
||
|
{
|
||
|
Exchange: types.ExchangeBybit,
|
||
|
Symbol: resp.Symbol,
|
||
|
StartTime: types.Time(resp.List[0].StartTime.Time()),
|
||
|
EndTime: types.Time(resp.List[0].StartTime.Time().Add(interval.Duration() - time.Millisecond)),
|
||
|
Interval: interval,
|
||
|
Open: fixedpoint.NewFromFloat(29045.3),
|
||
|
Close: fixedpoint.NewFromFloat(29228.56),
|
||
|
High: fixedpoint.NewFromFloat(29228.56),
|
||
|
Low: fixedpoint.NewFromFloat(29045.3),
|
||
|
Volume: fixedpoint.NewFromFloat(9.265593),
|
||
|
QuoteVolume: fixedpoint.NewFromFloat(270447.43520753),
|
||
|
Closed: false,
|
||
|
},
|
||
|
{
|
||
|
Exchange: types.ExchangeBybit,
|
||
|
Symbol: resp.Symbol,
|
||
|
StartTime: types.Time(resp.List[1].StartTime.Time()),
|
||
|
EndTime: types.Time(resp.List[1].StartTime.Time().Add(interval.Duration() - time.Millisecond)),
|
||
|
Interval: interval,
|
||
|
Open: fixedpoint.NewFromFloat(29167.33),
|
||
|
Close: fixedpoint.NewFromFloat(29045.3),
|
||
|
High: fixedpoint.NewFromFloat(29229.08),
|
||
|
Low: fixedpoint.NewFromFloat(29000),
|
||
|
Volume: fixedpoint.NewFromFloat(9.295508),
|
||
|
QuoteVolume: fixedpoint.NewFromFloat(270816.87513775),
|
||
|
Closed: false,
|
||
|
},
|
||
|
}
|
||
|
|
||
|
assert.Equal(t, toGlobalKLines(symbol, interval, resp.List), expKlines)
|
||
|
}
|