ftx: support queryClosedOrders

This commit is contained in:
ycdesu 2021-03-17 21:26:25 +08:00
parent 54ca62ac5c
commit 4a5a53ea28
3 changed files with 275 additions and 5 deletions

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"net/url" "net/url"
"sort"
"time" "time"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -156,8 +157,53 @@ func (e *Exchange) QueryOpenOrders(ctx context.Context, symbol string) (orders [
return orders, nil return orders, nil
} }
// symbol, since and until are all optional. FTX can only query by order created time, not updated time.
// FTX doesn't support lastOrderID, so we will query by the time range first, and filter by the lastOrderID.
func (e *Exchange) QueryClosedOrders(ctx context.Context, symbol string, since, until time.Time, lastOrderID uint64) (orders []types.Order, err error) { func (e *Exchange) QueryClosedOrders(ctx context.Context, symbol string, since, until time.Time, lastOrderID uint64) (orders []types.Order, err error) {
panic("implement me") if since.After(until) {
return nil, fmt.Errorf("since can't be after until")
}
if lastOrderID > 0 {
logger.Warn("FTX doesn't support lastOrderID")
}
limit := 100
hasMoreData := true
s := since
var lastOrder order
for hasMoreData {
resp, err := e.newRest().OrdersHistory(ctx, symbol, s, until, limit)
if err != nil {
return nil, err
}
if !resp.Success {
return nil, fmt.Errorf("ftx returns querying orders history failure")
}
sortByCreatedASC(resp.Result)
for _, r := range resp.Result {
// There may be more than one orders at the same time, so also have to check the ID
if r.CreatedAt.Before(lastOrder.CreatedAt) || r.ID == lastOrder.ID || r.Status != "closed" {
continue
}
lastOrder = r
o, err := toGlobalOrder(r)
if err != nil {
return nil, err
}
orders = append(orders, o)
}
hasMoreData = resp.HasMoreData
// the start_time and end_time precision is second. There might be more than one orders within one second.
s = lastOrder.CreatedAt.Add(-1 * time.Second)
}
return orders, nil
}
func sortByCreatedASC(orders []order) {
sort.Slice(orders, func(i, j int) bool {
return orders[i].CreatedAt.Before(orders[j].CreatedAt)
})
} }
func (e *Exchange) CancelOrders(ctx context.Context, orders ...types.Order) error { func (e *Exchange) CancelOrders(ctx context.Context, orders ...types.Order) error {

View File

@ -7,6 +7,7 @@ import (
"net/http/httptest" "net/http/httptest"
"net/url" "net/url"
"testing" "testing"
"time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -59,3 +60,223 @@ func TestExchange_QueryAccountBalances(t *testing.T) {
assert.EqualError(t, err, "ftx returns querying balances failure") assert.EqualError(t, err, "ftx returns querying balances failure")
assert.Nil(t, resp) assert.Nil(t, resp)
} }
func TestExchange_QueryOpenOrders(t *testing.T) {
successResp := `
{
"success": true,
"result": [
{
"createdAt": "2019-03-05T09:56:55.728933+00:00",
"filledSize": 10,
"future": "XRP-PERP",
"id": 9596912,
"market": "XRP-PERP",
"price": 0.306525,
"avgFillPrice": 0.306526,
"remainingSize": 31421,
"side": "sell",
"size": 31431,
"status": "open",
"type": "limit",
"reduceOnly": false,
"ioc": false,
"postOnly": false,
"clientId": null
}
]
}
`
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, successResp)
}))
defer ts.Close()
ex := NewExchange("", "", "")
serverURL, err := url.Parse(ts.URL)
assert.NoError(t, err)
ex.restEndpoint = serverURL
resp, err := ex.QueryOpenOrders(context.Background(), "XRP-PREP")
assert.NoError(t, err)
assert.Len(t, resp, 1)
assert.Equal(t, "XRP-PERP", resp[0].Symbol)
}
func TestExchange_QueryClosedOrders(t *testing.T) {
t.Run("no closed orders", func(t *testing.T) {
successResp := `{"success": true, "result": []}`
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, successResp)
}))
defer ts.Close()
ex := NewExchange("", "", "")
serverURL, err := url.Parse(ts.URL)
assert.NoError(t, err)
ex.restEndpoint = serverURL
resp, err := ex.QueryClosedOrders(context.Background(), "BTC-PERP", time.Time{}, time.Time{}, 100)
assert.NoError(t, err)
assert.Len(t, resp, 0)
})
t.Run("one closed order", func(t *testing.T) {
successResp := `
{
"success": true,
"result": [
{
"avgFillPrice": 10135.25,
"clientId": null,
"createdAt": "2019-06-27T15:24:03.101197+00:00",
"filledSize": 0.001,
"future": "BTC-PERP",
"id": 257132591,
"ioc": false,
"market": "BTC-PERP",
"postOnly": false,
"price": 10135.25,
"reduceOnly": false,
"remainingSize": 0.0,
"side": "buy",
"size": 0.001,
"status": "closed",
"type": "limit"
}
],
"hasMoreData": false
}
`
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, successResp)
}))
defer ts.Close()
ex := NewExchange("", "", "")
serverURL, err := url.Parse(ts.URL)
assert.NoError(t, err)
ex.restEndpoint = serverURL
resp, err := ex.QueryClosedOrders(context.Background(), "BTC-PERP", time.Time{}, time.Time{}, 100)
assert.NoError(t, err)
assert.Len(t, resp, 1)
assert.Equal(t, "BTC-PERP", resp[0].Symbol)
})
t.Run("sort the order", func(t *testing.T) {
successResp := `
{
"success": true,
"result": [
{
"status": "closed",
"createdAt": "2020-09-01T15:24:03.101197+00:00",
"id": 789
},
{
"status": "closed",
"createdAt": "2019-03-27T15:24:03.101197+00:00",
"id": 123
},
{
"status": "closed",
"createdAt": "2019-06-27T15:24:03.101197+00:00",
"id": 456
},
{
"status": "new",
"createdAt": "2019-06-27T15:24:03.101197+00:00",
"id": 999
}
],
"hasMoreData": false
}
`
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, successResp)
}))
defer ts.Close()
ex := NewExchange("", "", "")
serverURL, err := url.Parse(ts.URL)
assert.NoError(t, err)
ex.restEndpoint = serverURL
resp, err := ex.QueryClosedOrders(context.Background(), "BTC-PERP", time.Time{}, time.Time{}, 100)
assert.NoError(t, err)
assert.Len(t, resp, 3)
expectedOrderID := []uint64{123, 456, 789}
for i, o := range resp {
assert.Equal(t, expectedOrderID[i], o.OrderID)
}
})
t.Run("receive duplicated orders", func(t *testing.T) {
successRespOne := `
{
"success": true,
"result": [
{
"status": "closed",
"createdAt": "2020-09-01T15:24:03.101197+00:00",
"id": 123
}
],
"hasMoreData": true
}
`
successRespTwo := `
{
"success": true,
"result": [
{
"clientId": "ignored-by-created-at",
"status": "closed",
"createdAt": "1999-09-01T15:24:03.101197+00:00",
"id": 999
},
{
"clientId": "ignored-by-duplicated-id",
"status": "closed",
"createdAt": "2020-09-02T15:24:03.101197+00:00",
"id": 123
},
{
"clientId": "ignored-duplicated-entry",
"status": "closed",
"createdAt": "2020-09-01T15:24:03.101197+00:00",
"id": 123
},
{
"status": "closed",
"createdAt": "2020-09-02T15:24:03.101197+00:00",
"id": 456
}
],
"hasMoreData": false
}
`
i := 0
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if i == 0 {
i++
fmt.Fprintln(w, successRespOne)
return
}
fmt.Fprintln(w, successRespTwo)
}))
defer ts.Close()
ex := NewExchange("", "", "")
serverURL, err := url.Parse(ts.URL)
assert.NoError(t, err)
ex.restEndpoint = serverURL
resp, err := ex.QueryClosedOrders(context.Background(), "BTC-PERP", time.Time{}, time.Time{}, 100)
assert.NoError(t, err)
assert.Len(t, resp, 2)
expectedOrderID := []uint64{123, 456}
for i, o := range resp {
assert.Equal(t, expectedOrderID[i], o.OrderID)
}
})
}

View File

@ -117,11 +117,14 @@ func (r *orderRequest) OpenOrders(ctx context.Context, market string) (ordersRes
} }
func (r *orderRequest) OrdersHistory(ctx context.Context, market string, start, end time.Time, limit int) (ordersHistoryResponse, error) { func (r *orderRequest) OrdersHistory(ctx context.Context, market string, start, end time.Time, limit int) (ordersHistoryResponse, error) {
p := map[string]interface{}{ p := make(map[string]interface{})
"market": market,
"limit": limit,
}
if limit > 0 {
p["limit"] = limit
}
if len(market) > 0 {
p["market"] = market
}
if start != (time.Time{}) { if start != (time.Time{}) {
p["start_time"] = start.UnixNano() / int64(time.Second) p["start_time"] = start.UnixNano() / int64(time.Second)
} }