orders: add Filter() method to GetOrdersRequest and force FilteredOrders return type on wrapper functions (#1055)

* orders: filter options return on validate

* exchanges: force filter usage by wrapper return type and shift method functionality

* exchanges: update tests and fix err shadow

* exchanges: shadowland

* exchanges: more shadow land spookyness

* glorious: nits and ftx upgrade

* linter: fix

* orders: preserve unpopulated fields through filtering operation

* glorious: nits

Co-authored-by: Ryan O'Hara-Reid <ryan.oharareid@thrasher.io>
This commit is contained in:
Ryan O'Hara-Reid
2022-10-24 13:28:32 +11:00
committed by GitHub
parent 4e135c9590
commit 558a70e87f
65 changed files with 705 additions and 617 deletions

View File

@@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"reflect"
"strconv"
"strings"
"testing"
"time"
@@ -373,20 +374,21 @@ func TestFilterOrdersByType(t *testing.T) {
{
Type: Limit,
},
{}, // Unpopulated fields are preserved for API differences
}
FilterOrdersByType(&orders, AnyType)
if len(orders) != 2 {
if len(orders) != 3 {
t.Errorf("Orders failed to be filtered. Expected %v, received %v", 2, len(orders))
}
FilterOrdersByType(&orders, Limit)
if len(orders) != 1 {
if len(orders) != 2 {
t.Errorf("Orders failed to be filtered. Expected %v, received %v", 1, len(orders))
}
FilterOrdersByType(&orders, Stop)
if len(orders) != 0 {
if len(orders) != 1 {
t.Errorf("Orders failed to be filtered. Expected %v, received %v", 0, len(orders))
}
}
@@ -424,7 +426,7 @@ func TestFilterOrdersBySide(t *testing.T) {
{
Side: Sell,
},
{},
{}, // Unpopulated fields are preserved for API differences
}
FilterOrdersBySide(&orders, AnySide)
@@ -433,12 +435,12 @@ func TestFilterOrdersBySide(t *testing.T) {
}
FilterOrdersBySide(&orders, Buy)
if len(orders) != 1 {
if len(orders) != 2 {
t.Errorf("Orders failed to be filtered. Expected %v, received %v", 1, len(orders))
}
FilterOrdersBySide(&orders, Sell)
if len(orders) != 0 {
if len(orders) != 1 {
t.Errorf("Orders failed to be filtered. Expected %v, received %v", 0, len(orders))
}
}
@@ -567,43 +569,44 @@ func TestFilterOrdersByPairs(t *testing.T) {
{
Pair: currency.NewPair(currency.DOGE, currency.RUB),
},
{}, // Unpopulated fields are preserved for API differences
}
currencies := []currency.Pair{currency.NewPair(currency.BTC, currency.USD),
currency.NewPair(currency.LTC, currency.EUR),
currency.NewPair(currency.DOGE, currency.RUB)}
FilterOrdersByPairs(&orders, currencies)
if len(orders) != 3 {
if len(orders) != 4 {
t.Errorf("Orders failed to be filtered. Expected %v, received %v", 3, len(orders))
}
currencies = []currency.Pair{currency.NewPair(currency.BTC, currency.USD),
currency.NewPair(currency.LTC, currency.EUR)}
FilterOrdersByPairs(&orders, currencies)
if len(orders) != 2 {
if len(orders) != 3 {
t.Errorf("Orders failed to be filtered. Expected %v, received %v", 2, len(orders))
}
currencies = []currency.Pair{currency.NewPair(currency.BTC, currency.USD)}
FilterOrdersByPairs(&orders, currencies)
if len(orders) != 1 {
if len(orders) != 2 {
t.Errorf("Orders failed to be filtered. Expected %v, received %v", 1, len(orders))
}
currencies = []currency.Pair{currency.NewPair(currency.USD, currency.BTC)}
FilterOrdersByPairs(&orders, currencies)
if len(orders) != 1 {
if len(orders) != 2 {
t.Errorf("Reverse Orders failed to be filtered. Expected %v, received %v", 1, len(orders))
}
currencies = []currency.Pair{}
FilterOrdersByPairs(&orders, currencies)
if len(orders) != 1 {
if len(orders) != 2 {
t.Errorf("Orders failed to be filtered. Expected %v, received %v", 1, len(orders))
}
currencies = append(currencies, currency.EMPTYPAIR)
FilterOrdersByPairs(&orders, currencies)
if len(orders) != 1 {
if len(orders) != 2 {
t.Errorf("Orders failed to be filtered. Expected %v, received %v", 1, len(orders))
}
}
@@ -1310,25 +1313,43 @@ func TestValidationOnOrderTypes(t *testing.T) {
}
var getOrders *GetOrdersRequest
if getOrders.Validate() != ErrGetOrdersRequestIsNil {
t.Fatal("unexpected error")
err = getOrders.Validate()
if !errors.Is(err, ErrGetOrdersRequestIsNil) {
t.Fatalf("received: '%v' but expected: '%v'", err, ErrGetOrdersRequestIsNil)
}
getOrders = new(GetOrdersRequest)
if getOrders.Validate() == nil {
t.Fatal("should error since assetType hasn't been provided")
err = getOrders.Validate()
if !errors.Is(err, asset.ErrNotSupported) {
t.Fatalf("received: '%v' but expected: '%v'", err, asset.ErrNotSupported)
}
if getOrders.Validate(validate.Check(func() error {
return errors.New("this should error")
})) == nil {
t.Fatal("expected error")
getOrders.AssetType = asset.Spot
err = getOrders.Validate()
if !errors.Is(err, errUnrecognisedOrderSide) {
t.Fatalf("received: '%v' but expected: '%v'", err, errUnrecognisedOrderSide)
}
if getOrders.Validate(validate.Check(func() error {
getOrders.Side = AnySide
err = getOrders.Validate()
if !errors.Is(err, errUnrecognisedOrderType) {
t.Fatalf("received: '%v' but expected: '%v'", err, errUnrecognisedOrderType)
}
var errTestError = errors.New("test error")
getOrders.Type = AnyType
err = getOrders.Validate(validate.Check(func() error {
return errTestError
}))
if !errors.Is(err, errTestError) {
t.Fatalf("received: '%v' but expected: '%v'", err, errTestError)
}
err = getOrders.Validate(validate.Check(func() error {
return nil
})) == nil {
t.Fatal("should output an error since assetType isn't provided")
}))
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
}
var modifyOrder *Modify
@@ -1869,3 +1890,58 @@ func TestDeriveCancel(t *testing.T) {
t.Fatalf("unexpected values %+v", cancel)
}
}
func TestGetOrdersRequest_Filter(t *testing.T) {
request := new(GetOrdersRequest)
request.AssetType = asset.Spot
request.Type = AnyType
request.Side = AnySide
var orders = []Detail{
{OrderID: "0", Pair: btcusd, AssetType: asset.Spot, Type: Limit, Side: Buy},
{OrderID: "1", Pair: btcusd, AssetType: asset.Spot, Type: Limit, Side: Sell},
{OrderID: "2", Pair: btcusd, AssetType: asset.Spot, Type: Market, Side: Buy},
{OrderID: "3", Pair: btcusd, AssetType: asset.Spot, Type: Market, Side: Sell},
{OrderID: "4", Pair: btcusd, AssetType: asset.Futures, Type: Limit, Side: Buy},
{OrderID: "5", Pair: btcusd, AssetType: asset.Futures, Type: Limit, Side: Sell},
{OrderID: "6", Pair: btcusd, AssetType: asset.Futures, Type: Market, Side: Buy},
{OrderID: "7", Pair: btcusd, AssetType: asset.Futures, Type: Market, Side: Sell},
{OrderID: "8", Pair: btcltc, AssetType: asset.Spot, Type: Limit, Side: Buy},
{OrderID: "9", Pair: btcltc, AssetType: asset.Spot, Type: Limit, Side: Sell},
{OrderID: "10", Pair: btcltc, AssetType: asset.Spot, Type: Market, Side: Buy},
{OrderID: "11", Pair: btcltc, AssetType: asset.Spot, Type: Market, Side: Sell},
{OrderID: "12", Pair: btcltc, AssetType: asset.Futures, Type: Limit, Side: Buy},
{OrderID: "13", Pair: btcltc, AssetType: asset.Futures, Type: Limit, Side: Sell},
{OrderID: "14", Pair: btcltc, AssetType: asset.Futures, Type: Market, Side: Buy},
{OrderID: "15", Pair: btcltc, AssetType: asset.Futures, Type: Market, Side: Sell},
}
shinyAndClean := request.Filter("test", orders)
if len(shinyAndClean) != 16 {
t.Fatalf("received: '%v' but expected: '%v'", len(shinyAndClean), 16)
}
for x := range shinyAndClean {
if strconv.FormatInt(int64(x), 10) != shinyAndClean[x].OrderID {
t.Fatalf("received: '%v' but expected: '%v'", shinyAndClean[x].OrderID, int64(x))
}
}
request.Pairs = []currency.Pair{btcltc}
// Kicks off time error
request.EndTime = time.Unix(1336, 0)
request.StartTime = time.Unix(1337, 0)
shinyAndClean = request.Filter("test", orders)
if len(shinyAndClean) != 8 {
t.Fatalf("received: '%v' but expected: '%v'", len(shinyAndClean), 8)
}
for x := range shinyAndClean {
if strconv.FormatInt(int64(x)+8, 10) != shinyAndClean[x].OrderID {
t.Fatalf("received: '%v' but expected: '%v'", shinyAndClean[x].OrderID, int64(x)+8)
}
}
}

View File

@@ -356,3 +356,8 @@ type ClassificationError struct {
OrderID string
Err error
}
// FilteredOrders defines orders that have been filtered at the wrapper level
// forcing required filter operations when calling method Filter() on
// GetOrdersRequest.
type FilteredOrders []Detail

View File

@@ -12,6 +12,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/validate"
"github.com/thrasher-corp/gocryptotrader/log"
)
const (
@@ -772,7 +773,7 @@ func FilterOrdersBySide(orders *[]Detail, side Side) {
target := 0
for i := range *orders {
if (*orders)[i].Side == side {
if (*orders)[i].Side == UnknownSide || (*orders)[i].Side == side {
(*orders)[target] = (*orders)[i]
target++
}
@@ -789,7 +790,7 @@ func FilterOrdersByType(orders *[]Detail, orderType Type) {
target := 0
for i := range *orders {
if (*orders)[i].Type == orderType {
if (*orders)[i].Type == UnknownType || (*orders)[i].Type == orderType {
(*orders)[target] = (*orders)[i]
target++
}
@@ -834,6 +835,12 @@ func FilterOrdersByPairs(orders *[]Detail, pairs []currency.Pair) {
target := 0
for x := range *orders {
if (*orders)[x].Pair.IsEmpty() { // If pair not set then keep
(*orders)[target] = (*orders)[x]
target++
continue
}
for y := range pairs {
if (*orders)[x].Pair.EqualIncludeReciprocal(pairs[y]) {
(*orders)[target] = (*orders)[x]
@@ -1106,14 +1113,25 @@ func (c *Cancel) Validate(opt ...validate.Checker) error {
return nil
}
// Validate checks internal struct requirements
// Validate checks internal struct requirements and returns filter requirement
// options for wrapper standardization procedures.
func (g *GetOrdersRequest) Validate(opt ...validate.Checker) error {
if g == nil {
return ErrGetOrdersRequestIsNil
}
if !g.AssetType.IsValid() {
return fmt.Errorf("%v %w", g.AssetType, asset.ErrNotSupported)
}
if g.Side == UnknownSide {
return errUnrecognisedOrderSide
}
if g.Type == UnknownType {
return errUnrecognisedOrderType
}
var errs common.Errors
for _, o := range opt {
err := o.Check()
@@ -1121,13 +1139,26 @@ func (g *GetOrdersRequest) Validate(opt ...validate.Checker) error {
errs = append(errs, err)
}
}
if errs != nil {
return errs
}
return nil
}
// Filter reduces slice by optional fields
func (g *GetOrdersRequest) Filter(exch string, orders []Detail) FilteredOrders {
filtered := make([]Detail, len(orders))
copy(filtered, orders)
FilterOrdersByPairs(&filtered, g.Pairs)
FilterOrdersByType(&filtered, g.Type)
FilterOrdersBySide(&filtered, g.Side)
err := FilterOrdersByTimeRange(&filtered, g.StartTime, g.EndTime)
if err != nil {
log.Errorf(log.ExchangeSys, "%s %v", exch, err)
}
return filtered
}
// Validate checks internal struct requirements
func (m *Modify) Validate(opt ...validate.Checker) error {
if m == nil {