mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-26 16:55:15 +00:00
ftx: support queryClosedOrders
This commit is contained in:
parent
54ca62ac5c
commit
4a5a53ea28
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user