mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-21 23:16:49 +00:00
gctrpc/ordermanager/binance: Add new getManagedOrders command and various improvements (#712)
* first draft of getmanaged orders RPC call * - ClientIDs for binance, especially spot asset - applied old ClientOrderId for cancelled orders - added clientOrderId to GCTRPC * added tests for Matchfilter and GetManagedOrders * smaller fixes * comment fix added getFilteredOrders to store changed store mutex to RWMutex smaller fixes * fixed bug in Detail Copy and added test * fixes for Scotts review * processSubmittedOrder was missing clientOrderId * changed: TestGetOrdersFiltered expanded fixed: warning, where variable name collided with package name fixed: used req.AssetType in binance_wrapper.go Co-authored-by: Mark Dzulko <81071907+Mark-numus@users.noreply.github.com>
This commit is contained in:
@@ -526,7 +526,7 @@ func (b *Binance) newOrder(api string, o *NewOrderRequest, resp *NewOrderRespons
|
||||
}
|
||||
|
||||
if o.NewClientOrderID != "" {
|
||||
params.Set("newClientOrderID", o.NewClientOrderID)
|
||||
params.Set("newClientOrderId", o.NewClientOrderID)
|
||||
}
|
||||
|
||||
if o.StopPrice != 0 {
|
||||
|
||||
@@ -2498,7 +2498,7 @@ func TestWsOrderExecutionReport(t *testing.T) {
|
||||
Amount: 0.00028400,
|
||||
Exchange: "Binance",
|
||||
ID: "5340845958",
|
||||
ClientID: "c4wyKsIhoAaittTYlIVLqk",
|
||||
ClientOrderID: "c4wyKsIhoAaittTYlIVLqk",
|
||||
Side: order.Buy,
|
||||
Type: order.Limit,
|
||||
Status: order.New,
|
||||
|
||||
@@ -240,6 +240,10 @@ func (b *Binance) wsHandleData(respRaw []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
clientOrderID := data.Data.ClientOrderID
|
||||
if oStatus == order.Cancelled {
|
||||
clientOrderID = data.Data.CancelledClientOrderID
|
||||
}
|
||||
b.Websocket.DataHandler <- &order.Detail{
|
||||
Price: data.Data.Price,
|
||||
Amount: data.Data.Quantity,
|
||||
@@ -253,7 +257,7 @@ func (b *Binance) wsHandleData(respRaw []byte) error {
|
||||
AssetType: a,
|
||||
Date: data.Data.OrderCreationTime,
|
||||
Pair: p,
|
||||
ClientID: data.Data.ClientOrderID,
|
||||
ClientOrderID: clientOrderID,
|
||||
}
|
||||
return nil
|
||||
case "listStatus":
|
||||
|
||||
@@ -805,12 +805,13 @@ func (b *Binance) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) {
|
||||
}
|
||||
|
||||
var orderRequest = NewOrderRequest{
|
||||
Symbol: s.Pair,
|
||||
Side: sideType,
|
||||
Price: s.Price,
|
||||
Quantity: s.Amount,
|
||||
TradeType: requestParamsOrderType,
|
||||
TimeInForce: timeInForce,
|
||||
Symbol: s.Pair,
|
||||
Side: sideType,
|
||||
Price: s.Price,
|
||||
Quantity: s.Amount,
|
||||
TradeType: requestParamsOrderType,
|
||||
TimeInForce: timeInForce,
|
||||
NewClientOrderID: s.ClientOrderID,
|
||||
}
|
||||
response, err := b.NewOrder(&orderRequest)
|
||||
if err != nil {
|
||||
@@ -864,14 +865,14 @@ func (b *Binance) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) {
|
||||
default:
|
||||
return submitOrderResponse, errors.New("invalid type, check api docs for updates")
|
||||
}
|
||||
order, err := b.FuturesNewOrder(s.Pair, reqSide,
|
||||
o, err := b.FuturesNewOrder(s.Pair, reqSide,
|
||||
"", oType, "GTC", "",
|
||||
s.ClientOrderID, "", "",
|
||||
s.Amount, s.Price, 0, 0, 0, s.ReduceOnly)
|
||||
if err != nil {
|
||||
return submitOrderResponse, err
|
||||
}
|
||||
submitOrderResponse.OrderID = strconv.FormatInt(order.OrderID, 10)
|
||||
submitOrderResponse.OrderID = strconv.FormatInt(o.OrderID, 10)
|
||||
submitOrderResponse.IsOrderPlaced = true
|
||||
case asset.USDTMarginedFutures:
|
||||
var reqSide string
|
||||
@@ -1050,6 +1051,7 @@ func (b *Binance) GetOrderInfo(orderID string, pair currency.Pair, assetType ass
|
||||
Amount: resp.OrigQty,
|
||||
Exchange: b.Name,
|
||||
ID: strconv.FormatInt(resp.OrderID, 10),
|
||||
ClientOrderID: resp.ClientOrderID,
|
||||
Side: orderSide,
|
||||
Type: orderType,
|
||||
Pair: pair,
|
||||
@@ -1191,17 +1193,18 @@ func (b *Binance) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail,
|
||||
orderSide := order.Side(strings.ToUpper(resp[x].Side))
|
||||
orderType := order.Type(strings.ToUpper(resp[x].Type))
|
||||
orders = append(orders, order.Detail{
|
||||
Amount: resp[x].OrigQty,
|
||||
Date: resp[x].Time,
|
||||
Exchange: b.Name,
|
||||
ID: strconv.FormatInt(resp[x].OrderID, 10),
|
||||
Side: orderSide,
|
||||
Type: orderType,
|
||||
Price: resp[x].Price,
|
||||
Status: order.Status(resp[x].Status),
|
||||
Pair: req.Pairs[i],
|
||||
AssetType: asset.Spot,
|
||||
LastUpdated: resp[x].UpdateTime,
|
||||
Amount: resp[x].OrigQty,
|
||||
Date: resp[x].Time,
|
||||
Exchange: b.Name,
|
||||
ID: strconv.FormatInt(resp[x].OrderID, 10),
|
||||
ClientOrderID: resp[x].ClientOrderID,
|
||||
Side: orderSide,
|
||||
Type: orderType,
|
||||
Price: resp[x].Price,
|
||||
Status: order.Status(resp[x].Status),
|
||||
Pair: req.Pairs[i],
|
||||
AssetType: req.AssetType,
|
||||
LastUpdated: resp[x].UpdateTime,
|
||||
})
|
||||
}
|
||||
case asset.CoinMarginedFutures:
|
||||
|
||||
@@ -2,6 +2,7 @@ package order
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -277,6 +278,12 @@ func TestFilterOrdersByCurrencies(t *testing.T) {
|
||||
t.Errorf("Orders failed to be filtered. Expected %v, received %v", 1, len(orders))
|
||||
}
|
||||
|
||||
currencies = []currency.Pair{currency.NewPair(currency.USD, currency.BTC)}
|
||||
FilterOrdersByCurrencies(&orders, currencies)
|
||||
if len(orders) != 1 {
|
||||
t.Errorf("Reverse Orders failed to be filtered. Expected %v, received %v", 1, len(orders))
|
||||
}
|
||||
|
||||
currencies = []currency.Pair{}
|
||||
FilterOrdersByCurrencies(&orders, currencies)
|
||||
if len(orders) != 1 {
|
||||
@@ -1100,3 +1107,129 @@ func TestValidationOnOrderTypes(t *testing.T) {
|
||||
t.Fatal("unexpected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatchFilter(t *testing.T) {
|
||||
filters := map[int]Filter{
|
||||
0: {},
|
||||
1: {Exchange: "Binance"},
|
||||
2: {InternalOrderID: "1234"},
|
||||
3: {ID: "2222"},
|
||||
4: {ClientOrderID: "3333"},
|
||||
5: {ClientID: "4444"},
|
||||
6: {WalletAddress: "5555"},
|
||||
7: {Type: AnyType},
|
||||
8: {Type: Limit},
|
||||
9: {Side: AnySide},
|
||||
10: {Side: Sell},
|
||||
11: {Status: AnyStatus},
|
||||
12: {Status: New},
|
||||
13: {AssetType: asset.Spot},
|
||||
14: {Pair: currency.NewPair(currency.BTC, currency.USD)},
|
||||
15: {Exchange: "Binance", Type: Limit, Status: New},
|
||||
16: {Exchange: "Binance", Type: AnyType},
|
||||
17: {AccountID: "8888"},
|
||||
}
|
||||
|
||||
orders := map[int]Detail{
|
||||
0: {},
|
||||
1: {Exchange: "Binance"},
|
||||
2: {InternalOrderID: "1234"},
|
||||
3: {ID: "2222"},
|
||||
4: {ClientOrderID: "3333"},
|
||||
5: {ClientID: "4444"},
|
||||
6: {WalletAddress: "5555"},
|
||||
7: {Type: AnyType},
|
||||
8: {Type: Limit},
|
||||
9: {Side: AnySide},
|
||||
10: {Side: Sell},
|
||||
11: {Status: AnyStatus},
|
||||
12: {Status: New},
|
||||
13: {AssetType: asset.Spot},
|
||||
14: {Pair: currency.NewPair(currency.BTC, currency.USD)},
|
||||
15: {Exchange: "Binance", Type: Limit, Status: New},
|
||||
16: {AccountID: "8888"},
|
||||
}
|
||||
// empty filter tests
|
||||
emptyFilter := filters[0]
|
||||
for _, o := range orders {
|
||||
if !o.MatchFilter(&emptyFilter) {
|
||||
t.Error("empty filter should match everything")
|
||||
}
|
||||
}
|
||||
|
||||
tests := map[int]struct {
|
||||
f Filter
|
||||
o Detail
|
||||
expRes bool
|
||||
}{
|
||||
0: {filters[1], orders[1], true},
|
||||
1: {filters[1], orders[0], false},
|
||||
2: {filters[2], orders[2], true},
|
||||
3: {filters[2], orders[3], false},
|
||||
4: {filters[3], orders[3], true},
|
||||
5: {filters[3], orders[4], false},
|
||||
6: {filters[4], orders[4], true},
|
||||
7: {filters[4], orders[5], false},
|
||||
8: {filters[5], orders[5], true},
|
||||
9: {filters[5], orders[6], false},
|
||||
10: {filters[6], orders[6], true},
|
||||
11: {filters[6], orders[7], false},
|
||||
12: {filters[7], orders[7], true},
|
||||
13: {filters[7], orders[8], true},
|
||||
14: {filters[7], orders[9], true},
|
||||
15: {filters[8], orders[7], false},
|
||||
16: {filters[8], orders[8], true},
|
||||
17: {filters[8], orders[9], false},
|
||||
18: {filters[9], orders[9], true},
|
||||
19: {filters[9], orders[10], true},
|
||||
20: {filters[9], orders[11], true},
|
||||
21: {filters[10], orders[10], true},
|
||||
22: {filters[10], orders[11], false},
|
||||
23: {filters[10], orders[9], false},
|
||||
24: {filters[11], orders[11], true},
|
||||
25: {filters[11], orders[12], true},
|
||||
26: {filters[11], orders[10], true},
|
||||
27: {filters[12], orders[12], true},
|
||||
28: {filters[12], orders[13], false},
|
||||
29: {filters[12], orders[11], false},
|
||||
30: {filters[13], orders[13], true},
|
||||
31: {filters[13], orders[12], false},
|
||||
32: {filters[14], orders[14], true},
|
||||
33: {filters[14], orders[13], false},
|
||||
34: {filters[15], orders[15], true},
|
||||
35: {filters[16], orders[15], true},
|
||||
36: {filters[17], orders[16], true},
|
||||
37: {filters[17], orders[15], false},
|
||||
}
|
||||
// specific tests
|
||||
for num, tt := range tests {
|
||||
if tt.o.MatchFilter(&tt.f) != tt.expRes {
|
||||
t.Errorf("tests[%v] failed", num)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDetail_Copy(t *testing.T) {
|
||||
d := []Detail{
|
||||
{
|
||||
Exchange: "Binance",
|
||||
},
|
||||
{
|
||||
Exchange: "Binance",
|
||||
Trades: []TradeHistory{
|
||||
{Price: 1},
|
||||
},
|
||||
},
|
||||
}
|
||||
for i := range d {
|
||||
r := d[i].Copy()
|
||||
if !reflect.DeepEqual(d[i], r) {
|
||||
t.Errorf("[%d] Copy does not contain same elements, expected: %v\ngot:%v", i, d[i], r)
|
||||
}
|
||||
if len(d[i].Trades) > 0 {
|
||||
if &d[i].Trades[0] == &r.Trades[0] {
|
||||
t.Errorf("[%d]Trades point to the same data elements", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,6 +151,23 @@ type Detail struct {
|
||||
Trades []TradeHistory
|
||||
}
|
||||
|
||||
// Filter contains all properties an order can be filtered for
|
||||
// empty strings indicate to ignore the property otherwise all need to match
|
||||
type Filter struct {
|
||||
Exchange string
|
||||
InternalOrderID string
|
||||
ID string
|
||||
ClientOrderID string
|
||||
AccountID string
|
||||
ClientID string
|
||||
WalletAddress string
|
||||
Type Type
|
||||
Side Side
|
||||
Status Status
|
||||
AssetType asset.Item
|
||||
Pair currency.Pair
|
||||
}
|
||||
|
||||
// Cancel contains all properties that may be required
|
||||
// to cancel an order on an exchange
|
||||
// Each exchange has their own requirements, so not all fields
|
||||
|
||||
@@ -359,6 +359,58 @@ func (d *Detail) UpdateOrderFromModify(m *Modify) {
|
||||
}
|
||||
}
|
||||
|
||||
// MatchFilter will return true if a detail matches the filter criteria
|
||||
// empty elements are ignored
|
||||
func (d *Detail) MatchFilter(f *Filter) bool {
|
||||
if f.Exchange != "" && !strings.EqualFold(d.Exchange, f.Exchange) {
|
||||
return false
|
||||
}
|
||||
if f.AssetType != "" && d.AssetType != f.AssetType {
|
||||
return false
|
||||
}
|
||||
if !f.Pair.IsEmpty() && !d.Pair.Equal(f.Pair) {
|
||||
return false
|
||||
}
|
||||
if f.ID != "" && d.ID != f.ID {
|
||||
return false
|
||||
}
|
||||
if f.Type != "" && f.Type != AnyType && d.Type != f.Type {
|
||||
return false
|
||||
}
|
||||
if f.Side != "" && f.Side != AnySide && d.Side != f.Side {
|
||||
return false
|
||||
}
|
||||
if f.Status != "" && f.Status != AnyStatus && d.Status != f.Status {
|
||||
return false
|
||||
}
|
||||
if f.ClientOrderID != "" && d.ClientOrderID != f.ClientOrderID {
|
||||
return false
|
||||
}
|
||||
if f.ClientID != "" && d.ClientID != f.ClientID {
|
||||
return false
|
||||
}
|
||||
if f.InternalOrderID != "" && d.InternalOrderID != f.InternalOrderID {
|
||||
return false
|
||||
}
|
||||
if f.AccountID != "" && d.AccountID != f.AccountID {
|
||||
return false
|
||||
}
|
||||
if f.WalletAddress != "" && d.WalletAddress != f.WalletAddress {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Copy will return a copy of Detail
|
||||
func (d *Detail) Copy() Detail {
|
||||
c := *d
|
||||
if len(d.Trades) > 0 {
|
||||
c.Trades = make([]TradeHistory, len(d.Trades))
|
||||
copy(c.Trades, d.Trades)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// String implements the stringer interface
|
||||
func (t Type) String() string {
|
||||
return string(t)
|
||||
@@ -462,16 +514,12 @@ func FilterOrdersByCurrencies(orders *[]Detail, currencies []currency.Pair) {
|
||||
|
||||
var filteredOrders []Detail
|
||||
for i := range *orders {
|
||||
matchFound := false
|
||||
for _, c := range currencies {
|
||||
if !matchFound && (*orders)[i].Pair.EqualIncludeReciprocal(c) {
|
||||
matchFound = true
|
||||
if (*orders)[i].Pair.EqualIncludeReciprocal(c) {
|
||||
filteredOrders = append(filteredOrders, (*orders)[i])
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if matchFound {
|
||||
filteredOrders = append(filteredOrders, (*orders)[i])
|
||||
}
|
||||
}
|
||||
|
||||
*orders = filteredOrders
|
||||
|
||||
Reference in New Issue
Block a user