How to recalculate my TPs

Hello everyone! I try to create a bot to trade automatically. It starts to work after I set initial prices.

Briefly

I’m going to provide something like that that

{
    "ticker": "BTCUSDT",
    "positionSide": "LONG",
    "leverage": 25,
    "deposit": 1000,
    "percentageGrid": {
        "positionDeposit": 5,
	    "entryOrder": 30,
	    "takeProfit1": 60,
	    "takeProfit2": 20,
	    "limit1": 35,
	    "limit2": 35
    },
    "priceGrid": {
        "entryPrice": "",
        "takeProfit1": "20900",
        "takeProfit2": "21000",
        "takeProfit3": "22000",
        "limit1": "19000",
        "limit2": "18500",
        "stopLoss": "18000"
    }
}

If entryPrice is empty then it’s a market order.

What I do. I create orders based on their price and percentage. For example. Imagine BTC current price is $20 000, ok, my total deposit 1000, I have only 5% of it, it’s $50 for my 3 orders (main order 30% of $50 = $15, 1st limit 35% of $50 = $17.5, 2st limit 35% of 50 = $17.5). If leverage is 25 then I try to calculate qty. Main order qty = 15 * 25 / 20 000 = it’s about 0.018. I do the same calculation for others (the 1st limit and the 2nd limit). And the same calculation for the take profit orders of course. The 1st take profit 1 is 60% of 0.018 = about 0.01 qty, the 2nd take profit is 20% of 0.018 = about 0.0036 qty and the 3rd take profit order is going to close a remainder.

I send 7 orders and save them to a database to find them in the future by id.
What I want. If my first take profit order executes bot deletes all limits orders and moves the stop loss to breakeven. To do that I need to know the new entry price.
But if the limit order executes bot deletes old take profit orders and recalculates qty for new take profit orders because we have another entry size in position right now. The old entry size was 0.018 but after the 1st limit order was executed we have more entry size, imagine that the new entry size 0.02 (if the 1st limit order qty was 0.002 = 0.018 + 0.002). After that, I have to create new take profit orders with their percentages of new entry size (0.02). It means I need to know the new entry size.

In another thread or goroutines (I use golang), doesn’t matter, I start to listen to user data stream. When I receive balance and position event Binance API Documentation I save entry size and entry price from this event to the database because I saw it as the only chance to know this data.

In some time the 1st limit order executes and I can get several events. For example, I get 3 order update events Binance API Documentation, 2 of them with the status “Partially Filled” and the 3rd with status “Filled”. How I tried to work with it. I thought after each trade event order I can go to database, read new entry size of position (as I wrote above I save (update) it) delete old take profit orders. After that, I have to recalculate new sizes for new take profit orders based on new entry size and their percentages and place them again. In theory it’s easy because for each trade update I always get balance and position event Binance API Documentation and I can take new entry price and new entry size. BUT…

The problem here is that events can have the same event time and they can arrive to my backend simultaneously or I can get trade event order update firstly without knowing updated entry size and entry price of position, so I can’t just go to database and get the last entry size to recalculate new take profit size because I don’t have a guarantee that entry size I read from DB is the last.

Snippet of code with logic comments for understanding

wsUserDataHandler := func(event *futures.WsUserDataEvent) {
        time.Sleep(2 * time.Second)
        // Trade updates
        if event.OrderTradeUpdate.Symbol != "" {
            oEvent := event.OrderTradeUpdate
            fmt.Println("TRADE UPDATE", oEvent, "TIME", event.Time)
            o := &order.Order{
                Ticker:               oEvent.Symbol,
                OrderID:              oEvent.ID,
                Status:               oEvent.Status,
                AccumulatedFilledQty: oEvent.AccumulatedFilledQty,
                AveragePrice:         oEvent.AveragePrice,
                UpdatedAt:            time.Now().UTC(),
                EventTime:            event.Time,
            }
            _, err := postgres.UpdateOrder(ctx, o)
            if err != nil {
                sugarLogger.Errorf("Update order: %v\n", err.Error())
                return
            }
            ord, err := postgres.FindOrder(ctx, oEvent.Symbol, oEvent.ID)
            if err != nil {
                sugarLogger.Errorf("Find order: %v\n", err.Error())
                return
            }
            if ord == nil {
                return
            }
            if ord.Type == order.TakeProfitOrder1 && oEvent.Status == futures.OrderStatusTypePartiallyFilled {
                fmt.Println("---------------------PARTIALLY FILLED----------------------")
                pos, err := postgres.FindPosition(ctx, oEvent.Symbol, oEvent.PositionSide)
                if err != nil {
                    sugarLogger.Errorf("Find poisition: %v\n", err.Error())
                } else {
                    fmt.Printf("%+v", pos)
                }
                // Get current position (we need entry price)
                // Delete stop limit orders
                // Delete stop loss order
                // Create new stop loss order at breakeven (current entry price from position)
            }
            if ord.Type == order.TakeProfitOrder1 && oEvent.Status == futures.OrderStatusTypeFilled {
                fmt.Println("---------------------ORDER FILLED----------------------")
                pos, err := postgres.FindPosition(ctx, oEvent.Symbol, oEvent.PositionSide)
                if err != nil {
                    sugarLogger.Errorf("Find poisition: %v\n", err.Error())
                } else {
                    fmt.Printf("%+v", pos)
                }
                // Get current position (we need entry price)
                // Delete stop limit orders
                // Delete stop loss order
                // Create new stop loss order at breakeven (current entry price from position)

            }
            if ord.Type == order.LimitOrder1 && oEvent.Status == futures.OrderStatusTypePartiallyFilled {
                fmt.Println("---------------------PARTIALLY FILLED----------------------")
                pos, err := postgres.FindPosition(ctx, oEvent.Symbol, oEvent.PositionSide)
                if err != nil {
                    sugarLogger.Errorf("Find poisition: %v\n", err.Error())
                } else {
                    fmt.Printf("%+v", pos)
                }
                // Get current position (we need entry size)
                // Delete Take profit orders
                // Recalculate new sizes of new take profit orders based on their percentages and entry size from position
                // Place new take profit orders
            }
            if ord.Type == order.LimitOrder1 && oEvent.Status == futures.OrderStatusTypeFilled {
                fmt.Println("---------------------PARTIALLY FILLED----------------------")
                pos, err := postgres.FindPosition(ctx, oEvent.Symbol, oEvent.PositionSide)
                if err != nil {
                    sugarLogger.Errorf("Find poisition: %v\n", err.Error())
                } else {
                    fmt.Printf("%+v", pos)
                }
                // Get current position (we need entry size)
                // Delete Take profit orders
                // Recalculate new sizes of new take profit orders based on their percentages and entry size from position
                // Place new take profit orders
            }
        }

        // Position updates
        if event.AccountUpdate.Reason == futures.UserDataEventReasonTypeOrder {
            if len(event.AccountUpdate.Positions) != 0 {
                fmt.Println("POSITION UPDATE", event.AccountUpdate, "TIME", event.Time)
                for _, curPos := range event.AccountUpdate.Positions {
                    p := &position.Position{
                        Ticker:       curPos.Symbol,
                        PositionSide: curPos.Side,
                        EntryPrice:   curPos.EntryPrice,
                        EntrySize:    curPos.Amount,
                        UpdatedAt:    time.Now().UTC(),
                        EventTime:    event.Time,
                    }
                    _, err := postgres.UpdatePosition(ctx, p)
                    if err != nil {
                        sugarLogger.Errorf("Update position: %v\n", err.Error())
                    }
                }
            }
        }
    }

Could anyone help me please? How do you work with websocket and events correctly. How to do what I want. I’ve been trying to solve it since last Friday.

Hello, I’m so sorry for delay. But link https://testnet.binancefuture.com/myTrades doesn’t work.

Let me check it again please

The answer to this question is subjective to your use case, therefore we can only provide suggestions limited to the context provided here.

There are two suggested solutions to your issues:

  • Either disregard the balance updates, and calculate the balance change locally with the information received from the order update. (This assumes that no other factor impacts your position. If other factors impact your position, then it’s best to “allocate” a certain fund to your strategy and guarantee it is not affected by other strategies).
  • Or store the order changes in memory (preserving order) and correlate the balance events received to sort correctly. (Balance events are always sent after order events).

I’m so sorry, but I made mistakes yesterday when I wrote my question. I’m updating it. Please read again

I save entry size and entry price from this event to the database because I saw it as the only chance to know this data.

Entry size of a strategy can be calculated locally and does not require the balance update event.

Since order events received from the user data stream are in order, I would ignore the balance update events and handle entry price locally. You may also frequently poll the Query Order REST endpoint to double check that your local entry price correctly matches the server’s entry price.

This all assumes that no other strategy affects the same position.

Entry size of a strategy can be calculated locally and does not require the balance update event.

If I understand right for the entry size, when I place the first market order (open position), I need to save its qty (2 as example) and average price ($20 000) to db. I also need to save ids of other orders, for example (open position order id = 1, stop loss id = 2, tp1 id = 3, tp2 id = 4, tp 3 id = 5, limit1 id = 6, limit2 id = 7). When I get some event trade order with status “Partially Filled” and Order Last Filled Quantity (0.5), I’ll check it in db by id, imagine that limit1 was found. I take qty (2) from open position market order and do the following:

  1. Delete old take profit orders
  2. Calculate new entry size = 2 - 0.5 = 1.5 qty
  3. Calculate size of new 1st take profit = 60% of 1.5 = 0.9
  4. Place new 1st take profit
  5. Do the same things for the 2nd take profit
  6. Do the same thing if I get trade event for 1st limit with the status “Filled”
    Is that correct?

Entry size of a strategy can be calculated locally and does not require the balance update event.

Thank you, but what about new entry price if either 1st or 2nd limit orders were executed? I need new entry price to move stop loss to breakeven. Because if either 1st or 2nd limit order were executed I don’t get trade update about open position order. It seems I need to save average price when I place open position order firstly and if trade update event comes from 1st or 2nd limit order I need to take an average price from event and do following:

  1. get average price of open position order ($20 000)
  2. get average price from event ($21000)
  3. calculate 20 000 + 21 000 = 41 000 / 2 = new entry price is $20 500
    BUT it works only if my limit orders execute immediately with status FILLED. What about if it’s Partially Filled. Do you have a formula?

I need new entry price to move stop loss to breakeven. Because if either 1st or 2nd limit order were executed I don’t get trade update about open position order.

This is incorrect. You do receive an order event.

BUT it works only if my limit orders execute immediately with status FILLED. What about if it’s Partially Filled. Do you have a formula?

Entry price formula remains the same, irrespective how much of the order was filled.

new_entry_price = (entry_price*position_quantity + executed_price*executed_quantity)/(position_quantity + executed_quantity)

where entry_price and position_quantity are values prior to the order update.

Thank you very much! I’ve just tried it for some orders. I looked at trade history and calculated
18:53:18 19449.20 | 0.022
18:53:16 19449.20 | 0.022
18:53:11 19449.20 | 0.019

(19449.20 * 0.019 + 19449.20 * 0.022) / (0.019 + 0.022) =
entryPrice = (369.5348 + 427.8824) / 0.041 = 797.4172 / 0.041 = 19449.2
size = 0.019 + 0.022 = 0.041

(19449.2 * 0.041 + 19449.2 * 0.022) / (0.041 + 0.022) =
EntryPrice = (797.4172 + 427.8824) / 0.063 = 1225.2996 / 0.063 = 19449.2
size = 0.041 + 0.022 = 0.063

I got 19449.2 but entry price on Binance is 20,015.13.

It’s because we don’t get max precision from binance, right?

Precision is not the issue here. Was the position 0 prior to 18:53:11 19449.20 | 0.019?

Precision is not the issue here. Was the position 0 prior to 18:53:11 19449.20 | 0.019 ?

Nope. This 18:53:11 19449.20 | 0.019 was the first position (market order)

Please send a list of the trades (from /myTrades) for the following orders

18:53:18 19449.20 | 0.022
18:53:16 19449.20 | 0.022
18:53:11 19449.20 | 0.019

Entry price should be calculated on trade values not orders.