diff --git a/pkg/exchange/ftx/convert.go b/pkg/exchange/ftx/convert.go index 59cafba13..c72dd274a 100644 --- a/pkg/exchange/ftx/convert.go +++ b/pkg/exchange/ftx/convert.go @@ -1,7 +1,71 @@ package ftx -import "strings" +import ( + "fmt" + "strings" + + "github.com/c9s/bbgo/pkg/datatype" + "github.com/c9s/bbgo/pkg/fixedpoint" + "github.com/c9s/bbgo/pkg/types" +) func toGlobalCurrency(original string) string { + return TrimUpperString(original) +} + +func toGlobalSymbol(original string) string { + return TrimUpperString(original) +} + +func TrimUpperString(original string) string { return strings.ToUpper(strings.TrimSpace(original)) } + +var errUnsupportedOrderStatus = fmt.Errorf("unsupported order status") + +func toGlobalOrderFromOpenOrder(r order) (types.Order, error) { + // In exchange/max, it only parses these fields. + o := types.Order{ + SubmitOrder: types.SubmitOrder{ + ClientOrderID: r.ClientId, + Symbol: toGlobalSymbol(r.Market), + Side: types.SideType(TrimUpperString(r.Side)), + // order type definition: https://github.com/ftexchange/ftx/blob/master/rest/client.py#L122 + Type: types.OrderType(TrimUpperString(r.Type)), + Quantity: r.Size, + Price: r.Price, + TimeInForce: "GTC", + }, + Exchange: types.ExchangeFTX.String(), + IsWorking: r.Status == "open", + OrderID: uint64(r.ID), + Status: "", + ExecutedQuantity: r.FilledSize, + CreationTime: datatype.Time(r.CreatedAt), + UpdateTime: datatype.Time(r.CreatedAt), + } + + // `new` (accepted but not processed yet), `open`, or `closed` (filled or cancelled) + switch r.Status { + case "new": + o.Status = types.OrderStatusNew + case "open": + if fixedpoint.NewFromFloat(o.ExecutedQuantity) != fixedpoint.NewFromInt(0) { + o.Status = types.OrderStatusPartiallyFilled + } else { + o.Status = types.OrderStatusNew + } + case "closed": + // filled or canceled + if fixedpoint.NewFromFloat(o.Quantity) == fixedpoint.NewFromFloat(o.ExecutedQuantity) { + o.Status = types.OrderStatusFilled + } else { + // can't distinguish it's canceled or rejected from order response, so always set to canceled + o.Status = types.OrderStatusCanceled + } + default: + return types.Order{}, fmt.Errorf("unsupported status %s: %w", r.Status, errUnsupportedOrderStatus) + } + + return o, nil +} diff --git a/pkg/exchange/ftx/convert_test.go b/pkg/exchange/ftx/convert_test.go new file mode 100644 index 000000000..6c480eb68 --- /dev/null +++ b/pkg/exchange/ftx/convert_test.go @@ -0,0 +1,51 @@ +package ftx + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/c9s/bbgo/pkg/types" +) + +func Test_toGlobalOrderFromOpenOrder(t *testing.T) { + input := ` +{ + "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": "client-id-123" +} +` + + var r order + assert.NoError(t, json.Unmarshal([]byte(input), &r)) + + o, err := toGlobalOrderFromOpenOrder(r) + assert.NoError(t, err) + assert.Equal(t, "client-id-123", o.ClientOrderID) + assert.Equal(t, "XRP-PERP", o.Symbol) + assert.Equal(t, types.SideTypeSell, o.Side) + assert.Equal(t, types.OrderTypeLimit, o.Type) + assert.Equal(t, float64(31431), o.Quantity) + assert.Equal(t, 0.306525, o.Price) + assert.Equal(t, "GTC", o.TimeInForce) + assert.Equal(t, types.ExchangeFTX.String(), o.Exchange) + assert.True(t, o.IsWorking) + assert.Equal(t, uint64(9596912), o.OrderID) + assert.Equal(t, types.OrderStatusPartiallyFilled, o.Status) + assert.Equal(t, float64(10), o.ExecutedQuantity) +}