Files
gocryptotrader/exchanges/currencystate/currency_state.go
Ryan O'Hara-Reid 5dfbbf84de engine/exchanges: Add exchange currency state subsystem (#774)
* state: Add management system (init)

* linter: fix

* engine: gofmt

* gct: after merge fixup

* documentation: add

* rpc: implement services for testing

* gctcli: gofmt state_management.go

* documentation: reinstate lost information

* state: Add pair check to determine trading operation

* exchanges: add interface for specific state scoped subsystem functionality

* engine/order_man: reduce code footprint using new method

* RPC: implement pair trading request and change exported name to something specific to state

* engine: add tests

* engine: Add to withdraw manager

* documentation: reinstate soxipy in contrib. list

* engine: const fake name

* Glorious: NITERINOS

* merge: fix issues

* engine: csm incorporate service name into log output

* engine: fix linter issues

* gct: fix tests

* currencystate: remove management type

* rpc: fix tests

* backtester: fix tests

* Update engine/currency_state_manager.go

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* Update engine/currency_state_manager.go

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* Update exchanges/currencystate/currency_state.go

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* Update exchanges/alert/alert.go

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* Update exchanges/alert/alert.go

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* glorious: nits

* config: integrate with config and remove flag delay adjustment

* gctcli: fix issues after name changes

* engine: gofmt manager file

* Update engine/rpcserver.go

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* engine: Add enable/disable manager functions, add default popoulation for potential assets

* linter: fix

* engine/test: bump subsystem count

* Update engine/currency_state_manager.go

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* Update exchanges/bithumb/bithumb.go

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* glorious: nits addressed

* alert: fix commenting for its generalized purpose

* glorious: nits

* engine: use standard string in log output

* bitfinex: apply patch, thanks @thrasher-

* bitfinex: fix spelling

* engine/currencystate: Add logs/fix logs

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>
2021-09-27 13:33:49 +10:00

313 lines
7.4 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
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
}