Merge pull request #149 from c9s/ftx/place-order

This commit is contained in:
YC 2021-03-13 14:56:12 +08:00 committed by GitHub
commit 7dd67a8725
6 changed files with 228 additions and 19 deletions

View File

@ -4,15 +4,18 @@ import (
"context"
"fmt"
"github.com/google/uuid"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/c9s/bbgo/pkg/exchange/ftx"
"github.com/c9s/bbgo/pkg/types"
"github.com/c9s/bbgo/pkg/util"
)
// go run ./cmd/bbgo orders [open|closed] --session=ftx --symbol=BTC/USDT
var ordersCmd = &cobra.Command{
Use: "orders [status]",
// go run ./cmd/bbgo listorders [open|closed] --session=ftx --symbol=BTC/USDT
var listOrdersCmd = &cobra.Command{
Use: "listorders [status]",
Args: cobra.OnlyValidArgs,
// default is open which means we query open orders if you haven't provided args.
ValidArgs: []string{"", "open", "closed"},
@ -48,18 +51,84 @@ var ordersCmd = &cobra.Command{
return err
}
case "closed":
panic("not implemented")
default:
return fmt.Errorf("invalid status %s", status)
}
log.Infof("%s orders: %+v", status, os)
for _, o := range os {
log.Infof("%s orders: %+v", status, o)
}
return nil
},
}
func init() {
ordersCmd.Flags().String("session", "", "the exchange session name for sync")
ordersCmd.Flags().String("symbol", "", "the trading pair, like btcusdt")
// go run ./cmd/bbgo placeorder --session=ftx --symbol=BTC/USDT --side=buy --price=<price> --quantity=<quantity>
var placeOrderCmd = &cobra.Command{
Use: "placeorder",
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
session, err := cmd.Flags().GetString("session")
if err != nil {
return fmt.Errorf("can't get session from flags: %w", err)
}
ex, err := newExchange(session)
if err != nil {
return err
}
symbol, err := cmd.Flags().GetString("symbol")
if err != nil {
return fmt.Errorf("can't get the symbol from flags: %w", err)
}
if symbol == "" {
return fmt.Errorf("symbol is not found")
}
RootCmd.AddCommand(ordersCmd)
side, err := cmd.Flags().GetString("side")
if err != nil {
return fmt.Errorf("can't get side: %w", err)
}
price, err := cmd.Flags().GetString("price")
if err != nil {
return fmt.Errorf("can't get price: %w", err)
}
quantity, err := cmd.Flags().GetString("quantity")
if err != nil {
return fmt.Errorf("can't get quantity: %w", err)
}
so := types.SubmitOrder{
ClientOrderID: uuid.New().String(),
Symbol: symbol,
Side: types.SideType(ftx.TrimUpperString(side)),
Type: types.OrderTypeLimit,
Quantity: util.MustParseFloat(quantity),
Price: util.MustParseFloat(price),
Market: types.Market{Symbol: symbol},
TimeInForce: "GTC",
}
co, err := ex.SubmitOrders(ctx, so)
if err != nil {
return err
}
log.Infof("submitted order: %+v\ncreated order: %+v", so, co[0])
return nil
},
}
func init() {
listOrdersCmd.Flags().String("session", "", "the exchange session name for sync")
listOrdersCmd.Flags().String("symbol", "", "the trading pair, like btcusdt")
placeOrderCmd.Flags().String("session", "", "the exchange session name for sync")
placeOrderCmd.Flags().String("symbol", "", "the trading pair, like btcusdt")
placeOrderCmd.Flags().String("side", "", "the trading side: buy or sell")
placeOrderCmd.Flags().String("price", "", "the trading price")
placeOrderCmd.Flags().String("quantity", "", "the trading quantity")
RootCmd.AddCommand(listOrdersCmd)
RootCmd.AddCommand(placeOrderCmd)
}

View File

@ -21,10 +21,14 @@ func TrimUpperString(original string) string {
return strings.ToUpper(strings.TrimSpace(original))
}
func TrimLowerString(original string) string {
return strings.ToLower(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.
func toGlobalOrder(r order) (types.Order, error) {
// In exchange/max/convert.go, it only parses these fields.
o := types.Order{
SubmitOrder: types.SubmitOrder{
ClientOrderID: r.ClientId,

View File

@ -34,7 +34,7 @@ func Test_toGlobalOrderFromOpenOrder(t *testing.T) {
var r order
assert.NoError(t, json.Unmarshal([]byte(input), &r))
o, err := toGlobalOrderFromOpenOrder(r)
o, err := toGlobalOrder(r)
assert.NoError(t, err)
assert.Equal(t, "client-id-123", o.ClientOrderID)
assert.Equal(t, "XRP-PERP", o.Symbol)
@ -49,3 +49,50 @@ func Test_toGlobalOrderFromOpenOrder(t *testing.T) {
assert.Equal(t, types.OrderStatusPartiallyFilled, o.Status)
assert.Equal(t, float64(10), o.ExecutedQuantity)
}
func TestTrimLowerString(t *testing.T) {
type args struct {
original string
}
tests := []struct {
name string
args args
want string
}{
{
name: "spaces",
args: args{
original: " ",
},
want: "",
},
{
name: "uppercase",
args: args{
original: " HELLO ",
},
want: "hello",
},
{
name: "lowercase",
args: args{
original: " hello",
},
want: "hello",
},
{
name: "upper/lower cases",
args: args{
original: " heLLo ",
},
want: "hello",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := TrimLowerString(tt.args.original); got != tt.want {
t.Errorf("TrimLowerString() = %v, want %v", got, tt.want)
}
})
}
}

View File

@ -97,8 +97,38 @@ func (e *Exchange) QueryWithdrawHistory(ctx context.Context, asset string, since
panic("implement me")
}
func (e *Exchange) SubmitOrders(ctx context.Context, orders ...types.SubmitOrder) (createdOrders types.OrderSlice, err error) {
panic("implement me")
func (e *Exchange) SubmitOrders(ctx context.Context, orders ...types.SubmitOrder) (types.OrderSlice, error) {
var createdOrders types.OrderSlice
// TODO: currently only support limit and market order
// TODO: support time in force
for _, so := range orders {
if so.TimeInForce != "GTC" {
return createdOrders, fmt.Errorf("unsupported TimeInForce %s. only support GTC", so.TimeInForce)
}
or, err := e.rest.PlaceOrder(ctx, PlaceOrderPayload{
Market: TrimUpperString(so.Symbol),
Side: TrimLowerString(string(so.Side)),
Price: so.Price,
Type: TrimLowerString(string(so.Type)),
Size: so.Quantity,
ReduceOnly: false,
IOC: false,
PostOnly: false,
ClientID: so.ClientOrderID,
})
if err != nil {
return createdOrders, fmt.Errorf("failed to place order %+v: %w", so, err)
}
if !or.Success {
return createdOrders, fmt.Errorf("ftx returns placing order failure")
}
globalOrder, err := toGlobalOrder(or.Result)
if err != nil {
return createdOrders, fmt.Errorf("failed to convert response to global order")
}
createdOrders = append(createdOrders, globalOrder)
}
return createdOrders, nil
}
func (e *Exchange) QueryOpenOrders(ctx context.Context, symbol string) (orders []types.Order, err error) {
@ -111,7 +141,7 @@ func (e *Exchange) QueryOpenOrders(ctx context.Context, symbol string) (orders [
return nil, fmt.Errorf("ftx returns querying open orders failure")
}
for _, r := range resp.Result {
o, err := toGlobalOrderFromOpenOrder(r)
o, err := toGlobalOrder(r)
if err != nil {
return nil, err
}

View File

@ -10,7 +10,60 @@ type orderRequest struct {
*restRequest
}
func (r *orderRequest) OpenOrders(ctx context.Context, market string) (orders, error) {
/*
{
"market": "XRP-PERP",
"side": "sell",
"price": 0.306525,
"type": "limit",
"size": 31431.0,
"reduceOnly": false,
"ioc": false,
"postOnly": false,
"clientId": null
}
*/
type PlaceOrderPayload struct {
Market string
Side string
Price float64
Type string
Size float64
ReduceOnly bool
IOC bool
PostOnly bool
ClientID string
}
func (r *orderRequest) PlaceOrder(ctx context.Context, p PlaceOrderPayload) (orderResponse, error) {
resp, err := r.
Method("POST").
ReferenceURL("api/orders").
Payloads(map[string]interface{}{
"market": p.Market,
"side": p.Side,
"price": p.Price,
"type": p.Type,
"size": p.Size,
"reduceOnly": p.ReduceOnly,
"ioc": p.IOC,
"postOnly": p.PostOnly,
"clientId": p.ClientID,
}).
DoAuthenticatedRequest(ctx)
if err != nil {
return orderResponse{}, err
}
var o orderResponse
if err := json.Unmarshal(resp.Body, &o); err != nil {
return orderResponse{}, fmt.Errorf("failed to unmarshal order response body to json: %w", err)
}
return o, nil
}
func (r *orderRequest) OpenOrders(ctx context.Context, market string) (ordersResponse, error) {
resp, err := r.
Method("GET").
ReferenceURL("api/orders").
@ -18,12 +71,12 @@ func (r *orderRequest) OpenOrders(ctx context.Context, market string) (orders, e
DoAuthenticatedRequest(ctx)
if err != nil {
return orders{}, err
return ordersResponse{}, err
}
var o orders
var o ordersResponse
if err := json.Unmarshal(resp.Body, &o); err != nil {
return orders{}, fmt.Errorf("failed to unmarshal open orders response body to json: %w", err)
return ordersResponse{}, fmt.Errorf("failed to unmarshal open orders response body to json: %w", err)
}
return o, nil

View File

@ -12,7 +12,7 @@ type balances struct {
} `json:"result"`
}
type orders struct {
type ordersResponse struct {
Success bool `json:"Success"`
Result []order `json:"result"`
@ -37,3 +37,9 @@ type order struct {
PostOnly bool `json:"postOnly"`
ClientId string `json:"clientId"`
}
type orderResponse struct {
Success bool `json:"Success"`
Result order `json:"result"`
}