mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 23:16:45 +00:00
* initial concept of a nice validation tester for exchanges * adds some datahandler design * expand testing * more tests and fixes * minor end of day fix for bithumb * fixes implementation issues * more test coverage and improvements, but not sure if i should continue * fix more wrapper implementations * adds error type, more fixes * changes signature, fixes implementations * fixes more wrapper implementations * one more bit * more cleanup * WOW things work? * lintle 1/1337 * mini bump * fixes all linting * neaten * GetOrderInfo+ asset pair fixes+improvements * adds new websocket test * expand ws testing * fix bug, expand tests, improve implementation * code coverage of a lot of new codes * fixes everything * reverts accidental changes * minor fixes from reviewing code * removes Bitfinex cancelBatchOrder implementation * fixes dumb baby typo for babies * mini nit fixes * so many nits to address * addresses all the nits * Titlecase * switcheroo * removes websocket testing for now * fix appveyor, minor test fix * fixes typo, re-kindles killed kode * skip binance wrapper tests when running CI * expired context, huobi okx fixes * kodespull * fix ordering * time fix because why not * fix exmo, others * hopefully this fixes all of my life's problems * last thing today * huobi, more like hypotrophy * golangci-lint, more like mypooroldknee-splint * fix huobi times by removing them * should fix okx currency issues * blocks the application * adds last little contingency for pairs * addresses most nits and new problems * lovely fixed before seeing why okx sucks * fixes issues with okx websocket * the classic receieieivaier * lintle * adds test and fixes existing tests * expands error handling messages during setup * fixes dumb okx bugs introduced * quick fix for lint and exmo * fixes nixes * fix exmo deposit issue * lint * fixes issue with extra asset runs missing * fix surprise race * all the lint and merge fixes * fixes surprise bugs in OKx * fixes issues with times and chains * fixing all the merge stuff * merge fix * rm logs and a panic potential * lovely lint lament * an easy demonstration of scenario, but not of initial purpose * put it in the bin * Revert "put it in the bin" This reverts commit 15c6490f713233d43f10957367fcbf18e3818bdd. * re-add after immediate error popup * fix mini poor test design * okx okay * merge fixes * fixes issues discovered in lovely test * I FORGOT TO COMMIT THIS * nit fixaroonaboo * forgoetten test fix * revert old okx asset intrument work * fixes * revert problems I didnt understand. update bybit * fix merge bugs * test cleanup * further improvements * reshuffle and lint * rm redundant CI_TEST by rm the CI_TEST field that is redundant * path fix * move to its own section, dont run on 32 bit + appveyor * lint * fix lbank * address nits * let it rip * fix failing test time range * niteroo boogaloo * mod tidy, use common.SimpleTimeFormat
313 lines
7.5 KiB
Go
313 lines
7.5 KiB
Go
package currencystate
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"sync"
|
|
|
|
"github.com/thrasher-corp/gocryptotrader/common/convert"
|
|
"github.com/thrasher-corp/gocryptotrader/currency"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/alert"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
|
)
|
|
|
|
var (
|
|
errEmptyCurrency = errors.New("empty currency")
|
|
errUpdatesAreNil = errors.New("updates are nil")
|
|
errNilStates = errors.New("states is not started or set up")
|
|
|
|
// Specific operational errors
|
|
errDepositNotAllowed = errors.New("depositing not allowed")
|
|
errWithdrawalsNotAllowed = errors.New("withdrawals not allowed")
|
|
errTradingNotAllowed = errors.New("trading not allowed")
|
|
|
|
// ErrCurrencyStateNotFound is an error when the currency state has not been
|
|
// found
|
|
// TODO: distinguish between unsupported and not found
|
|
ErrCurrencyStateNotFound = errors.New("currency state not found")
|
|
)
|
|
|
|
// NewCurrencyStates gets a new type for tracking exchange currency states
|
|
func NewCurrencyStates() *States {
|
|
return &States{m: make(map[asset.Item]map[*currency.Item]*Currency)}
|
|
}
|
|
|
|
// States defines all currency states for an exchange
|
|
type States struct {
|
|
m map[asset.Item]map[*currency.Item]*Currency
|
|
mtx sync.RWMutex
|
|
}
|
|
|
|
// GetCurrencyStateSnapshot returns the exchange currency state snapshot
|
|
func (s *States) GetCurrencyStateSnapshot() ([]Snapshot, error) {
|
|
if s == nil {
|
|
return nil, errNilStates
|
|
}
|
|
|
|
s.mtx.RLock()
|
|
defer s.mtx.RUnlock()
|
|
var sh []Snapshot
|
|
for a, m1 := range s.m {
|
|
for c, val := range m1 {
|
|
sh = append(sh, Snapshot{
|
|
Code: currency.Code{Item: c},
|
|
Asset: a,
|
|
Options: val.GetState(),
|
|
})
|
|
}
|
|
}
|
|
return sh, nil
|
|
}
|
|
|
|
// CanTradePair returns if the currency pair is currently tradeable for this
|
|
// exchange. If there are no states loaded for a specific currency, this will
|
|
// assume the currency pair is operational. NOTE: Future exchanges will have
|
|
// functionality specific to a currency.Pair, can upgrade this when needed.
|
|
func (s *States) CanTradePair(pair currency.Pair, a asset.Item) error {
|
|
err := s.CanTrade(pair.Base, a)
|
|
if err != nil && err != ErrCurrencyStateNotFound {
|
|
return fmt.Errorf("cannot trade base currency %s %s: %w",
|
|
pair.Base, a, err)
|
|
}
|
|
err = s.CanTrade(pair.Quote, a)
|
|
if err != nil && err != ErrCurrencyStateNotFound {
|
|
return fmt.Errorf("cannot trade quote currency %s %s: %w",
|
|
pair.Base, a, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CanTrade returns if the currency is currently tradeable for this exchange
|
|
func (s *States) CanTrade(c currency.Code, a asset.Item) error {
|
|
if s == nil {
|
|
return errNilStates
|
|
}
|
|
|
|
p, err := s.Get(c, a)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !p.CanTrade() {
|
|
return errTradingNotAllowed
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CanWithdraw returns if the currency can be withdrawn from this exchange
|
|
func (s *States) CanWithdraw(c currency.Code, a asset.Item) error {
|
|
if s == nil {
|
|
return errNilStates
|
|
}
|
|
|
|
p, err := s.Get(c, a)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !p.CanWithdraw() {
|
|
return errWithdrawalsNotAllowed
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CanDeposit returns if the currency can be deposited onto this exchange
|
|
func (s *States) CanDeposit(c currency.Code, a asset.Item) error {
|
|
if s == nil {
|
|
return errNilStates
|
|
}
|
|
|
|
p, err := s.Get(c, a)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !p.CanDeposit() {
|
|
return errDepositNotAllowed
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// UpdateAll updates the full currency state, used for REST calls
|
|
func (s *States) UpdateAll(a asset.Item, updates map[currency.Code]Options) error {
|
|
if s == nil {
|
|
return errNilStates
|
|
}
|
|
|
|
if !a.IsValid() {
|
|
return fmt.Errorf("%s %w", a, asset.ErrNotSupported)
|
|
}
|
|
if updates == nil {
|
|
return errUpdatesAreNil
|
|
}
|
|
s.mtx.Lock()
|
|
for code, option := range updates {
|
|
s.update(code, a, option)
|
|
}
|
|
s.mtx.Unlock()
|
|
return nil
|
|
}
|
|
|
|
// Update updates a singular currency state, primarily used for singular
|
|
// websocket updates or alerts.
|
|
func (s *States) Update(c currency.Code, a asset.Item, o Options) error {
|
|
if s == nil {
|
|
return errNilStates
|
|
}
|
|
|
|
if c.String() == "" {
|
|
return errEmptyCurrency
|
|
}
|
|
if !a.IsValid() {
|
|
return fmt.Errorf("%s, %w", a, asset.ErrNotSupported)
|
|
}
|
|
s.mtx.Lock()
|
|
s.update(c, a, o)
|
|
s.mtx.Unlock()
|
|
return nil
|
|
}
|
|
|
|
// update updates a singular currency state without protection
|
|
func (s *States) update(c currency.Code, a asset.Item, o Options) {
|
|
m1, ok := s.m[a]
|
|
if !ok {
|
|
m1 = make(map[*currency.Item]*Currency)
|
|
s.m[a] = m1
|
|
}
|
|
p, ok := m1[c.Item]
|
|
if !ok {
|
|
p = &Currency{}
|
|
m1[c.Item] = p
|
|
}
|
|
p.update(o)
|
|
}
|
|
|
|
// Get returns the currency state by currency code
|
|
func (s *States) Get(c currency.Code, a asset.Item) (*Currency, error) {
|
|
if s == nil {
|
|
return nil, errNilStates
|
|
}
|
|
|
|
if c.String() == "" {
|
|
return nil, errEmptyCurrency
|
|
}
|
|
|
|
if !a.IsValid() {
|
|
return nil, fmt.Errorf("%s %w", a, asset.ErrNotSupported)
|
|
}
|
|
|
|
s.mtx.RLock()
|
|
defer s.mtx.RUnlock()
|
|
cs, ok := s.m[a][c.Item]
|
|
if !ok {
|
|
return nil, ErrCurrencyStateNotFound
|
|
}
|
|
return cs, nil
|
|
}
|
|
|
|
// Currency defines the state of currency operations
|
|
type Currency struct {
|
|
withdrawals bool
|
|
withdrawAlerts alert.Notice
|
|
deposits bool
|
|
depositAlerts alert.Notice
|
|
trading bool
|
|
tradingAlerts alert.Notice
|
|
mtx sync.RWMutex
|
|
}
|
|
|
|
// update updates the underlying values
|
|
func (c *Currency) update(o Options) {
|
|
c.mtx.Lock()
|
|
if o.Withdraw == nil {
|
|
c.withdrawals = true
|
|
c.withdrawAlerts.Alert()
|
|
} else if c.withdrawals != *o.Withdraw {
|
|
c.withdrawals = *o.Withdraw
|
|
c.withdrawAlerts.Alert()
|
|
}
|
|
|
|
if o.Deposit == nil {
|
|
c.deposits = true
|
|
c.depositAlerts.Alert()
|
|
} else if c.deposits != *o.Deposit {
|
|
c.deposits = *o.Deposit
|
|
c.depositAlerts.Alert()
|
|
}
|
|
|
|
if o.Trade == nil {
|
|
c.trading = true
|
|
c.tradingAlerts.Alert()
|
|
} else if c.trading != *o.Trade {
|
|
c.trading = *o.Trade
|
|
c.tradingAlerts.Alert()
|
|
}
|
|
c.mtx.Unlock()
|
|
}
|
|
|
|
// CanTrade returns if the currency is currently tradeable
|
|
func (c *Currency) CanTrade() bool {
|
|
c.mtx.RLock()
|
|
defer c.mtx.RUnlock()
|
|
return c.trading
|
|
}
|
|
|
|
// CanWithdraw returns if the currency can be withdrawn from the exchange
|
|
func (c *Currency) CanWithdraw() bool {
|
|
c.mtx.RLock()
|
|
defer c.mtx.RUnlock()
|
|
return c.withdrawals
|
|
}
|
|
|
|
// CanDeposit returns if the currency can be deposited onto an exchange
|
|
func (c *Currency) CanDeposit() bool {
|
|
c.mtx.RLock()
|
|
defer c.mtx.RUnlock()
|
|
return c.deposits
|
|
}
|
|
|
|
// WaitTrading allows a routine to wait until a trading change of state occurs
|
|
func (c *Currency) WaitTrading(kick <-chan struct{}) <-chan bool {
|
|
c.mtx.RLock()
|
|
defer c.mtx.RUnlock()
|
|
return c.tradingAlerts.Wait(kick)
|
|
}
|
|
|
|
// WaitDeposit allows a routine to wait until a deposit change of state occurs
|
|
func (c *Currency) WaitDeposit(kick <-chan struct{}) <-chan bool {
|
|
c.mtx.RLock()
|
|
defer c.mtx.RUnlock()
|
|
return c.depositAlerts.Wait(kick)
|
|
}
|
|
|
|
// WaitWithdraw allows a routine to wait until a withdraw change of state occurs
|
|
func (c *Currency) WaitWithdraw(kick <-chan struct{}) <-chan bool {
|
|
c.mtx.RLock()
|
|
defer c.mtx.RUnlock()
|
|
return c.withdrawAlerts.Wait(kick)
|
|
}
|
|
|
|
// GetState returns the internal state of the currency
|
|
func (c *Currency) GetState() Options {
|
|
c.mtx.RLock()
|
|
defer c.mtx.RUnlock()
|
|
return Options{
|
|
Withdraw: convert.BoolPtr(c.withdrawals),
|
|
Deposit: convert.BoolPtr(c.deposits),
|
|
Trade: convert.BoolPtr(c.trading),
|
|
}
|
|
}
|
|
|
|
// Options defines the current allowable options for a currency, using a bool
|
|
// pointer for optional setting for incomplete data, so we can default to true
|
|
// on nil values.
|
|
type Options struct {
|
|
Withdraw *bool
|
|
Deposit *bool
|
|
Trade *bool
|
|
}
|
|
|
|
// Snapshot defines a snapshot of the internal asset for exportation
|
|
type Snapshot struct {
|
|
Code currency.Code
|
|
Asset asset.Item
|
|
Options
|
|
}
|