mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 23:16:45 +00:00
* BTSE: Fix duplicate error on Million pairs (M_*) BTSE has listed Pitbull token with two symbols: PIT-USD and M_PIT-USD for millons of PIT / USD. The native token is not tradable, so we ignore them and get a base of M_PIT because that's what later APIs will accept * BTSE: Fix test errors on locked market * Common: Improve AppendError and ExcludeError This change switches from a stateful multiError to caring more about the Unwrap() []error interface, the same as [go standard lib](https://github.com/golang/go/blob/go1.21.4/src/errors/wrap.go#L54-L68) Notably, if we implement Unwrap() []error and do NOT implement Is() then we get free compatibility with the core functions. The only distateful thing here is needing to deeply unwrap fmt.Errorf errors, since they don't flatten. I can't see any way around that * Pairs: Fix exchange config Pairs loading When a pair string contained two punctuation runes, the first one is used, and the configFormat is ignored. This fix checks the list and corrects any with the wrong delimiter, or errors if the format is inconsistent. * BTSE: Fix all tickers retrieved by GetTicker PR #764 introduced GetTickers, but it wasn't rolled out to BTSE. This fix ensures that when one ticker is a locked market, the rest continue to function. Particularly important if the locked market wasn't even enabled anyway. * Kucoin: Fix test config future pairs * BTSE: Remove PIT tests; Token removed BTSE have removed the PIT token pairs All these changes stand, and this just removes the test * ITBit: Fix fatal error on second run This fix removes incorrect config pair delimiter, because it would be re-inserted into config the first run, and then error the second time. This delimiter doesn't match the config we have. There's no implementation of fetching pairs, so what's in config files now is all that matters * Engine: Fix TestConfigAllJsonResponse * Clarity of non-matching json improved * Handling for fixing pair delimiters
241 lines
7.6 KiB
Go
241 lines
7.6 KiB
Go
package currency
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"unicode"
|
|
)
|
|
|
|
// EMPTYFORMAT defines an empty pair format
|
|
var EMPTYFORMAT = PairFormat{}
|
|
|
|
// ErrCurrencyNotAssociatedWithPair defines an error where a currency is not
|
|
// associated with a pair.
|
|
var ErrCurrencyNotAssociatedWithPair = errors.New("currency not associated with pair")
|
|
|
|
// String returns a currency pair string
|
|
func (p Pair) String() string {
|
|
return p.Base.String() + p.Delimiter + p.Quote.String()
|
|
}
|
|
|
|
// Lower converts the pair object to lowercase
|
|
func (p Pair) Lower() Pair {
|
|
p.Base = p.Base.Lower()
|
|
p.Quote = p.Quote.Lower()
|
|
return p
|
|
}
|
|
|
|
// Upper converts the pair object to uppercase
|
|
func (p Pair) Upper() Pair {
|
|
p.Base = p.Base.Upper()
|
|
p.Quote = p.Quote.Upper()
|
|
return p
|
|
}
|
|
|
|
// UnmarshalJSON implements json.Unmarshaler
|
|
func (p *Pair) UnmarshalJSON(d []byte) error {
|
|
var pair string
|
|
err := json.Unmarshal(d, &pair)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if pair == "" {
|
|
*p = EMPTYPAIR
|
|
return nil
|
|
}
|
|
|
|
// Check if pair is in the format of BTC-USD
|
|
for x := range pair {
|
|
if unicode.IsPunct(rune(pair[x])) {
|
|
p.Base = NewCode(pair[:x])
|
|
p.Delimiter = string(pair[x])
|
|
p.Quote = NewCode(pair[x+1:])
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// NOTE: Pair string could be in format DUSKUSDT (Kucoin) which will be
|
|
// incorrectly converted to DUS-KUSDT, ELKRW (Bithumb) which will convert
|
|
// converted to ELK-RW and HTUSDT (Lbank) which will be incorrectly
|
|
// converted to HTU-SDT.
|
|
return fmt.Errorf("%w from %s cannot ensure pair is in correct format, please use exchange method MatchSymbolWithAvailablePairs", errCannotCreatePair, pair)
|
|
}
|
|
|
|
// MarshalJSON conforms type to the marshaler interface
|
|
func (p Pair) MarshalJSON() ([]byte, error) {
|
|
return json.Marshal(p.String())
|
|
}
|
|
|
|
// Format changes the currency based on user preferences overriding the default
|
|
// String() display
|
|
func (p Pair) Format(pf PairFormat) Pair {
|
|
p.Delimiter = pf.Delimiter
|
|
if pf.Uppercase {
|
|
return p.Upper()
|
|
}
|
|
return p.Lower()
|
|
}
|
|
|
|
// Equal compares two currency pairs and returns whether or not they are equal
|
|
func (p Pair) Equal(cPair Pair) bool {
|
|
return p.Base.Equal(cPair.Base) && p.Quote.Equal(cPair.Quote)
|
|
}
|
|
|
|
// EqualIncludeReciprocal compares two currency pairs and returns whether or not
|
|
// they are the same including reciprocal currencies.
|
|
func (p Pair) EqualIncludeReciprocal(cPair Pair) bool {
|
|
return (p.Base.Equal(cPair.Base) && p.Quote.Equal(cPair.Quote)) ||
|
|
(p.Base.Equal(cPair.Quote) && p.Quote.Equal(cPair.Base))
|
|
}
|
|
|
|
// IsCryptoPair checks to see if the pair is a crypto pair e.g. BTCLTC
|
|
func (p Pair) IsCryptoPair() bool {
|
|
return p.Base.IsCryptocurrency() && p.Quote.IsCryptocurrency()
|
|
}
|
|
|
|
// IsCryptoFiatPair checks to see if the pair is a crypto fiat pair e.g. BTCUSD
|
|
func (p Pair) IsCryptoFiatPair() bool {
|
|
return (p.Base.IsCryptocurrency() && p.Quote.IsFiatCurrency()) ||
|
|
(p.Base.IsFiatCurrency() && p.Quote.IsCryptocurrency())
|
|
}
|
|
|
|
// IsFiatPair checks to see if the pair is a fiat pair e.g. EURUSD
|
|
func (p Pair) IsFiatPair() bool {
|
|
return p.Base.IsFiatCurrency() && p.Quote.IsFiatCurrency()
|
|
}
|
|
|
|
// IsCryptoStablePair checks to see if the pair is a crypto stable pair e.g.
|
|
// LTC-USDT
|
|
func (p Pair) IsCryptoStablePair() bool {
|
|
return (p.Base.IsCryptocurrency() && p.Quote.IsStableCurrency()) ||
|
|
(p.Base.IsStableCurrency() && p.Quote.IsCryptocurrency())
|
|
}
|
|
|
|
// IsStablePair checks to see if the pair is a stable pair e.g. USDT-DAI
|
|
func (p Pair) IsStablePair() bool {
|
|
return p.Base.IsStableCurrency() && p.Quote.IsStableCurrency()
|
|
}
|
|
|
|
// IsInvalid checks invalid pair if base and quote are the same
|
|
func (p Pair) IsInvalid() bool {
|
|
return p.Base.Equal(p.Quote)
|
|
}
|
|
|
|
// Swap turns the currency pair into its reciprocal
|
|
func (p Pair) Swap() Pair {
|
|
return Pair{Base: p.Quote, Quote: p.Base}
|
|
}
|
|
|
|
// IsEmpty returns whether or not the pair is empty or is missing a currency
|
|
// code
|
|
func (p Pair) IsEmpty() bool {
|
|
return p.Base.IsEmpty() && p.Quote.IsEmpty()
|
|
}
|
|
|
|
// Contains checks to see if a pair contains a specific currency
|
|
func (p Pair) Contains(c Code) bool {
|
|
return p.Base.Equal(c) || p.Quote.Equal(c)
|
|
}
|
|
|
|
// Len derives full length for match exclusion.
|
|
func (p Pair) Len() int {
|
|
return len(p.Base.String()) + len(p.Quote.String())
|
|
}
|
|
|
|
// Other returns the other currency from pair, if not matched returns empty code.
|
|
func (p Pair) Other(c Code) (Code, error) {
|
|
if p.Base.Equal(c) {
|
|
return p.Quote, nil
|
|
}
|
|
if p.Quote.Equal(c) {
|
|
return p.Base, nil
|
|
}
|
|
return EMPTYCODE, ErrCurrencyCodeEmpty
|
|
}
|
|
|
|
// IsPopulated returns true if the currency pair have both non-empty values for
|
|
// base and quote.
|
|
func (p Pair) IsPopulated() bool {
|
|
return !p.Base.IsEmpty() && !p.Quote.IsEmpty()
|
|
}
|
|
|
|
// MarketSellOrderParameters returns order parameters for when you want to sell
|
|
// a currency which purchases another currency. This specifically returns what
|
|
// liquidity side you will be affecting, what order side you will be placing and
|
|
// what currency you will be purchasing.
|
|
func (p Pair) MarketSellOrderParameters(wantingToSell Code) (*OrderParameters, error) {
|
|
return p.getOrderParameters(wantingToSell, true, true)
|
|
}
|
|
|
|
// MarketBuyOrderParameters returns order parameters for when you want to sell a
|
|
// currency which purchases another currency. This specifically returns what
|
|
// liquidity side you will be affecting, what order side you will be placing and
|
|
// what currency you will be purchasing.
|
|
func (p Pair) MarketBuyOrderParameters(wantingToBuy Code) (*OrderParameters, error) {
|
|
return p.getOrderParameters(wantingToBuy, false, true)
|
|
}
|
|
|
|
// LimitSellOrderParameters returns order parameters for when you want to sell a
|
|
// currency which purchases another currency. This specifically returns what
|
|
// liquidity side you will be affecting, what order side you will be placing and
|
|
// what currency you will be purchasing.
|
|
func (p Pair) LimitSellOrderParameters(wantingToSell Code) (*OrderParameters, error) {
|
|
return p.getOrderParameters(wantingToSell, true, false)
|
|
}
|
|
|
|
// LimitBuyOrderParameters returns order parameters for when you want to
|
|
// sell a currency which purchases another currency. This specifically returns
|
|
// what liquidity side you will be affecting, what order side you will be
|
|
// placing and what currency you will be purchasing.
|
|
func (p Pair) LimitBuyOrderParameters(wantingToBuy Code) (*OrderParameters, error) {
|
|
return p.getOrderParameters(wantingToBuy, false, false)
|
|
}
|
|
|
|
// getOrderDecisionDetails returns order parameters for the currency pair using
|
|
// the provided currency code, whether or not you are selling and whether or not
|
|
// you are placing a market order.
|
|
func (p Pair) getOrderParameters(c Code, selling, market bool) (*OrderParameters, error) {
|
|
if !p.IsPopulated() {
|
|
return nil, ErrCurrencyPairEmpty
|
|
}
|
|
if c.IsEmpty() {
|
|
return nil, ErrCurrencyCodeEmpty
|
|
}
|
|
params := OrderParameters{}
|
|
switch {
|
|
case p.Base.Equal(c):
|
|
if selling {
|
|
params.SellingCurrency = p.Base
|
|
params.PurchasingCurrency = p.Quote
|
|
params.IsAskLiquidity = !market
|
|
} else {
|
|
params.SellingCurrency = p.Quote
|
|
params.PurchasingCurrency = p.Base
|
|
params.IsBuySide = true
|
|
params.IsAskLiquidity = market
|
|
}
|
|
case p.Quote.Equal(c):
|
|
if selling {
|
|
params.SellingCurrency = p.Quote
|
|
params.PurchasingCurrency = p.Base
|
|
params.IsBuySide = true
|
|
params.IsAskLiquidity = market
|
|
} else {
|
|
params.SellingCurrency = p.Base
|
|
params.PurchasingCurrency = p.Quote
|
|
params.IsAskLiquidity = !market
|
|
}
|
|
default:
|
|
return nil, fmt.Errorf("%w %v: %v", ErrCurrencyNotAssociatedWithPair, c, p)
|
|
}
|
|
params.Pair = p
|
|
return ¶ms, nil
|
|
}
|
|
|
|
// IsAssociated checks to see if the pair is associated with another pair
|
|
func (p Pair) IsAssociated(a Pair) bool {
|
|
return p.Base.Equal(a.Base) || p.Quote.Equal(a.Base) || p.Base.Equal(a.Quote) || p.Quote.Equal(a.Quote)
|
|
}
|