diff --git a/pkg/bbgo/order_execution.go b/pkg/bbgo/order_execution.go index 9f8e45934..cfee1a9fb 100644 --- a/pkg/bbgo/order_execution.go +++ b/pkg/bbgo/order_execution.go @@ -17,7 +17,7 @@ type ExchangeOrderExecutionRouter struct { sessions map[string]*ExchangeSession } -func (e *ExchangeOrderExecutionRouter) SubmitOrdersTo(ctx context.Context, session string, orders ...types.SubmitOrder) ([]types.Order, error) { +func (e *ExchangeOrderExecutionRouter) SubmitOrdersTo(ctx context.Context, session string, orders ...types.SubmitOrder) (types.OrderSlice, error) { es, ok := e.sessions[session] if !ok { return nil, errors.Errorf("exchange session %s not found", session) @@ -51,7 +51,7 @@ func (e *ExchangeOrderExecutor) notifySubmitOrders(orders ...types.SubmitOrder) } } -func (e *ExchangeOrderExecutor) SubmitOrders(ctx context.Context, orders ...types.SubmitOrder) ([]types.Order, error) { +func (e *ExchangeOrderExecutor) SubmitOrders(ctx context.Context, orders ...types.SubmitOrder) (types.OrderSlice, error) { formattedOrders, err := formatOrders(orders, e.session) if err != nil { return nil, err @@ -81,7 +81,7 @@ type BasicRiskControlOrderExecutor struct { MaxOrderAmount fixedpoint.Value `json:"maxOrderAmount,omitempty"` } -func (e *BasicRiskControlOrderExecutor) SubmitOrders(ctx context.Context, orders ...types.SubmitOrder) ([]types.Order, error) { +func (e *BasicRiskControlOrderExecutor) SubmitOrders(ctx context.Context, orders ...types.SubmitOrder) (types.OrderSlice, error) { var formattedOrders []types.SubmitOrder for _, order := range orders { currentPrice, ok := e.session.LastPrice(order.Symbol) diff --git a/pkg/bbgo/risk_controls.go b/pkg/bbgo/risk_controls.go index 374f3da5c..74a7fb8cd 100644 --- a/pkg/bbgo/risk_controls.go +++ b/pkg/bbgo/risk_controls.go @@ -17,7 +17,7 @@ type RiskControlOrderExecutors struct { BySymbol map[string]*SymbolBasedOrderExecutor `json:"bySymbol,omitempty" yaml:"bySymbol,omitempty"` } -func (e *RiskControlOrderExecutors) SubmitOrders(ctx context.Context, orders ...types.SubmitOrder) ([]types.Order, error) { +func (e *RiskControlOrderExecutors) SubmitOrders(ctx context.Context, orders ...types.SubmitOrder) (types.OrderSlice, error) { var symbolOrders = make(map[string][]types.SubmitOrder, len(orders)) for _, order := range orders { symbolOrders[order.Symbol] = append(symbolOrders[order.Symbol], order) diff --git a/pkg/bbgo/trader.go b/pkg/bbgo/trader.go index 6a4747f57..03526e87a 100644 --- a/pkg/bbgo/trader.go +++ b/pkg/bbgo/trader.go @@ -295,10 +295,10 @@ func (trader *Trader) ReportPnL() *PnLReporterManager { } type OrderExecutor interface { - SubmitOrders(ctx context.Context, orders ...types.SubmitOrder) (createdOrders []types.Order, err error) + SubmitOrders(ctx context.Context, orders ...types.SubmitOrder) (createdOrders types.OrderSlice, err error) } type OrderExecutionRouter interface { // SubmitOrderTo submit order to a specific exchange session - SubmitOrdersTo(ctx context.Context, session string, orders ...types.SubmitOrder) (createdOrders []types.Order, err error) + SubmitOrdersTo(ctx context.Context, session string, orders ...types.SubmitOrder) (createdOrders types.OrderSlice, err error) } diff --git a/pkg/exchange/binance/exchange.go b/pkg/exchange/binance/exchange.go index 2053ce8ee..e5ededa5d 100644 --- a/pkg/exchange/binance/exchange.go +++ b/pkg/exchange/binance/exchange.go @@ -310,7 +310,7 @@ func (e *Exchange) CancelOrders(ctx context.Context, orders ...types.Order) (err return err2 } -func (e *Exchange) SubmitOrders(ctx context.Context, orders ...types.SubmitOrder) (createdOrders []types.Order, err error) { +func (e *Exchange) SubmitOrders(ctx context.Context, orders ...types.SubmitOrder) (createdOrders types.OrderSlice, err error) { for _, order := range orders { orderType, err := toLocalOrderType(order.Type) if err != nil { diff --git a/pkg/exchange/max/exchange.go b/pkg/exchange/max/exchange.go index 7a7c36304..98776758a 100644 --- a/pkg/exchange/max/exchange.go +++ b/pkg/exchange/max/exchange.go @@ -111,7 +111,7 @@ func (e *Exchange) CancelOrders(ctx context.Context, orders ...types.Order) (err return err2 } -func (e *Exchange) SubmitOrders(ctx context.Context, orders ...types.SubmitOrder) (createdOrders []types.Order, err error) { +func (e *Exchange) SubmitOrders(ctx context.Context, orders ...types.SubmitOrder) (createdOrders types.OrderSlice, err error) { for _, order := range orders { orderType, err := toLocalOrderType(order.Type) if err != nil { diff --git a/pkg/strategy/grid/strategy.go b/pkg/strategy/grid/strategy.go index be192fb84..cafee7037 100644 --- a/pkg/strategy/grid/strategy.go +++ b/pkg/strategy/grid/strategy.go @@ -64,8 +64,7 @@ type Strategy struct { BaseQuantity float64 `json:"baseQuantity"` - activeBidOrders *types.SyncOrderMap - activeAskOrders *types.SyncOrderMap + activeOrders *types.LocalActiveOrderBook boll *indicator.BOLL } @@ -84,7 +83,7 @@ func (s *Strategy) updateBidOrders(orderExecutor bbgo.OrderExecutor, session *bb return } - var numOrders = s.GridNum - s.activeBidOrders.Len() + var numOrders = s.GridNum - s.activeOrders.NumOfBids() if numOrders <= 0 { return } @@ -113,10 +112,7 @@ func (s *Strategy) updateBidOrders(orderExecutor bbgo.OrderExecutor, session *bb return } - for _, o := range orders { - log.Infof("adding order %d to the active bid order pool...", o.OrderID) - s.activeBidOrders.Add(o) - } + s.activeOrders.Add(orders...) } func (s *Strategy) updateAskOrders(orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) { @@ -128,7 +124,7 @@ func (s *Strategy) updateAskOrders(orderExecutor bbgo.OrderExecutor, session *bb return } - var numOrders = s.GridNum - s.activeAskOrders.Len() + var numOrders = s.GridNum - s.activeOrders.NumOfAsks() if numOrders <= 0 { return } @@ -157,30 +153,22 @@ func (s *Strategy) updateAskOrders(orderExecutor bbgo.OrderExecutor, session *bb return } - for _, o := range orders { - log.Infof("adding order %d to the active ask order pool...", o.OrderID) - s.activeAskOrders.Add(o) - } + log.Infof("adding orders to the active ask order pool...") + s.activeOrders.Add(orders...) } func (s *Strategy) updateOrders(orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) { - log.Infof("checking grid orders, bids=%d asks=%d", s.activeBidOrders.Len(), s.activeAskOrders.Len()) + log.Infof("checking grid orders, bids=%d asks=%d", s.activeOrders.Bids.Len(), s.activeOrders.Asks.Len()) - for _, o := range s.activeBidOrders.Orders() { - log.Infof("bid order: %d -> %s", o.OrderID, o.Status) - } + s.activeOrders.Print() - for _, o := range s.activeAskOrders.Orders() { - log.Infof("ask order: %d -> %s", o.OrderID, o.Status) - } - - if s.activeBidOrders.Len() < s.GridNum { - log.Infof("active bid orders not enough: %d < %d, updating...", s.activeBidOrders.Len(), s.GridNum) + if s.activeOrders.Bids.Len() < s.GridNum { + log.Infof("active bid orders not enough: %d < %d, updating...", s.activeOrders.Bids.Len(), s.GridNum) s.updateBidOrders(orderExecutor, session) } - if s.activeAskOrders.Len() < s.GridNum { - log.Infof("active ask orders not enough: %d < %d, updating...", s.activeAskOrders.Len(), s.GridNum) + if s.activeOrders.Asks.Len() < s.GridNum { + log.Infof("active ask orders not enough: %d < %d, updating...", s.activeOrders.Asks.Len(), s.GridNum) s.updateAskOrders(orderExecutor, session) } } @@ -196,9 +184,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se }) // we don't persist orders so that we can not clear the previous orders for now. just need time to support this. - // TODO: pull this map out and add mutex lock - s.activeBidOrders = types.NewSyncOrderMap() - s.activeAskOrders = types.NewSyncOrderMap() + s.activeOrders = types.NewLocalActiveOrderBook() session.Stream.OnOrderUpdate(func(order types.Order) { log.Infof("received order update: %+v", order) @@ -208,43 +194,16 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se } switch order.Status { - case types.OrderStatusFilled: - switch order.Side { - case types.SideTypeSell: - // find the filled bid to remove - if filledOrder, ok := s.activeBidOrders.AnyFilled(); ok { - s.activeBidOrders.Delete(filledOrder.OrderID) - s.activeAskOrders.Delete(order.OrderID) - } - - case types.SideTypeBuy: - // find the filled ask order to remove - if filledOrder, ok := s.activeAskOrders.AnyFilled(); ok { - s.activeAskOrders.Delete(filledOrder.OrderID) - s.activeBidOrders.Delete(order.OrderID) - } - } + s.activeOrders.WriteOff(order) case types.OrderStatusCanceled, types.OrderStatusRejected: log.Infof("order status %s, removing %d from the active order pool...", order.Status, order.OrderID) - - switch order.Side { - case types.SideTypeSell: - s.activeAskOrders.Delete(order.OrderID) - case types.SideTypeBuy: - s.activeBidOrders.Delete(order.OrderID) - - } + s.activeOrders.Delete(order) default: log.Infof("order status %s, updating %d to the active order pool...", order.Status, order.OrderID) - switch order.Side { - case types.SideTypeSell: - s.activeAskOrders.Add(order) - case types.SideTypeBuy: - s.activeBidOrders.Add(order) - } + s.activeOrders.Add(order) } }) @@ -255,13 +214,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se s.updateOrders(orderExecutor, session) defer func() { - for _, o := range s.activeBidOrders.Orders() { - _ = session.Exchange.CancelOrders(context.Background(), o) - } - - for _, o := range s.activeAskOrders.Orders() { - _ = session.Exchange.CancelOrders(context.Background(), o) - } + _ = session.Exchange.CancelOrders(context.Background(), s.activeOrders.Orders()...) }() for { diff --git a/pkg/types/active_book.go b/pkg/types/active_book.go new file mode 100644 index 000000000..99a33d1d6 --- /dev/null +++ b/pkg/types/active_book.go @@ -0,0 +1,90 @@ +package types + +import log "github.com/sirupsen/logrus" + +// LocalActiveOrderBook manages the local active order books. +type LocalActiveOrderBook struct { + Bids *SyncOrderMap + Asks *SyncOrderMap +} + +func NewLocalActiveOrderBook() *LocalActiveOrderBook { + return &LocalActiveOrderBook{ + Bids: NewSyncOrderMap(), + Asks: NewSyncOrderMap(), + } +} + +func (b *LocalActiveOrderBook) Print() { + for _, o := range b.Bids.Orders() { + log.Infof("bid order: %d -> %s", o.OrderID, o.Status) + } + + for _, o := range b.Asks.Orders() { + log.Infof("ask order: %d -> %s", o.OrderID, o.Status) + } +} + +func (b *LocalActiveOrderBook) Add(orders ...Order) { + for _, order := range orders { + switch order.Side { + case SideTypeBuy: + b.Bids.Add(order) + + case SideTypeSell: + b.Asks.Add(order) + + } + } +} + +func (b *LocalActiveOrderBook) NumOfBids() int { + return b.Bids.Len() +} + +func (b *LocalActiveOrderBook) NumOfAsks() int { + return b.Asks.Len() +} + +func (b *LocalActiveOrderBook) Delete(order Order) { + switch order.Side { + case SideTypeBuy: + b.Bids.Delete(order.OrderID) + + case SideTypeSell: + b.Asks.Delete(order.OrderID) + + } +} + +// WriteOff writes off the filled order on the opposite side. +// This method does not write off order by order amount or order quantity. +func (b *LocalActiveOrderBook) WriteOff(order Order) bool { + if order.Status != OrderStatusFilled { + return false + } + + switch order.Side { + case SideTypeSell: + // find the filled bid to remove + if filledOrder, ok := b.Bids.AnyFilled(); ok { + b.Bids.Delete(filledOrder.OrderID) + b.Asks.Delete(order.OrderID) + return true + } + + case SideTypeBuy: + // find the filled ask order to remove + if filledOrder, ok := b.Asks.AnyFilled(); ok { + b.Asks.Delete(filledOrder.OrderID) + b.Bids.Delete(order.OrderID) + return true + } + } + + return false +} + +func (b *LocalActiveOrderBook) Orders() OrderSlice { + return append(b.Asks.Orders(), b.Bids.Orders()...) +} diff --git a/pkg/types/exchange.go b/pkg/types/exchange.go index def3e054e..4bc082e18 100644 --- a/pkg/types/exchange.go +++ b/pkg/types/exchange.go @@ -54,7 +54,7 @@ type Exchange interface { QueryWithdrawHistory(ctx context.Context, asset string, since, until time.Time) (allWithdraws []Withdraw, err error) - SubmitOrders(ctx context.Context, orders ...SubmitOrder) (createdOrders []Order, err error) + SubmitOrders(ctx context.Context, orders ...SubmitOrder) (createdOrders OrderSlice, err error) QueryOpenOrders(ctx context.Context, symbol string) (orders []Order, err error)