mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-10 09:11:55 +00:00
grid2: add ClearDuplicatedPriceOpenOrders option
This commit is contained in:
parent
b672889602
commit
ccf567fdab
|
@ -6,6 +6,7 @@ import (
|
|||
"math"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
@ -134,6 +135,8 @@ type Strategy struct {
|
|||
|
||||
ClearOpenOrdersIfMismatch bool `json:"clearOpenOrdersIfMismatch"`
|
||||
|
||||
ClearDuplicatedPriceOpenOrders bool `json:"clearDuplicatedPriceOpenOrders"`
|
||||
|
||||
// UseCancelAllOrdersApiWhenClose uses a different API to cancel all the orders on the market when closing a grid
|
||||
UseCancelAllOrdersApiWhenClose bool `json:"useCancelAllOrdersApiWhenClose"`
|
||||
|
||||
|
@ -1158,15 +1161,35 @@ func sortOrdersByPriceAscending(orders []types.Order) []types.Order {
|
|||
}
|
||||
|
||||
func (s *Strategy) debugGridOrders(submitOrders []types.SubmitOrder, lastPrice fixedpoint.Value) {
|
||||
s.logger.Infof("GRID ORDERS: [")
|
||||
var sb strings.Builder
|
||||
|
||||
sb.WriteString("GRID ORDERS [")
|
||||
for i, order := range submitOrders {
|
||||
if i > 0 && lastPrice.Compare(order.Price) >= 0 && lastPrice.Compare(submitOrders[i-1].Price) <= 0 {
|
||||
s.logger.Infof(" - LAST PRICE: %f", lastPrice.Float64())
|
||||
sb.WriteString(fmt.Sprintf(" - LAST PRICE: %f", lastPrice.Float64()))
|
||||
}
|
||||
|
||||
s.logger.Info(" - ", order.String())
|
||||
sb.WriteString(" - " + order.String())
|
||||
}
|
||||
s.logger.Infof("] END OF GRID ORDERS")
|
||||
sb.WriteString("] END OF GRID ORDERS")
|
||||
|
||||
s.logger.Infof(sb.String())
|
||||
}
|
||||
|
||||
func (s *Strategy) debugOrders(desc string, orders []types.Order) {
|
||||
var sb strings.Builder
|
||||
|
||||
if desc == "" {
|
||||
desc = "ORDERS"
|
||||
}
|
||||
|
||||
sb.WriteString(desc + " [")
|
||||
for i, order := range orders {
|
||||
sb.WriteString(fmt.Sprintf(" - %d) %s", i, order.String()))
|
||||
}
|
||||
sb.WriteString("]")
|
||||
|
||||
s.logger.Infof(sb.String())
|
||||
}
|
||||
|
||||
func (s *Strategy) generateGridOrders(totalQuote, totalBase, lastPrice fixedpoint.Value) ([]types.SubmitOrder, error) {
|
||||
|
@ -1884,6 +1907,13 @@ func (s *Strategy) Run(ctx context.Context, _ bbgo.OrderExecutor, session *bbgo.
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if s.ClearDuplicatedPriceOpenOrders {
|
||||
s.logger.Infof("clearDuplicatedPriceOpenOrders is set, finding duplicated open orders...")
|
||||
if err := s.cancelDuplicatedPriceOpenOrders(ctx, session); err != nil {
|
||||
s.logger.WithError(err).Errorf("cancelDuplicatedPriceOpenOrders error")
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// if TriggerPrice is zero, that means we need to open the grid when start up
|
||||
|
@ -2020,11 +2050,55 @@ func (s *Strategy) openOrdersMismatches(ctx context.Context, session *bbgo.Excha
|
|||
return false, nil
|
||||
}
|
||||
|
||||
func roundUpMarketQuantity(market types.Market, v fixedpoint.Value, c string) (fixedpoint.Value, int) {
|
||||
prec := market.VolumePrecision
|
||||
if c == market.QuoteCurrency {
|
||||
prec = market.PricePrecision
|
||||
func (s *Strategy) cancelDuplicatedPriceOpenOrders(ctx context.Context, session *bbgo.ExchangeSession) error {
|
||||
openOrders, err := session.Exchange.QueryOpenOrders(ctx, s.Symbol)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return v.Round(prec, fixedpoint.Up), prec
|
||||
if len(openOrders) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
dupOrders := s.findDuplicatedPriceOpenOrders(openOrders)
|
||||
|
||||
if len(dupOrders) > 0 {
|
||||
s.debugOrders("DUPLICATED ORDERS", dupOrders)
|
||||
return session.Exchange.CancelOrders(ctx, dupOrders...)
|
||||
}
|
||||
|
||||
s.logger.Infof("no duplicated order found")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Strategy) findDuplicatedPriceOpenOrders(openOrders []types.Order) (dupOrders []types.Order) {
|
||||
orderBook := bbgo.NewActiveOrderBook(s.Symbol)
|
||||
for _, openOrder := range openOrders {
|
||||
existingOrder := orderBook.Lookup(func(o types.Order) bool {
|
||||
return o.Price.Compare(openOrder.Price) == 0
|
||||
})
|
||||
|
||||
if existingOrder != nil {
|
||||
// found duplicated order
|
||||
// compare creation time and remove the latest created order
|
||||
// if the creation time equals, then we can just cancel one of them
|
||||
s.debugOrders(
|
||||
fmt.Sprintf("found duplicated order at price %s, comparing orders", openOrder.Price.String()),
|
||||
[]types.Order{*existingOrder, openOrder})
|
||||
|
||||
dupOrder := *existingOrder
|
||||
if openOrder.CreationTime.After(existingOrder.CreationTime.Time()) {
|
||||
dupOrder = openOrder
|
||||
} else if openOrder.CreationTime.Before(existingOrder.CreationTime.Time()) {
|
||||
// override the existing order and take the existing order as a duplicated one
|
||||
orderBook.Add(openOrder)
|
||||
}
|
||||
|
||||
dupOrders = append(dupOrders, dupOrder)
|
||||
} else {
|
||||
orderBook.Add(openOrder)
|
||||
}
|
||||
}
|
||||
|
||||
return dupOrders
|
||||
}
|
||||
|
|
|
@ -355,11 +355,10 @@ func TestStrategy_calculateQuoteInvestmentQuantity(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.InDelta(t, 0.099992, quantity.Float64(), 0.0001)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func newTestStrategy() *Strategy {
|
||||
market := types.Market{
|
||||
func newTestMarket() types.Market {
|
||||
return types.Market{
|
||||
BaseCurrency: "BTC",
|
||||
QuoteCurrency: "USDT",
|
||||
TickSize: number(0.01),
|
||||
|
@ -368,6 +367,36 @@ func newTestStrategy() *Strategy {
|
|||
MinNotional: number(10.0),
|
||||
MinQuantity: number(0.001),
|
||||
}
|
||||
}
|
||||
|
||||
var testOrderID = uint64(0)
|
||||
|
||||
func newTestOrder(price, quantity fixedpoint.Value, side types.SideType) types.Order {
|
||||
market := newTestMarket()
|
||||
testOrderID++
|
||||
return types.Order{
|
||||
SubmitOrder: types.SubmitOrder{
|
||||
Symbol: "BTCUSDT",
|
||||
Side: side,
|
||||
Type: types.OrderTypeLimit,
|
||||
Quantity: quantity,
|
||||
Price: price,
|
||||
AveragePrice: fixedpoint.Zero,
|
||||
StopPrice: fixedpoint.Zero,
|
||||
Market: market,
|
||||
TimeInForce: types.TimeInForceGTC,
|
||||
},
|
||||
Exchange: "binance",
|
||||
GID: testOrderID,
|
||||
OrderID: testOrderID,
|
||||
Status: types.OrderStatusNew,
|
||||
ExecutedQuantity: fixedpoint.Zero,
|
||||
IsWorking: true,
|
||||
}
|
||||
}
|
||||
|
||||
func newTestStrategy() *Strategy {
|
||||
market := newTestMarket()
|
||||
|
||||
s := &Strategy{
|
||||
logger: logrus.NewEntry(logrus.New()),
|
||||
|
@ -500,6 +529,33 @@ func TestStrategy_aggregateOrderBaseFee(t *testing.T) {
|
|||
assert.Equal(t, "0.01", baseFee.String())
|
||||
}
|
||||
|
||||
func TestStrategy_findDuplicatedPriceOpenOrders(t *testing.T) {
|
||||
t.Run("no duplicated open orders", func(t *testing.T) {
|
||||
s := newTestStrategy()
|
||||
s.grid = s.newGrid()
|
||||
|
||||
dupOrders := s.findDuplicatedPriceOpenOrders([]types.Order{
|
||||
newTestOrder(number(1900.0), number(0.1), types.SideTypeSell),
|
||||
newTestOrder(number(1800.0), number(0.1), types.SideTypeSell),
|
||||
newTestOrder(number(1700.0), number(0.1), types.SideTypeSell),
|
||||
})
|
||||
assert.Empty(t, dupOrders)
|
||||
assert.Len(t, dupOrders, 0)
|
||||
})
|
||||
|
||||
t.Run("1 duplicated open order SELL", func(t *testing.T) {
|
||||
s := newTestStrategy()
|
||||
s.grid = s.newGrid()
|
||||
|
||||
dupOrders := s.findDuplicatedPriceOpenOrders([]types.Order{
|
||||
newTestOrder(number(1900.0), number(0.1), types.SideTypeSell),
|
||||
newTestOrder(number(1900.0), number(0.1), types.SideTypeSell),
|
||||
newTestOrder(number(1800.0), number(0.1), types.SideTypeSell),
|
||||
})
|
||||
assert.Len(t, dupOrders, 1)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStrategy_handleOrderFilled(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
|
@ -971,17 +1027,6 @@ func TestStrategy_checkMinimalQuoteInvestment(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func Test_roundUpMarketQuantity(t *testing.T) {
|
||||
q := number("0.00000003")
|
||||
assert.Equal(t, "0.00000003", q.String())
|
||||
|
||||
q3, prec := roundUpMarketQuantity(types.Market{
|
||||
VolumePrecision: 8,
|
||||
}, q, "BTC")
|
||||
assert.Equal(t, "0.00000003", q3.String(), "rounding prec 8")
|
||||
assert.Equal(t, 8, prec)
|
||||
}
|
||||
|
||||
func Test_buildPinOrderMap(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
s := newTestStrategy()
|
||||
|
|
Loading…
Reference in New Issue
Block a user