Files
gocryptotrader/exchanges/order/order_test.go
Scott 80b2136ce9 GCT: fix spelling (#1164)
* kodespull

* fix my spelling

* silly sausage, rm sneaky pointer
2023-04-02 10:21:13 +10:00

1962 lines
51 KiB
Go

package order
import (
"errors"
"fmt"
"reflect"
"strconv"
"strings"
"testing"
"time"
"github.com/gofrs/uuid"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/validate"
)
var errValidationCheckFailed = errors.New("validation check failed")
func TestSubmit_Validate(t *testing.T) {
t.Parallel()
testPair := currency.NewPair(currency.BTC, currency.LTC)
tester := []struct {
ExpectedErr error
Submit *Submit
ValidOpts validate.Checker
}{
{
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: 255,
},
}, // 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!
}
for x := range tester {
err := tester[x].Submit.Validate(tester[x].ValidOpts)
if !errors.Is(err, tester[x].ExpectedErr) {
t.Fatalf("Unexpected result. %d Got: %v, want: %v", x+1, err, tester[x].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()
var 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 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()
var 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 x := 0; x < b.N; x++ {
FilterOrdersByType(filterOrdersByTypeBenchmark, Limit)
}
}
func TestFilterOrdersBySide(t *testing.T) {
t.Parallel()
var 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 x := 0; x < b.N; x++ {
FilterOrdersBySide(filterOrdersBySideBenchmark, Ask)
}
}
func TestFilterOrdersByTimeRange(t *testing.T) {
t.Parallel()
var 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 x := 0; x < b.N; x++ {
err := FilterOrdersByTimeRange(filterOrdersByTimeRangeBenchmark, time.Unix(50, 0), time.Unix(150, 0))
if err != nil {
b.Fatal(err)
}
}
}
func TestFilterOrdersByPairs(t *testing.T) {
t.Parallel()
var 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 x := 0; x < b.N; x++ {
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, errUnrecognisedOrderSide},
}
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 x := 0; x < b.N; x++ {
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},
{"woahMan", UnknownType, errUnrecognisedOrderType},
}
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 x := 0; x < b.N; x++ {
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 x := 0; x < b.N; x++ {
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) {
var 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",
WalletAddress: "1",
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.WalletAddress != "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")
}
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() != "test - OrderID: 1337 classification error: test error" {
t.Fatal("unexpected output")
}
class.OrderID = ""
if class.Error() != "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 *GetOrdersRequest
err = getOrders.Validate()
if !errors.Is(err, ErrGetOrdersRequestIsNil) {
t.Fatalf("received: '%v' but expected: '%v'", err, ErrGetOrdersRequestIsNil)
}
getOrders = new(GetOrdersRequest)
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, errUnrecognisedOrderSide) {
t.Fatalf("received: '%v' but expected: '%v'", err, errUnrecognisedOrderSide)
}
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
}))
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, err := uuid.NewV4()
if err != nil {
t.Fatal(err)
}
filters := map[int]Filter{
0: {},
1: {Exchange: "Binance"},
2: {InternalOrderID: id},
3: {OrderID: "2222"},
4: {ClientOrderID: "3333"},
5: {ClientID: "4444"},
6: {WalletAddress: "5555"},
7: {Type: AnyType},
8: {Type: Limit},
9: {Side: AnySide},
10: {Side: Sell},
11: {Status: AnyStatus},
12: {Status: New},
13: {AssetType: asset.Spot},
14: {Pair: currency.NewPair(currency.BTC, currency.USD)},
15: {Exchange: "Binance", Type: Limit, Status: New},
16: {Exchange: "Binance", Type: AnyType},
17: {AccountID: "8888"},
}
orders := map[int]Detail{
0: {},
1: {Exchange: "Binance"},
2: {InternalOrderID: id},
3: {OrderID: "2222"},
4: {ClientOrderID: "3333"},
5: {ClientID: "4444"},
6: {WalletAddress: "5555"},
7: {Type: AnyType},
8: {Type: Limit},
9: {Side: AnySide},
10: {Side: Sell},
11: {Status: AnyStatus},
12: {Status: New},
13: {AssetType: asset.Spot},
14: {Pair: currency.NewPair(currency.BTC, currency.USD)},
15: {Exchange: "Binance", Type: Limit, Status: New},
16: {AccountID: "8888"},
}
// empty filter tests
emptyFilter := filters[0]
for _, o := range orders {
if !o.MatchFilter(&emptyFilter) {
t.Error("empty filter should match everything")
}
}
tests := map[int]struct {
f Filter
o Detail
expectedResult bool
}{
0: {filters[1], orders[1], true},
1: {filters[1], orders[0], false},
2: {filters[2], orders[2], true},
3: {filters[2], orders[3], false},
4: {filters[3], orders[3], true},
5: {filters[3], orders[4], false},
6: {filters[4], orders[4], true},
7: {filters[4], orders[5], false},
8: {filters[5], orders[5], true},
9: {filters[5], orders[6], false},
10: {filters[6], orders[6], true},
11: {filters[6], orders[7], false},
12: {filters[7], orders[7], true},
13: {filters[7], orders[8], true},
14: {filters[7], orders[9], true},
15: {filters[8], orders[7], false},
16: {filters[8], orders[8], true},
17: {filters[8], orders[9], false},
18: {filters[9], orders[9], true},
19: {filters[9], orders[10], true},
20: {filters[9], orders[11], true},
21: {filters[10], orders[10], true},
22: {filters[10], orders[11], false},
23: {filters[10], orders[9], false},
24: {filters[11], orders[11], true},
25: {filters[11], orders[12], true},
26: {filters[11], orders[10], true},
27: {filters[12], orders[12], true},
28: {filters[12], orders[13], false},
29: {filters[12], orders[11], false},
30: {filters[13], orders[13], true},
31: {filters[13], orders[12], false},
32: {filters[14], orders[14], true},
33: {filters[14], orders[13], false},
34: {filters[15], orders[15], true},
35: {filters[16], orders[15], true},
36: {filters[17], orders[16], true},
37: {filters[17], orders[15], false},
}
// specific tests
for num, tt := range tests {
if tt.o.MatchFilter(&tt.f) != tt.expectedResult {
t.Errorf("tests[%v] failed", num)
}
}
}
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 x := 0; x < b.N; x++ {
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 x := 0; x < b.N; x++ {
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 {
num := num
tt := tt
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
if _, err := o.DeriveModify(); !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: "wow2",
ClientOrderID: "wow3",
Type: Market,
Side: Long,
AssetType: asset.Futures,
Pair: pair,
}
mod, err := o.DeriveModify()
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
}
if mod == nil {
t.Fatal("should not be nil")
}
if mod.Exchange != "wow" ||
mod.OrderID != "wow2" ||
mod.ClientOrderID != "wow3" ||
mod.Type != Market ||
mod.Side != Long ||
mod.AssetType != asset.Futures ||
!mod.Pair.Equal(pair) {
t.Fatal("unexpected values")
}
}
func TestDeriveModifyResponse(t *testing.T) {
t.Parallel()
var mod *Modify
if _, err := mod.DeriveModifyResponse(); !errors.Is(err, errOrderDetailIsNil) {
t.Fatalf("received: '%v' but expected: '%v'", 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()
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
}
if modresp == nil {
t.Fatal("should not be nil")
}
if modresp.Exchange != "wow" ||
modresp.OrderID != "wow2" ||
modresp.ClientOrderID != "wow3" ||
modresp.Type != Market ||
modresp.Side != Long ||
modresp.AssetType != asset.Futures ||
!modresp.Pair.Equal(pair) {
t.Fatal("unexpected values")
}
}
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",
WalletAddress: "wow5",
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.WalletAddress != "wow5" ||
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(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)
}
}
}
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")
}
}