mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-22 14:55:16 +00:00
all: improve cancel command and add uuid field to order struct
This commit is contained in:
parent
471d86c801
commit
0cef2c52ef
|
@ -108,3 +108,16 @@ godotenv -f .env.local -- go run ./cmd/bbgo --config config/bbgo.yaml userdatast
|
||||||
```shell
|
```shell
|
||||||
godotenv -f .env.local -- go run ./cmd/bbgo submit-order --session=kucoin --symbol=BTCUSDT --side=buy --price=18000 --quantity=0.001
|
godotenv -f .env.local -- go run ./cmd/bbgo submit-order --session=kucoin --symbol=BTCUSDT --side=buy --price=18000 --quantity=0.001
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Testing open orders query
|
||||||
|
|
||||||
|
```shell
|
||||||
|
godotenv -f .env.local -- go run ./cmd/bbgo list-orders --session kucoin --symbol=BTCUSDT open
|
||||||
|
godotenv -f .env.local -- go run ./cmd/bbgo list-orders --session kucoin --symbol=BTCUSDT closed
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing order cancel
|
||||||
|
|
||||||
|
```shell
|
||||||
|
godotenv -f .env.local -- go run ./cmd/bbgo cancel-order --session kucoin --order-uuid 61c745c44592c200014abdcf
|
||||||
|
```
|
||||||
|
|
|
@ -23,6 +23,7 @@ func init() {
|
||||||
cancelOrderCmd.Flags().String("symbol", "", "symbol to cancel orders")
|
cancelOrderCmd.Flags().String("symbol", "", "symbol to cancel orders")
|
||||||
cancelOrderCmd.Flags().Int64("group-id", 0, "group ID to cancel orders")
|
cancelOrderCmd.Flags().Int64("group-id", 0, "group ID to cancel orders")
|
||||||
cancelOrderCmd.Flags().Uint64("order-id", 0, "order ID to cancel orders")
|
cancelOrderCmd.Flags().Uint64("order-id", 0, "order ID to cancel orders")
|
||||||
|
cancelOrderCmd.Flags().String("order-uuid", "", "order UUID to cancel orders")
|
||||||
cancelOrderCmd.Flags().Bool("all", false, "cancel all orders")
|
cancelOrderCmd.Flags().Bool("all", false, "cancel all orders")
|
||||||
RootCmd.AddCommand(cancelOrderCmd)
|
RootCmd.AddCommand(cancelOrderCmd)
|
||||||
}
|
}
|
||||||
|
@ -54,60 +55,56 @@ var cancelOrderCmd = &cobra.Command{
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
orderUUID, err := cmd.Flags().GetString("order-uuid")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
all, err := cmd.Flags().GetBool("all")
|
all, err := cmd.Flags().GetBool("all")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
configFile, err := cmd.Flags().GetString("config")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(configFile) == 0 {
|
|
||||||
return errors.New("--config option is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
userConfig, err := bbgo.Load(configFile, false)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
environ := bbgo.NewEnvironment()
|
|
||||||
if err := environ.ConfigureDatabase(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := environ.ConfigureExchangeSessions(userConfig); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if userConfig.Persistence != nil {
|
|
||||||
if err := environ.ConfigurePersistence(userConfig.Persistence); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var sessions = environ.Sessions()
|
|
||||||
|
|
||||||
sessionName, err := cmd.Flags().GetString("session")
|
sessionName, err := cmd.Flags().GetString("session")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if userConfig == nil {
|
||||||
|
return errors.New("config file is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
environ := bbgo.NewEnvironment()
|
||||||
|
if err := environ.ConfigureExchangeSessions(userConfig); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := environ.Init(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var sessions = environ.Sessions()
|
||||||
|
|
||||||
|
|
||||||
if len(sessionName) > 0 {
|
if len(sessionName) > 0 {
|
||||||
ses, ok := sessions[sessionName]
|
ses, ok := environ.Session(sessionName)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("session %s not found", sessionName)
|
return fmt.Errorf("session %s not found", sessionName)
|
||||||
}
|
}
|
||||||
|
|
||||||
if orderID > 0 {
|
if orderID > 0 || orderUUID != "" {
|
||||||
logrus.Infof("canceling order by the given order id %d", orderID)
|
if orderID > 0 {
|
||||||
|
logrus.Infof("canceling order by the given order id %d", orderID)
|
||||||
|
} else if orderUUID != "" {
|
||||||
|
logrus.Infof("canceling order by the given order uuid %s", orderUUID)
|
||||||
|
}
|
||||||
|
|
||||||
err := ses.Exchange.CancelOrders(ctx, types.Order{
|
err := ses.Exchange.CancelOrders(ctx, types.Order{
|
||||||
SubmitOrder: types.SubmitOrder{
|
SubmitOrder: types.SubmitOrder{
|
||||||
Symbol: symbol,
|
Symbol: symbol,
|
||||||
},
|
},
|
||||||
OrderID: orderID,
|
OrderID: orderID,
|
||||||
|
UUID: orderUUID,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -5,15 +5,17 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||||
|
|
||||||
"github.com/c9s/bbgo/pkg/bbgo"
|
"github.com/c9s/bbgo/pkg/bbgo"
|
||||||
"github.com/c9s/bbgo/pkg/exchange/ftx"
|
"github.com/c9s/bbgo/pkg/exchange/ftx"
|
||||||
"github.com/c9s/bbgo/pkg/types"
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
|
@ -106,8 +108,9 @@ var listOrdersCmd = &cobra.Command{
|
||||||
return fmt.Errorf("invalid status %s", status)
|
return fmt.Errorf("invalid status %s", status)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Infof("%s ORDERS FROM %s SESSION", strings.ToUpper(status), session.Name)
|
||||||
for _, o := range os {
|
for _, o := range os {
|
||||||
log.Infof("%s orders: %+v", status, o)
|
log.Infof("%+v", o)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -130,6 +130,21 @@ func hashStringID(s string) uint64 {
|
||||||
return h.Sum64()
|
return h.Sum64()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func toGlobalOrderStatus(o kucoinapi.Order) types.OrderStatus {
|
||||||
|
var status types.OrderStatus
|
||||||
|
if o.IsActive {
|
||||||
|
status = types.OrderStatusNew
|
||||||
|
} else if o.DealSize > 0 {
|
||||||
|
status = types.OrderStatusPartiallyFilled
|
||||||
|
} else if o.CancelExist {
|
||||||
|
status = types.OrderStatusCanceled
|
||||||
|
} else {
|
||||||
|
status = types.OrderStatusFilled
|
||||||
|
}
|
||||||
|
|
||||||
|
return status
|
||||||
|
}
|
||||||
|
|
||||||
func toGlobalSide(s string) types.SideType {
|
func toGlobalSide(s string) types.SideType {
|
||||||
switch s {
|
switch s {
|
||||||
case "buy":
|
case "buy":
|
||||||
|
@ -173,3 +188,27 @@ func toLocalSide(side types.SideType) kucoinapi.SideType {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func toGlobalOrder(o kucoinapi.Order) types.Order {
|
||||||
|
var status = toGlobalOrderStatus(o)
|
||||||
|
var order = types.Order{
|
||||||
|
SubmitOrder: types.SubmitOrder{
|
||||||
|
ClientOrderID: o.ClientOrderID,
|
||||||
|
Symbol: toGlobalSymbol(o.Symbol),
|
||||||
|
Side: toGlobalSide(o.Side),
|
||||||
|
Type: toGlobalOrderType(o.Type),
|
||||||
|
Quantity: o.Size.Float64(),
|
||||||
|
Price: o.Price.Float64(),
|
||||||
|
StopPrice: o.StopPrice.Float64(),
|
||||||
|
TimeInForce: string(o.TimeInForce),
|
||||||
|
},
|
||||||
|
Exchange: types.ExchangeKucoin,
|
||||||
|
OrderID: hashStringID(o.ID),
|
||||||
|
UUID: o.ID,
|
||||||
|
Status: status,
|
||||||
|
ExecutedQuantity: o.DealSize.Float64(),
|
||||||
|
IsWorking: o.IsActive,
|
||||||
|
CreationTime: types.Time(o.CreatedAt.Time()),
|
||||||
|
UpdateTime: types.Time(o.CreatedAt.Time()), // kucoin does not response updated time
|
||||||
|
}
|
||||||
|
return order
|
||||||
|
}
|
||||||
|
|
|
@ -5,10 +5,12 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/c9s/bbgo/pkg/exchange/kucoin/kucoinapi"
|
|
||||||
"github.com/c9s/bbgo/pkg/types"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
"go.uber.org/multierr"
|
||||||
|
|
||||||
|
"github.com/c9s/bbgo/pkg/exchange/kucoin/kucoinapi"
|
||||||
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrMissingSequence = errors.New("sequence is missing")
|
var ErrMissingSequence = errors.New("sequence is missing")
|
||||||
|
@ -185,16 +187,67 @@ func (e *Exchange) SubmitOrders(ctx context.Context, orders ...types.SubmitOrder
|
||||||
return createdOrders, err
|
return createdOrders, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// QueryOpenOrders
|
||||||
|
/*
|
||||||
|
Documentation from the Kucoin API page
|
||||||
|
|
||||||
|
Any order on the exchange order book is in active status.
|
||||||
|
Orders removed from the order book will be marked with done status.
|
||||||
|
After an order becomes done, there may be a few milliseconds latency before it’s fully settled.
|
||||||
|
|
||||||
|
You can check the orders in any status.
|
||||||
|
If the status parameter is not specified, orders of done status will be returned by default.
|
||||||
|
|
||||||
|
When you query orders in active status, there is no time limit.
|
||||||
|
However, when you query orders in done status, the start and end time range cannot exceed 7* 24 hours.
|
||||||
|
An error will occur if the specified time window exceeds the range.
|
||||||
|
|
||||||
|
If you specify the end time only, the system will automatically calculate the start time as end time minus 7*24 hours, and vice versa.
|
||||||
|
|
||||||
|
The history for cancelled orders is only kept for one month.
|
||||||
|
You will not be able to query for cancelled orders that have happened more than a month ago.
|
||||||
|
*/
|
||||||
func (e *Exchange) QueryOpenOrders(ctx context.Context, symbol string) (orders []types.Order, err error) {
|
func (e *Exchange) QueryOpenOrders(ctx context.Context, symbol string) (orders []types.Order, err error) {
|
||||||
req := e.client.TradeService.NewListOrdersRequest()
|
req := e.client.TradeService.NewListOrdersRequest()
|
||||||
req.Symbol(toLocalSymbol(symbol))
|
req.Symbol(toLocalSymbol(symbol))
|
||||||
|
req.Status("active")
|
||||||
|
orderList, err := req.Do(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return nil, nil
|
// TODO: support pagination (right now we can only get 50 items from the first page)
|
||||||
|
for _, o := range orderList.Items {
|
||||||
|
order := toGlobalOrder(o)
|
||||||
|
orders = append(orders, order)
|
||||||
|
}
|
||||||
|
|
||||||
|
return orders, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Exchange) CancelOrders(ctx context.Context, orders ...types.Order) error {
|
func (e *Exchange) CancelOrders(ctx context.Context, orders ...types.Order) (errs error) {
|
||||||
panic("implement me")
|
for _, o := range orders {
|
||||||
return nil
|
req := e.client.TradeService.NewCancelOrderRequest()
|
||||||
|
|
||||||
|
if o.UUID != "" {
|
||||||
|
req.OrderID(o.UUID)
|
||||||
|
} else if o.ClientOrderID != "" {
|
||||||
|
req.ClientOrderID(o.ClientOrderID)
|
||||||
|
} else {
|
||||||
|
errs = multierr.Append(errs, errors.New("can not cancel order, either order uuid nor client order id is empty"))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := req.Do(ctx)
|
||||||
|
if err != nil {
|
||||||
|
errs = multierr.Append(errs, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("cancelled orders: %v", response.CancelledOrderIDs)
|
||||||
|
}
|
||||||
|
|
||||||
|
return errs
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Exchange) NewStream() types.Stream {
|
func (e *Exchange) NewStream() types.Stream {
|
||||||
|
|
|
@ -6,12 +6,13 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/c9s/bbgo/pkg/depth"
|
"github.com/c9s/bbgo/pkg/depth"
|
||||||
"github.com/c9s/bbgo/pkg/exchange/kucoin/kucoinapi"
|
"github.com/c9s/bbgo/pkg/exchange/kucoin/kucoinapi"
|
||||||
"github.com/c9s/bbgo/pkg/types"
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
"github.com/c9s/bbgo/pkg/util"
|
"github.com/c9s/bbgo/pkg/util"
|
||||||
"github.com/gorilla/websocket"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const readTimeout = 30 * time.Second
|
const readTimeout = 30 * time.Second
|
||||||
|
@ -27,7 +28,7 @@ type Stream struct {
|
||||||
connCtx context.Context
|
connCtx context.Context
|
||||||
connCancel context.CancelFunc
|
connCancel context.CancelFunc
|
||||||
|
|
||||||
bullet *kucoinapi.Bullet
|
bullet *kucoinapi.Bullet
|
||||||
candleEventCallbacks []func(candle *WebSocketCandleEvent, e *WebSocketEvent)
|
candleEventCallbacks []func(candle *WebSocketCandleEvent, e *WebSocketEvent)
|
||||||
orderBookL2EventCallbacks []func(e *WebSocketOrderBookL2Event)
|
orderBookL2EventCallbacks []func(e *WebSocketOrderBookL2Event)
|
||||||
tickerEventCallbacks []func(e *WebSocketTickerEvent)
|
tickerEventCallbacks []func(e *WebSocketTickerEvent)
|
||||||
|
@ -43,8 +44,8 @@ func NewStream(client *kucoinapi.RestClient, ex *Exchange) *Stream {
|
||||||
StandardStream: types.StandardStream{
|
StandardStream: types.StandardStream{
|
||||||
ReconnectC: make(chan struct{}, 1),
|
ReconnectC: make(chan struct{}, 1),
|
||||||
},
|
},
|
||||||
client: client,
|
client: client,
|
||||||
exchange: ex,
|
exchange: ex,
|
||||||
lastCandle: make(map[string]types.KLine),
|
lastCandle: make(map[string]types.KLine),
|
||||||
depthBuffers: make(map[string]*depth.Buffer),
|
depthBuffers: make(map[string]*depth.Buffer),
|
||||||
}
|
}
|
||||||
|
@ -136,9 +137,15 @@ func (s *Stream) handlePrivateOrderEvent(e *WebSocketPrivateOrderEvent) {
|
||||||
case "open", "match", "filled":
|
case "open", "match", "filled":
|
||||||
status := types.OrderStatusNew
|
status := types.OrderStatusNew
|
||||||
if e.Status == "done" {
|
if e.Status == "done" {
|
||||||
status = types.OrderStatusFilled
|
if e.FilledSize == e.Size {
|
||||||
} else if e.FilledSize > 0 {
|
status = types.OrderStatusFilled
|
||||||
status = types.OrderStatusPartiallyFilled
|
} else {
|
||||||
|
status = types.OrderStatusCanceled
|
||||||
|
}
|
||||||
|
} else if e.Status == "open" {
|
||||||
|
if e.FilledSize > 0 {
|
||||||
|
status = types.OrderStatusPartiallyFilled
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
s.StandardStream.EmitOrderUpdate(types.Order{
|
s.StandardStream.EmitOrderUpdate(types.Order{
|
||||||
|
@ -151,9 +158,10 @@ func (s *Stream) handlePrivateOrderEvent(e *WebSocketPrivateOrderEvent) {
|
||||||
},
|
},
|
||||||
Exchange: types.ExchangeKucoin,
|
Exchange: types.ExchangeKucoin,
|
||||||
OrderID: hashStringID(e.OrderId),
|
OrderID: hashStringID(e.OrderId),
|
||||||
|
UUID: e.OrderId,
|
||||||
Status: status,
|
Status: status,
|
||||||
ExecutedQuantity: e.FilledSize.Float64(),
|
ExecutedQuantity: e.FilledSize.Float64(),
|
||||||
IsWorking: true,
|
IsWorking: e.Status == "open",
|
||||||
CreationTime: types.Time(e.OrderTime.Time()),
|
CreationTime: types.Time(e.OrderTime.Time()),
|
||||||
UpdateTime: types.Time(e.Ts.Time()),
|
UpdateTime: types.Time(e.Ts.Time()),
|
||||||
})
|
})
|
||||||
|
|
|
@ -175,14 +175,18 @@ func (o *SubmitOrder) SlackAttachment() slack.Attachment {
|
||||||
type Order struct {
|
type Order struct {
|
||||||
SubmitOrder
|
SubmitOrder
|
||||||
|
|
||||||
Exchange ExchangeName `json:"exchange" db:"exchange"`
|
Exchange ExchangeName `json:"exchange" db:"exchange"`
|
||||||
GID uint64 `json:"gid" db:"gid"`
|
|
||||||
OrderID uint64 `json:"orderID" db:"order_id"` // order id
|
// GID is used for relational database storage, it's an incremental ID
|
||||||
Status OrderStatus `json:"status" db:"status"`
|
GID uint64 `json:"gid" db:"gid"`
|
||||||
ExecutedQuantity float64 `json:"executedQuantity" db:"executed_quantity"`
|
OrderID uint64 `json:"orderID" db:"order_id"` // order id
|
||||||
IsWorking bool `json:"isWorking" db:"is_working"`
|
UUID string `json:"uuid,omitempty"`
|
||||||
CreationTime Time `json:"creationTime" db:"created_at"`
|
|
||||||
UpdateTime Time `json:"updateTime" db:"updated_at"`
|
Status OrderStatus `json:"status" db:"status"`
|
||||||
|
ExecutedQuantity float64 `json:"executedQuantity" db:"executed_quantity"`
|
||||||
|
IsWorking bool `json:"isWorking" db:"is_working"`
|
||||||
|
CreationTime Time `json:"creationTime" db:"created_at"`
|
||||||
|
UpdateTime Time `json:"updateTime" db:"updated_at"`
|
||||||
|
|
||||||
IsMargin bool `json:"isMargin" db:"is_margin"`
|
IsMargin bool `json:"isMargin" db:"is_margin"`
|
||||||
IsIsolated bool `json:"isIsolated" db:"is_isolated"`
|
IsIsolated bool `json:"isIsolated" db:"is_isolated"`
|
||||||
|
@ -200,7 +204,14 @@ func (o Order) Backup() SubmitOrder {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o Order) String() string {
|
func (o Order) String() string {
|
||||||
return fmt.Sprintf("ORDER %s %d %s %s %f/%f @ %f -> %s", o.Exchange.String(), o.OrderID, o.Symbol, o.Side, o.ExecutedQuantity, o.Quantity, o.Price, o.Status)
|
var orderID string
|
||||||
|
if o.UUID != "" {
|
||||||
|
orderID = o.UUID
|
||||||
|
} else {
|
||||||
|
orderID = strconv.FormatUint(o.OrderID, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("ORDER %s %s %s %s %f/%f @ %f -> %s", o.Exchange.String(), orderID, o.Symbol, o.Side, o.ExecutedQuantity, o.Quantity, o.Price, o.Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PlainText is used for telegram-styled messages
|
// PlainText is used for telegram-styled messages
|
||||||
|
|
Loading…
Reference in New Issue
Block a user