engine/order manager: Initial REST managed order updating (resolves #772) (#775)

* Initial REST managed order updating

* Apply gloriousCode's changes.go patch

* Update internal order ID handling

* Check error

* Replace string with string pointer

* Avoid nil pointers in upsert

* Update test for UpdateOrderFromDetail()

* Add tests for orders.go

* Remove unnecessary newline

* Address comments

* Add missing nil check

* Add tests for new functions in order_manager.go

* Remove empty line

* Change log level for updates from Info to Debug (keep added orders at Info)

* Initialize orders before running the timer

* [TEMP] Add verbosity for debugging

* Nil checking on exchangeManager in GetExchanges()

- exchangeManager.GetExchanges() and iExchangeManager.GetExchanges() return an error on nil
- bot.GetExchanges() wraps exchangeManager.GetExchanges() and returns an empty slice

* Revert b5afe1a46b

* Do not start the order manager runner thread

Instead, mark the order manager as running

* Remove redundant error.Is() and remove print wrapper on msg

* Add atomic blocker and waitgroup on processOrders()

* Disable unnecessary orderManager runner thread for rpcserver_test

* Remove redundant err from orderStore.getActiveOrders()

* [FIX] Populate requiresProcessing using UpsertResponse data instead of REST return data

.. because the data returned by the REST calls do not include the internal user ID's

* [TEST] Verify that processOrders() actually processes queried order data

* Remove leftover warning and add nil check on wg.Done()

* Apply suggestions from code review

Log category changes - as suggested

Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>

* Return when no exchanges available

Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>
This commit is contained in:
TaltaM
2021-09-15 01:28:31 +02:00
committed by GitHub
parent 068b375867
commit fd600972ba
17 changed files with 833 additions and 79 deletions

View File

@@ -84,6 +84,7 @@ func (b *Bittrex) SetDefaults() {
TradeFetching: true,
OrderbookFetching: true,
AutoPairUpdates: true,
GetOrder: true,
GetOrders: true,
CancelOrder: true,
CancelOrders: true,

View File

@@ -7,6 +7,7 @@ import (
"testing"
"time"
"github.com/gofrs/uuid"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/validate"
@@ -815,7 +816,7 @@ func TestUpdateOrderFromDetail(t *testing.T) {
RemainingAmount: 0,
Fee: 0,
Exchange: "test",
ID: "1",
ID: "",
AccountID: "",
ClientID: "",
WalletAddress: "",
@@ -866,8 +867,8 @@ func TestUpdateOrderFromDetail(t *testing.T) {
}
od.UpdateOrderFromDetail(&om)
if od.InternalOrderID == "1" {
t.Error("Should not be able to update the internal order ID")
if od.InternalOrderID != "1" {
t.Error("Failed to initialize the internal order ID")
}
if !od.ImmediateOrCancel {
t.Error("Failed to update")
@@ -987,6 +988,15 @@ func TestUpdateOrderFromDetail(t *testing.T) {
if od.Trades[0].Amount != 1337 {
t.Error("Failed to update trades")
}
om = Detail{
InternalOrderID: "2",
}
od.UpdateOrderFromDetail(&om)
if od.InternalOrderID == "2" {
t.Error("Should not be able to update the internal order ID after initialization")
}
}
func TestClassificationError_Error(t *testing.T) {
@@ -1209,6 +1219,136 @@ func TestMatchFilter(t *testing.T) {
}
}
func TestIsActive(t *testing.T) {
orders := map[int]Detail{
0: {Amount: 0.0, Status: Active},
1: {Amount: 1.0, ExecutedAmount: 0.9, Status: Active},
2: {Amount: 1.0, ExecutedAmount: 1.0, Status: Active},
3: {Amount: 1.0, ExecutedAmount: 1.1, Status: Active},
}
amountTests := map[int]struct {
o Detail
expRes bool
}{
0: {orders[0], false},
1: {orders[1], true},
2: {orders[2], false},
3: {orders[3], false},
}
// specific tests
for num, tt := range amountTests {
if tt.o.IsActive() != tt.expRes {
t.Errorf("amountTests[%v] failed", num)
}
}
statusTests := map[int]struct {
o Detail
expRes bool
}{
0: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: AnyStatus}, true},
1: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: New}, true},
2: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: Active}, true},
3: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: PartiallyCancelled}, false},
4: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: PartiallyFilled}, true},
5: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: Filled}, false},
6: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: Cancelled}, false},
7: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: PendingCancel}, true},
8: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: InsufficientBalance}, false},
9: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: MarketUnavailable}, false},
10: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: Rejected}, false},
11: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: Expired}, false},
12: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: Hidden}, true},
13: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: UnknownStatus}, true},
14: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: Open}, true},
15: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: AutoDeleverage}, true},
16: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: Closed}, false},
17: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: Pending}, true},
}
// specific tests
for num, tt := range statusTests {
if tt.o.IsActive() != tt.expRes {
t.Errorf("statusTests[%v] failed", num)
}
}
}
func TestIsInctive(t *testing.T) {
orders := map[int]Detail{
0: {Amount: 0.0, Status: Active},
1: {Amount: 1.0, ExecutedAmount: 0.9, Status: Active},
2: {Amount: 1.0, ExecutedAmount: 1.0, Status: Active},
3: {Amount: 1.0, ExecutedAmount: 1.1, Status: Active},
}
amountTests := map[int]struct {
o Detail
expRes bool
}{
0: {orders[0], true},
1: {orders[1], false},
2: {orders[2], true},
3: {orders[3], true},
}
// specific tests
for num, tt := range amountTests {
if tt.o.IsInactive() != tt.expRes {
t.Errorf("amountTests[%v] failed", num)
}
}
statusTests := map[int]struct {
o Detail
expRes bool
}{
0: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: AnyStatus}, false},
1: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: New}, false},
2: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: Active}, false},
3: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: PartiallyCancelled}, true},
4: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: PartiallyFilled}, false},
5: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: Filled}, true},
6: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: Cancelled}, true},
7: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: PendingCancel}, false},
8: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: InsufficientBalance}, true},
9: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: MarketUnavailable}, true},
10: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: Rejected}, true},
11: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: Expired}, true},
12: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: Hidden}, false},
13: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: UnknownStatus}, false},
14: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: Open}, false},
15: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: AutoDeleverage}, false},
16: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: Closed}, true},
17: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: Pending}, false},
}
// specific tests
for num, tt := range statusTests {
if tt.o.IsInactive() != tt.expRes {
t.Errorf("statusTests[%v] failed", num)
}
}
}
func TestGenerateInternalOrderID(t *testing.T) {
id, err := uuid.NewV4()
if err != nil {
t.Errorf("unable to create uuid: %s", err)
}
od := Detail{
InternalOrderID: id.String(),
}
od.GenerateInternalOrderID()
if od.InternalOrderID != id.String() {
t.Error("Should not be able to generate a new internal order ID")
}
od = Detail{}
od.GenerateInternalOrderID()
if od.InternalOrderID == "" {
t.Error("unable to generate internal order ID")
}
}
func TestDetail_Copy(t *testing.T) {
d := []Detail{
{

View File

@@ -7,6 +7,7 @@ import (
"strings"
"time"
"github.com/gofrs/uuid"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/validate"
@@ -208,6 +209,9 @@ func (d *Detail) UpdateOrderFromDetail(m *Detail) {
if d.ID == "" {
d.ID = m.ID
}
if d.InternalOrderID == "" {
d.InternalOrderID = m.InternalOrderID
}
}
// UpdateOrderFromModify Will update an order detail (used in order management)
@@ -405,6 +409,40 @@ func (d *Detail) MatchFilter(f *Filter) bool {
return true
}
// IsActive returns true if an order has a status that indicates it is
// currently available on the exchange
func (d *Detail) IsActive() bool {
if d.Amount <= 0 || d.Amount <= d.ExecutedAmount {
return false
}
return d.Status == Active || d.Status == Open || d.Status == PartiallyFilled || d.Status == New ||
d.Status == AnyStatus || d.Status == PendingCancel || d.Status == Hidden || d.Status == UnknownStatus ||
d.Status == AutoDeleverage || d.Status == Pending
}
// IsInactive returns true if an order has a status that indicates it is
// currently not available on the exchange
func (d *Detail) IsInactive() bool {
if d.Amount <= 0 || d.Amount <= d.ExecutedAmount {
return true
}
return d.Status == Filled || d.Status == Cancelled || d.Status == InsufficientBalance || d.Status == MarketUnavailable ||
d.Status == Rejected || d.Status == PartiallyCancelled || d.Status == Expired || d.Status == Closed
}
// GenerateInternalOrderID sets a new V4 order ID or a V5 order ID if
// the V4 function returns an error
func (d *Detail) GenerateInternalOrderID() {
if d.InternalOrderID == "" {
var id uuid.UUID
id, err := uuid.NewV4()
if err != nil {
id = uuid.NewV5(uuid.UUID{}, d.ID)
}
d.InternalOrderID = id.String()
}
}
// Copy will return a copy of Detail
func (d *Detail) Copy() Detail {
c := *d