go-binance icon indicating copy to clipboard operation
go-binance copied to clipboard

Support batchOrder for futures

Open Denton-L opened this issue 4 years ago • 12 comments

The Binance futures API allows the placement of multiple orders or the cancellation of multiple orders. We should support this in the library too.

One open question is that I'm not sure how you would shoehorn multiple orders into a fluent API design.

Denton-L avatar Nov 29 '20 06:11 Denton-L

Hi,

I started using this lib a couple of weeks ago and I do miss this future already a bit.

I have looked into code a bit and I don't see easy way for such thing to be implemented. I guess the most basic would be to introduce new service structure that contains array of CreateOrderServices.

Or maybe on today's CreateOrderService introduce new method, idk chain that will return CreateOrderServiceChain. On which you can set new CreateOrderService and the list goes on and on. At the end calling Do on CreateOrderServicesChain will call the multiple orders api.

straddler avatar Dec 12 '20 23:12 straddler

A new method on CreateOrderService is probably not the way to go, in my opinion. If I understand what you're suggesting correctly, it would require a recursive data structure for CreateOrderService that would be somewhat confusing and counterintuitive. As the name implies, a CreateOrderService is for a single order and should stay that way.

I like your first suggestion of a new service structure with a method that either takes either an array of CreateOrderService or can be called multiple times to add new orders. I think that is in keeping with the overall design of the library and should be straightforward to build and test.

OrmEmbaar avatar Dec 13 '20 07:12 OrmEmbaar

I definitely agree that we shouldn't be altering CreateOrderService. Perhaps a new CreateBatchOrderService?

Thinking about it some more, here's my proposal: we have CreateBatchOrderService.CreateOrder() which returns a CreateBatchOrderServiceOrder. From there, CreateBatchOrderServiceOrder has the same fluent design with the same interface as CreateOrderService (as in, you can call CreateBatchOrderServiceOrder.Price().SideType()...). Then finally, you call CreateBatchOrderService.Do()` to execute the batch order.

What do you guys think? If my proposal isn't clear, please let me know.

Denton-L avatar Dec 13 '20 09:12 Denton-L

@Denton-L lets say I want to create 2 orders I would do something like:

client.NewCreateBatchOrderService().CreateOrder().Price(***).SideType(***).CreateOrder().Price(***).SideType(**).Do(***)

I am fine with that, sounds reasonable enough to me from the API standpoint. I dislike the naming a bit, this CreateBatchOrderServiceOrder to many orders in there.

Also I think we don't need that additional service at all, CreateBatchOrderService can just reuse inside CreateOrderService. To me it seems that this new SubService(orders orders) is going to have all the same functionalities as already in place CreateOrderService

straddler avatar Dec 14 '20 21:12 straddler

I read Denton's proposal a little differently. CreateOrder would return a CreateBatchOrderServiceOrder, not a CreateBatchOrderService, meaning that it would have no CreateOrder method. You would need to go back to the original object to create another order, like so:

bo := NewCreateBatchOrderService()        // Returns a CreateBatchOrderService
bo.CreateOrder().Price(***).SideType(***) // Returns a CreateBatchOrderServiceOrder
bo.CreateOrder().Price(***).SideType(***) // Must use the original CreateBatchOrderService to call CreateOrder again.
res, err := bo.Do()

Please correct me if I am wrong, Denton. I think something like that would be okay. For the sake of completeness, here is what I propose:

// Create orders as normal.
o1 := cli.NewCreateOrderService().Price(***).SideType(***)
o2 := cli.NewCreateOrderService().Price(***).SideType(***)

// Add those orders to a batch and call Do
res, err := cli.NewCreateBatchOrdersService().AddOrder(o1).AddOrder(o2).Do()

// or 
res, err := cli.NewCreateBatchOrdersService().AddOrders([]CreateOrderService{o1, o2}).Do()

I think this is the cleaner approach. It requires fewer changes to the codebase and has a smaller overall API surface, meaning a lower cognitive burden for the user. Having said that, I am not super attached to it and would be fine with either.

OrmEmbaar avatar Dec 14 '20 22:12 OrmEmbaar

Yep, you read it correctly. Between your proposal and mine, I like yours much better. Given the way my code interacts with this library, it is much easier for me to integrate your design than it would be for mine.

Denton-L avatar Dec 14 '20 22:12 Denton-L

@afenrir great proposal, I really like it!

straddler avatar Dec 16 '20 20:12 straddler

Hey, folks. What is a status of NewCreateBatchOrdersService? I'm trying to use it but it does not fill my orders

var orders []*futures.CreateOrderService
	log.Println("[exchange][CreateOrder] Creating base order")
	baseOrder := s.client.NewCreateOrderService().
		Symbol(s.pair.BaseName).
		Side(futures.SideType(s.pair.BaseSide)).
		Quantity(s.pair.BaseAmount).
		Type(futures.OrderTypeMarket)
	log.Println("[exchange][CreateOrder] Creating quote order")
	quoteOrder := s.client.NewCreateOrderService().
		Symbol(s.pair.QuoteName).
		Side(futures.SideType(s.pair.QuoteSide)).
		Quantity(s.pair.QuoteAmount).
		Type(futures.OrderTypeMarket)

	orders = append(orders, baseOrder, quoteOrder)
	log.Println("[exchange][CreateOrder] Executing multiple orders")
	res, err := s.client.NewCreateBatchOrdersService().OrderList(orders).Do(context.Background())

	if err != nil {
		log.Println("[exchange][CreateOrder] Error from multiple orders")
		return nil, err
	}

It returns me just a nil in res

P.S. If I change it to single order execution adding Do() after orders, then execution works, but with two requests. P.P.S. client.Debug = true, shows me the truth :)

Binance-golang 2021/11/16 23:28:22 response body: [ {
  "code" : -4003,
  "msg" : "Quantity less than zero."
}, {
  "code" : -4003,
  "msg" : "Quantity less than zero."
} ]
Binance-golang 2021/11/16 23:28:22 response status code: 200
2021/11/16 23:28:22 &{[]}

That was expected error from binance i want to see

But, how i can get this errors from a response, because I need it to report

boskiv avatar Nov 17 '21 05:11 boskiv

Errors are located in `rawMessages array, but it can't be unmarshaled to Order structure and no error about it. :( Screen Shot 2021-11-16 at 23 39 15 Screen Shot 2021-11-16 at 23 39 11

boskiv avatar Nov 17 '21 05:11 boskiv

Maybe something like this should help

func StrictUnmarshal(data []byte, v interface{}) error {
	dec := json.NewDecoder(bytes.NewReader(data))
	dec.DisallowUnknownFields()
	return dec.Decode(v)
}

boskiv avatar Nov 17 '21 05:11 boskiv

@adshao

boskiv avatar Nov 17 '21 05:11 boskiv

@adshao

sorry, I just found the @ . Yes, since Binance returned status code 200 for the failed request, so it did not try to convert response body to error. We should retry to convert to error if it failed to convert to Order.

adshao avatar Dec 16 '21 14:12 adshao