mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-14 07:26:47 +00:00
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>
This commit is contained in:
276
engine/currency_state_manager.go
Normal file
276
engine/currency_state_manager.go
Normal file
@@ -0,0 +1,276 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/currencystate"
|
||||
"github.com/thrasher-corp/gocryptotrader/gctrpc"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
)
|
||||
|
||||
const (
|
||||
// CurrencyStateManagementName defines the manager name string
|
||||
CurrencyStateManagementName = "currency_state_manager"
|
||||
// DefaultStateManagerDelay defines the default duration when the manager
|
||||
// fetches and updates each exchange for its currency state
|
||||
DefaultStateManagerDelay = time.Minute
|
||||
)
|
||||
|
||||
var enabled = &gctrpc.GenericResponse{Status: "enabled"}
|
||||
|
||||
// CurrencyStateManager manages currency states
|
||||
type CurrencyStateManager struct {
|
||||
started int32
|
||||
shutdown chan struct{}
|
||||
wg sync.WaitGroup
|
||||
iExchangeManager
|
||||
sleep time.Duration
|
||||
}
|
||||
|
||||
// SetupCurrencyStateManager applies configuration parameters before running
|
||||
func SetupCurrencyStateManager(interval time.Duration, em iExchangeManager) (*CurrencyStateManager, error) {
|
||||
if em == nil {
|
||||
return nil, errNilExchangeManager
|
||||
}
|
||||
var c CurrencyStateManager
|
||||
if interval <= 0 {
|
||||
log.Warnf(log.ExchangeSys,
|
||||
"Currency state manager interval is invalid, defaulting to: %s",
|
||||
DefaultStateManagerDelay)
|
||||
interval = DefaultStateManagerDelay
|
||||
}
|
||||
c.sleep = interval
|
||||
c.iExchangeManager = em
|
||||
c.shutdown = make(chan struct{})
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
// Start runs the subsystem
|
||||
func (c *CurrencyStateManager) Start() error {
|
||||
log.Debugln(log.ExchangeSys, "Currency state manager starting...")
|
||||
if c == nil {
|
||||
return fmt.Errorf("%s %w", CurrencyStateManagementName, ErrNilSubsystem)
|
||||
}
|
||||
|
||||
if !atomic.CompareAndSwapInt32(&c.started, 0, 1) {
|
||||
return fmt.Errorf("%s %w", CurrencyStateManagementName, ErrSubSystemAlreadyStarted)
|
||||
}
|
||||
c.wg.Add(1)
|
||||
go c.monitor()
|
||||
log.Debugln(log.ExchangeSys, "Currency state manager started.")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop stops the subsystem
|
||||
func (c *CurrencyStateManager) Stop() error {
|
||||
if c == nil {
|
||||
return fmt.Errorf("%s %w", CurrencyStateManagementName, ErrNilSubsystem)
|
||||
}
|
||||
if atomic.LoadInt32(&c.started) == 0 {
|
||||
return fmt.Errorf("%s %w", CurrencyStateManagementName, ErrSubSystemNotStarted)
|
||||
}
|
||||
|
||||
log.Debugf(log.ExchangeSys, "Currency state manager %s", MsgSubSystemShuttingDown)
|
||||
close(c.shutdown)
|
||||
c.wg.Wait()
|
||||
c.shutdown = make(chan struct{})
|
||||
log.Debugf(log.ExchangeSys, "Currency state manager %s", MsgSubSystemShutdown)
|
||||
atomic.StoreInt32(&c.started, 0)
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsRunning safely checks whether the subsystem is running
|
||||
func (c *CurrencyStateManager) IsRunning() bool {
|
||||
if c == nil {
|
||||
return false
|
||||
}
|
||||
return atomic.LoadInt32(&c.started) == 1
|
||||
}
|
||||
|
||||
func (c *CurrencyStateManager) monitor() {
|
||||
defer c.wg.Done()
|
||||
timer := time.NewTimer(0) // Prime firing of channel for initial sync.
|
||||
for {
|
||||
select {
|
||||
case <-c.shutdown:
|
||||
return
|
||||
case <-timer.C:
|
||||
var wg sync.WaitGroup
|
||||
exchs, err := c.GetExchanges()
|
||||
if err != nil {
|
||||
log.Errorf(log.Global,
|
||||
"Currency state manager failed to get exchanges error: %v",
|
||||
err)
|
||||
}
|
||||
for x := range exchs {
|
||||
wg.Add(1)
|
||||
go c.update(exchs[x], &wg, exchs[x].GetAssetTypes(true))
|
||||
}
|
||||
wg.Wait() // This causes some variability in the timer due to
|
||||
// longest length of request time. Can do time.Ticker but don't
|
||||
// want routines to stack behind, this is more uniform.
|
||||
timer.Reset(c.sleep)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CurrencyStateManager) update(exch exchange.IBotExchange, wg *sync.WaitGroup, enabledAssets asset.Items) {
|
||||
defer wg.Done()
|
||||
for y := range enabledAssets {
|
||||
err := exch.UpdateCurrencyStates(context.TODO(), enabledAssets[y])
|
||||
if err != nil {
|
||||
if errors.Is(err, common.ErrNotYetImplemented) {
|
||||
// Deploy default values for outbound gRPC aspects.
|
||||
var pairs currency.Pairs
|
||||
pairs, err = exch.GetAvailablePairs(enabledAssets[y])
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys, "Currency state manager %s %s: %v",
|
||||
exch.GetName(),
|
||||
enabledAssets[y],
|
||||
err)
|
||||
return
|
||||
}
|
||||
|
||||
// Deploys a full spectrum supported list for the currency states
|
||||
update := map[currency.Code]currencystate.Options{}
|
||||
for x := range pairs {
|
||||
update[pairs[x].Base] = currencystate.Options{}
|
||||
update[pairs[x].Quote] = currencystate.Options{}
|
||||
}
|
||||
|
||||
b := exch.GetBase()
|
||||
if b == nil {
|
||||
log.Errorf(log.ExchangeSys, "Currency state manager %s %s: %v",
|
||||
exch.GetName(),
|
||||
enabledAssets[y],
|
||||
"cannot update because base is nil")
|
||||
return
|
||||
}
|
||||
err = b.States.UpdateAll(enabledAssets[y], update)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys, "Currency state manager %s %s: %v",
|
||||
exch.GetName(),
|
||||
enabledAssets[y],
|
||||
err)
|
||||
}
|
||||
return
|
||||
}
|
||||
log.Errorf(log.ExchangeSys, "Currency state manager %s %s: %v",
|
||||
exch.GetName(),
|
||||
enabledAssets[y],
|
||||
err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetAllRPC returns a full snapshot of currency states, whether they are able
|
||||
// to be withdrawn, deposited or traded on an exchange for RPC.
|
||||
func (c *CurrencyStateManager) GetAllRPC(exchName string) (*gctrpc.CurrencyStateResponse, error) {
|
||||
if !c.IsRunning() {
|
||||
return nil, fmt.Errorf("%s %w", CurrencyStateManagementName, ErrSubSystemNotStarted)
|
||||
}
|
||||
exch, err := c.GetExchangeByName(exchName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sh, err := exch.GetCurrencyStateSnapshot()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var resp = &gctrpc.CurrencyStateResponse{}
|
||||
for x := range sh {
|
||||
resp.CurrencyStates = append(resp.CurrencyStates, &gctrpc.CurrencyState{
|
||||
Currency: sh[x].Code.String(),
|
||||
Asset: sh[x].Asset.String(),
|
||||
WithdrawEnabled: sh[x].Withdraw == nil || *sh[x].Withdraw,
|
||||
DepositEnabled: sh[x].Deposit == nil || *sh[x].Deposit,
|
||||
TradingEnabled: sh[x].Trade == nil || *sh[x].Trade,
|
||||
})
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// CanWithdrawRPC determines if the currency code is operational for withdrawal
|
||||
// from an exchange for RPC
|
||||
func (c *CurrencyStateManager) CanWithdrawRPC(exchName string, cc currency.Code, a asset.Item) (*gctrpc.GenericResponse, error) {
|
||||
if !c.IsRunning() {
|
||||
return nil, fmt.Errorf("%s %w", CurrencyStateManagementName, ErrSubSystemNotStarted)
|
||||
}
|
||||
|
||||
exch, err := c.GetExchangeByName(exchName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = exch.CanWithdraw(cc, a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return enabled, nil
|
||||
}
|
||||
|
||||
// CanDepositRPC determines if the currency code is operational for depositing
|
||||
// to an exchange for RPC
|
||||
func (c *CurrencyStateManager) CanDepositRPC(exchName string, cc currency.Code, a asset.Item) (*gctrpc.GenericResponse, error) {
|
||||
if !c.IsRunning() {
|
||||
return nil, fmt.Errorf("%s %w", CurrencyStateManagementName, ErrSubSystemNotStarted)
|
||||
}
|
||||
|
||||
exch, err := c.GetExchangeByName(exchName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = exch.CanDeposit(cc, a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return enabled, nil
|
||||
}
|
||||
|
||||
// CanTradeRPC determines if the currency code is operational for trading for
|
||||
// RPC
|
||||
func (c *CurrencyStateManager) CanTradeRPC(exchName string, cc currency.Code, a asset.Item) (*gctrpc.GenericResponse, error) {
|
||||
if !c.IsRunning() {
|
||||
return nil, fmt.Errorf("%s %w", CurrencyStateManagementName, ErrSubSystemNotStarted)
|
||||
}
|
||||
|
||||
exch, err := c.GetExchangeByName(exchName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = exch.CanTrade(cc, a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return enabled, nil
|
||||
}
|
||||
|
||||
// CanTradePairRPC determines if the pair is operational for trading for RPC
|
||||
func (c *CurrencyStateManager) CanTradePairRPC(exchName string, pair currency.Pair, a asset.Item) (*gctrpc.GenericResponse, error) {
|
||||
if !c.IsRunning() {
|
||||
return nil, fmt.Errorf("%s %w", CurrencyStateManagementName, ErrSubSystemNotStarted)
|
||||
}
|
||||
|
||||
exch, err := c.GetExchangeByName(exchName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = exch.CanTradePair(pair, a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return enabled, nil
|
||||
}
|
||||
48
engine/currency_state_manager.md
Normal file
48
engine/currency_state_manager.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# GoCryptoTrader package State manager
|
||||
|
||||
<img src="/common/gctlogo.png?raw=true" width="350px" height="350px" hspace="70">
|
||||
|
||||
|
||||
[](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml)
|
||||
[](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE)
|
||||
[](https://godoc.org/github.com/thrasher-corp/gocryptotrader/engine/state_manager)
|
||||
[](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master)
|
||||
[](https://goreportcard.com/report/github.com/thrasher-corp/gocryptotrader)
|
||||
|
||||
|
||||
This state_manager package is part of the GoCryptoTrader codebase.
|
||||
|
||||
## This is still in active development
|
||||
|
||||
You can track ideas, planned features and what's in progress on this Trello board: [https://trello.com/b/ZAhMhpOy/gocryptotrader](https://trello.com/b/ZAhMhpOy/gocryptotrader).
|
||||
|
||||
Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader Slack](https://join.slack.com/t/gocryptotrader/shared_invite/enQtNTQ5NDAxMjA2Mjc5LTc5ZDE1ZTNiOGM3ZGMyMmY1NTAxYWZhODE0MWM5N2JlZDk1NDU0YTViYzk4NTk3OTRiMDQzNGQ1YTc4YmRlMTk)
|
||||
|
||||
## Current Features for State manager
|
||||
+ The state manager keeps currency states up to date, which include:
|
||||
* Withdrawal - Determines if the currency is allowed to be withdrawn from the exchange.
|
||||
* Deposit - Determines if the currency is allowed to be deposited to an exchange.
|
||||
* Trading - Determines if the currency is allowed to be traded on the exchange.
|
||||
|
||||
+ This allows for an internal state check to compliment internal and external
|
||||
strategies.
|
||||
|
||||
|
||||
## Contribution
|
||||
|
||||
Please feel free to submit any pull requests or suggest any desired features to be added.
|
||||
|
||||
When submitting a PR, please abide by our coding guidelines:
|
||||
|
||||
+ Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting) guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)).
|
||||
+ Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary) guidelines.
|
||||
+ Code must adhere to our [coding style](https://github.com/thrasher-corp/gocryptotrader/blob/master/doc/coding_style.md).
|
||||
+ Pull requests need to be based on and opened against the `master` branch.
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
***bc1qk0jareu4jytc0cfrhr5wgshsq8282awpavfahc***
|
||||
376
engine/currency_state_manager_test.go
Normal file
376
engine/currency_state_manager_test.go
Normal file
@@ -0,0 +1,376 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/currencystate"
|
||||
)
|
||||
|
||||
func TestSetupCurrencyStateManager(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := SetupCurrencyStateManager(0, nil)
|
||||
if !errors.Is(err, errNilExchangeManager) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errNilExchangeManager)
|
||||
}
|
||||
|
||||
cm, err := SetupCurrencyStateManager(0, &ExchangeManager{})
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
if cm.sleep != DefaultStateManagerDelay {
|
||||
t.Fatal("unexpected value")
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
errManager = errors.New("manager level error")
|
||||
errExchange = errors.New("exchange level error")
|
||||
)
|
||||
|
||||
type fakeExchangeManagerino struct {
|
||||
ErrorMeOne bool
|
||||
ErrorMeTwo bool
|
||||
}
|
||||
|
||||
func (f *fakeExchangeManagerino) GetExchanges() ([]exchange.IBotExchange, error) {
|
||||
if f.ErrorMeOne {
|
||||
return nil, errManager
|
||||
}
|
||||
return []exchange.IBotExchange{&fakerino{errorMe: f.ErrorMeTwo}}, nil
|
||||
}
|
||||
|
||||
func (f *fakeExchangeManagerino) GetExchangeByName(_ string) (exchange.IBotExchange, error) {
|
||||
if f.ErrorMeOne {
|
||||
return nil, errManager
|
||||
}
|
||||
return &fakerino{errorMe: f.ErrorMeTwo}, nil
|
||||
}
|
||||
|
||||
type fakerino struct {
|
||||
exchange.IBotExchange
|
||||
errorMe bool
|
||||
GetAvailablePairsError bool
|
||||
GetBaseError bool
|
||||
}
|
||||
|
||||
func (f *fakerino) UpdateCurrencyStates(_ context.Context, _ asset.Item) error {
|
||||
if f.errorMe {
|
||||
return common.ErrNotYetImplemented
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fakerino) GetAssetTypes(_ bool) asset.Items {
|
||||
return asset.Items{asset.Spot}
|
||||
}
|
||||
|
||||
func (f *fakerino) GetName() string {
|
||||
return "testssssssssssssss"
|
||||
}
|
||||
|
||||
func (f *fakerino) GetCurrencyStateSnapshot() ([]currencystate.Snapshot, error) {
|
||||
if f.errorMe {
|
||||
return nil, errExchange
|
||||
}
|
||||
return []currencystate.Snapshot{
|
||||
{Code: currency.SHORTY, Asset: asset.Spot},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f *fakerino) CanWithdraw(c currency.Code, a asset.Item) error {
|
||||
if f.errorMe {
|
||||
return errExchange
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fakerino) CanDeposit(c currency.Code, a asset.Item) error {
|
||||
if f.errorMe {
|
||||
return errExchange
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fakerino) CanTrade(c currency.Code, a asset.Item) error {
|
||||
if f.errorMe {
|
||||
return errExchange
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fakerino) CanTradePair(p currency.Pair, a asset.Item) error {
|
||||
if f.errorMe {
|
||||
return errExchange
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fakerino) GetAvailablePairs(a asset.Item) (currency.Pairs, error) {
|
||||
if f.GetAvailablePairsError {
|
||||
return nil, errExchange
|
||||
}
|
||||
return currency.Pairs{currency.NewPair(currency.BTC, currency.USD)}, nil
|
||||
}
|
||||
|
||||
func (f *fakerino) GetBase() *exchange.Base {
|
||||
if f.GetBaseError {
|
||||
return nil
|
||||
}
|
||||
return &exchange.Base{States: currencystate.NewCurrencyStates()}
|
||||
}
|
||||
|
||||
func TestCurrencyStateManagerIsRunning(t *testing.T) {
|
||||
t.Parallel()
|
||||
err := (*CurrencyStateManager)(nil).Stop()
|
||||
if !errors.Is(err, ErrNilSubsystem) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, ErrNilSubsystem)
|
||||
}
|
||||
|
||||
err = (&CurrencyStateManager{}).Stop()
|
||||
if !errors.Is(err, ErrSubSystemNotStarted) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, ErrSubSystemNotStarted)
|
||||
}
|
||||
|
||||
err = (&CurrencyStateManager{started: 1, shutdown: make(chan struct{})}).Stop()
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
err = (*CurrencyStateManager)(nil).Start()
|
||||
if !errors.Is(err, ErrNilSubsystem) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, ErrNilSubsystem)
|
||||
}
|
||||
|
||||
err = (&CurrencyStateManager{started: 1}).Start()
|
||||
if !errors.Is(err, ErrSubSystemAlreadyStarted) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, ErrSubSystemAlreadyStarted)
|
||||
}
|
||||
|
||||
man := &CurrencyStateManager{
|
||||
shutdown: make(chan struct{}),
|
||||
iExchangeManager: &fakeExchangeManagerino{ErrorMeOne: true},
|
||||
sleep: time.Minute}
|
||||
err = man.Start()
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
time.Sleep(time.Millisecond)
|
||||
|
||||
err = man.Stop()
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
man.iExchangeManager = &fakeExchangeManagerino{ErrorMeOne: true}
|
||||
err = man.Start()
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
time.Sleep(time.Millisecond)
|
||||
|
||||
err = man.Stop()
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
man.iExchangeManager = &fakeExchangeManagerino{ErrorMeOne: true}
|
||||
err = man.Start()
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
time.Sleep(time.Millisecond)
|
||||
|
||||
if !man.IsRunning() {
|
||||
t.Fatal("this should be running")
|
||||
}
|
||||
|
||||
err = man.Stop()
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
if man.IsRunning() {
|
||||
t.Fatal("this should be stopped")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAllRPC(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := (*CurrencyStateManager)(nil).GetAllRPC("")
|
||||
if !errors.Is(err, ErrSubSystemNotStarted) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, ErrSubSystemNotStarted)
|
||||
}
|
||||
|
||||
_, err = (&CurrencyStateManager{
|
||||
started: 1,
|
||||
iExchangeManager: &fakeExchangeManagerino{ErrorMeOne: true},
|
||||
}).GetAllRPC("")
|
||||
if !errors.Is(err, errManager) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errManager)
|
||||
}
|
||||
|
||||
_, err = (&CurrencyStateManager{
|
||||
started: 1,
|
||||
iExchangeManager: &fakeExchangeManagerino{ErrorMeTwo: true},
|
||||
}).GetAllRPC("")
|
||||
if !errors.Is(err, errExchange) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errExchange)
|
||||
}
|
||||
|
||||
_, err = (&CurrencyStateManager{
|
||||
started: 1,
|
||||
iExchangeManager: &fakeExchangeManagerino{},
|
||||
}).GetAllRPC("")
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCanWithdrawRPC(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := (*CurrencyStateManager)(nil).CanWithdrawRPC("", currency.Code{}, "")
|
||||
if !errors.Is(err, ErrSubSystemNotStarted) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, ErrSubSystemNotStarted)
|
||||
}
|
||||
|
||||
_, err = (&CurrencyStateManager{
|
||||
started: 1,
|
||||
iExchangeManager: &fakeExchangeManagerino{ErrorMeOne: true},
|
||||
}).CanWithdrawRPC("", currency.Code{}, "")
|
||||
if !errors.Is(err, errManager) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errManager)
|
||||
}
|
||||
|
||||
_, err = (&CurrencyStateManager{
|
||||
started: 1,
|
||||
iExchangeManager: &fakeExchangeManagerino{ErrorMeTwo: true},
|
||||
}).CanWithdrawRPC("", currency.Code{}, "")
|
||||
if !errors.Is(err, errExchange) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errExchange)
|
||||
}
|
||||
|
||||
_, err = (&CurrencyStateManager{
|
||||
started: 1,
|
||||
iExchangeManager: &fakeExchangeManagerino{},
|
||||
}).CanWithdrawRPC("", currency.Code{}, "")
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCanDepositRPC(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := (*CurrencyStateManager)(nil).CanDepositRPC("", currency.Code{}, "")
|
||||
if !errors.Is(err, ErrSubSystemNotStarted) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, ErrSubSystemNotStarted)
|
||||
}
|
||||
|
||||
_, err = (&CurrencyStateManager{
|
||||
started: 1,
|
||||
iExchangeManager: &fakeExchangeManagerino{ErrorMeOne: true},
|
||||
}).CanDepositRPC("", currency.Code{}, "")
|
||||
if !errors.Is(err, errManager) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errManager)
|
||||
}
|
||||
|
||||
_, err = (&CurrencyStateManager{
|
||||
started: 1,
|
||||
iExchangeManager: &fakeExchangeManagerino{ErrorMeTwo: true},
|
||||
}).CanDepositRPC("", currency.Code{}, "")
|
||||
if !errors.Is(err, errExchange) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errExchange)
|
||||
}
|
||||
|
||||
_, err = (&CurrencyStateManager{
|
||||
started: 1,
|
||||
iExchangeManager: &fakeExchangeManagerino{},
|
||||
}).CanDepositRPC("", currency.Code{}, "")
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCanTradeRPC(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := (*CurrencyStateManager)(nil).CanTradeRPC("", currency.Code{}, "")
|
||||
if !errors.Is(err, ErrSubSystemNotStarted) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, ErrSubSystemNotStarted)
|
||||
}
|
||||
|
||||
_, err = (&CurrencyStateManager{
|
||||
started: 1,
|
||||
iExchangeManager: &fakeExchangeManagerino{ErrorMeOne: true},
|
||||
}).CanTradeRPC("", currency.Code{}, "")
|
||||
if !errors.Is(err, errManager) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errManager)
|
||||
}
|
||||
|
||||
_, err = (&CurrencyStateManager{
|
||||
started: 1,
|
||||
iExchangeManager: &fakeExchangeManagerino{ErrorMeTwo: true},
|
||||
}).CanTradeRPC("", currency.Code{}, "")
|
||||
if !errors.Is(err, errExchange) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errExchange)
|
||||
}
|
||||
|
||||
_, err = (&CurrencyStateManager{
|
||||
started: 1,
|
||||
iExchangeManager: &fakeExchangeManagerino{},
|
||||
}).CanTradeRPC("", currency.Code{}, "")
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCanTradePairRPC(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := (*CurrencyStateManager)(nil).CanTradePairRPC("", currency.Pair{}, "")
|
||||
if !errors.Is(err, ErrSubSystemNotStarted) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, ErrSubSystemNotStarted)
|
||||
}
|
||||
|
||||
_, err = (&CurrencyStateManager{
|
||||
started: 1,
|
||||
iExchangeManager: &fakeExchangeManagerino{ErrorMeOne: true},
|
||||
}).CanTradePairRPC("", currency.Pair{}, "")
|
||||
if !errors.Is(err, errManager) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errManager)
|
||||
}
|
||||
|
||||
_, err = (&CurrencyStateManager{
|
||||
started: 1,
|
||||
iExchangeManager: &fakeExchangeManagerino{ErrorMeTwo: true},
|
||||
}).CanTradePairRPC("", currency.Pair{}, "")
|
||||
if !errors.Is(err, errExchange) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errExchange)
|
||||
}
|
||||
|
||||
_, err = (&CurrencyStateManager{
|
||||
started: 1,
|
||||
iExchangeManager: &fakeExchangeManagerino{},
|
||||
}).CanTradePairRPC("", currency.Pair{}, "")
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdate(t *testing.T) {
|
||||
man := &CurrencyStateManager{}
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(3)
|
||||
man.update(&fakerino{errorMe: true, GetAvailablePairsError: true}, &wg, asset.Items{asset.Spot})
|
||||
man.update(&fakerino{errorMe: true, GetBaseError: true}, &wg, asset.Items{asset.Spot})
|
||||
man.update(&fakerino{errorMe: true}, &wg, asset.Items{asset.Spot})
|
||||
}
|
||||
@@ -46,6 +46,7 @@ type Engine struct {
|
||||
websocketRoutineManager *websocketRoutineManager
|
||||
WithdrawManager *WithdrawManager
|
||||
dataHistoryManager *DataHistoryManager
|
||||
currencyStateManager *CurrencyStateManager
|
||||
Settings Settings
|
||||
uptime time.Time
|
||||
ServicesWG sync.WaitGroup
|
||||
@@ -145,13 +146,17 @@ func validateSettings(b *Engine, s *Settings, flagSet map[string]bool) {
|
||||
|
||||
b.Settings.EnableDataHistoryManager = (flagSet["datahistorymanager"] && b.Settings.EnableDatabaseManager) || b.Config.DataHistoryManager.Enabled
|
||||
|
||||
b.Settings.EnableCurrencyStateManager = (flagSet["currencystatemanager"] &&
|
||||
b.Settings.EnableCurrencyStateManager) ||
|
||||
b.Config.CurrencyStateManager.Enabled != nil &&
|
||||
*b.Config.CurrencyStateManager.Enabled
|
||||
|
||||
b.Settings.EnableGCTScriptManager = b.Settings.EnableGCTScriptManager &&
|
||||
(flagSet["gctscriptmanager"] || b.Config.GCTScript.Enabled)
|
||||
|
||||
if b.Settings.EnablePortfolioManager {
|
||||
if b.Settings.PortfolioManagerDelay <= 0 {
|
||||
b.Settings.PortfolioManagerDelay = PortfolioSleepDelay
|
||||
}
|
||||
if b.Settings.EnablePortfolioManager &&
|
||||
b.Settings.PortfolioManagerDelay <= 0 {
|
||||
b.Settings.PortfolioManagerDelay = PortfolioSleepDelay
|
||||
}
|
||||
|
||||
if !flagSet["grpc"] {
|
||||
@@ -242,6 +247,7 @@ func PrintSettings(s *Settings) {
|
||||
gctlog.Debugf(gctlog.Global, "\t Enable coinmarketcap analaysis: %v", s.EnableCoinmarketcapAnalysis)
|
||||
gctlog.Debugf(gctlog.Global, "\t Enable portfolio manager: %v", s.EnablePortfolioManager)
|
||||
gctlog.Debugf(gctlog.Global, "\t Enable data history manager: %v", s.EnableDataHistoryManager)
|
||||
gctlog.Debugf(gctlog.Global, "\t Enable currency state manager: %v", s.EnableCurrencyStateManager)
|
||||
gctlog.Debugf(gctlog.Global, "\t Portfolio manager sleep delay: %v\n", s.PortfolioManagerDelay)
|
||||
gctlog.Debugf(gctlog.Global, "\t Enable gPRC: %v", s.EnableGRPC)
|
||||
gctlog.Debugf(gctlog.Global, "\t Enable gRPC Proxy: %v", s.EnableGRPCProxy)
|
||||
@@ -460,8 +466,7 @@ func (bot *Engine) Start() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if bot.Settings.EnableDeprecatedRPC ||
|
||||
bot.Settings.EnableWebsocketRPC {
|
||||
if bot.Settings.EnableDeprecatedRPC || bot.Settings.EnableWebsocketRPC {
|
||||
var filePath string
|
||||
filePath, err = config.GetAndMigrateDefaultPath(bot.Settings.ConfigFile)
|
||||
if err != nil {
|
||||
@@ -570,11 +575,30 @@ func (bot *Engine) Start() error {
|
||||
if err != nil {
|
||||
gctlog.Errorf(gctlog.Global, "failed to create script manager. Err: %s", err)
|
||||
}
|
||||
if err := bot.gctScriptManager.Start(&bot.ServicesWG); err != nil {
|
||||
if err = bot.gctScriptManager.Start(&bot.ServicesWG); err != nil {
|
||||
gctlog.Errorf(gctlog.Global, "GCTScript manager unable to start: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if bot.Settings.EnableCurrencyStateManager {
|
||||
bot.currencyStateManager, err = SetupCurrencyStateManager(
|
||||
bot.Config.CurrencyStateManager.Delay,
|
||||
bot.ExchangeManager)
|
||||
if err != nil {
|
||||
gctlog.Errorf(gctlog.Global,
|
||||
"%s unable to setup: %s",
|
||||
CurrencyStateManagementName,
|
||||
err)
|
||||
} else {
|
||||
err = bot.currencyStateManager.Start()
|
||||
if err != nil {
|
||||
gctlog.Errorf(gctlog.Global,
|
||||
"%s unable to start: %s",
|
||||
CurrencyStateManagementName,
|
||||
err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -599,61 +623,51 @@ func (bot *Engine) Stop() {
|
||||
gctlog.Errorf(gctlog.Global, "Order manager unable to stop. Error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if bot.eventManager.IsRunning() {
|
||||
if err := bot.eventManager.Stop(); err != nil {
|
||||
gctlog.Errorf(gctlog.Global, "event manager unable to stop. Error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if bot.ntpManager.IsRunning() {
|
||||
if err := bot.ntpManager.Stop(); err != nil {
|
||||
gctlog.Errorf(gctlog.Global, "NTP manager unable to stop. Error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if bot.CommunicationsManager.IsRunning() {
|
||||
if err := bot.CommunicationsManager.Stop(); err != nil {
|
||||
gctlog.Errorf(gctlog.Global, "Communication manager unable to stop. Error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if bot.portfolioManager.IsRunning() {
|
||||
if err := bot.portfolioManager.Stop(); err != nil {
|
||||
gctlog.Errorf(gctlog.Global, "Fund manager unable to stop. Error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if bot.connectionManager.IsRunning() {
|
||||
if err := bot.connectionManager.Stop(); err != nil {
|
||||
gctlog.Errorf(gctlog.Global, "Connection manager unable to stop. Error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if bot.apiServer.IsRESTServerRunning() {
|
||||
if err := bot.apiServer.StopRESTServer(); err != nil {
|
||||
gctlog.Errorf(gctlog.Global, "API Server unable to stop REST server. Error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if bot.apiServer.IsWebsocketServerRunning() {
|
||||
if err := bot.apiServer.StopWebsocketServer(); err != nil {
|
||||
gctlog.Errorf(gctlog.Global, "API Server unable to stop websocket server. Error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if bot.dataHistoryManager.IsRunning() {
|
||||
if err := bot.dataHistoryManager.Stop(); err != nil {
|
||||
gctlog.Errorf(gctlog.DataHistory, "data history manager unable to stop. Error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if bot.DatabaseManager.IsRunning() {
|
||||
if err := bot.DatabaseManager.Stop(); err != nil {
|
||||
gctlog.Errorf(gctlog.Global, "Database manager unable to stop. Error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if dispatch.IsRunning() {
|
||||
if err := dispatch.Stop(); err != nil {
|
||||
gctlog.Errorf(gctlog.DispatchMgr, "Dispatch system unable to stop. Error: %v", err)
|
||||
@@ -664,6 +678,13 @@ func (bot *Engine) Stop() {
|
||||
gctlog.Errorf(gctlog.Global, "websocket routine manager unable to stop. Error: %v", err)
|
||||
}
|
||||
}
|
||||
if bot.currencyStateManager.IsRunning() {
|
||||
if err := bot.currencyStateManager.Stop(); err != nil {
|
||||
gctlog.Errorf(gctlog.Global,
|
||||
"currency state manager unable to stop. Error: %v",
|
||||
err)
|
||||
}
|
||||
}
|
||||
|
||||
if bot.Settings.EnableCoinmarketcapAnalysis ||
|
||||
bot.Settings.EnableCurrencyConverter ||
|
||||
|
||||
@@ -36,6 +36,7 @@ type Settings struct {
|
||||
EnableGCTScriptManager bool
|
||||
EnableNTPClient bool
|
||||
EnableWebsocketRoutine bool
|
||||
EnableCurrencyStateManager bool
|
||||
EventManagerDelay time.Duration
|
||||
Verbose bool
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@ func (bot *Engine) GetSubsystemsStatus() map[string]bool {
|
||||
WebsocketName: bot.Settings.EnableWebsocketRPC,
|
||||
dispatch.Name: dispatch.IsRunning(),
|
||||
dataHistoryManagerName: bot.dataHistoryManager.IsRunning(),
|
||||
CurrencyStateManagementName: bot.currencyStateManager.IsRunning(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,6 +265,19 @@ func (bot *Engine) SetSubsystem(subSystemName string, enable bool) error {
|
||||
return bot.gctScriptManager.Start(&bot.ServicesWG)
|
||||
}
|
||||
return bot.gctScriptManager.Stop()
|
||||
case strings.ToLower(CurrencyStateManagementName):
|
||||
if enable {
|
||||
if bot.currencyStateManager == nil {
|
||||
bot.currencyStateManager, err = SetupCurrencyStateManager(
|
||||
bot.Config.CurrencyStateManager.Delay,
|
||||
bot.ExchangeManager)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return bot.currencyStateManager.Start()
|
||||
}
|
||||
return bot.currencyStateManager.Stop()
|
||||
}
|
||||
return fmt.Errorf("%s: %w", subSystemName, errSubsystemNotFound)
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ func CreateTestBot(t *testing.T) *Engine {
|
||||
|
||||
func TestGetSubsystemsStatus(t *testing.T) {
|
||||
m := (&Engine{}).GetSubsystemsStatus()
|
||||
if len(m) != 14 {
|
||||
if len(m) != 15 {
|
||||
t.Fatalf("subsystem count is wrong expecting: %d but received: %d", 14, len(m))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -393,6 +393,17 @@ func (m *OrderManager) Submit(ctx context.Context, newOrder *order.Submit) (*Ord
|
||||
err)
|
||||
}
|
||||
|
||||
// Determines if current trading activity is turned off by the exchange for
|
||||
// the currency pair
|
||||
err = exch.CanTradePair(newOrder.Pair, newOrder.AssetType)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("order manager: exchange %s cannot trade pair %s %s: %w",
|
||||
newOrder.Exchange,
|
||||
newOrder.Pair,
|
||||
newOrder.AssetType,
|
||||
err)
|
||||
}
|
||||
|
||||
result, err := exch.SubmitOrder(ctx, newOrder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -190,6 +190,16 @@ func OrdersSetup(t *testing.T) *OrderManager {
|
||||
}
|
||||
exch.SetDefaults()
|
||||
|
||||
cfg, err := exch.GetDefaultConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = exch.Setup(cfg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
fakeExchange := omfExchange{
|
||||
IBotExchange: exch,
|
||||
}
|
||||
|
||||
@@ -3889,3 +3889,59 @@ func (s *RPCServer) UpdateDataHistoryJobPrerequisite(_ context.Context, r *gctrp
|
||||
}
|
||||
return &gctrpc.GenericResponse{Status: status, Data: fmt.Sprintf("Set job '%v' prerequisite job to '%v' and set status to paused", r.Nickname, r.PrerequisiteJobNickname)}, nil
|
||||
}
|
||||
|
||||
// CurrencyStateGetAll returns a full snapshot of currency states, whether they
|
||||
// are able to be withdrawn, deposited or traded on an exchange.
|
||||
func (s *RPCServer) CurrencyStateGetAll(_ context.Context, r *gctrpc.CurrencyStateGetAllRequest) (*gctrpc.CurrencyStateResponse, error) {
|
||||
return s.currencyStateManager.GetAllRPC(r.Exchange)
|
||||
}
|
||||
|
||||
// CurrencyStateWithdraw determines via RPC if the currency code is operational for
|
||||
// withdrawal from an exchange
|
||||
func (s *RPCServer) CurrencyStateWithdraw(_ context.Context, r *gctrpc.CurrencyStateWithdrawRequest) (*gctrpc.GenericResponse, error) {
|
||||
return s.currencyStateManager.CanWithdrawRPC(r.Exchange,
|
||||
currency.NewCode(r.Code),
|
||||
asset.Item(r.Asset))
|
||||
}
|
||||
|
||||
// CurrencyStateDeposit determines via RPC if the currency code is operational for
|
||||
// depositing to an exchange
|
||||
func (s *RPCServer) CurrencyStateDeposit(_ context.Context, r *gctrpc.CurrencyStateDepositRequest) (*gctrpc.GenericResponse, error) {
|
||||
return s.currencyStateManager.CanDepositRPC(r.Exchange,
|
||||
currency.NewCode(r.Code),
|
||||
asset.Item(r.Asset))
|
||||
}
|
||||
|
||||
// CurrencyStateTrading determines via RPC if the currency code is operational for trading
|
||||
func (s *RPCServer) CurrencyStateTrading(_ context.Context, r *gctrpc.CurrencyStateTradingRequest) (*gctrpc.GenericResponse, error) {
|
||||
return s.currencyStateManager.CanTradeRPC(r.Exchange,
|
||||
currency.NewCode(r.Code),
|
||||
asset.Item(r.Asset))
|
||||
}
|
||||
|
||||
// CurrencyStateTradingPair determines via RPC if the pair is operational for trading
|
||||
func (s *RPCServer) CurrencyStateTradingPair(_ context.Context, r *gctrpc.CurrencyStateTradingPairRequest) (*gctrpc.GenericResponse, error) {
|
||||
exch, err := s.GetExchangeByName(r.Exchange)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cp, err := currency.NewPairFromString(r.Pair)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
a := asset.Item(r.Asset)
|
||||
err = checkParams(r.Exchange, exch, a, cp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = exch.CanTradePair(cp, a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.currencyStateManager.CanTradePairRPC(r.Exchange,
|
||||
cp,
|
||||
asset.Item(r.Asset))
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/account"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/binance"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/currencystate"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
@@ -43,6 +44,7 @@ const (
|
||||
unexpectedLackOfError = "unexpected lack of error"
|
||||
migrationsFolder = "migrations"
|
||||
databaseFolder = "database"
|
||||
fakeExchangeName = "fake"
|
||||
)
|
||||
|
||||
// fExchange is a fake exchange with function overrides
|
||||
@@ -53,7 +55,7 @@ type fExchange struct {
|
||||
|
||||
func (f fExchange) GetHistoricCandles(ctx context.Context, p currency.Pair, a asset.Item, timeStart, _ time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
return kline.Item{
|
||||
Exchange: "fake",
|
||||
Exchange: fakeExchangeName,
|
||||
Pair: p,
|
||||
Asset: a,
|
||||
Interval: interval,
|
||||
@@ -72,7 +74,7 @@ func (f fExchange) GetHistoricCandles(ctx context.Context, p currency.Pair, a as
|
||||
|
||||
func (f fExchange) GetHistoricCandlesExtended(ctx context.Context, p currency.Pair, a asset.Item, timeStart, _ time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
return kline.Item{
|
||||
Exchange: "fake",
|
||||
Exchange: fakeExchangeName,
|
||||
Pair: p,
|
||||
Asset: a,
|
||||
Interval: interval,
|
||||
@@ -122,6 +124,36 @@ func (f fExchange) UpdateAccountInfo(ctx context.Context, a asset.Item) (account
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetCurrencyStateSnapshot overrides interface function
|
||||
func (f fExchange) GetCurrencyStateSnapshot() ([]currencystate.Snapshot, error) {
|
||||
return []currencystate.Snapshot{
|
||||
{
|
||||
Code: currency.BTC,
|
||||
Asset: asset.Spot,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CanTradePair overrides interface function
|
||||
func (f fExchange) CanTradePair(p currency.Pair, a asset.Item) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CanTrade overrides interface function
|
||||
func (f fExchange) CanTrade(c currency.Code, a asset.Item) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CanWithdraw overrides interface function
|
||||
func (f fExchange) CanWithdraw(c currency.Code, a asset.Item) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CanDeposit overrides interface function
|
||||
func (f fExchange) CanDeposit(c currency.Code, a asset.Item) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sets up everything required to run any function inside rpcserver
|
||||
// Only use if you require a database, this makes tests slow
|
||||
func RPCTestSetup(t *testing.T) *Engine {
|
||||
@@ -238,7 +270,7 @@ func TestGetSavedTrades(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
_, err = s.GetSavedTrades(context.Background(), &gctrpc.GetSavedTradesRequest{
|
||||
Exchange: "fake",
|
||||
Exchange: fakeExchangeName,
|
||||
Pair: &gctrpc.CurrencyPair{
|
||||
Delimiter: currency.DashDelimiter,
|
||||
Base: currency.BTC.String(),
|
||||
@@ -849,7 +881,7 @@ func TestGetRecentTrades(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
_, err = s.GetRecentTrades(context.Background(), &gctrpc.GetSavedTradesRequest{
|
||||
Exchange: "fake",
|
||||
Exchange: fakeExchangeName,
|
||||
Pair: &gctrpc.CurrencyPair{
|
||||
Delimiter: currency.DashDelimiter,
|
||||
Base: currency.BTC.String(),
|
||||
@@ -897,7 +929,7 @@ func TestGetHistoricTrades(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
err = s.GetHistoricTrades(&gctrpc.GetSavedTradesRequest{
|
||||
Exchange: "fake",
|
||||
Exchange: fakeExchangeName,
|
||||
Pair: &gctrpc.CurrencyPair{
|
||||
Delimiter: currency.DashDelimiter,
|
||||
Base: currency.BTC.String(),
|
||||
@@ -934,7 +966,7 @@ func TestGetAccountInfo(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b := exch.GetBase()
|
||||
b.Name = "fake"
|
||||
b.Name = fakeExchangeName
|
||||
b.Enabled = true
|
||||
b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore)
|
||||
b.CurrencyPairs.Pairs[asset.Spot] = ¤cy.PairStore{
|
||||
@@ -945,7 +977,7 @@ func TestGetAccountInfo(t *testing.T) {
|
||||
}
|
||||
em.Add(fakeExchange)
|
||||
s := RPCServer{Engine: &Engine{ExchangeManager: em}}
|
||||
_, err = s.GetAccountInfo(context.Background(), &gctrpc.GetAccountInfoRequest{Exchange: "fake", AssetType: asset.Spot.String()})
|
||||
_, err = s.GetAccountInfo(context.Background(), &gctrpc.GetAccountInfoRequest{Exchange: fakeExchangeName, AssetType: asset.Spot.String()})
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("received '%v', expected '%v'", err, nil)
|
||||
}
|
||||
@@ -959,7 +991,7 @@ func TestUpdateAccountInfo(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b := exch.GetBase()
|
||||
b.Name = "fake"
|
||||
b.Name = fakeExchangeName
|
||||
b.Enabled = true
|
||||
b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore)
|
||||
b.CurrencyPairs.Pairs[asset.Spot] = ¤cy.PairStore{
|
||||
@@ -971,18 +1003,18 @@ func TestUpdateAccountInfo(t *testing.T) {
|
||||
em.Add(fakeExchange)
|
||||
s := RPCServer{Engine: &Engine{ExchangeManager: em}}
|
||||
|
||||
_, err = s.GetAccountInfo(context.Background(), &gctrpc.GetAccountInfoRequest{Exchange: "fake", AssetType: asset.Spot.String()})
|
||||
_, err = s.GetAccountInfo(context.Background(), &gctrpc.GetAccountInfoRequest{Exchange: fakeExchangeName, AssetType: asset.Spot.String()})
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("received '%v', expected '%v'", err, nil)
|
||||
}
|
||||
|
||||
_, err = s.UpdateAccountInfo(context.Background(), &gctrpc.GetAccountInfoRequest{Exchange: "fake", AssetType: asset.Futures.String()})
|
||||
_, err = s.UpdateAccountInfo(context.Background(), &gctrpc.GetAccountInfoRequest{Exchange: fakeExchangeName, AssetType: asset.Futures.String()})
|
||||
if !errors.Is(err, errAssetTypeDisabled) {
|
||||
t.Errorf("received '%v', expected '%v'", err, errAssetTypeDisabled)
|
||||
}
|
||||
|
||||
_, err = s.UpdateAccountInfo(context.Background(), &gctrpc.GetAccountInfoRequest{
|
||||
Exchange: "fake",
|
||||
Exchange: fakeExchangeName,
|
||||
AssetType: asset.Spot.String(),
|
||||
})
|
||||
if !errors.Is(err, nil) {
|
||||
@@ -1862,3 +1894,87 @@ func TestUpdateDataHistoryJobPrerequisite(t *testing.T) {
|
||||
t.Errorf("received %v, expected %v", err, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCurrencyStateGetAll(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := (&RPCServer{Engine: &Engine{}}).CurrencyStateGetAll(context.Background(),
|
||||
&gctrpc.CurrencyStateGetAllRequest{Exchange: fakeExchangeName})
|
||||
if !errors.Is(err, ErrSubSystemNotStarted) {
|
||||
t.Errorf("received %v, expected %v", err, ErrSubSystemNotStarted)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCurrencyStateWithdraw(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := (&RPCServer{
|
||||
Engine: &Engine{},
|
||||
}).CurrencyStateWithdraw(context.Background(),
|
||||
&gctrpc.CurrencyStateWithdrawRequest{
|
||||
Exchange: "wow"})
|
||||
if !errors.Is(err, ErrSubSystemNotStarted) {
|
||||
t.Fatalf("received: %v, but expected: %v", err, ErrSubSystemNotStarted)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCurrencyStateDeposit(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := (&RPCServer{
|
||||
Engine: &Engine{},
|
||||
}).CurrencyStateDeposit(context.Background(),
|
||||
&gctrpc.CurrencyStateDepositRequest{Exchange: "wow"})
|
||||
if !errors.Is(err, ErrSubSystemNotStarted) {
|
||||
t.Fatalf("received: %v, but expected: %v", err, ErrSubSystemNotStarted)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCurrencyStateTrading(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := (&RPCServer{
|
||||
Engine: &Engine{},
|
||||
}).CurrencyStateTrading(context.Background(),
|
||||
&gctrpc.CurrencyStateTradingRequest{Exchange: "wow"})
|
||||
if !errors.Is(err, ErrSubSystemNotStarted) {
|
||||
t.Fatalf("received: %v, but expected: %v", err, ErrSubSystemNotStarted)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCurrencyStateTradingPair(t *testing.T) {
|
||||
t.Parallel()
|
||||
em := SetupExchangeManager()
|
||||
exch, err := em.NewExchangeByName(testExchange)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b := exch.GetBase()
|
||||
b.Name = fakeExchangeName
|
||||
b.Enabled = true
|
||||
|
||||
cp, err := currency.NewPairFromString("btc-usd")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore)
|
||||
b.CurrencyPairs.Pairs[asset.Spot] = ¤cy.PairStore{
|
||||
AssetEnabled: convert.BoolPtr(true),
|
||||
ConfigFormat: ¤cy.PairFormat{},
|
||||
Available: currency.Pairs{cp},
|
||||
Enabled: currency.Pairs{cp},
|
||||
}
|
||||
fakeExchange := fExchange{
|
||||
IBotExchange: exch,
|
||||
}
|
||||
em.Add(fakeExchange)
|
||||
s := RPCServer{Engine: &Engine{ExchangeManager: em,
|
||||
currencyStateManager: &CurrencyStateManager{started: 1, iExchangeManager: em}}}
|
||||
|
||||
_, err = s.CurrencyStateTradingPair(context.Background(),
|
||||
&gctrpc.CurrencyStateTradingPairRequest{
|
||||
Exchange: fakeExchangeName,
|
||||
Pair: "btc-usd",
|
||||
Asset: "spot",
|
||||
})
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: %v, but expected: %v", err, nil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"time"
|
||||
|
||||
dbwithdraw "github.com/thrasher-corp/gocryptotrader/database/repository/withdraw"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/currencystate"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
|
||||
)
|
||||
@@ -45,6 +47,12 @@ func (m *WithdrawManager) SubmitWithdrawal(ctx context.Context, req *withdraw.Re
|
||||
RequestDetails: *req,
|
||||
}
|
||||
|
||||
// Determines if the currency can be withdrawn from the exchange
|
||||
errF := exch.CanWithdraw(req.Currency, asset.Spot)
|
||||
if errF != nil && !errors.Is(errF, currencystate.ErrCurrencyStateNotFound) { // Suppress not found error
|
||||
return nil, errF
|
||||
}
|
||||
|
||||
if m.isDryRun {
|
||||
log.Warnln(log.Global, "Dry run enabled, no withdrawal request will be submitted or have an event created")
|
||||
resp.ID = withdraw.DryRunID
|
||||
|
||||
@@ -25,6 +25,14 @@ func withdrawManagerTestHelper(t *testing.T) (*ExchangeManager, *portfolioManage
|
||||
em := SetupExchangeManager()
|
||||
b := new(binance.Binance)
|
||||
b.SetDefaults()
|
||||
cfg, err := b.GetDefaultConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = b.Setup(cfg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
em.Add(b)
|
||||
pm, err := setupPortfolioManager(em, 0, &portfolio.Base{Addresses: []portfolio.Address{}})
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user