Files
gocryptotrader/exchanges/order/orders.go
Adam 504c2fad6d Feature: Implement funding rates, futures and coin margin (exchange API coverage) (#530)
* ALMOST THERE

* more api wips

* more api thingz

* testing n more api wipz

* more apiz

* more wips

* what is goin on

* more wips

* whip n testing

* testing

* testing

no keys

* remove log

* kraken is broken

ugh

* still broken

* fixing auth funcs + usdtm api docs

* wip

* api stuffs

* whip

* more wips

* whip

* more wip

* api wip n testing

* wip

* wip

* unsaved

* wip n testing

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* whip

* wrapper authenticated functions

* adding asset type and fixing dependencies

* wip

* binance auth wrapper start

* wrapper functionality

* wip

* wip

* wip

* wrapper cancel functions

* order submission for wrappers

* wip

* more error fixing and nits

* websocket beginning n error fix

* wip

* WOW

* glorious n shazzy nits

* useless nits

* wip

* fixing things

* merge stuffs

* crapveyor

* crapveyor rebuild

* probably broke more things than he fixed

* rm lns n other thangs

* hope

* please

* stop it

* done

* ofcourse

* rm vb

* fix lbank

* appveyor please

* float lev

* DONT ASK RYAN FOR HELP EVER

* wip

* wip

* endpoint upgrades continued

* path upgrade

* NeeeNeeeNeeeNeeeNING

* fix stuffs

* fixing time issue

* fixing broken funcs

* glorious nits

* shaz changes

* fixing errors for fundmon

* more error fixing for fundmon

* test running past 30s

* basic changes

* THX AGAIN SHAZBERT

* path system upgrade

* config upgrade

* unsaved stuffs

* broken wip config upgrade

* path system upgrade contd.

* path system upgrade contd

* path upgrade ready for review

* testing verbose removed

* linter stuffs

* appveyor stuffs

* appveyor stuff

* fixed?

* bugfix

* wip

* broken stuff

* fix test

* wierd hack fix

* appveyor pls stop

* error found

* more useless nits

* bitmex err

* broken wip

* broken wip path upgrade change to uint32

* changed url lookups to uint

* WOW

* ready4review

* config fixed HOPEFULLY

* config fix and glorious changes

* efficient way of getting orders and open orders

* binance wrapper logic fixing

* testing, adding tests and fixing lot of errrrrs

* merge master

* appveyor stuffs

* appveyor stuffs

* fmt

* test

* octalLiteral issue fix?

* octalLiteral fix?

* rm vb

* prnt ln to restart

* adding testz

* test fixzzz

* READY FOR REVIEW

* Actually ready now

* FORMATTING

* addressing shazzy n glorious nits

* crapveyor

* rm vb

* small change

* fixing err

* shazbert nits

* review changes

* requested changes

* more requested changes

* noo

* last nit fixes

* restart appveyor

* improving test cov

* Update .golangci.yml

* shazbert changes

* moving pair formatting

* format pair update wip

* path upgrade complete

* error fix

* appveyor linters

* more linters

* remove testexch

* more formatting changes

* changes

* shazbert changes

* checking older requested changes to ensure completion

* wip

* fixing broken code

* error fix

* all fixed

* additional changes

* more changes

* remove commented code

* ftx margin api

* appveyor fixes

* more appveyor issues + test addition

* more appveyor issues + test addition

* remove unnecessary

* testing

* testing, fixing okex api, error fix

* git merge fix

* go sum

* glorious changes and error fix

* rm vb

* more glorious changes and go mod tidy

* fixed now

* okex testing upgrade

* old config migration and batch fetching fix

* added test

* glorious requested changes WIP

* tested and fixed

* go fmted

* go fmt and test fix

* additional funcs and tests for fundingRates

* OKEX tested and fixed

* appveyor fixes

* ineff assign

* 1 glorious change

* error fix

* typo

* shazbert changes

* glorious code changes and path fixing huobi WIP

* adding assetType to accountinfo functions

* fixing panic

* panic fix and updating account info wrappers WIP

* updateaccountinfo updated

* testing WIP binance USDT n Coin Margined and Kraken Futures

* auth functions tested and fixed

* added test

* config reverted

* shazbert and glorious changes

* shazbert and glorious changes

* latest changes and portfolio update

* go fmt change:

* remove commented codes

* improved error checking

* index out of range fix

* rm ln

* critical nit

* glorious latest changes

* appveyor changes

* shazbert change

* easier readability

* latest glorious changes

* shadow dec

* assetstore updated

* last change

* another last change

* merge changes

* go mod tidy

* thrasher requested changes wip

* improving struct layouts

* appveyor go fmt

* remove unnecessary code

* shazbert changes

* small change

* oopsie

* tidy

* configtest reverted

* error fix

* oopsie

* for what

* test patch fix

* insecurities

* fixing tests

* fix config
2021-02-12 16:19:18 +11:00

795 lines
19 KiB
Go

package order
import (
"errors"
"fmt"
"sort"
"strings"
"time"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/validate"
)
// Validate checks the supplied data and returns whether or not it's valid
func (s *Submit) Validate(opt ...validate.Checker) error {
if s == nil {
return ErrSubmissionIsNil
}
if s.Pair.IsEmpty() {
return ErrPairIsEmpty
}
if s.AssetType == "" {
return ErrAssetNotSet
}
if s.Side != Buy &&
s.Side != Sell &&
s.Side != Bid &&
s.Side != Ask {
return ErrSideIsInvalid
}
if s.Type != Market && s.Type != Limit {
return ErrTypeIsInvalid
}
if s.Amount <= 0 {
return ErrAmountIsInvalid
}
if s.Type == Limit && s.Price <= 0 {
return ErrPriceMustBeSetIfLimitOrder
}
var errs common.Errors
for _, o := range opt {
err := o.Check()
if err != nil {
errs = append(errs, err)
}
}
if errs != nil {
return errs
}
return nil
}
// UpdateOrderFromDetail Will update an order detail (used in order management)
// by comparing passed in and existing values
func (d *Detail) UpdateOrderFromDetail(m *Detail) {
var updated bool
if d.ImmediateOrCancel != m.ImmediateOrCancel {
d.ImmediateOrCancel = m.ImmediateOrCancel
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
}
if m.Amount > 0 && m.Amount != d.Amount {
d.Amount = m.Amount
updated = true
}
if m.LimitPriceUpper > 0 && m.LimitPriceUpper != d.LimitPriceUpper {
d.LimitPriceUpper = m.LimitPriceUpper
updated = true
}
if m.LimitPriceLower > 0 && m.LimitPriceLower != d.LimitPriceLower {
d.LimitPriceLower = m.LimitPriceLower
updated = true
}
if m.TriggerPrice > 0 && m.TriggerPrice != d.TriggerPrice {
d.TriggerPrice = m.TriggerPrice
updated = true
}
if m.TargetAmount > 0 && m.TargetAmount != d.TargetAmount {
d.TargetAmount = m.TargetAmount
updated = true
}
if m.ExecutedAmount > 0 && m.ExecutedAmount != d.ExecutedAmount {
d.ExecutedAmount = m.ExecutedAmount
updated = true
}
if m.Fee > 0 && m.Fee != d.Fee {
d.Fee = m.Fee
updated = true
}
if m.AccountID != "" && m.AccountID != d.AccountID {
d.AccountID = m.AccountID
updated = true
}
if m.PostOnly != d.PostOnly {
d.PostOnly = m.PostOnly
updated = true
}
if !m.Pair.IsEmpty() && m.Pair != d.Pair {
d.Pair = m.Pair
updated = true
}
if m.Leverage != 0 && m.Leverage != d.Leverage {
d.Leverage = m.Leverage
updated = true
}
if m.ClientID != "" && m.ClientID != d.ClientID {
d.ClientID = m.ClientID
updated = true
}
if m.WalletAddress != "" && m.WalletAddress != d.WalletAddress {
d.WalletAddress = m.WalletAddress
updated = true
}
if m.Type != "" && m.Type != d.Type {
d.Type = m.Type
updated = true
}
if m.Side != "" && m.Side != d.Side {
d.Side = m.Side
updated = true
}
if m.Status != "" && m.Status != d.Status {
d.Status = m.Status
updated = true
}
if m.AssetType != "" && m.AssetType != d.AssetType {
d.AssetType = m.AssetType
updated = true
}
if m.Trades != nil {
for x := range m.Trades {
var found bool
for y := range d.Trades {
if d.Trades[y].TID != m.Trades[x].TID {
continue
}
found = true
if d.Trades[y].Fee != m.Trades[x].Fee {
d.Trades[y].Fee = m.Trades[x].Fee
updated = true
}
if m.Trades[x].Price != 0 && d.Trades[y].Price != m.Trades[x].Price {
d.Trades[y].Price = m.Trades[x].Price
updated = true
}
if d.Trades[y].Side != m.Trades[x].Side {
d.Trades[y].Side = m.Trades[x].Side
updated = true
}
if d.Trades[y].Type != m.Trades[x].Type {
d.Trades[y].Type = m.Trades[x].Type
updated = true
}
if d.Trades[y].Description != m.Trades[x].Description {
d.Trades[y].Description = m.Trades[x].Description
updated = true
}
if m.Trades[x].Amount != 0 && d.Trades[y].Amount != m.Trades[x].Amount {
d.Trades[y].Amount = m.Trades[x].Amount
updated = true
}
if d.Trades[y].Timestamp != m.Trades[x].Timestamp {
d.Trades[y].Timestamp = m.Trades[x].Timestamp
updated = true
}
if d.Trades[y].IsMaker != m.Trades[x].IsMaker {
d.Trades[y].IsMaker = m.Trades[x].IsMaker
updated = true
}
}
if !found {
d.Trades = append(d.Trades, m.Trades[x])
updated = true
}
m.RemainingAmount -= m.Trades[x].Amount
}
}
if m.RemainingAmount > 0 && m.RemainingAmount != d.RemainingAmount {
d.RemainingAmount = m.RemainingAmount
updated = true
}
if updated {
if d.LastUpdated == m.LastUpdated {
d.LastUpdated = time.Now()
} else {
d.LastUpdated = m.LastUpdated
}
}
}
// UpdateOrderFromModify Will update an order detail (used in order management)
// by comparing passed in and existing values
func (d *Detail) UpdateOrderFromModify(m *Modify) {
var updated bool
if d.ImmediateOrCancel != m.ImmediateOrCancel {
d.ImmediateOrCancel = m.ImmediateOrCancel
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
}
if m.Amount > 0 && m.Amount != d.Amount {
d.Amount = m.Amount
updated = true
}
if m.LimitPriceUpper > 0 && m.LimitPriceUpper != d.LimitPriceUpper {
d.LimitPriceUpper = m.LimitPriceUpper
updated = true
}
if m.LimitPriceLower > 0 && m.LimitPriceLower != d.LimitPriceLower {
d.LimitPriceLower = m.LimitPriceLower
updated = true
}
if m.TriggerPrice > 0 && m.TriggerPrice != d.TriggerPrice {
d.TriggerPrice = m.TriggerPrice
updated = true
}
if m.TargetAmount > 0 && m.TargetAmount != d.TargetAmount {
d.TargetAmount = m.TargetAmount
updated = true
}
if m.ExecutedAmount > 0 && m.ExecutedAmount != d.ExecutedAmount {
d.ExecutedAmount = m.ExecutedAmount
updated = true
}
if m.Fee > 0 && m.Fee != d.Fee {
d.Fee = m.Fee
updated = true
}
if m.AccountID != "" && m.AccountID != d.AccountID {
d.AccountID = m.AccountID
updated = true
}
if m.PostOnly != d.PostOnly {
d.PostOnly = m.PostOnly
updated = true
}
if !m.Pair.IsEmpty() && m.Pair != d.Pair {
d.Pair = m.Pair
updated = true
}
if m.Leverage != 0 && m.Leverage != d.Leverage {
d.Leverage = m.Leverage
updated = true
}
if m.ClientID != "" && m.ClientID != d.ClientID {
d.ClientID = m.ClientID
updated = true
}
if m.WalletAddress != "" && m.WalletAddress != d.WalletAddress {
d.WalletAddress = m.WalletAddress
updated = true
}
if m.Type != "" && m.Type != d.Type {
d.Type = m.Type
updated = true
}
if m.Side != "" && m.Side != d.Side {
d.Side = m.Side
updated = true
}
if m.Status != "" && m.Status != d.Status {
d.Status = m.Status
updated = true
}
if m.AssetType != "" && m.AssetType != d.AssetType {
d.AssetType = m.AssetType
updated = true
}
if m.Trades != nil {
for x := range m.Trades {
var found bool
for y := range d.Trades {
if d.Trades[y].TID != m.Trades[x].TID {
continue
}
found = true
if d.Trades[y].Fee != m.Trades[x].Fee {
d.Trades[y].Fee = m.Trades[x].Fee
updated = true
}
if m.Trades[x].Price != 0 && d.Trades[y].Price != m.Trades[x].Price {
d.Trades[y].Price = m.Trades[x].Price
updated = true
}
if d.Trades[y].Side != m.Trades[x].Side {
d.Trades[y].Side = m.Trades[x].Side
updated = true
}
if d.Trades[y].Type != m.Trades[x].Type {
d.Trades[y].Type = m.Trades[x].Type
updated = true
}
if d.Trades[y].Description != m.Trades[x].Description {
d.Trades[y].Description = m.Trades[x].Description
updated = true
}
if m.Trades[x].Amount != 0 && d.Trades[y].Amount != m.Trades[x].Amount {
d.Trades[y].Amount = m.Trades[x].Amount
updated = true
}
if d.Trades[y].Timestamp != m.Trades[x].Timestamp {
d.Trades[y].Timestamp = m.Trades[x].Timestamp
updated = true
}
if d.Trades[y].IsMaker != m.Trades[x].IsMaker {
d.Trades[y].IsMaker = m.Trades[x].IsMaker
updated = true
}
}
if !found {
d.Trades = append(d.Trades, m.Trades[x])
updated = true
}
m.RemainingAmount -= m.Trades[x].Amount
}
}
if m.RemainingAmount > 0 && m.RemainingAmount != d.RemainingAmount {
d.RemainingAmount = m.RemainingAmount
updated = true
}
if updated {
if d.LastUpdated == m.LastUpdated {
d.LastUpdated = time.Now()
} else {
d.LastUpdated = m.LastUpdated
}
}
}
// String implements the stringer interface
func (t Type) String() string {
return string(t)
}
// Lower returns the type lower case string
func (t Type) Lower() string {
return strings.ToLower(string(t))
}
// Title returns the type titleized, eg "Limit"
func (t Type) Title() string {
return strings.Title(strings.ToLower(string(t)))
}
// String implements the stringer interface
func (s Side) String() string {
return string(s)
}
// Lower returns the side lower case string
func (s Side) Lower() string {
return strings.ToLower(string(s))
}
// Title returns the side titleized, eg "Buy"
func (s Side) Title() string {
return strings.Title(strings.ToLower(string(s)))
}
// String implements the stringer interface
func (s Status) String() string {
return string(s)
}
// FilterOrdersBySide removes any order details that don't match the
// order status provided
func FilterOrdersBySide(orders *[]Detail, side Side) {
if side == "" || side == AnySide {
return
}
var filteredOrders []Detail
for i := range *orders {
if strings.EqualFold(string((*orders)[i].Side), string(side)) {
filteredOrders = append(filteredOrders, (*orders)[i])
}
}
*orders = filteredOrders
}
// FilterOrdersByType removes any order details that don't match the order type
// provided
func FilterOrdersByType(orders *[]Detail, orderType Type) {
if orderType == "" || orderType == AnyType {
return
}
var filteredOrders []Detail
for i := range *orders {
if strings.EqualFold(string((*orders)[i].Type), string(orderType)) {
filteredOrders = append(filteredOrders, (*orders)[i])
}
}
*orders = filteredOrders
}
// FilterOrdersByTickRange removes any OrderDetails outside of the tick range
func FilterOrdersByTickRange(orders *[]Detail, startTicks, endTicks time.Time) {
if startTicks.IsZero() ||
endTicks.IsZero() ||
startTicks.Unix() == 0 ||
endTicks.Unix() == 0 ||
endTicks.Before(startTicks) {
return
}
var filteredOrders []Detail
for i := range *orders {
if (*orders)[i].Date.Unix() >= startTicks.Unix() &&
(*orders)[i].Date.Unix() <= endTicks.Unix() {
filteredOrders = append(filteredOrders, (*orders)[i])
}
}
*orders = filteredOrders
}
// FilterOrdersByCurrencies removes any order details that do not match the
// provided currency list. It is forgiving in that the provided currencies can
// match quote or base currencies
func FilterOrdersByCurrencies(orders *[]Detail, currencies []currency.Pair) {
if len(currencies) == 0 {
return
}
var filteredOrders []Detail
for i := range *orders {
matchFound := false
for _, c := range currencies {
if !matchFound && (*orders)[i].Pair.EqualIncludeReciprocal(c) {
matchFound = true
}
}
if matchFound {
filteredOrders = append(filteredOrders, (*orders)[i])
}
}
*orders = filteredOrders
}
func (b ByPrice) Len() int {
return len(b)
}
func (b ByPrice) Less(i, j int) bool {
return b[i].Price < b[j].Price
}
func (b ByPrice) Swap(i, j int) {
b[i], b[j] = b[j], b[i]
}
// SortOrdersByPrice the caller function to sort orders
func SortOrdersByPrice(orders *[]Detail, reverse bool) {
if reverse {
sort.Sort(sort.Reverse(ByPrice(*orders)))
} else {
sort.Sort(ByPrice(*orders))
}
}
func (b ByOrderType) Len() int {
return len(b)
}
func (b ByOrderType) Less(i, j int) bool {
return b[i].Type.String() < b[j].Type.String()
}
func (b ByOrderType) Swap(i, j int) {
b[i], b[j] = b[j], b[i]
}
// SortOrdersByType the caller function to sort orders
func SortOrdersByType(orders *[]Detail, reverse bool) {
if reverse {
sort.Sort(sort.Reverse(ByOrderType(*orders)))
} else {
sort.Sort(ByOrderType(*orders))
}
}
func (b ByCurrency) Len() int {
return len(b)
}
func (b ByCurrency) Less(i, j int) bool {
return b[i].Pair.String() < b[j].Pair.String()
}
func (b ByCurrency) Swap(i, j int) {
b[i], b[j] = b[j], b[i]
}
// SortOrdersByCurrency the caller function to sort orders
func SortOrdersByCurrency(orders *[]Detail, reverse bool) {
if reverse {
sort.Sort(sort.Reverse(ByCurrency(*orders)))
} else {
sort.Sort(ByCurrency(*orders))
}
}
func (b ByDate) Len() int {
return len(b)
}
func (b ByDate) Less(i, j int) bool {
return b[i].Date.Unix() < b[j].Date.Unix()
}
func (b ByDate) Swap(i, j int) {
b[i], b[j] = b[j], b[i]
}
// SortOrdersByDate the caller function to sort orders
func SortOrdersByDate(orders *[]Detail, reverse bool) {
if reverse {
sort.Sort(sort.Reverse(ByDate(*orders)))
} else {
sort.Sort(ByDate(*orders))
}
}
func (b ByOrderSide) Len() int {
return len(b)
}
func (b ByOrderSide) Less(i, j int) bool {
return b[i].Side.String() < b[j].Side.String()
}
func (b ByOrderSide) Swap(i, j int) {
b[i], b[j] = b[j], b[i]
}
// SortOrdersBySide the caller function to sort orders
func SortOrdersBySide(orders *[]Detail, reverse bool) {
if reverse {
sort.Sort(sort.Reverse(ByOrderSide(*orders)))
} else {
sort.Sort(ByOrderSide(*orders))
}
}
// StringToOrderSide for converting case insensitive order side
// and returning a real Side
func StringToOrderSide(side string) (Side, error) {
switch {
case strings.EqualFold(side, Buy.String()):
return Buy, nil
case strings.EqualFold(side, Sell.String()):
return Sell, nil
case strings.EqualFold(side, Bid.String()):
return Bid, nil
case strings.EqualFold(side, Ask.String()):
return Ask, nil
case strings.EqualFold(side, AnySide.String()):
return AnySide, nil
default:
return UnknownSide, errors.New(side + " not recognised as order side")
}
}
// StringToOrderType for converting case insensitive order type
// and returning a real Type
func StringToOrderType(oType string) (Type, error) {
switch {
case strings.EqualFold(oType, Limit.String()),
strings.EqualFold(oType, "EXCHANGE LIMIT"):
return Limit, nil
case strings.EqualFold(oType, Market.String()),
strings.EqualFold(oType, "EXCHANGE MARKET"):
return Market, nil
case strings.EqualFold(oType, ImmediateOrCancel.String()),
strings.EqualFold(oType, "immediate or cancel"),
strings.EqualFold(oType, "IOC"),
strings.EqualFold(oType, "EXCHANGE IOC"):
return ImmediateOrCancel, nil
case strings.EqualFold(oType, Stop.String()),
strings.EqualFold(oType, "stop loss"),
strings.EqualFold(oType, "stop_loss"),
strings.EqualFold(oType, "EXCHANGE STOP"):
return Stop, nil
case strings.EqualFold(oType, StopLimit.String()),
strings.EqualFold(oType, "EXCHANGE STOP LIMIT"):
return StopLimit, nil
case strings.EqualFold(oType, TrailingStop.String()),
strings.EqualFold(oType, "trailing stop"),
strings.EqualFold(oType, "EXCHANGE TRAILING STOP"):
return TrailingStop, nil
case strings.EqualFold(oType, FillOrKill.String()),
strings.EqualFold(oType, "EXCHANGE FOK"):
return FillOrKill, nil
case strings.EqualFold(oType, IOS.String()):
return IOS, nil
case strings.EqualFold(oType, PostOnly.String()):
return PostOnly, nil
case strings.EqualFold(oType, AnyType.String()):
return AnyType, nil
case strings.EqualFold(oType, Trigger.String()):
return Trigger, nil
default:
return UnknownType, errors.New(oType + " not recognised as order type")
}
}
// StringToOrderStatus for converting case insensitive order status
// and returning a real Status
func StringToOrderStatus(status string) (Status, error) {
switch {
case strings.EqualFold(status, AnyStatus.String()):
return AnyStatus, nil
case strings.EqualFold(status, New.String()),
strings.EqualFold(status, "placed"):
return New, nil
case strings.EqualFold(status, Active.String()):
return Active, nil
case strings.EqualFold(status, PartiallyFilled.String()),
strings.EqualFold(status, "partially matched"),
strings.EqualFold(status, "partially filled"):
return PartiallyFilled, nil
case strings.EqualFold(status, Filled.String()),
strings.EqualFold(status, "fully matched"),
strings.EqualFold(status, "fully filled"):
return Filled, nil
case strings.EqualFold(status, PartiallyCancelled.String()),
strings.EqualFold(status, "partially cancelled"):
return PartiallyCancelled, nil
case strings.EqualFold(status, Open.String()):
return Open, nil
case strings.EqualFold(status, Closed.String()):
return Closed, nil
case strings.EqualFold(status, Cancelled.String()):
return Cancelled, nil
case strings.EqualFold(status, "CANCELED"): // Kraken case
return Cancelled, nil
case strings.EqualFold(status, PendingCancel.String()),
strings.EqualFold(status, "pending cancel"),
strings.EqualFold(status, "pending cancellation"):
return PendingCancel, nil
case strings.EqualFold(status, Rejected.String()):
return Rejected, nil
case strings.EqualFold(status, Expired.String()):
return Expired, nil
case strings.EqualFold(status, Hidden.String()):
return Hidden, nil
case strings.EqualFold(status, InsufficientBalance.String()):
return InsufficientBalance, nil
case strings.EqualFold(status, MarketUnavailable.String()):
return MarketUnavailable, nil
default:
return UnknownStatus, errors.New(status + " not recognised as order status")
}
}
func (o *ClassificationError) Error() string {
if o.OrderID != "" {
return fmt.Sprintf("%s - OrderID: %s classification error: %v",
o.Exchange,
o.OrderID,
o.Err)
}
return fmt.Sprintf("%s - classification error: %v",
o.Exchange,
o.Err)
}
// StandardCancel defines an option in the validator to make sure an ID is set
// for a standard cancel
func (c *Cancel) StandardCancel() validate.Checker {
return validate.Check(func() error {
if c.ID == "" {
return errors.New("ID not set")
}
return nil
})
}
// Validate checks internal struct requirements
func (c *Cancel) Validate(opt ...validate.Checker) error {
if c == nil {
return ErrCancelOrderIsNil
}
if c.Pair.IsEmpty() {
return ErrPairIsEmpty
}
if c.AssetType == "" {
return ErrAssetNotSet
}
var errs common.Errors
for _, o := range opt {
err := o.Check()
if err != nil {
errs = append(errs, err)
}
}
if errs != nil {
return errs
}
return nil
}
// Validate checks internal struct requirements
func (g *GetOrdersRequest) Validate(opt ...validate.Checker) error {
if g == nil {
return ErrGetOrdersRequestIsNil
}
if !g.AssetType.IsValid() {
return fmt.Errorf("assetType %v not supported", g.AssetType)
}
var errs common.Errors
for _, o := range opt {
err := o.Check()
if err != nil {
errs = append(errs, err)
}
}
if errs != nil {
return errs
}
return nil
}
// Validate checks internal struct requirements
func (m *Modify) Validate(opt ...validate.Checker) error {
if m == nil {
return ErrModifyOrderIsNil
}
if m.Pair.IsEmpty() {
return ErrPairIsEmpty
}
if m.AssetType.String() == "" {
return ErrAssetNotSet
}
var errs common.Errors
for _, o := range opt {
err := o.Check()
if err != nil {
errs = append(errs, err)
}
}
if errs != nil {
return errs
}
if m.ClientOrderID == "" && m.ID == "" {
return ErrOrderIDNotSet
}
return nil
}