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"
|
"math"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -134,6 +135,8 @@ type Strategy struct {
|
||||||
|
|
||||||
ClearOpenOrdersIfMismatch bool `json:"clearOpenOrdersIfMismatch"`
|
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 uses a different API to cancel all the orders on the market when closing a grid
|
||||||
UseCancelAllOrdersApiWhenClose bool `json:"useCancelAllOrdersApiWhenClose"`
|
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) {
|
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 {
|
for i, order := range submitOrders {
|
||||||
if i > 0 && lastPrice.Compare(order.Price) >= 0 && lastPrice.Compare(submitOrders[i-1].Price) <= 0 {
|
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) {
|
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
|
// 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
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func roundUpMarketQuantity(market types.Market, v fixedpoint.Value, c string) (fixedpoint.Value, int) {
|
func (s *Strategy) cancelDuplicatedPriceOpenOrders(ctx context.Context, session *bbgo.ExchangeSession) error {
|
||||||
prec := market.VolumePrecision
|
openOrders, err := session.Exchange.QueryOpenOrders(ctx, s.Symbol)
|
||||||
if c == market.QuoteCurrency {
|
if err != nil {
|
||||||
prec = market.PricePrecision
|
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.NoError(t, err)
|
||||||
assert.InDelta(t, 0.099992, quantity.Float64(), 0.0001)
|
assert.InDelta(t, 0.099992, quantity.Float64(), 0.0001)
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTestStrategy() *Strategy {
|
func newTestMarket() types.Market {
|
||||||
market := types.Market{
|
return types.Market{
|
||||||
BaseCurrency: "BTC",
|
BaseCurrency: "BTC",
|
||||||
QuoteCurrency: "USDT",
|
QuoteCurrency: "USDT",
|
||||||
TickSize: number(0.01),
|
TickSize: number(0.01),
|
||||||
|
@ -368,6 +367,36 @@ func newTestStrategy() *Strategy {
|
||||||
MinNotional: number(10.0),
|
MinNotional: number(10.0),
|
||||||
MinQuantity: number(0.001),
|
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{
|
s := &Strategy{
|
||||||
logger: logrus.NewEntry(logrus.New()),
|
logger: logrus.NewEntry(logrus.New()),
|
||||||
|
@ -500,6 +529,33 @@ func TestStrategy_aggregateOrderBaseFee(t *testing.T) {
|
||||||
assert.Equal(t, "0.01", baseFee.String())
|
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) {
|
func TestStrategy_handleOrderFilled(t *testing.T) {
|
||||||
ctx := context.Background()
|
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) {
|
func Test_buildPinOrderMap(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
s := newTestStrategy()
|
s := newTestStrategy()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user