mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 23:16:45 +00:00
* orderbook/buffer: data integrity and resubscription pass * btcmarkets: REMOVE THAT LIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIINE!!!!!!!!!!!!!!!!! * buffer: reinstate publish, refaactor, invalidate more and comments * buffer/orderbook: improve update and snapshot performance. Move Update type to orderbook package to util. pointer through entire function calls. (cleanup). Change action string to uint8 for easier comparison. Add parsing helper. Update current test benchmark comments. * dispatch: change publish func to variadic id param * dispatch: remove sender receiver wait time as this adds overhead and complexity. update tests. * dispatch: don't create pointers for every job container * rpcserver: fix assertion issues with data publishing change * linter: fixes * glorious: nits addr * depth: change validation handling to incorporate and store err * linter: fix more issues * dispatch: fix race * travis: update before fetching * depth: wrap and return wrapped error in invalidate call and fix tests * btcmarkets: fix commenting * workflow: check * workflow: check * orderbook: check error * buffer/depth: return invalidation error and fix tests * gctcli: display errors on orderbook streams * buffer: remove unused types * orderbook/bitmex: shift function to bitmex * orderbook: Add specific comments to unexported functions that don't have locking require locking. * orderbook: restrict published data functionality to orderbook.Outbound interface * common: add assertion failure helper for error * dispatch: remove atomics, add mutex protection, remove add/remove worker, redo main tests * dispatch: export function * engine: revert and change sub logger to manager * engine: remove old test * dispatch: add common variable ;) * btcmarket: don't overflow int in tests on 32bit systems * ci: force 1.17.7 usage for go * Revert "ci: force 1.17.7 usage for go" This reverts commit af2f95563bf218cf2b9f36a9fcf3258e2c6a2d91. * golangci: bump version add and remove linter items * Revert "golangci: bump version add and remove linter items" This reverts commit 3c98bffc9d030e39faca0387ea40c151df2ab06b. * dispatch: remove unsused mutex from mux * order: slight optimizations * nits: glorious * dispatch: fix regression on uuid generation and input inline with master * linter: fix * linter: fix * glorious: nit - rm slice segration * account: fix test after merge * coinbasepro: revert change * account: close channel instead of needing a receiver, push alert in routine to prepare for waiter. Co-authored-by: Ryan O'Hara-Reid <ryan.oharareid@thrasher.io>
1502 lines
38 KiB
Go
1502 lines
38 KiB
Go
package order
|
|
|
|
import (
|
|
"errors"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gofrs/uuid"
|
|
"github.com/thrasher-corp/gocryptotrader/currency"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/validate"
|
|
)
|
|
|
|
var errValidationCheckFailed = errors.New("validation check failed")
|
|
|
|
func TestValidate(t *testing.T) {
|
|
testPair := currency.NewPair(currency.BTC, currency.LTC)
|
|
tester := []struct {
|
|
ExpectedErr error
|
|
Submit *Submit
|
|
ValidOpts validate.Checker
|
|
}{
|
|
{
|
|
ExpectedErr: ErrSubmissionIsNil,
|
|
Submit: nil,
|
|
}, // nil struct
|
|
{
|
|
ExpectedErr: ErrPairIsEmpty,
|
|
Submit: &Submit{},
|
|
}, // empty pair
|
|
{
|
|
|
|
ExpectedErr: ErrAssetNotSet,
|
|
Submit: &Submit{Pair: testPair},
|
|
}, // valid pair but invalid asset
|
|
{
|
|
|
|
ExpectedErr: ErrSideIsInvalid,
|
|
Submit: &Submit{Pair: testPair, AssetType: asset.Spot},
|
|
}, // valid pair but invalid order side
|
|
{
|
|
ExpectedErr: errTimeInForceConflict,
|
|
Submit: &Submit{
|
|
Pair: testPair,
|
|
AssetType: asset.Spot,
|
|
Side: Ask,
|
|
Type: Market,
|
|
ImmediateOrCancel: true,
|
|
FillOrKill: true,
|
|
},
|
|
},
|
|
{
|
|
ExpectedErr: ErrTypeIsInvalid,
|
|
Submit: &Submit{Pair: testPair,
|
|
Side: Buy,
|
|
AssetType: asset.Spot},
|
|
}, // valid pair and order side but invalid order type
|
|
{
|
|
ExpectedErr: ErrTypeIsInvalid,
|
|
Submit: &Submit{Pair: testPair,
|
|
Side: Sell,
|
|
AssetType: asset.Spot},
|
|
}, // valid pair and order side but invalid order type
|
|
{
|
|
ExpectedErr: ErrTypeIsInvalid,
|
|
Submit: &Submit{Pair: testPair,
|
|
Side: Bid,
|
|
AssetType: asset.Spot},
|
|
}, // valid pair and order side but invalid order type
|
|
{
|
|
ExpectedErr: ErrTypeIsInvalid,
|
|
Submit: &Submit{Pair: testPair,
|
|
Side: Ask,
|
|
AssetType: asset.Spot},
|
|
}, // valid pair and order side but invalid order type
|
|
{
|
|
ExpectedErr: ErrAmountIsInvalid,
|
|
Submit: &Submit{Pair: testPair,
|
|
Side: Ask,
|
|
Type: Market,
|
|
AssetType: asset.Spot},
|
|
}, // valid pair, order side, type but invalid amount
|
|
{
|
|
ExpectedErr: ErrPriceMustBeSetIfLimitOrder,
|
|
Submit: &Submit{Pair: testPair,
|
|
Side: Ask,
|
|
Type: Limit,
|
|
Amount: 1,
|
|
AssetType: asset.Spot},
|
|
}, // valid pair, order side, type, amount but invalid price
|
|
{
|
|
ExpectedErr: errValidationCheckFailed,
|
|
Submit: &Submit{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{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.Errorf("Unexpected result. Got: %v, want: %v", err, tester[x].ExpectedErr)
|
|
}
|
|
}
|
|
}
|
|
|
|
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 ot Type = "Mo'Money"
|
|
|
|
if ot.String() != "Mo'Money" {
|
|
t.Errorf("unexpected string %s", ot.String())
|
|
}
|
|
|
|
if ot.Lower() != "mo'money" {
|
|
t.Errorf("unexpected string %s", ot.Lower())
|
|
}
|
|
|
|
if ot.Title() != "Mo'Money" {
|
|
t.Errorf("unexpected string %s", ot.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,
|
|
},
|
|
}
|
|
|
|
FilterOrdersByType(&orders, AnyType)
|
|
if len(orders) != 2 {
|
|
t.Errorf("Orders failed to be filtered. Expected %v, received %v", 2, len(orders))
|
|
}
|
|
|
|
FilterOrdersByType(&orders, Limit)
|
|
if len(orders) != 1 {
|
|
t.Errorf("Orders failed to be filtered. Expected %v, received %v", 1, len(orders))
|
|
}
|
|
|
|
FilterOrdersByType(&orders, Stop)
|
|
if len(orders) != 0 {
|
|
t.Errorf("Orders failed to be filtered. Expected %v, received %v", 0, len(orders))
|
|
}
|
|
}
|
|
|
|
func TestFilterOrdersBySide(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var orders = []Detail{
|
|
{
|
|
Side: Buy,
|
|
},
|
|
{
|
|
Side: Sell,
|
|
},
|
|
{},
|
|
}
|
|
|
|
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) != 1 {
|
|
t.Errorf("Orders failed to be filtered. Expected %v, received %v", 1, len(orders))
|
|
}
|
|
|
|
FilterOrdersBySide(&orders, Sell)
|
|
if len(orders) != 0 {
|
|
t.Errorf("Orders failed to be filtered. Expected %v, received %v", 0, len(orders))
|
|
}
|
|
}
|
|
|
|
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),
|
|
},
|
|
}
|
|
|
|
FilterOrdersByTimeRange(&orders, time.Unix(0, 0), time.Unix(0, 0))
|
|
if len(orders) != 3 {
|
|
t.Errorf("Orders failed to be filtered. Expected %v, received %v", 3, len(orders))
|
|
}
|
|
|
|
FilterOrdersByTimeRange(&orders, time.Unix(100, 0), time.Unix(111, 0))
|
|
if len(orders) != 3 {
|
|
t.Errorf("Orders failed to be filtered. Expected %v, received %v", 3, len(orders))
|
|
}
|
|
|
|
FilterOrdersByTimeRange(&orders, time.Unix(101, 0), time.Unix(111, 0))
|
|
if len(orders) != 2 {
|
|
t.Errorf("Orders failed to be filtered. Expected %v, received %v", 2, len(orders))
|
|
}
|
|
|
|
FilterOrdersByTimeRange(&orders, time.Unix(200, 0), time.Unix(300, 0))
|
|
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
|
|
FilterOrdersByTimeRange(&orders, time.Unix(200, 0), time.Unix(300, 0))
|
|
if len(orders) != 1 {
|
|
t.Errorf("Orders failed to be filtered. Expected %v, received %v", 1, len(orders))
|
|
}
|
|
}
|
|
|
|
func TestFilterOrdersByCurrencies(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),
|
|
},
|
|
}
|
|
|
|
currencies := []currency.Pair{currency.NewPair(currency.BTC, currency.USD),
|
|
currency.NewPair(currency.LTC, currency.EUR),
|
|
currency.NewPair(currency.DOGE, currency.RUB)}
|
|
FilterOrdersByCurrencies(&orders, currencies)
|
|
if len(orders) != 3 {
|
|
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)}
|
|
FilterOrdersByCurrencies(&orders, currencies)
|
|
if len(orders) != 2 {
|
|
t.Errorf("Orders failed to be filtered. Expected %v, received %v", 2, len(orders))
|
|
}
|
|
|
|
currencies = []currency.Pair{currency.NewPair(currency.BTC, currency.USD)}
|
|
FilterOrdersByCurrencies(&orders, currencies)
|
|
if len(orders) != 1 {
|
|
t.Errorf("Orders failed to be filtered. Expected %v, received %v", 1, len(orders))
|
|
}
|
|
|
|
currencies = []currency.Pair{currency.NewPair(currency.USD, currency.BTC)}
|
|
FilterOrdersByCurrencies(&orders, currencies)
|
|
if len(orders) != 1 {
|
|
t.Errorf("Reverse Orders failed to be filtered. Expected %v, received %v", 1, len(orders))
|
|
}
|
|
|
|
currencies = []currency.Pair{}
|
|
FilterOrdersByCurrencies(&orders, currencies)
|
|
if len(orders) != 1 {
|
|
t.Errorf("Orders failed to be filtered. Expected %v, received %v", 1, len(orders))
|
|
}
|
|
currencies = append(currencies, currency.EMPTYPAIR)
|
|
FilterOrdersByCurrencies(&orders, currencies)
|
|
if len(orders) != 1 {
|
|
t.Errorf("Orders failed to be filtered. Expected %v, received %v", 1, len(orders))
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
var stringsToOrderSide = []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", Buy, errors.New("WOAHMAN not recognised as order side")},
|
|
}
|
|
|
|
func TestStringToOrderSide(t *testing.T) {
|
|
for i := range stringsToOrderSide {
|
|
testData := &stringsToOrderSide[i]
|
|
t.Run(testData.in, func(t *testing.T) {
|
|
out, err := StringToOrderSide(testData.in)
|
|
if err != nil {
|
|
if err.Error() != testData.err.Error() {
|
|
t.Error("Unexpected error", err)
|
|
}
|
|
} else 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")
|
|
}
|
|
}
|
|
|
|
var stringsToOrderType = []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, errors.New("WOAHMAN not recognised as order type")},
|
|
}
|
|
|
|
func TestStringToOrderType(t *testing.T) {
|
|
for i := range stringsToOrderType {
|
|
testData := &stringsToOrderType[i]
|
|
t.Run(testData.in, func(t *testing.T) {
|
|
out, err := StringToOrderType(testData.in)
|
|
if err != nil {
|
|
if err.Error() != testData.err.Error() {
|
|
t.Error("Unexpected error", err)
|
|
}
|
|
} else 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},
|
|
{"woahMan", UnknownStatus, errors.New("WOAHMAN not recognised as order status")},
|
|
}
|
|
|
|
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 err != nil {
|
|
if err.Error() != testData.err.Error() {
|
|
t.Error("Unexpected error", err)
|
|
}
|
|
} else 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 TestUpdateOrderFromModify(t *testing.T) {
|
|
var leet = "1337"
|
|
od := Detail{
|
|
ImmediateOrCancel: false,
|
|
HiddenOrder: false,
|
|
FillOrKill: false,
|
|
PostOnly: false,
|
|
Leverage: 0,
|
|
Price: 0,
|
|
Amount: 0,
|
|
LimitPriceUpper: 0,
|
|
LimitPriceLower: 0,
|
|
TriggerPrice: 0,
|
|
QuoteAmount: 0,
|
|
ExecutedAmount: 0,
|
|
RemainingAmount: 0,
|
|
Fee: 0,
|
|
Exchange: "",
|
|
ID: "1",
|
|
AccountID: "",
|
|
ClientID: "",
|
|
WalletAddress: "",
|
|
Type: "",
|
|
Side: "",
|
|
Status: "",
|
|
Date: time.Time{},
|
|
LastUpdated: time.Time{},
|
|
Pair: currency.EMPTYPAIR,
|
|
Trades: nil,
|
|
}
|
|
updated := time.Now()
|
|
|
|
pair, err := currency.NewPairFromString("BTCUSD")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
om := Modify{
|
|
ImmediateOrCancel: true,
|
|
HiddenOrder: true,
|
|
FillOrKill: true,
|
|
PostOnly: true,
|
|
Leverage: 1.0,
|
|
Price: 1,
|
|
Amount: 1,
|
|
LimitPriceUpper: 1,
|
|
LimitPriceLower: 1,
|
|
TriggerPrice: 1,
|
|
QuoteAmount: 1,
|
|
ExecutedAmount: 1,
|
|
RemainingAmount: 1,
|
|
Fee: 1,
|
|
Exchange: "1",
|
|
InternalOrderID: "1",
|
|
ID: "1",
|
|
AccountID: "1",
|
|
ClientID: "1",
|
|
WalletAddress: "1",
|
|
Type: "1",
|
|
Side: "1",
|
|
Status: "1",
|
|
AssetType: 1,
|
|
LastUpdated: updated,
|
|
Pair: pair,
|
|
Trades: []TradeHistory{},
|
|
}
|
|
|
|
od.UpdateOrderFromModify(&om)
|
|
if od.InternalOrderID == "1" {
|
|
t.Error("Should not be able to update 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 != "" {
|
|
t.Error("Should not be able to update exchange via modify")
|
|
}
|
|
if od.ID != "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"})
|
|
od.UpdateOrderFromModify(&om)
|
|
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
|
|
od.UpdateOrderFromModify(&om)
|
|
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")
|
|
}
|
|
}
|
|
|
|
func TestUpdateOrderFromDetail(t *testing.T) {
|
|
var leet = "1337"
|
|
od := Detail{
|
|
ImmediateOrCancel: false,
|
|
HiddenOrder: false,
|
|
FillOrKill: false,
|
|
PostOnly: false,
|
|
Leverage: 0,
|
|
Price: 0,
|
|
Amount: 0,
|
|
LimitPriceUpper: 0,
|
|
LimitPriceLower: 0,
|
|
TriggerPrice: 0,
|
|
QuoteAmount: 0,
|
|
ExecutedAmount: 0,
|
|
RemainingAmount: 0,
|
|
Fee: 0,
|
|
Exchange: "test",
|
|
ID: "",
|
|
AccountID: "",
|
|
ClientID: "",
|
|
WalletAddress: "",
|
|
Type: "",
|
|
Side: "",
|
|
Status: "",
|
|
Date: time.Time{},
|
|
LastUpdated: time.Time{},
|
|
Pair: currency.EMPTYPAIR,
|
|
Trades: nil,
|
|
}
|
|
updated := time.Now()
|
|
|
|
pair, err := currency.NewPairFromString("BTCUSD")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
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: "1",
|
|
ID: "1",
|
|
AccountID: "1",
|
|
ClientID: "1",
|
|
WalletAddress: "1",
|
|
Type: "1",
|
|
Side: "1",
|
|
Status: "1",
|
|
AssetType: 1,
|
|
LastUpdated: updated,
|
|
Pair: pair,
|
|
Trades: []TradeHistory{},
|
|
}
|
|
|
|
od.UpdateOrderFromDetail(&om)
|
|
if od.InternalOrderID != "1" {
|
|
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.ID != "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"})
|
|
od.UpdateOrderFromDetail(&om)
|
|
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
|
|
od.UpdateOrderFromDetail(&om)
|
|
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")
|
|
}
|
|
|
|
om = Detail{
|
|
InternalOrderID: "2",
|
|
}
|
|
|
|
od.UpdateOrderFromDetail(&om)
|
|
if od.InternalOrderID == "2" {
|
|
t.Error("Should not be able to update the internal order ID after initialization")
|
|
}
|
|
}
|
|
|
|
func TestClassificationError_Error(t *testing.T) {
|
|
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.ID = "1337"
|
|
if cancelMe.Validate(cancelMe.StandardCancel()) != nil {
|
|
t.Fatal("should return nil")
|
|
}
|
|
|
|
var getOrders *GetOrdersRequest
|
|
if getOrders.Validate() != ErrGetOrdersRequestIsNil {
|
|
t.Fatal("unexpected error")
|
|
}
|
|
|
|
getOrders = new(GetOrdersRequest)
|
|
if getOrders.Validate() == nil {
|
|
t.Fatal("should error since assetType hasn't been provided")
|
|
}
|
|
|
|
if getOrders.Validate(validate.Check(func() error {
|
|
return errors.New("this should error")
|
|
})) == nil {
|
|
t.Fatal("expected error")
|
|
}
|
|
|
|
if getOrders.Validate(validate.Check(func() error {
|
|
return nil
|
|
})) == nil {
|
|
t.Fatal("should output an error since assetType isn't provided")
|
|
}
|
|
|
|
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) {
|
|
filters := map[int]Filter{
|
|
0: {},
|
|
1: {Exchange: "Binance"},
|
|
2: {InternalOrderID: "1234"},
|
|
3: {ID: "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: "1234"},
|
|
3: {ID: "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
|
|
expRes 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.expRes {
|
|
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
|
|
expRes bool
|
|
}{
|
|
0: {orders[0], false},
|
|
1: {orders[1], true},
|
|
2: {orders[2], false},
|
|
3: {orders[3], false},
|
|
}
|
|
// specific tests
|
|
for num, tt := range amountTests {
|
|
if tt.o.IsActive() != tt.expRes {
|
|
t.Errorf("amountTests[%v] failed", num)
|
|
}
|
|
}
|
|
|
|
statusTests := map[int]struct {
|
|
o Detail
|
|
expRes bool
|
|
}{
|
|
0: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: AnyStatus}, true},
|
|
1: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: New}, true},
|
|
2: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: Active}, true},
|
|
3: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: PartiallyCancelled}, false},
|
|
4: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: PartiallyFilled}, true},
|
|
5: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: Filled}, false},
|
|
6: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: Cancelled}, false},
|
|
7: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: PendingCancel}, true},
|
|
8: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: InsufficientBalance}, false},
|
|
9: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: MarketUnavailable}, false},
|
|
10: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: Rejected}, false},
|
|
11: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: Expired}, false},
|
|
12: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: Hidden}, true},
|
|
13: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: UnknownStatus}, true},
|
|
14: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: Open}, true},
|
|
15: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: AutoDeleverage}, true},
|
|
16: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: Closed}, false},
|
|
17: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: Pending}, true},
|
|
}
|
|
// specific tests
|
|
for num, tt := range statusTests {
|
|
if tt.o.IsActive() != tt.expRes {
|
|
t.Errorf("statusTests[%v] failed", num)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestIsInctive(t *testing.T) {
|
|
orders := map[int]Detail{
|
|
0: {Amount: 0.0, Status: Active},
|
|
1: {Amount: 1.0, ExecutedAmount: 0.9, Status: Active},
|
|
2: {Amount: 1.0, ExecutedAmount: 1.0, Status: Active},
|
|
3: {Amount: 1.0, ExecutedAmount: 1.1, Status: Active},
|
|
}
|
|
|
|
amountTests := map[int]struct {
|
|
o Detail
|
|
expRes bool
|
|
}{
|
|
0: {orders[0], true},
|
|
1: {orders[1], false},
|
|
2: {orders[2], true},
|
|
3: {orders[3], true},
|
|
}
|
|
// specific tests
|
|
for num, tt := range amountTests {
|
|
if tt.o.IsInactive() != tt.expRes {
|
|
t.Errorf("amountTests[%v] failed", num)
|
|
}
|
|
}
|
|
|
|
statusTests := map[int]struct {
|
|
o Detail
|
|
expRes bool
|
|
}{
|
|
0: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: AnyStatus}, false},
|
|
1: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: New}, false},
|
|
2: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: Active}, false},
|
|
3: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: PartiallyCancelled}, true},
|
|
4: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: PartiallyFilled}, false},
|
|
5: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: Filled}, true},
|
|
6: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: Cancelled}, true},
|
|
7: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: PendingCancel}, false},
|
|
8: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: InsufficientBalance}, true},
|
|
9: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: MarketUnavailable}, true},
|
|
10: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: Rejected}, true},
|
|
11: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: Expired}, true},
|
|
12: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: Hidden}, false},
|
|
13: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: UnknownStatus}, false},
|
|
14: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: Open}, false},
|
|
15: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: AutoDeleverage}, false},
|
|
16: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: Closed}, true},
|
|
17: {Detail{Amount: 1.0, ExecutedAmount: 0.0, Status: Pending}, false},
|
|
}
|
|
// specific tests
|
|
for num, tt := range statusTests {
|
|
if tt.o.IsInactive() != tt.expRes {
|
|
t.Errorf("statusTests[%v] failed", num)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGenerateInternalOrderID(t *testing.T) {
|
|
id, err := uuid.NewV4()
|
|
if err != nil {
|
|
t.Errorf("unable to create uuid: %s", err)
|
|
}
|
|
od := Detail{
|
|
InternalOrderID: id.String(),
|
|
}
|
|
od.GenerateInternalOrderID()
|
|
if od.InternalOrderID != id.String() {
|
|
t.Error("Should not be able to generate a new internal order ID")
|
|
}
|
|
|
|
od = Detail{}
|
|
od.GenerateInternalOrderID()
|
|
if od.InternalOrderID == "" {
|
|
t.Error("unable to generate internal order ID")
|
|
}
|
|
}
|
|
|
|
func TestDetail_Copy(t *testing.T) {
|
|
d := []Detail{
|
|
{
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
}
|