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:
Ryan O'Hara-Reid
2021-09-27 13:33:49 +10:00
committed by GitHub
parent 1d7c656665
commit 5dfbbf84de
48 changed files with 4685 additions and 1110 deletions

View File

@@ -10,26 +10,26 @@ Rots | https://github.com/Rots
vazha | https://github.com/vazha
ermalguni | https://github.com/ermalguni
MadCozBadd | https://github.com/MadCozBadd
ydm | https://github.com/ydm
vadimzhukck | https://github.com/vadimzhukck
140am | https://github.com/140am
marcofranssen | https://github.com/marcofranssen
ydm | https://github.com/ydm
lrascao | https://github.com/lrascao
dackroyd | https://github.com/dackroyd
cranktakular | https://github.com/cranktakular
woshidama323 | https://github.com/woshidama323
yangrq1018 | https://github.com/yangrq1018
crackcomm | https://github.com/crackcomm
azhang | https://github.com/azhang
andreygrehov | https://github.com/andreygrehov
bretep | https://github.com/bretep
Christian-Achilli | https://github.com/Christian-Achilli
lrascao | https://github.com/lrascao
MarkDzulko | https://github.com/MarkDzulko
yangrq1018 | https://github.com/yangrq1018
TaltaM | https://github.com/TaltaM
gam-phon | https://github.com/gam-phon
cornelk | https://github.com/cornelk
if1live | https://github.com/if1live
lozdog245 | https://github.com/lozdog245
soxipy | https://github.com/soxipy
tk42 | https://github.com/tk42
mshogin | https://github.com/mshogin
herenow | https://github.com/herenow
@@ -50,3 +50,4 @@ CodeLingoTeam | https://github.com/CodeLingoTeam
Daanikus | https://github.com/Daanikus
CodeLingoBot | https://github.com/CodeLingoBot
blombard | https://github.com/blombard
soxipy | https://github.com/soxipy

View File

@@ -142,36 +142,36 @@ Binaries will be published once the codebase reaches a stable condition.
|User|Contribution Amount|
|--|--|
| [thrasher-](https://github.com/thrasher-) | 656 |
| [shazbert](https://github.com/shazbert) | 214 |
| [gloriousCode](https://github.com/gloriousCode) | 189 |
| [thrasher-](https://github.com/thrasher-) | 658 |
| [shazbert](https://github.com/shazbert) | 222 |
| [gloriousCode](https://github.com/gloriousCode) | 190 |
| [dependabot-preview[bot]](https://github.com/apps/dependabot-preview) | 88 |
| [xtda](https://github.com/xtda) | 47 |
| [dependabot[bot]](https://github.com/apps/dependabot) | 20 |
| [dependabot[bot]](https://github.com/apps/dependabot) | 22 |
| [Rots](https://github.com/Rots) | 15 |
| [vazha](https://github.com/vazha) | 15 |
| [ermalguni](https://github.com/ermalguni) | 14 |
| [MadCozBadd](https://github.com/MadCozBadd) | 13 |
| [ydm](https://github.com/ydm) | 11 |
| [vadimzhukck](https://github.com/vadimzhukck) | 10 |
| [140am](https://github.com/140am) | 8 |
| [marcofranssen](https://github.com/marcofranssen) | 8 |
| [ydm](https://github.com/ydm) | 8 |
| [lrascao](https://github.com/lrascao) | 6 |
| [dackroyd](https://github.com/dackroyd) | 5 |
| [cranktakular](https://github.com/cranktakular) | 5 |
| [woshidama323](https://github.com/woshidama323) | 3 |
| [yangrq1018](https://github.com/yangrq1018) | 3 |
| [crackcomm](https://github.com/crackcomm) | 3 |
| [azhang](https://github.com/azhang) | 2 |
| [andreygrehov](https://github.com/andreygrehov) | 2 |
| [bretep](https://github.com/bretep) | 2 |
| [Christian-Achilli](https://github.com/Christian-Achilli) | 2 |
| [lrascao](https://github.com/lrascao) | 2 |
| [MarkDzulko](https://github.com/MarkDzulko) | 2 |
| [yangrq1018](https://github.com/yangrq1018) | 2 |
| [TaltaM](https://github.com/TaltaM) | 2 |
| [gam-phon](https://github.com/gam-phon) | 2 |
| [cornelk](https://github.com/cornelk) | 2 |
| [if1live](https://github.com/if1live) | 2 |
| [lozdog245](https://github.com/lozdog245) | 2 |
| [soxipy](https://github.com/soxipy) | 2 |
| [tk42](https://github.com/tk42) | 2 |
| [mshogin](https://github.com/mshogin) | 2 |
| [herenow](https://github.com/herenow) | 2 |
@@ -192,3 +192,4 @@ Binaries will be published once the codebase reaches a stable condition.
| [Daanikus](https://github.com/Daanikus) | 1 |
| [CodeLingoBot](https://github.com/CodeLingoBot) | 1 |
| [blombard](https://github.com/blombard) | 1 |
| [soxipy](https://github.com/soxipy) | 2 |

View File

@@ -141,6 +141,14 @@ func TestPlaceOrder(t *testing.T) {
t.Fatal(err)
}
exch.SetDefaults()
cfg, err := exch.GetDefaultConfig()
if err != nil {
t.Fatal(err)
}
err = exch.Setup(cfg)
if err != nil {
t.Fatal(err)
}
em.Add(exch)
bot.ExchangeManager = em
bot.OrderManager, err = engine.SetupOrderManager(em, &engine.CommunicationManager{}, &bot.ServicesWG, false)
@@ -191,6 +199,14 @@ func TestExecuteOrder(t *testing.T) {
t.Fatal(err)
}
exch.SetDefaults()
cfg, err := exch.GetDefaultConfig()
if err != nil {
t.Fatal(err)
}
err = exch.Setup(cfg)
if err != nil {
t.Fatal(err)
}
em.Add(exch)
bot.ExchangeManager = em
bot.OrderManager, err = engine.SetupOrderManager(em, &engine.CommunicationManager{}, &bot.ServicesWG, false)
@@ -287,6 +303,15 @@ func TestExecuteOrderBuySellSizeLimit(t *testing.T) {
t.Fatal(err)
}
exch.SetDefaults()
cfg, err := exch.GetDefaultConfig()
if err != nil {
t.Fatal(err)
}
err = exch.Setup(cfg)
if err != nil {
t.Fatal(err)
}
em.Add(exch)
bot.ExchangeManager = em
bot.OrderManager, err = engine.SetupOrderManager(em, &engine.CommunicationManager{}, &bot.ServicesWG, false)

View File

@@ -273,6 +273,11 @@ func main() {
URL: "https://github.com/blombard",
Contributions: 1,
},
{
Login: "soxipy",
URL: "https://github.com/soxipy",
Contributions: 2,
},
}...)
if verbose {

View File

@@ -0,0 +1,14 @@
{{define "engine currency_state_manager" -}}
{{template "header" .}}
## Current Features for {{.CapitalName}}
+ 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.
{{template "contributions"}}
{{template "donations" .}}
{{end}}

View File

@@ -11,6 +11,11 @@ implementation
+ A guide on implementing API support for a new exchange can be found [here](../docs/ADD_NEW_EXCHANGE.md)
## websocket notes
+ If contributing websocket improvements, please make sure order reports
follow [these rules](../docs/WS_ORDER_EVENTS.md).
### Please click GoDocs chevron above to view current GoDoc information for this package
{{template "contributions"}}
{{template "donations" .}}

View File

@@ -0,0 +1,300 @@
package main
import (
"github.com/thrasher-corp/gocryptotrader/gctrpc"
"github.com/urfave/cli/v2"
)
var currencyStateManagementCommand = &cli.Command{
Name: "currencystate",
Usage: "execute exchange currency state management command",
ArgsUsage: "<command> <args>",
Subcommands: []*cli.Command{
{
Name: "getall",
Usage: "fetch all currency states associated with an exchange",
ArgsUsage: "<exchange>",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "exchange",
Usage: "the exchange to act on",
},
},
Action: stateGetAll,
},
{
Name: "withdraw",
Usage: "returns if the currency can be withdrawn from the exchange",
ArgsUsage: "<exchange> <code> <asset>",
Flags: stateFlags,
Action: stateGetWithdrawal,
},
{
Name: "deposit",
Usage: "returns if the currency can be deposited onto an exchange",
ArgsUsage: "<exchange> <code> <asset>",
Flags: stateFlags,
Action: stateGetDeposit,
},
{
Name: "trade",
Usage: "returns if the currency can be traded on the exchange",
ArgsUsage: "<exchange> <code> <asset>",
Flags: stateFlags,
Action: stateGetTrading,
},
{
Name: "tradepair",
Usage: "returns if the currency pair can be traded on the exchange",
ArgsUsage: "<exchange> <pair> <asset>",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "exchange",
Usage: "the exchange to act on",
},
&cli.StringFlag{
Name: "pair",
Usage: "the currency pair e.g. btc-usd",
},
&cli.StringFlag{
Name: "asset",
Usage: "the asset type",
},
},
Action: stateGetPairTrading,
},
},
}
var stateFlags = []cli.Flag{
&cli.StringFlag{
Name: "exchange",
Usage: "the exchange to act on",
},
&cli.StringFlag{
Name: "code",
Usage: "the currency code",
},
&cli.StringFlag{
Name: "asset",
Usage: "the asset type",
},
}
func stateGetAll(c *cli.Context) error {
if c.NArg() == 0 && c.NumFlags() == 0 {
return cli.ShowSubcommandHelp(c)
}
var exchange string
if c.IsSet("exchange") {
exchange = c.String("exchange")
} else {
exchange = c.Args().First()
}
conn, cancel, err := setupClient(c)
if err != nil {
return err
}
defer closeConn(conn, cancel)
client := gctrpc.NewGoCryptoTraderClient(conn)
result, err := client.CurrencyStateGetAll(c.Context,
&gctrpc.CurrencyStateGetAllRequest{Exchange: exchange},
)
if err != nil {
return err
}
jsonOutput(result)
return nil
}
func stateGetDeposit(c *cli.Context) error {
if c.NArg() == 0 && c.NumFlags() == 0 {
return cli.ShowSubcommandHelp(c)
}
var exchange string
if c.IsSet("exchange") {
exchange = c.String("exchange")
} else {
exchange = c.Args().First()
}
var code string
if c.IsSet("code") {
code = c.String("code")
} else {
code = c.Args().Get(1)
}
var a string
if c.IsSet("asset") {
a = c.String("asset")
} else {
a = c.Args().Get(2)
}
conn, cancel, err := setupClient(c)
if err != nil {
return err
}
defer closeConn(conn, cancel)
client := gctrpc.NewGoCryptoTraderClient(conn)
result, err := client.CurrencyStateDeposit(c.Context,
&gctrpc.CurrencyStateDepositRequest{
Exchange: exchange,
Code: code,
Asset: a},
)
if err != nil {
return err
}
jsonOutput(result)
return nil
}
func stateGetWithdrawal(c *cli.Context) error {
if c.NArg() == 0 && c.NumFlags() == 0 {
return cli.ShowSubcommandHelp(c)
}
var exchange string
if c.IsSet("exchange") {
exchange = c.String("exchange")
} else {
exchange = c.Args().First()
}
var code string
if c.IsSet("code") {
code = c.String("code")
} else {
code = c.Args().Get(1)
}
var a string
if c.IsSet("asset") {
a = c.String("asset")
} else {
a = c.Args().Get(2)
}
conn, cancel, err := setupClient(c)
if err != nil {
return err
}
defer closeConn(conn, cancel)
client := gctrpc.NewGoCryptoTraderClient(conn)
result, err := client.CurrencyStateWithdraw(c.Context,
&gctrpc.CurrencyStateWithdrawRequest{
Exchange: exchange,
Code: code,
Asset: a},
)
if err != nil {
return err
}
jsonOutput(result)
return nil
}
func stateGetTrading(c *cli.Context) error {
if c.NArg() == 0 && c.NumFlags() == 0 {
return cli.ShowSubcommandHelp(c)
}
var exchange string
if c.IsSet("exchange") {
exchange = c.String("exchange")
} else {
exchange = c.Args().First()
}
var code string
if c.IsSet("code") {
code = c.String("code")
} else {
code = c.Args().Get(1)
}
var a string
if c.IsSet("asset") {
a = c.String("asset")
} else {
a = c.Args().Get(2)
}
conn, cancel, err := setupClient(c)
if err != nil {
return err
}
defer closeConn(conn, cancel)
client := gctrpc.NewGoCryptoTraderClient(conn)
result, err := client.CurrencyStateTrading(c.Context,
&gctrpc.CurrencyStateTradingRequest{
Exchange: exchange,
Code: code,
Asset: a},
)
if err != nil {
return err
}
jsonOutput(result)
return nil
}
func stateGetPairTrading(c *cli.Context) error {
if c.NArg() == 0 && c.NumFlags() == 0 {
return cli.ShowSubcommandHelp(c)
}
var exchange string
if c.IsSet("exchange") {
exchange = c.String("exchange")
} else {
exchange = c.Args().First()
}
var pair string
if c.IsSet("pair") {
pair = c.String("pair")
} else {
pair = c.Args().Get(1)
}
var a string
if c.IsSet("asset") {
a = c.String("asset")
} else {
a = c.Args().Get(2)
}
conn, cancel, err := setupClient(c)
if err != nil {
return err
}
defer closeConn(conn, cancel)
client := gctrpc.NewGoCryptoTraderClient(conn)
result, err := client.CurrencyStateTradingPair(c.Context,
&gctrpc.CurrencyStateTradingPairRequest{
Exchange: exchange,
Pair: pair,
Asset: a},
)
if err != nil {
return err
}
jsonOutput(result)
return nil
}

View File

@@ -161,6 +161,7 @@ func main() {
websocketManagerCommand,
tradeCommand,
dataHistoryCommands,
currencyStateManagementCommand,
}
ctx, cancel := context.WithCancel(context.Background())

View File

@@ -1422,6 +1422,19 @@ func (c *Config) CheckDataHistoryMonitorConfig() {
}
}
// CheckCurrencyStateManager ensures the currency state config is valid, or sets
// default values
func (c *Config) CheckCurrencyStateManager() {
m.Lock()
defer m.Unlock()
if c.CurrencyStateManager.Delay <= 0 {
c.CurrencyStateManager.Delay = defaultCurrencyStateManagerDelay
}
if c.CurrencyStateManager.Enabled == nil { // default on, when being upgraded
c.CurrencyStateManager.Enabled = convert.BoolPtr(true)
}
}
// CheckConnectionMonitorConfig checks and if zero value assigns default values
func (c *Config) CheckConnectionMonitorConfig() {
m.Lock()
@@ -1769,6 +1782,7 @@ func (c *Config) CheckConfig() error {
c.CheckConnectionMonitorConfig()
c.CheckDataHistoryMonitorConfig()
c.CheckCurrencyStateManager()
c.CheckCommunicationsConfig()
c.CheckClientBankAccounts()
c.CheckBankAccountConfig()

View File

@@ -37,6 +37,7 @@ const (
DefaultAPISecret = "Secret"
DefaultAPIClientID = "ClientID"
defaultDataHistoryMonitorCheckTimer = time.Minute
defaultCurrencyStateManagerDelay = time.Minute
defaultMaxJobsPerCycle = 5
)
@@ -72,23 +73,24 @@ var (
// prestart management of Portfolio, Communications, Webserver and Enabled
// Exchanges
type Config struct {
Name string `json:"name"`
DataDirectory string `json:"dataDirectory"`
EncryptConfig int `json:"encryptConfig"`
GlobalHTTPTimeout time.Duration `json:"globalHTTPTimeout"`
Database database.Config `json:"database"`
Logging log.Config `json:"logging"`
ConnectionMonitor ConnectionMonitorConfig `json:"connectionMonitor"`
DataHistoryManager DataHistoryManager `json:"dataHistoryManager"`
Profiler Profiler `json:"profiler"`
NTPClient NTPClientConfig `json:"ntpclient"`
GCTScript gctscript.Config `json:"gctscript"`
Currency CurrencyConfig `json:"currencyConfig"`
Communications base.CommunicationsConfig `json:"communications"`
RemoteControl RemoteControlConfig `json:"remoteControl"`
Portfolio portfolio.Base `json:"portfolioAddresses"`
Exchanges []ExchangeConfig `json:"exchanges"`
BankAccounts []banking.Account `json:"bankAccounts"`
Name string `json:"name"`
DataDirectory string `json:"dataDirectory"`
EncryptConfig int `json:"encryptConfig"`
GlobalHTTPTimeout time.Duration `json:"globalHTTPTimeout"`
Database database.Config `json:"database"`
Logging log.Config `json:"logging"`
ConnectionMonitor ConnectionMonitorConfig `json:"connectionMonitor"`
DataHistoryManager DataHistoryManager `json:"dataHistoryManager"`
CurrencyStateManager CurrencyStateManager `json:"currencyStateManager"`
Profiler Profiler `json:"profiler"`
NTPClient NTPClientConfig `json:"ntpclient"`
GCTScript gctscript.Config `json:"gctscript"`
Currency CurrencyConfig `json:"currencyConfig"`
Communications base.CommunicationsConfig `json:"communications"`
RemoteControl RemoteControlConfig `json:"remoteControl"`
Portfolio portfolio.Base `json:"portfolioAddresses"`
Exchanges []ExchangeConfig `json:"exchanges"`
BankAccounts []banking.Account `json:"bankAccounts"`
// Deprecated config settings, will be removed at a future date
Webserver *WebserverConfig `json:"webserver,omitempty"`
@@ -110,6 +112,13 @@ type DataHistoryManager struct {
Verbose bool `json:"verbose"`
}
// CurrencyStateManager defines a set of configuration options for the currency
// state manager
type CurrencyStateManager struct {
Enabled *bool `json:"enabled"`
Delay time.Duration `json:"delay"`
}
// ConnectionMonitorConfig defines the connection monitor variables to ensure
// that there is internet connectivity
type ConnectionMonitorConfig struct {

View 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
}

View File

@@ -0,0 +1,48 @@
# GoCryptoTrader package State manager
<img src="/common/gctlogo.png?raw=true" width="350px" height="350px" hspace="70">
[![Build Status](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml)
[![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE)
[![GoDoc](https://godoc.org/github.com/thrasher-corp/gocryptotrader?status.svg)](https://godoc.org/github.com/thrasher-corp/gocryptotrader/engine/state_manager)
[![Coverage Status](http://codecov.io/github/thrasher-corp/gocryptotrader/coverage.svg?branch=master)](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master)
[![Go Report Card](https://goreportcard.com/badge/github.com/thrasher-corp/gocryptotrader)](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***

View 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})
}

View File

@@ -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 ||

View File

@@ -36,6 +36,7 @@ type Settings struct {
EnableGCTScriptManager bool
EnableNTPClient bool
EnableWebsocketRoutine bool
EnableCurrencyStateManager bool
EventManagerDelay time.Duration
Verbose bool

View File

@@ -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)
}

View File

@@ -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))
}
}

View File

@@ -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

View File

@@ -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,
}

View File

@@ -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))
}

View File

@@ -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] = &currency.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] = &currency.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] = &currency.PairStore{
AssetEnabled: convert.BoolPtr(true),
ConfigFormat: &currency.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)
}
}

View File

@@ -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

View File

@@ -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 {

View File

@@ -29,6 +29,11 @@ implementation
+ A guide on implementing API support for a new exchange can be found [here](../docs/ADD_NEW_EXCHANGE.md)
## websocket notes
+ If contributing websocket improvements, please make sure order reports
follow [these rules](../docs/WS_ORDER_EVENTS.md).
### Please click GoDocs chevron above to view current GoDoc information for this package
## Contribution
@@ -42,9 +47,6 @@ When submitting a PR, please abide by our coding 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.
If contributing websocket improvements, please make sure order reports
follow [these rules](../docs/WS_ORDER_EVENTS.md).
## Donations
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">

83
exchanges/alert/alert.go Normal file
View File

@@ -0,0 +1,83 @@
package alert
import (
"sync"
"sync/atomic"
)
// Notice defines fields required to alert sub-systems of a change of state so a
// routine can re-check in memory data
type Notice struct {
// Channel to wait for an alert on.
forAlert chan struct{}
// Lets the updater functions know if there are any routines waiting for an
// alert.
sema uint32
// After closing the forAlert channel this will notify when all the routines
// that have waited, have completed their checks.
wg sync.WaitGroup
// Segregated lock only for waiting routines, so as this does not interfere
// with the main calling lock, this acts as a rolling gate.
m sync.Mutex
}
// Alert establishes a state change on the required struct.
func (n *Notice) Alert() {
// CompareAndSwap is used to swap from 1 -> 2 so we don't keep actuating
// the opposing compare and swap in method wait. This function can return
// freely when an alert operation is in process.
if !atomic.CompareAndSwapUint32(&n.sema, 1, 2) {
// Return if no waiting routines or currently alerting.
return
}
go n.actuate()
}
// Actuate lock in a different routine, as alerting is a second order priority
// compared to updating and releasing calling routine.
func (n *Notice) actuate() {
n.m.Lock()
// Closing; alerts many waiting routines.
close(n.forAlert)
// Wait for waiting routines to receive alert and return.
n.wg.Wait()
atomic.SwapUint32(&n.sema, 0) // Swap back to neutral state.
n.m.Unlock()
}
// Wait pauses calling routine until change of state has been established via
// notice method Alert. Kick allows for cancellation of waiting or when the
// caller has been shut down, if this is not needed it can be set to nil. This
// returns a channel so strategies can cleanly wait on a select statement case.
func (n *Notice) Wait(kick <-chan struct{}) <-chan bool {
reply := make(chan bool)
n.m.Lock()
n.wg.Add(1)
if atomic.CompareAndSwapUint32(&n.sema, 0, 1) {
n.forAlert = make(chan struct{})
}
go n.hold(reply, kick)
n.m.Unlock()
return reply
}
// hold waits on either channel in the event that the routine has
// finished/cancelled or an alert from an update has occurred.
func (n *Notice) hold(ch chan<- bool, kick <-chan struct{}) {
select {
// In a select statement, if by chance there is no receiver or its late,
// we can still close and return, limiting dead-lock potential.
case <-n.forAlert: // Main waiting channel from alert
select {
case ch <- false:
default:
}
case <-kick: // This can be nil.
select {
case ch <- true:
default:
}
}
n.wg.Done()
close(ch)
}

View File

@@ -0,0 +1,92 @@
package alert
import (
"log"
"sync"
"testing"
"time"
)
func TestWait(t *testing.T) {
wait := Notice{}
var wg sync.WaitGroup
// standard alert
wg.Add(100)
for x := 0; x < 100; x++ {
go func() {
w := wait.Wait(nil)
wg.Done()
if <-w {
log.Fatal("incorrect routine wait response for alert expecting false")
}
wg.Done()
}()
}
wg.Wait()
wg.Add(100)
isLeaky(&wait, nil, t)
wait.Alert()
wg.Wait()
isLeaky(&wait, nil, t)
// use kick
ch := make(chan struct{})
wg.Add(100)
for x := 0; x < 100; x++ {
go func() {
w := wait.Wait(ch)
wg.Done()
if !<-w {
log.Fatal("incorrect routine wait response for kick expecting true")
}
wg.Done()
}()
}
wg.Wait()
wg.Add(100)
isLeaky(&wait, ch, t)
close(ch)
wg.Wait()
ch = make(chan struct{})
isLeaky(&wait, ch, t)
// late receivers
wg.Add(100)
for x := 0; x < 100; x++ {
go func(x int) {
bb := wait.Wait(ch)
wg.Done()
if x%2 == 0 {
time.Sleep(time.Millisecond * 5)
}
b := <-bb
if b {
log.Fatal("incorrect routine wait response since we call alert below; expecting false")
}
wg.Done()
}(x)
}
wg.Wait()
wg.Add(100)
isLeaky(&wait, ch, t)
wait.Alert()
wg.Wait()
isLeaky(&wait, ch, t)
}
// isLeaky tests to see if the wait functionality is returning an abnormal
// channel that is operational when it shouldn't be.
func isLeaky(a *Notice, ch chan struct{}, t *testing.T) {
t.Helper()
check := a.Wait(ch)
time.Sleep(time.Millisecond * 5) // When we call wait a routine for hold is
// spawned, so for a test we need to add in a time for goschedular to allow
// routine to actually wait on the forAlert and kick channels
select {
case <-check:
t.Fatal("leaky waiter")
default:
}
}

View File

@@ -480,7 +480,7 @@ func (b *Bitfinex) GetDerivativeStatusInfo(ctx context.Context, keys, startTime,
if response.NextFundingEventTS, ok = result[z][8].(float64); !ok {
return finalResp, fmt.Errorf("%v GetDerivativeStatusInfo: %w for NextFundingEventTS", b.Name, errTypeAssert)
}
if response.NextFundingAccured, ok = result[z][9].(float64); !ok {
if response.NextFundingAccrued, ok = result[z][9].(float64); !ok {
return finalResp, fmt.Errorf("%v GetDerivativeStatusInfo: %w for NextFundingAccrued", b.Name, errTypeAssert)
}
if response.NextFundingStep, ok = result[z][10].(float64); !ok {
@@ -493,8 +493,17 @@ func (b *Bitfinex) GetDerivativeStatusInfo(ctx context.Context, keys, startTime,
return finalResp, fmt.Errorf("%v GetDerivativeStatusInfo: %w for MarkPrice", b.Name, errTypeAssert)
}
if response.OpenInterest, ok = result[z][18].(float64); !ok {
return finalResp, fmt.Errorf("%v GetDerivativeStatusInfo: %w for OpenInterest", b.Name, errTypeAssert)
switch t := result[z][18].(type) {
case float64:
response.OpenInterest = t
case nil:
break // OpenInterest will default to 0
default:
return finalResp, fmt.Errorf("%v GetDerivativeStatusInfo: %w for OpenInterest. Type received: %v",
b.Name,
errTypeAssert,
t,
)
}
finalResp = append(finalResp, response)
}

View File

@@ -116,7 +116,7 @@ type DerivativeDataResponse struct {
MarkPrice float64
InsuranceFundBalance float64
NextFundingEventTS float64
NextFundingAccured float64
NextFundingAccrued float64
NextFundingStep float64
CurrentFunding float64
OpenInterest float64

View File

@@ -150,6 +150,21 @@ func (b *Bithumb) GetAssetStatus(ctx context.Context, symbol string) (*Status, e
return &response, nil
}
// GetAssetStatusAll returns the withdrawal and deposit status for all symbols
func (b *Bithumb) GetAssetStatusAll(ctx context.Context) (*StatusAll, error) {
var response StatusAll
err := b.SendHTTPRequest(ctx, exchange.RestSpot, publicAssetStatus+"ALL", &response)
if err != nil {
return nil, err
}
if response.Status != noError {
return nil, errors.New(response.Message)
}
return &response, nil
}
// GetTransactionHistory returns recent transactions
//
// symbol e.g. "btc"

View File

@@ -741,3 +741,19 @@ func TestGetAssetStatus(t *testing.T) {
t.Fatalf("received: %v but expected: %v", err, nil)
}
}
func TestGetAssetStatusAll(t *testing.T) {
t.Parallel()
_, err := b.GetAssetStatusAll(context.Background())
if !errors.Is(err, nil) {
t.Fatalf("received: %v but expected: %v", err, nil)
}
}
func TestUpdateCurrencyStates(t *testing.T) {
t.Parallel()
err := b.UpdateCurrencyStates(context.Background(), asset.Spot)
if !errors.Is(err, nil) {
t.Fatalf("received: %v but expected: %v", err, nil)
}
}

View File

@@ -301,3 +301,14 @@ type Status struct {
} `json:"data"`
Message string `json:"message"`
}
// StatusAll defines the current exchange allowance to deposit or withdraw a
// currency
type StatusAll struct {
Status string `json:"status"`
Data map[string]struct {
DepositStatus int64 `json:"deposit_status"`
WithdrawalStatus int64 `json:"withdrawal_status"`
} `json:"data"`
Message string `json:"message"`
}

View File

@@ -11,11 +11,13 @@ import (
"time"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/convert"
"github.com/thrasher-corp/gocryptotrader/config"
"github.com/thrasher-corp/gocryptotrader/currency"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/account"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"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/orderbook"
@@ -891,3 +893,20 @@ func (b *Bithumb) UpdateOrderExecutionLimits(ctx context.Context, _ asset.Item)
}
return b.LoadLimits(limits)
}
// UpdateCurrencyStates updates currency states for exchange
func (b *Bithumb) UpdateCurrencyStates(ctx context.Context, a asset.Item) error {
status, err := b.GetAssetStatusAll(ctx)
if err != nil {
return err
}
payload := make(map[currency.Code]currencystate.Options)
for coin, options := range status.Data {
payload[currency.NewCode(coin)] = currencystate.Options{
Withdraw: convert.BoolPtr(options.WithdrawalStatus == 1),
Deposit: convert.BoolPtr(options.DepositStatus == 1),
}
}
return b.States.UpdateAll(a, payload)
}

View File

@@ -0,0 +1,312 @@
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
}

View File

@@ -0,0 +1,324 @@
package currencystate
import (
"errors"
"sync"
"testing"
"github.com/thrasher-corp/gocryptotrader/common/convert"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
)
func TestNewCurrencyStates(t *testing.T) {
if NewCurrencyStates() == nil {
t.Fatal("unexpected value")
}
}
func TestGetSnapshot(t *testing.T) {
t.Parallel()
_, err := (*States)(nil).GetCurrencyStateSnapshot()
if !errors.Is(err, errNilStates) {
t.Fatalf("received: %v, but expected: %v", err, errNilStates)
}
o, err := (&States{
m: map[asset.Item]map[*currency.Item]*Currency{
asset.Spot: {currency.BTC.Item: {
withdrawals: true,
deposits: true,
trading: true,
}},
},
}).GetCurrencyStateSnapshot()
if !errors.Is(err, nil) {
t.Fatalf("received: %v, but expected: %v", err, nil)
}
if o == nil {
t.Fatal("unexpected value")
}
}
func TestCanTradePair(t *testing.T) {
t.Parallel()
err := (*States)(nil).CanTradePair(currency.Pair{}, "")
if !errors.Is(err, errNilStates) {
t.Fatalf("received: %v, but expected: %v", err, errNilStates)
}
err = (&States{}).CanTradePair(currency.Pair{}, "")
if !errors.Is(err, errEmptyCurrency) {
t.Fatalf("received: %v, but expected: %v", err, errEmptyCurrency)
}
cp := currency.NewPair(currency.BTC, currency.USD)
err = (&States{}).CanTradePair(cp, "")
if !errors.Is(err, asset.ErrNotSupported) {
t.Fatalf("received: %v, but expected: %v", err, asset.ErrNotSupported)
}
err = (&States{}).CanTradePair(cp, asset.Spot)
if !errors.Is(err, nil) { // not found but default to operational
t.Fatalf("received: %v, but expected: %v", err, nil)
}
err = (&States{
m: map[asset.Item]map[*currency.Item]*Currency{
asset.Spot: {
currency.BTC.Item: {trading: true},
currency.USD.Item: {trading: true},
},
},
}).CanTradePair(cp, asset.Spot)
if !errors.Is(err, nil) {
t.Fatalf("received: %v, but expected: %v", err, nil)
}
err = (&States{
m: map[asset.Item]map[*currency.Item]*Currency{
asset.Spot: {
currency.BTC.Item: {trading: false},
currency.USD.Item: {trading: true},
},
},
}).CanTradePair(cp, asset.Spot)
if !errors.Is(err, errTradingNotAllowed) {
t.Fatalf("received: %v, but expected: %v", err, errTradingNotAllowed)
}
err = (&States{
m: map[asset.Item]map[*currency.Item]*Currency{
asset.Spot: {
currency.BTC.Item: {trading: true},
currency.USD.Item: {trading: false},
},
},
}).CanTradePair(cp, asset.Spot)
if !errors.Is(err, errTradingNotAllowed) {
t.Fatalf("received: %v, but expected: %v", err, errTradingNotAllowed)
}
err = (&States{
m: map[asset.Item]map[*currency.Item]*Currency{
asset.Spot: {
currency.BTC.Item: {trading: false},
currency.USD.Item: {trading: false},
},
},
}).CanTradePair(cp, asset.Spot)
if !errors.Is(err, errTradingNotAllowed) {
t.Fatalf("received: %v, but expected: %v", err, errTradingNotAllowed)
}
}
func TestStatesCanTrade(t *testing.T) {
t.Parallel()
err := (*States)(nil).CanTrade(currency.Code{}, "")
if !errors.Is(err, errNilStates) {
t.Fatalf("received: %v, but expected: %v", err, errNilStates)
}
err = (&States{}).CanTrade(currency.Code{}, "")
if !errors.Is(err, errEmptyCurrency) {
t.Fatalf("received: %v, but expected: %v", err, errEmptyCurrency)
}
}
func TestStatesCanWithdraw(t *testing.T) {
t.Parallel()
err := (*States)(nil).CanWithdraw(currency.Code{}, "")
if !errors.Is(err, errNilStates) {
t.Fatalf("received: %v, but expected: %v", err, errNilStates)
}
err = (&States{}).CanWithdraw(currency.Code{}, "")
if !errors.Is(err, errEmptyCurrency) {
t.Fatalf("received: %v, but expected: %v", err, errEmptyCurrency)
}
err = (&States{
m: map[asset.Item]map[*currency.Item]*Currency{
asset.Spot: {
currency.BTC.Item: {withdrawals: true},
},
},
}).CanWithdraw(currency.BTC, asset.Spot)
if !errors.Is(err, nil) {
t.Fatalf("received: %v, but expected: %v", err, nil)
}
err = (&States{
m: map[asset.Item]map[*currency.Item]*Currency{
asset.Spot: {
currency.BTC.Item: {},
},
},
}).CanWithdraw(currency.BTC, asset.Spot)
if !errors.Is(err, errWithdrawalsNotAllowed) {
t.Fatalf("received: %v, but expected: %v", err, errWithdrawalsNotAllowed)
}
}
func TestStatesCanDeposit(t *testing.T) {
t.Parallel()
err := (*States)(nil).CanDeposit(currency.Code{}, "")
if !errors.Is(err, errNilStates) {
t.Fatalf("received: %v, but expected: %v", err, errNilStates)
}
err = (&States{}).CanDeposit(currency.Code{}, "")
if !errors.Is(err, errEmptyCurrency) {
t.Fatalf("received: %v, but expected: %v", err, errEmptyCurrency)
}
err = (&States{
m: map[asset.Item]map[*currency.Item]*Currency{
asset.Spot: {
currency.BTC.Item: {deposits: true},
},
},
}).CanDeposit(currency.BTC, asset.Spot)
if !errors.Is(err, nil) {
t.Fatalf("received: %v, but expected: %v", err, nil)
}
err = (&States{
m: map[asset.Item]map[*currency.Item]*Currency{
asset.Spot: {
currency.BTC.Item: {},
},
},
}).CanDeposit(currency.BTC, asset.Spot)
if !errors.Is(err, errDepositNotAllowed) {
t.Fatalf("received: %v, but expected: %v", err, errDepositNotAllowed)
}
}
func TestStatesUpdateAll(t *testing.T) {
t.Parallel()
err := (*States)(nil).UpdateAll("", nil)
if !errors.Is(err, errNilStates) {
t.Fatalf("received: %v, but expected: %v", err, errNilStates)
}
err = (&States{}).UpdateAll("", nil)
if !errors.Is(err, asset.ErrNotSupported) {
t.Fatalf("received: %v, but expected: %v", err, asset.ErrNotSupported)
}
err = (&States{}).UpdateAll(asset.Spot, nil)
if !errors.Is(err, errUpdatesAreNil) {
t.Fatalf("received: %v, but expected: %v", err, errUpdatesAreNil)
}
s := &States{
m: map[asset.Item]map[*currency.Item]*Currency{},
}
err = s.UpdateAll(asset.Spot, map[currency.Code]Options{
currency.BTC: {
Withdraw: convert.BoolPtr(true),
Trade: convert.BoolPtr(true),
Deposit: convert.BoolPtr(true)},
})
if !errors.Is(err, nil) {
t.Fatalf("received: %v, but expected: %v", err, nil)
}
err = s.UpdateAll(asset.Spot, map[currency.Code]Options{currency.BTC: {
Withdraw: convert.BoolPtr(false),
Deposit: convert.BoolPtr(false),
Trade: convert.BoolPtr(false),
}})
if !errors.Is(err, nil) {
t.Fatalf("received: %v, but expected: %v", err, nil)
}
c, err := s.Get(currency.BTC, asset.Spot)
if !errors.Is(err, nil) {
t.Fatalf("received: %v, but expected: %v", err, nil)
}
if c.CanDeposit() || c.CanTrade() || c.CanWithdraw() {
t.Fatal()
}
}
func TestStatesUpdate(t *testing.T) {
t.Parallel()
err := (*States)(nil).Update(currency.Code{}, "", Options{})
if !errors.Is(err, errNilStates) {
t.Fatalf("received: %v, but expected: %v", err, errNilStates)
}
err = (&States{}).Update(currency.Code{}, "", Options{})
if !errors.Is(err, errEmptyCurrency) {
t.Fatalf("received: %v, but expected: %v", err, errEmptyCurrency)
}
err = (&States{}).Update(currency.BTC, "", Options{})
if !errors.Is(err, asset.ErrNotSupported) {
t.Fatalf("received: %v, but expected: %v", err, asset.ErrNotSupported)
}
err = (&States{
m: map[asset.Item]map[*currency.Item]*Currency{
asset.Spot: {currency.BTC.Item: &Currency{}},
},
}).Update(currency.BTC, asset.Spot, Options{})
if !errors.Is(err, nil) {
t.Fatalf("received: %v, but expected: %v", err, nil)
}
}
func TestStatesGet(t *testing.T) {
t.Parallel()
_, err := (*States)(nil).Get(currency.Code{}, "")
if !errors.Is(err, errNilStates) {
t.Fatalf("received: %v, but expected: %v", err, errNilStates)
}
_, err = (&States{}).Get(currency.Code{}, "")
if !errors.Is(err, errEmptyCurrency) {
t.Fatalf("received: %v, but expected: %v", err, errEmptyCurrency)
}
_, err = (&States{}).Get(currency.BTC, "")
if !errors.Is(err, asset.ErrNotSupported) {
t.Fatalf("received: %v, but expected: %v", err, asset.ErrNotSupported)
}
_, err = (&States{}).Get(currency.BTC, asset.Spot)
if !errors.Is(err, ErrCurrencyStateNotFound) {
t.Fatalf("received: %v, but expected: %v", err, ErrCurrencyStateNotFound)
}
}
func TestCurrencyGetState(t *testing.T) {
o := (&Currency{}).GetState()
if *o.Deposit || *o.Trade || *o.Withdraw {
t.Fatal("unexpected values")
}
}
func TestAlerting(t *testing.T) {
c := Currency{}
var start, finish sync.WaitGroup
start.Add(3)
finish.Add(3)
go waitForAlert(c.WaitTrading(nil), &start, &finish)
go waitForAlert(c.WaitDeposit(nil), &start, &finish)
go waitForAlert(c.WaitWithdraw(nil), &start, &finish)
start.Wait()
c.update(Options{
Trade: convert.BoolPtr(true),
Withdraw: convert.BoolPtr(true),
Deposit: convert.BoolPtr(true)})
finish.Wait()
}
func waitForAlert(ch <-chan bool, start, finish *sync.WaitGroup) {
defer finish.Done()
start.Done()
<-ch
}

View File

@@ -17,6 +17,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/config"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/currencystate"
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
"github.com/thrasher-corp/gocryptotrader/exchanges/protocol"
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
@@ -115,8 +116,7 @@ func (b *Base) SetClientProxyAddress(addr string) error {
return nil
}
// SetFeatureDefaults sets the exchanges default feature
// support set
// SetFeatureDefaults sets the exchanges default feature support set
func (b *Base) SetFeatureDefaults() {
if b.Config.Features == nil {
s := &config.FeaturesConfig{
@@ -620,7 +620,8 @@ func (b *Base) SetupDefaults(exch *config.ExchangeConfig) error {
b.Name)
}
b.CanVerifyOrderbook = !exch.OrderbookConfig.VerificationBypass
return nil
b.States = currencystate.NewCurrencyStates()
return err
}
// AllowAuthenticatedRequest checks to see if the required fields have been set
@@ -1385,3 +1386,8 @@ func (a *AssetWebsocketSupport) IsAssetWebsocketSupported(aType asset.Item) bool
defer a.m.RUnlock()
return a.unsupported == nil || !a.unsupported[aType]
}
// UpdateCurrencyStates updates currency states
func (b *Base) UpdateCurrencyStates(ctx context.Context, a asset.Item) error {
return common.ErrNotYetImplemented
}

View File

@@ -1267,7 +1267,7 @@ func TestSetAPIKeys(t *testing.T) {
func TestSetupDefaults(t *testing.T) {
t.Parallel()
var b Base
var b = Base{Name: "awesomeTest"}
cfg := config.ExchangeConfig{
HTTPTimeout: time.Duration(-1),
API: config.APIConfig{

View File

@@ -7,6 +7,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/config"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"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/protocol"
@@ -231,6 +232,7 @@ type Base struct {
order.ExecutionLimits
AssetWebsocketSupport
*currencystate.States
}
// url lookup consts

View File

@@ -9,6 +9,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/account"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"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/orderbook"
@@ -90,4 +91,16 @@ type IBotExchange interface {
GetOrderExecutionLimits(a asset.Item, cp currency.Pair) (*order.Limits, error)
CheckOrderExecutionLimits(a asset.Item, cp currency.Pair, price, amount float64, orderType order.Type) error
UpdateOrderExecutionLimits(ctx context.Context, a asset.Item) error
CurrencyStateManagement
}
// CurrencyStateManagement defines functionality for currency state management
type CurrencyStateManagement interface {
GetCurrencyStateSnapshot() ([]currencystate.Snapshot, error)
UpdateCurrencyStates(ctx context.Context, a asset.Item) error
CanTradePair(p currency.Pair, a asset.Item) error
CanTrade(c currency.Code, a asset.Item) error
CanWithdraw(c currency.Code, a asset.Item) error
CanDeposit(c currency.Code, a asset.Item) error
}

View File

@@ -2,11 +2,11 @@ package orderbook
import (
"sync"
"sync/atomic"
"time"
"github.com/gofrs/uuid"
"github.com/thrasher-corp/gocryptotrader/dispatch"
"github.com/thrasher-corp/gocryptotrader/exchanges/alert"
"github.com/thrasher-corp/gocryptotrader/log"
)
@@ -18,7 +18,7 @@ type Depth struct {
// unexported stack of nodes
stack *stack
Alert
alert.Notice
mux *dispatch.Mux
id uuid.UUID
@@ -101,7 +101,7 @@ func (d *Depth) LoadSnapshot(bids, asks []Item, lastUpdateID int64, lastUpdated
d.restSnapshot = updateByREST
d.bids.load(bids, d.stack)
d.asks.load(asks, d.stack)
d.alert()
d.Alert()
d.m.Unlock()
}
@@ -112,7 +112,7 @@ func (d *Depth) Flush() {
d.lastUpdated = time.Time{}
d.bids.load(nil, d.stack)
d.asks.load(nil, d.stack)
d.alert()
d.Alert()
d.m.Unlock()
}
@@ -132,7 +132,7 @@ func (d *Depth) UpdateBidAskByPrice(bidUpdts, askUpdts Items, maxDepth int, last
if len(askUpdts) != 0 {
d.asks.updateInsertByPrice(askUpdts, d.stack, maxDepth, tn)
}
d.alert()
d.Alert()
d.m.Unlock()
}
@@ -157,7 +157,7 @@ func (d *Depth) UpdateBidAskByID(bidUpdts, askUpdts Items, lastUpdateID int64, l
}
d.lastUpdateID = lastUpdateID
d.lastUpdated = lastUpdated
d.alert()
d.Alert()
return nil
}
@@ -182,7 +182,7 @@ func (d *Depth) DeleteBidAskByID(bidUpdts, askUpdts Items, bypassErr bool, lastU
}
d.lastUpdateID = lastUpdateID
d.lastUpdated = lastUpdated
d.alert()
d.Alert()
return nil
}
@@ -207,7 +207,7 @@ func (d *Depth) InsertBidAskByID(bidUpdts, askUpdts Items, lastUpdateID int64, l
}
d.lastUpdateID = lastUpdateID
d.lastUpdated = lastUpdated
d.alert()
d.Alert()
return nil
}
@@ -230,7 +230,7 @@ func (d *Depth) UpdateInsertByID(bidUpdts, askUpdts Items, lastUpdateID int64, l
return err
}
}
d.alert()
d.Alert()
d.lastUpdateID = lastUpdateID
d.lastUpdated = lastUpdated
return nil
@@ -281,79 +281,3 @@ func (d *Depth) IsFundingRate() bool {
defer d.m.Unlock()
return d.isFundingRate
}
// Alert defines fields required to alert sub-systems of a change of state to
// re-check depth list
type Alert struct {
// Channel to wait for an alert on.
forAlert chan struct{}
// Lets the updater functions know if there are any routines waiting for an
// alert.
sema uint32
// After closing the forAlert channel this will notify when all the routines
// that have waited, have either checked the orderbook depth or finished.
wg sync.WaitGroup
// Segregated lock only for waiting routines, so as this does not interfere
// with the main depth lock, acts as a rolling gate.
m sync.Mutex
}
// alert establishes a state change on the orderbook depth.
func (a *Alert) alert() {
// CompareAndSwap is used to swap from 1 -> 2 so we don't keep actuating
// the opposing compare and swap in method wait. This function can return
// freely when an alert operation is in process.
if !atomic.CompareAndSwapUint32(&a.sema, 1, 2) {
// Return if no waiting routines or currently alerting.
return
}
go func() {
// Actuate lock in a different routine, as alerting is a second order
// priority compared to updating and releasing calling routine.
a.m.Lock()
// Closing; alerts many waiting routines.
close(a.forAlert)
// Wait for waiting routines to receive alert and return.
a.wg.Wait()
atomic.SwapUint32(&a.sema, 0) // Swap back to neutral state.
a.m.Unlock()
}()
}
// Wait pauses calling routine until depth change has been established via depth
// method alert. Kick allows for cancellation of waiting or when the caller has
// has been shut down, if this is not needed it can be set to nil. This
// returns a channel so strategies can cleanly wait on a select statement case.
func (a *Alert) Wait(kick <-chan struct{}) <-chan bool {
reply := make(chan bool)
a.m.Lock()
a.wg.Add(1)
if atomic.CompareAndSwapUint32(&a.sema, 0, 1) {
a.forAlert = make(chan struct{})
}
go a.hold(reply, kick)
a.m.Unlock()
return reply
}
// hold waits on either channel in the event that the routine has finished or an
// alert from a depth update has occurred.
func (a *Alert) hold(ch chan<- bool, kick <-chan struct{}) {
select {
// In a select statement, if by chance there is no receiver or its late,
// we can still close and return, limiting dead-lock potential.
case <-a.forAlert: // Main waiting channel from alert
select {
case ch <- false:
default:
}
case <-kick: // This can be nil.
select {
case ch <- true:
default:
}
}
a.wg.Done()
close(ch)
}

View File

@@ -2,9 +2,7 @@ package orderbook
import (
"errors"
"log"
"reflect"
"sync"
"testing"
"time"
@@ -307,87 +305,3 @@ func TestPublish(t *testing.T) {
d := Depth{}
d.Publish()
}
func TestWait(t *testing.T) {
wait := Alert{}
var wg sync.WaitGroup
// standard alert
wg.Add(100)
for x := 0; x < 100; x++ {
go func() {
w := wait.Wait(nil)
wg.Done()
if <-w {
log.Fatal("incorrect routine wait response for alert expecting false")
}
wg.Done()
}()
}
wg.Wait()
wg.Add(100)
isLeaky(&wait, nil, t)
wait.alert()
wg.Wait()
isLeaky(&wait, nil, t)
// use kick
ch := make(chan struct{})
wg.Add(100)
for x := 0; x < 100; x++ {
go func() {
w := wait.Wait(ch)
wg.Done()
if !<-w {
log.Fatal("incorrect routine wait response for kick expecting true")
}
wg.Done()
}()
}
wg.Wait()
wg.Add(100)
isLeaky(&wait, ch, t)
close(ch)
wg.Wait()
ch = make(chan struct{})
isLeaky(&wait, ch, t)
// late receivers
wg.Add(100)
for x := 0; x < 100; x++ {
go func(x int) {
bb := wait.Wait(ch)
wg.Done()
if x%2 == 0 {
time.Sleep(time.Millisecond * 5)
}
b := <-bb
if b {
log.Fatal("incorrect routine wait response since we call alert below; expecting false")
}
wg.Done()
}(x)
}
wg.Wait()
wg.Add(100)
isLeaky(&wait, ch, t)
wait.alert()
wg.Wait()
isLeaky(&wait, ch, t)
}
// isLeaky tests to see if the wait functionality is returning an abnormal
// channel that is operational when it shouldn't be.
func isLeaky(a *Alert, ch chan struct{}, t *testing.T) {
t.Helper()
check := a.Wait(ch)
time.Sleep(time.Millisecond * 5) // When we call wait a routine for hold is
// spawned, so for a test we need to add in a time for goschedular to allow
// routine to actually wait on the forAlert and kick channels
select {
case <-check:
t.Fatal("leaky waiter")
default:
}
}

View File

@@ -3,6 +3,8 @@ package orderbook
import (
"sync"
"time"
"github.com/thrasher-corp/gocryptotrader/exchanges/alert"
)
// Unsafe is an exported linked list reference to the current bid/ask heads and
@@ -20,7 +22,7 @@ type Unsafe struct {
// protocol then this book is not considered live and cannot be trusted.
UpdatedViaREST *bool
LastUpdated *time.Time
*Alert
*alert.Notice
}
// Lock locks down the underlying linked list which inhibits all pending updates
@@ -55,7 +57,7 @@ func (d *Depth) GetUnsafe() Unsafe {
BidHead: &d.bids.linkedList.head,
AskHead: &d.asks.linkedList.head,
m: &d.m,
Alert: &d.Alert,
Notice: &d.Notice,
UpdatedViaREST: &d.options.restSnapshot,
LastUpdated: &d.options.lastUpdated,
}

File diff suppressed because it is too large Load Diff

View File

@@ -2809,6 +2809,186 @@ func local_request_GoCryptoTrader_ModifyOrder_0(ctx context.Context, marshaler r
}
var (
filter_GoCryptoTrader_CurrencyStateGetAll_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_GoCryptoTrader_CurrencyStateGetAll_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq CurrencyStateGetAllRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_CurrencyStateGetAll_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.CurrencyStateGetAll(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_GoCryptoTrader_CurrencyStateGetAll_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq CurrencyStateGetAllRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_CurrencyStateGetAll_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.CurrencyStateGetAll(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_GoCryptoTrader_CurrencyStateTrading_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_GoCryptoTrader_CurrencyStateTrading_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq CurrencyStateTradingRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_CurrencyStateTrading_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.CurrencyStateTrading(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_GoCryptoTrader_CurrencyStateTrading_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq CurrencyStateTradingRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_CurrencyStateTrading_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.CurrencyStateTrading(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_GoCryptoTrader_CurrencyStateDeposit_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_GoCryptoTrader_CurrencyStateDeposit_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq CurrencyStateDepositRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_CurrencyStateDeposit_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.CurrencyStateDeposit(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_GoCryptoTrader_CurrencyStateDeposit_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq CurrencyStateDepositRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_CurrencyStateDeposit_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.CurrencyStateDeposit(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_GoCryptoTrader_CurrencyStateWithdraw_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_GoCryptoTrader_CurrencyStateWithdraw_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq CurrencyStateWithdrawRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_CurrencyStateWithdraw_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.CurrencyStateWithdraw(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_GoCryptoTrader_CurrencyStateWithdraw_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq CurrencyStateWithdrawRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_CurrencyStateWithdraw_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.CurrencyStateWithdraw(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_GoCryptoTrader_CurrencyStateTradingPair_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_GoCryptoTrader_CurrencyStateTradingPair_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq CurrencyStateTradingPairRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_CurrencyStateTradingPair_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.CurrencyStateTradingPair(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_GoCryptoTrader_CurrencyStateTradingPair_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq CurrencyStateTradingPairRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_CurrencyStateTradingPair_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.CurrencyStateTradingPair(ctx, &protoReq)
return msg, metadata, err
}
// RegisterGoCryptoTraderHandlerServer registers the http handlers for service GoCryptoTrader to "mux".
// UnaryRPC :call GoCryptoTraderServer directly.
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
@@ -4743,6 +4923,121 @@ func RegisterGoCryptoTraderHandlerServer(ctx context.Context, mux *runtime.Serve
})
mux.Handle("GET", pattern_GoCryptoTrader_CurrencyStateGetAll_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTrader/CurrencyStateGetAll", runtime.WithHTTPPathPattern("/v1/currencystategetall"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_GoCryptoTrader_CurrencyStateGetAll_0(rctx, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_GoCryptoTrader_CurrencyStateGetAll_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_GoCryptoTrader_CurrencyStateTrading_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTrader/CurrencyStateTrading", runtime.WithHTTPPathPattern("/v1/currencystatetrading"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_GoCryptoTrader_CurrencyStateTrading_0(rctx, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_GoCryptoTrader_CurrencyStateTrading_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_GoCryptoTrader_CurrencyStateDeposit_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTrader/CurrencyStateDeposit", runtime.WithHTTPPathPattern("/v1/currencystatedeposit"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_GoCryptoTrader_CurrencyStateDeposit_0(rctx, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_GoCryptoTrader_CurrencyStateDeposit_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_GoCryptoTrader_CurrencyStateWithdraw_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTrader/CurrencyStateWithdraw", runtime.WithHTTPPathPattern("/v1/currencystatewithdraw"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_GoCryptoTrader_CurrencyStateWithdraw_0(rctx, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_GoCryptoTrader_CurrencyStateWithdraw_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_GoCryptoTrader_CurrencyStateTradingPair_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTrader/CurrencyStateTradingPair", runtime.WithHTTPPathPattern("/v1/currencystatetradingpair"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_GoCryptoTrader_CurrencyStateTradingPair_0(rctx, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_GoCryptoTrader_CurrencyStateTradingPair_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
@@ -6544,6 +6839,106 @@ func RegisterGoCryptoTraderHandlerClient(ctx context.Context, mux *runtime.Serve
})
mux.Handle("GET", pattern_GoCryptoTrader_CurrencyStateGetAll_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTrader/CurrencyStateGetAll", runtime.WithHTTPPathPattern("/v1/currencystategetall"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_GoCryptoTrader_CurrencyStateGetAll_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_GoCryptoTrader_CurrencyStateGetAll_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_GoCryptoTrader_CurrencyStateTrading_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTrader/CurrencyStateTrading", runtime.WithHTTPPathPattern("/v1/currencystatetrading"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_GoCryptoTrader_CurrencyStateTrading_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_GoCryptoTrader_CurrencyStateTrading_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_GoCryptoTrader_CurrencyStateDeposit_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTrader/CurrencyStateDeposit", runtime.WithHTTPPathPattern("/v1/currencystatedeposit"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_GoCryptoTrader_CurrencyStateDeposit_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_GoCryptoTrader_CurrencyStateDeposit_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_GoCryptoTrader_CurrencyStateWithdraw_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTrader/CurrencyStateWithdraw", runtime.WithHTTPPathPattern("/v1/currencystatewithdraw"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_GoCryptoTrader_CurrencyStateWithdraw_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_GoCryptoTrader_CurrencyStateWithdraw_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_GoCryptoTrader_CurrencyStateTradingPair_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTrader/CurrencyStateTradingPair", runtime.WithHTTPPathPattern("/v1/currencystatetradingpair"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_GoCryptoTrader_CurrencyStateTradingPair_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_GoCryptoTrader_CurrencyStateTradingPair_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
@@ -6723,6 +7118,16 @@ var (
pattern_GoCryptoTrader_GetManagedOrders_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getmanagedorders"}, ""))
pattern_GoCryptoTrader_ModifyOrder_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "modifyorder"}, ""))
pattern_GoCryptoTrader_CurrencyStateGetAll_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "currencystategetall"}, ""))
pattern_GoCryptoTrader_CurrencyStateTrading_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "currencystatetrading"}, ""))
pattern_GoCryptoTrader_CurrencyStateDeposit_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "currencystatedeposit"}, ""))
pattern_GoCryptoTrader_CurrencyStateWithdraw_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "currencystatewithdraw"}, ""))
pattern_GoCryptoTrader_CurrencyStateTradingPair_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "currencystatetradingpair"}, ""))
)
var (
@@ -6901,4 +7306,14 @@ var (
forward_GoCryptoTrader_GetManagedOrders_0 = runtime.ForwardResponseMessage
forward_GoCryptoTrader_ModifyOrder_0 = runtime.ForwardResponseMessage
forward_GoCryptoTrader_CurrencyStateGetAll_0 = runtime.ForwardResponseMessage
forward_GoCryptoTrader_CurrencyStateTrading_0 = runtime.ForwardResponseMessage
forward_GoCryptoTrader_CurrencyStateDeposit_0 = runtime.ForwardResponseMessage
forward_GoCryptoTrader_CurrencyStateWithdraw_0 = runtime.ForwardResponseMessage
forward_GoCryptoTrader_CurrencyStateTradingPair_0 = runtime.ForwardResponseMessage
)

View File

@@ -960,6 +960,46 @@ message ModifyOrderResponse {
string modified_order_id = 1;
}
message CurrencyStateGetAllRequest {
string exchange = 1;
}
message CurrencyStateTradingRequest {
string exchange = 1;
string code = 2;
string asset = 3;
}
message CurrencyStateTradingPairRequest {
string exchange = 1;
string pair = 2;
string asset = 3;
}
message CurrencyStateWithdrawRequest {
string exchange = 1;
string code = 2;
string asset = 3;
}
message CurrencyStateDepositRequest {
string exchange = 1;
string code = 2;
string asset = 3;
}
message CurrencyStateResponse {
repeated CurrencyState currency_states = 1;
}
message CurrencyState {
string currency = 1;
string asset = 2;
bool withdraw_enabled = 3;
bool deposit_enabled = 4;
bool trading_enabled = 5;
}
service GoCryptoTrader {
rpc GetInfo (GetInfoRequest) returns (GetInfoResponse) {
option (google.api.http) = {
@@ -1517,4 +1557,30 @@ service GoCryptoTrader {
get: "/v1/modifyorder"
};
}
rpc CurrencyStateGetAll (CurrencyStateGetAllRequest) returns (CurrencyStateResponse) {
option (google.api.http) = {
get: "/v1/currencystategetall"
};
}
rpc CurrencyStateTrading (CurrencyStateTradingRequest) returns (GenericResponse) {
option (google.api.http) = {
get: "/v1/currencystatetrading"
};
}
rpc CurrencyStateDeposit (CurrencyStateDepositRequest) returns (GenericResponse) {
option (google.api.http) = {
get: "/v1/currencystatedeposit"
};
}
rpc CurrencyStateWithdraw (CurrencyStateWithdrawRequest) returns (GenericResponse) {
option (google.api.http) = {
get: "/v1/currencystatewithdraw"
};
}
rpc CurrencyStateTradingPair (CurrencyStateTradingPairRequest) returns (GenericResponse) {
option (google.api.http) = {
get: "/v1/currencystatetradingpair"
};
}
}

View File

@@ -261,6 +261,204 @@
]
}
},
"/v1/currencystatedeposit": {
"get": {
"operationId": "GoCryptoTrader_CurrencyStateDeposit",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/gctrpcGenericResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "exchange",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "code",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "asset",
"in": "query",
"required": false,
"type": "string"
}
],
"tags": [
"GoCryptoTrader"
]
}
},
"/v1/currencystategetall": {
"get": {
"operationId": "GoCryptoTrader_CurrencyStateGetAll",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/gctrpcCurrencyStateResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "exchange",
"in": "query",
"required": false,
"type": "string"
}
],
"tags": [
"GoCryptoTrader"
]
}
},
"/v1/currencystatetrading": {
"get": {
"operationId": "GoCryptoTrader_CurrencyStateTrading",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/gctrpcGenericResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "exchange",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "code",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "asset",
"in": "query",
"required": false,
"type": "string"
}
],
"tags": [
"GoCryptoTrader"
]
}
},
"/v1/currencystatetradingpair": {
"get": {
"operationId": "GoCryptoTrader_CurrencyStateTradingPair",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/gctrpcGenericResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "exchange",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "pair",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "asset",
"in": "query",
"required": false,
"type": "string"
}
],
"tags": [
"GoCryptoTrader"
]
}
},
"/v1/currencystatewithdraw": {
"get": {
"operationId": "GoCryptoTrader_CurrencyStateWithdraw",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/gctrpcGenericResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "exchange",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "code",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "asset",
"in": "query",
"required": false,
"type": "string"
}
],
"tags": [
"GoCryptoTrader"
]
}
},
"/v1/disableexchange": {
"post": {
"operationId": "GoCryptoTrader_DisableExchange",
@@ -3312,6 +3510,37 @@
}
}
},
"gctrpcCurrencyState": {
"type": "object",
"properties": {
"currency": {
"type": "string"
},
"asset": {
"type": "string"
},
"withdrawEnabled": {
"type": "boolean"
},
"depositEnabled": {
"type": "boolean"
},
"tradingEnabled": {
"type": "boolean"
}
}
},
"gctrpcCurrencyStateResponse": {
"type": "object",
"properties": {
"currencyStates": {
"type": "array",
"items": {
"$ref": "#/definitions/gctrpcCurrencyState"
}
}
}
},
"gctrpcDataHistoryJob": {
"type": "object",
"properties": {

View File

@@ -106,6 +106,11 @@ type GoCryptoTraderClient interface {
UpdateDataHistoryJobPrerequisite(ctx context.Context, in *UpdateDataHistoryJobPrerequisiteRequest, opts ...grpc.CallOption) (*GenericResponse, error)
GetManagedOrders(ctx context.Context, in *GetOrdersRequest, opts ...grpc.CallOption) (*GetOrdersResponse, error)
ModifyOrder(ctx context.Context, in *ModifyOrderRequest, opts ...grpc.CallOption) (*ModifyOrderResponse, error)
CurrencyStateGetAll(ctx context.Context, in *CurrencyStateGetAllRequest, opts ...grpc.CallOption) (*CurrencyStateResponse, error)
CurrencyStateTrading(ctx context.Context, in *CurrencyStateTradingRequest, opts ...grpc.CallOption) (*GenericResponse, error)
CurrencyStateDeposit(ctx context.Context, in *CurrencyStateDepositRequest, opts ...grpc.CallOption) (*GenericResponse, error)
CurrencyStateWithdraw(ctx context.Context, in *CurrencyStateWithdrawRequest, opts ...grpc.CallOption) (*GenericResponse, error)
CurrencyStateTradingPair(ctx context.Context, in *CurrencyStateTradingPairRequest, opts ...grpc.CallOption) (*GenericResponse, error)
}
type goCryptoTraderClient struct {
@@ -1046,6 +1051,51 @@ func (c *goCryptoTraderClient) ModifyOrder(ctx context.Context, in *ModifyOrderR
return out, nil
}
func (c *goCryptoTraderClient) CurrencyStateGetAll(ctx context.Context, in *CurrencyStateGetAllRequest, opts ...grpc.CallOption) (*CurrencyStateResponse, error) {
out := new(CurrencyStateResponse)
err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/CurrencyStateGetAll", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *goCryptoTraderClient) CurrencyStateTrading(ctx context.Context, in *CurrencyStateTradingRequest, opts ...grpc.CallOption) (*GenericResponse, error) {
out := new(GenericResponse)
err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/CurrencyStateTrading", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *goCryptoTraderClient) CurrencyStateDeposit(ctx context.Context, in *CurrencyStateDepositRequest, opts ...grpc.CallOption) (*GenericResponse, error) {
out := new(GenericResponse)
err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/CurrencyStateDeposit", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *goCryptoTraderClient) CurrencyStateWithdraw(ctx context.Context, in *CurrencyStateWithdrawRequest, opts ...grpc.CallOption) (*GenericResponse, error) {
out := new(GenericResponse)
err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/CurrencyStateWithdraw", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *goCryptoTraderClient) CurrencyStateTradingPair(ctx context.Context, in *CurrencyStateTradingPairRequest, opts ...grpc.CallOption) (*GenericResponse, error) {
out := new(GenericResponse)
err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/CurrencyStateTradingPair", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// GoCryptoTraderServer is the server API for GoCryptoTrader service.
// All implementations must embed UnimplementedGoCryptoTraderServer
// for forward compatibility
@@ -1138,6 +1188,11 @@ type GoCryptoTraderServer interface {
UpdateDataHistoryJobPrerequisite(context.Context, *UpdateDataHistoryJobPrerequisiteRequest) (*GenericResponse, error)
GetManagedOrders(context.Context, *GetOrdersRequest) (*GetOrdersResponse, error)
ModifyOrder(context.Context, *ModifyOrderRequest) (*ModifyOrderResponse, error)
CurrencyStateGetAll(context.Context, *CurrencyStateGetAllRequest) (*CurrencyStateResponse, error)
CurrencyStateTrading(context.Context, *CurrencyStateTradingRequest) (*GenericResponse, error)
CurrencyStateDeposit(context.Context, *CurrencyStateDepositRequest) (*GenericResponse, error)
CurrencyStateWithdraw(context.Context, *CurrencyStateWithdrawRequest) (*GenericResponse, error)
CurrencyStateTradingPair(context.Context, *CurrencyStateTradingPairRequest) (*GenericResponse, error)
mustEmbedUnimplementedGoCryptoTraderServer()
}
@@ -1409,6 +1464,21 @@ func (UnimplementedGoCryptoTraderServer) GetManagedOrders(context.Context, *GetO
func (UnimplementedGoCryptoTraderServer) ModifyOrder(context.Context, *ModifyOrderRequest) (*ModifyOrderResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ModifyOrder not implemented")
}
func (UnimplementedGoCryptoTraderServer) CurrencyStateGetAll(context.Context, *CurrencyStateGetAllRequest) (*CurrencyStateResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CurrencyStateGetAll not implemented")
}
func (UnimplementedGoCryptoTraderServer) CurrencyStateTrading(context.Context, *CurrencyStateTradingRequest) (*GenericResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CurrencyStateTrading not implemented")
}
func (UnimplementedGoCryptoTraderServer) CurrencyStateDeposit(context.Context, *CurrencyStateDepositRequest) (*GenericResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CurrencyStateDeposit not implemented")
}
func (UnimplementedGoCryptoTraderServer) CurrencyStateWithdraw(context.Context, *CurrencyStateWithdrawRequest) (*GenericResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CurrencyStateWithdraw not implemented")
}
func (UnimplementedGoCryptoTraderServer) CurrencyStateTradingPair(context.Context, *CurrencyStateTradingPairRequest) (*GenericResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CurrencyStateTradingPair not implemented")
}
func (UnimplementedGoCryptoTraderServer) mustEmbedUnimplementedGoCryptoTraderServer() {}
// UnsafeGoCryptoTraderServer may be embedded to opt out of forward compatibility for this service.
@@ -3024,6 +3094,96 @@ func _GoCryptoTrader_ModifyOrder_Handler(srv interface{}, ctx context.Context, d
return interceptor(ctx, in, info, handler)
}
func _GoCryptoTrader_CurrencyStateGetAll_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CurrencyStateGetAllRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(GoCryptoTraderServer).CurrencyStateGetAll(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/gctrpc.GoCryptoTrader/CurrencyStateGetAll",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(GoCryptoTraderServer).CurrencyStateGetAll(ctx, req.(*CurrencyStateGetAllRequest))
}
return interceptor(ctx, in, info, handler)
}
func _GoCryptoTrader_CurrencyStateTrading_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CurrencyStateTradingRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(GoCryptoTraderServer).CurrencyStateTrading(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/gctrpc.GoCryptoTrader/CurrencyStateTrading",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(GoCryptoTraderServer).CurrencyStateTrading(ctx, req.(*CurrencyStateTradingRequest))
}
return interceptor(ctx, in, info, handler)
}
func _GoCryptoTrader_CurrencyStateDeposit_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CurrencyStateDepositRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(GoCryptoTraderServer).CurrencyStateDeposit(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/gctrpc.GoCryptoTrader/CurrencyStateDeposit",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(GoCryptoTraderServer).CurrencyStateDeposit(ctx, req.(*CurrencyStateDepositRequest))
}
return interceptor(ctx, in, info, handler)
}
func _GoCryptoTrader_CurrencyStateWithdraw_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CurrencyStateWithdrawRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(GoCryptoTraderServer).CurrencyStateWithdraw(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/gctrpc.GoCryptoTrader/CurrencyStateWithdraw",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(GoCryptoTraderServer).CurrencyStateWithdraw(ctx, req.(*CurrencyStateWithdrawRequest))
}
return interceptor(ctx, in, info, handler)
}
func _GoCryptoTrader_CurrencyStateTradingPair_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CurrencyStateTradingPairRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(GoCryptoTraderServer).CurrencyStateTradingPair(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/gctrpc.GoCryptoTrader/CurrencyStateTradingPair",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(GoCryptoTraderServer).CurrencyStateTradingPair(ctx, req.(*CurrencyStateTradingPairRequest))
}
return interceptor(ctx, in, info, handler)
}
// GoCryptoTrader_ServiceDesc is the grpc.ServiceDesc for GoCryptoTrader service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
@@ -3359,6 +3519,26 @@ var GoCryptoTrader_ServiceDesc = grpc.ServiceDesc{
MethodName: "ModifyOrder",
Handler: _GoCryptoTrader_ModifyOrder_Handler,
},
{
MethodName: "CurrencyStateGetAll",
Handler: _GoCryptoTrader_CurrencyStateGetAll_Handler,
},
{
MethodName: "CurrencyStateTrading",
Handler: _GoCryptoTrader_CurrencyStateTrading_Handler,
},
{
MethodName: "CurrencyStateDeposit",
Handler: _GoCryptoTrader_CurrencyStateDeposit_Handler,
},
{
MethodName: "CurrencyStateWithdraw",
Handler: _GoCryptoTrader_CurrencyStateWithdraw_Handler,
},
{
MethodName: "CurrencyStateTradingPair",
Handler: _GoCryptoTrader_CurrencyStateTradingPair_Handler,
},
},
Streams: []grpc.StreamDesc{
{

View File

@@ -37,6 +37,14 @@ func TestMain(m *testing.M) {
os.Exit(1)
}
exch.SetDefaults()
cfg, err := exch.GetDefaultConfig()
if err != nil {
log.Fatal(err)
}
err = exch.Setup(cfg)
if err != nil {
log.Fatal(err)
}
em.Add(exch)
engine.Bot.ExchangeManager = em
engine.Bot.WithdrawManager, err = engine.SetupWithdrawManager(em, nil, true)

View File

@@ -55,6 +55,7 @@ func main() {
flag.DurationVar(&settings.EventManagerDelay, "eventmanagerdelay", time.Duration(0), "sets the event managers sleep delay between event checking")
flag.BoolVar(&settings.EnableNTPClient, "ntpclient", true, "enables the NTP client to check system clock drift")
flag.BoolVar(&settings.EnableDispatcher, "dispatch", true, "enables the dispatch system")
flag.BoolVar(&settings.EnableCurrencyStateManager, "currencystatemanager", true, "enables the currency state manager")
flag.IntVar(&settings.DispatchMaxWorkerAmount, "dispatchworkers", dispatch.DefaultMaxWorkers, "sets the dispatch package max worker generation limit")
flag.IntVar(&settings.DispatchJobsLimit, "dispatchjobslimit", dispatch.DefaultJobsLimit, "sets the dispatch package max jobs limit")