mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-21 22:43:52 +00:00
all: improve cancel command and add uuid field to order struct
This commit is contained in:
parent
471d86c801
commit
0cef2c52ef
|
@ -107,4 +107,17 @@ godotenv -f .env.local -- go run ./cmd/bbgo --config config/bbgo.yaml userdatast
|
|||
|
||||
```shell
|
||||
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().Int64("group-id", 0, "group 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")
|
||||
RootCmd.AddCommand(cancelOrderCmd)
|
||||
}
|
||||
|
@ -54,60 +55,56 @@ var cancelOrderCmd = &cobra.Command{
|
|||
return err
|
||||
}
|
||||
|
||||
orderUUID, err := cmd.Flags().GetString("order-uuid")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
all, err := cmd.Flags().GetBool("all")
|
||||
if err != nil {
|
||||
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")
|
||||
if err != nil {
|
||||
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 {
|
||||
ses, ok := sessions[sessionName]
|
||||
ses, ok := environ.Session(sessionName)
|
||||
if !ok {
|
||||
return fmt.Errorf("session %s not found", sessionName)
|
||||
}
|
||||
|
||||
if orderID > 0 {
|
||||
logrus.Infof("canceling order by the given order id %d", orderID)
|
||||
if orderID > 0 || orderUUID != "" {
|
||||
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{
|
||||
SubmitOrder: types.SubmitOrder{
|
||||
Symbol: symbol,
|
||||
},
|
||||
OrderID: orderID,
|
||||
UUID: orderUUID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -5,15 +5,17 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/bbgo"
|
||||
"github.com/c9s/bbgo/pkg/exchange/ftx"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
|
@ -106,8 +108,9 @@ var listOrdersCmd = &cobra.Command{
|
|||
return fmt.Errorf("invalid status %s", status)
|
||||
}
|
||||
|
||||
log.Infof("%s ORDERS FROM %s SESSION", strings.ToUpper(status), session.Name)
|
||||
for _, o := range os {
|
||||
log.Infof("%s orders: %+v", status, o)
|
||||
log.Infof("%+v", o)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -130,6 +130,21 @@ func hashStringID(s string) uint64 {
|
|||
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 {
|
||||
switch s {
|
||||
case "buy":
|
||||
|
@ -173,3 +188,27 @@ func toLocalSide(side types.SideType) kucoinapi.SideType {
|
|||
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"
|
||||
"time"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/exchange/kucoin/kucoinapi"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
"github.com/pkg/errors"
|
||||
"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")
|
||||
|
@ -185,16 +187,67 @@ func (e *Exchange) SubmitOrders(ctx context.Context, orders ...types.SubmitOrder
|
|||
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) {
|
||||
req := e.client.TradeService.NewListOrdersRequest()
|
||||
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 {
|
||||
panic("implement me")
|
||||
return nil
|
||||
func (e *Exchange) CancelOrders(ctx context.Context, orders ...types.Order) (errs error) {
|
||||
for _, o := range orders {
|
||||
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 {
|
||||
|
|
|
@ -6,12 +6,13 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/depth"
|
||||
"github.com/c9s/bbgo/pkg/exchange/kucoin/kucoinapi"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
"github.com/c9s/bbgo/pkg/util"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const readTimeout = 30 * time.Second
|
||||
|
@ -27,7 +28,7 @@ type Stream struct {
|
|||
connCtx context.Context
|
||||
connCancel context.CancelFunc
|
||||
|
||||
bullet *kucoinapi.Bullet
|
||||
bullet *kucoinapi.Bullet
|
||||
candleEventCallbacks []func(candle *WebSocketCandleEvent, e *WebSocketEvent)
|
||||
orderBookL2EventCallbacks []func(e *WebSocketOrderBookL2Event)
|
||||
tickerEventCallbacks []func(e *WebSocketTickerEvent)
|
||||
|
@ -43,8 +44,8 @@ func NewStream(client *kucoinapi.RestClient, ex *Exchange) *Stream {
|
|||
StandardStream: types.StandardStream{
|
||||
ReconnectC: make(chan struct{}, 1),
|
||||
},
|
||||
client: client,
|
||||
exchange: ex,
|
||||
client: client,
|
||||
exchange: ex,
|
||||
lastCandle: make(map[string]types.KLine),
|
||||
depthBuffers: make(map[string]*depth.Buffer),
|
||||
}
|
||||
|
@ -136,9 +137,15 @@ func (s *Stream) handlePrivateOrderEvent(e *WebSocketPrivateOrderEvent) {
|
|||
case "open", "match", "filled":
|
||||
status := types.OrderStatusNew
|
||||
if e.Status == "done" {
|
||||
status = types.OrderStatusFilled
|
||||
} else if e.FilledSize > 0 {
|
||||
status = types.OrderStatusPartiallyFilled
|
||||
if e.FilledSize == e.Size {
|
||||
status = types.OrderStatusFilled
|
||||
} else {
|
||||
status = types.OrderStatusCanceled
|
||||
}
|
||||
} else if e.Status == "open" {
|
||||
if e.FilledSize > 0 {
|
||||
status = types.OrderStatusPartiallyFilled
|
||||
}
|
||||
}
|
||||
|
||||
s.StandardStream.EmitOrderUpdate(types.Order{
|
||||
|
@ -151,9 +158,10 @@ func (s *Stream) handlePrivateOrderEvent(e *WebSocketPrivateOrderEvent) {
|
|||
},
|
||||
Exchange: types.ExchangeKucoin,
|
||||
OrderID: hashStringID(e.OrderId),
|
||||
UUID: e.OrderId,
|
||||
Status: status,
|
||||
ExecutedQuantity: e.FilledSize.Float64(),
|
||||
IsWorking: true,
|
||||
IsWorking: e.Status == "open",
|
||||
CreationTime: types.Time(e.OrderTime.Time()),
|
||||
UpdateTime: types.Time(e.Ts.Time()),
|
||||
})
|
||||
|
|
|
@ -175,14 +175,18 @@ func (o *SubmitOrder) SlackAttachment() slack.Attachment {
|
|||
type Order struct {
|
||||
SubmitOrder
|
||||
|
||||
Exchange ExchangeName `json:"exchange" db:"exchange"`
|
||||
GID uint64 `json:"gid" db:"gid"`
|
||||
OrderID uint64 `json:"orderID" db:"order_id"` // order id
|
||||
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"`
|
||||
Exchange ExchangeName `json:"exchange" db:"exchange"`
|
||||
|
||||
// GID is used for relational database storage, it's an incremental ID
|
||||
GID uint64 `json:"gid" db:"gid"`
|
||||
OrderID uint64 `json:"orderID" db:"order_id"` // order id
|
||||
UUID string `json:"uuid,omitempty"`
|
||||
|
||||
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"`
|
||||
IsIsolated bool `json:"isIsolated" db:"is_isolated"`
|
||||
|
@ -200,7 +204,14 @@ func (o Order) Backup() SubmitOrder {
|
|||
}
|
||||
|
||||
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
|
||||
|
|
Loading…
Reference in New Issue
Block a user