mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-17 23:16:52 +00:00
* 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>
313 lines
7.4 KiB
Go
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
|
|
}
|