diff --git a/engine/order_manager.go b/engine/order_manager.go index d4b9a217..5e90bc93 100644 --- a/engine/order_manager.go +++ b/engine/order_manager.go @@ -47,10 +47,7 @@ func SetupOrderManager(exchangeManager iExchangeManager, communicationsManager i // IsRunning safely checks whether the subsystem is running func (m *OrderManager) IsRunning() bool { - if m == nil { - return false - } - return atomic.LoadInt32(&m.started) == 1 + return m != nil && atomic.LoadInt32(&m.started) == 1 } // Start runs the subsystem @@ -63,6 +60,7 @@ func (m *OrderManager) Start() error { } log.Debugln(log.OrderMgr, "Order manager starting...") m.shutdown = make(chan struct{}) + m.orderStore.wg.Add(1) go m.run() return nil } @@ -75,48 +73,44 @@ func (m *OrderManager) Stop() error { if atomic.LoadInt32(&m.started) == 0 { return fmt.Errorf("order manager %w", ErrSubSystemNotStarted) } - - defer func() { - atomic.CompareAndSwapInt32(&m.started, 1, 0) - }() - log.Debugln(log.OrderMgr, "Order manager shutting down...") close(m.shutdown) + atomic.CompareAndSwapInt32(&m.started, 1, 0) return nil } // gracefulShutdown cancels all orders (if enabled) before shutting down func (m *OrderManager) gracefulShutdown() { - if m.cfg.CancelOrdersOnShutdown { - log.Debugln(log.OrderMgr, "Order manager: Cancelling any open orders...") - exchanges, err := m.orderStore.exchangeManager.GetExchanges() - if err != nil { - log.Errorf(log.OrderMgr, "Order manager cannot get exchanges: %v", err) - return - } - m.CancelAllOrders(context.TODO(), exchanges) + if !m.cfg.CancelOrdersOnShutdown { + return } + log.Debugln(log.OrderMgr, "Order manager: Cancelling any open orders...") + exchanges, err := m.orderStore.exchangeManager.GetExchanges() + if err != nil { + log.Errorf(log.OrderMgr, "Order manager cannot get exchanges: %v", err) + return + } + m.CancelAllOrders(context.TODO(), exchanges) } // run will periodically process orders func (m *OrderManager) run() { log.Debugln(log.OrderMgr, "Order manager started.") - m.processOrders() - tick := time.NewTicker(orderManagerDelay) - m.orderStore.wg.Add(1) - defer func() { - log.Debugln(log.OrderMgr, "Order manager shutdown.") - tick.Stop() - m.orderStore.wg.Done() - }() - + timer := time.NewTimer(orderManagerDelay) for { select { case <-m.shutdown: m.gracefulShutdown() + if !timer.Stop() { + <-timer.C + } + m.orderStore.wg.Done() + log.Debugln(log.OrderMgr, "Order manager shutdown.") return - case <-tick.C: + case <-timer.C: + // Process orders go routine allows shutdown procedures to continue go m.processOrders() + timer.Reset(orderManagerDelay) } } } @@ -138,7 +132,9 @@ func (m *OrderManager) CancelAllOrders(ctx context.Context, exchangeNames []exch continue } for j := range exchangeOrders { - log.Debugf(log.OrderMgr, "Order manager: Cancelling order(s) for exchange %s.", exchangeNames[i].GetName()) + log.Debugf(log.OrderMgr, + "Order manager: Cancelling order(s) for exchange %s.", + exchangeNames[i].GetName()) err := m.Cancel(ctx, &order.Cancel{ Exchange: exchangeOrders[j].Exchange, ID: exchangeOrders[j].ID, @@ -207,22 +203,22 @@ func (m *OrderManager) Cancel(ctx context.Context, cancel *order.Cancel) error { err = fmt.Errorf("%v - Failed to cancel order: %w", cancel.Exchange, err) return err } - var od *order.Detail - od, err = m.orderStore.getByExchangeAndID(cancel.Exchange, cancel.ID) + od, err := m.orderStore.getByExchangeAndID(cancel.Exchange, cancel.ID) if err != nil { err = fmt.Errorf("%v - Failed to retrieve order %v to update cancelled status: %w", cancel.Exchange, cancel.ID, err) return err } - od.Status = order.Cancelled + err = m.orderStore.updateExisting(od) + if err != nil { + err = fmt.Errorf("%v - Failed to update existing order when cancelled: %w", cancel.Exchange, err) + return err + } + msg := fmt.Sprintf("Order manager: Exchange %s order ID=%v cancelled.", od.Exchange, od.ID) log.Debugln(log.OrderMgr, msg) - m.orderStore.commsManager.PushEvent(base.Event{ - Type: "order", - Message: msg, - }) - + m.orderStore.commsManager.PushEvent(base.Event{Type: "order", Message: msg}) return nil } @@ -534,9 +530,6 @@ func (m *OrderManager) GetOrdersFiltered(f *order.Filter) ([]order.Detail, error if m == nil { return nil, fmt.Errorf("order manager %w", ErrNilSubsystem) } - if f == nil { - return nil, fmt.Errorf("order manager, GetOrdersFiltered: Filter is nil") - } if atomic.LoadInt32(&m.started) == 0 { return nil, fmt.Errorf("order manager %w", ErrSubSystemNotStarted) } @@ -640,9 +633,8 @@ func (m *OrderManager) processOrders() { if !atomic.CompareAndSwapInt32(&m.processingOrders, 0, 1) { return } - defer func() { - atomic.StoreInt32(&m.processingOrders, 0) - }() + defer atomic.StoreInt32(&m.processingOrders, 0) + exchanges, err := m.orderStore.exchangeManager.GetExchanges() if err != nil { log.Errorf(log.OrderMgr, "Order manager cannot get exchanges: %v", err) @@ -657,14 +649,14 @@ func (m *OrderManager) processOrders() { "Order manager: Processing orders for exchange %v.", exchanges[i].GetName()) - supportedAssets := exchanges[i].GetAssetTypes(true) - for y := range supportedAssets { - pairs, err := exchanges[i].GetEnabledPairs(supportedAssets[y]) + enabledAssets := exchanges[i].GetAssetTypes(true) + for y := range enabledAssets { + pairs, err := exchanges[i].GetEnabledPairs(enabledAssets[y]) if err != nil { log.Errorf(log.OrderMgr, "Order manager: Unable to get enabled pairs for %s and asset type %s: %s", exchanges[i].GetName(), - supportedAssets[y], + enabledAssets[y], err) continue } @@ -674,33 +666,26 @@ func (m *OrderManager) processOrders() { log.Debugf(log.OrderMgr, "Order manager: No pairs enabled for %s and asset type %s, skipping...", exchanges[i].GetName(), - supportedAssets[y]) + enabledAssets[y]) } continue } - filter := &order.Filter{ - Exchange: exchanges[i].GetName(), - } + filter := &order.Filter{Exchange: exchanges[i].GetName()} orders := m.orderStore.getActiveOrders(filter) order.FilterOrdersByPairs(&orders, pairs) - requiresProcessing := make(map[string]bool, len(orders)) - for x := range orders { - requiresProcessing[orders[x].InternalOrderID] = true - } - req := order.GetOrdersRequest{ + result, err := exchanges[i].GetActiveOrders(context.TODO(), &order.GetOrdersRequest{ Side: order.AnySide, Type: order.AnyType, Pairs: pairs, - AssetType: supportedAssets[y], - } - result, err := exchanges[i].GetActiveOrders(context.TODO(), &req) + AssetType: enabledAssets[y], + }) if err != nil { log.Errorf(log.OrderMgr, "Order manager: Unable to get active orders for %s and asset type %s: %s", exchanges[i].GetName(), - supportedAssets[y], + enabledAssets[y], err) continue } @@ -713,36 +698,38 @@ func (m *OrderManager) processOrders() { if err != nil { log.Error(log.OrderMgr, err) } else { - requiresProcessing[upsertResponse.OrderDetails.InternalOrderID] = false + for i := range orders { + if orders[i].InternalOrderID != upsertResponse.OrderDetails.InternalOrderID { + continue + } + orders[i] = orders[len(orders)-1] + orders = orders[:len(orders)-1] + } } } if !exchanges[i].GetBase().GetSupportedFeatures().RESTCapabilities.GetOrder { continue } wg.Add(1) - go m.processMatchingOrders(exchanges[i], orders, requiresProcessing, &wg) + go m.processMatchingOrders(exchanges[i], orders, &wg) } } wg.Wait() } -func (m *OrderManager) processMatchingOrders(exch exchange.IBotExchange, orders []order.Detail, requiresProcessing map[string]bool, wg *sync.WaitGroup) { - defer func() { - if wg != nil { - wg.Done() - } - }() +func (m *OrderManager) processMatchingOrders(exch exchange.IBotExchange, orders []order.Detail, wg *sync.WaitGroup) { for x := range orders { if time.Since(orders[x].LastUpdated) < time.Minute { continue } - if requiresProcessing[orders[x].InternalOrderID] { - err := m.FetchAndUpdateExchangeOrder(exch, &orders[x], orders[x].AssetType) - if err != nil { - log.Error(log.OrderMgr, err) - } + err := m.FetchAndUpdateExchangeOrder(exch, &orders[x], orders[x].AssetType) + if err != nil { + log.Error(log.OrderMgr, err) } } + if wg != nil { + wg.Done() + } } // FetchAndUpdateExchangeOrder calls the exchange to upsert an order to the order store @@ -762,11 +749,7 @@ func (m *OrderManager) FetchAndUpdateExchangeOrder(exch exchange.IBotExchange, o // Exists checks whether an order exists in the order store func (m *OrderManager) Exists(o *order.Detail) bool { - if m == nil || atomic.LoadInt32(&m.started) == 0 { - return false - } - - return m.orderStore.exists(o) + return m != nil && atomic.LoadInt32(&m.started) != 0 && m.orderStore.exists(o) } // Add adds an order to the orderstore @@ -857,11 +840,13 @@ func (m *OrderManager) UpsertOrder(od *order.Detail) (resp *OrderUpsertResponse, return upsertResponse, nil } -// get returns all orders for all exchanges -// should not be exported as it can have large impact if used improperly +// get returns a copy of all orders for all exchanges. func (s *store) get() map[string][]*order.Detail { + orders := make(map[string][]*order.Detail) s.m.Lock() - orders := s.Orders + for k, val := range s.Orders { + orders[k] = order.CopyPointerOrderSlice(val) + } s.m.Unlock() return orders } @@ -877,7 +862,7 @@ func (s *store) getByExchangeAndID(exchange, id string) (*order.Detail, error) { for x := range r { if r[x].ID == id { - return r[x], nil + return r[x].CopyToPointer(), nil } } return nil, ErrOrderNotFound @@ -896,20 +881,19 @@ func (s *store) updateExisting(od *order.Detail) error { return ErrExchangeNotFound } for x := range r { - if r[x].ID == od.ID { - r[x].UpdateOrderFromDetail(od) - if r[x].AssetType.IsFutures() { - err := s.futuresPositionController.TrackNewOrder(r[x]) - if err != nil { - if !errors.Is(err, order.ErrPositionClosed) { - return err - } - } - } + if r[x].ID != od.ID { + continue + } + r[x].UpdateOrderFromDetail(od) + if !r[x].AssetType.IsFutures() { return nil } + err := s.futuresPositionController.TrackNewOrder(r[x]) + if err != nil && !errors.Is(err, order.ErrPositionClosed) { + return err + } + return nil } - return ErrOrderNotFound } @@ -923,30 +907,30 @@ func (s *store) modifyExisting(id string, mod *order.Modify) error { return ErrExchangeNotFound } for x := range r { - if r[x].ID == id { - r[x].UpdateOrderFromModify(mod) - if r[x].AssetType.IsFutures() { - err := s.futuresPositionController.TrackNewOrder(r[x]) - if err != nil { - if !errors.Is(err, order.ErrPositionClosed) { - return err - } - } - } + if r[x].ID != id { + continue + } + r[x].UpdateOrderFromModify(mod) + if !r[x].AssetType.IsFutures() { return nil } + err := s.futuresPositionController.TrackNewOrder(r[x]) + if err != nil && !errors.Is(err, order.ErrPositionClosed) { + return err + } + return nil } return ErrOrderNotFound } // upsert (1) checks if such an exchange exists in the exchangeManager, (2) checks if // order exists and updates/creates it. -func (s *store) upsert(od *order.Detail) (resp *OrderUpsertResponse, err error) { +func (s *store) upsert(od *order.Detail) (*OrderUpsertResponse, error) { if od == nil { return nil, errNilOrder } lName := strings.ToLower(od.Exchange) - _, err = s.exchangeManager.GetExchangeByName(lName) + _, err := s.exchangeManager.GetExchangeByName(lName) if err != nil { return nil, err } @@ -954,66 +938,27 @@ func (s *store) upsert(od *order.Detail) (resp *OrderUpsertResponse, err error) defer s.m.Unlock() if od.AssetType.IsFutures() { err = s.futuresPositionController.TrackNewOrder(od) - if err != nil { - if !errors.Is(err, order.ErrPositionClosed) { - return nil, err - } + if err != nil && !errors.Is(err, order.ErrPositionClosed) { + return nil, err } } r, ok := s.Orders[lName] if !ok { od.GenerateInternalOrderID() s.Orders[lName] = []*order.Detail{od} - resp = &OrderUpsertResponse{ - OrderDetails: od.Copy(), - IsNewOrder: true, - } - return resp, nil + return &OrderUpsertResponse{OrderDetails: od.Copy(), IsNewOrder: true}, nil } for x := range r { - if r[x].ID == od.ID { - r[x].UpdateOrderFromDetail(od) - resp = &OrderUpsertResponse{ - OrderDetails: r[x].Copy(), - IsNewOrder: false, - } - return resp, nil + if r[x].ID != od.ID { + continue } + r[x].UpdateOrderFromDetail(od) + return &OrderUpsertResponse{OrderDetails: r[x].Copy(), IsNewOrder: false}, nil } // Untracked websocket orders will not have internalIDs yet od.GenerateInternalOrderID() s.Orders[lName] = append(s.Orders[lName], od) - resp = &OrderUpsertResponse{ - OrderDetails: od.Copy(), - IsNewOrder: true, - } - return resp, nil -} - -// getByExchange returns orders by exchange -func (s *store) getByExchange(exchange string) ([]*order.Detail, error) { - s.m.RLock() - defer s.m.RUnlock() - r, ok := s.Orders[strings.ToLower(exchange)] - if !ok { - return nil, ErrExchangeNotFound - } - return r, nil -} - -// getByInternalOrderID will search all orders for our internal orderID -// and return the order -func (s *store) getByInternalOrderID(internalOrderID string) (*order.Detail, error) { - s.m.RLock() - defer s.m.RUnlock() - for _, v := range s.Orders { - for x := range v { - if v[x].InternalOrderID == internalOrderID { - return v[x], nil - } - } - } - return nil, ErrOrderNotFound + return &OrderUpsertResponse{OrderDetails: od.Copy(), IsNewOrder: true}, nil } // exists verifies if the orderstore contains the provided order @@ -1056,13 +1001,10 @@ func (s *store) add(det *order.Detail) error { orders = append(orders, det) s.Orders[strings.ToLower(det.Exchange)] = orders - if det.AssetType.IsFutures() { - err = s.futuresPositionController.TrackNewOrder(det) - if err != nil { - return err - } + if !det.AssetType.IsFutures() { + return nil } - return nil + return s.futuresPositionController.TrackNewOrder(det) } // getFilteredOrders returns a filtered copy of the orders @@ -1107,7 +1049,7 @@ func (s *store) getActiveOrders(f *order.Filter) []order.Detail { case f == nil: for _, e := range s.Orders { for i := range e { - if !e[i].IsActive() { + if e[i].Status != order.UnknownStatus && !e[i].IsActive() { continue } orders = append(orders, e[i].Copy()) @@ -1117,7 +1059,7 @@ func (s *store) getActiveOrders(f *order.Filter) []order.Detail { // optimization if Exchange is filtered if e, ok := s.Orders[strings.ToLower(f.Exchange)]; ok { for i := range e { - if !e[i].IsActive() || !e[i].MatchFilter(f) { + if e[i].Status != order.UnknownStatus && (!e[i].IsActive() || !e[i].MatchFilter(f)) { continue } orders = append(orders, e[i].Copy()) @@ -1126,7 +1068,7 @@ func (s *store) getActiveOrders(f *order.Filter) []order.Detail { default: for _, e := range s.Orders { for i := range e { - if !e[i].IsActive() || !e[i].MatchFilter(f) { + if e[i].Status != order.UnknownStatus && (!e[i].IsActive() || !e[i].MatchFilter(f)) { continue } orders = append(orders, e[i].Copy()) diff --git a/engine/order_manager_test.go b/engine/order_manager_test.go index c5aff320..755ec584 100644 --- a/engine/order_manager_test.go +++ b/engine/order_manager_test.go @@ -253,95 +253,6 @@ func TestOrdersAdd(t *testing.T) { } } -func TestGetByInternalOrderID(t *testing.T) { - m := OrdersSetup(t) - err := m.orderStore.add(&order.Detail{ - Exchange: testExchange, - ID: "TestGetByInternalOrderID", - InternalOrderID: "internalTest", - }) - if err != nil { - t.Error(err) - } - - o, err := m.orderStore.getByInternalOrderID("internalTest") - if err != nil { - t.Fatal(err) - } - if o == nil { //nolint:staticcheck,nolintlint // SA5011 Ignore the nil warnings - t.Fatal("Expected a matching order") - } - if o.ID != "TestGetByInternalOrderID" { //nolint:staticcheck,nolintlint // SA5011 Ignore the nil warnings - t.Error("Expected to retrieve order") - } - - _, err = m.orderStore.getByInternalOrderID("NoOrder") - if err != ErrOrderNotFound { - t.Error(err) - } -} - -func TestGetByExchange(t *testing.T) { - m := OrdersSetup(t) - err := m.orderStore.add(&order.Detail{ - Exchange: testExchange, - ID: "TestGetByExchange", - InternalOrderID: "internalTestGetByExchange", - }) - if err != nil { - t.Error(err) - } - - err = m.orderStore.add(&order.Detail{ - Exchange: testExchange, - ID: "TestGetByExchange2", - InternalOrderID: "internalTestGetByExchange2", - }) - if err != nil { - t.Error(err) - } - - err = m.orderStore.add(&order.Detail{ - Exchange: testExchange, - ID: "TestGetByExchange3", - InternalOrderID: "internalTest3", - }) - if err != nil { - t.Error(err) - } - var o []*order.Detail - o, err = m.orderStore.getByExchange(testExchange) - if err != nil { - t.Error(err) - } - if o == nil { - t.Error("Expected non nil response") - } - var o1Found, o2Found bool - for i := range o { - if o[i].ID == "TestGetByExchange" && o[i].Exchange == testExchange { - o1Found = true - } - if o[i].ID == "TestGetByExchange2" && o[i].Exchange == testExchange { - o2Found = true - } - } - if !o1Found || !o2Found { - t.Error("Expected orders 'TestGetByExchange' and 'TestGetByExchange2' to be returned") - } - - _, err = m.orderStore.getByInternalOrderID("NoOrder") - if err != ErrOrderNotFound { - t.Error(err) - } - err = m.orderStore.add(&order.Detail{ - Exchange: "thisWillFail", - }) - if err == nil { - t.Error("Expected exchange not found error") - } -} - func TestGetByExchangeAndID(t *testing.T) { m := OrdersSetup(t) err := m.orderStore.add(&order.Detail{ @@ -557,7 +468,11 @@ func TestCancelAllOrders(t *testing.T) { } m.CancelAllOrders(context.Background(), []exchange.IBotExchange{}) - if o.Status == order.Cancelled { + checkDeets, err := m.orderStore.getByExchangeAndID(testExchange, "TestCancelAllOrders") + if err != nil { + t.Fatal(err) + } + if checkDeets.Status == order.Cancelled { t.Error("Order should not be cancelled") } @@ -567,14 +482,13 @@ func TestCancelAllOrders(t *testing.T) { } m.CancelAllOrders(context.Background(), []exchange.IBotExchange{exch}) - if o.Status != order.Cancelled { - t.Error("Order should be cancelled") + checkDeets, err = m.orderStore.getByExchangeAndID(testExchange, "TestCancelAllOrders") + if err != nil { + t.Fatal(err) } - o.Status = order.New - m.CancelAllOrders(context.Background(), nil) - if o.Status != order.New { - t.Error("Order should not be cancelled") + if checkDeets.Status != order.Cancelled { + t.Error("Order should be cancelled", checkDeets.Status) } } @@ -1024,39 +938,20 @@ func Test_processMatchingOrders(t *testing.T) { t.Fatal(err) } orders := []order.Detail{ - { - Exchange: testExchange, - ID: "Test1", - LastUpdated: time.Now(), - }, { Exchange: testExchange, ID: "Test2", LastUpdated: time.Now(), }, - { - Exchange: testExchange, - ID: "Test3", - LastUpdated: time.Now().Add(-time.Hour), - }, { Exchange: testExchange, ID: "Test4", LastUpdated: time.Now().Add(-time.Hour), }, } - requiresProcessing := make(map[string]bool, len(orders)) - for i := range orders { - orders[i].GenerateInternalOrderID() - if i%2 == 0 { - requiresProcessing[orders[i].InternalOrderID] = false - } else { - requiresProcessing[orders[i].InternalOrderID] = true - } - } var wg sync.WaitGroup wg.Add(1) - m.processMatchingOrders(exch, orders, requiresProcessing, &wg) + go m.processMatchingOrders(exch, orders, &wg) wg.Wait() res, err := m.GetOrdersFiltered(&order.Filter{Exchange: testExchange}) if err != nil { diff --git a/exchanges/credentials_test.go b/exchanges/credentials_test.go index 42b2b2f1..d8d704b5 100644 --- a/exchanges/credentials_test.go +++ b/exchanges/credentials_test.go @@ -212,7 +212,6 @@ func TestAreCredentialsValid(t *testing.T) { func TestValidateAPICredentials(t *testing.T) { t.Parallel() - var b Base type tester struct { Key string Secret string @@ -247,7 +246,8 @@ func TestValidateAPICredentials(t *testing.T) { {RequiresBase64DecodeSecret: true, Secret: "aGVsbG8gd29ybGQ="}, } - setupBase := func(b *Base, tData *tester) { + setupBase := func(tData *tester) *Base { + b := &Base{} b.API.SetKey(tData.Key) b.API.SetSecret(tData.Secret) b.API.SetClientID(tData.ClientID) @@ -257,13 +257,14 @@ func TestValidateAPICredentials(t *testing.T) { b.API.CredentialsValidator.RequiresPEM = tData.RequiresPEM b.API.CredentialsValidator.RequiresClientID = tData.RequiresClientID b.API.CredentialsValidator.RequiresBase64DecodeSecret = tData.RequiresBase64DecodeSecret + return b } for x := range testCases { testData := &testCases[x] t.Run("", func(t *testing.T) { t.Parallel() - setupBase(&b, testData) + b := setupBase(testData) if err := b.ValidateAPICredentials(b.API.credentials); !errors.Is(err, testData.Expected) { t.Errorf("Test %d: expected: %v: got %v", x+1, testData.Expected, err) } diff --git a/exchanges/order/order_test.go b/exchanges/order/order_test.go index 22eec131..2dbadf9e 100644 --- a/exchanges/order/order_test.go +++ b/exchanges/order/order_test.go @@ -1554,6 +1554,7 @@ func TestGenerateInternalOrderID(t *testing.T) { } func TestDetail_Copy(t *testing.T) { + t.Parallel() d := []Detail{ { Exchange: "Binance", @@ -1577,3 +1578,56 @@ func TestDetail_Copy(t *testing.T) { } } } + +func TestDetail_CopyToPointer(t *testing.T) { + t.Parallel() + d := []Detail{ + { + Exchange: "Binance", + }, + { + Exchange: "Binance", + Trades: []TradeHistory{ + {Price: 1}, + }, + }, + } + for i := range d { + r := d[i].CopyToPointer() + 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) + } + } + } +} + +func TestDetail_CopyPointerOrderSlice(t *testing.T) { + t.Parallel() + d := []*Detail{ + { + Exchange: "Binance", + }, + { + Exchange: "Binance", + Trades: []TradeHistory{ + {Price: 1}, + }, + }, + } + + sliceCopy := CopyPointerOrderSlice(d) + for i := range sliceCopy { + if !reflect.DeepEqual(*sliceCopy[i], *d[i]) { + t.Errorf("[%d] Copy does not contain same elements, expected: %v\ngot:%v", i, sliceCopy[i], d[i]) + } + if len(sliceCopy[i].Trades) > 0 { + if &sliceCopy[i].Trades[0] == &d[i].Trades[0] { + t.Errorf("[%d]Trades point to the same data elements", i) + } + } + } +} diff --git a/exchanges/order/orders.go b/exchanges/order/orders.go index bbd67519..26e355d6 100644 --- a/exchanges/order/orders.go +++ b/exchanges/order/orders.go @@ -466,7 +466,14 @@ func (d *Detail) GenerateInternalOrderID() { } } -// Copy will return a copy of Detail +// CopyToPointer will return the address of a new copy of the order Detail +// WARNING: DO NOT DEREFERENCE USE METHOD Copy(). +func (d *Detail) CopyToPointer() *Detail { + c := d.Copy() + return &c +} + +// Copy makes a full copy of underlying details NOTE: This is Addressable. func (d *Detail) Copy() Detail { c := *d if len(d.Trades) > 0 { @@ -476,6 +483,16 @@ func (d *Detail) Copy() Detail { return c } +// CopyPointerOrderSlice returns a copy of all order detail and returns a slice +// of pointers. +func CopyPointerOrderSlice(old []*Detail) []*Detail { + copySlice := make([]*Detail, len(old)) + for x := range old { + copySlice[x] = old[x].CopyToPointer() + } + return copySlice +} + // String implements the stringer interface func (t Type) String() string { switch t {