protocol/order: adds additional fields for trading requirements (#1552)

* Add in initial handling for quote/base currency deployment requirements

* include client order ID checking

* glorious: suggestions

* spell and fix

* linter/context/test

* rework tests and order side specific requirements

* linter

* mend panic at the disco

* mending more panics at the disco

* anudda fix brudda

* glorious: NITTTTTT BOOOOOMB

* leftover things and stuff

* whoops

* tie in gateio

* glorious: nit fixes life and everything.

* thrasher: nits

---------

Co-authored-by: Ryan O'Hara-Reid <ryan.oharareid@thrasher.io>
This commit is contained in:
Ryan O'Hara-Reid
2024-07-03 16:07:23 +10:00
committed by GitHub
parent b7a2f617d9
commit 48349bc3bb
35 changed files with 210 additions and 77 deletions

View File

@@ -15,6 +15,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/protocol"
"github.com/thrasher-corp/gocryptotrader/exchanges/validate"
)
@@ -24,9 +25,12 @@ func TestSubmit_Validate(t *testing.T) {
t.Parallel()
testPair := currency.NewPair(currency.BTC, currency.LTC)
tester := []struct {
ExpectedErr error
Submit *Submit
ValidOpts validate.Checker
ExpectedErr error
Submit *Submit
ValidOpts validate.Checker
HasToPurchaseWithQuoteAmountSet bool
HasToSellWithBaseAmountSet bool
RequiresID bool
}{
{
ExpectedErr: ErrSubmissionIsNil,
@@ -178,13 +182,72 @@ func TestSubmit_Validate(t *testing.T) {
},
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 := range tester {
err := tester[x].Submit.Validate(tester[x].ValidOpts)
if !errors.Is(err, tester[x].ExpectedErr) {
t.Fatalf("Unexpected result. %d Got: %v, want: %v", x+1, err, tester[x].ExpectedErr)
}
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)
})
}
}

View File

@@ -14,6 +14,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/protocol"
"github.com/thrasher-corp/gocryptotrader/exchanges/validate"
"github.com/thrasher-corp/gocryptotrader/log"
"golang.org/x/text/cases"
@@ -30,16 +31,17 @@ const (
notPlaced = InsufficientBalance | MarketUnavailable | Rejected
)
// Public error vars for order package
var (
// ErrUnableToPlaceOrder defines an error when an order submission has
// failed.
ErrUnableToPlaceOrder = errors.New("order not placed")
// ErrOrderNotFound is returned when no order is found
ErrOrderNotFound = errors.New("order not found")
// ErrUnknownPriceType returned when price type is unknown
ErrUnknownPriceType = errors.New("unknown price type")
ErrUnableToPlaceOrder = errors.New("order not placed")
ErrOrderNotFound = errors.New("order not found")
ErrUnknownPriceType = errors.New("unknown price type")
ErrAmountMustBeSet = errors.New("amount must be set")
ErrClientOrderIDMustBeSet = errors.New("client order ID must be set")
ErrUnknownSubmissionAmountType = errors.New("unknown submission amount type")
)
var (
errTimeInForceConflict = errors.New("multiple time in force options applied")
errUnrecognisedOrderType = errors.New("unrecognised order type")
errUnrecognisedOrderStatus = errors.New("unrecognised order status")
@@ -56,7 +58,7 @@ func IsValidOrderSubmissionSide(s Side) bool {
}
// Validate checks the supplied data and returns whether it's valid
func (s *Submit) Validate(opt ...validate.Checker) error {
func (s *Submit) Validate(requirements protocol.TradingRequirements, opt ...validate.Checker) error {
if s == nil {
return ErrSubmissionIsNil
}
@@ -105,6 +107,18 @@ func (s *Submit) Validate(opt ...validate.Checker) error {
return ErrPriceMustBeSetIfLimitOrder
}
if requirements.ClientOrderID && s.ClientOrderID == "" {
return fmt.Errorf("submit validation error %w, client order ID must be set to satisfy submission requirements", ErrClientOrderIDMustBeSet)
}
if requirements.SpotMarketOrderAmountPurchaseQuotationOnly && s.QuoteAmount == 0 && s.Type == Market && s.AssetType == asset.Spot && s.Side.IsLong() {
return fmt.Errorf("submit validation error %w, quote amount to be sold must be set to 'QuoteAmount' field to satisfy trading requirements", ErrAmountMustBeSet)
}
if requirements.SpotMarketOrderAmountSellBaseOnly && s.Amount == 0 && s.Type == Market && s.AssetType == asset.Spot && s.Side.IsShort() {
return fmt.Errorf("submit validation error %w, base amount being sold must be set to 'Amount' field to satisfy trading requirements", ErrAmountMustBeSet)
}
for _, o := range opt {
err := o.Check()
if err != nil {