exchanges/order: Add TimeInForce type (#1382)

* Added TimeInForce type and updated related files

* Linter issue fix and minor coinbasepro type update

* Bitrex consts update

* added unit test and minor changes in bittrex

* Unit tests update

* Fix minor linter issues

* Update TestStringToTimeInForce unit test

* fix conflict with gateio timeInForce

* Update order tests

* Complete updating the order unit tests

* update kucoin and deribit wrapper to match the time in force change

* fix time-in-force related test errors

* linter issue fix

* time in force constants, functions and unit tests update

* shift tif policies to TimeInForce

* Update time-in-force, related functions, and unit tests

* fix linter issue and time-in-force processing

* added a good till crossing tif value

* order type fix and fix related tim-in-force entries

* update time-in-force unmarshaling and unit test

* fix time-in-force error in gateio

* linter issue fix

* update based on review comments

* add unit test and fix missing issues

* minor fix and added benchmark unit test

* change GTT to GTC for limit

* fix linter issue

* added time-in-force value to place order param

* fix minor issues based on review comment and move tif code to separate files

* update on exchanges linked to time-in-force

* resolve missing review comments

* minor linter issues fix

* added time-in-force handler and update timeInForce parametered endpoint

* minor fixes based on review

* nits fix

* update based on review

* linter fix

* rm getTimeInForce func and minor change to time-in-force

* minor change

* update based on review comments

* wrappers and time-in-force calling approach

* minor change

* update gateio string to timeInForce conversion and unit test

* updated order test unit tes functions

* minor fixes on unit tests

* nits fix based on feedback

* update TestDeriveCancel unit test assert messages

* update TestDeriveCancel unit test assert messages

* update timeInForceFromString method to return formatted error and update functions using it

* restructure and fix minor exchanges time-in-force handling issues

* replaced unused getTypeFromTimeInForce with inline switch-based order type check

* separated the repeated timeInForce conversion code to  a function

* update exchanges time-in-force handling based on review comments

* limter fix

* edded comment to validTimesInForce var

* added comment to gateio's timeInForceString func

* added goodTillCancel switch case to gateio timeInForceString func
This commit is contained in:
Samuael A.
2025-05-23 02:07:09 +03:00
committed by GitHub
parent 8c678063b5
commit 640960aec1
46 changed files with 1201 additions and 1424 deletions

View File

@@ -1,10 +1,11 @@
package order
import (
"errors"
"testing"
"github.com/shopspring/decimal"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
)
@@ -19,9 +20,7 @@ func TestLoadLimits(t *testing.T) {
t.Parallel()
e := ExecutionLimits{}
err := e.LoadLimits(nil)
if !errors.Is(err, errCannotLoadLimit) {
t.Fatalf("expected error %v but received %v", errCannotLoadLimit, err)
}
assert.ErrorIs(t, err, errCannotLoadLimit)
invalidAsset := []MinMaxLevel{
{
@@ -33,11 +32,7 @@ func TestLoadLimits(t *testing.T) {
},
}
err = e.LoadLimits(invalidAsset)
if !errors.Is(err, asset.ErrNotSupported) {
t.Fatalf("expected error %v but received %v",
asset.ErrNotSupported,
err)
}
require.ErrorIs(t, err, asset.ErrNotSupported)
invalidPairLoading := []MinMaxLevel{
{
@@ -50,9 +45,7 @@ func TestLoadLimits(t *testing.T) {
}
err = e.LoadLimits(invalidPairLoading)
if !errors.Is(err, currency.ErrCurrencyPairEmpty) {
t.Fatalf("expected error %v but received %v", currency.ErrCurrencyPairEmpty, err)
}
assert.ErrorIs(t, err, currency.ErrCurrencyPairEmpty)
newLimits := []MinMaxLevel{
{
@@ -66,9 +59,7 @@ func TestLoadLimits(t *testing.T) {
}
err = e.LoadLimits(newLimits)
if !errors.Is(err, nil) {
t.Fatalf("expected error %v but received %v", nil, err)
}
require.NoError(t, err)
badLimit := []MinMaxLevel{
{
@@ -82,9 +73,7 @@ func TestLoadLimits(t *testing.T) {
}
err = e.LoadLimits(badLimit)
if !errors.Is(err, errInvalidPriceLevels) {
t.Fatalf("expected error %v but received %v", errInvalidPriceLevels, err)
}
require.ErrorIs(t, err, errInvalidPriceLevels)
badLimit = []MinMaxLevel{
{
@@ -98,9 +87,7 @@ func TestLoadLimits(t *testing.T) {
}
err = e.LoadLimits(badLimit)
if !errors.Is(err, errInvalidAmountLevels) {
t.Fatalf("expected error %v but received %v", errInvalidPriceLevels, err)
}
require.ErrorIs(t, err, errInvalidAmountLevels)
goodLimit := []MinMaxLevel{
{
@@ -110,9 +97,7 @@ func TestLoadLimits(t *testing.T) {
}
err = e.LoadLimits(goodLimit)
if !errors.Is(err, nil) {
t.Fatalf("expected error %v but received %v", nil, err)
}
require.NoError(t, err)
noCompare := []MinMaxLevel{
{
@@ -123,9 +108,7 @@ func TestLoadLimits(t *testing.T) {
}
err = e.LoadLimits(noCompare)
if !errors.Is(err, nil) {
t.Fatalf("expected error %v but received %v", nil, err)
}
require.NoError(t, err)
noCompare = []MinMaxLevel{
{
@@ -136,18 +119,14 @@ func TestLoadLimits(t *testing.T) {
}
err = e.LoadLimits(noCompare)
if !errors.Is(err, nil) {
t.Fatalf("expected error %v but received %v", nil, err)
}
assert.NoError(t, err)
}
func TestGetOrderExecutionLimits(t *testing.T) {
t.Parallel()
e := ExecutionLimits{}
_, err := e.GetOrderExecutionLimits(asset.Spot, btcusd)
if !errors.Is(err, ErrExchangeLimitNotLoaded) {
t.Fatalf("expected error %v but received %v", ErrExchangeLimitNotLoaded, err)
}
require.ErrorIs(t, err, ErrExchangeLimitNotLoaded)
newLimits := []MinMaxLevel{
{
@@ -161,45 +140,30 @@ func TestGetOrderExecutionLimits(t *testing.T) {
}
err = e.LoadLimits(newLimits)
if !errors.Is(err, nil) {
t.Fatalf("expected error %v but received %v", errCannotLoadLimit, err)
}
require.NoError(t, err)
_, err = e.GetOrderExecutionLimits(asset.Futures, ltcusd)
if !errors.Is(err, ErrCannotValidateAsset) {
t.Fatalf("expected error %v but received %v", ErrCannotValidateAsset, err)
}
require.ErrorIs(t, err, ErrCannotValidateAsset)
_, err = e.GetOrderExecutionLimits(asset.Spot, ltcusd)
if !errors.Is(err, errExchangeLimitBase) {
t.Fatalf("expected error %v but received %v", errExchangeLimitBase, err)
}
require.ErrorIs(t, err, errExchangeLimitBase)
_, err = e.GetOrderExecutionLimits(asset.Spot, btcltc)
if !errors.Is(err, errExchangeLimitQuote) {
t.Fatalf("expected error %v but received %v", errExchangeLimitQuote, err)
}
require.ErrorIs(t, err, errExchangeLimitQuote)
tt, err := e.GetOrderExecutionLimits(asset.Spot, btcusd)
if !errors.Is(err, nil) {
t.Fatalf("expected error %v but received %v", nil, err)
}
if tt.MaximumBaseAmount != newLimits[0].MaximumBaseAmount ||
tt.MinimumBaseAmount != newLimits[0].MinimumBaseAmount ||
tt.MaxPrice != newLimits[0].MaxPrice ||
tt.MinPrice != newLimits[0].MinPrice {
t.Fatal("unexpected values")
}
require.NoError(t, err)
assert.Equal(t, newLimits[0].MaximumBaseAmount, tt.MaximumBaseAmount)
assert.Equal(t, newLimits[0].MinimumBaseAmount, tt.MinimumBaseAmount)
assert.Equal(t, newLimits[0].MaxPrice, tt.MaxPrice)
assert.Equal(t, newLimits[0].MinPrice, tt.MinPrice)
}
func TestCheckLimit(t *testing.T) {
t.Parallel()
e := ExecutionLimits{}
err := e.CheckOrderExecutionLimits(asset.Spot, btcusd, 1337, 1337, Limit)
if !errors.Is(err, nil) {
t.Fatalf("expected error %v but received %v", nil, err)
}
require.NoError(t, err)
newLimits := []MinMaxLevel{
{
@@ -213,97 +177,63 @@ func TestCheckLimit(t *testing.T) {
}
err = e.LoadLimits(newLimits)
if !errors.Is(err, nil) {
t.Fatalf("expected error %v but received %v", errCannotLoadLimit, err)
}
require.NoError(t, err)
err = e.CheckOrderExecutionLimits(asset.Futures, ltcusd, 1337, 1337, Limit)
if !errors.Is(err, ErrCannotValidateAsset) {
t.Fatalf("expected error %v but received %v", ErrCannotValidateAsset, err)
}
require.ErrorIs(t, err, ErrCannotValidateAsset)
err = e.CheckOrderExecutionLimits(asset.Spot, ltcusd, 1337, 1337, Limit)
if !errors.Is(err, ErrCannotValidateBaseCurrency) {
t.Fatalf("expected error %v but received %v", ErrCannotValidateBaseCurrency, err)
}
require.ErrorIs(t, err, ErrCannotValidateBaseCurrency)
err = e.CheckOrderExecutionLimits(asset.Spot, btcltc, 1337, 1337, Limit)
if !errors.Is(err, ErrCannotValidateQuoteCurrency) {
t.Fatalf("expected error %v but received %v", ErrCannotValidateQuoteCurrency, err)
}
require.ErrorIs(t, err, ErrCannotValidateQuoteCurrency)
err = e.CheckOrderExecutionLimits(asset.Spot, btcusd, 1337, 9, Limit)
if !errors.Is(err, ErrPriceBelowMin) {
t.Fatalf("expected error %v but received %v", ErrPriceBelowMin, err)
}
require.ErrorIs(t, err, ErrPriceBelowMin)
err = e.CheckOrderExecutionLimits(asset.Spot, btcusd, 1000001, 9, Limit)
if !errors.Is(err, ErrPriceExceedsMax) {
t.Fatalf("expected error %v but received %v", ErrPriceExceedsMax, err)
}
require.ErrorIs(t, err, ErrPriceExceedsMax)
err = e.CheckOrderExecutionLimits(asset.Spot, btcusd, 999999, .5, Limit)
if !errors.Is(err, ErrAmountBelowMin) {
t.Fatalf("expected error %v but received %v", ErrAmountBelowMin, err)
}
require.ErrorIs(t, err, ErrAmountBelowMin)
err = e.CheckOrderExecutionLimits(asset.Spot, btcusd, 999999, 11, Limit)
if !errors.Is(err, ErrAmountExceedsMax) {
t.Fatalf("expected error %v but received %v", ErrAmountExceedsMax, err)
}
require.ErrorIs(t, err, ErrAmountExceedsMax)
err = e.CheckOrderExecutionLimits(asset.Spot, btcusd, 999999, 7, Limit)
if !errors.Is(err, nil) {
t.Fatalf("expected error %v but received %v", nil, err)
}
require.NoError(t, err)
err = e.CheckOrderExecutionLimits(asset.Spot, btcusd, 999999, 7, Market)
if !errors.Is(err, nil) {
t.Fatalf("expected error %v but received %v", nil, err)
}
assert.NoError(t, err)
}
func TestConforms(t *testing.T) {
t.Parallel()
var tt MinMaxLevel
err := tt.Conforms(0, 0, Limit)
if err != nil {
t.Fatal(err)
}
require.NoError(t, err)
tt = MinMaxLevel{
MinNotional: 100,
}
err = tt.Conforms(1, 1, Limit)
if !errors.Is(err, ErrNotionalValue) {
t.Fatalf("expected error %v but received %v", ErrNotionalValue, err)
}
require.ErrorIs(t, err, ErrNotionalValue)
err = tt.Conforms(200, .5, Limit)
if !errors.Is(err, nil) {
t.Fatalf("expected error %v but received %v", nil, err)
}
require.NoError(t, err)
tt.PriceStepIncrementSize = 0.001
err = tt.Conforms(200.0001, .5, Limit)
if !errors.Is(err, ErrPriceExceedsStep) {
t.Fatalf("expected error %v but received %v", ErrPriceExceedsStep, err)
}
require.ErrorIs(t, err, ErrPriceExceedsStep)
err = tt.Conforms(200.004, .5, Limit)
if !errors.Is(err, nil) {
t.Fatalf("expected error %v but received %v", nil, err)
}
require.NoError(t, err)
tt.AmountStepIncrementSize = 0.001
err = tt.Conforms(200, .0002, Limit)
if !errors.Is(err, ErrAmountExceedsStep) {
t.Fatalf("expected error %v but received %v", ErrAmountExceedsStep, err)
}
require.ErrorIs(t, err, ErrAmountExceedsStep)
err = tt.Conforms(200000, .003, Limit)
if !errors.Is(err, nil) {
t.Fatalf("expected error %v but received %v", nil, err)
}
require.NoError(t, err)
tt.MinimumBaseAmount = 1
tt.MaximumBaseAmount = 10
@@ -311,115 +241,73 @@ func TestConforms(t *testing.T) {
tt.MarketMaxQty = 9.9
err = tt.Conforms(200000, 1, Market)
if !errors.Is(err, ErrMarketAmountBelowMin) {
t.Fatalf("expected error %v but received: %v", ErrMarketAmountBelowMin, err)
}
require.ErrorIs(t, err, ErrMarketAmountBelowMin)
err = tt.Conforms(200000, 10, Market)
if !errors.Is(err, ErrMarketAmountExceedsMax) {
t.Fatalf("expected error %v but received: %v", ErrMarketAmountExceedsMax, err)
}
require.ErrorIs(t, err, ErrMarketAmountExceedsMax)
tt.MarketStepIncrementSize = 10
err = tt.Conforms(200000, 9.1, Market)
if !errors.Is(err, ErrMarketAmountExceedsStep) {
t.Fatalf("expected error %v but received: %v", ErrMarketAmountExceedsStep, err)
}
require.ErrorIs(t, err, ErrMarketAmountExceedsStep)
tt.MarketStepIncrementSize = 1
err = tt.Conforms(200000, 9.1, Market)
if !errors.Is(err, nil) {
t.Fatalf("expected error %v but received: %v", nil, err)
}
assert.NoError(t, err)
}
func TestConformToDecimalAmount(t *testing.T) {
t.Parallel()
var tt MinMaxLevel
if !tt.ConformToDecimalAmount(decimal.NewFromFloat(1.001)).Equal(decimal.NewFromFloat(1.001)) {
t.Fatal("value should not be changed")
}
require.True(t, tt.ConformToDecimalAmount(decimal.NewFromFloat(1.001)).Equal(decimal.NewFromFloat(1.001)))
tt = MinMaxLevel{}
val := tt.ConformToDecimalAmount(decimal.NewFromInt(1))
if !val.Equal(decimal.NewFromInt(1)) { // If there is no step amount set this should not change
// the inputted amount
t.Fatal("unexpected amount")
}
assert.True(t, val.Equal(decimal.NewFromInt(1))) // If there is no step amount set, this should not change the inputted amount
tt.AmountStepIncrementSize = 0.001
val = tt.ConformToDecimalAmount(decimal.NewFromFloat(1.001))
if !val.Equal(decimal.NewFromFloat(1.001)) {
t.Error("unexpected amount", val)
}
assert.True(t, val.Equal(decimal.NewFromFloat(1.001)))
val = tt.ConformToDecimalAmount(decimal.NewFromFloat(0.0001))
if !val.IsZero() {
t.Error("unexpected amount", val)
}
assert.True(t, val.IsZero())
val = tt.ConformToDecimalAmount(decimal.NewFromFloat(0.7777))
if !val.Equal(decimal.NewFromFloat(0.777)) {
t.Error("unexpected amount", val)
}
assert.True(t, val.Equal(decimal.NewFromFloat(0.777)))
tt.AmountStepIncrementSize = 100
val = tt.ConformToDecimalAmount(decimal.NewFromInt(100))
if !val.Equal(decimal.NewFromInt(100)) {
t.Fatal("unexpected amount", val)
}
assert.True(t, val.Equal(decimal.NewFromInt(100)))
val = tt.ConformToDecimalAmount(decimal.NewFromInt(200))
if !val.Equal(decimal.NewFromInt(200)) {
t.Fatal("unexpected amount", val)
}
assert.True(t, val.Equal(decimal.NewFromInt(200)))
val = tt.ConformToDecimalAmount(decimal.NewFromInt(150))
if !val.Equal(decimal.NewFromInt(100)) {
t.Fatal("unexpected amount", val)
}
assert.True(t, val.Equal(decimal.NewFromInt(100)))
}
func TestConformToAmount(t *testing.T) {
t.Parallel()
var tt MinMaxLevel
if tt.ConformToAmount(1.001) != 1.001 {
t.Fatal("value should not be changed")
}
require.Equal(t, 1.001, tt.ConformToAmount(1.001))
tt = MinMaxLevel{}
val := tt.ConformToAmount(1)
if val != 1 { // If there is no step amount set this should not change
// the inputted amount
t.Fatal("unexpected amount")
}
assert.Equal(t, 1.0, val, "ConformToAmount should return the same value with no step amount set")
tt.AmountStepIncrementSize = 0.001
val = tt.ConformToAmount(1.001)
if val != 1.001 {
t.Error("unexpected amount", val)
}
assert.Equal(t, 1.001, val)
val = tt.ConformToAmount(0.0001)
if val != 0 {
t.Error("unexpected amount", val)
}
assert.Zero(t, val)
val = tt.ConformToAmount(0.7777)
if val != 0.777 {
t.Error("unexpected amount", val)
}
assert.Equal(t, 0.777, val)
tt.AmountStepIncrementSize = 100
val = tt.ConformToAmount(100)
if val != 100 {
t.Fatal("unexpected amount", val)
}
assert.Equal(t, 100.0, val)
val = tt.ConformToAmount(200)
if val != 200 {
t.Fatal("unexpected amount", val)
}
require.Equal(t, 200.0, val)
val = tt.ConformToAmount(150)
if val != 100 {
t.Fatal("unexpected amount", val)
}
assert.Equal(t, 100.0, val)
}