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.