mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-06-06 07:26:47 +00:00
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:
@@ -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)
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -46,11 +46,9 @@ type Submit struct {
|
||||
Pair currency.Pair
|
||||
AssetType asset.Item
|
||||
|
||||
// Time in force values ------ TODO: Time In Force uint8
|
||||
ImmediateOrCancel bool
|
||||
FillOrKill bool
|
||||
// TimeInForce holds time in force values
|
||||
TimeInForce TimeInForce
|
||||
|
||||
PostOnly bool
|
||||
// ReduceOnly reduces a position instead of opening an opposing
|
||||
// position; this also equates to closing the position in huobi_wrapper.go
|
||||
// swaps.
|
||||
@@ -109,19 +107,17 @@ type SubmitResponse struct {
|
||||
Pair currency.Pair
|
||||
AssetType asset.Item
|
||||
|
||||
ImmediateOrCancel bool
|
||||
FillOrKill bool
|
||||
PostOnly bool
|
||||
TimeInForce TimeInForce
|
||||
ReduceOnly bool
|
||||
Leverage float64
|
||||
Price float64
|
||||
AverageExecutedPrice float64
|
||||
Amount float64
|
||||
QuoteAmount float64
|
||||
RemainingAmount float64
|
||||
TriggerPrice float64
|
||||
ClientID string
|
||||
ClientOrderID string
|
||||
AverageExecutedPrice float64
|
||||
|
||||
LastUpdated time.Time
|
||||
Date time.Time
|
||||
@@ -164,11 +160,10 @@ type Modify struct {
|
||||
Pair currency.Pair
|
||||
|
||||
// Change fields
|
||||
ImmediateOrCancel bool
|
||||
PostOnly bool
|
||||
Price float64
|
||||
Amount float64
|
||||
TriggerPrice float64
|
||||
TimeInForce TimeInForce
|
||||
Price float64
|
||||
Amount float64
|
||||
TriggerPrice float64
|
||||
|
||||
// added to represent a unified trigger price type information such as LastPrice, MarkPrice, and IndexPrice
|
||||
// https://bybit-exchange.github.io/docs/v5/order/create-order
|
||||
@@ -190,11 +185,10 @@ type ModifyResponse struct {
|
||||
AssetType asset.Item
|
||||
|
||||
// Fields that will be copied over from Modify
|
||||
ImmediateOrCancel bool
|
||||
PostOnly bool
|
||||
Price float64
|
||||
Amount float64
|
||||
TriggerPrice float64
|
||||
TimeInForce TimeInForce
|
||||
Price float64
|
||||
Amount float64
|
||||
TriggerPrice float64
|
||||
|
||||
// Fields that need to be handled in scope after DeriveModifyResponse()
|
||||
// if applicable
|
||||
@@ -206,10 +200,8 @@ type ModifyResponse struct {
|
||||
// Detail contains all properties of an order
|
||||
// Each exchange has their own requirements, so not all fields are required to be populated
|
||||
type Detail struct {
|
||||
ImmediateOrCancel bool
|
||||
HiddenOrder bool
|
||||
FillOrKill bool
|
||||
PostOnly bool
|
||||
TimeInForce TimeInForce
|
||||
ReduceOnly bool
|
||||
Leverage float64
|
||||
Price float64
|
||||
@@ -276,6 +268,7 @@ type Cancel struct {
|
||||
AssetType asset.Item
|
||||
Pair currency.Pair
|
||||
MarginType margin.Type
|
||||
TimeInForce TimeInForce
|
||||
}
|
||||
|
||||
// CancelAllResponse returns the status from attempting to
|
||||
@@ -311,12 +304,13 @@ type TradeHistory struct {
|
||||
type MultiOrderRequest struct {
|
||||
// Currencies Empty array = all currencies. Some endpoints only support
|
||||
// singular currency enquiries
|
||||
Pairs currency.Pairs
|
||||
AssetType asset.Item
|
||||
Type Type
|
||||
Side Side
|
||||
StartTime time.Time
|
||||
EndTime time.Time
|
||||
Pairs currency.Pairs
|
||||
AssetType asset.Item
|
||||
Type Type
|
||||
Side Side
|
||||
TimeInForce TimeInForce
|
||||
StartTime time.Time
|
||||
EndTime time.Time
|
||||
// FromOrderID for some APIs require order history searching
|
||||
// from a specific orderID rather than via timestamps
|
||||
FromOrderID string
|
||||
@@ -361,15 +355,12 @@ const (
|
||||
UnknownType Type = 0
|
||||
Limit Type = 1 << iota
|
||||
Market
|
||||
PostOnly
|
||||
ImmediateOrCancel
|
||||
Stop
|
||||
StopLimit
|
||||
StopMarket
|
||||
TakeProfit
|
||||
TakeProfitMarket
|
||||
TrailingStop
|
||||
FillOrKill
|
||||
IOS
|
||||
AnyType
|
||||
Liquidation
|
||||
|
||||
@@ -43,7 +43,6 @@ var (
|
||||
)
|
||||
|
||||
var (
|
||||
errTimeInForceConflict = errors.New("multiple time in force options applied")
|
||||
errUnrecognisedOrderType = errors.New("unrecognised order type")
|
||||
errUnrecognisedOrderStatus = errors.New("unrecognised order status")
|
||||
errExchangeNameUnset = errors.New("exchange name unset")
|
||||
@@ -88,8 +87,8 @@ func (s *Submit) Validate(requirements protocol.TradingRequirements, opt ...vali
|
||||
return ErrTypeIsInvalid
|
||||
}
|
||||
|
||||
if s.ImmediateOrCancel && s.FillOrKill {
|
||||
return errTimeInForceConflict
|
||||
if !s.TimeInForce.IsValid() {
|
||||
return ErrInvalidTimeInForce
|
||||
}
|
||||
|
||||
if s.Amount == 0 && s.QuoteAmount == 0 {
|
||||
@@ -159,18 +158,14 @@ func (d *Detail) UpdateOrderFromDetail(m *Detail) error {
|
||||
}
|
||||
|
||||
var updated bool
|
||||
if d.ImmediateOrCancel != m.ImmediateOrCancel {
|
||||
d.ImmediateOrCancel = m.ImmediateOrCancel
|
||||
if m.TimeInForce != UnknownTIF && d.TimeInForce != m.TimeInForce {
|
||||
d.TimeInForce = m.TimeInForce
|
||||
updated = true
|
||||
}
|
||||
if d.HiddenOrder != m.HiddenOrder {
|
||||
d.HiddenOrder = m.HiddenOrder
|
||||
updated = true
|
||||
}
|
||||
if d.FillOrKill != m.FillOrKill {
|
||||
d.FillOrKill = m.FillOrKill
|
||||
updated = true
|
||||
}
|
||||
if m.Price > 0 && m.Price != d.Price {
|
||||
d.Price = m.Price
|
||||
updated = true
|
||||
@@ -207,10 +202,6 @@ func (d *Detail) UpdateOrderFromDetail(m *Detail) error {
|
||||
d.AccountID = m.AccountID
|
||||
updated = true
|
||||
}
|
||||
if m.PostOnly != d.PostOnly {
|
||||
d.PostOnly = m.PostOnly
|
||||
updated = true
|
||||
}
|
||||
if !m.Pair.IsEmpty() && !m.Pair.Equal(d.Pair) {
|
||||
// TODO: Add a check to see if the original pair is empty as well, but
|
||||
// error if it is changing from BTC-USD -> LTC-USD.
|
||||
@@ -322,8 +313,8 @@ func (d *Detail) UpdateOrderFromModifyResponse(m *ModifyResponse) {
|
||||
d.OrderID = m.OrderID
|
||||
updated = true
|
||||
}
|
||||
if d.ImmediateOrCancel != m.ImmediateOrCancel {
|
||||
d.ImmediateOrCancel = m.ImmediateOrCancel
|
||||
if d.TimeInForce != m.TimeInForce && m.TimeInForce != UnknownTIF {
|
||||
d.TimeInForce = m.TimeInForce
|
||||
updated = true
|
||||
}
|
||||
if m.Price > 0 && m.Price != d.Price {
|
||||
@@ -338,10 +329,6 @@ func (d *Detail) UpdateOrderFromModifyResponse(m *ModifyResponse) {
|
||||
d.TriggerPrice = m.TriggerPrice
|
||||
updated = true
|
||||
}
|
||||
if m.PostOnly != d.PostOnly {
|
||||
d.PostOnly = m.PostOnly
|
||||
updated = true
|
||||
}
|
||||
if !m.Pair.IsEmpty() && !m.Pair.Equal(d.Pair) {
|
||||
// TODO: Add a check to see if the original pair is empty as well, but
|
||||
// error if it is changing from BTC-USD -> LTC-USD.
|
||||
@@ -496,18 +483,16 @@ func (s *Submit) DeriveSubmitResponse(orderID string) (*SubmitResponse, error) {
|
||||
Pair: s.Pair,
|
||||
AssetType: s.AssetType,
|
||||
|
||||
ImmediateOrCancel: s.ImmediateOrCancel,
|
||||
FillOrKill: s.FillOrKill,
|
||||
PostOnly: s.PostOnly,
|
||||
ReduceOnly: s.ReduceOnly,
|
||||
Leverage: s.Leverage,
|
||||
Price: s.Price,
|
||||
Amount: s.Amount,
|
||||
QuoteAmount: s.QuoteAmount,
|
||||
TriggerPrice: s.TriggerPrice,
|
||||
ClientID: s.ClientID,
|
||||
ClientOrderID: s.ClientOrderID,
|
||||
MarginType: s.MarginType,
|
||||
TimeInForce: s.TimeInForce,
|
||||
ReduceOnly: s.ReduceOnly,
|
||||
Leverage: s.Leverage,
|
||||
Price: s.Price,
|
||||
Amount: s.Amount,
|
||||
QuoteAmount: s.QuoteAmount,
|
||||
TriggerPrice: s.TriggerPrice,
|
||||
ClientID: s.ClientID,
|
||||
ClientOrderID: s.ClientOrderID,
|
||||
MarginType: s.MarginType,
|
||||
|
||||
LastUpdated: time.Now(),
|
||||
Date: time.Now(),
|
||||
@@ -589,17 +574,15 @@ func (s *SubmitResponse) DeriveDetail(internal uuid.UUID) (*Detail, error) {
|
||||
Pair: s.Pair,
|
||||
AssetType: s.AssetType,
|
||||
|
||||
ImmediateOrCancel: s.ImmediateOrCancel,
|
||||
FillOrKill: s.FillOrKill,
|
||||
PostOnly: s.PostOnly,
|
||||
ReduceOnly: s.ReduceOnly,
|
||||
Leverage: s.Leverage,
|
||||
Price: s.Price,
|
||||
Amount: s.Amount,
|
||||
QuoteAmount: s.QuoteAmount,
|
||||
TriggerPrice: s.TriggerPrice,
|
||||
ClientID: s.ClientID,
|
||||
ClientOrderID: s.ClientOrderID,
|
||||
TimeInForce: s.TimeInForce,
|
||||
ReduceOnly: s.ReduceOnly,
|
||||
Leverage: s.Leverage,
|
||||
Price: s.Price,
|
||||
Amount: s.Amount,
|
||||
QuoteAmount: s.QuoteAmount,
|
||||
TriggerPrice: s.TriggerPrice,
|
||||
ClientID: s.ClientID,
|
||||
ClientOrderID: s.ClientOrderID,
|
||||
|
||||
InternalOrderID: internal,
|
||||
|
||||
@@ -649,18 +632,17 @@ func (m *Modify) DeriveModifyResponse() (*ModifyResponse, error) {
|
||||
return nil, errOrderDetailIsNil
|
||||
}
|
||||
return &ModifyResponse{
|
||||
Exchange: m.Exchange,
|
||||
OrderID: m.OrderID,
|
||||
ClientOrderID: m.ClientOrderID,
|
||||
Type: m.Type,
|
||||
Side: m.Side,
|
||||
AssetType: m.AssetType,
|
||||
Pair: m.Pair,
|
||||
ImmediateOrCancel: m.ImmediateOrCancel,
|
||||
PostOnly: m.PostOnly,
|
||||
Price: m.Price,
|
||||
Amount: m.Amount,
|
||||
TriggerPrice: m.TriggerPrice,
|
||||
Exchange: m.Exchange,
|
||||
OrderID: m.OrderID,
|
||||
ClientOrderID: m.ClientOrderID,
|
||||
Type: m.Type,
|
||||
Side: m.Side,
|
||||
AssetType: m.AssetType,
|
||||
Pair: m.Pair,
|
||||
TimeInForce: m.TimeInForce,
|
||||
Price: m.Price,
|
||||
Amount: m.Amount,
|
||||
TriggerPrice: m.TriggerPrice,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -691,10 +673,6 @@ func (t Type) String() string {
|
||||
return "LIMIT"
|
||||
case Market:
|
||||
return "MARKET"
|
||||
case PostOnly:
|
||||
return "POST_ONLY"
|
||||
case ImmediateOrCancel:
|
||||
return "IMMEDIATE_OR_CANCEL"
|
||||
case Stop:
|
||||
return "STOP"
|
||||
case ConditionalStop:
|
||||
@@ -717,8 +695,6 @@ func (t Type) String() string {
|
||||
return "TAKE PROFIT MARKET"
|
||||
case TrailingStop:
|
||||
return "TRAILING_STOP"
|
||||
case FillOrKill:
|
||||
return "FOK"
|
||||
case IOS:
|
||||
return "IOS"
|
||||
case Liquidation:
|
||||
@@ -1130,8 +1106,6 @@ func StringToOrderType(oType string) (Type, error) {
|
||||
return Limit, nil
|
||||
case Market.String(), "EXCHANGE MARKET":
|
||||
return Market, nil
|
||||
case ImmediateOrCancel.String(), "IMMEDIATE OR CANCEL", "IOC", "EXCHANGE IOC":
|
||||
return ImmediateOrCancel, nil
|
||||
case Stop.String(), "STOP LOSS", "STOP_LOSS", "EXCHANGE STOP":
|
||||
return Stop, nil
|
||||
case StopLimit.String(), "EXCHANGE STOP LIMIT", "STOP_LIMIT":
|
||||
@@ -1140,12 +1114,8 @@ func StringToOrderType(oType string) (Type, error) {
|
||||
return StopMarket, nil
|
||||
case TrailingStop.String(), "TRAILING STOP", "EXCHANGE TRAILING STOP", "MOVE_ORDER_STOP":
|
||||
return TrailingStop, nil
|
||||
case FillOrKill.String(), "EXCHANGE FOK":
|
||||
return FillOrKill, nil
|
||||
case IOS.String():
|
||||
return IOS, nil
|
||||
case PostOnly.String():
|
||||
return PostOnly, nil
|
||||
case AnyType.String():
|
||||
return AnyType, nil
|
||||
case Trigger.String():
|
||||
|
||||
140
exchanges/order/timeinforce.go
Normal file
140
exchanges/order/timeinforce.go
Normal file
@@ -0,0 +1,140 @@
|
||||
package order
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// var error definitions
|
||||
var (
|
||||
ErrInvalidTimeInForce = errors.New("invalid time in force value provided")
|
||||
ErrUnsupportedTimeInForce = errors.New("unsupported time in force value")
|
||||
)
|
||||
|
||||
// TimeInForce enforces a standard for time-in-force values across the code base.
|
||||
type TimeInForce uint8
|
||||
|
||||
// TimeInForce types
|
||||
const (
|
||||
UnknownTIF TimeInForce = 0
|
||||
GoodTillCancel TimeInForce = 1 << iota
|
||||
GoodTillDay
|
||||
GoodTillTime
|
||||
GoodTillCrossing
|
||||
FillOrKill
|
||||
ImmediateOrCancel
|
||||
PostOnly
|
||||
|
||||
supportedTimeInForceFlag = GoodTillCancel | GoodTillDay | GoodTillTime | GoodTillCrossing | FillOrKill | ImmediateOrCancel | PostOnly
|
||||
)
|
||||
|
||||
// time-in-force string representations
|
||||
const (
|
||||
gtcStr = "GTC"
|
||||
gtdStr = "GTD"
|
||||
gttStr = "GTT"
|
||||
gtxStr = "GTX"
|
||||
fokStr = "FOK"
|
||||
iocStr = "IOC"
|
||||
postonlyStr = "POSTONLY"
|
||||
)
|
||||
|
||||
// Is checks to see if the enum contains the flag
|
||||
func (t TimeInForce) Is(in TimeInForce) bool {
|
||||
return in != 0 && t&in == in
|
||||
}
|
||||
|
||||
// StringToTimeInForce converts time in force string value to TimeInForce instance.
|
||||
func StringToTimeInForce(timeInForce string) (TimeInForce, error) {
|
||||
var result TimeInForce
|
||||
timeInForce = strings.ToUpper(timeInForce)
|
||||
switch timeInForce {
|
||||
case "IMMEDIATEORCANCEL", "IMMEDIATE_OR_CANCEL", iocStr:
|
||||
result = ImmediateOrCancel
|
||||
case "GOODTILLCANCEL", "GOODTILCANCEL", "GOOD_TIL_CANCELLED", "GOOD_TILL_CANCELLED", "GOOD_TILL_CANCELED", gtcStr:
|
||||
result = GoodTillCancel
|
||||
case "GOODTILLDAY", "GOOD_TIL_DAY", "GOOD_TILL_DAY", gtdStr:
|
||||
result = GoodTillDay
|
||||
case "GOODTILLTIME", "GOOD_TIL_TIME", gttStr:
|
||||
result = GoodTillTime
|
||||
case "GOODTILLCROSSING", "GOOD_TIL_CROSSING", "GOOD TIL CROSSING", "GOOD_TILL_CROSSING", gtxStr:
|
||||
result = GoodTillCrossing
|
||||
case "FILLORKILL", "FILL_OR_KILL", fokStr:
|
||||
result = FillOrKill
|
||||
case "POC", "POST_ONLY", "PENDINGORCANCEL", postonlyStr:
|
||||
result = PostOnly
|
||||
}
|
||||
if result == UnknownTIF && timeInForce != "" {
|
||||
return UnknownTIF, fmt.Errorf("%w: tif=%s", ErrInvalidTimeInForce, timeInForce)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// IsValid returns whether or not the supplied time in force value is valid or
|
||||
// not
|
||||
func (t TimeInForce) IsValid() bool {
|
||||
// Neither ImmediateOrCancel nor FillOrKill can coexist with anything else
|
||||
// If either bit is set then it must be the only bit set
|
||||
isIOCorFOK := t&(ImmediateOrCancel|FillOrKill) != 0
|
||||
hasTwoBitsSet := t&(t-1) != 0
|
||||
if isIOCorFOK && hasTwoBitsSet {
|
||||
return false
|
||||
}
|
||||
return t == UnknownTIF || supportedTimeInForceFlag&t == t
|
||||
}
|
||||
|
||||
// String implements the stringer interface.
|
||||
func (t TimeInForce) String() string {
|
||||
if t == UnknownTIF {
|
||||
return ""
|
||||
}
|
||||
var tifStrings []string
|
||||
if t.Is(ImmediateOrCancel) {
|
||||
tifStrings = append(tifStrings, iocStr)
|
||||
}
|
||||
if t.Is(GoodTillCancel) {
|
||||
tifStrings = append(tifStrings, gtcStr)
|
||||
}
|
||||
if t.Is(GoodTillDay) {
|
||||
tifStrings = append(tifStrings, gtdStr)
|
||||
}
|
||||
if t.Is(GoodTillTime) {
|
||||
tifStrings = append(tifStrings, gttStr)
|
||||
}
|
||||
if t.Is(GoodTillCrossing) {
|
||||
tifStrings = append(tifStrings, gtxStr)
|
||||
}
|
||||
if t.Is(FillOrKill) {
|
||||
tifStrings = append(tifStrings, fokStr)
|
||||
}
|
||||
if t.Is(PostOnly) {
|
||||
tifStrings = append(tifStrings, postonlyStr)
|
||||
}
|
||||
if len(tifStrings) == 0 {
|
||||
return "UNKNOWN"
|
||||
}
|
||||
return strings.Join(tifStrings, ",")
|
||||
}
|
||||
|
||||
// Lower returns a lower case string representation of time-in-force
|
||||
func (t TimeInForce) Lower() string {
|
||||
return strings.ToLower(t.String())
|
||||
}
|
||||
|
||||
// UnmarshalJSON deserializes a string data into TimeInForce instance.
|
||||
func (t *TimeInForce) UnmarshalJSON(data []byte) error {
|
||||
for val := range strings.SplitSeq(strings.Trim(string(data), `"`), ",") {
|
||||
tif, err := StringToTimeInForce(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*t |= tif
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON returns the JSON-encoded order time-in-force value
|
||||
func (t TimeInForce) MarshalJSON() ([]byte, error) {
|
||||
return []byte(`"` + t.String() + `"`), nil
|
||||
}
|
||||
168
exchanges/order/timeinforce_test.go
Normal file
168
exchanges/order/timeinforce_test.go
Normal file
@@ -0,0 +1,168 @@
|
||||
package order
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/thrasher-corp/gocryptotrader/encoding/json"
|
||||
)
|
||||
|
||||
func TestTimeInForceIs(t *testing.T) {
|
||||
t.Parallel()
|
||||
tifValuesMap := map[TimeInForce][]TimeInForce{
|
||||
GoodTillCancel | PostOnly: {GoodTillCancel, PostOnly},
|
||||
GoodTillCancel: {GoodTillCancel},
|
||||
GoodTillCrossing | PostOnly: {GoodTillCrossing, PostOnly},
|
||||
GoodTillDay: {GoodTillDay},
|
||||
GoodTillTime: {GoodTillTime},
|
||||
GoodTillTime | PostOnly: {GoodTillTime, PostOnly},
|
||||
ImmediateOrCancel: {ImmediateOrCancel},
|
||||
FillOrKill: {FillOrKill},
|
||||
PostOnly: {PostOnly},
|
||||
GoodTillCrossing: {GoodTillCrossing},
|
||||
}
|
||||
for tif, exps := range tifValuesMap {
|
||||
for _, v := range exps {
|
||||
require.Truef(t, tif.Is(v), "%s should be %s", tif, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsValid(t *testing.T) {
|
||||
t.Parallel()
|
||||
timeInForceValidityMap := map[TimeInForce]bool{
|
||||
TimeInForce(1): false,
|
||||
ImmediateOrCancel: true,
|
||||
GoodTillTime: true,
|
||||
GoodTillCancel: true,
|
||||
GoodTillDay: true,
|
||||
FillOrKill: true,
|
||||
PostOnly: true,
|
||||
FillOrKill | ImmediateOrCancel: false,
|
||||
FillOrKill | GoodTillCancel: false,
|
||||
FillOrKill | PostOnly: false,
|
||||
ImmediateOrCancel | GoodTillCancel: false,
|
||||
ImmediateOrCancel | PostOnly: false,
|
||||
GoodTillTime | PostOnly: true,
|
||||
GoodTillDay | PostOnly: true,
|
||||
GoodTillCrossing | PostOnly: true,
|
||||
GoodTillCancel | PostOnly: true,
|
||||
UnknownTIF: true,
|
||||
}
|
||||
for tif, value := range timeInForceValidityMap {
|
||||
assert.Equal(t, value, tif.IsValid())
|
||||
}
|
||||
}
|
||||
|
||||
var timeInForceStringToValueMap = map[string]struct {
|
||||
TIF TimeInForce
|
||||
Error error
|
||||
}{
|
||||
"GoodTillCancel": {TIF: GoodTillCancel},
|
||||
"GOOD_TILL_CANCELED": {TIF: GoodTillCancel},
|
||||
"GTT": {TIF: GoodTillTime},
|
||||
"GOOD_TIL_TIME": {TIF: GoodTillTime},
|
||||
"FILLORKILL": {TIF: FillOrKill},
|
||||
"immedIate_Or_Cancel": {TIF: ImmediateOrCancel},
|
||||
"IOC": {TIF: ImmediateOrCancel},
|
||||
"immediate_or_cancel": {TIF: ImmediateOrCancel},
|
||||
"IMMEDIATE_OR_CANCEL": {TIF: ImmediateOrCancel},
|
||||
"IMMEDIATEORCANCEL": {TIF: ImmediateOrCancel},
|
||||
"GOOD_TILL_CANCELLED": {TIF: GoodTillCancel},
|
||||
"good_till_day": {TIF: GoodTillDay},
|
||||
"GOOD_TILL_DAY": {TIF: GoodTillDay},
|
||||
"GTD": {TIF: GoodTillDay},
|
||||
"GOODtillday": {TIF: GoodTillDay},
|
||||
"PoC": {TIF: PostOnly},
|
||||
"PendingORCANCEL": {TIF: PostOnly},
|
||||
"GTX": {TIF: GoodTillCrossing},
|
||||
"GOOD_TILL_CROSSING": {TIF: GoodTillCrossing},
|
||||
"Good Til crossing": {TIF: GoodTillCrossing},
|
||||
"abcdfeg": {TIF: UnknownTIF, Error: ErrInvalidTimeInForce},
|
||||
}
|
||||
|
||||
func TestStringToTimeInForce(t *testing.T) {
|
||||
t.Parallel()
|
||||
for tk, exp := range timeInForceStringToValueMap {
|
||||
t.Run(tk, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
result, err := StringToTimeInForce(tk)
|
||||
if exp.Error != nil {
|
||||
require.ErrorIs(t, err, exp.Error)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
assert.Equal(t, exp.TIF, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestString(t *testing.T) {
|
||||
t.Parallel()
|
||||
valMap := map[TimeInForce]string{
|
||||
ImmediateOrCancel: "IOC",
|
||||
GoodTillCancel: "GTC",
|
||||
GoodTillTime: "GTT",
|
||||
GoodTillDay: "GTD",
|
||||
FillOrKill: "FOK",
|
||||
UnknownTIF: "",
|
||||
PostOnly: "POSTONLY",
|
||||
GoodTillCancel | PostOnly: "GTC,POSTONLY",
|
||||
GoodTillTime | PostOnly: "GTT,POSTONLY",
|
||||
GoodTillDay | PostOnly: "GTD,POSTONLY",
|
||||
FillOrKill | ImmediateOrCancel: "IOC,FOK",
|
||||
TimeInForce(1): "UNKNOWN",
|
||||
}
|
||||
for x := range valMap {
|
||||
assert.Equal(t, valMap[x], x.String())
|
||||
assert.Equal(t, strings.ToLower(valMap[x]), x.Lower())
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalJSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
targets := []TimeInForce{
|
||||
GoodTillCancel | PostOnly | ImmediateOrCancel, GoodTillCancel | PostOnly, GoodTillCancel, UnknownTIF, PostOnly | ImmediateOrCancel,
|
||||
GoodTillCancel, GoodTillCancel, PostOnly, PostOnly, ImmediateOrCancel, GoodTillDay, GoodTillDay, GoodTillTime, FillOrKill, FillOrKill,
|
||||
}
|
||||
data := `{"tifs": ["GTC,POSTONLY,IOC", "GTC,POSTONLY", "GTC", "", "POSTONLY,IOC", "GoodTilCancel", "GoodTILLCANCEL", "POST_ONLY", "POC","IOC", "GTD", "gtd","gtt", "fok", "fillOrKill"]}`
|
||||
target := &struct {
|
||||
TIFs []TimeInForce `json:"tifs"`
|
||||
}{}
|
||||
err := json.Unmarshal([]byte(data), &target)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, targets, target.TIFs)
|
||||
|
||||
data = `{"tifs": ["abcd,POSTONLY,IOC", "GTC,POSTONLY", "GTC", "", "POSTONLY,IOC", "GoodTilCancel", "GoodTILLCANCEL", "POST_ONLY", "POC","IOC", "GTD", "gtd","gtt", "fok", "fillOrKill"]}`
|
||||
target = &struct {
|
||||
TIFs []TimeInForce `json:"tifs"`
|
||||
}{}
|
||||
err = json.Unmarshal([]byte(data), &target)
|
||||
require.ErrorIs(t, err, ErrInvalidTimeInForce)
|
||||
}
|
||||
|
||||
func TestMarshalJSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
data, err := json.Marshal(GoodTillCrossing)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []byte(`"GTX"`), data)
|
||||
|
||||
data = []byte(`{"tif":"IOC"}`)
|
||||
target := &struct {
|
||||
TimeInForce TimeInForce `json:"tif"`
|
||||
}{}
|
||||
err = json.Unmarshal(data, &target)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "IOC", target.TimeInForce.String())
|
||||
}
|
||||
|
||||
// BenchmarkStringToTimeInForce-8 416595 2834 ns/op 1368 B/op 81 allocs/op
|
||||
func BenchmarkStringToTimeInForce(b *testing.B) {
|
||||
for b.Loop() {
|
||||
for k := range timeInForceStringToValueMap {
|
||||
_, _ = StringToTimeInForce(k)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user