mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-14 07:26:47 +00:00
* Initial codes for a trade tracker * Moving everything in a broken fashion * Removes tradetracker. Removes some errors for subsystems * Cleans up some subsystems, renames stuttering types. Removes some global Bot usage * More basic subsystem renaming and file moving * Removes engine dependency from events,ntpserver,ordermanager,comms manager * Exports eventManager, fixes rpcserver. puts rpcserver back for now * Removes redundant error message, further removes engine dependencies * experimental end of day interface usage * adds ability to build the application * Withdraw and event manager handling * cleans up apiserver and communications manager * Cleans up some start/setup processes. Though should separate * More consistency with Setup Start Stop IsRunning funcs * Final consistency pass before testing phase * Fixes engine tests. Fixes stop nil issue * api server tests * Communications manager testing * Connection manager tests and nilsubsystem error * End of day currencypairsyncer tests * Adds databaseconnection/databaseconnection_test.go * Adds withdrawal manager tests * Deposit address testing. Moved orderbook sync first as its more important * Adds test for event manager * More full eventmanager testing * Adds testfile. Enables skipped test. * ntp manager tests * Adds ordermanager tests, Extracts a whole new subsystem from engine and fanangles import cycles * Adds websocket routine manager tests * Basic portfolio manager testing * Fixes issue with currency pair sync startup * Fixes issue with event manager startup * Starts the order manager before backtester starts * Fixes fee tests. Expands testing. Doesnt fix races * Fixes most test races * Resolves data races * Fixes subsystem test issues * currency pair syncer coverage tests * Refactors portfolio. Fixes tests. Withdraw validation Portfolio didn't need to exist with a portfolio manager. Now the porfolio manager is in charge how the portfolio is handled and all portfolio functions are attached to the base instead of just exported at the package level Withdrawal validation occurred at the exchange level when it can just be run at the withdrawal manager level. All withdrawal requests go through that endpoint * lint -fix * golang lint fixes * lints and comments everything * Updates GCT logo, adds documentation for some subsystems * More documentation and more logo updates * Fixes backtesting and apiserver errors encountered * Fixes errors and typos from reviewing * More minor fixes * Changes %h verb to %w * reverbs to %s * Humbly begins reverting to more flat engine package The main reasoning for this is that the subsystem split doesn't make sense in a golang environment. The subsystems are only meant to be used with engine and so by placing them in a non-engine area, it does not work and is inconsistent with the rest of the application's package layout. This will begin salvaging the changes made by reverting to a flat engine package, but maintaining the consistent designs introduced. Further, I will look to remove any TestMains and decrease the scope of testing to be more local and decrease the issues that have been caused from our style of testing. * Manages to re-flatten things. Everything is within its own file * mini fixes * Fixes tests and data races and lints * Updates docs tool for engine to create filename readmes * os -> ioutil * remove err * Appveyor version increase test * Removes tCleanup as its unsupported on appveyor * Adds stuff that I thought was in previous merge master commit * Removes cancel from test * Fixes really fun test-exclusive data race * minor nit fixes * niterinos * docs gen * rm;rf test * Remove typoline. expands startstop helper. Splits apiserver * Removes accidental folder * Uses update instead of replace for order upsert * addresses nits. Renames files. Regenerates documentation. * lint and removal of comments * Add new test for default scenario * Fixes typo * regen docs
862 lines
26 KiB
Go
862 lines
26 KiB
Go
package engine
|
|
|
|
import (
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/rand"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/pem"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"math/big"
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/pquerna/otp/totp"
|
|
"github.com/thrasher-corp/gocryptotrader/common"
|
|
"github.com/thrasher-corp/gocryptotrader/common/file"
|
|
"github.com/thrasher-corp/gocryptotrader/config"
|
|
"github.com/thrasher-corp/gocryptotrader/currency"
|
|
"github.com/thrasher-corp/gocryptotrader/dispatch"
|
|
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/orderbook"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/stats"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
|
"github.com/thrasher-corp/gocryptotrader/gctscript/vm"
|
|
"github.com/thrasher-corp/gocryptotrader/log"
|
|
)
|
|
|
|
var (
|
|
errCertExpired = errors.New("gRPC TLS certificate has expired")
|
|
errCertDataIsNil = errors.New("gRPC TLS certificate PEM data is nil")
|
|
errCertTypeInvalid = errors.New("gRPC TLS certificate type is invalid")
|
|
)
|
|
|
|
// GetSubsystemsStatus returns the status of various subsystems
|
|
func (bot *Engine) GetSubsystemsStatus() map[string]bool {
|
|
systems := make(map[string]bool)
|
|
systems[SyncManagerName] = bot.CommunicationsManager.IsRunning()
|
|
systems[ConnectionManagerName] = bot.connectionManager.IsRunning()
|
|
systems[OrderManagerName] = bot.OrderManager.IsRunning()
|
|
systems[PortfolioManagerName] = bot.portfolioManager.IsRunning()
|
|
systems[NTPManagerName] = bot.ntpManager.IsRunning()
|
|
systems[DatabaseConnectionManagerName] = bot.DatabaseManager.IsRunning()
|
|
systems[SyncManagerName] = bot.Settings.EnableExchangeSyncManager
|
|
systems[grpcName] = bot.Settings.EnableGRPC
|
|
systems[grpcProxyName] = bot.Settings.EnableGRPCProxy
|
|
systems[vm.Name] = bot.gctScriptManager.IsRunning()
|
|
systems[DeprecatedName] = bot.Settings.EnableDeprecatedRPC
|
|
systems[WebsocketName] = bot.Settings.EnableWebsocketRPC
|
|
systems[dispatch.Name] = dispatch.IsRunning()
|
|
return systems
|
|
}
|
|
|
|
// RPCEndpoint stores an RPC endpoint status and addr
|
|
type RPCEndpoint struct {
|
|
Started bool
|
|
ListenAddr string
|
|
}
|
|
|
|
// GetRPCEndpoints returns a list of RPC endpoints and their listen addrs
|
|
func GetRPCEndpoints() map[string]RPCEndpoint {
|
|
endpoints := make(map[string]RPCEndpoint)
|
|
endpoints[grpcName] = RPCEndpoint{
|
|
Started: Bot.Settings.EnableGRPC,
|
|
ListenAddr: "grpc://" + Bot.Config.RemoteControl.GRPC.ListenAddress,
|
|
}
|
|
endpoints[grpcProxyName] = RPCEndpoint{
|
|
Started: Bot.Settings.EnableGRPCProxy,
|
|
ListenAddr: "http://" + Bot.Config.RemoteControl.GRPC.GRPCProxyListenAddress,
|
|
}
|
|
endpoints[DeprecatedName] = RPCEndpoint{
|
|
Started: Bot.Settings.EnableDeprecatedRPC,
|
|
ListenAddr: "http://" + Bot.Config.RemoteControl.DeprecatedRPC.ListenAddress,
|
|
}
|
|
endpoints[WebsocketName] = RPCEndpoint{
|
|
Started: Bot.Settings.EnableWebsocketRPC,
|
|
ListenAddr: "ws://" + Bot.Config.RemoteControl.WebsocketRPC.ListenAddress,
|
|
}
|
|
return endpoints
|
|
}
|
|
|
|
// SetSubsystem enables or disables an engine subsystem
|
|
func (bot *Engine) SetSubsystem(subSystemName string, enable bool) error {
|
|
var err error
|
|
switch strings.ToLower(subSystemName) {
|
|
case CommunicationsManagerName:
|
|
if enable {
|
|
if bot.CommunicationsManager == nil {
|
|
communicationsConfig := bot.Config.GetCommunicationsConfig()
|
|
bot.CommunicationsManager, err = SetupCommunicationManager(&communicationsConfig)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return bot.CommunicationsManager.Start()
|
|
}
|
|
return bot.CommunicationsManager.Stop()
|
|
case ConnectionManagerName:
|
|
if enable {
|
|
if bot.connectionManager == nil {
|
|
bot.connectionManager, err = setupConnectionManager(&bot.Config.ConnectionMonitor)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return bot.connectionManager.Start()
|
|
}
|
|
return bot.connectionManager.Stop()
|
|
case OrderManagerName:
|
|
if enable {
|
|
if bot.OrderManager == nil {
|
|
bot.OrderManager, err = SetupOrderManager(
|
|
bot.ExchangeManager,
|
|
bot.CommunicationsManager,
|
|
&bot.ServicesWG,
|
|
bot.Settings.Verbose)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return bot.OrderManager.Start()
|
|
}
|
|
return bot.OrderManager.Stop()
|
|
case PortfolioManagerName:
|
|
if enable {
|
|
if bot.portfolioManager == nil {
|
|
bot.portfolioManager, err = setupPortfolioManager(bot.ExchangeManager, bot.Settings.PortfolioManagerDelay, &bot.Config.Portfolio)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return bot.portfolioManager.Start(&bot.ServicesWG)
|
|
}
|
|
return bot.portfolioManager.Stop()
|
|
case NTPManagerName:
|
|
if enable {
|
|
if bot.ntpManager == nil {
|
|
bot.ntpManager, err = setupNTPManager(
|
|
&bot.Config.NTPClient,
|
|
*bot.Config.Logging.Enabled)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return bot.ntpManager.Start()
|
|
}
|
|
return bot.ntpManager.Stop()
|
|
case DatabaseConnectionManagerName:
|
|
if enable {
|
|
if bot.DatabaseManager == nil {
|
|
bot.DatabaseManager, err = SetupDatabaseConnectionManager(&bot.Config.Database)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return bot.DatabaseManager.Start(&bot.ServicesWG)
|
|
}
|
|
return bot.DatabaseManager.Stop()
|
|
case SyncManagerName:
|
|
if enable {
|
|
if bot.currencyPairSyncer == nil {
|
|
exchangeSyncCfg := &Config{
|
|
SyncTicker: bot.Settings.EnableTickerSyncing,
|
|
SyncOrderbook: bot.Settings.EnableOrderbookSyncing,
|
|
SyncTrades: bot.Settings.EnableTradeSyncing,
|
|
SyncContinuously: bot.Settings.SyncContinuously,
|
|
NumWorkers: bot.Settings.SyncWorkers,
|
|
Verbose: bot.Settings.Verbose,
|
|
SyncTimeoutREST: bot.Settings.SyncTimeoutREST,
|
|
SyncTimeoutWebsocket: bot.Settings.SyncTimeoutWebsocket,
|
|
}
|
|
bot.currencyPairSyncer, err = setupSyncManager(exchangeSyncCfg,
|
|
bot.ExchangeManager,
|
|
bot.websocketRoutineManager,
|
|
&bot.Config.RemoteControl)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return bot.currencyPairSyncer.Start()
|
|
}
|
|
return bot.currencyPairSyncer.Stop()
|
|
case dispatch.Name:
|
|
if enable {
|
|
return dispatch.Start(bot.Settings.DispatchMaxWorkerAmount, bot.Settings.DispatchJobsLimit)
|
|
}
|
|
return dispatch.Stop()
|
|
case DeprecatedName:
|
|
if enable {
|
|
if bot.apiServer == nil {
|
|
var filePath string
|
|
filePath, err = config.GetAndMigrateDefaultPath(bot.Settings.ConfigFile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
bot.apiServer, err = setupAPIServerManager(&bot.Config.RemoteControl, &bot.Config.Profiler, bot.ExchangeManager, bot, bot.portfolioManager, filePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return bot.apiServer.StartRESTServer()
|
|
}
|
|
return bot.apiServer.StopRESTServer()
|
|
case WebsocketName:
|
|
if enable {
|
|
if bot.apiServer == nil {
|
|
var filePath string
|
|
filePath, err = config.GetAndMigrateDefaultPath(bot.Settings.ConfigFile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
bot.apiServer, err = setupAPIServerManager(&bot.Config.RemoteControl, &bot.Config.Profiler, bot.ExchangeManager, bot, bot.portfolioManager, filePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return bot.apiServer.StartWebsocketServer()
|
|
}
|
|
return bot.apiServer.StopWebsocketServer()
|
|
case grpcName, grpcProxyName:
|
|
return errors.New("cannot manage GRPC subsystem via GRPC. Please manually change your config")
|
|
|
|
case vm.Name:
|
|
if enable {
|
|
if bot.gctScriptManager == nil {
|
|
bot.gctScriptManager, err = vm.NewManager(&bot.Config.GCTScript)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return bot.gctScriptManager.Start(&bot.ServicesWG)
|
|
}
|
|
return bot.gctScriptManager.Stop()
|
|
}
|
|
|
|
return errors.New("subsystem not found")
|
|
}
|
|
|
|
// GetExchangeOTPs returns OTP codes for all exchanges which have a otpsecret
|
|
// stored
|
|
func (bot *Engine) GetExchangeOTPs() (map[string]string, error) {
|
|
otpCodes := make(map[string]string)
|
|
for x := range bot.Config.Exchanges {
|
|
if otpSecret := bot.Config.Exchanges[x].API.Credentials.OTPSecret; otpSecret != "" {
|
|
exchName := bot.Config.Exchanges[x].Name
|
|
o, err := totp.GenerateCode(otpSecret, time.Now())
|
|
if err != nil {
|
|
log.Errorf(log.Global, "Unable to generate OTP code for exchange %s. Err: %s\n",
|
|
exchName, err)
|
|
continue
|
|
}
|
|
otpCodes[exchName] = o
|
|
}
|
|
}
|
|
|
|
if len(otpCodes) == 0 {
|
|
return nil, errors.New("no exchanges found which have a OTP secret stored")
|
|
}
|
|
|
|
return otpCodes, nil
|
|
}
|
|
|
|
// GetExchangeOTPByName returns a OTP code for the desired exchange
|
|
// if it exists
|
|
func (bot *Engine) GetExchangeOTPByName(exchName string) (string, error) {
|
|
for x := range bot.Config.Exchanges {
|
|
if !strings.EqualFold(bot.Config.Exchanges[x].Name, exchName) {
|
|
continue
|
|
}
|
|
|
|
if otpSecret := bot.Config.Exchanges[x].API.Credentials.OTPSecret; otpSecret != "" {
|
|
return totp.GenerateCode(otpSecret, time.Now())
|
|
}
|
|
}
|
|
return "", errors.New("exchange does not have a OTP secret stored")
|
|
}
|
|
|
|
// GetAuthAPISupportedExchanges returns a list of auth api enabled exchanges
|
|
func (bot *Engine) GetAuthAPISupportedExchanges() []string {
|
|
var exchangeNames []string
|
|
exchanges := bot.GetExchanges()
|
|
for x := range exchanges {
|
|
if !exchanges[x].GetAuthenticatedAPISupport(exchange.RestAuthentication) &&
|
|
!exchanges[x].GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) {
|
|
continue
|
|
}
|
|
exchangeNames = append(exchangeNames, exchanges[x].GetName())
|
|
}
|
|
return exchangeNames
|
|
}
|
|
|
|
// IsOnline returns whether or not the engine has Internet connectivity
|
|
func (bot *Engine) IsOnline() bool {
|
|
return bot.connectionManager.IsOnline()
|
|
}
|
|
|
|
// GetAllAvailablePairs returns a list of all available pairs on either enabled
|
|
// or disabled exchanges
|
|
func (bot *Engine) GetAllAvailablePairs(enabledExchangesOnly bool, assetType asset.Item) currency.Pairs {
|
|
var pairList currency.Pairs
|
|
for x := range bot.Config.Exchanges {
|
|
if enabledExchangesOnly && !bot.Config.Exchanges[x].Enabled {
|
|
continue
|
|
}
|
|
|
|
exchName := bot.Config.Exchanges[x].Name
|
|
pairs, err := bot.Config.GetAvailablePairs(exchName, assetType)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
for y := range pairs {
|
|
if pairList.Contains(pairs[y], false) {
|
|
continue
|
|
}
|
|
pairList = append(pairList, pairs[y])
|
|
}
|
|
}
|
|
return pairList
|
|
}
|
|
|
|
// GetSpecificAvailablePairs returns a list of supported pairs based on specific
|
|
// parameters
|
|
func (bot *Engine) GetSpecificAvailablePairs(enabledExchangesOnly, fiatPairs, includeUSDT, cryptoPairs bool, assetType asset.Item) currency.Pairs {
|
|
var pairList currency.Pairs
|
|
supportedPairs := bot.GetAllAvailablePairs(enabledExchangesOnly, assetType)
|
|
|
|
for x := range supportedPairs {
|
|
if fiatPairs {
|
|
if supportedPairs[x].IsCryptoFiatPair() &&
|
|
!supportedPairs[x].ContainsCurrency(currency.USDT) ||
|
|
(includeUSDT &&
|
|
supportedPairs[x].ContainsCurrency(currency.USDT) &&
|
|
supportedPairs[x].IsCryptoPair()) {
|
|
if pairList.Contains(supportedPairs[x], false) {
|
|
continue
|
|
}
|
|
pairList = append(pairList, supportedPairs[x])
|
|
}
|
|
}
|
|
if cryptoPairs {
|
|
if supportedPairs[x].IsCryptoPair() {
|
|
if pairList.Contains(supportedPairs[x], false) {
|
|
continue
|
|
}
|
|
pairList = append(pairList, supportedPairs[x])
|
|
}
|
|
}
|
|
}
|
|
return pairList
|
|
}
|
|
|
|
// IsRelatablePairs checks to see if the two pairs are relatable
|
|
func IsRelatablePairs(p1, p2 currency.Pair, includeUSDT bool) bool {
|
|
if p1.EqualIncludeReciprocal(p2) {
|
|
return true
|
|
}
|
|
|
|
var relatablePairs = GetRelatableCurrencies(p1, true, includeUSDT)
|
|
if p1.IsCryptoFiatPair() {
|
|
for x := range relatablePairs {
|
|
relatablePairs = append(relatablePairs,
|
|
GetRelatableFiatCurrencies(relatablePairs[x])...)
|
|
}
|
|
}
|
|
return relatablePairs.Contains(p2, false)
|
|
}
|
|
|
|
// MapCurrenciesByExchange returns a list of currency pairs mapped to an
|
|
// exchange
|
|
func (bot *Engine) MapCurrenciesByExchange(p currency.Pairs, enabledExchangesOnly bool, assetType asset.Item) map[string]currency.Pairs {
|
|
currencyExchange := make(map[string]currency.Pairs)
|
|
for x := range p {
|
|
for y := range bot.Config.Exchanges {
|
|
if enabledExchangesOnly && !bot.Config.Exchanges[y].Enabled {
|
|
continue
|
|
}
|
|
exchName := bot.Config.Exchanges[y].Name
|
|
if !bot.Config.SupportsPair(exchName, p[x], assetType) {
|
|
continue
|
|
}
|
|
|
|
result, ok := currencyExchange[exchName]
|
|
if !ok {
|
|
var pairs []currency.Pair
|
|
pairs = append(pairs, p[x])
|
|
currencyExchange[exchName] = pairs
|
|
} else {
|
|
if result.Contains(p[x], false) {
|
|
continue
|
|
}
|
|
result = append(result, p[x])
|
|
currencyExchange[exchName] = result
|
|
}
|
|
}
|
|
}
|
|
return currencyExchange
|
|
}
|
|
|
|
// GetExchangeNamesByCurrency returns a list of exchanges supporting
|
|
// a currency pair based on whether the exchange is enabled or not
|
|
func (bot *Engine) GetExchangeNamesByCurrency(p currency.Pair, enabled bool, assetType asset.Item) []string {
|
|
var exchanges []string
|
|
for x := range bot.Config.Exchanges {
|
|
if enabled != bot.Config.Exchanges[x].Enabled {
|
|
continue
|
|
}
|
|
|
|
exchName := bot.Config.Exchanges[x].Name
|
|
if !bot.Config.SupportsPair(exchName, p, assetType) {
|
|
continue
|
|
}
|
|
exchanges = append(exchanges, exchName)
|
|
}
|
|
return exchanges
|
|
}
|
|
|
|
// GetRelatableCryptocurrencies returns a list of currency pairs if it can find
|
|
// any relatable currencies (e.g ETHBTC -> ETHLTC -> ETHUSDT -> ETHREP)
|
|
func GetRelatableCryptocurrencies(p currency.Pair) currency.Pairs {
|
|
var pairs currency.Pairs
|
|
cryptocurrencies := currency.GetCryptocurrencies()
|
|
|
|
for x := range cryptocurrencies {
|
|
newPair := currency.NewPair(p.Base, cryptocurrencies[x])
|
|
if newPair.IsInvalid() {
|
|
continue
|
|
}
|
|
|
|
if newPair.Base.Upper() == p.Base.Upper() &&
|
|
newPair.Quote.Upper() == p.Quote.Upper() {
|
|
continue
|
|
}
|
|
|
|
if pairs.Contains(newPair, false) {
|
|
continue
|
|
}
|
|
pairs = append(pairs, newPair)
|
|
}
|
|
return pairs
|
|
}
|
|
|
|
// GetRelatableFiatCurrencies returns a list of currency pairs if it can find
|
|
// any relatable currencies (e.g ETHUSD -> ETHAUD -> ETHGBP -> ETHJPY)
|
|
func GetRelatableFiatCurrencies(p currency.Pair) currency.Pairs {
|
|
var pairs currency.Pairs
|
|
fiatCurrencies := currency.GetFiatCurrencies()
|
|
|
|
for x := range fiatCurrencies {
|
|
newPair := currency.NewPair(p.Base, fiatCurrencies[x])
|
|
if newPair.Base.Upper() == newPair.Quote.Upper() {
|
|
continue
|
|
}
|
|
|
|
if newPair.Base.Upper() == p.Base.Upper() &&
|
|
newPair.Quote.Upper() == p.Quote.Upper() {
|
|
continue
|
|
}
|
|
|
|
if pairs.Contains(newPair, false) {
|
|
continue
|
|
}
|
|
pairs = append(pairs, newPair)
|
|
}
|
|
return pairs
|
|
}
|
|
|
|
// GetRelatableCurrencies returns a list of currency pairs if it can find
|
|
// any relatable currencies (e.g BTCUSD -> BTC USDT -> XBT USDT -> XBT USD)
|
|
// incOrig includes the supplied pair if desired
|
|
func GetRelatableCurrencies(p currency.Pair, incOrig, incUSDT bool) currency.Pairs {
|
|
var pairs currency.Pairs
|
|
|
|
addPair := func(p currency.Pair) {
|
|
if pairs.Contains(p, true) {
|
|
return
|
|
}
|
|
pairs = append(pairs, p)
|
|
}
|
|
|
|
buildPairs := func(p currency.Pair, incOrig bool) {
|
|
if incOrig {
|
|
addPair(p)
|
|
}
|
|
|
|
first := currency.GetTranslation(p.Base)
|
|
if first != p.Base {
|
|
addPair(currency.NewPair(first, p.Quote))
|
|
|
|
second := currency.GetTranslation(p.Quote)
|
|
if second != p.Quote {
|
|
addPair(currency.NewPair(first, second))
|
|
}
|
|
}
|
|
|
|
second := currency.GetTranslation(p.Quote)
|
|
if second != p.Quote {
|
|
addPair(currency.NewPair(p.Base, second))
|
|
}
|
|
}
|
|
|
|
buildPairs(p, incOrig)
|
|
buildPairs(p.Swap(), incOrig)
|
|
|
|
if !incUSDT {
|
|
pairs = pairs.RemovePairsByFilter(currency.USDT)
|
|
}
|
|
|
|
return pairs
|
|
}
|
|
|
|
// GetSpecificOrderbook returns a specific orderbook given the currency,
|
|
// exchangeName and assetType
|
|
func (bot *Engine) GetSpecificOrderbook(p currency.Pair, exchangeName string, assetType asset.Item) (*orderbook.Base, error) {
|
|
exch := bot.GetExchangeByName(exchangeName)
|
|
if exch == nil {
|
|
return nil, ErrExchangeNotFound
|
|
}
|
|
return exch.FetchOrderbook(p, assetType)
|
|
}
|
|
|
|
// GetSpecificTicker returns a specific ticker given the currency,
|
|
// exchangeName and assetType
|
|
func (bot *Engine) GetSpecificTicker(p currency.Pair, exchangeName string, assetType asset.Item) (*ticker.Price, error) {
|
|
exch := bot.GetExchangeByName(exchangeName)
|
|
if exch == nil {
|
|
return nil, ErrExchangeNotFound
|
|
}
|
|
return exch.FetchTicker(p, assetType)
|
|
}
|
|
|
|
// GetCollatedExchangeAccountInfoByCoin collates individual exchange account
|
|
// information and turns into into a map string of
|
|
// exchange.AccountCurrencyInfo
|
|
func GetCollatedExchangeAccountInfoByCoin(accounts []account.Holdings) map[currency.Code]account.Balance {
|
|
result := make(map[currency.Code]account.Balance)
|
|
for x := range accounts {
|
|
for y := range accounts[x].Accounts {
|
|
for z := range accounts[x].Accounts[y].Currencies {
|
|
currencyName := accounts[x].Accounts[y].Currencies[z].CurrencyName
|
|
avail := accounts[x].Accounts[y].Currencies[z].TotalValue
|
|
onHold := accounts[x].Accounts[y].Currencies[z].Hold
|
|
info, ok := result[currencyName]
|
|
if !ok {
|
|
accountInfo := account.Balance{
|
|
CurrencyName: currencyName,
|
|
Hold: onHold,
|
|
TotalValue: avail,
|
|
}
|
|
result[currencyName] = accountInfo
|
|
} else {
|
|
info.Hold += onHold
|
|
info.TotalValue += avail
|
|
result[currencyName] = info
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// GetExchangeHighestPriceByCurrencyPair returns the exchange with the highest
|
|
// price for a given currency pair and asset type
|
|
func GetExchangeHighestPriceByCurrencyPair(p currency.Pair, a asset.Item) (string, error) {
|
|
result := stats.SortExchangesByPrice(p, a, true)
|
|
if len(result) == 0 {
|
|
return "", fmt.Errorf("no stats for supplied currency pair and asset type")
|
|
}
|
|
|
|
return result[0].Exchange, nil
|
|
}
|
|
|
|
// GetExchangeLowestPriceByCurrencyPair returns the exchange with the lowest
|
|
// price for a given currency pair and asset type
|
|
func GetExchangeLowestPriceByCurrencyPair(p currency.Pair, assetType asset.Item) (string, error) {
|
|
result := stats.SortExchangesByPrice(p, assetType, false)
|
|
if len(result) == 0 {
|
|
return "", fmt.Errorf("no stats for supplied currency pair and asset type")
|
|
}
|
|
|
|
return result[0].Exchange, nil
|
|
}
|
|
|
|
// GetCryptocurrenciesByExchange returns a list of cryptocurrencies the exchange supports
|
|
func (bot *Engine) GetCryptocurrenciesByExchange(exchangeName string, enabledExchangesOnly, enabledPairs bool, assetType asset.Item) ([]string, error) {
|
|
var cryptocurrencies []string
|
|
for x := range bot.Config.Exchanges {
|
|
if !strings.EqualFold(bot.Config.Exchanges[x].Name, exchangeName) {
|
|
continue
|
|
}
|
|
if enabledExchangesOnly && !bot.Config.Exchanges[x].Enabled {
|
|
continue
|
|
}
|
|
|
|
var err error
|
|
var pairs []currency.Pair
|
|
if enabledPairs {
|
|
pairs, err = bot.Config.GetEnabledPairs(exchangeName, assetType)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
pairs, err = bot.Config.GetAvailablePairs(exchangeName, assetType)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
for y := range pairs {
|
|
if pairs[y].Base.IsCryptocurrency() &&
|
|
!common.StringDataCompareInsensitive(cryptocurrencies, pairs[y].Base.String()) {
|
|
cryptocurrencies = append(cryptocurrencies, pairs[y].Base.String())
|
|
}
|
|
|
|
if pairs[y].Quote.IsCryptocurrency() &&
|
|
!common.StringDataCompareInsensitive(cryptocurrencies, pairs[y].Quote.String()) {
|
|
cryptocurrencies = append(cryptocurrencies, pairs[y].Quote.String())
|
|
}
|
|
}
|
|
}
|
|
return cryptocurrencies, nil
|
|
}
|
|
|
|
// GetCryptocurrencyDepositAddressesByExchange returns the cryptocurrency deposit addresses for a particular exchange
|
|
func (bot *Engine) GetCryptocurrencyDepositAddressesByExchange(exchName string) (map[string]string, error) {
|
|
if bot.DepositAddressManager != nil {
|
|
return bot.DepositAddressManager.GetDepositAddressesByExchange(exchName)
|
|
}
|
|
|
|
result := bot.GetExchangeCryptocurrencyDepositAddresses()
|
|
r, ok := result[exchName]
|
|
if !ok {
|
|
return nil, ErrExchangeNotFound
|
|
}
|
|
return r, nil
|
|
}
|
|
|
|
// GetExchangeCryptocurrencyDepositAddress returns the cryptocurrency deposit address for a particular
|
|
// exchange
|
|
func (bot *Engine) GetExchangeCryptocurrencyDepositAddress(exchName, accountID string, item currency.Code) (string, error) {
|
|
if bot.DepositAddressManager != nil {
|
|
return bot.DepositAddressManager.GetDepositAddressByExchangeAndCurrency(exchName, item)
|
|
}
|
|
|
|
exch := bot.GetExchangeByName(exchName)
|
|
if exch == nil {
|
|
return "", ErrExchangeNotFound
|
|
}
|
|
return exch.GetDepositAddress(item, accountID)
|
|
}
|
|
|
|
// GetExchangeCryptocurrencyDepositAddresses obtains an exchanges deposit cryptocurrency list
|
|
func (bot *Engine) GetExchangeCryptocurrencyDepositAddresses() map[string]map[string]string {
|
|
result := make(map[string]map[string]string)
|
|
exchanges := bot.GetExchanges()
|
|
for x := range exchanges {
|
|
exchName := exchanges[x].GetName()
|
|
if !exchanges[x].GetAuthenticatedAPISupport(exchange.RestAuthentication) {
|
|
if bot.Settings.Verbose {
|
|
log.Debugf(log.ExchangeSys, "GetExchangeCryptocurrencyDepositAddresses: Skippping %s due to disabled authenticated API support.\n", exchName)
|
|
}
|
|
continue
|
|
}
|
|
|
|
cryptoCurrencies, err := bot.GetCryptocurrenciesByExchange(exchName, true, true, asset.Spot)
|
|
if err != nil {
|
|
log.Debugf(log.ExchangeSys, "%s failed to get cryptocurrency deposit addresses. Err: %s\n", exchName, err)
|
|
continue
|
|
}
|
|
|
|
cryptoAddr := make(map[string]string)
|
|
for y := range cryptoCurrencies {
|
|
cryptocurrency := cryptoCurrencies[y]
|
|
depositAddr, err := exchanges[x].GetDepositAddress(currency.NewCode(cryptocurrency), "")
|
|
if err != nil {
|
|
log.Errorf(log.Global, "%s failed to get cryptocurrency deposit addresses. Err: %s\n", exchName, err)
|
|
continue
|
|
}
|
|
cryptoAddr[cryptocurrency] = depositAddr
|
|
}
|
|
result[exchName] = cryptoAddr
|
|
}
|
|
return result
|
|
}
|
|
|
|
// GetExchangeNames returns a list of enabled or disabled exchanges
|
|
func (bot *Engine) GetExchangeNames(enabledOnly bool) []string {
|
|
exchanges := bot.ExchangeManager.GetExchanges()
|
|
var response []string
|
|
for i := range exchanges {
|
|
if !enabledOnly || (enabledOnly && exchanges[i].IsEnabled()) {
|
|
response = append(response, exchanges[i].GetName())
|
|
}
|
|
}
|
|
return response
|
|
}
|
|
|
|
// GetAllActiveTickers returns all enabled exchange tickers
|
|
func (bot *Engine) GetAllActiveTickers() []EnabledExchangeCurrencies {
|
|
var tickerData []EnabledExchangeCurrencies
|
|
exchanges := bot.GetExchanges()
|
|
for x := range exchanges {
|
|
assets := exchanges[x].GetAssetTypes()
|
|
exchName := exchanges[x].GetName()
|
|
var exchangeTicker EnabledExchangeCurrencies
|
|
exchangeTicker.ExchangeName = exchName
|
|
|
|
for y := range assets {
|
|
currencies, err := exchanges[x].GetEnabledPairs(assets[y])
|
|
if err != nil {
|
|
log.Errorf(log.ExchangeSys,
|
|
"Exchange %s could not retrieve enabled currencies. Err: %s\n",
|
|
exchName,
|
|
err)
|
|
continue
|
|
}
|
|
for z := range currencies {
|
|
tp, err := exchanges[x].FetchTicker(currencies[z], assets[y])
|
|
if err != nil {
|
|
log.Errorf(log.ExchangeSys, "Exchange %s failed to retrieve %s ticker. Err: %s\n", exchName,
|
|
currencies[z].String(),
|
|
err)
|
|
continue
|
|
}
|
|
exchangeTicker.ExchangeValues = append(exchangeTicker.ExchangeValues, *tp)
|
|
}
|
|
tickerData = append(tickerData, exchangeTicker)
|
|
}
|
|
}
|
|
return tickerData
|
|
}
|
|
|
|
func verifyCert(pemData []byte) error {
|
|
var pemBlock *pem.Block
|
|
pemBlock, _ = pem.Decode(pemData)
|
|
if pemBlock == nil {
|
|
return errCertDataIsNil
|
|
}
|
|
|
|
if pemBlock.Type != "CERTIFICATE" {
|
|
return errCertTypeInvalid
|
|
}
|
|
|
|
cert, err := x509.ParseCertificate(pemBlock.Bytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if time.Now().After(cert.NotAfter) {
|
|
return errCertExpired
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func checkCerts(certDir string) error {
|
|
certFile := filepath.Join(certDir, "cert.pem")
|
|
keyFile := filepath.Join(certDir, "key.pem")
|
|
|
|
if !file.Exists(certFile) || !file.Exists(keyFile) {
|
|
log.Warnln(log.Global, "gRPC certificate/key file missing, recreating...")
|
|
return genCert(certDir)
|
|
}
|
|
|
|
pemData, err := ioutil.ReadFile(certFile)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to open TLS cert file: %s", err)
|
|
}
|
|
|
|
if err = verifyCert(pemData); err != nil {
|
|
if err != errCertExpired {
|
|
return err
|
|
}
|
|
log.Warnln(log.Global, "gRPC certificate has expired, regenerating...")
|
|
return genCert(certDir)
|
|
}
|
|
|
|
log.Infoln(log.Global, "gRPC TLS certificate and key files exist, will use them.")
|
|
return nil
|
|
}
|
|
|
|
func genCert(targetDir string) error {
|
|
privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to generate ecdsa private key: %s", err)
|
|
}
|
|
|
|
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
|
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to generate serial number: %s", err)
|
|
}
|
|
|
|
host, err := os.Hostname()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get hostname: %s", err)
|
|
}
|
|
|
|
dnsNames := []string{host}
|
|
if host != "localhost" {
|
|
dnsNames = append(dnsNames, "localhost")
|
|
}
|
|
|
|
template := x509.Certificate{
|
|
SerialNumber: serialNumber,
|
|
Subject: pkix.Name{
|
|
Organization: []string{"gocryptotrader"},
|
|
CommonName: host,
|
|
},
|
|
NotBefore: time.Now(),
|
|
NotAfter: time.Now().Add(time.Hour * 24 * 365),
|
|
IsCA: true,
|
|
BasicConstraintsValid: true,
|
|
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
|
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
|
IPAddresses: []net.IP{
|
|
net.ParseIP("127.0.0.1"),
|
|
net.ParseIP("::1"),
|
|
},
|
|
DNSNames: dnsNames,
|
|
}
|
|
|
|
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privKey.PublicKey, privKey)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create certificate: %s", err)
|
|
}
|
|
|
|
certData := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
|
|
if certData == nil {
|
|
return fmt.Errorf("cert data is nil")
|
|
}
|
|
|
|
b, err := x509.MarshalECPrivateKey(privKey)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal ECDSA private key: %s", err)
|
|
}
|
|
|
|
keyData := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: b})
|
|
if keyData == nil {
|
|
return fmt.Errorf("key pem data is nil")
|
|
}
|
|
|
|
err = file.Write(filepath.Join(targetDir, "key.pem"), keyData)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to write key.pem file %s", err)
|
|
}
|
|
|
|
err = file.Write(filepath.Join(targetDir, "cert.pem"), certData)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to write cert.pem file %s", err)
|
|
}
|
|
|
|
log.Infof(log.Global, "gRPC TLS key.pem and cert.pem files written to %s\n", targetDir)
|
|
return nil
|
|
}
|