-
Notifications
You must be signed in to change notification settings - Fork 778
support place algo order via websocket api #795
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
|
@wanxsb thank you for your contribution |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This pull request adds WebSocket API support for algorithmic orders in the Binance futures trading API, specifically for placing and canceling algo orders. The implementation follows the established patterns used in other WebSocket services within the codebase.
Key changes:
- Added
AlgoOrderPlaceWsServicefor placing algorithmic orders via WebSocket - Added
AlgoOrderCancelWsServicefor canceling algorithmic orders via WebSocket - Both services support synchronous and asynchronous operation modes
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| v2/algo_order_place_service_ws.go | Implements WebSocket service for placing algo orders with full parameter support (symbol, side, type, price, quantity, etc.) |
| v2/algo_order_cancel_service_ws.go | Implements WebSocket service for canceling algo orders by algoId or clientAlgoId |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| package binance | ||
|
|
||
| import ( | ||
| "encoding/json" | ||
| "time" | ||
|
|
||
| "github.com/adshao/go-binance/v2/common" | ||
| "github.com/adshao/go-binance/v2/common/websocket" | ||
| "github.com/adshao/go-binance/v2/futures" | ||
| ) | ||
|
|
||
| // AlgoOrderCancelWsService cancels algo order using WebSocket API | ||
| type AlgoOrderCancelWsService struct { | ||
| c websocket.Client | ||
| ApiKey string | ||
| SecretKey string | ||
| KeyType string | ||
| TimeOffset int64 | ||
| } | ||
|
|
||
| // NewAlgoOrderCancelWsService init AlgoOrderCancelWsService | ||
| func NewAlgoOrderCancelWsService(apiKey, secretKey string) (*AlgoOrderCancelWsService, error) { | ||
| conn, err := websocket.NewConnection(futures.WsApiInitReadWriteConn, futures.WebsocketKeepalive, futures.WebsocketTimeoutReadWriteConnection) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| client, err := websocket.NewClient(conn) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| return &AlgoOrderCancelWsService{ | ||
| c: client, | ||
| ApiKey: apiKey, | ||
| SecretKey: secretKey, | ||
| KeyType: common.KeyTypeHmac, | ||
| }, nil | ||
| } | ||
|
|
||
| // AlgoOrderCancelWsRequest parameters for 'algoOrder.cancel' websocket API | ||
| type AlgoOrderCancelWsRequest struct { | ||
| algoId *int64 | ||
| clientAlgoId *string | ||
| recvWindow *int64 | ||
| } | ||
|
|
||
| // NewAlgoOrderCancelWsRequest init AlgoOrderCancelWsRequest | ||
| func NewAlgoOrderCancelWsRequest() *AlgoOrderCancelWsRequest { | ||
| return &AlgoOrderCancelWsRequest{} | ||
| } | ||
|
|
||
| // AlgoID set algoID | ||
| func (s *AlgoOrderCancelWsRequest) AlgoID(algoID int64) *AlgoOrderCancelWsRequest { | ||
| s.algoId = &algoID | ||
| return s | ||
| } | ||
|
|
||
| // ClientAlgoID set clientAlgoID | ||
| func (s *AlgoOrderCancelWsRequest) ClientAlgoID(clientAlgoID string) *AlgoOrderCancelWsRequest { | ||
| s.clientAlgoId = &clientAlgoID | ||
| return s | ||
| } | ||
|
|
||
| // RecvWindow set recvWindow | ||
| func (s *AlgoOrderCancelWsRequest) RecvWindow(recvWindow int64) *AlgoOrderCancelWsRequest { | ||
| s.recvWindow = &recvWindow | ||
| return s | ||
| } | ||
|
|
||
| // buildParams builds params | ||
| func (s *AlgoOrderCancelWsRequest) buildParams() map[string]interface{} { | ||
| m := map[string]interface{}{} | ||
|
|
||
| if s.algoId != nil { | ||
| m["algoid"] = *s.algoId | ||
| } | ||
|
|
||
| if s.clientAlgoId != nil { | ||
| m["clientalgoid"] = *s.clientAlgoId | ||
| } | ||
|
|
||
| if s.recvWindow != nil { | ||
| m["recvWindow"] = *s.recvWindow | ||
| } | ||
|
|
||
| return m | ||
| } | ||
|
|
||
| // CancelAlgoOrderResult define algo order cancel result | ||
| type CancelAlgoOrderResult struct { | ||
| AlgoId int64 `json:"algoId"` | ||
| ClientAlgoId string `json:"clientAlgoId"` | ||
| Code string `json:"code"` | ||
| Message string `json:"msg"` | ||
| } | ||
|
|
||
| // CancelAlgoOrderWsResponse define 'algoOrder.cancel' websocket API response | ||
| type CancelAlgoOrderWsResponse struct { | ||
| Id string `json:"id"` | ||
| Status int `json:"status"` | ||
| Result CancelAlgoOrderResult `json:"result"` | ||
|
|
||
| // error response | ||
| Error *common.APIError `json:"error,omitempty"` | ||
| } | ||
|
|
||
| // Do - sends 'algoOrder.cancel' request | ||
| func (s *AlgoOrderCancelWsService) Do(requestID string, request *AlgoOrderCancelWsRequest) error { | ||
| // Use custom method "algoOrder.cancel" | ||
| method := websocket.WsApiMethodType("algoOrder.cancel") | ||
|
|
||
| rawData, err := websocket.CreateRequest( | ||
| websocket.NewRequestData( | ||
| requestID, | ||
| s.ApiKey, | ||
| s.SecretKey, | ||
| s.TimeOffset, | ||
| s.KeyType, | ||
| ), | ||
| method, | ||
| request.buildParams(), | ||
| ) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| if err := s.c.Write(requestID, rawData); err != nil { | ||
| return err | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // SyncDo - sends 'algoOrder.cancel' request and receives response | ||
| func (s *AlgoOrderCancelWsService) SyncDo(requestID string, request *AlgoOrderCancelWsRequest) (*CancelAlgoOrderWsResponse, error) { | ||
| // Use custom method "algoOrder.cancel" | ||
| method := websocket.WsApiMethodType("algoOrder.cancel") | ||
|
|
||
| rawData, err := websocket.CreateRequest( | ||
| websocket.NewRequestData( | ||
| requestID, | ||
| s.ApiKey, | ||
| s.SecretKey, | ||
| s.TimeOffset, | ||
| s.KeyType, | ||
| ), | ||
| method, | ||
| request.buildParams(), | ||
| ) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| response, err := s.c.WriteSync(requestID, rawData, websocket.WriteSyncWsTimeout) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| cancelAlgoOrderWsResponse := &CancelAlgoOrderWsResponse{} | ||
| if err := json.Unmarshal(response, cancelAlgoOrderWsResponse); err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| return cancelAlgoOrderWsResponse, nil | ||
| } | ||
|
|
||
| // ReceiveAllDataBeforeStop waits until all responses will be received from websocket until timeout expired | ||
| func (s *AlgoOrderCancelWsService) ReceiveAllDataBeforeStop(timeout time.Duration) { | ||
| s.c.Wait(timeout) | ||
| } | ||
|
|
||
| // GetReadChannel returns channel with API response data (including API errors) | ||
| func (s *AlgoOrderCancelWsService) GetReadChannel() <-chan []byte { | ||
| return s.c.GetReadChannel() | ||
| } | ||
|
|
||
| // GetReadErrorChannel returns channel with errors which are occurred while reading websocket connection | ||
| func (s *AlgoOrderCancelWsService) GetReadErrorChannel() <-chan error { | ||
| return s.c.GetReadErrorChannel() | ||
| } | ||
|
|
||
| // GetReconnectCount returns count of reconnect attempts by client | ||
| func (s *AlgoOrderCancelWsService) GetReconnectCount() int64 { | ||
| return s.c.GetReconnectCount() | ||
| } | ||
|
|
Copilot
AI
Jan 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new AlgoOrderCancelWsService lacks test coverage. Other similar WebSocket services in this codebase (e.g., order_service_ws_create.go, sor_order_place_service_ws.go, order_list_cancel_service_ws.go) all have corresponding test files. Consider adding comprehensive tests to verify the service initialization, request building, parameter handling, and response parsing.
| m := map[string]interface{}{} | ||
|
|
||
| if s.algoId != nil { | ||
| m["algoid"] = *s.algoId |
Copilot
AI
Jan 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The parameter name should be "algoId" (camelCase) instead of "algoid" to match the API specification and maintain consistency with the REST API implementation in futures/algo_order_service.go which uses "algoId".
| m["algoid"] = *s.algoId | |
| m["algoId"] = *s.algoId |
| } | ||
|
|
||
| if s.clientAlgoId != nil { | ||
| m["clientalgoid"] = *s.clientAlgoId |
Copilot
AI
Jan 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The parameter name should be "clientAlgoId" (camelCase) instead of "clientalgoid" to match the API specification and maintain consistency with the REST API implementation in futures/algo_order_service.go which uses "clientAlgoId".
| m["clientalgoid"] = *s.clientAlgoId | |
| m["clientAlgoId"] = *s.clientAlgoId |
| package binance | ||
|
|
||
| import ( | ||
| "encoding/json" | ||
| "time" | ||
|
|
||
| "github.com/adshao/go-binance/v2/common" | ||
| "github.com/adshao/go-binance/v2/common/websocket" | ||
| "github.com/adshao/go-binance/v2/futures" | ||
| ) | ||
|
|
||
| // AlgoOrderPlaceWsService creates algo order using WebSocket API | ||
| type AlgoOrderPlaceWsService struct { | ||
| c websocket.Client | ||
| ApiKey string | ||
| SecretKey string | ||
| KeyType string | ||
| TimeOffset int64 | ||
| } | ||
|
|
||
| // NewAlgoOrderPlaceWsService init AlgoOrderPlaceWsService | ||
| func NewAlgoOrderPlaceWsService(apiKey, secretKey string) (*AlgoOrderPlaceWsService, error) { | ||
| conn, err := websocket.NewConnection(futures.WsApiInitReadWriteConn, futures.WebsocketKeepalive, futures.WebsocketTimeoutReadWriteConnection) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| client, err := websocket.NewClient(conn) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| return &AlgoOrderPlaceWsService{ | ||
| c: client, | ||
| ApiKey: apiKey, | ||
| SecretKey: secretKey, | ||
| KeyType: common.KeyTypeHmac, | ||
| }, nil | ||
| } | ||
|
|
||
| // AlgoOrderPlaceWsRequest parameters for 'algoOrder.place' websocket API | ||
| type AlgoOrderPlaceWsRequest struct { | ||
| algoType futures.OrderAlgoType | ||
| symbol string | ||
| side futures.SideType | ||
| _type futures.AlgoOrderType | ||
| positionSide *futures.PositionSideType | ||
| timeInForce *futures.TimeInForceType | ||
| quantity *string | ||
| price *string | ||
| triggerPrice string | ||
| workingType *futures.WorkingType | ||
| closePosition *bool | ||
| reduceOnly *bool | ||
| newClientOrderID *string | ||
| newOrderRespType futures.NewOrderRespType | ||
| recvWindow *int64 | ||
| } | ||
|
|
||
| // NewAlgoOrderPlaceWsRequest init AlgoOrderPlaceWsRequest | ||
| func NewAlgoOrderPlaceWsRequest() *AlgoOrderPlaceWsRequest { | ||
| return &AlgoOrderPlaceWsRequest{ | ||
| algoType: futures.OrderAlgoTypeConditional, | ||
| newOrderRespType: futures.NewOrderRespTypeRESULT, | ||
| } | ||
| } | ||
|
|
||
| // Symbol set symbol | ||
| func (s *AlgoOrderPlaceWsRequest) Symbol(symbol string) *AlgoOrderPlaceWsRequest { | ||
| s.symbol = symbol | ||
| return s | ||
| } | ||
|
|
||
| // Side set side | ||
| func (s *AlgoOrderPlaceWsRequest) Side(side futures.SideType) *AlgoOrderPlaceWsRequest { | ||
| s.side = side | ||
| return s | ||
| } | ||
|
|
||
| // Type set type | ||
| func (s *AlgoOrderPlaceWsRequest) Type(_type futures.AlgoOrderType) *AlgoOrderPlaceWsRequest { | ||
| s._type = _type | ||
| return s | ||
| } | ||
|
|
||
| // PositionSide set positionSide | ||
| func (s *AlgoOrderPlaceWsRequest) PositionSide(positionSide futures.PositionSideType) *AlgoOrderPlaceWsRequest { | ||
| s.positionSide = &positionSide | ||
| return s | ||
| } | ||
|
|
||
| // TimeInForce set timeInForce | ||
| func (s *AlgoOrderPlaceWsRequest) TimeInForce(timeInForce futures.TimeInForceType) *AlgoOrderPlaceWsRequest { | ||
| s.timeInForce = &timeInForce | ||
| return s | ||
| } | ||
|
|
||
| // Quantity set quantity | ||
| func (s *AlgoOrderPlaceWsRequest) Quantity(quantity string) *AlgoOrderPlaceWsRequest { | ||
| s.quantity = &quantity | ||
| return s | ||
| } | ||
|
|
||
| // Price set price | ||
| func (s *AlgoOrderPlaceWsRequest) Price(price string) *AlgoOrderPlaceWsRequest { | ||
| s.price = &price | ||
| return s | ||
| } | ||
|
|
||
| // TriggerPrice set triggerPrice | ||
| func (s *AlgoOrderPlaceWsRequest) TriggerPrice(triggerPrice string) *AlgoOrderPlaceWsRequest { | ||
| s.triggerPrice = triggerPrice | ||
| return s | ||
| } | ||
|
|
||
| // WorkingType set workingType | ||
| func (s *AlgoOrderPlaceWsRequest) WorkingType(workingType futures.WorkingType) *AlgoOrderPlaceWsRequest { | ||
| s.workingType = &workingType | ||
| return s | ||
| } | ||
|
|
||
| // ClosePosition set closePosition | ||
| func (s *AlgoOrderPlaceWsRequest) ClosePosition(closePosition bool) *AlgoOrderPlaceWsRequest { | ||
| s.closePosition = &closePosition | ||
| return s | ||
| } | ||
|
|
||
| // ReduceOnly set reduceOnly | ||
| func (s *AlgoOrderPlaceWsRequest) ReduceOnly(reduceOnly bool) *AlgoOrderPlaceWsRequest { | ||
| s.reduceOnly = &reduceOnly | ||
| return s | ||
| } | ||
|
|
||
| // NewClientOrderID set newClientOrderID | ||
| func (s *AlgoOrderPlaceWsRequest) NewClientOrderID(newClientOrderID string) *AlgoOrderPlaceWsRequest { | ||
| s.newClientOrderID = &newClientOrderID | ||
| return s | ||
| } | ||
|
|
||
| // NewOrderResponseType set newOrderResponseType | ||
| func (s *AlgoOrderPlaceWsRequest) NewOrderResponseType(newOrderResponseType futures.NewOrderRespType) *AlgoOrderPlaceWsRequest { | ||
| s.newOrderRespType = newOrderResponseType | ||
| return s | ||
| } | ||
|
|
||
| // RecvWindow set recvWindow | ||
| func (s *AlgoOrderPlaceWsRequest) RecvWindow(recvWindow int64) *AlgoOrderPlaceWsRequest { | ||
| s.recvWindow = &recvWindow | ||
| return s | ||
| } | ||
|
|
||
| // buildParams builds params | ||
| func (s *AlgoOrderPlaceWsRequest) buildParams() map[string]interface{} { | ||
| m := map[string]interface{}{ | ||
| "algoType": s.algoType, | ||
| "symbol": s.symbol, | ||
| "side": s.side, | ||
| "type": s._type, | ||
| "triggerPrice": s.triggerPrice, | ||
| "newOrderRespType": s.newOrderRespType, | ||
| } | ||
|
|
||
| if s.positionSide != nil { | ||
| m["positionSide"] = *s.positionSide | ||
| } | ||
| if s.timeInForce != nil { | ||
| m["timeInForce"] = *s.timeInForce | ||
| } | ||
| if s.quantity != nil { | ||
| m["quantity"] = *s.quantity | ||
| } | ||
| if s.price != nil { | ||
| m["price"] = *s.price | ||
| } | ||
| if s.workingType != nil { | ||
| m["workingType"] = *s.workingType | ||
| } | ||
| if s.closePosition != nil { | ||
| m["closePosition"] = *s.closePosition | ||
| } | ||
| if s.reduceOnly != nil { | ||
| m["reduceOnly"] = *s.reduceOnly | ||
| } | ||
| if s.newClientOrderID != nil { | ||
| m["newClientOrderId"] = *s.newClientOrderID | ||
| } | ||
| if s.recvWindow != nil { | ||
| m["recvWindow"] = *s.recvWindow | ||
| } | ||
|
|
||
| return m | ||
| } | ||
|
|
||
| // CreateAlgoOrderResult define algo order creation result | ||
| type CreateAlgoOrderResult struct { | ||
| AlgoId int64 `json:"algoId"` | ||
| ClientAlgoId string `json:"clientAlgoId"` | ||
| } | ||
|
|
||
| // CreateAlgoOrderWsResponse define 'algoOrder.place' websocket API response | ||
| type CreateAlgoOrderWsResponse struct { | ||
| Id string `json:"id"` | ||
| Status int `json:"status"` | ||
| Result CreateAlgoOrderResult `json:"result"` | ||
|
|
||
| // error response | ||
| Error *common.APIError `json:"error,omitempty"` | ||
| } | ||
|
|
||
| // SyncDo - sends 'algoOrder.place' request and receives response | ||
| func (s *AlgoOrderPlaceWsService) SyncDo(requestID string, request *AlgoOrderPlaceWsRequest) (*CreateAlgoOrderWsResponse, error) { | ||
| // Use custom method "algoOrder.place" | ||
| method := websocket.WsApiMethodType("algoOrder.place") | ||
|
|
||
| rawData, err := websocket.CreateRequest( | ||
| websocket.NewRequestData( | ||
| requestID, | ||
| s.ApiKey, | ||
| s.SecretKey, | ||
| s.TimeOffset, | ||
| s.KeyType, | ||
| ), | ||
| method, | ||
| request.buildParams(), | ||
| ) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| response, err := s.c.WriteSync(requestID, rawData, websocket.WriteSyncWsTimeout) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| createAlgoOrderWsResponse := &CreateAlgoOrderWsResponse{} | ||
| if err := json.Unmarshal(response, createAlgoOrderWsResponse); err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| return createAlgoOrderWsResponse, nil | ||
| } | ||
|
|
||
| // ReceiveAllDataBeforeStop waits until all responses will be received from websocket until timeout expired | ||
| func (s *AlgoOrderPlaceWsService) ReceiveAllDataBeforeStop(timeout time.Duration) { | ||
| s.c.Wait(timeout) | ||
| } | ||
|
|
||
| // GetReadChannel returns channel with API response data (including API errors) | ||
| func (s *AlgoOrderPlaceWsService) GetReadChannel() <-chan []byte { | ||
| return s.c.GetReadChannel() | ||
| } | ||
|
|
||
| // GetReadErrorChannel returns channel with errors which are occurred while reading websocket connection | ||
| func (s *AlgoOrderPlaceWsService) GetReadErrorChannel() <-chan error { | ||
| return s.c.GetReadErrorChannel() | ||
| } | ||
|
|
||
| // GetReconnectCount returns count of reconnect attempts by client | ||
| func (s *AlgoOrderPlaceWsService) GetReconnectCount() int64 { | ||
| return s.c.GetReconnectCount() | ||
| } | ||
|
|
||
|
|
Copilot
AI
Jan 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new AlgoOrderPlaceWsService lacks test coverage. Other similar WebSocket services in this codebase (e.g., order_service_ws_create.go, sor_order_place_service_ws.go, order_list_place_service_ws.go) all have corresponding test files. Consider adding comprehensive tests to verify the service initialization, request building, parameter handling, and response parsing.
|
@wanxsb can you please check the formatting? |
No description provided.