mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 23:16:45 +00:00
* WIP * end of day WIP started migration of trade history * added kline support to hitbtc huobi lbank * added exchangehistory to all supported exchanges started work on coinbase 300 candles/request method * end of day WIP * removed unused ta and misc changes to flag ready for review * yobit cleanup * revert coinbase changES * general code clean up and added zb support * poloniex support added * renamed method to FormatExchangeKlineInterval other misc fixes * linter fixes * linter fixes * removed verbose * fixed poloniex test coverage * revert poloniex mock data * regenerated poloniex mock data * a very verbose clean up * binance mock clean up * removed unneeded t.Log() * setting verbose to true to debug CI issue * first pass changes addressed * common.ErrNotYetImplemented implemented :D * comments added * WIP-addressed exchange requests and reverted previous GetExchangeHistory changes * WIP-addressed exchange requests and reverted previous GetExchangeHistory changes * increased test coverage added kraken support * OKGroup support completed started work on address GetExchangeHistory feedback and migrating to own PR under https://github.com/xtda/gocryptotrader/tree/exchange_history * convert zb ratelimits * gofmt run on okcoin * increased delay on rate limit * gofmt package * fixed panic with coinbene and bithumb if conversion fails * very broken end of day WIP * added support for GetHistoricCandlesEx to coinbase and binance * gofmt package * coinbase, btcmarkets, zb ex wrapper function added * added all exchange support for ex regenerated mock data * update bithumb to return wrapper method * gofmt package * end of day started work on changes * models created for exchange/asset/currency/currencypair, new seed system created * reworked test coverage added okgroup support general fixes/change requests addressed * Added OneMonth * limit checks on supportedexchanges * reverted getexchangehistory * started work on currency seeding and insertion * reworked binance tesT * added workaround for kraken panic * :D extremely broken WIP * renamed command to extended removed interval check on non-implemented commands * added wrapperconfig back * increased test coverage for FormatExchangeKlineInterval * WIP * increased test coverage for FormatExchangeKlineInterval bitfinex/gateio/huobi * linter fixes * zb kraken lbank coinbene btcmarkets support added * removed verbose * OK group support for other asset types added * swapped margin to use spot endpoint * index support added test coverage added for asset types * added asset type to okcoin test * gofmt * add asset to extended method * removed verbose * Very broken WIP models need to be regenerated * add support for coinbene swap increase test coverage * removed verbose * small clean up of okgroup wrapper functions * verbose to troubleshoot CI issues * removed verbose * added error check reverted coinbasechanges * attempting to fix broken model generation * readme updated * :D i broke so much * model regeneration fixed & complete * candle model filled out * removed unused start/finish started work on decoupling api requests from kline package * restructured coinbene, bithumb methods, added bitstamp support * kraken time fix * BTCMarkets restructure * typo fix * removed test for futures due to contact changing * removed test for futures due to contact changing * added start/end date to extended method over range * WIP * added sync option to candles * converted to assettranslator * removed verbose * removed verbose * removed invalid char * reverted incorrectly removed return * added import * further template updates * macos hates my keyboard :D * misc canges * started work on creating kline from databases eed * x -> i * removed verbose * updated fixCasing to allocate var before checks * sqlite3 supported work started * removed time conversion * further work on tets * sort all outgoing kline candles * fixCasing fix * after/before checks added * added parallel to test * logic check on BTCmarkets * removed unused param, used correct iterator * converted HitBTC to use time.Time * test update * add iszero false check to candle times * Seed exchange & OHCLV data for test usage * updated resultlimit to 5000 * new line added * added comment to exported const * move date forward * use configured ratelimit * fixed pair for test * panic fixed WIP on fixCasing * fixCasing rework, started work on readme docs * enable rate limiter for wrapper issues tool * docs updated * removed unused vars from tests * removed err from return and formatted currency * updated Yobit supported status * Updated HitBTC to use onehour candles due to test exeuction times * added further details to gctcli output * added link to docs * added link to tempalte * disable FTX websocket in config_example * fix poloneix * regenerated poloniex mock data * removed recording flag * format on package * moved exchange var outside of method scope * reworked seeding into package * verbose output improved * removed verbose from candles * Added comments to exported functions * removed verbose output * Reworking of tests * end of day commit * added SQLite migration for asset, test updates for exchange, added support for withdrawal for new exchange_name_id relation * regenerated database models * WIP * test rework, sqlite migration updates for withdrawal * Reworked error returns to stop duplications, format all output to UTC, changed gctscript OHLCV output to be unix timestmap, started work on seeding tool * gofmt * dbseed command for seeding exchanges added, removed seed from dbmigrate, LoadCSV method added for exchange * go mod tidy * added import candle from csv command to dbseed * Removed reset & duplicate migrate command from helper, renamed struct to Item/Candle over Candle/Tick, added test coverage to dbseed, improved withdraw tests * remove broken tests due to ORM generation of Fk, removed go unneeded goroutine for inserting records * removed t.Cleanup usage because appveyor * added test coverage to StoreInDatabase() * removed unneeded data from config for test * added new line * Added down migration support to candle/asset removal, return original error and display rollback error * removed unneeded err assignment, break out of loop on error * add err check to method for test * first pass changes * WIP * Updated migrations for both sqlite3 & postgres to create exchanges if any withdrawal_history records are found, removed migrate command * removed argusage as usage information is provided by flags * added inserted records return count and test coverage * new line added * Database: comment config details out to disable local postgresl testing by default * added asset support * Database: added error return when no exchanges are found, title exchange name across queries * Fixed test data * Database: removed migration bool from ConnectToDatabase(), removed empty line, insert asset on test * Database: verbose linter :D * Database/OHLCV: removed go module from dbseed command and reverted back to main module, converted interval to int64 to match other parts of code base, provided migrations to update database, poloniex fix to skip first candle * dbseed: add completed message to output * Database: added migration to add asset to uniq index for candle table * Database: database -> exchange * Database: add asset to upsert conflict * Poloneix: fix for invalid interval * regenerated poloniex mock data * Database: added down migration for candle interval update * OHCLV/Database: WIP * OHLCV/Datastore: added new ValidateKline() method to check that asset, pair, interval are enabled/supported by exchange and updated tests * revert configtest changes * OHCLV/Kline: pointer assignment to ErrorKline & format pairs on check * goimports * migration updates * Database/Candle: updated tests * revert configtest changes * ZB: updaed defaults to use uppercase pairs * ZB: updaed defaults to use uppercase pairs * revert pair formatting * Switch over to .Cotains() method from pairManagement * Added comment & ftx back 😆 * OHLCV/Datastore: (Candles): added not null to asset, (WithdrawalHistory): added not null to exchange_name_id), reworked KlineError return * set verbose to false * updated btc markets test * Updated readme * removed ability to have blank exchange name as indexing requires it * remove old usedb and empty exchange check, return error on no data instead * Updated kline test to match date range * Renamed candles.exchange_id column to candles.xchange_name_id to match withdrawal table, return err on unwrap, zb fix revert * regenerated sqlite3 models * force UTC for sqlite.... because sqlite * OHLCV/Datastore: upper case pairs on insert for consistency, remove unneeded rollback call on commit failure as it has no effect, move rollback outside of insertsqlite/insertpsql methods * add error check for no candles * nil length/cap is 0 in golang :D * OHLCV/Datastore: updated wording on examples, removed duplicate testfile * OHLCV/Datastore: updated README with links to dbseed * dbtool -> dbseed
1100 lines
34 KiB
Go
1100 lines
34 KiB
Go
package exchange
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/thrasher-corp/gocryptotrader/common"
|
|
"github.com/thrasher-corp/gocryptotrader/common/crypto"
|
|
"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/kline"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/protocol"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
|
|
"github.com/thrasher-corp/gocryptotrader/log"
|
|
"github.com/thrasher-corp/gocryptotrader/portfolio/banking"
|
|
)
|
|
|
|
const (
|
|
warningBase64DecryptSecretKeyFailed = "exchange %s unable to base64 decode secret key.. Disabling Authenticated API support" // nolint // False positive (G101: Potential hardcoded credentials)
|
|
// WarningAuthenticatedRequestWithoutCredentialsSet error message for authenticated request without credentials set
|
|
WarningAuthenticatedRequestWithoutCredentialsSet = "exchange %s authenticated HTTP request called but not supported due to unset/default API keys"
|
|
// DefaultHTTPTimeout is the default HTTP/HTTPS Timeout for exchange requests
|
|
DefaultHTTPTimeout = time.Second * 15
|
|
// DefaultWebsocketResponseCheckTimeout is the default delay in checking for an expected websocket response
|
|
DefaultWebsocketResponseCheckTimeout = time.Millisecond * 50
|
|
// DefaultWebsocketResponseMaxLimit is the default max wait for an expected websocket response before a timeout
|
|
DefaultWebsocketResponseMaxLimit = time.Second * 7
|
|
// DefaultWebsocketOrderbookBufferLimit is the maximum number of orderbook updates that get stored before being applied
|
|
DefaultWebsocketOrderbookBufferLimit = 5
|
|
)
|
|
|
|
func (e *Base) checkAndInitRequester() {
|
|
if e.Requester == nil {
|
|
e.Requester = request.New(e.Name,
|
|
&http.Client{Transport: new(http.Transport)})
|
|
}
|
|
}
|
|
|
|
// SetHTTPClientTimeout sets the timeout value for the exchanges HTTP Client and
|
|
// also the underlying transports idle connection timeout
|
|
func (e *Base) SetHTTPClientTimeout(t time.Duration) error {
|
|
e.checkAndInitRequester()
|
|
e.Requester.HTTPClient.Timeout = t
|
|
tr, ok := e.Requester.HTTPClient.Transport.(*http.Transport)
|
|
if !ok {
|
|
return errors.New("transport not set, cannot set timeout")
|
|
}
|
|
tr.IdleConnTimeout = t
|
|
return nil
|
|
}
|
|
|
|
// SetHTTPClient sets exchanges HTTP client
|
|
func (e *Base) SetHTTPClient(h *http.Client) {
|
|
e.checkAndInitRequester()
|
|
e.Requester.HTTPClient = h
|
|
}
|
|
|
|
// GetHTTPClient gets the exchanges HTTP client
|
|
func (e *Base) GetHTTPClient() *http.Client {
|
|
e.checkAndInitRequester()
|
|
return e.Requester.HTTPClient
|
|
}
|
|
|
|
// SetHTTPClientUserAgent sets the exchanges HTTP user agent
|
|
func (e *Base) SetHTTPClientUserAgent(ua string) {
|
|
e.checkAndInitRequester()
|
|
e.Requester.UserAgent = ua
|
|
e.HTTPUserAgent = ua
|
|
}
|
|
|
|
// GetHTTPClientUserAgent gets the exchanges HTTP user agent
|
|
func (e *Base) GetHTTPClientUserAgent() string {
|
|
return e.HTTPUserAgent
|
|
}
|
|
|
|
// SetClientProxyAddress sets a proxy address for REST and websocket requests
|
|
func (e *Base) SetClientProxyAddress(addr string) error {
|
|
if addr == "" {
|
|
return nil
|
|
}
|
|
proxy, err := url.Parse(addr)
|
|
if err != nil {
|
|
return fmt.Errorf("exchange.go - setting proxy address error %s",
|
|
err)
|
|
}
|
|
|
|
err = e.Requester.SetProxy(proxy)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if e.Websocket != nil {
|
|
err = e.Websocket.SetProxyAddress(addr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SetFeatureDefaults sets the exchanges default feature
|
|
// support set
|
|
func (e *Base) SetFeatureDefaults() {
|
|
if e.Config.Features == nil {
|
|
s := &config.FeaturesConfig{
|
|
Supports: config.FeaturesSupportedConfig{
|
|
Websocket: e.Features.Supports.Websocket,
|
|
REST: e.Features.Supports.REST,
|
|
RESTCapabilities: protocol.Features{
|
|
AutoPairUpdates: e.Features.Supports.RESTCapabilities.AutoPairUpdates,
|
|
},
|
|
},
|
|
}
|
|
|
|
if e.Config.SupportsAutoPairUpdates != nil {
|
|
s.Supports.RESTCapabilities.AutoPairUpdates = *e.Config.SupportsAutoPairUpdates
|
|
s.Enabled.AutoPairUpdates = *e.Config.SupportsAutoPairUpdates
|
|
} else {
|
|
s.Supports.RESTCapabilities.AutoPairUpdates = e.Features.Supports.RESTCapabilities.AutoPairUpdates
|
|
s.Enabled.AutoPairUpdates = e.Features.Supports.RESTCapabilities.AutoPairUpdates
|
|
if !s.Supports.RESTCapabilities.AutoPairUpdates {
|
|
e.Config.CurrencyPairs.LastUpdated = time.Now().Unix()
|
|
e.CurrencyPairs.LastUpdated = e.Config.CurrencyPairs.LastUpdated
|
|
}
|
|
}
|
|
e.Config.Features = s
|
|
e.Config.SupportsAutoPairUpdates = nil
|
|
} else {
|
|
if e.Features.Supports.RESTCapabilities.AutoPairUpdates != e.Config.Features.Supports.RESTCapabilities.AutoPairUpdates {
|
|
e.Config.Features.Supports.RESTCapabilities.AutoPairUpdates = e.Features.Supports.RESTCapabilities.AutoPairUpdates
|
|
|
|
if !e.Config.Features.Supports.RESTCapabilities.AutoPairUpdates {
|
|
e.Config.CurrencyPairs.LastUpdated = time.Now().Unix()
|
|
}
|
|
}
|
|
|
|
if e.Features.Supports.REST != e.Config.Features.Supports.REST {
|
|
e.Config.Features.Supports.REST = e.Features.Supports.REST
|
|
}
|
|
|
|
if e.Features.Supports.RESTCapabilities.TickerBatching != e.Config.Features.Supports.RESTCapabilities.TickerBatching {
|
|
e.Config.Features.Supports.RESTCapabilities.TickerBatching = e.Features.Supports.RESTCapabilities.TickerBatching
|
|
}
|
|
|
|
if e.Features.Supports.Websocket != e.Config.Features.Supports.Websocket {
|
|
e.Config.Features.Supports.Websocket = e.Features.Supports.Websocket
|
|
}
|
|
|
|
e.Features.Enabled.AutoPairUpdates = e.Config.Features.Enabled.AutoPairUpdates
|
|
}
|
|
}
|
|
|
|
// SetAPICredentialDefaults sets the API Credential validator defaults
|
|
func (e *Base) SetAPICredentialDefaults() {
|
|
// Exchange hardcoded settings take precedence and overwrite the config settings
|
|
if e.Config.API.CredentialsValidator == nil {
|
|
e.Config.API.CredentialsValidator = new(config.APICredentialsValidatorConfig)
|
|
}
|
|
if e.Config.API.CredentialsValidator.RequiresKey != e.API.CredentialsValidator.RequiresKey {
|
|
e.Config.API.CredentialsValidator.RequiresKey = e.API.CredentialsValidator.RequiresKey
|
|
}
|
|
|
|
if e.Config.API.CredentialsValidator.RequiresSecret != e.API.CredentialsValidator.RequiresSecret {
|
|
e.Config.API.CredentialsValidator.RequiresSecret = e.API.CredentialsValidator.RequiresSecret
|
|
}
|
|
|
|
if e.Config.API.CredentialsValidator.RequiresBase64DecodeSecret != e.API.CredentialsValidator.RequiresBase64DecodeSecret {
|
|
e.Config.API.CredentialsValidator.RequiresBase64DecodeSecret = e.API.CredentialsValidator.RequiresBase64DecodeSecret
|
|
}
|
|
|
|
if e.Config.API.CredentialsValidator.RequiresClientID != e.API.CredentialsValidator.RequiresClientID {
|
|
e.Config.API.CredentialsValidator.RequiresClientID = e.API.CredentialsValidator.RequiresClientID
|
|
}
|
|
|
|
if e.Config.API.CredentialsValidator.RequiresPEM != e.API.CredentialsValidator.RequiresPEM {
|
|
e.Config.API.CredentialsValidator.RequiresPEM = e.API.CredentialsValidator.RequiresPEM
|
|
}
|
|
}
|
|
|
|
// SupportsRESTTickerBatchUpdates returns whether or not the
|
|
// exhange supports REST batch ticker fetching
|
|
func (e *Base) SupportsRESTTickerBatchUpdates() bool {
|
|
return e.Features.Supports.RESTCapabilities.TickerBatching
|
|
}
|
|
|
|
// SupportsAutoPairUpdates returns whether or not the exchange supports
|
|
// auto currency pair updating
|
|
func (e *Base) SupportsAutoPairUpdates() bool {
|
|
if e.Features.Supports.RESTCapabilities.AutoPairUpdates ||
|
|
e.Features.Supports.WebsocketCapabilities.AutoPairUpdates {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// GetLastPairsUpdateTime returns the unix timestamp of when the exchanges
|
|
// currency pairs were last updated
|
|
func (e *Base) GetLastPairsUpdateTime() int64 {
|
|
return e.CurrencyPairs.LastUpdated
|
|
}
|
|
|
|
// GetAssetTypes returns the available asset types for an individual exchange
|
|
func (e *Base) GetAssetTypes() asset.Items {
|
|
return e.CurrencyPairs.GetAssetTypes()
|
|
}
|
|
|
|
// GetPairAssetType returns the associated asset type for the currency pair
|
|
// This method is only useful for exchanges that have pair names with multiple delimiters (BTC-USD-0626)
|
|
// Helpful if the exchange has only a single asset type but in that case the asset type can be hard coded
|
|
func (e *Base) GetPairAssetType(c currency.Pair) (asset.Item, error) {
|
|
assetTypes := e.GetAssetTypes()
|
|
for i := range assetTypes {
|
|
avail, err := e.GetAvailablePairs(assetTypes[i])
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if avail.Contains(c, true) {
|
|
return assetTypes[i], nil
|
|
}
|
|
}
|
|
return "", errors.New("asset type not associated with currency pair")
|
|
}
|
|
|
|
// GetClientBankAccounts returns banking details associated with
|
|
// a client for withdrawal purposes
|
|
func (e *Base) GetClientBankAccounts(exchangeName, withdrawalCurrency string) (*banking.Account, error) {
|
|
cfg := config.GetConfig()
|
|
return cfg.GetClientBankAccounts(exchangeName, withdrawalCurrency)
|
|
}
|
|
|
|
// GetExchangeBankAccounts returns banking details associated with an
|
|
// exchange for funding purposes
|
|
func (e *Base) GetExchangeBankAccounts(id, depositCurrency string) (*banking.Account, error) {
|
|
cfg := config.GetConfig()
|
|
return cfg.GetExchangeBankAccounts(e.Name, id, depositCurrency)
|
|
}
|
|
|
|
// SetCurrencyPairFormat checks the exchange request and config currency pair
|
|
// formats and syncs it with the exchanges SetDefault settings
|
|
func (e *Base) SetCurrencyPairFormat() {
|
|
if e.Config.CurrencyPairs == nil {
|
|
e.Config.CurrencyPairs = new(currency.PairsManager)
|
|
}
|
|
|
|
e.Config.CurrencyPairs.UseGlobalFormat = e.CurrencyPairs.UseGlobalFormat
|
|
if e.Config.CurrencyPairs.UseGlobalFormat {
|
|
e.Config.CurrencyPairs.RequestFormat = e.CurrencyPairs.RequestFormat
|
|
e.Config.CurrencyPairs.ConfigFormat = e.CurrencyPairs.ConfigFormat
|
|
return
|
|
}
|
|
|
|
if e.Config.CurrencyPairs.ConfigFormat != nil {
|
|
e.Config.CurrencyPairs.ConfigFormat = nil
|
|
}
|
|
if e.Config.CurrencyPairs.RequestFormat != nil {
|
|
e.Config.CurrencyPairs.RequestFormat = nil
|
|
}
|
|
|
|
assetTypes := e.GetAssetTypes()
|
|
for x := range assetTypes {
|
|
if _, err := e.Config.CurrencyPairs.Get(assetTypes[x]); err != nil {
|
|
ps, err := e.CurrencyPairs.Get(assetTypes[x])
|
|
if err != nil {
|
|
continue
|
|
}
|
|
e.Config.CurrencyPairs.Store(assetTypes[x], *ps)
|
|
}
|
|
}
|
|
}
|
|
|
|
// SetConfigPairs sets the exchanges currency pairs to the pairs set in the config
|
|
func (e *Base) SetConfigPairs() error {
|
|
assetTypes := e.Config.CurrencyPairs.GetAssetTypes()
|
|
exchangeAssets := e.CurrencyPairs.GetAssetTypes()
|
|
for x := range assetTypes {
|
|
if !exchangeAssets.Contains(assetTypes[x]) {
|
|
log.Warnf(log.ExchangeSys,
|
|
"%s exchange asset type %s unsupported, please manually remove from configuration",
|
|
e.Name,
|
|
assetTypes[x])
|
|
}
|
|
cfgPS, err := e.Config.CurrencyPairs.Get(assetTypes[x])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var enabledAsset bool
|
|
if e.Config.CurrencyPairs.IsAssetEnabled(assetTypes[x]) == nil {
|
|
enabledAsset = true
|
|
}
|
|
e.CurrencyPairs.SetAssetEnabled(assetTypes[x], enabledAsset)
|
|
|
|
if e.Config.CurrencyPairs.UseGlobalFormat {
|
|
e.CurrencyPairs.StorePairs(assetTypes[x], cfgPS.Available, false)
|
|
e.CurrencyPairs.StorePairs(assetTypes[x], cfgPS.Enabled, true)
|
|
continue
|
|
}
|
|
exchPS, err := e.CurrencyPairs.Get(assetTypes[x])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cfgPS.ConfigFormat = exchPS.ConfigFormat
|
|
cfgPS.RequestFormat = exchPS.RequestFormat
|
|
e.CurrencyPairs.StorePairs(assetTypes[x], cfgPS.Available, false)
|
|
e.CurrencyPairs.StorePairs(assetTypes[x], cfgPS.Enabled, true)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetAuthenticatedAPISupport returns whether the exchange supports
|
|
// authenticated API requests
|
|
func (e *Base) GetAuthenticatedAPISupport(endpoint uint8) bool {
|
|
switch endpoint {
|
|
case RestAuthentication:
|
|
return e.API.AuthenticatedSupport
|
|
case WebsocketAuthentication:
|
|
return e.API.AuthenticatedWebsocketSupport
|
|
}
|
|
return false
|
|
}
|
|
|
|
// GetName is a method that returns the name of the exchange base
|
|
func (e *Base) GetName() string {
|
|
return e.Name
|
|
}
|
|
|
|
// GetEnabledFeatures returns the exchanges enabled features
|
|
func (e *Base) GetEnabledFeatures() FeaturesEnabled {
|
|
return e.Features.Enabled
|
|
}
|
|
|
|
// GetSupportedFeatures returns the exchanges supported features
|
|
func (e *Base) GetSupportedFeatures() FeaturesSupported {
|
|
return e.Features.Supports
|
|
}
|
|
|
|
// GetPairFormat returns the pair format based on the exchange and
|
|
// asset type
|
|
func (e *Base) GetPairFormat(assetType asset.Item, requestFormat bool) (currency.PairFormat, error) {
|
|
if e.CurrencyPairs.UseGlobalFormat {
|
|
if requestFormat {
|
|
if e.CurrencyPairs.RequestFormat == nil {
|
|
return currency.PairFormat{},
|
|
errors.New("global request format is nil")
|
|
}
|
|
return *e.CurrencyPairs.RequestFormat, nil
|
|
}
|
|
|
|
if e.CurrencyPairs.ConfigFormat == nil {
|
|
return currency.PairFormat{},
|
|
errors.New("global config format is nil")
|
|
}
|
|
return *e.CurrencyPairs.ConfigFormat, nil
|
|
}
|
|
|
|
ps, err := e.CurrencyPairs.Get(assetType)
|
|
if err != nil {
|
|
return currency.PairFormat{}, err
|
|
}
|
|
|
|
if requestFormat {
|
|
if ps.RequestFormat == nil {
|
|
return currency.PairFormat{},
|
|
errors.New("asset type request format is nil")
|
|
}
|
|
return *ps.RequestFormat, nil
|
|
}
|
|
|
|
if ps.ConfigFormat == nil {
|
|
return currency.PairFormat{},
|
|
errors.New("asset type config format is nil")
|
|
}
|
|
return *ps.ConfigFormat, nil
|
|
}
|
|
|
|
// GetEnabledPairs is a method that returns the enabled currency pairs of
|
|
// the exchange by asset type, if the asset type is disabled this will return no
|
|
// enabled pairs
|
|
func (e *Base) GetEnabledPairs(a asset.Item) (currency.Pairs, error) {
|
|
err := e.CurrencyPairs.IsAssetEnabled(a)
|
|
if err != nil {
|
|
return nil, nil
|
|
}
|
|
format, err := e.GetPairFormat(a, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
enabledpairs, err := e.CurrencyPairs.GetPairs(a, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return enabledpairs.Format(format.Delimiter,
|
|
format.Index,
|
|
format.Uppercase),
|
|
nil
|
|
}
|
|
|
|
// GetRequestFormattedPairAndAssetType is a method that returns the enabled currency pair of
|
|
// along with its asset type. Only use when there is no chance of the same name crossing over
|
|
func (e *Base) GetRequestFormattedPairAndAssetType(p string) (currency.Pair, asset.Item, error) {
|
|
assetTypes := e.GetAssetTypes()
|
|
var response currency.Pair
|
|
for i := range assetTypes {
|
|
format, err := e.GetPairFormat(assetTypes[i], true)
|
|
if err != nil {
|
|
return response, assetTypes[i], err
|
|
}
|
|
|
|
pairs, err := e.CurrencyPairs.GetPairs(assetTypes[i], true)
|
|
if err != nil {
|
|
return response, assetTypes[i], err
|
|
}
|
|
|
|
for j := range pairs {
|
|
formattedPair := pairs[j].Format(format.Delimiter, format.Uppercase)
|
|
if strings.EqualFold(formattedPair.String(), p) {
|
|
return formattedPair, assetTypes[i], nil
|
|
}
|
|
}
|
|
}
|
|
return response, "", errors.New("pair not found: " + p)
|
|
}
|
|
|
|
// GetAvailablePairs is a method that returns the available currency pairs
|
|
// of the exchange by asset type
|
|
func (e *Base) GetAvailablePairs(assetType asset.Item) (currency.Pairs, error) {
|
|
format, err := e.GetPairFormat(assetType, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pairs, err := e.CurrencyPairs.GetPairs(assetType, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return pairs.Format(format.Delimiter, format.Index, format.Uppercase), nil
|
|
}
|
|
|
|
// SupportsPair returns true or not whether a currency pair exists in the
|
|
// exchange available currencies or not
|
|
func (e *Base) SupportsPair(p currency.Pair, enabledPairs bool, assetType asset.Item) error {
|
|
if enabledPairs {
|
|
pairs, err := e.GetEnabledPairs(assetType)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if pairs.Contains(p, false) {
|
|
return nil
|
|
}
|
|
return errors.New("pair not supported")
|
|
}
|
|
|
|
avail, err := e.GetAvailablePairs(assetType)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if avail.Contains(p, false) {
|
|
return nil
|
|
}
|
|
return errors.New("pair not supported")
|
|
}
|
|
|
|
// FormatExchangeCurrencies returns a string containing
|
|
// the exchanges formatted currency pairs
|
|
func (e *Base) FormatExchangeCurrencies(pairs []currency.Pair, assetType asset.Item) (string, error) {
|
|
var currencyItems strings.Builder
|
|
pairFmt, err := e.GetPairFormat(assetType, true)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
for x := range pairs {
|
|
format, err := e.FormatExchangeCurrency(pairs[x], assetType)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
currencyItems.WriteString(format.String())
|
|
if x == len(pairs)-1 {
|
|
continue
|
|
}
|
|
currencyItems.WriteString(pairFmt.Separator)
|
|
}
|
|
|
|
if currencyItems.Len() == 0 {
|
|
return "", errors.New("returned empty string")
|
|
}
|
|
return currencyItems.String(), nil
|
|
}
|
|
|
|
// FormatExchangeCurrency is a method that formats and returns a currency pair
|
|
// based on the user currency display preferences
|
|
func (e *Base) FormatExchangeCurrency(p currency.Pair, assetType asset.Item) (currency.Pair, error) {
|
|
pairFmt, err := e.GetPairFormat(assetType, true)
|
|
if err != nil {
|
|
return currency.Pair{}, err
|
|
}
|
|
return p.Format(pairFmt.Delimiter, pairFmt.Uppercase), nil
|
|
}
|
|
|
|
// SetEnabled is a method that sets if the exchange is enabled
|
|
func (e *Base) SetEnabled(enabled bool) {
|
|
e.Enabled = enabled
|
|
}
|
|
|
|
// IsEnabled is a method that returns if the current exchange is enabled
|
|
func (e *Base) IsEnabled() bool {
|
|
return e.Enabled
|
|
}
|
|
|
|
// SetAPIKeys is a method that sets the current API keys for the exchange
|
|
func (e *Base) SetAPIKeys(apiKey, apiSecret, clientID string) {
|
|
e.API.Credentials.Key = apiKey
|
|
e.API.Credentials.ClientID = clientID
|
|
|
|
if e.API.CredentialsValidator.RequiresBase64DecodeSecret {
|
|
result, err := crypto.Base64Decode(apiSecret)
|
|
if err != nil {
|
|
e.API.AuthenticatedSupport = false
|
|
e.API.AuthenticatedWebsocketSupport = false
|
|
log.Warnf(log.ExchangeSys,
|
|
warningBase64DecryptSecretKeyFailed,
|
|
e.Name)
|
|
return
|
|
}
|
|
e.API.Credentials.Secret = string(result)
|
|
} else {
|
|
e.API.Credentials.Secret = apiSecret
|
|
}
|
|
}
|
|
|
|
// SetupDefaults sets the exchange settings based on the supplied config
|
|
func (e *Base) SetupDefaults(exch *config.ExchangeConfig) error {
|
|
e.Enabled = true
|
|
e.LoadedByConfig = true
|
|
e.Config = exch
|
|
e.Verbose = exch.Verbose
|
|
|
|
e.API.AuthenticatedSupport = exch.API.AuthenticatedSupport
|
|
e.API.AuthenticatedWebsocketSupport = exch.API.AuthenticatedWebsocketSupport
|
|
if e.API.AuthenticatedSupport || e.API.AuthenticatedWebsocketSupport {
|
|
e.SetAPIKeys(exch.API.Credentials.Key,
|
|
exch.API.Credentials.Secret,
|
|
exch.API.Credentials.ClientID)
|
|
}
|
|
|
|
if exch.HTTPTimeout <= time.Duration(0) {
|
|
exch.HTTPTimeout = DefaultHTTPTimeout
|
|
} else {
|
|
err := e.SetHTTPClientTimeout(exch.HTTPTimeout)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if exch.CurrencyPairs == nil {
|
|
exch.CurrencyPairs = new(currency.PairsManager)
|
|
}
|
|
|
|
e.HTTPDebugging = exch.HTTPDebugging
|
|
e.SetHTTPClientUserAgent(exch.HTTPUserAgent)
|
|
e.SetCurrencyPairFormat()
|
|
|
|
err := e.SetConfigPairs()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
e.SetFeatureDefaults()
|
|
err = e.SetAPIURL()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
e.SetAPICredentialDefaults()
|
|
|
|
err = e.SetClientProxyAddress(exch.ProxyAddress)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
e.BaseCurrencies = exch.BaseCurrencies
|
|
return nil
|
|
}
|
|
|
|
// AllowAuthenticatedRequest checks to see if the required fields have been set
|
|
// before sending an authenticated API request
|
|
func (e *Base) AllowAuthenticatedRequest() bool {
|
|
if e.SkipAuthCheck {
|
|
return true
|
|
}
|
|
|
|
// Individual package usage, allow request if API credentials are valid a
|
|
// and without needing to set AuthenticatedSupport to true
|
|
if !e.LoadedByConfig {
|
|
return e.ValidateAPICredentials()
|
|
}
|
|
|
|
// Bot usage, AuthenticatedSupport can be disabled by user if desired, so
|
|
// don't allow authenticated requests.
|
|
if !e.API.AuthenticatedSupport && !e.API.AuthenticatedWebsocketSupport {
|
|
return false
|
|
}
|
|
|
|
// Check to see if the user has enabled AuthenticatedSupport, but has
|
|
// invalid API credentials set and loaded by config
|
|
return e.ValidateAPICredentials()
|
|
}
|
|
|
|
// ValidateAPICredentials validates the exchanges API credentials
|
|
func (e *Base) ValidateAPICredentials() bool {
|
|
if e.API.CredentialsValidator.RequiresKey {
|
|
if e.API.Credentials.Key == "" ||
|
|
e.API.Credentials.Key == config.DefaultAPIKey {
|
|
log.Warnf(log.ExchangeSys,
|
|
"exchange %s requires API key but default/empty one set",
|
|
e.Name)
|
|
return false
|
|
}
|
|
}
|
|
|
|
if e.API.CredentialsValidator.RequiresSecret {
|
|
if e.API.Credentials.Secret == "" ||
|
|
e.API.Credentials.Secret == config.DefaultAPISecret {
|
|
log.Warnf(log.ExchangeSys,
|
|
"exchange %s requires API secret but default/empty one set",
|
|
e.Name)
|
|
return false
|
|
}
|
|
}
|
|
|
|
if e.API.CredentialsValidator.RequiresPEM {
|
|
if e.API.Credentials.PEMKey == "" ||
|
|
strings.Contains(e.API.Credentials.PEMKey, "JUSTADUMMY") {
|
|
log.Warnf(log.ExchangeSys,
|
|
"exchange %s requires API PEM key but default/empty one set",
|
|
e.Name)
|
|
return false
|
|
}
|
|
}
|
|
|
|
if e.API.CredentialsValidator.RequiresClientID {
|
|
if e.API.Credentials.ClientID == "" ||
|
|
e.API.Credentials.ClientID == config.DefaultAPIClientID {
|
|
log.Warnf(log.ExchangeSys,
|
|
"exchange %s requires API ClientID but default/empty one set",
|
|
e.Name)
|
|
return false
|
|
}
|
|
}
|
|
|
|
if e.API.CredentialsValidator.RequiresBase64DecodeSecret && !e.LoadedByConfig {
|
|
_, err := crypto.Base64Decode(e.API.Credentials.Secret)
|
|
if err != nil {
|
|
log.Warnf(log.ExchangeSys,
|
|
"exchange %s API secret base64 decode failed: %s",
|
|
e.Name, err)
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// SetPairs sets the exchange currency pairs for either enabledPairs or
|
|
// availablePairs
|
|
func (e *Base) SetPairs(pairs currency.Pairs, assetType asset.Item, enabled bool) error {
|
|
if len(pairs) == 0 {
|
|
return fmt.Errorf("%s SetPairs error - pairs is empty", e.Name)
|
|
}
|
|
|
|
pairFmt, err := e.GetPairFormat(assetType, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var newPairs currency.Pairs
|
|
for x := range pairs {
|
|
newPairs = append(newPairs, pairs[x].Format(pairFmt.Delimiter,
|
|
pairFmt.Uppercase))
|
|
}
|
|
|
|
e.CurrencyPairs.StorePairs(assetType, newPairs, enabled)
|
|
e.Config.CurrencyPairs.StorePairs(assetType, newPairs, enabled)
|
|
return nil
|
|
}
|
|
|
|
// UpdatePairs updates the exchange currency pairs for either enabledPairs or
|
|
// availablePairs
|
|
func (e *Base) UpdatePairs(exchangeProducts currency.Pairs, assetType asset.Item, enabled, force bool) error {
|
|
exchangeProducts = exchangeProducts.Upper()
|
|
var products currency.Pairs
|
|
for x := range exchangeProducts {
|
|
if exchangeProducts[x].String() == "" {
|
|
continue
|
|
}
|
|
products = append(products, exchangeProducts[x])
|
|
}
|
|
|
|
var updateType string
|
|
targetPairs, err := e.CurrencyPairs.GetPairs(assetType, enabled)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if enabled {
|
|
updateType = "enabled"
|
|
} else {
|
|
updateType = "available"
|
|
}
|
|
|
|
newPairs, removedPairs := targetPairs.FindDifferences(products)
|
|
if force || len(newPairs) > 0 || len(removedPairs) > 0 {
|
|
if force {
|
|
log.Debugf(log.ExchangeSys,
|
|
"%s forced update of %s [%v] pairs.",
|
|
e.Name,
|
|
updateType,
|
|
strings.ToUpper(assetType.String()))
|
|
} else {
|
|
if len(newPairs) > 0 {
|
|
log.Debugf(log.ExchangeSys,
|
|
"%s Updating %s pairs [%v] - Added: %s.\n",
|
|
e.Name,
|
|
updateType,
|
|
strings.ToUpper(assetType.String()),
|
|
newPairs)
|
|
}
|
|
if len(removedPairs) > 0 {
|
|
log.Debugf(log.ExchangeSys,
|
|
"%s Updating %s pairs [%v] - Removed: %s.\n",
|
|
e.Name,
|
|
updateType,
|
|
strings.ToUpper(assetType.String()),
|
|
removedPairs)
|
|
}
|
|
}
|
|
|
|
e.Config.CurrencyPairs.StorePairs(assetType, products, enabled)
|
|
e.CurrencyPairs.StorePairs(assetType, products, enabled)
|
|
|
|
if !enabled {
|
|
// If available pairs are changed we will remove currency pair items
|
|
// that are still included in the enabled pairs list.
|
|
enabledPairs, err := e.CurrencyPairs.GetPairs(assetType, true)
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
_, remove := enabledPairs.FindDifferences(products)
|
|
for i := range remove {
|
|
enabledPairs = enabledPairs.Remove(remove[i])
|
|
}
|
|
|
|
if len(remove) > 0 {
|
|
log.Debugf(log.ExchangeSys,
|
|
"%s Checked and updated enabled pairs [%v] - Removed: %s.\n",
|
|
e.Name,
|
|
strings.ToUpper(assetType.String()),
|
|
remove)
|
|
|
|
e.Config.CurrencyPairs.StorePairs(assetType, enabledPairs, true)
|
|
e.CurrencyPairs.StorePairs(assetType, enabledPairs, true)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SetAPIURL sets configuration API URL for an exchange
|
|
func (e *Base) SetAPIURL() error {
|
|
if e.Config.API.Endpoints.URL == "" || e.Config.API.Endpoints.URLSecondary == "" {
|
|
return fmt.Errorf("exchange %s: SetAPIURL error. URL vals are empty", e.Name)
|
|
}
|
|
|
|
checkInsecureEndpoint := func(endpoint string) {
|
|
if strings.Contains(endpoint, "https") {
|
|
return
|
|
}
|
|
log.Warnf(log.ExchangeSys,
|
|
"%s is using HTTP instead of HTTPS [%s] for API functionality, an"+
|
|
" attacker could eavesdrop on this connection. Use at your"+
|
|
" own risk.",
|
|
e.Name, endpoint)
|
|
}
|
|
|
|
if e.Config.API.Endpoints.URL != config.APIURLNonDefaultMessage {
|
|
e.API.Endpoints.URL = e.Config.API.Endpoints.URL
|
|
checkInsecureEndpoint(e.API.Endpoints.URL)
|
|
}
|
|
if e.Config.API.Endpoints.URLSecondary != config.APIURLNonDefaultMessage {
|
|
e.API.Endpoints.URLSecondary = e.Config.API.Endpoints.URLSecondary
|
|
checkInsecureEndpoint(e.API.Endpoints.URLSecondary)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetAPIURL returns the set API URL
|
|
func (e *Base) GetAPIURL() string {
|
|
return e.API.Endpoints.URL
|
|
}
|
|
|
|
// GetSecondaryAPIURL returns the set Secondary API URL
|
|
func (e *Base) GetSecondaryAPIURL() string {
|
|
return e.API.Endpoints.URLSecondary
|
|
}
|
|
|
|
// GetAPIURLDefault returns exchange default URL
|
|
func (e *Base) GetAPIURLDefault() string {
|
|
return e.API.Endpoints.URLDefault
|
|
}
|
|
|
|
// GetAPIURLSecondaryDefault returns exchange default secondary URL
|
|
func (e *Base) GetAPIURLSecondaryDefault() string {
|
|
return e.API.Endpoints.URLSecondaryDefault
|
|
}
|
|
|
|
// SupportsREST returns whether or not the exchange supports
|
|
// REST
|
|
func (e *Base) SupportsREST() bool {
|
|
return e.Features.Supports.REST
|
|
}
|
|
|
|
// GetWithdrawPermissions passes through the exchange's withdraw permissions
|
|
func (e *Base) GetWithdrawPermissions() uint32 {
|
|
return e.Features.Supports.WithdrawPermissions
|
|
}
|
|
|
|
// SupportsWithdrawPermissions compares the supplied permissions with the exchange's to verify they're supported
|
|
func (e *Base) SupportsWithdrawPermissions(permissions uint32) bool {
|
|
exchangePermissions := e.GetWithdrawPermissions()
|
|
return permissions&exchangePermissions == permissions
|
|
}
|
|
|
|
// FormatWithdrawPermissions will return each of the exchange's compatible withdrawal methods in readable form
|
|
func (e *Base) FormatWithdrawPermissions() string {
|
|
var services []string
|
|
for i := 0; i < 32; i++ {
|
|
var check uint32 = 1 << uint32(i)
|
|
if e.GetWithdrawPermissions()&check != 0 {
|
|
switch check {
|
|
case AutoWithdrawCrypto:
|
|
services = append(services, AutoWithdrawCryptoText)
|
|
case AutoWithdrawCryptoWithAPIPermission:
|
|
services = append(services, AutoWithdrawCryptoWithAPIPermissionText)
|
|
case AutoWithdrawCryptoWithSetup:
|
|
services = append(services, AutoWithdrawCryptoWithSetupText)
|
|
case WithdrawCryptoWith2FA:
|
|
services = append(services, WithdrawCryptoWith2FAText)
|
|
case WithdrawCryptoWithSMS:
|
|
services = append(services, WithdrawCryptoWithSMSText)
|
|
case WithdrawCryptoWithEmail:
|
|
services = append(services, WithdrawCryptoWithEmailText)
|
|
case WithdrawCryptoWithWebsiteApproval:
|
|
services = append(services, WithdrawCryptoWithWebsiteApprovalText)
|
|
case WithdrawCryptoWithAPIPermission:
|
|
services = append(services, WithdrawCryptoWithAPIPermissionText)
|
|
case AutoWithdrawFiat:
|
|
services = append(services, AutoWithdrawFiatText)
|
|
case AutoWithdrawFiatWithAPIPermission:
|
|
services = append(services, AutoWithdrawFiatWithAPIPermissionText)
|
|
case AutoWithdrawFiatWithSetup:
|
|
services = append(services, AutoWithdrawFiatWithSetupText)
|
|
case WithdrawFiatWith2FA:
|
|
services = append(services, WithdrawFiatWith2FAText)
|
|
case WithdrawFiatWithSMS:
|
|
services = append(services, WithdrawFiatWithSMSText)
|
|
case WithdrawFiatWithEmail:
|
|
services = append(services, WithdrawFiatWithEmailText)
|
|
case WithdrawFiatWithWebsiteApproval:
|
|
services = append(services, WithdrawFiatWithWebsiteApprovalText)
|
|
case WithdrawFiatWithAPIPermission:
|
|
services = append(services, WithdrawFiatWithAPIPermissionText)
|
|
case WithdrawCryptoViaWebsiteOnly:
|
|
services = append(services, WithdrawCryptoViaWebsiteOnlyText)
|
|
case WithdrawFiatViaWebsiteOnly:
|
|
services = append(services, WithdrawFiatViaWebsiteOnlyText)
|
|
case NoFiatWithdrawals:
|
|
services = append(services, NoFiatWithdrawalsText)
|
|
default:
|
|
services = append(services, fmt.Sprintf("%s[1<<%v]", UnknownWithdrawalTypeText, i))
|
|
}
|
|
}
|
|
}
|
|
if len(services) > 0 {
|
|
return strings.Join(services, " & ")
|
|
}
|
|
|
|
return NoAPIWithdrawalMethodsText
|
|
}
|
|
|
|
// SupportsAsset whether or not the supplied asset is supported
|
|
// by the exchange
|
|
func (e *Base) SupportsAsset(a asset.Item) bool {
|
|
_, ok := e.CurrencyPairs.Pairs[a]
|
|
return ok
|
|
}
|
|
|
|
// PrintEnabledPairs prints the exchanges enabled asset pairs
|
|
func (e *Base) PrintEnabledPairs() {
|
|
for k, v := range e.CurrencyPairs.Pairs {
|
|
log.Infof(log.ExchangeSys, "%s Asset type %v:\n\t Enabled pairs: %v",
|
|
e.Name, strings.ToUpper(k.String()), v.Enabled)
|
|
}
|
|
}
|
|
|
|
// GetBase returns the exchange base
|
|
func (e *Base) GetBase() *Base { return e }
|
|
|
|
// CheckTransientError catches transient errors and returns nil if found, used
|
|
// for validation of API credentials
|
|
func (e *Base) CheckTransientError(err error) error {
|
|
if _, ok := err.(net.Error); ok {
|
|
log.Warnf(log.ExchangeSys,
|
|
"%s net error captured, will not disable authentication %s",
|
|
e.Name,
|
|
err)
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
// DisableRateLimiter disables the rate limiting system for the exchange
|
|
func (e *Base) DisableRateLimiter() error {
|
|
return e.Requester.DisableRateLimiter()
|
|
}
|
|
|
|
// EnableRateLimiter enables the rate limiting system for the exchange
|
|
func (e *Base) EnableRateLimiter() error {
|
|
return e.Requester.EnableRateLimiter()
|
|
}
|
|
|
|
// StoreAssetPairFormat initialises and stores a defined asset format
|
|
func (e *Base) StoreAssetPairFormat(a asset.Item, f currency.PairStore) error {
|
|
if a.String() == "" {
|
|
return fmt.Errorf("%s cannot add to pairs manager, no asset provided",
|
|
e.Name)
|
|
}
|
|
|
|
if f.RequestFormat == nil {
|
|
return fmt.Errorf("%s cannot add to pairs manager, request pair format not provided",
|
|
e.Name)
|
|
}
|
|
|
|
if f.ConfigFormat == nil {
|
|
return fmt.Errorf("%s cannot add to pairs manager, config pair format not provided",
|
|
e.Name)
|
|
}
|
|
|
|
if e.CurrencyPairs.Pairs == nil {
|
|
e.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore)
|
|
}
|
|
|
|
e.CurrencyPairs.Pairs[a] = &f
|
|
return nil
|
|
}
|
|
|
|
// SetGlobalPairsManager sets defined asset and pairs management system with
|
|
// with global formatting
|
|
func (e *Base) SetGlobalPairsManager(request, config *currency.PairFormat, assets ...asset.Item) error {
|
|
if request == nil {
|
|
return fmt.Errorf("%s cannot set pairs manager, request pair format not provided",
|
|
e.Name)
|
|
}
|
|
|
|
if config == nil {
|
|
return fmt.Errorf("%s cannot set pairs manager, config pair format not provided",
|
|
e.Name)
|
|
}
|
|
|
|
if len(assets) == 0 {
|
|
return fmt.Errorf("%s cannot set pairs manager, no assets provided",
|
|
e.Name)
|
|
}
|
|
|
|
e.CurrencyPairs.UseGlobalFormat = true
|
|
e.CurrencyPairs.RequestFormat = request
|
|
e.CurrencyPairs.ConfigFormat = config
|
|
|
|
if e.CurrencyPairs.Pairs != nil {
|
|
return fmt.Errorf("%s cannot set pairs manager, pairs already set",
|
|
e.Name)
|
|
}
|
|
|
|
e.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore)
|
|
|
|
for i := range assets {
|
|
if assets[i].String() == "" {
|
|
e.CurrencyPairs.Pairs = nil
|
|
return fmt.Errorf("%s cannot set pairs manager, asset is empty string",
|
|
e.Name)
|
|
}
|
|
e.CurrencyPairs.Pairs[assets[i]] = new(currency.PairStore)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetWebsocket returns a pointer to the exchange websocket
|
|
func (e *Base) GetWebsocket() (*stream.Websocket, error) {
|
|
if e.Websocket == nil {
|
|
return nil, common.ErrFunctionNotSupported
|
|
}
|
|
return e.Websocket, nil
|
|
}
|
|
|
|
// SupportsWebsocket returns whether or not the exchange supports
|
|
// websocket
|
|
func (e *Base) SupportsWebsocket() bool {
|
|
return e.Features.Supports.Websocket
|
|
}
|
|
|
|
// IsWebsocketEnabled returns whether or not the exchange has its
|
|
// websocket client enabled
|
|
func (e *Base) IsWebsocketEnabled() bool {
|
|
if e.Websocket == nil {
|
|
return false
|
|
}
|
|
return e.Websocket.IsEnabled()
|
|
}
|
|
|
|
// FlushWebsocketChannels refreshes websocket channel subscriptions based on
|
|
// websocket features. Used in the event of a pair/asset or subscription change.
|
|
func (e *Base) FlushWebsocketChannels() error {
|
|
if e.Websocket == nil {
|
|
return nil
|
|
}
|
|
return e.Websocket.FlushChannels()
|
|
}
|
|
|
|
// SubscribeToWebsocketChannels appends to ChannelsToSubscribe
|
|
// which lets websocket.manageSubscriptions handle subscribing
|
|
func (e *Base) SubscribeToWebsocketChannels(channels []stream.ChannelSubscription) error {
|
|
if e.Websocket == nil {
|
|
return common.ErrFunctionNotSupported
|
|
}
|
|
return e.Websocket.SubscribeToChannels(channels)
|
|
}
|
|
|
|
// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe
|
|
// which lets websocket.manageSubscriptions handle unsubscribing
|
|
func (e *Base) UnsubscribeToWebsocketChannels(channels []stream.ChannelSubscription) error {
|
|
if e.Websocket == nil {
|
|
return common.ErrFunctionNotSupported
|
|
}
|
|
return e.Websocket.UnsubscribeChannels(channels)
|
|
}
|
|
|
|
// GetSubscriptions returns a copied list of subscriptions
|
|
func (e *Base) GetSubscriptions() ([]stream.ChannelSubscription, error) {
|
|
if e.Websocket == nil {
|
|
return nil, common.ErrFunctionNotSupported
|
|
}
|
|
return e.Websocket.GetSubscriptions(), nil
|
|
}
|
|
|
|
// AuthenticateWebsocket sends an authentication message to the websocket
|
|
func (e *Base) AuthenticateWebsocket() error {
|
|
return common.ErrFunctionNotSupported
|
|
}
|
|
|
|
// KlineIntervalEnabled returns if requested interval is enabled on exchange
|
|
func (e *Base) klineIntervalEnabled(in kline.Interval) bool {
|
|
return e.Features.Enabled.Kline.Intervals[in.Word()]
|
|
}
|
|
|
|
// FormatExchangeKlineInterval returns Interval to string
|
|
// Exchanges can override this if they require custom formatting
|
|
func (e *Base) FormatExchangeKlineInterval(in kline.Interval) string {
|
|
return strconv.FormatFloat(in.Duration().Seconds(), 'f', 0, 64)
|
|
}
|
|
|
|
// ValidateKline confirms that the requested pair, asset & interval are supported and/or enabled by the requested exchange
|
|
func (e *Base) ValidateKline(pair currency.Pair, a asset.Item, interval kline.Interval) error {
|
|
var errorList []string
|
|
var err kline.ErrorKline
|
|
if e.CurrencyPairs.IsAssetEnabled(a) != nil {
|
|
err.Asset = a
|
|
errorList = append(errorList, "asset not enabled")
|
|
} else if !e.CurrencyPairs.Pairs[a].Enabled.Contains(pair, true) {
|
|
err.Pair = pair
|
|
errorList = append(errorList, "pair not enabled")
|
|
}
|
|
|
|
if !e.klineIntervalEnabled(interval) {
|
|
err.Interval = interval
|
|
errorList = append(errorList, "interval not supported")
|
|
}
|
|
|
|
if len(errorList) > 0 {
|
|
err.Err = errors.New(strings.Join(errorList, ","))
|
|
return &err
|
|
}
|
|
|
|
return nil
|
|
}
|