mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 23:16:45 +00:00
* Config: Move assetEnabled upgrade to Version management * Assets: Do not error on asset not enabled, or disabled This became more messy with Disabling something that's defaulted to disabled. Taking an idealogical stance against erroring that what you want to have done is already done. * CurrencyManager: Set AssetEnabled when StorePairs(enabled) * RPCServer: Fix tests expecting StoreAssetPairFormat to enable the asset Also assertifies * Bitfinex: Fix tests for MarginFunding subs * GCTWrapper: Improve TestMain clarity * BTSE: Add futures to testconfig * Exchanges: Rename StoreAssetPairStore Previously we were calling it "Format", but accepting everything from the PairStore. We were also defaulting to turning the Asset on. Now callers need to get their AssetEnabled set as they want it, so there's no magic This change also moves responsibility for error wrapping outside to the caller. * Config: AssetEnabled upgrade should respect assetTypes Previously we ignored the field and just turned on everything. I think that was because we couldn't get at the old value. In either case, we have the option to do better, and respect the assetEnabled value * Config: Improve exchange config version upgrade error messages
2206 lines
58 KiB
Go
2206 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()
|
|
|
|
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 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()
|
|
|
|
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 b.Loop() {
|
|
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 b.Loop() {
|
|
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 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()
|
|
|
|
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 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) {
|
|
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",
|
|
ClientOrderID: "DukeOfWombleton",
|
|
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.ClientOrderID != "DukeOfWombleton" {
|
|
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() != "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)
|
|
}
|
|
|
|
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 {
|
|
t.Run(strconv.Itoa(num), func(t *testing.T) {
|
|
t.Parallel()
|
|
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 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",
|
|
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(MultiOrderRequest)
|
|
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")
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|