mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-21 23:16:49 +00:00
* Exchanges: Remove cancel order walletAddress * Order: Refactor TestMatchFilter TestMatchFilter had inconsistent testing of empty values, and was painful when a field was removed due to index methodology. This should provide equal test clarity, but improve maintainability and improve coverage on empty values.
2149 lines
58 KiB
Go
2149 lines
58 KiB
Go
package order
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gofrs/uuid"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/thrasher-corp/gocryptotrader/common"
|
|
"github.com/thrasher-corp/gocryptotrader/currency"
|
|
"github.com/thrasher-corp/gocryptotrader/encoding/json"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/margin"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/protocol"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/validate"
|
|
)
|
|
|
|
var errValidationCheckFailed = errors.New("validation check failed")
|
|
|
|
func TestSubmitValidate(t *testing.T) {
|
|
t.Parallel()
|
|
testPair := currency.NewPair(currency.BTC, currency.LTC)
|
|
tester := []struct {
|
|
ExpectedErr error
|
|
Submit *Submit
|
|
ValidOpts validate.Checker
|
|
HasToPurchaseWithQuoteAmountSet bool
|
|
HasToSellWithBaseAmountSet bool
|
|
RequiresID bool
|
|
}{
|
|
{
|
|
ExpectedErr: ErrSubmissionIsNil,
|
|
Submit: nil,
|
|
}, // nil struct
|
|
{
|
|
ExpectedErr: errExchangeNameUnset,
|
|
Submit: &Submit{},
|
|
}, // empty exchange
|
|
{
|
|
ExpectedErr: ErrPairIsEmpty,
|
|
Submit: &Submit{Exchange: "test"},
|
|
}, // empty pair
|
|
{
|
|
ExpectedErr: ErrAssetNotSet,
|
|
Submit: &Submit{Exchange: "test", Pair: testPair},
|
|
}, // valid pair but invalid asset
|
|
{
|
|
ExpectedErr: asset.ErrNotSupported,
|
|
Submit: &Submit{
|
|
Exchange: "test",
|
|
Pair: testPair,
|
|
AssetType: asset.All,
|
|
},
|
|
}, // valid pair but invalid asset
|
|
{
|
|
ExpectedErr: ErrSideIsInvalid,
|
|
Submit: &Submit{
|
|
Exchange: "test",
|
|
Pair: testPair,
|
|
AssetType: asset.Spot,
|
|
},
|
|
}, // valid pair but invalid order side
|
|
{
|
|
ExpectedErr: errTimeInForceConflict,
|
|
Submit: &Submit{
|
|
Exchange: "test",
|
|
Pair: testPair,
|
|
AssetType: asset.Spot,
|
|
Side: Ask,
|
|
Type: Market,
|
|
ImmediateOrCancel: true,
|
|
FillOrKill: true,
|
|
},
|
|
},
|
|
{
|
|
ExpectedErr: ErrTypeIsInvalid,
|
|
Submit: &Submit{
|
|
Exchange: "test",
|
|
Pair: testPair,
|
|
Side: Buy,
|
|
AssetType: asset.Spot,
|
|
},
|
|
}, // valid pair and order side but invalid order type
|
|
{
|
|
ExpectedErr: ErrTypeIsInvalid,
|
|
Submit: &Submit{
|
|
Exchange: "test",
|
|
Pair: testPair,
|
|
Side: Sell,
|
|
AssetType: asset.Spot,
|
|
},
|
|
}, // valid pair and order side but invalid order type
|
|
{
|
|
ExpectedErr: ErrTypeIsInvalid,
|
|
Submit: &Submit{
|
|
Exchange: "test",
|
|
Pair: testPair,
|
|
Side: Bid,
|
|
AssetType: asset.Spot,
|
|
},
|
|
}, // valid pair and order side but invalid order type
|
|
{
|
|
ExpectedErr: ErrTypeIsInvalid,
|
|
Submit: &Submit{
|
|
Exchange: "test",
|
|
Pair: testPair,
|
|
Side: Ask,
|
|
AssetType: asset.Spot,
|
|
},
|
|
}, // valid pair and order side but invalid order type
|
|
{
|
|
ExpectedErr: ErrAmountIsInvalid,
|
|
Submit: &Submit{
|
|
Exchange: "test",
|
|
Pair: testPair,
|
|
Side: Ask,
|
|
Type: Market,
|
|
AssetType: asset.Spot,
|
|
},
|
|
}, // valid pair, order side, type but invalid amount
|
|
{
|
|
ExpectedErr: ErrAmountIsInvalid,
|
|
Submit: &Submit{
|
|
Exchange: "test",
|
|
Pair: testPair,
|
|
Side: Ask,
|
|
Type: Market,
|
|
AssetType: asset.Spot,
|
|
Amount: -1,
|
|
},
|
|
}, // valid pair, order side, type but invalid amount
|
|
{
|
|
ExpectedErr: ErrAmountIsInvalid,
|
|
Submit: &Submit{
|
|
Exchange: "test",
|
|
Pair: testPair,
|
|
Side: Ask,
|
|
Type: Market,
|
|
AssetType: asset.Spot,
|
|
QuoteAmount: -1,
|
|
},
|
|
}, // valid pair, order side, type but invalid amount
|
|
{
|
|
ExpectedErr: ErrPriceMustBeSetIfLimitOrder,
|
|
Submit: &Submit{
|
|
Exchange: "test",
|
|
Pair: testPair,
|
|
Side: Ask,
|
|
Type: Limit,
|
|
Amount: 1,
|
|
AssetType: asset.Spot,
|
|
},
|
|
}, // valid pair, order side, type, amount but invalid price
|
|
{
|
|
ExpectedErr: errValidationCheckFailed,
|
|
Submit: &Submit{
|
|
Exchange: "test",
|
|
Pair: testPair,
|
|
Side: Ask,
|
|
Type: Limit,
|
|
Amount: 1,
|
|
Price: 1000,
|
|
AssetType: asset.Spot,
|
|
},
|
|
ValidOpts: validate.Check(func() error { return errValidationCheckFailed }),
|
|
}, // custom validation error check
|
|
{
|
|
ExpectedErr: nil,
|
|
Submit: &Submit{
|
|
Exchange: "test",
|
|
Pair: testPair,
|
|
Side: Ask,
|
|
Type: Limit,
|
|
Amount: 1,
|
|
Price: 1000,
|
|
AssetType: asset.Spot,
|
|
},
|
|
ValidOpts: validate.Check(func() error { return nil }),
|
|
}, // valid order!
|
|
{
|
|
ExpectedErr: ErrAmountMustBeSet,
|
|
Submit: &Submit{
|
|
Exchange: "test",
|
|
Pair: testPair,
|
|
Side: Buy,
|
|
Type: Market,
|
|
Amount: 1,
|
|
AssetType: asset.Spot,
|
|
},
|
|
HasToPurchaseWithQuoteAmountSet: true,
|
|
ValidOpts: validate.Check(func() error { return nil }),
|
|
},
|
|
{
|
|
ExpectedErr: ErrAmountMustBeSet,
|
|
Submit: &Submit{
|
|
Exchange: "test",
|
|
Pair: testPair,
|
|
Side: Sell,
|
|
Type: Market,
|
|
QuoteAmount: 1,
|
|
AssetType: asset.Spot,
|
|
},
|
|
HasToSellWithBaseAmountSet: true,
|
|
ValidOpts: validate.Check(func() error { return nil }),
|
|
},
|
|
{
|
|
ExpectedErr: ErrClientOrderIDMustBeSet,
|
|
Submit: &Submit{
|
|
Exchange: "test",
|
|
Pair: testPair,
|
|
Side: Buy,
|
|
Type: Market,
|
|
Amount: 1,
|
|
AssetType: asset.Spot,
|
|
},
|
|
RequiresID: true,
|
|
ValidOpts: validate.Check(func() error { return nil }),
|
|
},
|
|
{
|
|
ExpectedErr: nil,
|
|
Submit: &Submit{
|
|
Exchange: "test",
|
|
Pair: testPair,
|
|
Side: Buy,
|
|
Type: Market,
|
|
Amount: 1,
|
|
AssetType: asset.Spot,
|
|
ClientOrderID: "69420",
|
|
},
|
|
RequiresID: true,
|
|
ValidOpts: validate.Check(func() error { return nil }),
|
|
},
|
|
}
|
|
|
|
for x, tc := range tester {
|
|
t.Run(strconv.Itoa(x), func(t *testing.T) {
|
|
t.Parallel()
|
|
requirements := protocol.TradingRequirements{
|
|
SpotMarketOrderAmountPurchaseQuotationOnly: tc.HasToPurchaseWithQuoteAmountSet,
|
|
SpotMarketOrderAmountSellBaseOnly: tc.HasToSellWithBaseAmountSet,
|
|
ClientOrderID: tc.RequiresID,
|
|
}
|
|
err := tc.Submit.Validate(requirements, tc.ValidOpts)
|
|
assert.ErrorIs(t, err, tc.ExpectedErr)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSubmit_DeriveSubmitResponse(t *testing.T) {
|
|
t.Parallel()
|
|
var s *Submit
|
|
_, err := s.DeriveSubmitResponse("")
|
|
if !errors.Is(err, errOrderSubmitIsNil) {
|
|
t.Fatalf("received: '%v' but expected: '%v'", err, errOrderSubmitIsNil)
|
|
}
|
|
|
|
s = &Submit{}
|
|
_, err = s.DeriveSubmitResponse("")
|
|
if !errors.Is(err, ErrOrderIDNotSet) {
|
|
t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderIDNotSet)
|
|
}
|
|
|
|
resp, err := s.DeriveSubmitResponse("1337")
|
|
if !errors.Is(err, nil) {
|
|
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
|
}
|
|
|
|
if resp.OrderID != "1337" {
|
|
t.Fatal("unexpected value")
|
|
}
|
|
|
|
if resp.Status != New {
|
|
t.Fatal("unexpected value")
|
|
}
|
|
|
|
if resp.Date.IsZero() {
|
|
t.Fatal("unexpected value")
|
|
}
|
|
|
|
if resp.LastUpdated.IsZero() {
|
|
t.Fatal("unexpected value")
|
|
}
|
|
}
|
|
|
|
func TestSubmitResponse_DeriveDetail(t *testing.T) {
|
|
t.Parallel()
|
|
var s *SubmitResponse
|
|
_, err := s.DeriveDetail(uuid.Nil)
|
|
if !errors.Is(err, errOrderSubmitResponseIsNil) {
|
|
t.Fatalf("received: '%v' but expected: '%v'", err, errOrderSubmitResponseIsNil)
|
|
}
|
|
|
|
id, err := uuid.NewV4()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
s = &SubmitResponse{}
|
|
deets, err := s.DeriveDetail(id)
|
|
if !errors.Is(err, nil) {
|
|
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
|
}
|
|
|
|
if deets.InternalOrderID != id {
|
|
t.Fatal("unexpected value")
|
|
}
|
|
}
|
|
|
|
func TestOrderSides(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
os := Buy
|
|
if os.String() != "BUY" {
|
|
t.Errorf("unexpected string %s", os.String())
|
|
}
|
|
|
|
if os.Lower() != "buy" {
|
|
t.Errorf("unexpected string %s", os.Lower())
|
|
}
|
|
|
|
if os.Title() != "Buy" {
|
|
t.Errorf("unexpected string %s", os.Title())
|
|
}
|
|
}
|
|
|
|
func TestTitle(t *testing.T) {
|
|
t.Parallel()
|
|
orderType := Limit
|
|
if orderType.Title() != "Limit" {
|
|
t.Errorf("received '%v' expected 'Limit'", orderType.Title())
|
|
}
|
|
}
|
|
|
|
func TestOrderTypes(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var orderType Type
|
|
if orderType.String() != "UNKNOWN" {
|
|
t.Errorf("unexpected string %s", orderType.String())
|
|
}
|
|
|
|
if orderType.Lower() != "unknown" {
|
|
t.Errorf("unexpected string %s", orderType.Lower())
|
|
}
|
|
|
|
if orderType.Title() != "Unknown" {
|
|
t.Errorf("unexpected string %s", orderType.Title())
|
|
}
|
|
}
|
|
|
|
func TestInferCostsAndTimes(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var detail Detail
|
|
detail.InferCostsAndTimes()
|
|
if detail.Amount != detail.ExecutedAmount+detail.RemainingAmount {
|
|
t.Errorf(
|
|
"Order detail amounts not equals. Expected 0, received %f",
|
|
detail.Amount-(detail.ExecutedAmount+detail.RemainingAmount),
|
|
)
|
|
}
|
|
|
|
detail.CloseTime = time.Now()
|
|
detail.InferCostsAndTimes()
|
|
if detail.LastUpdated != detail.CloseTime {
|
|
t.Errorf(
|
|
"Order last updated not equals close time. Expected %s, received %s",
|
|
detail.CloseTime,
|
|
detail.LastUpdated,
|
|
)
|
|
}
|
|
|
|
detail.Amount = 1
|
|
detail.ExecutedAmount = 1
|
|
detail.InferCostsAndTimes()
|
|
if detail.AverageExecutedPrice != 0 {
|
|
t.Errorf(
|
|
"Unexpected AverageExecutedPrice. Expected 0, received %f",
|
|
detail.AverageExecutedPrice,
|
|
)
|
|
}
|
|
|
|
detail.Amount = 1
|
|
detail.ExecutedAmount = 1
|
|
detail.InferCostsAndTimes()
|
|
if detail.Cost != 0 {
|
|
t.Errorf(
|
|
"Unexpected Cost. Expected 0, received %f",
|
|
detail.Cost,
|
|
)
|
|
}
|
|
detail.ExecutedAmount = 0
|
|
|
|
detail.Amount = 1
|
|
detail.RemainingAmount = 1
|
|
detail.InferCostsAndTimes()
|
|
if detail.Amount != detail.ExecutedAmount+detail.RemainingAmount {
|
|
t.Errorf(
|
|
"Order detail amounts not equals. Expected 0, received %f",
|
|
detail.Amount-(detail.ExecutedAmount+detail.RemainingAmount),
|
|
)
|
|
}
|
|
detail.RemainingAmount = 0
|
|
|
|
detail.Amount = 1
|
|
detail.ExecutedAmount = 1
|
|
detail.Price = 2
|
|
detail.InferCostsAndTimes()
|
|
if detail.AverageExecutedPrice != 2 {
|
|
t.Errorf(
|
|
"Unexpected AverageExecutedPrice. Expected 2, received %f",
|
|
detail.AverageExecutedPrice,
|
|
)
|
|
}
|
|
|
|
detail = Detail{Amount: 1, ExecutedAmount: 2, Cost: 3, Price: 0}
|
|
detail.InferCostsAndTimes()
|
|
if detail.AverageExecutedPrice != 1.5 {
|
|
t.Errorf(
|
|
"Unexpected AverageExecutedPrice. Expected 1.5, received %f",
|
|
detail.AverageExecutedPrice,
|
|
)
|
|
}
|
|
|
|
detail = Detail{Amount: 1, ExecutedAmount: 2, AverageExecutedPrice: 3}
|
|
detail.InferCostsAndTimes()
|
|
if detail.Cost != 6 {
|
|
t.Errorf(
|
|
"Unexpected Cost. Expected 6, received %f",
|
|
detail.Cost,
|
|
)
|
|
}
|
|
}
|
|
|
|
func TestFilterOrdersByType(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
orders := []Detail{
|
|
{
|
|
Type: ImmediateOrCancel,
|
|
},
|
|
{
|
|
Type: Limit,
|
|
},
|
|
{}, // Unpopulated fields are preserved for API differences
|
|
}
|
|
|
|
FilterOrdersByType(&orders, AnyType)
|
|
if len(orders) != 3 {
|
|
t.Errorf("Orders failed to be filtered. Expected %v, received %v", 2, len(orders))
|
|
}
|
|
|
|
FilterOrdersByType(&orders, Limit)
|
|
if len(orders) != 2 {
|
|
t.Errorf("Orders failed to be filtered. Expected %v, received %v", 1, len(orders))
|
|
}
|
|
|
|
FilterOrdersByType(&orders, Stop)
|
|
if len(orders) != 1 {
|
|
t.Errorf("Orders failed to be filtered. Expected %v, received %v", 0, len(orders))
|
|
}
|
|
}
|
|
|
|
var filterOrdersByTypeBenchmark = &[]Detail{
|
|
{Type: Limit},
|
|
{Type: Limit},
|
|
{Type: Limit},
|
|
{Type: Limit},
|
|
{Type: Limit},
|
|
{Type: Limit},
|
|
{Type: Limit},
|
|
{Type: Limit},
|
|
{Type: Limit},
|
|
{Type: Limit},
|
|
}
|
|
|
|
// BenchmarkFilterOrdersByType benchmark
|
|
//
|
|
// 392455 3226 ns/op 15840 B/op 5 allocs/op // PREV
|
|
// 9486490 109.5 ns/op 0 B/op 0 allocs/op // CURRENT
|
|
func BenchmarkFilterOrdersByType(b *testing.B) {
|
|
for b.Loop() {
|
|
FilterOrdersByType(filterOrdersByTypeBenchmark, Limit)
|
|
}
|
|
}
|
|
|
|
func TestFilterOrdersBySide(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
orders := []Detail{
|
|
{
|
|
Side: Buy,
|
|
},
|
|
{
|
|
Side: Sell,
|
|
},
|
|
{}, // Unpopulated fields are preserved for API differences
|
|
}
|
|
|
|
FilterOrdersBySide(&orders, AnySide)
|
|
if len(orders) != 3 {
|
|
t.Errorf("Orders failed to be filtered. Expected %v, received %v", 3, len(orders))
|
|
}
|
|
|
|
FilterOrdersBySide(&orders, Buy)
|
|
if len(orders) != 2 {
|
|
t.Errorf("Orders failed to be filtered. Expected %v, received %v", 1, len(orders))
|
|
}
|
|
|
|
FilterOrdersBySide(&orders, Sell)
|
|
if len(orders) != 1 {
|
|
t.Errorf("Orders failed to be filtered. Expected %v, received %v", 0, len(orders))
|
|
}
|
|
}
|
|
|
|
var filterOrdersBySideBenchmark = &[]Detail{
|
|
{Side: Ask},
|
|
{Side: Ask},
|
|
{Side: Ask},
|
|
{Side: Ask},
|
|
{Side: Ask},
|
|
{Side: Ask},
|
|
{Side: Ask},
|
|
{Side: Ask},
|
|
{Side: Ask},
|
|
{Side: Ask},
|
|
}
|
|
|
|
// BenchmarkFilterOrdersBySide benchmark
|
|
//
|
|
// 372594 3049 ns/op 15840 B/op 5 allocs/op // PREV
|
|
// 7412187 148.8 ns/op 0 B/op 0 allocs/op // CURRENT
|
|
func BenchmarkFilterOrdersBySide(b *testing.B) {
|
|
for b.Loop() {
|
|
FilterOrdersBySide(filterOrdersBySideBenchmark, Ask)
|
|
}
|
|
}
|
|
|
|
func TestFilterOrdersByTimeRange(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
orders := []Detail{
|
|
{
|
|
Date: time.Unix(100, 0),
|
|
},
|
|
{
|
|
Date: time.Unix(110, 0),
|
|
},
|
|
{
|
|
Date: time.Unix(111, 0),
|
|
},
|
|
}
|
|
|
|
err := FilterOrdersByTimeRange(&orders, time.Unix(0, 0), time.Unix(0, 0))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(orders) != 3 {
|
|
t.Errorf("Orders failed to be filtered. Expected %v, received %v", 3, len(orders))
|
|
}
|
|
|
|
err = FilterOrdersByTimeRange(&orders, time.Unix(100, 0), time.Unix(111, 0))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(orders) != 3 {
|
|
t.Errorf("Orders failed to be filtered. Expected %v, received %v", 3, len(orders))
|
|
}
|
|
|
|
err = FilterOrdersByTimeRange(&orders, time.Unix(101, 0), time.Unix(111, 0))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(orders) != 2 {
|
|
t.Errorf("Orders failed to be filtered. Expected %v, received %v", 2, len(orders))
|
|
}
|
|
|
|
err = FilterOrdersByTimeRange(&orders, time.Unix(200, 0), time.Unix(300, 0))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(orders) != 0 {
|
|
t.Errorf("Orders failed to be filtered. Expected %v, received %v", 0, len(orders))
|
|
}
|
|
orders = append(orders, Detail{})
|
|
// test for event no timestamp is set on an order, best to include it
|
|
err = FilterOrdersByTimeRange(&orders, time.Unix(200, 0), time.Unix(300, 0))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(orders) != 1 {
|
|
t.Errorf("Orders failed to be filtered. Expected %v, received %v", 1, len(orders))
|
|
}
|
|
|
|
err = FilterOrdersByTimeRange(&orders, time.Unix(300, 0), time.Unix(50, 0))
|
|
if !errors.Is(err, common.ErrStartAfterEnd) {
|
|
t.Fatalf("received: '%v' but expected: '%v'", err, common.ErrStartAfterEnd)
|
|
}
|
|
}
|
|
|
|
var filterOrdersByTimeRangeBenchmark = &[]Detail{
|
|
{Date: time.Unix(100, 0)},
|
|
{Date: time.Unix(100, 0)},
|
|
{Date: time.Unix(100, 0)},
|
|
{Date: time.Unix(100, 0)},
|
|
{Date: time.Unix(100, 0)},
|
|
{Date: time.Unix(100, 0)},
|
|
{Date: time.Unix(100, 0)},
|
|
{Date: time.Unix(100, 0)},
|
|
{Date: time.Unix(100, 0)},
|
|
{Date: time.Unix(100, 0)},
|
|
}
|
|
|
|
// BenchmarkFilterOrdersByTimeRange benchmark
|
|
//
|
|
// 390822 3335 ns/op 15840 B/op 5 allocs/op // PREV
|
|
// 6201034 172.1 ns/op 0 B/op 0 allocs/op // CURRENT
|
|
func BenchmarkFilterOrdersByTimeRange(b *testing.B) {
|
|
for b.Loop() {
|
|
err := FilterOrdersByTimeRange(filterOrdersByTimeRangeBenchmark, time.Unix(50, 0), time.Unix(150, 0))
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFilterOrdersByPairs(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
orders := []Detail{
|
|
{
|
|
Pair: currency.NewPair(currency.BTC, currency.USD),
|
|
},
|
|
{
|
|
Pair: currency.NewPair(currency.LTC, currency.EUR),
|
|
},
|
|
{
|
|
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) != 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) != 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) != 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) != 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) != 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) != 2 {
|
|
t.Errorf("Orders failed to be filtered. Expected %v, received %v", 1, len(orders))
|
|
}
|
|
}
|
|
|
|
var filterOrdersByPairsBenchmark = &[]Detail{
|
|
{Pair: currency.NewPair(currency.BTC, currency.USD)},
|
|
{Pair: currency.NewPair(currency.BTC, currency.USD)},
|
|
{Pair: currency.NewPair(currency.BTC, currency.USD)},
|
|
{Pair: currency.NewPair(currency.BTC, currency.USD)},
|
|
{Pair: currency.NewPair(currency.BTC, currency.USD)},
|
|
{Pair: currency.NewPair(currency.BTC, currency.USD)},
|
|
{Pair: currency.NewPair(currency.BTC, currency.USD)},
|
|
{Pair: currency.NewPair(currency.BTC, currency.USD)},
|
|
{Pair: currency.NewPair(currency.BTC, currency.USD)},
|
|
{Pair: currency.NewPair(currency.BTC, currency.USD)},
|
|
}
|
|
|
|
// BenchmarkFilterOrdersByPairs benchmark
|
|
//
|
|
// 400032 2977 ns/op 15840 B/op 5 allocs/op // PREV
|
|
// 6977242 172.8 ns/op 0 B/op 0 allocs/op // CURRENT
|
|
func BenchmarkFilterOrdersByPairs(b *testing.B) {
|
|
pairs := []currency.Pair{currency.NewPair(currency.BTC, currency.USD)}
|
|
for b.Loop() {
|
|
FilterOrdersByPairs(filterOrdersByPairsBenchmark, pairs)
|
|
}
|
|
}
|
|
|
|
func TestSortOrdersByPrice(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
orders := []Detail{
|
|
{
|
|
Price: 100,
|
|
}, {
|
|
Price: 0,
|
|
}, {
|
|
Price: 50,
|
|
},
|
|
}
|
|
|
|
SortOrdersByPrice(&orders, false)
|
|
if orders[0].Price != 0 {
|
|
t.Errorf("Expected: '%v', received: '%v'", 0, orders[0].Price)
|
|
}
|
|
|
|
SortOrdersByPrice(&orders, true)
|
|
if orders[0].Price != 100 {
|
|
t.Errorf("Expected: '%v', received: '%v'", 100, orders[0].Price)
|
|
}
|
|
}
|
|
|
|
func TestSortOrdersByDate(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
orders := []Detail{
|
|
{
|
|
Date: time.Unix(0, 0),
|
|
}, {
|
|
Date: time.Unix(1, 0),
|
|
}, {
|
|
Date: time.Unix(2, 0),
|
|
},
|
|
}
|
|
|
|
SortOrdersByDate(&orders, false)
|
|
if orders[0].Date.Unix() != time.Unix(0, 0).Unix() {
|
|
t.Errorf("Expected: '%v', received: '%v'",
|
|
time.Unix(0, 0).Unix(),
|
|
orders[0].Date.Unix())
|
|
}
|
|
|
|
SortOrdersByDate(&orders, true)
|
|
if orders[0].Date.Unix() != time.Unix(2, 0).Unix() {
|
|
t.Errorf("Expected: '%v', received: '%v'",
|
|
time.Unix(2, 0).Unix(),
|
|
orders[0].Date.Unix())
|
|
}
|
|
}
|
|
|
|
func TestSortOrdersByCurrency(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
orders := []Detail{
|
|
{
|
|
Pair: currency.NewPairWithDelimiter(currency.BTC.String(),
|
|
currency.USD.String(),
|
|
"-"),
|
|
}, {
|
|
Pair: currency.NewPairWithDelimiter(currency.DOGE.String(),
|
|
currency.USD.String(),
|
|
"-"),
|
|
}, {
|
|
Pair: currency.NewPairWithDelimiter(currency.BTC.String(),
|
|
currency.RUB.String(),
|
|
"-"),
|
|
}, {
|
|
Pair: currency.NewPairWithDelimiter(currency.LTC.String(),
|
|
currency.EUR.String(),
|
|
"-"),
|
|
}, {
|
|
Pair: currency.NewPairWithDelimiter(currency.LTC.String(),
|
|
currency.AUD.String(),
|
|
"-"),
|
|
},
|
|
}
|
|
|
|
SortOrdersByCurrency(&orders, false)
|
|
if orders[0].Pair.String() != currency.BTC.String()+"-"+currency.RUB.String() {
|
|
t.Errorf("Expected: '%v', received: '%v'",
|
|
currency.BTC.String()+"-"+currency.RUB.String(),
|
|
orders[0].Pair.String())
|
|
}
|
|
|
|
SortOrdersByCurrency(&orders, true)
|
|
if orders[0].Pair.String() != currency.LTC.String()+"-"+currency.EUR.String() {
|
|
t.Errorf("Expected: '%v', received: '%v'",
|
|
currency.LTC.String()+"-"+currency.EUR.String(),
|
|
orders[0].Pair.String())
|
|
}
|
|
}
|
|
|
|
func TestSortOrdersByOrderSide(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
orders := []Detail{
|
|
{
|
|
Side: Buy,
|
|
}, {
|
|
Side: Sell,
|
|
}, {
|
|
Side: Sell,
|
|
}, {
|
|
Side: Buy,
|
|
},
|
|
}
|
|
|
|
SortOrdersBySide(&orders, false)
|
|
if !strings.EqualFold(orders[0].Side.String(), Buy.String()) {
|
|
t.Errorf("Expected: '%v', received: '%v'",
|
|
Buy,
|
|
orders[0].Side)
|
|
}
|
|
|
|
SortOrdersBySide(&orders, true)
|
|
if !strings.EqualFold(orders[0].Side.String(), Sell.String()) {
|
|
t.Errorf("Expected: '%v', received: '%v'",
|
|
Sell,
|
|
orders[0].Side)
|
|
}
|
|
}
|
|
|
|
func TestSortOrdersByOrderType(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
orders := []Detail{
|
|
{
|
|
Type: Market,
|
|
}, {
|
|
Type: Limit,
|
|
}, {
|
|
Type: ImmediateOrCancel,
|
|
}, {
|
|
Type: TrailingStop,
|
|
},
|
|
}
|
|
|
|
SortOrdersByType(&orders, false)
|
|
if !strings.EqualFold(orders[0].Type.String(), ImmediateOrCancel.String()) {
|
|
t.Errorf("Expected: '%v', received: '%v'",
|
|
ImmediateOrCancel,
|
|
orders[0].Type)
|
|
}
|
|
|
|
SortOrdersByType(&orders, true)
|
|
if !strings.EqualFold(orders[0].Type.String(), TrailingStop.String()) {
|
|
t.Errorf("Expected: '%v', received: '%v'",
|
|
TrailingStop,
|
|
orders[0].Type)
|
|
}
|
|
}
|
|
|
|
func TestStringToOrderSide(t *testing.T) {
|
|
cases := []struct {
|
|
in string
|
|
out Side
|
|
err error
|
|
}{
|
|
{"buy", Buy, nil},
|
|
{"BUY", Buy, nil},
|
|
{"bUy", Buy, nil},
|
|
{"sell", Sell, nil},
|
|
{"SELL", Sell, nil},
|
|
{"sElL", Sell, nil},
|
|
{"bid", Bid, nil},
|
|
{"BID", Bid, nil},
|
|
{"bId", Bid, nil},
|
|
{"ask", Ask, nil},
|
|
{"ASK", Ask, nil},
|
|
{"aSk", Ask, nil},
|
|
{"lOnG", Long, nil},
|
|
{"ShoRt", Short, nil},
|
|
{"any", AnySide, nil},
|
|
{"ANY", AnySide, nil},
|
|
{"aNy", AnySide, nil},
|
|
{"woahMan", UnknownSide, ErrSideIsInvalid},
|
|
}
|
|
for i := range cases {
|
|
testData := &cases[i]
|
|
t.Run(testData.in, func(t *testing.T) {
|
|
out, err := StringToOrderSide(testData.in)
|
|
if !errors.Is(err, testData.err) {
|
|
t.Fatalf("received: '%v' but expected: '%v'", err, testData.err)
|
|
}
|
|
if out != testData.out {
|
|
t.Errorf("Unexpected output %v. Expected %v", out, testData.out)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
var sideBenchmark Side
|
|
|
|
// 9756914 126.7 ns/op 0 B/op 0 allocs/op // PREV
|
|
// 25200660 57.63 ns/op 3 B/op 1 allocs/op // CURRENT
|
|
func BenchmarkStringToOrderSide(b *testing.B) {
|
|
for b.Loop() {
|
|
sideBenchmark, _ = StringToOrderSide("any")
|
|
}
|
|
}
|
|
|
|
func TestStringToOrderType(t *testing.T) {
|
|
cases := []struct {
|
|
in string
|
|
out Type
|
|
err error
|
|
}{
|
|
{"limit", Limit, nil},
|
|
{"LIMIT", Limit, nil},
|
|
{"lImIt", Limit, nil},
|
|
{"market", Market, nil},
|
|
{"MARKET", Market, nil},
|
|
{"mArKeT", Market, nil},
|
|
{"immediate_or_cancel", ImmediateOrCancel, nil},
|
|
{"IMMEDIATE_OR_CANCEL", ImmediateOrCancel, nil},
|
|
{"iMmEdIaTe_Or_CaNcEl", ImmediateOrCancel, nil},
|
|
{"iMmEdIaTe Or CaNcEl", ImmediateOrCancel, nil},
|
|
{"stop", Stop, nil},
|
|
{"STOP", Stop, nil},
|
|
{"sToP", Stop, nil},
|
|
{"sToP LiMit", StopLimit, nil},
|
|
{"ExchangE sToP Limit", StopLimit, nil},
|
|
{"trailing_stop", TrailingStop, nil},
|
|
{"TRAILING_STOP", TrailingStop, nil},
|
|
{"tRaIlInG_sToP", TrailingStop, nil},
|
|
{"tRaIlInG sToP", TrailingStop, nil},
|
|
{"fOk", FillOrKill, nil},
|
|
{"exchange fOk", FillOrKill, nil},
|
|
{"ios", IOS, nil},
|
|
{"post_ONly", PostOnly, nil},
|
|
{"any", AnyType, nil},
|
|
{"ANY", AnyType, nil},
|
|
{"aNy", AnyType, nil},
|
|
{"trigger", Trigger, nil},
|
|
{"TRIGGER", Trigger, nil},
|
|
{"tRiGgEr", Trigger, nil},
|
|
{"conDitiOnal", ConditionalStop, nil},
|
|
{"oCo", OCO, nil},
|
|
{"mMp", MarketMakerProtection, nil},
|
|
{"Mmp_And_Post_oNly", MarketMakerProtectionAndPostOnly, nil},
|
|
{"tWaP", TWAP, nil},
|
|
{"TWAP", TWAP, nil},
|
|
{"woahMan", UnknownType, errUnrecognisedOrderType},
|
|
{"chase", Chase, nil},
|
|
{"MOVE_ORDER_STOP", TrailingStop, nil},
|
|
{"mOVe_OrdeR_StoP", TrailingStop, nil},
|
|
{"optimal_limit_IoC", OptimalLimitIOC, nil},
|
|
{"Stop_market", StopMarket, nil},
|
|
{"liquidation", Liquidation, nil},
|
|
{"LiQuidation", Liquidation, nil},
|
|
{"take_profit", TakeProfit, nil},
|
|
{"Take ProfIt", TakeProfit, nil},
|
|
{"TAKE PROFIT MARkEt", TakeProfitMarket, nil},
|
|
{"TAKE_PROFIT_MARkEt", TakeProfitMarket, nil},
|
|
}
|
|
for i := range cases {
|
|
testData := &cases[i]
|
|
t.Run(testData.in, func(t *testing.T) {
|
|
out, err := StringToOrderType(testData.in)
|
|
if !errors.Is(err, testData.err) {
|
|
t.Fatalf("received: '%v' but expected: '%v'", err, testData.err)
|
|
}
|
|
if out != testData.out {
|
|
t.Errorf("Unexpected output %v. Expected %v", out, testData.out)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
var typeBenchmark Type
|
|
|
|
// 5703705 299.9 ns/op 0 B/op 0 allocs/op // PREV
|
|
// 16353608 81.23 ns/op 8 B/op 1 allocs/op // CURRENT
|
|
func BenchmarkStringToOrderType(b *testing.B) {
|
|
for b.Loop() {
|
|
typeBenchmark, _ = StringToOrderType("trigger")
|
|
}
|
|
}
|
|
|
|
var stringsToOrderStatus = []struct {
|
|
in string
|
|
out Status
|
|
err error
|
|
}{
|
|
{"any", AnyStatus, nil},
|
|
{"ANY", AnyStatus, nil},
|
|
{"aNy", AnyStatus, nil},
|
|
{"new", New, nil},
|
|
{"NEW", New, nil},
|
|
{"nEw", New, nil},
|
|
{"active", Active, nil},
|
|
{"ACTIVE", Active, nil},
|
|
{"aCtIvE", Active, nil},
|
|
{"partially_filled", PartiallyFilled, nil},
|
|
{"PARTIALLY_FILLED", PartiallyFilled, nil},
|
|
{"pArTiAlLy_FiLlEd", PartiallyFilled, nil},
|
|
{"filled", Filled, nil},
|
|
{"FILLED", Filled, nil},
|
|
{"fIlLeD", Filled, nil},
|
|
{"cancelled", Cancelled, nil},
|
|
{"CANCELlED", Cancelled, nil},
|
|
{"cAnCellEd", Cancelled, nil},
|
|
{"pending_cancel", PendingCancel, nil},
|
|
{"PENDING_CANCEL", PendingCancel, nil},
|
|
{"pENdInG_cAnCeL", PendingCancel, nil},
|
|
{"rejected", Rejected, nil},
|
|
{"REJECTED", Rejected, nil},
|
|
{"rEjEcTeD", Rejected, nil},
|
|
{"expired", Expired, nil},
|
|
{"EXPIRED", Expired, nil},
|
|
{"eXpIrEd", Expired, nil},
|
|
{"hidden", Hidden, nil},
|
|
{"HIDDEN", Hidden, nil},
|
|
{"hIdDeN", Hidden, nil},
|
|
{"market_unavailable", MarketUnavailable, nil},
|
|
{"MARKET_UNAVAILABLE", MarketUnavailable, nil},
|
|
{"mArKeT_uNaVaIlAbLe", MarketUnavailable, nil},
|
|
{"insufficient_balance", InsufficientBalance, nil},
|
|
{"INSUFFICIENT_BALANCE", InsufficientBalance, nil},
|
|
{"iNsUfFiCiEnT_bAlAnCe", InsufficientBalance, nil},
|
|
{"PARTIALLY_CANCELLEd", PartiallyCancelled, nil},
|
|
{"partially canceLLed", PartiallyCancelled, nil},
|
|
{"opeN", Open, nil},
|
|
{"cLosEd", Closed, nil},
|
|
{"cancellinG", Cancelling, nil},
|
|
{"woahMan", UnknownStatus, errUnrecognisedOrderStatus},
|
|
{"PLAcED", New, nil},
|
|
{"ACCePTED", New, nil},
|
|
{"FAILeD", Rejected, nil},
|
|
}
|
|
|
|
func TestStringToOrderStatus(t *testing.T) {
|
|
for i := range stringsToOrderStatus {
|
|
testData := &stringsToOrderStatus[i]
|
|
t.Run(testData.in, func(t *testing.T) {
|
|
out, err := StringToOrderStatus(testData.in)
|
|
if !errors.Is(err, testData.err) {
|
|
t.Fatalf("received: '%v' but expected: '%v'", err, testData.err)
|
|
}
|
|
if out != testData.out {
|
|
t.Errorf("Unexpected output %v. Expected %v", out, testData.out)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
var statusBenchmark Status
|
|
|
|
// 3569052 351.8 ns/op 0 B/op 0 allocs/op // PREV
|
|
// 11126791 101.9 ns/op 24 B/op 1 allocs/op // CURRENT
|
|
func BenchmarkStringToOrderStatus(b *testing.B) {
|
|
for b.Loop() {
|
|
statusBenchmark, _ = StringToOrderStatus("market_unavailable")
|
|
}
|
|
}
|
|
|
|
func TestUpdateOrderFromModifyResponse(t *testing.T) {
|
|
od := Detail{OrderID: "1"}
|
|
updated := time.Now()
|
|
|
|
pair, err := currency.NewPairFromString("BTCUSD")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
om := ModifyResponse{
|
|
ImmediateOrCancel: true,
|
|
PostOnly: true,
|
|
Price: 1,
|
|
Amount: 1,
|
|
TriggerPrice: 1,
|
|
RemainingAmount: 1,
|
|
Exchange: "1",
|
|
Type: 1,
|
|
Side: 1,
|
|
Status: 1,
|
|
AssetType: 1,
|
|
LastUpdated: updated,
|
|
Pair: pair,
|
|
}
|
|
|
|
od.UpdateOrderFromModifyResponse(&om)
|
|
|
|
if !od.ImmediateOrCancel {
|
|
t.Error("Failed to update")
|
|
}
|
|
if !od.PostOnly {
|
|
t.Error("Failed to update")
|
|
}
|
|
if od.Price != 1 {
|
|
t.Error("Failed to update")
|
|
}
|
|
if od.Amount != 1 {
|
|
t.Error("Failed to update")
|
|
}
|
|
if od.TriggerPrice != 1 {
|
|
t.Error("Failed to update")
|
|
}
|
|
if od.RemainingAmount != 1 {
|
|
t.Error("Failed to update")
|
|
}
|
|
if od.Exchange != "" {
|
|
t.Error("Should not be able to update exchange via modify")
|
|
}
|
|
if od.OrderID != "1" {
|
|
t.Error("Failed to update")
|
|
}
|
|
if od.Type != 1 {
|
|
t.Error("Failed to update")
|
|
}
|
|
if od.Side != 1 {
|
|
t.Error("Failed to update")
|
|
}
|
|
if od.Status != 1 {
|
|
t.Error("Failed to update")
|
|
}
|
|
if od.AssetType != 1 {
|
|
t.Error("Failed to update")
|
|
}
|
|
if od.LastUpdated != updated {
|
|
t.Error("Failed to update")
|
|
}
|
|
if od.Pair.String() != "BTCUSD" {
|
|
t.Error("Failed to update")
|
|
}
|
|
if od.Trades != nil {
|
|
t.Error("Failed to update")
|
|
}
|
|
}
|
|
|
|
func TestUpdateOrderFromDetail(t *testing.T) {
|
|
leet := "1337"
|
|
|
|
updated := time.Now()
|
|
|
|
pair, err := currency.NewPairFromString("BTCUSD")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
id, err := uuid.NewV4()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var od *Detail
|
|
err = od.UpdateOrderFromDetail(nil)
|
|
if !errors.Is(err, ErrOrderDetailIsNil) {
|
|
t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderDetailIsNil)
|
|
}
|
|
|
|
om := &Detail{
|
|
ImmediateOrCancel: true,
|
|
HiddenOrder: true,
|
|
FillOrKill: true,
|
|
PostOnly: true,
|
|
Leverage: 1,
|
|
Price: 1,
|
|
Amount: 1,
|
|
LimitPriceUpper: 1,
|
|
LimitPriceLower: 1,
|
|
TriggerPrice: 1,
|
|
QuoteAmount: 1,
|
|
ExecutedAmount: 1,
|
|
RemainingAmount: 1,
|
|
Fee: 1,
|
|
Exchange: "1",
|
|
InternalOrderID: id,
|
|
OrderID: "1",
|
|
AccountID: "1",
|
|
ClientID: "1",
|
|
ClientOrderID: "DukeOfWombleton",
|
|
Type: 1,
|
|
Side: 1,
|
|
Status: 1,
|
|
AssetType: 1,
|
|
LastUpdated: updated,
|
|
Pair: pair,
|
|
Trades: []TradeHistory{},
|
|
}
|
|
|
|
od = &Detail{Exchange: "test"}
|
|
|
|
err = od.UpdateOrderFromDetail(nil)
|
|
if !errors.Is(err, ErrOrderDetailIsNil) {
|
|
t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderDetailIsNil)
|
|
}
|
|
|
|
err = od.UpdateOrderFromDetail(om)
|
|
if !errors.Is(err, nil) {
|
|
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
|
}
|
|
if od.InternalOrderID != id {
|
|
t.Error("Failed to initialize the internal order ID")
|
|
}
|
|
if !od.ImmediateOrCancel {
|
|
t.Error("Failed to update")
|
|
}
|
|
if !od.HiddenOrder {
|
|
t.Error("Failed to update")
|
|
}
|
|
if !od.FillOrKill {
|
|
t.Error("Failed to update")
|
|
}
|
|
if !od.PostOnly {
|
|
t.Error("Failed to update")
|
|
}
|
|
if od.Leverage != 1 {
|
|
t.Error("Failed to update")
|
|
}
|
|
if od.Price != 1 {
|
|
t.Error("Failed to update")
|
|
}
|
|
if od.Amount != 1 {
|
|
t.Error("Failed to update")
|
|
}
|
|
if od.LimitPriceLower != 1 {
|
|
t.Error("Failed to update")
|
|
}
|
|
if od.LimitPriceUpper != 1 {
|
|
t.Error("Failed to update")
|
|
}
|
|
if od.TriggerPrice != 1 {
|
|
t.Error("Failed to update")
|
|
}
|
|
if od.QuoteAmount != 1 {
|
|
t.Error("Failed to update")
|
|
}
|
|
if od.ExecutedAmount != 1 {
|
|
t.Error("Failed to update")
|
|
}
|
|
if od.RemainingAmount != 1 {
|
|
t.Error("Failed to update")
|
|
}
|
|
if od.Fee != 1 {
|
|
t.Error("Failed to update")
|
|
}
|
|
if od.Exchange != "test" {
|
|
t.Error("Should not be able to update exchange via modify")
|
|
}
|
|
if od.OrderID != "1" {
|
|
t.Error("Failed to update")
|
|
}
|
|
if od.ClientID != "1" {
|
|
t.Error("Failed to update")
|
|
}
|
|
if od.ClientOrderID != "DukeOfWombleton" {
|
|
t.Error("Failed to update")
|
|
}
|
|
if od.Type != 1 {
|
|
t.Error("Failed to update")
|
|
}
|
|
if od.Side != 1 {
|
|
t.Error("Failed to update")
|
|
}
|
|
if od.Status != 1 {
|
|
t.Error("Failed to update")
|
|
}
|
|
if od.AssetType != 1 {
|
|
t.Error("Failed to update")
|
|
}
|
|
if od.LastUpdated != updated {
|
|
t.Error("Failed to update")
|
|
}
|
|
if od.Pair.String() != "BTCUSD" {
|
|
t.Error("Failed to update")
|
|
}
|
|
if od.Trades != nil {
|
|
t.Error("Failed to update")
|
|
}
|
|
|
|
om.Trades = append(om.Trades, TradeHistory{TID: "1"}, TradeHistory{TID: "2"})
|
|
err = od.UpdateOrderFromDetail(om)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(od.Trades) != 2 {
|
|
t.Error("Failed to add trades")
|
|
}
|
|
om.Trades[0].Exchange = leet
|
|
om.Trades[0].Price = 1337
|
|
om.Trades[0].Fee = 1337
|
|
om.Trades[0].IsMaker = true
|
|
om.Trades[0].Timestamp = updated
|
|
om.Trades[0].Description = leet
|
|
om.Trades[0].Side = UnknownSide
|
|
om.Trades[0].Type = UnknownType
|
|
om.Trades[0].Amount = 1337
|
|
err = od.UpdateOrderFromDetail(om)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if od.Trades[0].Exchange == leet {
|
|
t.Error("Should not be able to update exchange from update")
|
|
}
|
|
if od.Trades[0].Price != 1337 {
|
|
t.Error("Failed to update trades")
|
|
}
|
|
if od.Trades[0].Fee != 1337 {
|
|
t.Error("Failed to update trades")
|
|
}
|
|
if !od.Trades[0].IsMaker {
|
|
t.Error("Failed to update trades")
|
|
}
|
|
if od.Trades[0].Timestamp != updated {
|
|
t.Error("Failed to update trades")
|
|
}
|
|
if od.Trades[0].Description != leet {
|
|
t.Error("Failed to update trades")
|
|
}
|
|
if od.Trades[0].Side != UnknownSide {
|
|
t.Error("Failed to update trades")
|
|
}
|
|
if od.Trades[0].Type != UnknownType {
|
|
t.Error("Failed to update trades")
|
|
}
|
|
if od.Trades[0].Amount != 1337 {
|
|
t.Error("Failed to update trades")
|
|
}
|
|
|
|
id, err = uuid.NewV4()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
om = &Detail{
|
|
InternalOrderID: id,
|
|
}
|
|
|
|
err = od.UpdateOrderFromDetail(om)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if od.InternalOrderID == id {
|
|
t.Error("Should not be able to update the internal order ID after initialization")
|
|
}
|
|
}
|
|
|
|
func TestClassificationError_Error(t *testing.T) {
|
|
class := ClassificationError{OrderID: "1337", Exchange: "test", Err: errors.New("test error")}
|
|
if class.Error() != "Exchange test: OrderID: 1337 classification error: test error" {
|
|
t.Fatal("unexpected output")
|
|
}
|
|
class.OrderID = ""
|
|
if class.Error() != "Exchange test: classification error: test error" {
|
|
t.Fatal("unexpected output")
|
|
}
|
|
}
|
|
|
|
func TestValidationOnOrderTypes(t *testing.T) {
|
|
var cancelMe *Cancel
|
|
if cancelMe.Validate() != ErrCancelOrderIsNil {
|
|
t.Fatal("unexpected error")
|
|
}
|
|
|
|
cancelMe = new(Cancel)
|
|
err := cancelMe.Validate()
|
|
if !errors.Is(err, nil) {
|
|
t.Errorf("received '%v' expected '%v'", err, nil)
|
|
}
|
|
|
|
err = cancelMe.Validate(cancelMe.PairAssetRequired())
|
|
if err == nil || err.Error() != ErrPairIsEmpty.Error() {
|
|
t.Errorf("received '%v' expected '%v'", err, ErrPairIsEmpty)
|
|
}
|
|
|
|
cancelMe.Pair = currency.NewPair(currency.BTC, currency.USDT)
|
|
err = cancelMe.Validate(cancelMe.PairAssetRequired())
|
|
if err == nil || err.Error() != ErrAssetNotSet.Error() {
|
|
t.Errorf("received '%v' expected '%v'", err, ErrAssetNotSet)
|
|
}
|
|
|
|
cancelMe.AssetType = asset.Spot
|
|
err = cancelMe.Validate(cancelMe.PairAssetRequired())
|
|
if !errors.Is(err, nil) {
|
|
t.Errorf("received '%v' expected '%v'", err, nil)
|
|
}
|
|
|
|
if cancelMe.Validate(cancelMe.StandardCancel()) == nil {
|
|
t.Fatal("expected error")
|
|
}
|
|
|
|
if cancelMe.Validate(validate.Check(func() error {
|
|
return nil
|
|
})) != nil {
|
|
t.Fatal("should return nil")
|
|
}
|
|
cancelMe.OrderID = "1337"
|
|
if cancelMe.Validate(cancelMe.StandardCancel()) != nil {
|
|
t.Fatal("should return nil")
|
|
}
|
|
|
|
var getOrders *MultiOrderRequest
|
|
err = getOrders.Validate()
|
|
if !errors.Is(err, ErrGetOrdersRequestIsNil) {
|
|
t.Fatalf("received: '%v' but expected: '%v'", err, ErrGetOrdersRequestIsNil)
|
|
}
|
|
|
|
getOrders = new(MultiOrderRequest)
|
|
err = getOrders.Validate()
|
|
if !errors.Is(err, asset.ErrNotSupported) {
|
|
t.Fatalf("received: '%v' but expected: '%v'", err, asset.ErrNotSupported)
|
|
}
|
|
|
|
getOrders.AssetType = asset.Spot
|
|
err = getOrders.Validate()
|
|
if !errors.Is(err, ErrSideIsInvalid) {
|
|
t.Fatalf("received: '%v' but expected: '%v'", err, ErrSideIsInvalid)
|
|
}
|
|
|
|
getOrders.Side = AnySide
|
|
err = getOrders.Validate()
|
|
if !errors.Is(err, errUnrecognisedOrderType) {
|
|
t.Fatalf("received: '%v' but expected: '%v'", err, errUnrecognisedOrderType)
|
|
}
|
|
|
|
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
|
|
}))
|
|
if !errors.Is(err, nil) {
|
|
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
|
}
|
|
|
|
var modifyOrder *Modify
|
|
if modifyOrder.Validate() != ErrModifyOrderIsNil {
|
|
t.Fatal("unexpected error")
|
|
}
|
|
|
|
modifyOrder = new(Modify)
|
|
if modifyOrder.Validate() != ErrPairIsEmpty {
|
|
t.Fatal("unexpected error")
|
|
}
|
|
|
|
p, err := currency.NewPairFromString("BTC-USD")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
modifyOrder.Pair = p
|
|
if modifyOrder.Validate() != ErrAssetNotSet {
|
|
t.Fatal("unexpected error")
|
|
}
|
|
|
|
modifyOrder.AssetType = asset.Spot
|
|
if modifyOrder.Validate() != ErrOrderIDNotSet {
|
|
t.Fatal("unexpected error")
|
|
}
|
|
|
|
modifyOrder.ClientOrderID = "1337"
|
|
if modifyOrder.Validate() != nil {
|
|
t.Fatal("should not error")
|
|
}
|
|
|
|
if modifyOrder.Validate(validate.Check(func() error {
|
|
return errors.New("this should error")
|
|
})) == nil {
|
|
t.Fatal("expected error")
|
|
}
|
|
|
|
if modifyOrder.Validate(validate.Check(func() error {
|
|
return nil
|
|
})) != nil {
|
|
t.Fatal("unexpected error")
|
|
}
|
|
}
|
|
|
|
func TestMatchFilter(t *testing.T) {
|
|
t.Parallel()
|
|
id := uuid.Must(uuid.NewV4())
|
|
|
|
assert.True(t, new(Detail).MatchFilter(&Filter{}), "an empty filter should match an empty order")
|
|
assert.True(t, (&Detail{Exchange: "E", OrderID: "A", Side: Sell, Pair: currency.NewBTCUSD()}).MatchFilter(&Filter{}), "an empty filter should match any order")
|
|
|
|
tests := []struct {
|
|
description string
|
|
filter Filter
|
|
order Detail
|
|
result bool
|
|
}{
|
|
{"Exchange ✓", Filter{Exchange: "A"}, Detail{Exchange: "A"}, true},
|
|
{"Exchange 𐄂", Filter{Exchange: "A"}, Detail{Exchange: "B"}, false},
|
|
{"Exchange Empty", Filter{Exchange: "A"}, Detail{}, false},
|
|
{"InternalOrderID ✓", Filter{InternalOrderID: id}, Detail{InternalOrderID: id}, true},
|
|
{"InternalOrderID 𐄂", Filter{InternalOrderID: id}, Detail{InternalOrderID: uuid.Must(uuid.NewV4())}, false},
|
|
{"InternalOrderID Empty", Filter{InternalOrderID: id}, Detail{}, false},
|
|
{"OrderID ✓", Filter{OrderID: "A"}, Detail{OrderID: "A"}, true},
|
|
{"OrderID 𐄂", Filter{OrderID: "A"}, Detail{OrderID: "B"}, false},
|
|
{"OrderID Empty", Filter{OrderID: "A"}, Detail{}, false},
|
|
{"ClientOrderID ✓", Filter{ClientOrderID: "A"}, Detail{ClientOrderID: "A"}, true},
|
|
{"ClientOrderID 𐄂", Filter{ClientOrderID: "A"}, Detail{ClientOrderID: "B"}, false},
|
|
{"ClientOrderID Empty", Filter{ClientOrderID: "A"}, Detail{}, false},
|
|
{"ClientID ✓", Filter{ClientID: "A"}, Detail{ClientID: "A"}, true},
|
|
{"ClientID 𐄂", Filter{ClientID: "A"}, Detail{ClientID: "B"}, false},
|
|
{"ClientID Empty", Filter{ClientID: "A"}, Detail{}, false},
|
|
{"AnySide Buy", Filter{Side: AnySide}, Detail{Side: Buy}, true},
|
|
{"AnySide Sell", Filter{Side: AnySide}, Detail{Side: Sell}, true},
|
|
{"AnySide Empty", Filter{Side: AnySide}, Detail{}, true},
|
|
{"Side ✓", Filter{Side: Buy}, Detail{Side: Buy}, true},
|
|
{"Side 𐄂", Filter{Side: Buy}, Detail{Side: Sell}, false},
|
|
{"Side Empty", Filter{Side: Buy}, Detail{}, false},
|
|
{"Status ✓", Filter{Status: Open}, Detail{Status: Open}, true},
|
|
{"Status 𐄂", Filter{Status: Open}, Detail{Status: New}, false},
|
|
{"Status Empty", Filter{Status: Open}, Detail{}, false},
|
|
{"AssetType ✓", Filter{AssetType: asset.Spot}, Detail{AssetType: asset.Spot}, true},
|
|
{"AssetType 𐄂", Filter{AssetType: asset.Spot}, Detail{AssetType: asset.Index}, false},
|
|
{"AssetType Empty", Filter{AssetType: asset.Spot}, Detail{}, false},
|
|
{"Pair ✓", Filter{Pair: currency.NewBTCUSDT()}, Detail{Pair: currency.NewBTCUSDT()}, true},
|
|
{"Pair 𐄂", Filter{Pair: currency.NewBTCUSDT()}, Detail{Pair: currency.NewBTCUSD()}, false},
|
|
{"Pair Empty", Filter{Pair: currency.NewBTCUSDT()}, Detail{}, false},
|
|
{"AccountID ✓", Filter{AccountID: "A"}, Detail{AccountID: "A"}, true},
|
|
{"AccountID 𐄂", Filter{AccountID: "A"}, Detail{AccountID: "B"}, false},
|
|
{"AccountID Empty", Filter{AccountID: "A"}, Detail{}, false},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.description, func(t *testing.T) {
|
|
t.Parallel()
|
|
require.Equal(t, tt.result, tt.order.MatchFilter(&tt.filter), "MatchFilter must return correctly")
|
|
})
|
|
}
|
|
}
|
|
|
|
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
|
|
expectedResult 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.expectedResult {
|
|
t.Errorf("amountTests[%v] failed", num)
|
|
}
|
|
}
|
|
|
|
statusTests := map[int]struct {
|
|
o Detail
|
|
expectedResult bool
|
|
}{
|
|
// For now force inactive on any status
|
|
0: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: AnyStatus}, false},
|
|
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},
|
|
// For now force inactive on unknown status
|
|
13: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: UnknownStatus}, false},
|
|
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.expectedResult {
|
|
t.Fatalf("statusTests[%v] failed", num)
|
|
}
|
|
}
|
|
}
|
|
|
|
var activeBenchmark = Detail{Status: Pending, Amount: 1}
|
|
|
|
// 610732089 2.414 ns/op 0 B/op 0 allocs/op // PREV
|
|
// 1000000000 1.188 ns/op 0 B/op 0 allocs/op // CURRENT
|
|
func BenchmarkIsActive(b *testing.B) {
|
|
for b.Loop() {
|
|
if !activeBenchmark.IsActive() {
|
|
b.Fatal("expected true")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestIsInactive(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
|
|
expectedResult 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.expectedResult {
|
|
t.Errorf("amountTests[%v] failed", num)
|
|
}
|
|
}
|
|
|
|
statusTests := map[int]struct {
|
|
o Detail
|
|
expectedResult bool
|
|
}{
|
|
// For now force inactive on any status
|
|
0: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: AnyStatus}, true},
|
|
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},
|
|
// For now force inactive on unknown status
|
|
13: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: UnknownStatus}, true},
|
|
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.expectedResult {
|
|
t.Errorf("statusTests[%v] failed", num)
|
|
}
|
|
}
|
|
}
|
|
|
|
var inactiveBenchmark = Detail{Status: Closed, Amount: 1}
|
|
|
|
// 1000000000 1.043 ns/op 0 B/op 0 allocs/op // CURRENT
|
|
func BenchmarkIsInactive(b *testing.B) {
|
|
for b.Loop() {
|
|
if !inactiveBenchmark.IsInactive() {
|
|
b.Fatal("expected true")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestIsOrderPlaced(t *testing.T) {
|
|
t.Parallel()
|
|
statusTests := map[int]struct {
|
|
o Detail
|
|
expectedResult bool
|
|
}{
|
|
0: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: AnyStatus}, false},
|
|
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}, true},
|
|
4: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: PartiallyFilled}, true},
|
|
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}, 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}, true},
|
|
12: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: Hidden}, true},
|
|
13: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: UnknownStatus}, false},
|
|
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}, true},
|
|
17: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: Pending}, true},
|
|
}
|
|
// specific tests
|
|
for num, tt := range statusTests {
|
|
t.Run(fmt.Sprintf("TEST CASE: %d", num), func(t *testing.T) {
|
|
t.Parallel()
|
|
if tt.o.WasOrderPlaced() != tt.expectedResult {
|
|
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,
|
|
}
|
|
od.GenerateInternalOrderID()
|
|
if od.InternalOrderID != id {
|
|
t.Error("Should not be able to generate a new internal order ID")
|
|
}
|
|
|
|
od = Detail{}
|
|
od.GenerateInternalOrderID()
|
|
if od.InternalOrderID.IsNil() {
|
|
t.Error("unable to generate internal order ID")
|
|
}
|
|
}
|
|
|
|
func TestDetail_Copy(t *testing.T) {
|
|
t.Parallel()
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDeriveModify(t *testing.T) {
|
|
t.Parallel()
|
|
var o *Detail
|
|
_, err := o.DeriveModify()
|
|
require.ErrorIs(t, err, errOrderDetailIsNil)
|
|
|
|
pair := currency.NewPair(currency.BTC, currency.AUD)
|
|
|
|
o = &Detail{
|
|
Exchange: "wow",
|
|
OrderID: "wow2",
|
|
ClientOrderID: "wow3",
|
|
Type: Market,
|
|
Side: Long,
|
|
AssetType: asset.Futures,
|
|
Pair: pair,
|
|
}
|
|
|
|
mod, err := o.DeriveModify()
|
|
require.NoError(t, err)
|
|
require.NotNil(t, mod)
|
|
|
|
exp := &Modify{
|
|
Exchange: "wow",
|
|
OrderID: "wow2",
|
|
ClientOrderID: "wow3",
|
|
Type: Market,
|
|
Side: Long,
|
|
AssetType: asset.Futures,
|
|
Pair: pair,
|
|
}
|
|
assert.Equal(t, exp, mod)
|
|
}
|
|
|
|
func TestDeriveModifyResponse(t *testing.T) {
|
|
t.Parallel()
|
|
var mod *Modify
|
|
_, err := mod.DeriveModifyResponse()
|
|
require.ErrorIs(t, err, errOrderDetailIsNil)
|
|
|
|
pair := currency.NewPair(currency.BTC, currency.AUD)
|
|
|
|
mod = &Modify{
|
|
Exchange: "wow",
|
|
OrderID: "wow2",
|
|
ClientOrderID: "wow3",
|
|
Type: Market,
|
|
Side: Long,
|
|
AssetType: asset.Futures,
|
|
Pair: pair,
|
|
}
|
|
|
|
modresp, err := mod.DeriveModifyResponse()
|
|
require.NoError(t, err, "DeriveModifyResponse must not error")
|
|
require.NotNil(t, modresp)
|
|
|
|
exp := &ModifyResponse{
|
|
Exchange: "wow",
|
|
OrderID: "wow2",
|
|
ClientOrderID: "wow3",
|
|
Type: Market,
|
|
Side: Long,
|
|
AssetType: asset.Futures,
|
|
Pair: pair,
|
|
}
|
|
assert.Equal(t, exp, modresp)
|
|
}
|
|
|
|
func TestDeriveCancel(t *testing.T) {
|
|
t.Parallel()
|
|
var o *Detail
|
|
if _, err := o.DeriveCancel(); !errors.Is(err, errOrderDetailIsNil) {
|
|
t.Fatalf("received: '%v' but expected: '%v'", err, errOrderDetailIsNil)
|
|
}
|
|
|
|
pair := currency.NewPair(currency.BTC, currency.AUD)
|
|
|
|
o = &Detail{
|
|
Exchange: "wow",
|
|
OrderID: "wow1",
|
|
AccountID: "wow2",
|
|
ClientID: "wow3",
|
|
ClientOrderID: "wow4",
|
|
Type: Market,
|
|
Side: Long,
|
|
Pair: pair,
|
|
AssetType: asset.Futures,
|
|
}
|
|
cancel, err := o.DeriveCancel()
|
|
if !errors.Is(err, nil) {
|
|
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
|
}
|
|
if cancel.Exchange != "wow" ||
|
|
cancel.OrderID != "wow1" ||
|
|
cancel.AccountID != "wow2" ||
|
|
cancel.ClientID != "wow3" ||
|
|
cancel.ClientOrderID != "wow4" ||
|
|
cancel.Type != Market ||
|
|
cancel.Side != Long ||
|
|
!cancel.Pair.Equal(pair) ||
|
|
cancel.AssetType != asset.Futures {
|
|
t.Fatalf("unexpected values %+v", cancel)
|
|
}
|
|
}
|
|
|
|
func TestGetOrdersRequest_Filter(t *testing.T) {
|
|
request := new(MultiOrderRequest)
|
|
request.AssetType = asset.Spot
|
|
request.Type = AnyType
|
|
request.Side = AnySide
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestIsValidOrderSubmissionSide(t *testing.T) {
|
|
t.Parallel()
|
|
if IsValidOrderSubmissionSide(UnknownSide) {
|
|
t.Error("expected false")
|
|
}
|
|
if !IsValidOrderSubmissionSide(Buy) {
|
|
t.Error("expected true")
|
|
}
|
|
if IsValidOrderSubmissionSide(CouldNotBuy) {
|
|
t.Error("expected false")
|
|
}
|
|
}
|
|
|
|
func TestAdjustBaseAmount(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var s *SubmitResponse
|
|
err := s.AdjustBaseAmount(0)
|
|
if !errors.Is(err, errOrderSubmitResponseIsNil) {
|
|
t.Fatalf("received: '%v' but expected: '%v'", err, errOrderSubmitResponseIsNil)
|
|
}
|
|
|
|
s = &SubmitResponse{}
|
|
err = s.AdjustBaseAmount(0)
|
|
if !errors.Is(err, errAmountIsZero) {
|
|
t.Fatalf("received: '%v' but expected: '%v'", err, errAmountIsZero)
|
|
}
|
|
|
|
s.Amount = 1.7777777777
|
|
err = s.AdjustBaseAmount(1.7777777777)
|
|
if !errors.Is(err, nil) {
|
|
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
|
}
|
|
|
|
if s.Amount != 1.7777777777 {
|
|
t.Fatalf("received: '%v' but expected: '%v'", s.Amount, 1.7777777777)
|
|
}
|
|
|
|
s.Amount = 1.7777777777
|
|
err = s.AdjustBaseAmount(1.777)
|
|
if !errors.Is(err, nil) {
|
|
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
|
}
|
|
|
|
if s.Amount != 1.777 {
|
|
t.Fatalf("received: '%v' but expected: '%v'", s.Amount, 1.777)
|
|
}
|
|
}
|
|
|
|
func TestAdjustQuoteAmount(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var s *SubmitResponse
|
|
err := s.AdjustQuoteAmount(0)
|
|
if !errors.Is(err, errOrderSubmitResponseIsNil) {
|
|
t.Fatalf("received: '%v' but expected: '%v'", err, errOrderSubmitResponseIsNil)
|
|
}
|
|
|
|
s = &SubmitResponse{}
|
|
err = s.AdjustQuoteAmount(0)
|
|
if !errors.Is(err, errAmountIsZero) {
|
|
t.Fatalf("received: '%v' but expected: '%v'", err, errAmountIsZero)
|
|
}
|
|
|
|
s.QuoteAmount = 5.222222222222
|
|
err = s.AdjustQuoteAmount(5.222222222222)
|
|
if !errors.Is(err, nil) {
|
|
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
|
}
|
|
|
|
if s.QuoteAmount != 5.222222222222 {
|
|
t.Fatalf("received: '%v' but expected: '%v'", s.Amount, 5.222222222222)
|
|
}
|
|
|
|
s.QuoteAmount = 5.222222222222
|
|
err = s.AdjustQuoteAmount(5.22222222)
|
|
if !errors.Is(err, nil) {
|
|
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
|
}
|
|
|
|
if s.QuoteAmount != 5.22222222 {
|
|
t.Fatalf("received: '%v' but expected: '%v'", s.Amount, 5.22222222)
|
|
}
|
|
}
|
|
|
|
func TestSideUnmarshal(t *testing.T) {
|
|
t.Parallel()
|
|
var s Side
|
|
assert.NoError(t, s.UnmarshalJSON([]byte(`"SELL"`)), "Quoted valid side okay")
|
|
assert.Equal(t, Sell, s, "Correctly set order Side")
|
|
assert.ErrorIs(t, s.UnmarshalJSON([]byte(`"STEAL"`)), ErrSideIsInvalid, "Quoted invalid side errors")
|
|
var jErr *json.UnmarshalTypeError
|
|
assert.ErrorAs(t, s.UnmarshalJSON([]byte(`14`)), &jErr, "non-string valid json is rejected")
|
|
}
|
|
|
|
func TestSideMarshalJSON(t *testing.T) {
|
|
t.Parallel()
|
|
b, err := Buy.MarshalJSON()
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, `"BUY"`, string(b))
|
|
b, err = UnknownSide.MarshalJSON()
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, `"UNKNOWN"`, string(b))
|
|
}
|
|
|
|
func TestGetTradeAmount(t *testing.T) {
|
|
t.Parallel()
|
|
var s *Submit
|
|
require.Zero(t, s.GetTradeAmount(protocol.TradingRequirements{}))
|
|
baseAmount := 420.0
|
|
quoteAmount := 69.0
|
|
s = &Submit{Amount: baseAmount, QuoteAmount: quoteAmount}
|
|
// below will default to base amount with nothing set
|
|
require.Equal(t, baseAmount, s.GetTradeAmount(protocol.TradingRequirements{}))
|
|
require.Equal(t, baseAmount, s.GetTradeAmount(protocol.TradingRequirements{SpotMarketOrderAmountPurchaseQuotationOnly: true}))
|
|
s.AssetType = asset.Spot
|
|
s.Type = Market
|
|
s.Side = Buy
|
|
require.Equal(t, quoteAmount, s.GetTradeAmount(protocol.TradingRequirements{SpotMarketOrderAmountPurchaseQuotationOnly: true}))
|
|
require.Equal(t, baseAmount, s.GetTradeAmount(protocol.TradingRequirements{SpotMarketOrderAmountSellBaseOnly: true}))
|
|
s.Side = Sell
|
|
require.Equal(t, baseAmount, s.GetTradeAmount(protocol.TradingRequirements{SpotMarketOrderAmountSellBaseOnly: true}))
|
|
}
|
|
|
|
func TestStringToTrackingMode(t *testing.T) {
|
|
t.Parallel()
|
|
inputs := map[string]TrackingMode{
|
|
"diStance": Distance,
|
|
"distance": Distance,
|
|
"Percentage": Percentage,
|
|
"percentage": Percentage,
|
|
"": UnknownTrackingMode,
|
|
}
|
|
for k, v := range inputs {
|
|
assert.Equal(t, v, StringToTrackingMode(k))
|
|
}
|
|
}
|
|
|
|
func TestTrackingModeString(t *testing.T) {
|
|
t.Parallel()
|
|
inputs := map[TrackingMode]string{
|
|
Distance: "distance",
|
|
Percentage: "percentage",
|
|
UnknownTrackingMode: "",
|
|
}
|
|
for k, v := range inputs {
|
|
require.Equal(t, v, k.String())
|
|
}
|
|
}
|
|
|
|
func TestMarshalOrder(t *testing.T) {
|
|
t.Parallel()
|
|
btx := currency.NewBTCUSDT()
|
|
btx.Delimiter = "-"
|
|
orderSubmit := Submit{
|
|
Exchange: "test",
|
|
Pair: btx,
|
|
AssetType: asset.Spot,
|
|
MarginType: margin.Multi,
|
|
Side: Buy,
|
|
Type: Market,
|
|
Amount: 1,
|
|
Price: 1000,
|
|
}
|
|
j, err := json.Marshal(orderSubmit)
|
|
require.NoError(t, err, "json.Marshal must not error")
|
|
exp := []byte(`{"Exchange":"test","Type":4,"Side":"BUY","Pair":"BTC-USDT","AssetType":"spot","ImmediateOrCancel":false,"FillOrKill":false,"PostOnly":false,"ReduceOnly":false,"Leverage":0,"Price":1000,"Amount":1,"QuoteAmount":0,"TriggerPrice":0,"TriggerPriceType":0,"ClientID":"","ClientOrderID":"","AutoBorrow":false,"MarginType":"multi","RetrieveFees":false,"RetrieveFeeDelay":0,"RiskManagementModes":{"Mode":"","TakeProfit":{"Enabled":false,"TriggerPriceType":0,"Price":0,"LimitPrice":0,"OrderType":0},"StopLoss":{"Enabled":false,"TriggerPriceType":0,"Price":0,"LimitPrice":0,"OrderType":0},"StopEntry":{"Enabled":false,"TriggerPriceType":0,"Price":0,"LimitPrice":0,"OrderType":0}},"Hidden":false,"Iceberg":false,"TrackingMode":0,"TrackingValue":0}`)
|
|
assert.Equal(t, exp, j)
|
|
}
|