Files
gocryptotrader/exchanges/poloniex/currency_details.go
Ryan O'Hara-Reid ca87ddf825 poloniex: websocket update (#659)
* poloniex: websocket update with debug output and currency tracking system

* linter: fix issues

* nits: Addr

* poloniex: govet fix

* nits: addr

* Bittrex: Fix fee test
2021-04-27 12:05:10 +10:00

236 lines
5.9 KiB
Go

package poloniex
import (
"errors"
"strconv"
"sync"
"github.com/thrasher-corp/gocryptotrader/currency"
)
// CurrencyDetails stores a map of currencies associated with their ID
type CurrencyDetails struct {
pairs map[float64]PairSummaryInfo
codes map[float64]CodeSummaryInfo
// Mutex used for future when we will periodically update this table every
// week or so in production
m sync.RWMutex
}
// PairSummaryInfo defines currency pair information
type PairSummaryInfo struct {
Pair currency.Pair
IsFrozen bool
PostOnly bool
}
// CodeSummaryInfo defines currency information
type CodeSummaryInfo struct {
Currency currency.Code
WithdrawalTXFee float64
MinimumConfirmations int64
DepositAddress string
WithdrawalDepositDisabled bool
Frozen bool
}
var (
errCannotLoadNoData = errors.New("cannot load websocket currency data as data is nil")
errNoDepositAddress = errors.New("no public deposit address for currency")
errPairMapIsNil = errors.New("cannot get currency pair, map is nil")
errCodeMapIsNil = errors.New("cannot get currency code, map is nil")
errCurrencyNotFoundInMap = errors.New("currency not found")
)
// loadPairs loads currency pair associations with unique identifiers from
// ticker data map
func (w *CurrencyDetails) loadPairs(data map[string]Ticker) error {
if data == nil {
return errCannotLoadNoData
}
w.m.Lock()
defer w.m.Unlock()
for k, v := range data {
pair, err := currency.NewPairFromString(k)
if err != nil {
return err
}
if w.pairs == nil {
w.pairs = make(map[float64]PairSummaryInfo)
}
w.pairs[v.ID] = PairSummaryInfo{
Pair: pair,
IsFrozen: v.IsFrozen == 1,
PostOnly: v.PostOnly == 1,
}
}
return nil
}
// loadCodes loads currency codes from currency map
func (w *CurrencyDetails) loadCodes(data map[string]Currencies) error {
if data == nil {
return errCannotLoadNoData
}
w.m.Lock()
defer w.m.Unlock()
for k, v := range data {
if v.Delisted == 1 {
continue
}
if w.codes == nil {
w.codes = make(map[float64]CodeSummaryInfo)
}
w.codes[v.ID] = CodeSummaryInfo{
Currency: currency.NewCode(k),
WithdrawalTXFee: v.TxFee,
MinimumConfirmations: v.MinConfirmations,
DepositAddress: v.DepositAddress,
WithdrawalDepositDisabled: v.WithdrawalDepositDisabled == 1,
Frozen: v.Frozen == 1,
}
}
return nil
}
// GetPair returns a currency pair by its ID
func (w *CurrencyDetails) GetPair(id float64) (currency.Pair, error) {
w.m.RLock()
defer w.m.RUnlock()
if w.pairs == nil {
return currency.Pair{}, errPairMapIsNil
}
p, ok := w.pairs[id]
if ok {
return p.Pair, nil
}
// This is here so we can still log an order with the ID as the currency
// pair which you can then cross reference later with the exchange ID list,
// rather than error out.
op, err := currency.NewPairFromString(strconv.FormatFloat(id, 'f', -1, 64))
if err != nil {
return op, err
}
return op, errIDNotFoundInPairMap
}
// GetCode returns a currency code by its ID
func (w *CurrencyDetails) GetCode(id float64) (currency.Code, error) {
w.m.RLock()
defer w.m.RUnlock()
if w.codes == nil {
return currency.Code{}, errCodeMapIsNil
}
c, ok := w.codes[id]
if ok {
return c.Currency, nil
}
return currency.Code{}, errIDNotFoundInCodeMap
}
// GetWithdrawalTXFee returns withdrawal transaction fee for the currency
func (w *CurrencyDetails) GetWithdrawalTXFee(c currency.Code) (float64, error) {
w.m.RLock()
defer w.m.RUnlock()
if w.codes == nil {
return 0, errCodeMapIsNil
}
for _, v := range w.codes {
if v.Currency == c {
return v.WithdrawalTXFee, nil
}
}
return 0, errCurrencyNotFoundInMap
}
// GetDepositAddress returns the public deposit address details for the currency
func (w *CurrencyDetails) GetDepositAddress(c currency.Code) (string, error) {
w.m.RLock()
defer w.m.RUnlock()
if w.codes == nil {
return "", errCodeMapIsNil
}
for _, v := range w.codes {
if v.Currency == c {
if v.DepositAddress == "" {
return "", errNoDepositAddress
}
return v.DepositAddress, nil
}
}
return "", errCurrencyNotFoundInMap
}
// IsWithdrawAndDepositsEnabled returns if withdrawals or deposits are enabled
func (w *CurrencyDetails) IsWithdrawAndDepositsEnabled(c currency.Code) (bool, error) {
w.m.RLock()
defer w.m.RUnlock()
if w.codes == nil {
return false, errCodeMapIsNil
}
for _, v := range w.codes {
if v.Currency == c {
return !v.WithdrawalDepositDisabled, nil
}
}
return false, errCurrencyNotFoundInMap
}
// IsTradingEnabledForCurrency returns if the currency is allowed to be traded
func (w *CurrencyDetails) IsTradingEnabledForCurrency(c currency.Code) (bool, error) {
w.m.RLock()
defer w.m.RUnlock()
if w.codes == nil {
return false, errCodeMapIsNil
}
for _, v := range w.codes {
if v.Currency == c {
return !v.Frozen, nil
}
}
return false, errCurrencyNotFoundInMap
}
// IsTradingEnabledForPair returns if the currency pair is allowed to be traded
func (w *CurrencyDetails) IsTradingEnabledForPair(pair currency.Pair) (bool, error) {
w.m.RLock()
defer w.m.RUnlock()
if w.codes == nil {
return false, errCodeMapIsNil
}
for _, v := range w.pairs {
if v.Pair.Equal(pair) {
return !v.IsFrozen, nil
}
}
return false, errCurrencyNotFoundInMap
}
// IsPostOnlyForPair returns if an order is allowed to take liquidity from the
// books or reduce positions
func (w *CurrencyDetails) IsPostOnlyForPair(pair currency.Pair) (bool, error) {
w.m.RLock()
defer w.m.RUnlock()
if w.codes == nil {
return false, errCodeMapIsNil
}
for _, v := range w.pairs {
if v.Pair.Equal(pair) {
return v.PostOnly, nil
}
}
return false, errCurrencyNotFoundInMap
}
// isInitial checks state of maps to determine if they have been loaded or not
func (w *CurrencyDetails) isInitial() bool {
w.m.RLock()
defer w.m.RUnlock()
return w.codes == nil || w.pairs == nil
}